summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:57:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:57:42 +0000
commit61f3ab8f23f4c924d455757bf3e65f8487521b5a (patch)
tree885599a36a308f422af98616bc733a0494fe149a
parentInitial commit. (diff)
downloadlib2geom-61f3ab8f23f4c924d455757bf3e65f8487521b5a.tar.xz
lib2geom-61f3ab8f23f4c924d455757bf3e65f8487521b5a.zip
Adding upstream version 1.3.upstream/1.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitignore40
-rw-r--r--.gitlab-ci.yml99
-rw-r--r--2GeomConfig.cmake3
-rw-r--r--2geom.pc.in13
-rw-r--r--AUTHORS.md26
-rw-r--r--CMakeLists.txt156
-rw-r--r--CMakeScripts/Dist.cmake7
-rw-r--r--CMakeScripts/FindCython.cmake37
-rw-r--r--CMakeScripts/FindDoubleConversion.cmake27
-rw-r--r--CMakeScripts/FindPython.cmake14
-rw-r--r--CMakeScripts/UseCython.cmake288
-rw-r--r--COPYING-LGPL-2.1510
-rw-r--r--COPYING-MPL-1.1470
-rw-r--r--Doxyfile2355
-rw-r--r--HACKING.md53
-rw-r--r--LICENSE.md16
-rw-r--r--NEWS.md45
-rw-r--r--README.md123
-rw-r--r--README.win32.md56
-rw-r--r--RELEASING.md21
-rw-r--r--TODO.md85
-rw-r--r--_clang-format53
-rw-r--r--cmake_uninstall.cmake.in23
-rw-r--r--doc/2geom-logo.svg70
-rw-r--r--doc/Makefile6
-rw-r--r--doc/bezier-utils-work.txt34
-rw-r--r--doc/boolops.svg1622
-rw-r--r--doc/degenerate_conic_decomposition.pdfbin0 -> 79341 bytes
-rw-r--r--doc/extradoxygen.css14
-rw-r--r--doc/features.txt28
-rw-r--r--doc/manual.bib55
-rw-r--r--doc/manual.tex734
-rw-r--r--doc/manual2/ack47
-rw-r--r--doc/manual2/concepts128
-rw-r--r--doc/manual2/d2106
-rw-r--r--doc/manual2/geometric primitives65
-rw-r--r--doc/manual2/introduction41
-rw-r--r--doc/manual2/piecewise134
-rw-r--r--doc/manual2/s-basis91
-rw-r--r--doc/media/2geom-logo.pngbin0 -> 15181 bytes
-rw-r--r--doc/media/Rect.svg283
-rw-r--r--doc/media/bezier-curve-evaluation.pngbin0 -> 25116 bytes
-rw-r--r--doc/media/convex.pngbin0 -> 20321 bytes
-rw-r--r--doc/media/convex.svg232
-rw-r--r--doc/media/coords.pngbin0 -> 2365 bytes
-rw-r--r--doc/media/coords.svg142
-rw-r--r--doc/media/ellipse-angular-coordinates.pngbin0 -> 23125 bytes
-rw-r--r--doc/media/ellipse-angular-coordinates.svg249
-rw-r--r--doc/media/elliptical-arc-flags.pngbin0 -> 13944 bytes
-rw-r--r--doc/media/elliptical-arc-flags.svg197
-rw-r--r--doc/media/gear.pngbin0 -> 75841 bytes
-rw-r--r--doc/media/involute.pdfbin0 -> 9087 bytes
-rw-r--r--doc/media/matrix.pngbin0 -> 49751 bytes
-rw-r--r--doc/media/matrix.svg247
-rw-r--r--doc/media/point.pngbin0 -> 6612 bytes
-rw-r--r--doc/media/point.svg156
-rw-r--r--doc/media/rect.pngbin0 -> 19800 bytes
-rw-r--r--doc/media/rotate.pngbin0 -> 40354 bytes
-rw-r--r--doc/media/rotate.svg239
-rw-r--r--doc/media/sbasis.pngbin0 -> 18753 bytes
-rw-r--r--doc/media/sbasis.svg1121
-rw-r--r--doc/media/scale.pngbin0 -> 26639 bytes
-rw-r--r--doc/media/scale.svg243
-rw-r--r--doc/media/translate.pngbin0 -> 45560 bytes
-rw-r--r--doc/media/translate.svg252
-rw-r--r--doc/patchwise.svg131
-rw-r--r--doc/s-pb-thoughts.txt78
-rw-r--r--doc/shapeops.svg653
-rw-r--r--doc/sweep.svg430
-rw-r--r--doc/tutorial.txt291
-rw-r--r--include/2geom/2geom.h75
-rw-r--r--include/2geom/affine.h244
-rw-r--r--include/2geom/angle.h408
-rw-r--r--include/2geom/basic-intersection.h151
-rw-r--r--include/2geom/bezier-curve.h366
-rw-r--r--include/2geom/bezier-to-sbasis.h94
-rw-r--r--include/2geom/bezier-utils.h99
-rw-r--r--include/2geom/bezier.h394
-rw-r--r--include/2geom/cairo-path-sink.h91
-rw-r--r--include/2geom/choose.h147
-rw-r--r--include/2geom/circle.h165
-rw-r--r--include/2geom/concepts.h209
-rw-r--r--include/2geom/conic_section_clipper.h58
-rw-r--r--include/2geom/conic_section_clipper_cr.h64
-rw-r--r--include/2geom/conic_section_clipper_impl.h346
-rw-r--r--include/2geom/conicsec.h537
-rw-r--r--include/2geom/convex-hull.h346
-rw-r--r--include/2geom/coord.h208
-rw-r--r--include/2geom/crossing.h213
-rw-r--r--include/2geom/curve.h375
-rw-r--r--include/2geom/curves.h54
-rw-r--r--include/2geom/d2.h564
-rw-r--r--include/2geom/ellipse.h260
-rw-r--r--include/2geom/elliptical-arc.h344
-rw-r--r--include/2geom/exception.h157
-rw-r--r--include/2geom/forward.h127
-rw-r--r--include/2geom/generic-interval.h374
-rw-r--r--include/2geom/generic-rect.h547
-rw-r--r--include/2geom/geom.h66
-rw-r--r--include/2geom/int-interval.h63
-rw-r--r--include/2geom/int-point.h202
-rw-r--r--include/2geom/int-rect.h75
-rw-r--r--include/2geom/intersection-graph.h259
-rw-r--r--include/2geom/intersection.h147
-rw-r--r--include/2geom/interval.h245
-rw-r--r--include/2geom/intervaltree/interval_tree.h126
-rw-r--r--include/2geom/line.h605
-rw-r--r--include/2geom/linear.h167
-rw-r--r--include/2geom/math-utils.h140
-rw-r--r--include/2geom/nearest-time.h141
-rw-r--r--include/2geom/numeric/fitting-model.h521
-rw-r--r--include/2geom/numeric/fitting-tool.h562
-rw-r--r--include/2geom/numeric/linear_system.h138
-rw-r--r--include/2geom/numeric/matrix.h603
-rw-r--r--include/2geom/numeric/symmetric-matrix-fs-operation.h102
-rw-r--r--include/2geom/numeric/symmetric-matrix-fs-trace.h427
-rw-r--r--include/2geom/numeric/symmetric-matrix-fs.h733
-rw-r--r--include/2geom/numeric/vector.h594
-rw-r--r--include/2geom/ord.h80
-rw-r--r--include/2geom/orphan-code/arc-length.h58
-rw-r--r--include/2geom/orphan-code/chebyshev.h30
-rw-r--r--include/2geom/orphan-code/intersection-by-smashing.h78
-rw-r--r--include/2geom/orphan-code/linear-of.h269
-rw-r--r--include/2geom/orphan-code/linearN.h363
-rw-r--r--include/2geom/orphan-code/redblacktree.h121
-rw-r--r--include/2geom/orphan-code/rtree.h241
-rw-r--r--include/2geom/orphan-code/sbasis-of.h638
-rw-r--r--include/2geom/orphan-code/sbasisN.h1123
-rw-r--r--include/2geom/parallelogram.h83
-rw-r--r--include/2geom/path-intersection.h118
-rw-r--r--include/2geom/path-sink.h253
-rw-r--r--include/2geom/path.h917
-rw-r--r--include/2geom/pathvector.h304
-rw-r--r--include/2geom/piecewise.h945
-rw-r--r--include/2geom/point.h449
-rw-r--r--include/2geom/polynomial.h264
-rw-r--r--include/2geom/ray.h192
-rw-r--r--include/2geom/rect.h263
-rw-r--r--include/2geom/sbasis-2d.h371
-rw-r--r--include/2geom/sbasis-curve.h160
-rw-r--r--include/2geom/sbasis-geometric.h146
-rw-r--r--include/2geom/sbasis-math.h99
-rw-r--r--include/2geom/sbasis-poly.h56
-rw-r--r--include/2geom/sbasis-to-bezier.h87
-rw-r--r--include/2geom/sbasis.h530
-rw-r--r--include/2geom/solver.h88
-rw-r--r--include/2geom/svg-path-parser.h199
-rw-r--r--include/2geom/svg-path-writer.h122
-rw-r--r--include/2geom/sweep-bounds.h62
-rw-r--r--include/2geom/sweeper.h189
-rw-r--r--include/2geom/symbolic/determinant-minor.h175
-rw-r--r--include/2geom/symbolic/implicit.h353
-rw-r--r--include/2geom/symbolic/matrix.h265
-rw-r--r--include/2geom/symbolic/multi-index.h169
-rw-r--r--include/2geom/symbolic/multipoly.h684
-rw-r--r--include/2geom/symbolic/mvpoly-tools.h690
-rw-r--r--include/2geom/symbolic/polynomial.h569
-rw-r--r--include/2geom/symbolic/unity-builder.h102
-rw-r--r--include/2geom/transforms.h370
-rw-r--r--include/2geom/utils.h114
-rw-r--r--include/toys/lpe-framework.h77
-rw-r--r--include/toys/path-cairo.h57
-rw-r--r--include/toys/toy-framework-2.h451
-rw-r--r--makefile.in17
-rw-r--r--mingwenv.bat18
-rwxr-xr-xsrc/2geom/CMakeLists.txt206
-rw-r--r--src/2geom/affine.cpp522
-rw-r--r--src/2geom/basic-intersection.cpp493
-rw-r--r--src/2geom/bezier-clipping.cpp1174
-rw-r--r--src/2geom/bezier-curve.cpp695
-rw-r--r--src/2geom/bezier-utils.cpp997
-rw-r--r--src/2geom/bezier.cpp415
-rw-r--r--src/2geom/cairo-path-sink.cpp127
-rw-r--r--src/2geom/circle.cpp337
-rw-r--r--src/2geom/concepts.cpp69
-rw-r--r--src/2geom/conic_section_clipper_impl.cpp574
-rw-r--r--src/2geom/conicsec.cpp1640
-rw-r--r--src/2geom/convex-hull.cpp746
-rw-r--r--src/2geom/coord.cpp123
-rw-r--r--src/2geom/crossing.cpp233
-rw-r--r--src/2geom/curve.cpp235
-rw-r--r--src/2geom/d2-sbasis.cpp364
-rw-r--r--src/2geom/doxygen.cpp301
-rw-r--r--src/2geom/ellipse.cpp790
-rw-r--r--src/2geom/elliptical-arc-from-sbasis.cpp341
-rw-r--r--src/2geom/elliptical-arc.cpp1045
-rw-r--r--src/2geom/geom.cpp396
-rw-r--r--src/2geom/intersection-graph.cpp535
-rw-r--r--src/2geom/intervaltree/interval_tree.cc799
-rw-r--r--src/2geom/intervaltree/test2.cc74
-rw-r--r--src/2geom/line.cpp610
-rw-r--r--src/2geom/nearest-time.cpp322
-rw-r--r--src/2geom/numeric/matrix.cpp154
-rw-r--r--src/2geom/orphan-code/arc-length.cpp292
-rw-r--r--src/2geom/orphan-code/chebyshev.cpp126
-rw-r--r--src/2geom/orphan-code/intersection-by-bezier-clipping.cpp560
-rw-r--r--src/2geom/orphan-code/intersection-by-smashing.cpp349
-rw-r--r--src/2geom/orphan-code/nearestpoint.cpp405
-rw-r--r--src/2geom/orphan-code/redblack-toy.cpp327
-rw-r--r--src/2geom/orphan-code/redblacktree.cpp575
-rw-r--r--src/2geom/orphan-code/rtree.cpp1350
-rw-r--r--src/2geom/parallelogram.cpp136
-rw-r--r--src/2geom/parting-point.cpp280
-rw-r--r--src/2geom/path-extrema.cpp156
-rw-r--r--src/2geom/path-intersection.cpp728
-rw-r--r--src/2geom/path-sink.cpp104
-rw-r--r--src/2geom/path.cpp1161
-rw-r--r--src/2geom/pathvector.cpp336
-rw-r--r--src/2geom/piecewise.cpp266
-rw-r--r--src/2geom/planar-graph.h1252
-rw-r--r--src/2geom/point.cpp274
-rw-r--r--src/2geom/polynomial.cpp337
-rw-r--r--src/2geom/rect.cpp187
-rw-r--r--src/2geom/recursive-bezier-intersection.cpp476
-rw-r--r--src/2geom/sbasis-2d.cpp202
-rw-r--r--src/2geom/sbasis-geometric.cpp790
-rw-r--r--src/2geom/sbasis-math.cpp379
-rw-r--r--src/2geom/sbasis-poly.cpp59
-rw-r--r--src/2geom/sbasis-roots.cpp656
-rw-r--r--src/2geom/sbasis-to-bezier.cpp584
-rw-r--r--src/2geom/sbasis.cpp681
-rw-r--r--src/2geom/self-intersect.cpp313
-rw-r--r--src/2geom/solve-bezier-one-d.cpp243
-rw-r--r--src/2geom/solve-bezier-parametric.cpp189
-rw-r--r--src/2geom/solve-bezier.cpp304
-rw-r--r--src/2geom/svg-path-parser.cpp1615
-rw-r--r--src/2geom/svg-path-parser.rl487
-rw-r--r--src/2geom/svg-path-writer.cpp296
-rw-r--r--src/2geom/sweep-bounds.cpp154
-rw-r--r--src/2geom/transforms.cpp205
-rw-r--r--src/2geom/utils.cpp86
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/cython/CMakeLists.txt131
-rw-r--r--src/cython/README.md29
-rw-r--r--src/cython/_common_decl.pxd14
-rw-r--r--src/cython/_common_decl.pyx12
-rw-r--r--src/cython/_cy_affine.pxd247
-rw-r--r--src/cython/_cy_affine.pyx736
-rw-r--r--src/cython/_cy_conicsection.pxd50
-rw-r--r--src/cython/_cy_conicsection.pyx183
-rw-r--r--src/cython/_cy_curves.pxd421
-rw-r--r--src/cython/_cy_curves.pyx1945
-rw-r--r--src/cython/_cy_path.pxd124
-rw-r--r--src/cython/_cy_path.pyx457
-rw-r--r--src/cython/_cy_primitives.pxd237
-rw-r--r--src/cython/_cy_primitives.pyx846
-rw-r--r--src/cython/_cy_rectangle.pxd442
-rw-r--r--src/cython/_cy_rectangle.pyx2202
-rw-r--r--src/cython/cy2geom.pyx71
-rw-r--r--src/cython/report.md237
-rw-r--r--src/cython/test-affine.py249
-rw-r--r--src/cython/test-conicsection.py137
-rw-r--r--src/cython/test-curves.py458
-rw-r--r--src/cython/test-path.py218
-rw-r--r--src/cython/test-primitives.py288
-rw-r--r--src/cython/test-rectangle.py601
-rw-r--r--src/cython/utils.py52
-rw-r--r--src/cython/wrapped-pyobject.h237
-rw-r--r--src/cython/wrapper.py360
-rw-r--r--src/performance-tests/CMakeLists.txt27
-rw-r--r--src/performance-tests/bendpath-test.cpp128
-rw-r--r--src/performance-tests/bezier-utils-test.cpp133
-rw-r--r--src/performance-tests/boolops-performance-test.cpp101
-rw-r--r--src/performance-tests/example-performance-test.cpp217
-rw-r--r--src/performance-tests/parse-svg-test.cpp88
-rw-r--r--src/performance-tests/path-operations-test.cpp100
-rw-r--r--src/py2geom/CMakeLists.txt118
-rw-r--r--src/py2geom/__init__.py26
-rw-r--r--src/py2geom/bezier.cpp89
-rw-r--r--src/py2geom/cairo-helpers.cpp164
-rw-r--r--src/py2geom/cairo-helpers.h27
-rw-r--r--src/py2geom/circle.cpp72
-rw-r--r--src/py2geom/conic.cpp176
-rw-r--r--src/py2geom/convexcover.cpp93
-rw-r--r--src/py2geom/crossing.cpp69
-rw-r--r--src/py2geom/d2.cpp99
-rw-r--r--src/py2geom/ellipse.cpp88
-rw-r--r--src/py2geom/etc.cpp66
-rw-r--r--src/py2geom/helpers.h59
-rw-r--r--src/py2geom/interval.cpp159
-rw-r--r--src/py2geom/line.cpp96
-rw-r--r--src/py2geom/linear.cpp110
-rw-r--r--src/py2geom/parser.cpp85
-rw-r--r--src/py2geom/path.cpp265
-rw-r--r--src/py2geom/point.cpp146
-rw-r--r--src/py2geom/pw.cpp228
-rw-r--r--src/py2geom/py2geom.cpp85
-rw-r--r--src/py2geom/py2geom.h71
-rw-r--r--src/py2geom/ray.cpp99
-rw-r--r--src/py2geom/rect.cpp125
-rw-r--r--src/py2geom/sbasis.cpp173
-rw-r--r--src/py2geom/transforms.cpp107
-rw-r--r--src/python/cy2geom_example.py10
-rw-r--r--src/python/elip.py155
-rw-r--r--src/python/exact-arc-length-quad-bez.py16
-rw-r--r--src/python/test_py2geom.py41
-rw-r--r--src/toys/2dsb2d.cpp128
-rw-r--r--src/toys/CMakeLists.txt172
-rw-r--r--src/toys/aa.cpp520
-rw-r--r--src/toys/arc-bez.cpp129
-rw-r--r--src/toys/arc-length-param.cpp101
-rw-r--r--src/toys/auto-cross.cpp321
-rw-r--r--src/toys/boolops-toy.cpp242
-rw-r--r--src/toys/bound-path.cpp289
-rw-r--r--src/toys/bounds-test.cpp171
-rw-r--r--src/toys/box3d.cpp153
-rw-r--r--src/toys/center-warp.cpp113
-rw-r--r--src/toys/circle-fitting.cpp164
-rw-r--r--src/toys/circle-intersect.cpp70
-rw-r--r--src/toys/circle-line-intersect.cpp67
-rw-r--r--src/toys/circle-tangent-fitting.cpp224
-rw-r--r--src/toys/collinear-normal.cpp204
-rw-r--r--src/toys/conic-3.cpp96
-rw-r--r--src/toys/conic-4.cpp129
-rw-r--r--src/toys/conic-5.cpp356
-rw-r--r--src/toys/conic-6.cpp304
-rw-r--r--src/toys/conic-section-toy.cpp813
-rw-r--r--src/toys/convole.cpp352
-rw-r--r--src/toys/curvature-curve.cpp142
-rw-r--r--src/toys/curvature-test.cpp110
-rw-r--r--src/toys/curve-curve-distance.cpp1000
-rw-r--r--src/toys/curve-curve-nearest-time.cpp609
-rw-r--r--src/toys/curve-intersection-by-bezier-clipping.cpp127
-rw-r--r--src/toys/curve-intersection-by-implicitization.cpp300
-rw-r--r--src/toys/cylinder3d.cpp253
-rw-r--r--src/toys/d2sbasis-fitting-with-np.cpp147
-rw-r--r--src/toys/d2sbasis-fitting.cpp120
-rw-r--r--src/toys/data/london-locations.csv298
-rw-r--r--src/toys/data/london.txt86
-rw-r--r--src/toys/data/nsw-centre.txt56
-rw-r--r--src/toys/data/nsw-locations.csv805
-rw-r--r--src/toys/data/nsw-london.zipbin0 -> 21209 bytes
-rw-r--r--src/toys/data/nsw.txt86
-rw-r--r--src/toys/data/parser.cpp47
-rw-r--r--src/toys/differential-constraint.cpp132
-rw-r--r--src/toys/draw-toy.cpp116
-rw-r--r--src/toys/ellipse-area-minimizer.cpp352
-rw-r--r--src/toys/ellipse-bezier-intersect-toy.cpp74
-rw-r--r--src/toys/ellipse-fitting.cpp191
-rw-r--r--src/toys/ellipse-intersect-toy.cpp158
-rw-r--r--src/toys/ellipse-line-intersect-toy.cpp67
-rw-r--r--src/toys/elliptiarc-3point-center-fitting.cpp266
-rw-r--r--src/toys/elliptiarc-curve-fitting.cpp127
-rw-r--r--src/toys/elliptical-arc-toy.cpp903
-rw-r--r--src/toys/evolute.cpp93
-rw-r--r--src/toys/filet-minion.cpp159
-rw-r--r--src/toys/find-derivative.cpp500
-rw-r--r--src/toys/gear.cpp317
-rw-r--r--src/toys/hatches.cpp386
-rw-r--r--src/toys/implicit-toy.cpp510
-rw-r--r--src/toys/ineaa.cpp655
-rw-r--r--src/toys/inner-product-clip.cpp174
-rw-r--r--src/toys/intersect-data.cpp436
-rw-r--r--src/toys/inverse-test.cpp174
-rw-r--r--src/toys/kinematic_templates.cpp365
-rw-r--r--src/toys/levelsets-test.cpp155
-rw-r--r--src/toys/line-toy.cpp916
-rw-r--r--src/toys/load-svgd.cpp76
-rw-r--r--src/toys/load-svgd.py68
-rw-r--r--src/toys/lpe-framework.cpp128
-rw-r--r--src/toys/lpe-test.cpp93
-rw-r--r--src/toys/match-curve.cpp162
-rw-r--r--src/toys/mesh-grad.cpp134
-rw-r--r--src/toys/metro.cpp922
-rw-r--r--src/toys/minsb2d-solver.cpp376
-rw-r--r--src/toys/nasty.svg74
-rw-r--r--src/toys/nearest-times.cpp258
-rw-r--r--src/toys/nearest-times2.cpp313
-rw-r--r--src/toys/normal-bundle.cpp230
-rw-r--r--src/toys/offset-toy.cpp156
-rw-r--r--src/toys/pair-intersect.cpp147
-rw-r--r--src/toys/paptest.cpp107
-rw-r--r--src/toys/parametrics.cpp229
-rw-r--r--src/toys/parser.cpp108
-rw-r--r--src/toys/path-along-path.cpp112
-rw-r--r--src/toys/path-cairo.cpp342
-rw-r--r--src/toys/path-effects.cpp140
-rw-r--r--src/toys/path-toy.py41
-rw-r--r--src/toys/pencil-2.cpp1133
-rw-r--r--src/toys/pencil.cpp374
-rw-r--r--src/toys/plane3d.cpp130
-rw-r--r--src/toys/plane3d.py78
-rw-r--r--src/toys/point-curve-nearest-time.cpp397
-rw-r--r--src/toys/portion-test.cpp105
-rw-r--r--src/toys/precise-flat.cpp86
-rw-r--r--src/toys/pw-compose-test.cpp98
-rw-r--r--src/toys/pw-funcs.cpp100
-rw-r--r--src/toys/pw-toy.cpp117
-rw-r--r--src/toys/pw-toy.py180
-rw-r--r--src/toys/pwsbhandle.cpp77
-rw-r--r--src/toys/py2geom_glue.py42
-rw-r--r--src/toys/ray_test.py20
-rw-r--r--src/toys/rdm-area.cpp479
-rw-r--r--src/toys/rect-toy.cpp395
-rw-r--r--src/toys/rect_01.cpp98
-rw-r--r--src/toys/rect_02.cpp81
-rw-r--r--src/toys/rect_03.cpp78
-rw-r--r--src/toys/root-finder-comparer.cpp247
-rw-r--r--src/toys/rtree-toy.cpp573
-rw-r--r--src/toys/sanitize.cpp204
-rw-r--r--src/toys/sb-math-test.cpp164
-rw-r--r--src/toys/sb-of-interval.cpp187
-rw-r--r--src/toys/sb-of-sb.cpp478
-rw-r--r--src/toys/sb-to-bez.cpp404
-rw-r--r--src/toys/sb-zeros.cpp63
-rw-r--r--src/toys/sb1d.cpp125
-rw-r--r--src/toys/sb2d-solver.cpp282
-rw-r--r--src/toys/sb2d.cpp83
-rw-r--r--src/toys/sbasis-fitting.cpp214
-rw-r--r--src/toys/sbasisdim.cpp262
-rw-r--r--src/toys/scribble.cpp366
-rw-r--r--src/toys/self-intersect.cpp67
-rw-r--r--src/toys/sketch-fitter.cpp923
-rw-r--r--src/toys/smash-intersector.cpp583
-rw-r--r--src/toys/squiggles.cpp216
-rw-r--r--src/toys/star-gap.hand1
-rw-r--r--src/toys/svgd/2rect.svgd1
-rw-r--r--src/toys/svgd/4rect.svgd1
-rw-r--r--src/toys/svgd/ant.svgd1
-rw-r--r--src/toys/svgd/arcs.svgd1
-rw-r--r--src/toys/svgd/banana.svgd1
-rw-r--r--src/toys/svgd/cat.svgd1
-rw-r--r--src/toys/svgd/circle.svgd1
-rw-r--r--src/toys/svgd/degenerate-line.svgd1
-rw-r--r--src/toys/svgd/diederik.svgd1
-rw-r--r--src/toys/svgd/diederik1.svgd1
-rw-r--r--src/toys/svgd/double-move.svgd1
-rw-r--r--src/toys/svgd/ellipses.svgd1
-rw-r--r--src/toys/svgd/emptyset.svgd1
-rw-r--r--src/toys/svgd/fan.svgd1
-rw-r--r--src/toys/svgd/lotsarect.svgd1
-rw-r--r--src/toys/svgd/monkey.svgd1
-rw-r--r--src/toys/svgd/nasty.svgd3
-rw-r--r--src/toys/svgd/onlyarcs.svgd10
-rw-r--r--src/toys/svgd/ptitle.svgd1
-rw-r--r--src/toys/svgd/rect.svgd1
-rw-r--r--src/toys/svgd/sanitize-examples.svgd1
-rw-r--r--src/toys/svgd/scribble.svgd1
-rw-r--r--src/toys/svgd/spiral.svgd1
-rw-r--r--src/toys/svgd/star.svg60
-rw-r--r--src/toys/svgd/star.svgd1
-rw-r--r--src/toys/svgd/tadpole.svgd1
-rw-r--r--src/toys/svgd/touchboxes.svgd1
-rw-r--r--src/toys/svgd/toy.svgd1
-rw-r--r--src/toys/svgd/triarrange.svgd1
-rw-r--r--src/toys/svgd/tricky.svgd1
-rw-r--r--src/toys/svgd/winding.svgd1
-rw-r--r--src/toys/sweep.cpp89
-rw-r--r--src/toys/sweeper-toy.cpp170
-rw-r--r--src/toys/sweeper.cpp1135
-rw-r--r--src/toys/topology.cpp668
-rw-r--r--src/toys/toy-framework-2.cpp972
-rw-r--r--src/toys/toy-template.cpp35
-rw-r--r--src/toys/toyframework.py435
-rw-r--r--src/toys/uncross.cpp500
-rw-r--r--src/toys/winding-test.cpp107
-rw-r--r--src/toys/worms.cpp138
-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
-rw-r--r--tools/lib2geom_gdb.py107
-rw-r--r--toy.pc.in13
504 files changed, 129410 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4e1ec9d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+CMakeCache.txt
+**/CMakeFiles
+**/__pycache__
+cmake_uninstall.cmake
+cmake_install.cmake
+config.h
+/Makefile
+src/**/Makefile
+.ninja*
+*.ninja
+2geom.pc
+src/toys/*
+!src/toys/*.cpp
+!src/toys/data
+!src/toys/svgd
+!src/toys/*.h
+!src/toys/*.svg
+!src/toys/*.svgd
+!src/toys/*.hand
+!src/toys/*.rb
+!src/toys/*.py
+doc/html/
+doc/latex/
+doxygen_warnings.log
+Testing
+CTestTestfile.cmake
+src/tests/*-test
+src/tests/*-test.exe
+src/performance-tests/*-test
+src/performance-tests/*-test.exe
+src/cython-bindings/cython_debug
+install_manifest.txt
+*.cxx
+*.so.*
+*.so
+*.a
+*.dll
+*.pyd
+*.tar.bz2
+*.sig
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..d9ffb8f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,99 @@
+# Dependencies are managed in the Dockerfile in the inkscape-ci-docker
+# Git repository. Recycle the one for the master Inkscape branch for now
+image: registry.gitlab.com/inkscape/inkscape-ci-docker/master
+
+variables:
+ GIT_DEPTH: "10"
+ GIT_SUBMODULE_STRATEGY: recursive
+
+# The main build of the library
+2geom:
+ stage: build
+ before_script:
+ - mkdir -p ccache
+ - export CCACHE_BASEDIR=${PWD}
+ - export CCACHE_DIR=${PWD}/ccache
+ script:
+ - mkdir -p build opt
+ - cd build
+ - cmake .. -DCMAKE_C_COMPILER_LAUNCHER=ccache
+ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
+ -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer"
+ -DCMAKE_BUILD_TYPE=Debug
+ -DCMAKE_INSTALL_PREFIX=../opt
+ -D2GEOM_BOOST_PYTHON=ON
+ - make -j3
+ - make -j3 perf
+ - make py2geom
+ - make install
+ cache:
+ paths:
+ - ccache/
+ artifacts:
+ expire_in: 1 day
+ paths:
+ - build # needed by unit tests
+ - opt # needed by find-package
+
+# Build an executable including lib2geom as a subproject
+2geom:as-subproject:
+ stage: build
+ before_script:
+ - mkdir -p ccache
+ - export CCACHE_BASEDIR="${PWD}"
+ - export CCACHE_DIR="${PWD}/ccache"
+ script:
+ - mkdir -p opt2
+ - cd tests/dependent-project
+ - mkdir -p build-as-subproject
+ - cd build-as-subproject
+ - cmake .. -D2GEOM_AS_SUBPROJECT=ON
+ -DCMAKE_INSTALL_PREFIX=../../../opt2
+ - make -j2 main
+ - ./main
+ - make install
+ cache:
+ paths:
+ - ccache/
+
+# Build an executable linking against lib2geom found via cmake
+2geom:find-package:
+ stage: test
+ dependencies:
+ - 2geom
+ needs: ["2geom"]
+ before_script:
+ - mkdir -p ccache
+ - export CCACHE_BASEDIR="${PWD}"
+ - export CCACHE_DIR="${PWD}/ccache"
+ script:
+ - mkdir -p opt3
+ - cd tests/dependent-project
+ - mkdir -p build-with-find-package
+ - cd build-with-find-package
+ - cmake .. -D2GEOM_AS_SUBPROJECT=OFF
+ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
+ -DCMAKE_INSTALL_PREFIX=../../../opt3
+ -D2Geom_DIR="$PWD/../../../opt/lib/cmake/2Geom"
+ - make -j2 main
+ - ./main
+ - make install
+ cache:
+ paths:
+ - ccache/
+
+# Run tests
+2geom:tests:
+ stage: test
+ dependencies:
+ - 2geom
+ needs: ["2geom"]
+ script:
+ - cd build
+ - make -j3 test
+ artifacts:
+ expire_in: 1 month
+ paths:
+ - build/Testing
+ expose_as: 'Test logs'
+ when: on_failure
diff --git a/2GeomConfig.cmake b/2GeomConfig.cmake
new file mode 100644
index 0000000..926f751
--- /dev/null
+++ b/2GeomConfig.cmake
@@ -0,0 +1,3 @@
+include(CMakeFindDependencyMacro)
+find_dependency(Boost 1.40 REQUIRED)
+include("${CMAKE_CURRENT_LIST_DIR}/2GeomTargets.cmake") \ No newline at end of file
diff --git a/2geom.pc.in b/2geom.pc.in
new file mode 100644
index 0000000..4039851
--- /dev/null
+++ b/2geom.pc.in
@@ -0,0 +1,13 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+
+Name: 2geom
+Description: A 2D geometry framework
+Version: @2GEOM_VERSION@
+
+Requires:
+Libs: -L${libdir} -l2geom
+Cflags: -I${includedir}/2geom-@2GEOM_VERSION@
+
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 0000000..2a54918
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,26 @@
+# Authors
+
+ - Maximilian Albert
+ - Joshua Andler
+ - Tavmjong Bah
+ - Jean-François Barraud
+ - Vincent Barrielle
+ - Olaf Bjarnason
+ - Joshua L. Blocher
+ - Marco Cecchetti
+ - Kris De Gussem
+ - Tim G. Dwyer
+ - Markus Engel
+ - Johan B. C. Engelen
+ - Paul F. Harrison
+ - Nathan J. Hurst
+ - Vangelis Katsikaros
+ - Krzysztof Kosiński
+ - Diederik van Lierop
+ - Mentalguy
+ - Jelle R. Moulder
+ - Alvin Penner
+ - Jan Pulmann
+ - Michael G. Sloan
+ - Aaron Spike
+ - Michael Wybrow
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..43fb4cd
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,156 @@
+cmake_minimum_required(VERSION 3.12)
+cmake_policy(SET CMP0054 NEW)
+
+
+set(2GEOM_MAJOR_VERSION 1)
+set(2GEOM_MINOR_VERSION 3)
+set(2GEOM_PATCH_VERSION 0)
+set(2GEOM_VERSION ${2GEOM_MAJOR_VERSION}.${2GEOM_MINOR_VERSION}.${2GEOM_PATCH_VERSION} CACHE INTERNAL "" FORCE)
+set(2GEOM_ABI_VERSION ${2GEOM_MAJOR_VERSION}.${2GEOM_MINOR_VERSION}.0)
+
+project(lib2geom
+ VERSION
+ ${2GEOM_VERSION}
+ LANGUAGES
+ CXX
+ C # C is required by CHECK_SYMBOL_EXISTS
+ )
+
+set(2GEOM_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "")
+include_directories("${CMAKE_CURRENT_LIST_DIR}/src/2geom") # for private headers/template support.
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF) # enforces -std=c++17 instead of -std=gnu++17
+set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib)
+set(CMAKE_SKIP_RPATH:BOOL OFF)
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeScripts)
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+
+include(GNUInstallDirs)
+include(CheckCXXSourceCompiles)
+
+# Find dependencies
+find_package(Boost 1.60 REQUIRED)
+find_package(DoubleConversion REQUIRED)
+find_package(PkgConfig REQUIRED)
+find_package(Cython)
+find_package(Threads)
+pkg_check_modules(GTK3 gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED glib-2.0)
+pkg_check_modules(CAIRO cairo)
+pkg_check_modules(GSL REQUIRED gsl)
+
+if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ set(2GEOM_STANDALONE TRUE)
+endif()
+
+option(2GEOM_TESTING "Build tests" "${2GEOM_STANDALONE}")
+
+if (2GEOM_TESTING)
+ enable_testing()
+ add_subdirectory(tests)
+endif()
+
+
+check_cxx_source_compiles("#include <math.h>\nint main() { double a=0.5,b=0.5,c=0.5; sincos(a, &b, &c); return 0; }" HAVE_SINCOS)
+if(HAVE_SINCOS)
+ add_definitions(-DHAVE_SINCOS)
+endif()
+if(GSL_FOUND)
+ add_definitions(-DHAVE_GSL)
+endif()
+if(CAIRO_FOUND)
+ add_definitions(-DHAVE_CAIRO)
+endif()
+if(PYCAIRO_FOUND)
+ add_definitions(-DHAVE_PYCAIRO)
+endif()
+
+
+# SET(NEEDS_GSL
+# differential-constraint
+# root-finder-comparer
+# # contour
+# sb-to-bez
+# )
+
+add_compile_options(-Wall -Wformat-security -Woverloaded-virtual -Wpointer-arith -Werror=return-type)
+add_compile_options(-O2)
+
+# suppress the very annoying "#pragma ms_struct" Clang warning, caused by -mms-bitfield required for GTK
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ if(WIN32)
+ add_compile_options(-Wno-ignored-attributes)
+ endif()
+ add_compile_options(-Wno-unused-local-typedef)
+endif()
+
+option(WITH_PROFILING
+ "Build lib2geom with profiling enabled"
+ OFF)
+
+if(WITH_PROFILING)
+ add_compile_options(-pg)
+ set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg")
+endif()
+
+option(2GEOM_BUILD_SHARED
+ "Build lib2geom and libtoy as shared libraries."
+ OFF)
+
+if(2GEOM_BUILD_SHARED)
+ set(LIB_TYPE SHARED)
+else()
+ set(LIB_TYPE STATIC)
+endif()
+
+option(2GEOM_USE_GPL_CODE
+ "Build lib2geom with GPL licensed Code."
+ ON)
+
+if(2GEOM_USE_GPL_CODE)
+ add_definitions(-DGPL_TAINT)
+endif()
+
+if(2GEOM_STANDALONE)
+ include(CMakeScripts/Dist.cmake)
+endif()
+
+# make unistall target
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+ IMMEDIATE @ONLY)
+
+add_custom_target(uninstall_${PROJECT_NAME}
+ "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+
+add_subdirectory(src)
+
+install(EXPORT 2geom_targets
+ FILE 2GeomTargets.cmake
+ NAMESPACE 2Geom::
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/2Geom"
+ COMPONENT "lib2geom_dev"
+ )
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file("2GeomConfigVersion.cmake"
+ VERSION ${PROJECT_VERSION}
+ COMPATIBILITY SameMajorVersion)
+
+install(FILES "2GeomConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/2GeomConfigVersion.cmake"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/2Geom"
+ COMPONENT "lib2geom_dev"
+ )
+
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/2geom"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/2geom-${2GEOM_VERSION}"
+ COMPONENT "lib2geom_dev")
+
+configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/2geom.pc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/2geom.pc @ONLY IMMEDIATE )
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/2geom.pc"
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
+ COMPONENT "lib2geom_dev")
diff --git a/CMakeScripts/Dist.cmake b/CMakeScripts/Dist.cmake
new file mode 100644
index 0000000..8de9b62
--- /dev/null
+++ b/CMakeScripts/Dist.cmake
@@ -0,0 +1,7 @@
+#make dist target
+SET(2GEOM_DIST_PREFIX "${PROJECT_NAME}-${2GEOM_VERSION}")
+ADD_CUSTOM_TARGET(dist
+ COMMAND git config tar.bz2.command bzip2
+ COMMAND git archive --prefix=${2GEOM_DIST_PREFIX}/ -o ${CMAKE_BINARY_DIR}/${2GEOM_DIST_PREFIX}.tar.bz2 HEAD
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ VERBATIM)
diff --git a/CMakeScripts/FindCython.cmake b/CMakeScripts/FindCython.cmake
new file mode 100644
index 0000000..6d06de9
--- /dev/null
+++ b/CMakeScripts/FindCython.cmake
@@ -0,0 +1,37 @@
+# Find the Cython compiler.
+#
+# This code sets the following variables:
+#
+# CYTHON_EXECUTABLE
+#
+# See also UseCython.cmake
+
+#=============================================================================
+# Copyright 2011 Kitware, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#=============================================================================
+
+find_program( CYTHON_EXECUTABLE NAMES cython cython.py
+ PATHS $ENV{PYTHON_PATH}\\Lib\\site-packages ${PYTHON_PATH}\\Lib\\site-packages)
+
+include( FindPackageHandleStandardArgs )
+FIND_PACKAGE_HANDLE_STANDARD_ARGS( Cython REQUIRED_VARS CYTHON_EXECUTABLE )
+
+mark_as_advanced( CYTHON_EXECUTABLE )
+
+IF (CYTHON_FOUND)
+ MESSAGE(STATUS "cython: FOUND")
+ELSE(CYTHON_FOUND)
+ MESSAGE(STATUS "cython: NOT FOUND")
+ENDIF (CYTHON_FOUND)
diff --git a/CMakeScripts/FindDoubleConversion.cmake b/CMakeScripts/FindDoubleConversion.cmake
new file mode 100644
index 0000000..bd4c002
--- /dev/null
+++ b/CMakeScripts/FindDoubleConversion.cmake
@@ -0,0 +1,27 @@
+# - Try to find double-conversion
+# Once done, this will define
+#
+# DoubleConversion_FOUND - system has double-conversion
+# DoubleConversion_INCLUDE_DIRS - the double-conversion include directories
+# DoubleConversion_LIBRARIES - link these to use double-conversion
+
+include(FindPackageHandleStandardArgs)
+
+find_library(DoubleConversion_LIBRARY double-conversion
+ PATHS ${DoubleConversion_LIBRARYDIR})
+
+find_path(DoubleConversion_INCLUDE_DIR double-conversion/double-conversion.h
+ PATHS ${DoubleConversion_INCLUDEDIR})
+
+find_package_handle_standard_args(DoubleConversion DEFAULT_MSG
+ DoubleConversion_LIBRARY
+ DoubleConversion_INCLUDE_DIR)
+
+mark_as_advanced(
+ DoubleConversion_LIBRARY
+ DoubleConversion_INCLUDE_DIR)
+
+if(DoubleConversion_FOUND)
+ set(DoubleConversion_LIBRARIES ${DoubleConversion_LIBRARY})
+ set(DoubleConversion_INCLUDE_DIRS ${DoubleConversion_INCLUDE_DIR})
+endif()
diff --git a/CMakeScripts/FindPython.cmake b/CMakeScripts/FindPython.cmake
new file mode 100644
index 0000000..3f05b58
--- /dev/null
+++ b/CMakeScripts/FindPython.cmake
@@ -0,0 +1,14 @@
+# figure out Python flags
+#TODO - rewrite to use ALLCAPS?
+FIND_PACKAGE(PythonInterp)
+IF(PYTHONINTERP_FOUND)
+ EXEC_PROGRAM(${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/makelib/python_config_var.py LIBS
+ OUTPUT_VARIABLE PYTHON_LINK_LIBRARIES )
+ EXEC_PROGRAM(${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/makelib/python_config_var.py LINKFORSTATIC
+ OUTPUT_VARIABLE PYTHON_LINKFORSTATIC )
+ELSE(PYTHONINTERP_FOUND)
+ SET(PYTHON_LINK_LIBRARIES "")
+ SET(PYTHON_LINKFORSTATIC "")
+ENDIF(PYTHONINTERP_FOUND)
diff --git a/CMakeScripts/UseCython.cmake b/CMakeScripts/UseCython.cmake
new file mode 100644
index 0000000..38c3155
--- /dev/null
+++ b/CMakeScripts/UseCython.cmake
@@ -0,0 +1,288 @@
+# Define a function to create Cython modules.
+#
+# For more information on the Cython project, see http://cython.org/.
+# "Cython is a language that makes writing C extensions for the Python language
+# as easy as Python itself."
+#
+# This file defines a CMake function to build a Cython Python module.
+# To use it, first include this file.
+#
+# include( UseCython )
+#
+# Then call cython_add_module to create a module.
+#
+# cython_add_module( <module_name> <src1> <src2> ... <srcN> )
+#
+# To create a standalone executable, the function
+#
+# cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
+#
+# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
+# to a static library. If a MAIN_MODULE source is specified,
+# the "if __name__ == '__main__':" from that module is used as the C main() method
+# for the executable. If MAIN_MODULE, the source with the same basename as
+# <executable_name> is assumed to be the MAIN_MODULE.
+#
+# Where <module_name> is the name of the resulting Python module and
+# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
+# *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can
+# be used for target_link_libraries(), etc.
+#
+# The sample paths set with the CMake include_directories() command will be used
+# for include directories to search for *.pxd when running the Cython complire.
+#
+# Cache variables that effect the behavior include:
+#
+# CYTHON_ANNOTATE
+# CYTHON_NO_DOCSTRINGS
+# CYTHON_FLAGS
+#
+# Source file properties that effect the build process are
+#
+# CYTHON_IS_CXX
+#
+# If this is set of a *.pyx file with CMake set_source_files_properties()
+# command, the file will be compiled as a C++ file.
+#
+# See also FindCython.cmake
+
+#=============================================================================
+# Copyright 2011 Kitware, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#=============================================================================
+
+# Configuration options.
+
+#TODO - rewrite to use ALLCAPS?
+set( CYTHON_ANNOTATE OFF
+ CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
+set( CYTHON_NO_DOCSTRINGS OFF
+ CACHE BOOL "Strip docstrings from the compiled module." )
+set( CYTHON_FLAGS "" CACHE STRING
+ "Extra flags to the cython compiler." )
+mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )
+
+find_package( Cython REQUIRED )
+find_package( PythonLibs REQUIRED )
+
+set( CYTHON_CXX_EXTENSION "cxx" )
+set( CYTHON_C_EXTENSION "c" )
+
+# Create a *.c or *.cxx file from a *.pyx file.
+# Input the generated file basename. The generate file will put into the variable
+# placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
+function( compile_pyx _name generated_file )
+ # Default to assuming all files are C.
+ set( cxx_arg "" )
+ set( extension ${CYTHON_C_EXTENSION} )
+ set( pyx_lang "C" )
+ set( comment "Compiling Cython C source for ${_name}..." )
+
+ set( cython_include_directories "" )
+ set( pxd_dependencies "" )
+ set( c_header_dependencies "" )
+ set( pyx_locations "" )
+
+ foreach( pyx_file ${ARGN} )
+ get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
+
+ # Determine if it is a C or C++ file.
+ get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
+ if( ${property_is_cxx} )
+ set( cxx_arg "--cplus" )
+ set( extension ${CYTHON_CXX_EXTENSION} )
+ set( pyx_lang "CXX" )
+ set( comment "Compiling Cython CXX source for ${_name}..." )
+ endif()
+
+ # Get the include directories.
+ get_source_file_property( pyx_location ${pyx_file} LOCATION )
+ get_filename_component( pyx_path ${pyx_location} PATH )
+ get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES )
+ list( APPEND cython_include_directories ${cmake_include_directories} )
+ list( APPEND pyx_locations "${pyx_location}" )
+
+ # Determine dependencies.
+ # Add the pxd file will the same name as the given pyx file.
+ unset( corresponding_pxd_file CACHE )
+ find_file( corresponding_pxd_file ${pyx_file_basename}.pxd
+ PATHS "${pyx_path}" ${cmake_include_directories}
+ NO_DEFAULT_PATH )
+ if( corresponding_pxd_file )
+ list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
+ endif()
+
+ # pxd files to check for additional dependencies.
+ set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" )
+ set( pxds_checked "" )
+ set( number_pxds_to_check 1 )
+ while( ${number_pxds_to_check} GREATER 0 )
+ foreach( pxd ${pxds_to_check} )
+ list( APPEND pxds_checked "${pxd}" )
+ list( REMOVE_ITEM pxds_to_check "${pxd}" )
+
+ # check for C header dependencies
+ file( STRINGS "${pxd}" extern_from_statements
+ REGEX "cdef[ ]+extern[ ]+from.*$" )
+ foreach( statement ${extern_from_statements} )
+ # Had trouble getting the quote in the regex
+ string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" )
+ unset( header_location CACHE )
+ find_file( header_location ${header} PATHS ${cmake_include_directories} )
+ if( header_location )
+ list( FIND c_header_dependencies "${header_location}" header_idx )
+ if( ${header_idx} LESS 0 )
+ list( APPEND c_header_dependencies "${header_location}" )
+ endif()
+ endif()
+ endforeach()
+
+ set( module_dependencies "" )
+
+ # Look for cimport and include statements.
+ file( STRINGS "${pxd}" cimport_statements REGEX "(cimport|include)" )
+ foreach( statement ${cimport_statements} )
+ if( ${statement} MATCHES from )
+ string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1.pxd" module "${statement}" )
+ elseif( ${statement} MATCHES include )
+ string( REGEX REPLACE "include[ ]+[\"]([^\"]+)[\"].*" "\\1" module "${statement}" )
+ else()
+ string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1.pxd" module "${statement}" )
+ endif()
+ list( APPEND module_dependencies ${module} )
+ endforeach()
+ list( REMOVE_DUPLICATES module_dependencies )
+ # Add the module to the files to check, if appropriate.
+ foreach( module ${module_dependencies} )
+ unset( pxd_location CACHE )
+ find_file( pxd_location ${module}
+ PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
+ if( pxd_location )
+ list( FIND pxds_checked ${pxd_location} pxd_idx )
+ if( ${pxd_idx} LESS 0 )
+ list( FIND pxds_to_check ${pxd_location} pxd_idx )
+ if( ${pxd_idx} LESS 0 )
+ list( APPEND pxds_to_check ${pxd_location} )
+ list( APPEND pxd_dependencies ${pxd_location} )
+ endif() # if it is not already going to be checked
+ endif() # if it has not already been checked
+ else()
+ message("${module} ignored")
+ endif() # if pxd file can be found
+ endforeach() # for each module dependency discovered
+ endforeach() # for each pxd file to check
+ list( LENGTH pxds_to_check number_pxds_to_check )
+ endwhile()
+ endforeach() # pyx_file
+
+ # Set additional flags.
+ if( CYTHON_ANNOTATE )
+ set( annotate_arg "--annotate" )
+ endif()
+
+ if( CYTHON_NO_DOCSTRINGS )
+ set( no_docstrings_arg "--no-docstrings" )
+ endif()
+
+ if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
+ "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
+ set( cython_debug_arg "--gdb" )
+ endif()
+
+ # Include directory arguments.
+ list( REMOVE_DUPLICATES cython_include_directories )
+ set( include_directory_arg "" )
+ foreach( _include_dir ${cython_include_directories} )
+ set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" )
+ endforeach()
+
+ # Determining generated file name.
+ set( _generated_file "${_name}.${extension}" )
+ set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
+ set( ${generated_file} ${_generated_file} PARENT_SCOPE )
+
+ list( REMOVE_DUPLICATES pxd_dependencies )
+ list( REMOVE_DUPLICATES c_header_dependencies )
+
+ # Add the command to run the compiler.
+ add_custom_command( OUTPUT ${_generated_file}
+ COMMAND ${CYTHON_EXECUTABLE}
+ ARGS ${cxx_arg} ${include_directory_arg}
+ ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS}
+ --output-file ${_generated_file} ${pyx_locations}
+ DEPENDS ${pyx_locations} ${pxd_dependencies}
+ IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
+ COMMENT ${comment}
+ )
+
+ # Remove their visibility to the user.
+ set( corresponding_pxd_file "" CACHE INTERNAL "" )
+ set( header_location "" CACHE INTERNAL "" )
+ set( pxd_location "" CACHE INTERNAL "" )
+endfunction()
+
+# cython_add_module( <name> src1 src2 ... srcN )
+# Build the Cython Python module.
+function( cython_add_module _name )
+ set( pyx_module_sources "" )
+ set( other_module_sources "" )
+ foreach( _file ${ARGN} )
+ if( ${_file} MATCHES ".*\\.py[x]?$" )
+ list( APPEND pyx_module_sources ${_file} )
+ else()
+ list( APPEND other_module_sources ${_file} )
+ endif()
+ endforeach()
+ compile_pyx( ${_name} generated_file ${pyx_module_sources} )
+ include_directories( ${PYTHON_INCLUDE_DIRS} )
+ python_add_module( ${_name} ${generated_file} ${other_module_sources} )
+ target_link_libraries( ${_name} ${PYTHON_LIBRARIES} )
+endfunction()
+
+include( CMakeParseArguments )
+# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
+# Creates a standalone executable the given sources.
+function( cython_add_standalone_executable _name )
+ set( pyx_module_sources "" )
+ set( other_module_sources "" )
+ set( main_module "" )
+ cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
+ include_directories( ${PYTHON_INCLUDE_DIRS} )
+ foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
+ if( ${_file} MATCHES ".*\\.py[x]?$" )
+ get_filename_component( _file_we ${_file} NAME_WE )
+ if( "${_file_we}" STREQUAL "${_name}" )
+ set( main_module "${_file}" )
+ elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
+ set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
+ compile_pyx( "${_file_we}_static" generated_file "${_file}" )
+ list( APPEND pyx_module_sources "${generated_file}" )
+ endif()
+ else()
+ list( APPEND other_module_sources ${_file} )
+ endif()
+ endforeach()
+
+ if( cython_arguments_MAIN_MODULE )
+ set( main_module ${cython_arguments_MAIN_MODULE} )
+ endif()
+ if( NOT main_module )
+ message( FATAL_ERROR "main module not found." )
+ endif()
+ get_filename_component( main_module_we "${main_module}" NAME_WE )
+ set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
+ compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
+ add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
+ target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} )
+endfunction()
diff --git a/COPYING-LGPL-2.1 b/COPYING-LGPL-2.1
new file mode 100644
index 0000000..b124cf5
--- /dev/null
+++ b/COPYING-LGPL-2.1
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/COPYING-MPL-1.1 b/COPYING-MPL-1.1
new file mode 100644
index 0000000..7714141
--- /dev/null
+++ b/COPYING-MPL-1.1
@@ -0,0 +1,470 @@
+ MOZILLA PUBLIC LICENSE
+ Version 1.1
+
+ ---------------
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``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/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
+
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..5fa12d1
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,2355 @@
+# Doxyfile 1.8.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = lib2geom
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+#ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = YES
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = NO
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE = doxygen_warnings.log
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = src/2geom
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS = *.cpp \
+ *.h \
+ *.rl \
+ *.py
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE = src/2geom/toys/* \
+ src/2geom/tests/*
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = YES
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = src/2geom/toys
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH = doc/media
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# compiled with the --with-libclang option.
+# The default value is: NO.
+
+#CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+#CLANG_OPTIONS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra stylesheet files is of importance (e.g. the last
+# stylesheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET = doc/extradoxygen.css
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES = amsmath \
+ amssymb
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty string,
+# for the replacement values of the other commands the user is referred to
+# HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+#MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+#DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+# DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+#PLANTUML_JAR_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/HACKING.md b/HACKING.md
new file mode 100644
index 0000000..198b14a
--- /dev/null
+++ b/HACKING.md
@@ -0,0 +1,53 @@
+# Mailing list
+Communication about this project occurs on the lib2geom
+[mailing list](https://lists.sourceforge.net/lists/listinfo/lib2geom-devel).
+
+
+# Help Wanted
+We greatly appreciate contributions. You don't need to be a `math-whiz` or
+`über-hacker` (though these are definitely appreciated :) ) to help. The tasks
+of code cleanup, consistency, testing, documentation, and toys mostly just
+require perseverance, and benefit the project greatly.
+
+As far as very specialized skill, we are always in need of mathy people, even if
+it is just for their insight on problems and techniques (as opposed to coding).
+
+# Coding Style
+Please refer to the
+[Coding Style Guidelines](http://www.inkscape.org/doc/coding_style.php)
+if you have specific questions on the style to use for code. If reading style
+guidelines doesn't interest you, just follow the general style of the
+surrounding code, so that it is at least internally consistent.
+
+# Compiling
+For Windows instructions, see [README.win32.md](README.win32.md)
+
+For Debian-like platforms, the following packages are required:
+ - cairo v1.1.7 or later (Debian package libcairo2-dev)
+ - cmake
+ - make
+ - libboost-dev
+ - libgsl0-dev (though eventually it will only be required in tests)
+ - refblas3* on dapper
+
+To compile, use
+```bash
+cmake .
+Make
+```
+
+If you have problems, just ask on the mailing list.
+
+# Running tests
+For Debian-like platforms, after compiling lib2geom, issue this command line
+```bash
+make test
+```
+
+# Adding a unit test
+Make sure you write it using GTest syntax - look at e.g.
+[src/tests/affine-test.cpp](src/tests/affine-test.cpp).
+
+To add the test to the build, add the test to
+[src/tests/CMakeLists.txt](src/tests/CMakeLists.txt) in under
+`SET(2GEOM_GTESTS_SRC)`.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..3e725ab
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,16 @@
+lib2geom is free software.
+
+Every source file in the implementation of lib2geom is available to be
+redistributed and/or modified under the terms of either the GNU Lesser
+General Public License (LGPL) version 2.1 or the Mozilla Public
+License (MPL) version 1.1. Some files may be available under more
+liberal terms, but we believe that in all cases, each file may be used
+under either the LGPL or the MPL.
+
+See the following files in this directory for the precise terms and
+conditions of either license:
+ - [COPYING-LGPL-2.1](COPYING-LGPL-2.1)
+ - [COPYING-MPL-1.1](COPYING-MPL-1.1)
+
+Please see each file in the implementation for Copyright and licensing
+information.
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..4d3ccb9
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,45 @@
+lib2geom v1.1.0
+===============
+
+2Geom v1.1 is not ABI compatible with v1.0, it switches from
+`boost::optional` to `std::optional`.
+
+Changes:
+
+- Add `Geom::Parallelogram`
+- Add `Geom::PathIteratorSink::inPath()`
+- Add `Geom::are_near_rel()` for `Geom::Point`
+- Move headers to `include` directory
+- Make build system git submodule friendly
+- Fix Python 3 support (py2geom)
+- Remove Python 2 support (py2geom)
+
+
+lib2geom v1.0.0
+===============
+
+2geom is a C++ library of mathematics for paths, curves, and other
+geometric calculations, designed to be well suited for vector graphics:
+Bézier curves, conics, paths, intersections, transformations, and basic
+geometries.
+
+Originally developed to restructure and improve path data structures in
+Inkscape, this library's codebase has been maintained and shipped as
+part of the professional vector graphics software for over a decade.
+
+The major contributors to the 2geom library are Nathan Hurst, Michael
+G. Sloan, Krzysztof Kosiński, Johan B. C. Engelen, MenTaLguY, Aaron
+Spike, Marco Cechetti and JF Barraud.
+
+Work on this release has focused on updating the 2geom source control,
+build, test and packaging systems for both Linux and Windows. The py2geom
+python extension package has been restored and improvements have been
+made to overall code stabilization and quality.
+
+The primary motivation for 2geom's 1.0 release is to support a future
+Inkscape 1.0 launch.
+
+With this evolution to a distinct package, the 2geom team is seeking
+new opportunities to collaborate with individuals and projects
+interested in using this proven tool.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a4444e6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,123 @@
+# 2Geom: easy 2D graphics library
+
+## What is this?
+
+2Geom is a C++ 2D geometry library geared towards robust processing of
+computational geometry data associated with vector graphics. The primary
+design consideration is ease of use and clarity. It is dual-licensed
+under LGPL 2.1 and MPL 1.1.
+
+The library is descended from a set of geometric routines present in
+Inkscape, a vector graphics editor based around the Scalable Vector
+Graphics format, the most widespread vector graphics interchange format
+on the Web and a W3C Recommendation. Due to this legacy, not all parts
+of the API form a coherent whole (yet).
+
+Rendering is outside the scope of this library, and it is assumed
+something like libcairo or similar is employed for this. 2geom
+concentrates on higher level algorithms and geometric computations.
+
+
+## Features List
+
+* C++ 17
+* Functional programming style.
+* Points
+* Efficient affine transformations
+* Rectangles
+* Convex Hulls
+* Bounded error
+* General purpose paths:
+ + Exact elliptical arcs
+ + Area
+ + Centroid and bending moments
+* Path Locations:
+ + Determination of special spots (e.g. maximum curvature)
+ + Splitting
+ + Point, tangent, curvature at location
+ + Efficient arc length and inverse arc length
+* Path algebra:
+ + Computations such as offset curves can be written with their mathematical definition
+ and still get a bounded error, efficient curve. (preliminary trials indicate offset
+ done this way out performs the method used in Inkscape)
+* Arbitrary distortion (with bounded error):
+ + Mesh distorts
+ + Computational distorts such as the GIMP's 'vortex' plugin
+ + 3d mapping (perspective, flag, sphere)
+* Exact boolean ops (elliptic arcs remain elliptic arcs)
+* Efficient 2d database
+* Implicit function plotting
+* NURBs input and output
+* Tunable path simplification
+* PDoF constraint system for CAD/CAGD
+
+
+## Dependencies
+
+To build 2Geom, you will need:
+
+* C++ 17
+* [Boost](http://www.boost.org/) (headers only)
+* [glib](https://wiki.gnome.org/Projects/GLib)
+* [GNU Scientific Library](http://www.gnu.org/software/gsl/)
+* [double-conversion](https://github.com/google/double-conversion)
+* [cairo](https://www.cairographics.org/)
+* [Ragel](http://www.colm.net/open-source/ragel/) (if you want to modify the SVG path parser)
+* [GTK+ 2](http://www.gtk.org/) (for demo programs)
+
+
+## Building
+
+2Geom uses CMake as the build and configuration system. To build, type:
+
+ mkdir build && cd build
+ cmake ..
+ make
+
+To run tests and performance tests:
+
+ make test
+ make perf
+
+**Note**: Tests are disabled by default. To enable tests pass `-D2GEOM_TESTING=ON` to `cmake` command.
+
+Also check out some of the interactive programs in src/toys.
+
+Documentation is generated from source comments using Doxygen.
+Run `doxygen` in the project root to generate documentation in
+`doc/html`.
+
+
+## API / ABI Stability
+
+Version 1.0 of 2Geom marks its first official release. With this
+release the library's API/ABI is considered stable:
+
+ * All public APIs will not be renamed or have their parameters changed
+ without providing backwards-compatible aliases.
+
+ * New functionality added to these APIs will not change their meaning
+ or fundamental behaviors.
+
+ * If an API needs to be removed or replaced, it will be declared
+ deprecated but will remain in the API until the next major version.
+ Warnings will be issued when the deprecated method is called.
+
+ * We'll only break backwards compatibility of these APIs if a bug or
+ security hole makes it completely unavoidable.
+
+Improvements that would break the API/ABI will be noted in our bug
+tracker, for a future release.
+
+2Geom does not follow Semantic Versioning. Instead, version numbers
+match major Inkscape release versions.
+
+
+## Further information
+
+Communications related to 2Geom development happen on a
+[SourceForge mailing list](https://lists.sourceforge.net/lists/listinfo/lib2geom-devel).
+
+The primary user of 2Geom is [Inkscape](https://inkscape.org/en/).
+API-breaking changes to 2Geom will require corresponding changes to
+Inkscape.
diff --git a/README.win32.md b/README.win32.md
new file mode 100644
index 0000000..fc7a770
--- /dev/null
+++ b/README.win32.md
@@ -0,0 +1,56 @@
+# win32 instructions
+## Installation
+- Download the latest mingw, gsl, and gtk bundles from [http://inkscape.modevia.com/win32libs/?C=M;O=D](http://inkscape.modevia.com/win32libs/?C=M;O=D)
+- Also download Boost from SourceForge and unpack it in a directory of your choice (you specify it's path later on)
+- Obtain, compile and install Inkscape svn following these instructions http://inkscape.org/win32/win32buildnotes.html
+- unpackage the gsl bundle into the directory with the gtk bundle
+- download cmake; install at `C:\cmake`
+- check the directories in mingwenv.bat. Are they set correctly for you? (ignore RAGEL_BIN)
+- make a build directory
+- open cmd.exe
+ ```
+ mingwenv.bat
+ cmake -G "MinGW Makefiles" c:\path\to\2geom # be sure you have a CAPITAL '-G' there!
+ cmake -i # especially mind to set the install dir to where you want it installed (probably the gtk folder, e.g. c:/gtk210))
+ mingw32-make
+ ```
+- copy the resulting `.exe` files into the inkscape install directory
+
+If you want to be able to compile the ragel defined svg-parser as well,
+you must download ragel from [here](http://www.cs.queensu.ca/~thurston/ragel/)
+Unpack the windows binary package into a dir of your own choice.
+Check whether ragel's dir is set correctly in mingwenv.bat (RAGEL_BIN).
+Make sure you have ragel.exe and rlcodegen.exe in the dir you specify.
+If your package does not contain rlcodegen.exe you can copy rlgen-cd.exe
+and rename it to rlcodegen.exe :-)
+
+## PYTHON BINDINGS
+To install python bindings, you need python2 and cython(>=0.16). Get cython from
+[http://www.lfd.uci.edu/~gohlke/pythonlibs/#cython](http://www.lfd.uci.edu/~gohlke/pythonlibs/#cython)
+and install it to `$PYTHON_PATH\Libs\site-packages`
+32-bit version is your safest bet.
+cmake should be able to find cython now. Make sure that `PYTHON_PATH` is set
+correctly to python2 in mingwenv.bat.
+
+Continue with installation as described above. When running "cmake -i",
+remember to choose to build cython bindings and also lib2geom as a
+shared library!
+
+If you get errors about missing DLL file, try copying lib2geom.dll to
+cython-bindings directory.
+
+Note: If you have both python2 and python3 installed, make sure cmake uses
+python2. To ensure this, choose "advanced options" when running "cmake -i",
+scroll and set PYTHON_INCLUDE_DIR and PYTHON_LIBRARY accordingly.
+
+## TROUBLESHOOTING
+If things don't work out, here is a list of things you can try.
+Type `make --version`. It should display:
+```
+E:\inkscapelpe>mingw32-make --version
+GNU Make 3.80
+Copyright (C) 2002 Free Software Foundation, Inc.
+```
+Or something very similar. If it does not, probably windows finds a different
+make (Borland's or another?). Type `set path=`, then start again with
+`mingwenv.bat` etc...
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..33a9b24
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,21 @@
+# REALISING
+Add a release note to `NEWS.md`.
+
+Set `2GEOM_MAJOR_VERSION`, `2GEOM_MINOR_VERSION`, and `2GEOM_PATCH_VERSION` in
+*CMakeLists.txt*.
+
+```bash
+cmake .
+make
+make test
+make dist
+```
+
+Next, copy `lib2geom-${VERSION}.tar.bz2` to `/tmp/``, extract it, and verify
+it builds.
+
+```bash
+gpg --armor --detach-sign --output lib2geom-${VERSION}.tar.bz2.sig lib2geom-${VERSION}.tar.bz2
+git tag -s ${MAJOR}.${MINOR}
+git push --tags
+```
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..697a044
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,85 @@
+# Code Health:
+ - Rewrite old tests using Google Test Framework.
+ - Add more unit tests. Ideally, every method should be tested.
+ - More Doxygen documentation. Document algorithms used to implement each
+ nontrivial method, preferably with pictures.
+ - Coding style.
+ - Rewrite or remove old junk.
+
+# Primitives:
+ - Add uniform points - Geom::UPoint.
+ - Add projective transformations - Geom::Projective.
+ - Add rational Bezier fragments / curves - Geom::D2U<Bezier>
+
+# Shapes:
+ - Geom::Triangle
+ - Geom::Quad
+ - Geom::Polygon
+ - Function that computes an Affine that maps between triangles.
+ - Function that maps between Rects.
+ - Function that maps between Ellipses (no skew).
+
+# Path / PathVector:
+ - Built in support for arc length parametrization, computed on demand.
+ - Boolean operations accessible via operators: &, |, / (or -), ^
+ - Uncrossing.
+ - Conversion from nonzero winding rule to even-odd winding rule.
+ - Dashing - requires arc length parametrization.
+ - Offset.
+ - Stroke-to-path.
+ - Minkowski sum.
+ - Functions to facilitate node editing without conversion to a node list.
+ - Approximation of arbitrary paths with cubic Beziers, quadratic Beziers
+ or line segments, available as a path sink.
+ - Approximation by lines and circular / elliptical arcs.
+ - Support for per-node and per-segment user data?
+
+# Fragments:
+ - Convert all uses of std::vector<Point> to D2<Bezier> where applicable.
+ - Consider adding push_back() for incremental building of Beziers.
+ - Implement Bezier versions of SBasis methods.
+
+# Toys:
+ - Better Bezier / SBasis handles.
+ - Use GTK 3 to provide widgets such as checkboxes and buttons (?)
+
+# Other:
+ - sweeper.h: add variants of Sweeper that accept void Item
+ and add a version that does sweepline over two lists rather
+ than one.
+ - Rewrite conic section code to match coding style.
+ - Rewrite QuadTree and RTree to make them actually usable.
+ - BSP tree.
+ - Interval tree - Geom::IntervalSet, Geom::IntervalMap<T>
+ - Geom::MultiInterval
+ - Using the above, add regions, as seen in Cairo.
+ - Add some basic 3D functionality, enough to draw a 3D box.
+ - Write GDB pretty printers for all core classes. See the directory "tools"
+ - Clothoid support (clothoids where the curvature is a not-necessarily linear
+ function, perhaps piecewise SBasis, add fitting)
+ - Special geometric shapes (stuff like circles, oriented rectangles, spirals,
+ triangles, regular polygons, inkscape-like polygons, etc)
+ - Constraint system for advanced handle stuff
+ - complete and tidy up intersection apis
+ - complete offset implementation: at this stage only the hard part has been
+ implemented
+ - conic sections: we currently support line and ellipse from xAx form, add
+ parabola and hyperbola; intersection
+ - NURBS (2geom handles division approximation of polynomials, code just needs
+ to be added to convert between standard forms (say x/w, y/w in bernstein and
+ pw<d2<sb>>(t))
+ - T-mesh form for 2D (and higher?) piecewise polynomials
+ - enhance contour tracing algorithm (sb-2d-solver)
+ - transform an svg stroke in an outline (extends offset curve code to handle
+ SVG options)
+ - variable width curve tracing (variant on path along path idea)
+ - collinear tangent (find a line that smoothly connects two curves); extension
+ of bezier clipping idea
+ - transform a path into a single curve (fitting) - improvements required
+ - exploiting modern architecture features (cache coherence, multi-core, packet
+ instruction set, gpgpu)
+ - finish boolops (use faster and more robust sweep-window algorithm, add
+ support for dB @ dB, dB @ B, B @ B where @ is any of the 16 boolean operators
+ that make sense)
+ - bucket fill (variant of bool ops for bucket filling)
+ - path topology and numerical soundness graph
diff --git a/_clang-format b/_clang-format
new file mode 100644
index 0000000..ce33080
--- /dev/null
+++ b/_clang-format
@@ -0,0 +1,53 @@
+---
+# SPDX-License-Identifier: GPL-2.0-or-later
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+ConstructorInitializerIndentWidth: 4
+AlignEscapedNewlinesLeft: false
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: true
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AlwaysBreakTemplateDeclarations: true
+AlwaysBreakBeforeMultilineStrings: false
+BreakBeforeBinaryOperators: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: true
+BinPackParameters: true
+ColumnLimit: 120
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+DerivePointerBinding: false
+ExperimentalAutoDetectBinPacking: false
+IndentCaseLabels: true
+MaxEmptyLinesToKeep: 3
+KeepEmptyLinesAtTheStartOfBlocks: true
+NamespaceIndentation: None
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 60
+PenaltyBreakString: 1000
+PenaltyBreakFirstLessLess: 120
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerBindsToType: false
+SpacesBeforeTrailingComments: 1
+Cpp11BracedListStyle: false
+Standard: Cpp11
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
+BreakBeforeBraces: WebKit
+IndentFunctionDeclarationAfterType: false
+SpacesInParentheses: false
+SpacesInAngles: false
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: true
+SpaceAfterControlStatementKeyword: true
+ContinuationIndentWidth: 4
+SpaceBeforeAssignmentOperators: true
+...
+
diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in
new file mode 100644
index 0000000..cd2001a
--- /dev/null
+++ b/cmake_uninstall.cmake.in
@@ -0,0 +1,23 @@
+IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+ MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
+ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+
+FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
+STRING(REGEX REPLACE "\n" ";" files "${files}")
+FOREACH(file ${files})
+ MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
+ IF(EXISTS "$ENV{DESTDIR}${file}")
+ EXEC_PROGRAM(
+ "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+ OUTPUT_VARIABLE rm_out
+ RETURN_VALUE rm_retval
+ )
+ IF("${rm_retval}" STREQUAL 0)
+ ELSE("${rm_retval}" STREQUAL 0)
+ MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
+ ENDIF("${rm_retval}" STREQUAL 0)
+ ELSE(EXISTS "$ENV{DESTDIR}${file}")
+ MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
+ ENDIF(EXISTS "$ENV{DESTDIR}${file}")
+ENDFOREACH(file)
+
diff --git a/doc/2geom-logo.svg b/doc/2geom-logo.svg
new file mode 100644
index 0000000..91a6dff
--- /dev/null
+++ b/doc/2geom-logo.svg
@@ -0,0 +1,70 @@
+<?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="113.27319mm"
+ height="113.59513mm"
+ viewBox="0 0 113.27319 113.59513"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.1 r15371"
+ sodipodi:docname="2geom-logo.svg"
+ inkscape:export-filename="/home/tweenkpl/2geom-logo.png"
+ inkscape:export-xdpi="111.80057"
+ inkscape:export-ydpi="111.80057">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="166.69098"
+ inkscape:cy="240.82631"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:object-paths="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1722"
+ inkscape:window-height="1358"
+ inkscape:window-x="639"
+ inkscape:window-y="321"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5">
+ <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"
+ transform="translate(-28.785322,-45.762063)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.45454025;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 107.19276,55.400456 c -15.230149,0 -24.140387,9.013472 -24.140387,24.554437 0,0.621638 2.2e-4,1.45063 0.103836,2.590299 h 13.883015 v -2.383058 c 0,-8.184908 3.833366,-12.950644 10.464176,-12.950644 6.4236,0 10.36078,4.454975 10.36078,11.811032 0,8.081301 -2.58994,11.293214 -18.752545,22.793528 -12.432769,8.49573 -16.266387,15.02298 -16.991633,28.59542 h 37.554848 c -1.05021,4.0163 -4.60797,6.85313 -9.02421,6.85313 H 60.194912 c -5.284123,0 -9.344303,-4.0602 -9.344303,-9.34431 V 77.464994 c 0,-5.284125 4.060178,-9.342587 9.344303,-9.342587 h 22.70085 c 1.164171,-3.007917 2.822357,-5.632208 4.960058,-7.80351 1.913386,-1.943461 4.188188,-3.497369 6.76988,-4.651129 H 60.194912 c -11.968506,0 -21.798514,9.82872 -21.798514,21.797226 v 50.455296 c 0,11.96851 9.830003,21.79851 21.798514,21.79851 h 50.455728 c 11.96851,0 21.79679,-9.83 21.79679,-21.79851 V 117.4604 h -0.38916 -12.06548 -19.01641 c 1.86492,-3.93705 4.14415,-6.00893 14.91922,-13.88301 12.74359,-9.324584 16.47374,-14.919299 16.47374,-24.865515 0,-14.194083 -9.73922,-23.311419 -25.17658,-23.311419 z"
+ id="rect3680"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..c8ab4a1
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,6 @@
+all: manual.pdf
+
+manual.pdf: manual.tex
+ pdflatex manual.tex
+ bibtex manual
+ pdflatex manual.tex
diff --git a/doc/bezier-utils-work.txt b/doc/bezier-utils-work.txt
new file mode 100644
index 0000000..8604c12
--- /dev/null
+++ b/doc/bezier-utils-work.txt
@@ -0,0 +1,34 @@
+min .5 * sum_i lensq(bez_pt(b, u[i]) - d[i])
+
+lensq(d)=dot(d, d) = d.x * d.x + d.y * d.y
+
+sum_i (f(i) + g(i)) = sum_i f(i) + sum_i g(i), so
+we can separate into x,y parts. Since they are the same, we write `z' in the below
+to mean either x or y.
+
+.5 * sum_i (bez_pt(b, u[i]) - d[i]).z^2
+
+= .5 * sum_i (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B2(u[i]) * b[2] +
+ B3(u[i]) * b[3]
+ - d[i] ).z^2
+
+= H.
+
+Suppose that b[0,1,3] are fixed (with b[1] perhaps being calculated
+from a prior call to existing generate_bezier).
+
+d H / d b[2].z = sum_i B2(u[i]) * (bez_pt(b, u[i]) - d[i]).z
+
+Solve for dH/db[2].z==0:
+
+-sum_i B2(u[i]) B2(u[i]) * b[2].z = sum_i B2(u[i]) * (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B3(u[i]) * b[3]
+ - d[i] ).z
+b[2].z = ((sum_i B2(u[i]) * (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B3(u[i]) * b[3]
+ - d[i] ).z)
+ / -sum_i (B2(u[i]))^2)
diff --git a/doc/boolops.svg b/doc/boolops.svg
new file mode 100644
index 0000000..093e03d
--- /dev/null
+++ b/doc/boolops.svg
@@ -0,0 +1,1622 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45+devel"
+ sodipodi:docname="boolops.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Dot_m"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Dot_m"
+ style="overflow:visible">
+ <path
+ id="path13539"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;marker-end:none"
+ transform="scale(0.4) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path5066"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path5063"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path5069"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path3301"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lstart"
+ style="overflow:visible">
+ <path
+ id="path3298"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.8) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3304"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.7071068"
+ inkscape:cx="244.82275"
+ inkscape:cy="325.44392"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="787"
+ inkscape:window-height="1020"
+ inkscape:window-x="0"
+ inkscape:window-y="0">
+ <sodipodi:guide
+ orientation="vertical"
+ position="0"
+ id="guide2519" />
+ <sodipodi:guide
+ orientation="vertical"
+ position="745.29055"
+ id="guide2576" />
+ </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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="opacity:0.5070422;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -1144.3715,-909.29614 C -1130.7209,-908.95152 -1113.1883,-906.68446 -1101.5143,-895.01043 C -1092.3992,-885.89531 -1090.0857,-871.31959 -1090.0857,-857.86757 C -1090.0857,-841.37471 -1094.9013,-835.74816 -1098.6572,-820.72472 C -1101.8376,-808.00312 -1109.561,-796.05986 -1115.8,-783.58186 C -1120.9667,-773.24862 -1118.6572,-752.60685 -1118.6572,-737.86757 C -1118.6572,-718.79529 -1107.396,-706.6622 -1098.6572,-695.01043 C -1081.2138,-671.75256 -1051.4077,-665.67141 -1030.0857,-655.01043 C -980.56756,-630.25133 -908.25189,-608.68486 -850.08576,-589.29615 C -816.67987,-578.16085 -794.4911,-580.07024 -761.51433,-563.58186 C -744.30839,-554.97889 -730.76848,-550.8954 -712.9429,-546.439 C -689.48675,-540.57497 -678.27784,-533.29735 -661.51433,-520.72472 C -651.27578,-513.0458 -648.2704,-502.11376 -638.65719,-489.29615 C -628.79688,-476.14908 -639.1503,-457.37446 -647.22862,-449.29615 C -660.19555,-436.32921 -689.09109,-423.58186 -707.22862,-423.58186 C -730.23567,-423.58186 -747.34884,-420.20138 -770.08576,-429.29615 C -817.68916,-448.33751 -860.87437,-478.60664 -910.08576,-495.01043 C -943.48116,-506.14223 -974.84358,-524.21338 -1001.5143,-546.439 C -1037.7259,-576.61531 -1076.5889,-604.37076 -1104.3715,-632.15329 C -1112.8389,-640.62075 -1119.5019,-665.27125 -1124.3715,-675.01043 C -1130.8513,-687.97013 -1134.9167,-697.19129 -1138.6572,-712.15329 C -1142.8203,-728.80565 -1148.2285,-741.8673 -1152.9429,-760.72472 C -1156.5643,-775.21039 -1163.4566,-788.49393 -1167.2286,-803.58186 C -1170.3434,-816.04089 -1167.2286,-833.52054 -1167.2286,-846.439 C -1167.2286,-861.44208 -1175.315,-864.55186 -1167.2286,-880.72471 C -1159.9567,-895.26852 -1160.7884,-896.16257 -1144.3715,-909.29614 z"
+ id="path2987"
+ sodipodi:nodetypes="cssssssssssssssssssssssssc" />
+ <path
+ style="opacity:0.50568183;fill:#0000ff;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -799.91455,-681.43839 C -887.51109,-684.38249 -965.32915,-744.70237 -1012.0466,-814.77852 C -1038.3927,-854.29768 -983.70547,-970.38465 -951.43743,-994.58567 C -855.64892,-1066.4271 -765.09423,-1097.3837 -648.39167,-1039.0324 C -579.60284,-1004.638 -506.18771,-985.96939 -454.44238,-923.875 C -416.40934,-878.23535 -469.68766,-766.16722 -496.86879,-729.92571 C -548.70738,-660.80759 -618.25596,-598.60588 -711.02113,-598.60588 C -737.82948,-598.60588 -838.11897,-599.3314 -828.19883,-639.01198 C -825.17618,-651.10255 -815.62052,-655.63089 -810.01608,-661.23533 C -805.73975,-665.51166 -802.81768,-675.63214 -799.91455,-681.43839 z"
+ id="path2227" />
+ <path
+ style="opacity:0.50568183;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -672.63532,-422.83933 C -673.30876,-423.51276 -673.98219,-424.1862 -674.65563,-424.85963 C -678.92567,-429.12967 -708.38695,-517.35852 -711.02112,-527.89519 C -714.39367,-541.3854 -711.02112,-560.32707 -711.02112,-574.36221 C -711.02112,-585.19036 -693.37737,-605.96843 -686.77746,-614.76831 C -677.92264,-626.57474 -670.9646,-640.6827 -662.5338,-649.1135 C -654.50114,-657.14616 -641.38773,-664.50154 -630.20892,-667.29624 C -619.95785,-669.85901 -607.73876,-671.39856 -599.90434,-673.35716 C -590.05857,-675.8186 -579.73699,-673.34823 -571.62007,-675.37747 C -560.52166,-678.15207 -548.66015,-679.41808 -537.27488,-679.41808 C -529.26369,-679.41808 -515.01085,-681.95363 -508.99061,-683.45869 C -502.09255,-685.1832 -494.56272,-692.69294 -488.78756,-695.58052 C -481.41977,-699.26441 -476.18116,-704.63088 -472.62512,-711.74296 C -469.19797,-718.59725 -473.11806,-735.93806 -474.64542,-742.04753 C -477.11091,-751.90948 -484.23165,-759.71498 -488.78756,-764.27089 C -493.69612,-769.17945 -496.96676,-782.84555 -498.88909,-790.53486 C -501.80768,-802.20925 -504.0379,-812.366 -500.90939,-824.88004 C -497.34889,-839.12203 -479.38936,-843.04037 -468.58451,-851.14401 C -458.91681,-858.39478 -446.07286,-858.79222 -436.25963,-861.24553 C -424.5508,-864.17274 -416.44575,-863.46042 -407.97536,-859.22523 C -403.29877,-856.88694 -400.69319,-841.84153 -395.85353,-837.00187 C -391.6837,-832.83205 -385.37678,-817.31825 -383.7317,-810.73791 C -380.57044,-798.09287 -376.38375,-784.0997 -373.63017,-770.3318 C -371.10634,-757.71264 -380.57159,-744.32713 -385.752,-733.96631 C -391.88886,-721.6926 -400.6153,-710.95021 -405.95505,-697.60082 C -407.07357,-694.80453 -408.64879,-692.21334 -409.99566,-689.5196 C -417.6298,-674.25132 -412.39478,-665.23387 -403.93475,-651.1338 C -390.47687,-628.70401 -354.82679,-598.76211 -335.24437,-582.44343 C -325.74155,-574.52441 -323.73745,-560.65938 -321.10224,-550.11855 C -320.57124,-547.99456 -335.58282,-525.19799 -337.26468,-521.83428 C -350.61508,-495.13347 -366.16504,-464.64965 -387.77231,-443.04238 C -422.13187,-408.68282 -470.95732,-366.18266 -519.09214,-354.14891 C -532.84253,-350.71131 -544.7411,-350.10831 -559.49824,-350.10831 C -578.46057,-350.10831 -593.91201,-348.53321 -610.00587,-358.18957 C -620.56665,-364.52603 -625.34158,-376.73916 -630.20892,-386.47384 C -634.24368,-394.54336 -648.94253,-398.73339 -656.47288,-400.61597 C -669.0554,-403.7616 -680.82347,-406.46305 -672.63532,-422.83933 z"
+ id="path2808" />
+ <path
+ id="path2810"
+ d="M -1157.5085,-885.48919 C -1158.182,-886.16262 -1158.8554,-886.83606 -1159.5288,-887.50949 C -1163.7989,-891.77953 -1193.2602,-980.00838 -1195.8943,-990.54505 C -1199.2669,-1004.0353 -1195.8943,-1022.9769 -1195.8943,-1037.0121 C -1195.8943,-1047.8402 -1178.2506,-1068.6183 -1171.6507,-1077.4182 C -1162.7958,-1089.2246 -1155.8378,-1103.3326 -1147.407,-1111.7634 C -1139.3743,-1119.796 -1126.2609,-1127.1514 -1115.0821,-1129.9461 C -1104.8311,-1132.5089 -1092.612,-1134.0484 -1084.7776,-1136.007 C -1074.9318,-1138.4685 -1064.6102,-1135.9981 -1056.4933,-1138.0273 C -1045.3949,-1140.8019 -1033.5334,-1142.0679 -1022.1481,-1142.0679 C -1014.1369,-1142.0679 -999.88407,-1144.6035 -993.86383,-1146.1086 C -986.96577,-1147.8331 -979.43594,-1155.3428 -973.66078,-1158.2304 C -966.29299,-1161.9143 -961.05438,-1167.2807 -957.49834,-1174.3928 C -954.07119,-1181.2471 -957.99128,-1198.5879 -959.51864,-1204.6974 C -961.98413,-1214.5593 -969.10487,-1222.3649 -973.66078,-1226.9208 C -978.56934,-1231.8293 -981.83998,-1245.4954 -983.76231,-1253.1847 C -986.6809,-1264.8591 -988.91112,-1275.0159 -985.78261,-1287.5299 C -982.22211,-1301.7719 -964.26258,-1305.6902 -953.45773,-1313.7939 C -943.79003,-1321.0446 -930.94608,-1321.4421 -921.13285,-1323.8954 C -909.42401,-1326.8226 -901.31896,-1326.1103 -892.84857,-1321.8751 C -888.17198,-1319.5368 -885.5664,-1304.4914 -880.72674,-1299.6517 C -876.55691,-1295.4819 -870.24999,-1279.9681 -868.60491,-1273.3878 C -865.44365,-1260.7427 -861.25696,-1246.7496 -858.50338,-1232.9817 C -855.97955,-1220.3625 -865.4448,-1206.977 -870.62521,-1196.6162 C -876.76207,-1184.3425 -885.48851,-1173.6001 -890.82826,-1160.2507 C -891.94678,-1157.4544 -893.522,-1154.8632 -894.86887,-1152.1695 C -902.50301,-1136.9012 -897.26799,-1127.8837 -888.80796,-1113.7837 C -875.35008,-1091.3539 -839.7,-1061.412 -820.11758,-1045.0933 C -810.61476,-1037.1743 -808.61066,-1023.3092 -805.97545,-1012.7684 C -805.44445,-1010.6444 -820.45603,-987.84785 -822.13789,-984.48414 C -835.48829,-957.78333 -984.37839,-981.84775 -1005.9857,-960.24048 C -1040.3452,-925.88092 -955.83054,-828.83252 -1003.9654,-816.79877 C -1017.7157,-813.36117 -1029.6143,-812.75817 -1044.3715,-812.75817 C -1063.3338,-812.75817 -1078.7852,-811.18307 -1094.8791,-820.83943 C -1105.4399,-827.17589 -1110.2148,-839.38902 -1115.0821,-849.1237 C -1119.1169,-857.19322 -1133.8157,-861.38325 -1141.3461,-863.26583 C -1153.9286,-866.41146 -1165.6967,-869.11291 -1157.5085,-885.48919 z"
+ style="opacity:0.50568183;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="csssssssssssssssssssssssssssssssssssc" />
+ <g
+ id="g2816"
+ transform="translate(-1113.6536,-1178.6859)">
+ <path
+ id="path2812"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2814" />
+ </g>
+ <g
+ transform="translate(-1252.4629,-1087.3029)"
+ id="g2820">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2822" />
+ <path
+ id="path2824"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2826"
+ transform="translate(-1290.1344,-934.47404)">
+ <path
+ id="path2828"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2830" />
+ </g>
+ <g
+ transform="translate(-976.86456,-717.58711)"
+ id="g2832">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2834" />
+ <path
+ id="path2836"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2838"
+ transform="translate(-817.28148,-798.99105)">
+ <path
+ id="path2840"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2842" />
+ </g>
+ <g
+ transform="translate(-758.91671,-873.3957)"
+ id="g2844">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2846" />
+ <path
+ id="path2848"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(-719.93919,-978.7982)"
+ id="g2850">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2852" />
+ <path
+ id="path2854"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2981"
+ transform="translate(-1285.32,-1020.1601)">
+ <path
+ id="path2983"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2985" />
+ </g>
+ <path
+ style="fill:#ff0000;fill-opacity:0.50588229;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M -921.13286,-295.5601 C -921.8063,-296.2335 -922.47973,-296.907 -923.15317,-297.5804 C -984.29068,-358.71794 -1016.0872,-423.42411 -1016.0872,-509.71245 C -1016.0872,-597.3637 -983.4151,-664.39202 -931.23439,-733.96631 C -865.35525,-821.80516 -815.84832,-922.08336 -737.2851,-1000.6466 C -693.29002,-1044.6417 -647.52402,-1120.4273 -628.18862,-1178.4334 C -613.76267,-1221.7113 -573.21021,-1251.8097 -549.39673,-1287.5299 C -536.11145,-1307.4578 -516.82915,-1316.0569 -500.9094,-1331.9766 C -488.49296,-1344.3931 -494.06725,-1302.7767 -496.86879,-1291.5705 C -501.74793,-1272.054 -496.97324,-1248.6219 -500.9094,-1228.9411 C -508.41847,-1191.3957 -530.38151,-1145.7532 -547.37642,-1111.7634 C -561.26485,-1083.9865 -583.92755,-1060.8845 -597.88405,-1032.9715 C -608.19334,-1012.3529 -622.12385,-996.61369 -632.22923,-976.40292 C -639.55197,-961.75745 -642.61989,-946.38567 -648.39168,-931.95621 C -652.90572,-920.6711 -658.98037,-908.75852 -664.55412,-897.61103 C -667.07062,-892.57801 -716.39482,-897.61103 -725.16327,-897.61103 C -734.0549,-897.61103 -748.276,-880.5592 -755.46784,-873.36737 C -764.57241,-864.26279 -767.9049,-857.45805 -775.67089,-847.1034 C -783.07496,-837.23132 -788.25189,-819.9211 -793.85364,-808.7176 C -803.04235,-790.34018 -811.09243,-768.04671 -816.077,-748.10845 C -821.96008,-724.57613 -824.46405,-700.41812 -830.21913,-677.39777 C -833.85459,-662.85593 -836.28005,-649.01243 -836.28005,-632.95106 C -836.28005,-598.82269 -856.2672,-555.02276 -864.56432,-521.83428 C -868.77151,-505.00551 -872.64554,-480.9603 -872.64554,-463.24543 C -872.64554,-452.00743 -874.66584,-438.87968 -874.66584,-426.87994 C -874.66584,-412.06437 -874.66584,-397.2488 -874.66584,-382.43323 C -874.66584,-367.88549 -881.26848,-345.9212 -884.76737,-331.9256 C -889.19493,-314.2153 -902.41425,-300.6724 -909.01103,-287.4789 C -914.1625,-277.1759 -926.05581,-283.2527 -921.13286,-295.5601 z"
+ id="path2989" />
+ <g
+ transform="translate(-988.39466,-665.52836)"
+ id="g2280">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2282" />
+ <path
+ id="path2284"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2286"
+ transform="translate(-956.06978,-550.37097)">
+ <path
+ id="path2288"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2290" />
+ </g>
+ <g
+ transform="translate(-1147.9988,-594.81768)"
+ id="g2292">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2294" />
+ <path
+ id="path2296"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2298"
+ transform="translate(-1121.9988,-706.81768)">
+ <path
+ id="path2300"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2302" />
+ </g>
+ <g
+ transform="translate(-1267.9988,-752.81768)"
+ id="g2304">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2306" />
+ <path
+ id="path2308"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2310"
+ transform="translate(-1289.9988,-672.81768)">
+ <path
+ id="path2312"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2314" />
+ </g>
+ <g
+ transform="translate(-1375.9988,-940.81768)"
+ id="g2316">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2318" />
+ <path
+ id="path2320"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2322"
+ transform="translate(-1439.9988,-1012.8177)">
+ <path
+ id="path2324"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2326" />
+ </g>
+ <g
+ transform="translate(-969.99876,-1174.8177)"
+ id="g2328">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2330" />
+ <path
+ id="path2332"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2334"
+ transform="translate(-879.99876,-1140.8177)">
+ <path
+ id="path2336"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2338" />
+ </g>
+ <g
+ transform="translate(-1105.9988,-804.81768)"
+ id="g2340">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2342" />
+ <path
+ id="path2344"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2346"
+ transform="translate(-1211.9988,-846.81768)">
+ <path
+ id="path2348"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2350" />
+ </g>
+ <path
+ style="opacity:0.50568183;fill:#0000ff;fill-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 215.90706,-1069.5786 C 150.58786,-1069.8445 91.365364,-1040.602 30.000864,-994.57855 C -2.2673361,-970.37753 -56.970236,-854.28523 -30.624136,-814.76605 C 16.093264,-744.6899 93.904164,-684.39763 181.50086,-681.45355 C 178.59766,-675.6473 165.68336,-592.66743 161.40706,-588.39105 C 155.80266,-582.78661 -84.509136,-475.38784 144.65706,-463.2973 C 185.50206,-461.14236 245.03616,-540.04733 271.84456,-540.0473 C 364.60966,-540.0473 432.72466,-660.80417 484.56326,-729.9223 C 511.74446,-766.16381 565.03386,-878.22015 527.00086,-923.8598 C 475.25556,-985.95419 408.07096,-1034.3404 339.28206,-1068.7348 C 297.34206,-1089.7048 252.54956,-1069.4294 215.90706,-1069.5786 z M 346.25086,-1022.9223 C 360.30246,-1022.9223 426.37576,-958.11727 426.37576,-947.57855 C 426.37586,-937.03983 360.30246,-984.7348 346.25086,-984.7348 C 332.19916,-984.73481 292.65706,-1030.7898 292.65706,-1041.3286 C 292.65706,-1051.8673 332.19916,-1022.9223 346.25086,-1022.9223 z"
+ id="path2352"
+ sodipodi:nodetypes="csscssssssccsssc" />
+ <path
+ style="fill:#ff0000;fill-opacity:0.50588229;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 484.37586,-1334.2661 C 483.34546,-1334.2376 482.08416,-1333.5369 480.53206,-1331.9848 C 464.61236,-1316.0651 445.31736,-1307.444 432.03206,-1287.5161 C 408.21856,-1251.7958 367.67676,-1221.7002 353.25086,-1178.4223 C 333.91536,-1120.4161 288.15206,-1044.6361 244.15706,-1000.6411 C 165.59386,-922.07783 116.06746,-821.7924 50.188264,-733.95355 C -1.9924361,-664.37926 -34.655436,-597.35479 -34.655436,-509.70355 C -34.655436,-423.41521 -70.087736,-479.73419 58.282064,-297.5785 C 58.830664,-296.8 59.608764,-296.2207 60.282064,-295.5473 C 55.359164,-283.2399 67.255564,-277.1818 72.407064,-287.4848 C 79.003864,-300.6783 92.229464,-314.212 96.657064,-331.9223 C 100.15596,-345.9179 106.75076,-367.87456 106.75086,-382.4223 C 106.75086,-397.23787 106.75076,-412.07548 106.75086,-426.89105 C 106.75086,-438.89079 108.78206,-451.99681 108.78206,-463.2348 C 108.78206,-480.94967 112.66866,-504.99977 116.87586,-521.82855 C 125.17296,-555.01703 145.15706,-598.82519 145.15706,-632.95355 C 145.15706,-649.01492 147.58406,-662.84922 151.21956,-677.39105 C 156.97466,-700.4114 159.46146,-724.57747 165.34456,-748.1098 C 170.32916,-768.04806 178.37466,-790.32614 187.56326,-808.70355 C 193.16506,-819.90705 198.34676,-837.23773 205.75086,-847.1098 C 213.51686,-857.46445 216.86496,-864.25522 225.96956,-873.3598 C 233.16136,-880.55163 247.35926,-897.60981 256.25086,-897.6098 C 265.01936,-897.6098 314.35926,-892.57679 316.87586,-897.6098 C 322.44946,-908.75729 328.51806,-920.66844 333.03206,-931.95355 C 338.80386,-946.38301 341.86546,-961.74558 349.18826,-976.39105 C 359.29366,-996.60182 373.22276,-1012.3662 383.53206,-1032.9848 C 397.48856,-1060.8978 420.17486,-1083.9892 434.06326,-1111.7661 C 451.05826,-1145.7559 473.02296,-1191.4082 480.53206,-1228.9536 C 484.46826,-1248.6344 479.68416,-1272.062 484.56326,-1291.5786 C 487.01466,-1301.384 491.58856,-1334.4653 484.37586,-1334.2661 z M 331.28206,-1066.0473 C 343.77236,-1066.0473 353.90706,-1054.6302 353.90706,-1040.5786 C 353.90706,-1026.5269 343.77236,-1015.1411 331.28206,-1015.1411 C 318.79176,-1015.1411 308.65706,-1026.5269 308.65706,-1040.5786 C 308.65706,-1054.6302 318.79176,-1066.0473 331.28206,-1066.0473 z"
+ id="path2354"
+ sodipodi:nodetypes="cssssssscssssssssssssssssssssccsssc" />
+ <g
+ transform="translate(99.144064,-1138.8177)"
+ id="g2356">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2358" />
+ <path
+ id="path2360"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2362"
+ transform="translate(-124.57024,-804.81768)">
+ <path
+ id="path2364"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2366" />
+ </g>
+ <g
+ transform="translate(-230.57019,-846.81768)"
+ id="g2368">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2370" />
+ <path
+ id="path2372"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2374"
+ transform="translate(12.858364,-1176.532)">
+ <path
+ id="path2376"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2378" />
+ </g>
+ <g
+ id="g2380"
+ transform="translate(-143.42734,-693.96054)">
+ <path
+ id="path2382"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ id="path2384" />
+ </g>
+ <g
+ transform="translate(-169.14163,-583.96054)"
+ id="g2386">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 264.65997,106.8594 L 289.91378,132.11321"
+ id="path2388" />
+ <path
+ id="path2390"
+ d="M 289.91378,106.8594 L 264.65997,132.11321"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ style="opacity:0.5070422;fill:#ff0000;fill-opacity:0.50588229;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 1135.7757,-973.9516 C 929.63638,-973.9516 709.30528,-890.79941 709.30528,-712.1047 C 709.30528,-533.40998 839.03848,-428.12946 1045.1777,-428.12946 C 1212.0524,-428.12945 1364.5424,-450.26799 1412.3705,-581.27925 C 1417.5407,-595.44179 1370.7938,-753.65554 1373.69,-768.58369 C 1377.0977,-786.14812 1467.26,-631.83353 1467.26,-650.23286 C 1467.26,-828.92758 1341.9152,-973.9516 1135.7757,-973.9516 z M 1162.3053,-962.886 C 1206.2166,-962.8859 1241.8677,-943.0936 1241.8677,-918.69845 C 1241.8677,-894.30327 1206.2165,-874.51095 1162.3053,-874.51095 C 1118.3939,-874.51096 1082.7427,-894.30327 1082.7427,-918.69845 C 1082.7428,-943.0936 1118.3939,-962.886 1162.3053,-962.886 z M 1216.4303,-772.8547 C 1299.9838,-772.85468 1367.8053,-741.16824 1367.8053,-702.13595 C 1367.8053,-663.10366 1299.9837,-631.44845 1216.4303,-631.44845 C 1132.8767,-631.44847 1065.0865,-663.10366 1065.0865,-702.13595 C 1065.0864,-741.16824 1132.8766,-772.8547 1216.4303,-772.8547 z M 1182.1803,-565.13595 C 1243.1682,-565.13593 1292.6803,-539.89606 1292.6803,-508.7922 C 1292.6803,-477.68834 1243.1682,-452.44845 1182.1803,-452.44845 C 1121.1923,-452.44847 1071.7115,-477.68834 1071.7115,-508.7922 C 1071.7114,-539.89606 1121.1923,-565.13595 1182.1803,-565.13595 z"
+ id="path3727"
+ sodipodi:nodetypes="csssssccsssccsssccsssc" />
+ <path
+ style="opacity:0.5070422;fill:#000080;fill-opacity:0.50588229;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 1129.1506,-1000.4648 L 1129.1506,-319.87102 L 1732.4006,-319.87102 L 1732.4006,-1000.4648 L 1129.1506,-1000.4648 z M 1187.7131,-783.90227 C 1196.8613,-783.90225 1204.3068,-706.69974 1204.3068,-611.55852 L 1204.2756,-611.55852 C 1204.2757,-516.4173 1196.8613,-439.18352 1187.7131,-439.18352 C 1178.5649,-439.18354 1171.1506,-516.4173 1171.1506,-611.55852 C 1171.1505,-706.69974 1178.5649,-783.90227 1187.7131,-783.90227 z M 1367.8068,-724.24602 C 1447.0912,-724.24603 1511.4318,-688.11098 1511.4318,-643.58977 C 1511.4318,-599.06856 1447.0913,-562.93352 1367.8068,-562.93352 C 1288.5225,-562.93351 1224.1818,-599.06856 1224.1818,-643.58977 C 1224.1819,-688.11098 1288.5226,-724.24602 1367.8068,-724.24602 z"
+ id="rect3729" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-weight:bold;text-align:end;writing-mode:lr-tb;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="723.4375"
+ y="35.174683"
+ id="text2328"><tspan
+ sodipodi:role="line"
+ id="tspan2330"
+ x="723.4375"
+ y="35.174683">Intersection Project</tspan><tspan
+ sodipodi:role="line"
+ x="723.4375"
+ y="55.681184"
+ id="tspan2332"
+ style="font-size:16px;font-weight:normal;text-align:end;text-anchor:end;font-family:Sans">Michael Sloan</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="15.625"
+ y="84.570549"
+ id="text2334"><tspan
+ sodipodi:role="line"
+ id="tspan2336"
+ x="15.625"
+ y="84.570549">Layer 1: Curve</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 15.60073,87.695549 L 728.14921,87.695549"
+ id="path2339" />
+ <text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="29.6875"
+ y="109.57055"
+ id="text2343"><tspan
+ sodipodi:role="line"
+ x="29.6875"
+ y="109.57055"
+ id="tspan2347"><tspan
+ style="font-weight:bold"
+ id="tspan8521">Curve</tspan>: A continuous 2d function on the interval [0,1], with precise start and end points</tspan></text>
+ <text
+ id="text2351"
+ y="336.49829"
+ x="15.625"
+ style="font-size:24px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="336.49829"
+ x="15.625"
+ id="tspan2353"
+ sodipodi:role="line">Layer 2: Path &amp; Region</tspan></text>
+ <path
+ id="path2355"
+ d="M 15.60073,339.62327 L 728.14921,339.62327"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 39.0625,142.38305 C 50.953679,82.266289 39.708637,183.98347 140.625,117.38305"
+ id="path2375"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="33.59375"
+ y="154.10181"
+ id="text2972"><tspan
+ sodipodi:role="line"
+ id="tspan2974"
+ x="33.59375"
+ y="154.10181">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="142.1875"
+ y="124.4143"
+ id="text2976"><tspan
+ sodipodi:role="line"
+ id="tspan2978"
+ x="142.1875"
+ y="124.4143">1</tspan></text>
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1, 47.99999964;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2994"
+ width="514.0625"
+ height="175.08554"
+ x="201.5625"
+ y="118.94555" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot3395"><flowRegion
+ id="flowRegion3397"><use
+ x="0"
+ y="0"
+ xlink:href="#rect2994"
+ id="use3399"
+ width="744.09448"
+ height="1052.3622"
+ transform="translate(0,24.041631)" /></flowRegion><flowPara
+ id="flowPara3401"><flowSpan
+ style="font-weight:bold"
+ id="flowSpan8519">Crossing</flowSpan>: A location where two curves cross. This is a subset of the intersections of the curves. Crossings are stored as two time values and a boolean indicating the relationship between the curves.</flowPara><flowPara
+ id="flowPara8507" /><flowPara
+ id="flowPara8511">This relationship is defined by the 'direction' of the curves at the intersection, and is quite useful in the later layers. When the curves are portions of paths with the same winding, this relation boolean indicates whether the first path is exiting (true), or leaving (false).</flowPara><flowPara
+ id="flowPara8515" /><flowPara
+ id="flowPara8517">In curve terms it is the orientation of the derivatives and the origin.</flowPara></flowRoot> <path
+ sodipodi:nodetypes="cc"
+ id="path3802"
+ d="M 39.0625,226.75805 C 50.953679,166.64129 39.708637,268.35847 140.625,201.75805"
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#Arrow1Send);marker-mid:none;marker-end:url(#Arrow1Send);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#Arrow1Send);marker-end:url(#Arrow1Send);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 68.75,265.82055 C 31.50892,217.735 75.40193,186.36624 125,179.88305"
+ id="path3806"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 57.8125,213.4768 L 57.8125,223.63305"
+ id="path4211" />
+ <path
+ id="path4213"
+ d="M 52.73437,218.55493 L 62.89062,218.55493"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="81.25"
+ y="245.50806"
+ id="text4215"><tspan
+ sodipodi:role="line"
+ id="tspan4217"
+ x="81.25"
+ y="245.50806">A<tspan
+ style="font-size:8px"
+ id="tspan4219"
+ dy="2.34375">t <tspan
+ style="font-size:12px"
+ id="tspan4221"
+ dy="-1.5625">= 0.4</tspan></tspan></tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="85.15625"
+ y="186.13306"
+ id="text4223"><tspan
+ sodipodi:role="line"
+ id="tspan4225"
+ x="85.15625"
+ y="186.13306">A</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="117.96875"
+ y="206.44556"
+ id="text4227"><tspan
+ sodipodi:role="line"
+ id="tspan4229"
+ x="117.96875"
+ y="206.44556">B</tspan></text>
+ <text
+ id="text4231"
+ y="260.35181"
+ x="80.46875"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="260.35181"
+ x="80.46875"
+ id="tspan4233"
+ sodipodi:role="line"><tspan
+ dy="0 2.34375"
+ id="tspan4235"
+ style="font-size:8px"><tspan
+ style="font-size:12px"
+ id="tspan4247">B</tspan>t <tspan
+ dy="-1.5625"
+ id="tspan4237"
+ style="font-size:12px">= 0.15</tspan></tspan></tspan></text>
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000001, 48.00000051;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8527"
+ width="700"
+ height="57.396477"
+ x="27.34375"
+ y="347.43579" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot8934"
+ transform="translate(0,-42.426407)"><flowRegion
+ id="flowRegion8936"><use
+ x="0"
+ y="0"
+ xlink:href="#rect8527"
+ id="use8938"
+ transform="translate(0,42.426407)"
+ width="744.09448"
+ height="1052.3622" /></flowRegion><flowPara
+ id="flowPara8940"><flowSpan
+ style="font-weight:bold"
+ id="flowSpan8942">Path</flowSpan>: A series of connected Curves, implicitly closed with a final line segment. For some applications, however, this line segment must be treated specially, such that if it is still expressed, it is the last segment of the path. In some cases this is impossible.</flowPara></flowRoot> <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ x="27.125"
+ y="468.62329"
+ id="text8946"><tspan
+ sodipodi:role="line"
+ id="tspan8948"
+ x="27.125"
+ y="468.62329">0</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 36.5,443.80039 C 36.5,443.80039 66.583183,434.01208 81.03125,430.51914 C 152.125,413.33164 150.5625,411.76914 99,432.86289 C 78.75366,441.14548 113.84375,446.14414 117.75,443.80039 C 287.26549,396.25895 87.015116,455.9959 165.40625,450.05039 C 177.90625,448.48789 234.9375,410.98789 234.9375,410.98789 C 251.87833,448.11151 247.88044,478.79245 210.71875,499.26914"
+ id="path8950"
+ sodipodi:nodetypes="csscccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 37.28125,444.58164 L 210.71875,499.26914"
+ id="path8952"
+ sodipodi:nodetypes="cc" />
+ <text
+ id="text9359"
+ y="427.30042"
+ x="77.875"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="427.30042"
+ x="77.875"
+ id="tspan9361"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ x="83.875"
+ y="441.30042"
+ id="text9363"><tspan
+ sodipodi:role="line"
+ id="tspan9365"
+ x="83.875"
+ y="441.30042">2</tspan></text>
+ <text
+ id="text9367"
+ y="455.30042"
+ x="111.875"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="455.30042"
+ x="111.875"
+ sodipodi:role="line"
+ id="tspan9371">3</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ x="163.875"
+ y="461.30042"
+ id="text9375"><tspan
+ id="tspan9377"
+ sodipodi:role="line"
+ x="163.875"
+ y="461.30042">4</tspan></text>
+ <text
+ id="text9379"
+ y="427.30042"
+ x="228.875"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="427.30042"
+ x="228.875"
+ sodipodi:role="line"
+ id="tspan9381">5</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Serif"
+ x="203.22182"
+ y="511.77289"
+ id="text9383"><tspan
+ sodipodi:role="line"
+ id="tspan9385"
+ x="203.22182"
+ y="511.77289">6</tspan></text>
+ <path
+ id="path9318"
+ d="M 181.00779,605.47923 L 181.00779,756.79173 L 326.69529,756.79173 L 326.69529,605.47923 L 181.00779,605.47923 z M 255.13279,630.91673 C 280.89411,630.91673 301.82029,651.84292 301.82029,677.60423 C 301.82027,703.36552 280.8941,724.26048 255.13279,724.26048 C 229.37148,724.2605 208.47654,703.36554 208.47654,677.60423 C 208.47653,651.84289 229.37148,630.91673 255.13279,630.91673 z"
+ style="opacity:1;fill:#6e5e5e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000007, 48.0000034;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9411"
+ width="440"
+ height="44.715744"
+ x="44.483189"
+ y="862.271" />
+ <g
+ id="g2521"
+ transform="translate(0,301.12698)">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 5.2867965,666.45725 C 5.2867965,666.45725 35.369977,656.66894 49.818047,653.176 C 120.9118,635.9885 119.3493,634.426 67.786797,655.51975 C 47.540457,663.80234 82.630547,668.801 86.536797,666.45725 C 256.05229,618.91581 55.801917,678.65276 134.19305,672.70725 C 146.69305,671.14475 203.7243,633.64475 203.7243,633.64475 C 220.66513,670.76837 216.66724,701.44931 179.50555,721.926"
+ id="path9389"
+ sodipodi:nodetypes="csscccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 2;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 6.0680465,667.2385 L 179.50555,721.926"
+ id="path9391"
+ sodipodi:nodetypes="cc" />
+ <path
+ transform="translate(-30.213203,-10.811184)"
+ d="M 180,671.36218 A 53,53 0 1 1 74,671.36218 A 53,53 0 1 1 180,671.36218 z"
+ sodipodi:ry="53"
+ sodipodi:rx="53"
+ sodipodi:cy="671.36218"
+ sodipodi:cx="127"
+ id="path9834"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <g
+ transform="translate(-21.213203,-38.811184)"
+ id="g10655">
+ <path
+ id="path10651"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ id="path10653" />
+ </g>
+ <g
+ transform="translate(-17.713203,-12.311184)"
+ id="g10659">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ id="path10661" />
+ <path
+ id="path10663"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g10665"
+ transform="translate(60.286797,12.188816)">
+ <path
+ id="path10667"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ id="path10669" />
+ </g>
+ <g
+ transform="translate(84.286797,-26.311184)"
+ id="g10671">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ id="path10673" />
+ <path
+ id="path10675"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g10677"
+ transform="translate(83.786797,-40.811183)">
+ <path
+ id="path10679"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ id="path10681" />
+ </g>
+ <g
+ transform="translate(82.286797,-44.311183)"
+ id="g10683">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 65.3125,688.01843 L 65.3125,698.17468"
+ id="path10685" />
+ <path
+ id="path10687"
+ d="M 60.23437,693.09656 L 70.39062,693.09656"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ id="g2554"
+ transform="translate(0,301.12698)">
+ <path
+ id="path11208"
+ d="M 220.05888,649.49465 L 263.83033,649.49465"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text12445"
+ y="638.83783"
+ x="222.01724"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="638.83783"
+ x="222.01724"
+ id="tspan12447"
+ sodipodi:role="line">Union</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 414.70605,930.12161 L 517.4494,870.17357"
+ id="path13315" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 416.70608,941.12165 L 526.01012,979.92317"
+ id="path13317" />
+ <g
+ id="g2544"
+ transform="translate(0,301.12698)">
+ <path
+ id="path12896"
+ d="M 415.51098,636.39534 C 415.96828,639.19439 416.20607,642.06692 416.20607,644.99465 L 416.20607,644.99465 C 416.20607,646.96475 416.0984,648.90985 415.88862,650.82438"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path12892"
+ d="M 310.51289,639.2627 C 313.37363,612.69928 335.88664,591.99465 363.20607,591.99465 C 388.72601,591.99465 410.05186,610.06154 415.0824,634.09412"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path12888"
+ d="M 392.28508,689.30395 C 383.93485,694.79747 373.94156,697.99465 363.20607,697.99465 C 340.78343,697.99465 321.59861,684.04712 313.85985,664.36028"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path12460"
+ d="M 415.27762,650.89645 C 436.57244,639.8338 469.64357,618.0884 469.64357,618.0884 C 486.5844,655.21202 482.58651,685.89296 445.42482,706.36965"
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ id="path12456"
+ d="M 416.89529,633.40969 C 426.42762,631.19088 423.32669,632.81532 415.90125,636.06666"
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ id="path12449"
+ d="M 310.81469,638.88458 C 295.40026,643.02872 271.20607,650.9009 271.20607,650.9009 L 313.95542,663.53315"
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path12900"
+ d="M 390.59038,689.68218 L 446.42479,706.36968"
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3.99999996, 1.99999998;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ transform="translate(-118.29394,-51.367533)"
+ d="M 540,685.86218 A 5.5,5.5 0 1 1 529,685.86218 A 5.5,5.5 0 1 1 540,685.86218 z"
+ sodipodi:ry="5.5"
+ sodipodi:rx="5.5"
+ sodipodi:cy="685.86218"
+ sodipodi:cx="534.5"
+ id="path12902"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.80000001;stroke-miterlimit:4;stroke-dasharray:0.8, 0.8;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g2564"
+ transform="translate(0,301.12698)">
+ <path
+ sodipodi:nodetypes="cc"
+ id="path13335"
+ d="M 523.51519,620.60763 C 520.12981,602.83246 515.33803,584.38874 510.2072,573.72907"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:19.23264885;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:19.23264885;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 540.87055,613.95765 C 632.53653,592.62085 602.717,608.242 531.31153,639.50794"
+ id="path13337" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ id="path13351"
+ d="M 537.05362,641.11822 L 518.0652,644.21465 C 520.08982,656.60699 521.65827,669.15297 522.75281,681.83516 C 534.90606,684.33516 534.16149,677.70481 541.83282,679.90433 C 540.72774,666.83066 539.14076,653.8932 537.05362,641.11822 z"
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:39.59658432;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.80000001;stroke-miterlimit:4;stroke-dasharray:0.8, 0.8;stroke-dashoffset:0;stroke-opacity:1"
+ id="path13339"
+ sodipodi:cx="534.5"
+ sodipodi:cy="685.86218"
+ sodipodi:rx="5.5"
+ sodipodi:ry="5.5"
+ d="M 540,685.86218 A 5.5,5.5 0 1 1 529,685.86218 A 5.5,5.5 0 1 1 540,685.86218 z"
+ transform="matrix(9.6163242,0,0,9.6163242,-4605.6823,-5971.0816)" />
+ <path
+ transform="matrix(9.6163242,0,0,9.6163242,-4605.6823,-5971.0816)"
+ d="M 540,685.86218 A 5.5,5.5 0 1 1 529,685.86218 A 5.5,5.5 0 1 1 540,685.86218 z"
+ sodipodi:ry="5.5"
+ sodipodi:rx="5.5"
+ sodipodi:cy="685.86218"
+ sodipodi:cx="534.5"
+ id="path13356"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.80000001;stroke-miterlimit:4;stroke-dasharray:0.8, 0.8;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g2571"
+ transform="translate(-6,301.12698)">
+ <path
+ sodipodi:nodetypes="ccccccsssssssscc"
+ id="path17529"
+ d="M 669.90625,568.88609 L 652.59375,577.22984 C 668.38738,617.91025 658.91917,631.04706 701.75,623.38609 C 661.44527,641.13907 670.89896,635.14549 673.8125,681.13609 C 685.96576,683.63612 685.20367,677.03032 692.875,679.22984 C 692.02305,669.15078 692.20265,658.99187 691.375,649.01109 C 705.86844,642.91912 717.48537,637.56766 726.0625,633.01109 C 731.20833,630.27738 735.25679,627.8742 738.5,625.41734 C 740.12161,624.18891 741.53504,622.95333 742.90625,621.32359 C 744.27746,619.69385 745.99222,617.53449 746.1875,613.57359 C 746.38278,609.61269 743.46312,605.17416 740.9375,603.54234 C 738.41188,601.91052 736.38131,601.53447 734.375,601.22984 C 732.36869,600.92521 730.28822,600.83068 728.03125,600.85484 C 725.77428,600.879 723.34589,601.04254 720.5625,601.29234 C 711.49835,602.10581 699.14713,604.09782 683.09375,607.32359 C 679.47139,592.40484 674.50899,578.44864 669.90625,568.88609 z"
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:19.23264885;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ transform="matrix(9.6163242,0,0,9.6163242,-4454.6363,-5971.7659)"
+ d="M 540,685.86218 A 5.5,5.5 0 1 1 529,685.86218 A 5.5,5.5 0 1 1 540,685.86218 z"
+ sodipodi:ry="5.5"
+ sodipodi:rx="5.5"
+ sodipodi:cy="685.86218"
+ sodipodi:cx="534.5"
+ id="path17535"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.80000001;stroke-miterlimit:4;stroke-dasharray:0.8, 0.8;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.80000001;stroke-miterlimit:4;stroke-dasharray:0.8, 0.8;stroke-dashoffset:0;stroke-opacity:1"
+ id="path17537"
+ sodipodi:cx="534.5"
+ sodipodi:cy="685.86218"
+ sodipodi:rx="5.5"
+ sodipodi:ry="5.5"
+ d="M 540,685.86218 A 5.5,5.5 0 1 1 529,685.86218 A 5.5,5.5 0 1 1 540,685.86218 z"
+ transform="matrix(9.6163242,0,0,9.6163242,-4454.6363,-5971.7659)" />
+ </g>
+ <g
+ id="g2559"
+ transform="translate(0,301.12698)">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 584.92598,583.02661 L 628.69743,583.02661"
+ id="path2443" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="590.88434"
+ y="576.36975"
+ id="text2445"><tspan
+ sodipodi:role="line"
+ id="tspan2447"
+ x="590.88434"
+ y="576.36975">Fix</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 73.436386,852.85756 L 663.36887,852.85756"
+ id="path2449" />
+ <flowRoot
+ id="flowRoot2451"
+ style="font-size:12px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"
+ transform="translate(-235.46656,388.08684)"><flowRegion
+ id="flowRegion2453"><use
+ id="use2455"
+ xlink:href="#rect9411"
+ y="0"
+ x="0"
+ width="744.09448"
+ height="1052.3622"
+ transform="translate(235.46656,-386.78742)" /></flowRegion><flowPara
+ id="flowPara2457">One problem with storing crossings as a time value is that the locations on the two paths may not perfectly coincide. To maintain continuity, the output portions must be tweaked.</flowPara></flowRoot> <path
+ id="path2477"
+ d="M 73.436386,1034.4627 L 663.36887,1034.4627"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <rect
+ y="415.47995"
+ x="272.02945"
+ height="91.384789"
+ width="440"
+ id="rect2501"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000004, 48.00000264;stroke-dashoffset:0;stroke-opacity:1" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot2503"
+ transform="translate(0,-48.870058)"><flowRegion
+ id="flowRegion2505"><use
+ x="0"
+ y="0"
+ xlink:href="#rect2501"
+ id="use2507"
+ width="744.09448"
+ height="1052.3622"
+ transform="translate(0,48.083262)" /></flowRegion><flowPara
+ id="flowPara2511">The Crossing data type is also valid on Paths, where a particular time value corresponds to the point on curve i at f, where i is the integral part, and f is the fractional part. The crossings function for paths is the basis of the important path_boolean function.</flowPara></flowRoot> <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="15.625"
+ y="1081.7888"
+ id="text2513"><tspan
+ sodipodi:role="line"
+ id="tspan2515"
+ x="15.625"
+ y="1081.7888">Layer 3: Shape</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 15.60073,1084.9138 L 728.14921,1084.9138"
+ id="path2517" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000004, 48.00000245;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2596"
+ width="690.3158"
+ height="72.698502"
+ x="21.713648"
+ y="520.41602" />
+ <rect
+ y="347.43579"
+ x="27.34375"
+ height="57.396477"
+ width="700"
+ id="rect2602"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000001, 48.00000051;stroke-dashoffset:0;stroke-opacity:1" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot2630"><flowRegion
+ id="flowRegion2632"><use
+ x="0"
+ y="0"
+ xlink:href="#rect2596"
+ id="use2634"
+ width="744.09448"
+ height="1052.3622" /></flowRegion><flowPara
+ id="flowPara2636"><flowSpan
+ style="font-style:normal;font-weight:bold;font-size:14"
+ id="flowSpan2640">Region</flowSpan>: A simple path (a path with no self-intersection), specifying a subset of the plane. Such a path also has a defined winding direction which specifies what the boundary means - a region of 'fill' will have a counter-clockwise wind, whereas a region of 'non-fill' will have a clockwise wind. Reversing the direction of the region's path will give the boolean complement of the region.</flowPara></flowRoot> <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:#6e5e5e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.45454550000000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path6202"
+ sodipodi:cx="91.923882"
+ sodipodi:cy="633.75494"
+ sodipodi:rx="22.627417"
+ sodipodi:ry="22.627417"
+ d="M 114.5513,633.75494 A 22.627417,22.627417 0 1 1 69.296465,633.75494 A 22.627417,22.627417 0 1 1 114.5513,633.75494 z"
+ transform="matrix(2.0625,0,0,2.0625,-91.428942,-630.93823)" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.4545455;marker-start:url(#Arrow1Mstart);marker-mid:url(#Arrow1Mstart);marker-end:url(#Arrow1Mstart);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2656"
+ sodipodi:cx="91.923882"
+ sodipodi:cy="633.75494"
+ sodipodi:rx="22.627417"
+ sodipodi:ry="22.627417"
+ d="M 114.5513,633.75494 A 22.627417,22.627417 0 1 1 69.296465,633.75494 A 22.627417,22.627417 0 1 1 114.5513,633.75494 z"
+ transform="matrix(2.0625,0,0,2.0625,-92.843157,-629.52402)" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9127"
+ width="145.66399"
+ height="151.32086"
+ x="22.627422"
+ y="605.4707" />
+ <path
+ transform="matrix(2.0625,0,0,2.0625,65.548763,-629.52403)"
+ d="M 114.5513,633.75494 A 22.627417,22.627417 0 1 1 69.296465,633.75494 A 22.627417,22.627417 0 1 1 114.5513,633.75494 z"
+ sodipodi:ry="22.627417"
+ sodipodi:rx="22.627417"
+ sodipodi:cy="633.75494"
+ sodipodi:cx="91.923882"
+ id="path9129"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.4545455;marker-start:url(#Arrow1Mend);marker-mid:url(#Arrow1Mend);marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="77.781746"
+ y="769.51947"
+ id="text11070"><tspan
+ sodipodi:role="line"
+ id="tspan11072"
+ x="77.781746"
+ y="769.51947">(Outer box represents the plane)</tspan></text>
+ <rect
+ y="606.68304"
+ x="344.15433"
+ height="143.40918"
+ width="367.87509"
+ id="rect11074"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988;stroke-miterlimit:4;stroke-dasharray:1, 48.00000017;stroke-dashoffset:0;stroke-opacity:1" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot11080"><flowRegion
+ id="flowRegion11082"><use
+ x="0"
+ y="0"
+ xlink:href="#rect11074"
+ id="use11084"
+ width="744.09448"
+ height="1052.3622" /></flowRegion><flowPara
+ id="flowPara11086">This behavior is mainly important for the main region boolean function, which takes two regions and performs an operation. The operation which is performed is specified by a bool which indicates either union (false) or intersection (true).</flowPara><flowPara
+ id="flowPara11777" /><flowPara
+ id="flowPara11779">This operation may be considered from the standpoint of regular path booleans:</flowPara></flowRoot> <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="390.38602"
+ y="771.6131"
+ id="text11088"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11090"
+ x="390.38602"
+ y="771.6131">Union</tspan></text>
+ <text
+ id="text11092"
+ y="786.07953"
+ x="455.04291"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="786.07953"
+ x="455.04291"
+ id="tspan11094"
+ sodipodi:role="line">A</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="340.86606"
+ y="823.54407"
+ id="text11096"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11098"
+ x="340.86606"
+ y="823.54407">B</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="415.87198"
+ y="798.57526"
+ id="text11100"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11102"
+ x="415.87198"
+ y="798.57526">Fill</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.00000002, 1.00000002;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 510.65997,802.88125 L 390.54555,802.88125 L 390.54555,837.88488"
+ id="path11104" />
+ <text
+ id="text11106"
+ y="798.57526"
+ x="466.74768"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="798.57526"
+ x="466.74768"
+ id="tspan11108"
+ sodipodi:role="line">Hole</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.00000002, 1.00000002;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 460.69754,802.57541 L 460.69754,840.23336"
+ id="path11110" />
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="395.08356"
+ y="817.79944"
+ id="text11112"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11114"
+ x="395.08356"
+ y="817.79944">A+B</tspan></text>
+ <text
+ id="text11116"
+ y="817.79944"
+ x="465.38834"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.79944"
+ x="465.38834"
+ id="tspan11118"
+ sodipodi:role="line">A-B</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="395.08356"
+ y="834.02338"
+ id="text11120"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11122"
+ x="395.08356"
+ y="834.02338">B-A</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="442.45596"
+ y="833.52515"
+ id="text11124"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11126"
+ x="442.45596"
+ y="833.52515">H</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="434.12323"
+ y="817.61108"
+ id="text11128"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11130"
+ x="434.12323"
+ y="817.61108">F</tspan></text>
+ <text
+ id="text11132"
+ y="817.89258"
+ x="442.45596"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.89258"
+ x="442.45596"
+ id="tspan11134"
+ sodipodi:role="line">H</tspan></text>
+ <text
+ id="text11136"
+ y="817.61108"
+ x="434.12323"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.61108"
+ x="434.12323"
+ id="tspan11138"
+ sodipodi:role="line">F</tspan></text>
+ <text
+ id="text11188"
+ y="817.89258"
+ x="498.86911"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.89258"
+ x="498.86911"
+ id="tspan11190"
+ sodipodi:role="line">H</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="465.38834"
+ y="834.11163"
+ id="text11260"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11262"
+ x="465.38834"
+ y="834.11163">AxB</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="498.86911"
+ y="834.20483"
+ id="text11264"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11266"
+ x="498.86911"
+ y="834.20483">H</tspan></text>
+ <text
+ id="text11268"
+ y="816.24683"
+ x="368.29462"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="816.24683"
+ x="368.29462"
+ id="tspan11270"
+ sodipodi:role="line">Fill</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="352.56204"
+ y="832.55908"
+ id="text11272"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11274"
+ x="352.56204"
+ y="832.55908">Hole</tspan></text>
+ <text
+ id="text11693"
+ y="771.6131"
+ x="555.71558"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="771.6131"
+ x="555.71558"
+ id="tspan11695"
+ sodipodi:role="line">Intersection</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="641.52271"
+ y="786.07953"
+ id="text11697"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11699"
+ x="641.52271"
+ y="786.07953">A</tspan></text>
+ <text
+ id="text11701"
+ y="825.54053"
+ x="539.36707"
+ style="font-size:16.31223488px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="825.54053"
+ x="539.36707"
+ id="tspan11703"
+ sodipodi:role="line">B</tspan></text>
+ <text
+ id="text11705"
+ y="798.57526"
+ x="608.36243"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="798.57526"
+ x="608.36243"
+ id="tspan11707"
+ sodipodi:role="line">Fill</tspan></text>
+ <path
+ id="path11709"
+ d="M 710.80928,802.88125 L 590.69486,802.88125 L 590.69486,837.88488"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.00000002, 1.00000002;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="655.01807"
+ y="798.57526"
+ id="text11711"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11713"
+ x="655.01807"
+ y="798.57526">Hole</tspan></text>
+ <path
+ id="path11715"
+ d="M 647.27741,802.57541 L 647.27741,840.23336"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.00000002, 1.00000002;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ id="text11717"
+ y="817.79944"
+ x="595.58807"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.79944"
+ x="595.58807"
+ id="tspan11719"
+ sodipodi:role="line">AxB</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="653.65869"
+ y="817.79944"
+ id="text11721"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11723"
+ x="653.65869"
+ y="817.79944">B-A</tspan></text>
+ <text
+ id="text11725"
+ y="836.06238"
+ x="595.58807"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="836.06238"
+ x="595.58807"
+ id="tspan11727"
+ sodipodi:role="line">A-B</tspan></text>
+ <text
+ id="text11733"
+ y="817.61108"
+ x="634.62775"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.61108"
+ x="634.62775"
+ id="tspan11735"
+ sodipodi:role="line">F</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="634.62775"
+ y="817.61108"
+ id="text11741"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11743"
+ x="634.62775"
+ y="817.61108">F</tspan></text>
+ <text
+ id="text11749"
+ y="835.47101"
+ x="653.65869"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="835.47101"
+ x="653.65869"
+ id="tspan11751"
+ sodipodi:role="line">A+B</tspan></text>
+ <text
+ id="text11753"
+ y="835.56415"
+ x="689.17853"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="835.56415"
+ x="689.17853"
+ id="tspan11755"
+ sodipodi:role="line">H</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="568.79919"
+ y="816.24683"
+ id="text11757"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11759"
+ x="568.79919"
+ y="816.24683">Fill</tspan></text>
+ <text
+ id="text11761"
+ y="832.55908"
+ x="553.06659"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="832.55908"
+ x="553.06659"
+ id="tspan11763"
+ sodipodi:role="line">Hole</tspan></text>
+ <text
+ id="text11765"
+ y="835.96234"
+ x="634.62775"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="835.96234"
+ x="634.62775"
+ id="tspan11767"
+ sodipodi:role="line">F</tspan></text>
+ <text
+ id="text11769"
+ y="817.61108"
+ x="700.5564"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ y="817.61108"
+ x="700.5564"
+ id="tspan11771"
+ sodipodi:role="line">F</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16.31223488px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="700.5564"
+ y="835.96234"
+ id="text11773"
+ transform="scale(0.9982283,1.0017748)"><tspan
+ sodipodi:role="line"
+ id="tspan11775"
+ x="700.5564"
+ y="835.96234">F</tspan></text>
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988;stroke-miterlimit:4;stroke-dasharray:0.99999998, 47.99999926;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11781"
+ width="312.72076"
+ height="59.970577"
+ x="23.127848"
+ y="783.45972" />
+ <flowRoot
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ id="flowRoot11787"><flowRegion
+ id="flowRegion11789"><use
+ x="0"
+ y="0"
+ xlink:href="#rect11781"
+ id="use11791" /></flowRegion><flowPara
+ id="flowPara11793">Though not on the chart, fill subtraction and hole subtraction may be performed by reversing one of the arguments.</flowPara></flowRoot> <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:1.00000001, 48.00000051;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect12212"
+ width="700"
+ height="57.396477"
+ x="27.34375"
+ y="346.02158" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;marker-start:url(#Dot_m)"
+ d="M 33.234019,725.63596 L 33.234019,748.26338"
+ id="path12214" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-start:none;marker-end:url(#Arrow1Mend);stroke-opacity:1"
+ d="M 34.062445,725.67885 L 56.689865,725.67885"
+ id="path12218" />
+ </g>
+</svg>
diff --git a/doc/degenerate_conic_decomposition.pdf b/doc/degenerate_conic_decomposition.pdf
new file mode 100644
index 0000000..9d5e1cf
--- /dev/null
+++ b/doc/degenerate_conic_decomposition.pdf
Binary files differ
diff --git a/doc/extradoxygen.css b/doc/extradoxygen.css
new file mode 100644
index 0000000..2e0c486
--- /dev/null
+++ b/doc/extradoxygen.css
@@ -0,0 +1,14 @@
+#top {
+ width: 1000px;
+ margin: 0 auto;
+}
+
+div.contents {
+ width: 1000px;
+ margin: 0 auto;
+}
+
+div.header {
+ width: 1000px;
+ margin: 0 auto;
+} \ No newline at end of file
diff --git a/doc/features.txt b/doc/features.txt
new file mode 100644
index 0000000..2dab29e
--- /dev/null
+++ b/doc/features.txt
@@ -0,0 +1,28 @@
+* C++
+* Functional programming style.
+* Points
+* Efficient affine transformations
+* Rectangles
+* Convex Hulls
+* Bounded error
+* General purpose paths:
+- exact elliptical arcs
+- area
+- centroid and bending moments
+* Path Locations
+- determination of special spots (e.g. maximum curvature)
+- splitting
+- point, tangent, curvature at location
+- efficient arc length and inverse arc length
+* Path algebra
+- computations such as offset curves can be written with their mathematical definition and still get a bounded error, efficient curve. (preliminary trials indicate offset done this way out performs the method used in Inkscape)
+- arbitrary distortion (with bounded error)
+-- mesh distorts
+-- computational distorts such as the GIMP's 'vortex' plugin
+-- 3d mapping (perspective, flag, sphere)
+* exact boolean ops (elliptic arcs remain elliptic arcs)
+* efficient 2d database
+* implicit function plotting
+* NURBs input and output
+* tunable path simplification
+* PDoF constraint system for CAD/ CAGD \ No newline at end of file
diff --git a/doc/manual.bib b/doc/manual.bib
new file mode 100644
index 0000000..af7f0d7
--- /dev/null
+++ b/doc/manual.bib
@@ -0,0 +1,55 @@
+@article{SanchezReyes1997,
+ author = {J. S\'{a}nchez-Reyes},
+ title = {The symmetric analogue of the polynomial power basis},
+ journal = {ACM Trans. Graph.},
+ volume = {16},
+ number = {3},
+ year = {1997},
+ issn = {0730-0301},
+ pages = {319--357},
+ doi = {http://doi.acm.org/10.1145/256157.256162},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ }
+
+@article{SanchezReyes2001,
+ author = {J. S\'{a}nchez-reyes},
+ title = {Inversion approximations for functions via s-power series},
+ journal = {Comput. Aided Geom. Des.},
+ volume = {18},
+ number = {6},
+ year = {2001},
+ issn = {0167-8396},
+ pages = {587--608},
+ doi = {http://dx.doi.org/10.1016/S0167-8396(01)00054-1},
+ publisher = {Elsevier Science Publishers B. V.},
+ address = {Amsterdam, The Netherlands, The Netherlands},
+ }
+
+@article{SanchezReyes2000,
+ author = {Javier S\'{a}nchez-Reyes},
+ title = {Applications of the polynomial s-power basis in geometry processing},
+ journal = {ACM Trans. Graph.},
+ volume = {19},
+ number = {1},
+ year = {2000},
+ issn = {0730-0301},
+ pages = {27--55},
+ doi = {http://doi.acm.org/10.1145/343002.343018},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ }
+
+@article{SanchezReyes2005,
+ author = {J. S\'{a}nchez-Reyes and J. M. Chac\'{o}n},
+ title = {s-power series: an alternative to Poisson expansions for representing analytic functions},
+ journal = {Comput. Aided Geom. Des.},
+ volume = {22},
+ number = {2},
+ year = {2005},
+ issn = {0167-8396},
+ pages = {103--119},
+ doi = {http://dx.doi.org/10.1016/j.cagd.2004.09.003},
+ publisher = {Elsevier Science Publishers B. V.},
+ address = {Amsterdam, The Netherlands, The Netherlands},
+ }
diff --git a/doc/manual.tex b/doc/manual.tex
new file mode 100644
index 0000000..196c990
--- /dev/null
+++ b/doc/manual.tex
@@ -0,0 +1,734 @@
+\documentclass[openany]{book}
+\usepackage{tabularx}
+\usepackage{graphicx}
+
+\title{Lib2geom Manual}
+
+\newcommand{\code}[1]{\textsf{#1}}
+
+\begin{document}
+\maketitle{}
+
+\chapter{Overview}
+
+\section{Introduction}
+
+This manual focuses on the lib2geom computational geometry framework.
+The main goal of this framework is the eventual replacement of
+Inkscape's multiple and shoddy geometry frameworks. As with any decent
+module or lib, 2geom is designed to achieve the desired functionality
+while maintaining a generality encouraging usage within other
+applications. The focus on robust, accurate algorithms, as well as
+utilization of newer and better representations makes the lib
+very attractive for many applications.
+
+\section{Design Considerations}
+2Geom is written with a functional programming style in mind.
+Generally data structures are considered immutable and rather than
+assignment we use labeling. However, C++ can become unwieldy if
+this is taken to extreme and so a certain amount of pragmatism is
+used in practice. In particular, usability is not forgotten in the
+mires of functional zeal.
+
+The code relies strongly on the type system and uses some of the more
+'tricky' elements of C++ to make the code more elegant and 'correct'.
+Despite this, the intended use of 2Geom is a serious vector graphics
+application. In such domains, performance is still used as a quality
+metric, and as such we consider inefficiency to be a bug, and have
+traded elegance for efficiency where it matters.
+
+In general the data structures used in 2Geom are relatively 'flat'
+and require little from the memory management. Currently most data
+structures are built on standard STL headers\cite{stl}, and new and
+delete are used sparingly. It is intended for 2Geom to be fully
+compatible with Boehm garbage collector\cite{boehm} though this has
+not yet been tested.
+
+\section{Toy-Based Development}
+We have managed to come up with a method of library development
+that is perfect for geometry: the development of toys exemplifying
+a feature while the feature is perfected. This has somewhat subsumed
+the role of tests, and provides immediate motivation/reward for work.
+
+Every toy uses the same framework, which hides the details of making a
+user interface. You simply request some handles to interact with, and
+write some code to draw the output from the handles.
+
+\begin{figure}
+ \begin{center}
+ \includegraphics[width=90mm]{media/gear.png}
+ \caption{The gear toy demonstrates constructing paths, and computing a path with a known function for the teeth.}
+ \label{fig:gear-toy}
+ \end{center}
+\end{figure}
+
+Every toy can save a screenshot in SVG, PDF or PNG format. You can
+save the current handles to a handle file (.hnd) and load an existing
+handle file. Every toy can be called with \verb|-s filename| to save
+a screenshot to file filename (the type being determined by the
+extension). The toy can be started in a known state from a handle
+file.
+
+If you find some strange or buggy behaviour, save a handle file and a
+screenshot and submit them with your bug report.
+
+\section{Getting started}
+
+Build the library and play with all the toys in src/2geom/toys. Copy one that is similar to the problem you want to solve, and modify it. For example, say you wanted to compute the involute of a curve.
+
+An involute of a curve is defined by
+$$I(t) = B(t) - \frac{dB(t) \int_0^t|dB(\tau)|_2 d\tau}{|dB(\tau)|_2}$$
+
+If we reparameterise this curve to have unit speed (arc length parameterisation) the equation simplifies to:
+
+$$I(t) = B(t) - \frac{dB_x(t) t}{1}$$
+
+so if we can reparameterise a path to have unit speed the involute is almost trivial. Let's start with \verb|arc_length_param.cpp|. We see that the function required is simply:
+\begin{verbatim}
+Piecewise<D2<SBasis> > uniform_B = arc_length_parametrization(B);
+\end{verbatim}
+
+Take the resulting function and apply the calculations. You'll get
+the involute of the curve, as shown in Figure~\ref{fig:involute-toy}.
+We do not include an involute toy, but we would welcome someone to
+work out how to make non-circular gears.
+
+\begin{figure}
+ \begin{center}
+ \includegraphics[width=70mm]{media/involute.pdf}
+ \caption{The output of an involute toy.}
+ \label{fig:involute-toy}
+ \end{center}
+\end{figure}
+
+
+\chapter{Geometric Primitives}
+
+What good is a geometry library without geometric primitives? By this
+I mean the basic stuff that every decent geometry library has:
+Points/Vectors, Matrices, etc.
+
+2geom's primitives are descendant from libNR's geometric primitives.
+They have been modified quite a bit since that initial import, and
+will likely change in the future.
+
+\section{Points}
+
+\includegraphics[height=30mm]{media/point.png}
+
+The mathematical concepts of points and vectors are merged into the
+2geom class called \code{Point}. See Appendix A for a further
+discussion of this decision.
+
+2geom also breaks with the more traditional point \code{struct} with
+\code{.x} and \code{.y} fields. Rather, each 2geom point has a 2
+element array of coordinates.
+
+We found that the availability of \code{.x} and \code{.y} encouraged
+people to attempt to inline geometry operations rather than using the
+operators, perhaps in pursuit of a performance enhancement. By using
+an array, we encourage people to think about \code{Point}s as
+symmetric objects and discourage direct use of the components. We
+still provide direct access for the rare occasion that it is needed.
+Even in these cases, the array method prevents bugs by encouraging
+iteration over the array rather than explicit element reference.
+
+\section{Transformations}
+
+Affine transformations are either represented with a canonical 6
+element matrix, or special forms.
+
+\subsection{Scale}
+
+\includegraphics[height=50mm]{media/scale.png}
+
+A \code{Scale} transformation stores x and y scaling factors.
+
+\subsection{Rotate}
+
+\includegraphics[height=50mm]{media/rotate.png}
+
+A \code{Rotate} transformation uses a vector(\code{Point}) to store
+a rotation about the origin.
+
+In correspondence with mathematical convention (y increasing upwards),
+0 degrees is encoded as a vector pointing along the x axis, and positive
+angles indicate anticlockwise rotation. So, for example, a vector along
+the y axis would encode a 90 degree anticlockwise rotation of 90 degrees.
+
+In the case that the computer convention of y increasing downwards,
+the \verb}Rotate} transformation works essentially the same, except
+that positive angles indicate clockwise rotation.
+
+\subsection{Translate}
+
+\includegraphics[height=70mm]{media/translate.png}
+
+A \code{Translate} transformation is a simple vector(\code{Point})
+which stores an offset.
+
+\subsection{Matrix}
+
+\includegraphics[height=70mm]{media/matrix.png}
+
+A \code{Matrix} is a general affine transform. Code is provided for
+various decompositions, constructions, and manipulations. A
+\code{Matrix} is composed of 6 coordinates, essentially storing the
+x axis, y axis, and offset of the transformation. A detailed
+explanation for matrices is given in Appendix B.
+
+\subsection{D2}
+The \code{D2} class takes two instances of a scalar data type and treats them like a point. All operations which make sense on a point are defined for D2.
+
+A \verb|D2<double>| is a \code{Point}. A \verb|D2<Interval>| is a standard axis aligned rectangle.
+\verb|D2<SBasis>| provides a 2d parametric function which maps $t$ to a point $x(t), y(t)$
+\verb|D2<SBasis2d>| provides a 2d parametric function which maps a point $u,v$ to a point $x(u,v), y(u,v)$ --- a distortion.
+
+\section{Bounding Structures}
+
+2geom currently provides two classes for storing approximate bounding
+regions: \code{Rect} and \code{ConvexHull}. These are mostly intended
+for use in (mathematical) optimization, however provide manipulations suitable for
+other uses.
+
+\subsection{Interval}
+
+An interval is simply a pair of Coords which define a closed interval $[min, max]$.
+
+\subsection{Rect}
+
+\includegraphics[height=50mm]{media/rect.png}
+
+\code{Rect}s are rectangles with all sides parallel to the axes.
+\code{Rect}s are in fact defined as a \verb|D2<Interval>|.
+
+A rectangle can have 0 area and not be empty --- $[0,0] \times [0,0]$ contains exactly 1 point 0,0 and has 0 area. Similarly, $[0,0] \times [0,1]$ defines a 0 area line segment.
+
+\subsection{ConvexHull}
+
+\includegraphics[height=50mm]{media/convex.png}
+
+\verb|convex-cover.h| defines convex hulls with a set of points in clockwise direction. These can bee unioned and tested for intersection.
+
+\subsubsection{Operations}
+\begin{description}
+\item[empty:] contains no points
+\item[singular:] contains exactly one point
+\item[linear:] all points are on a line
+\item[area:] area of the convex hull
+\item[furthest:] furthest point in a direction (log time)
+\item[intersect:] do two convex hulls intersect?
+\item[intersection:] find the convex hull intersection
+\item[merge:] find the convex hull of a set of convex hulls
+\item[hausdorf distance:] the greatest least distance between a point $a$ in convex hull $A$ and a second convex hull $B$.
+\end{description}
+
+\subsection{Piecewise}
+The \code{Piecewise} class manages a sequence of elements of a type as
+segments and the 'cuts' between them. These cuts are time values which
+separate the pieces. This function representation allows for
+more interesting functions, as it provides a viable output for operations
+such as inversion, which may require multiple SBasis to properly invert
+the original.
+
+As for technical details, while the actual SBasis segments begin on the
+first cut and end on the last, the function is defined throughout all
+inputs by extending the first and last segments. The exact switching
+between segments is arbitrarily such that beginnings (t=0) have
+preference over endings (t=1). That is, each segment is $[t_0, t_1)$.
+This only matters if it is discontinuous at the location.
+
+$$
+f(t) \rightarrow \left\{
+\begin{array}{cc}
+s_1,& t <= c_2 \\
+s_2,& c_2 <= t <= c_3\\
+\ldots
+s_n,& c_n <= t
+\end{array}\right.
+$$
+
+\chapter{Paths}
+
+A \code{Path} is an ordered set of \code{Curve}s. Each \code{Path} is $C0$ continuous. To make multiple contours for a region, use \code{PathVector}.
+
+A \code{PathVector} in Inkscape is drawn in a single fill and stroke.
+
+This allows us lots of flexibility with what sort of elements we can
+'understand', while only taking slightly more memory than a sequence
+of points in the poly line or poly bezier case.
+
+The Curves themselves are immutable, which means that they can be shared between Paths.
+
+There is an implicit line segment from the initialPoint to the finalPoint which is used if the path is closed.
+
+An empty (size() == 0) path defines a naked move to.
+
+\section{Curves}
+2Geom has a diversity of curve types and conversions between them.
+
+Curves define a continuous segments which yields points for values of
+t in the range of [0,1]. The start-point is at t=0, and the endpoint
+at t=1.
+
+We require that each \code{Curve} implements virtual functions which
+allow for traversers of a path to perform generic operations. All
+curves must provide functions which give a position and gradient of a
+$t \in [0,1]$, bounds functions, and conversion to s-basis.
+
+\subsection{Bezier Curve}
+We use a \code{Bezier} curve class to store lines and quadratic/cubic
+beziers. This flexibility is attained by templating on order:
+\code{Bezier$<1>$} is a line segment, \code{Bezier$<2>$} is a quadratic
+bezier, and \code{Bezier$<3>$} is a cubic bezier.
+
+Bezier curves have some nice properties:
+\begin{itemize}
+\item Affine transforming the handles also affine transforms the values.
+\item The convex hull of the handles encompasses the curve.
+\end{itemize}
+
+\subsection{SVG Elliptical Arc}
+The \code{SVGEllipticalArc} curve class is implemented for support of
+the SVG elliptical arc. See the svg documentation for details on this
+curve.
+
+\subsection{S-Basis Curve}
+The \code{SBasisCurve} wraps the \code{MultidimSBasis$<2>$} class
+described later.
+
+\subsection{Possible Future Curve Types}
+\begin{itemize}
+\item Clothoids / Raphoids
+\item NURBS
+\end{itemize}
+
+\chapter{S-Power-Basis Form}
+
+2Geom provides a very powerful algebra for modifying paths. Although
+paths are kept in an extended SVG native form where possible, many
+operations require approximation. To do this we can convert a path
+into a sequence of symmetric power basis polynomials, henceforth
+referred to as s-basis, perform the required operations and convert
+back, approximating to a requested tolerance as required.
+
+The precise details of the s-basis form are beyond the scope of this
+manual, the interested reader should consult \cite{SanchezReyes1997,SanchezReyes2000,SanchezReyes2001,SanchezReyes2003,SanchezReyes2005}.
+An elementary, functional description is given in Appendix C.
+
+Geometrically important properties:
+\begin{itemize}
+\item exact representation of bezier segments
+\item low condition number on bezier conversion.
+\item strong convergence guarantees
+\item $C^0$ continuity guarantee
+\end{itemize}
+
+The following operations are implemented and are very efficient:
+\begin{itemize}
+\item fast conversion from all svg elements
+\item basic arithmetic --- $+$, $-$, $\times$, $\div$
+\item algebraic derivative and integral
+\item elementary trigonometric functions: $\sqrt{\cdot}$, $\sin(\cdot)$, $\cos(\cdot)$, $\exp(\cdot)$
+\item efficient degree elevation and reduction
+\item function inversion
+\item exact solutions for many non trivial operations
+\item root finding
+\item composition
+\end{itemize}
+
+All of these operations are fast. For example, multiplication of two
+beziers by converting to s-basis form, multiplying and converting back
+takes roughly the same time as performing the bezier multiplication
+directly, and furthermore, subdivision and degree reduction and
+elevation are straightforward in this form.
+
+\section{Implementation}
+As described in Appendix C, SBasis functions have the form $f(t) \rightarrow y$.
+Just S-Basis functors (in the C++ sense) alone are not enough to perform
+useful geometric operations. There are quite a few 2geom classes to
+address these needs.
+
+\subsection{SBasis}
+The \code{SBasis} class provides the most basic function form,
+$f(t) \rightarrow y$. This is useful in its own right, as well as being an
+element of constructing more complicated forms. \code{SBasis} are made
+up of \code{BezOrd}s, which store from/to values for each polynomial
+coefficient.
+
+\subsection{SBasis2D}
+SBasis2D provides multivariate form --- functions of the form
+$f(u,v) \rightarrow z$. These can be used for arbitrary distortion
+functions (take a path $p(t) \rightarrow (u,v)$ and a pair of surfaces
+$f(u,v),g(u,v)$ and compose: $q(t) = (f(p(t)), g(p(t)))$.
+
+Subdivision for surfaces may be done with either quadtrees or kd-trees.
+
+In general it is recommended to use the \verb|Piecewise<SBasis>| type rather than the SBasis type directly, as it manages the domain arithmetic automatically. It also automatically subdivides when necessary to improve accuracy/convergence.
+
+\chapter{2D databases}
+
+2Geom provides an implementation of a 2D database using quad trees and
+using a list. Quad trees aren't the best data-structure for queries,
+but they usually out perform the linear list. We provide a
+standard interface for object databases with performance guarantees
+and provide a set of useful operations Operations:
+
+\begin{description}
+\item[Insert:] given a bounding box and a 'reference', insert into the db
+\item[Delete:] given a bounding box and a 'reference', delete from the db
+\item[Search:] given a box, find all objects that may interact with this box
+\item[Cast:] given a path (including rays) return a list of objects that interact with the path, roughly sorted by path order
+\item[Shape query:] given a closed path, find all objects whose bounding boxes intersect path. (this and cast are nearly the same)
+\item[Nearest:] given a point (or maybe box) find the nearest objects, perhaps as a generator to get all objects in order. To do this, we walk around the quad tree neighbourhood, pushing all the elements into a priority queue, while the queue is empty, move out a bit. Nearest could be manhattan, max norm or euc?
+\item[Binary:] take two dbs, generate all pairs that have intersecting boxes.
+\item[Sweep:] traverse the tree in say y order, maintaining a y-range of relevant objects. (to implement sweepline algorithms)
+\item[Walk:] traverse the tree in an arbitrary order.
+\end{description}
+
+\chapter{Root like operations}
+
+\section{Roots}
+
+Given a function $x(t)$ we often wish to know where it is 0.
+$roots(x)$ quickly finds all values of $t \in [0,1)$ for which this is
+true. For example, if you wish to find where a path intersects a line you can rotate the path to make the line the x-axis and find the crossings with roots: $B(t) = \langle x(t), y(t)\rangle$, $roots(B \cdot \vec n)$.
+
+\section{Path intersection}
+
+Given a pair of paths, we often wish to know where they intersect. \verb|pair_intersect| finds all points where $A(t) = B(s), t \in [0,1], s \in [0,1]$. This can be performed on paths or \verb|piecewise<d2<sbasis> >|.
+
+\section{Self intersection}
+
+Sometimes we wish to know where a path intersects itself.
+
+\section{Contour finding}
+
+Given a function $f(x, y)$ we wish to construct approximate paths such that $f = 0$. Find two points on the same contour and use \verb|sb2d_cubic_solve|. (still needs code to refine and integration with aa.cpp to find the initial points)
+
+\section{Collinear normals}
+
+Given two paths we wish to find all pairs of points where a line through these points is parallel to the normal at that point.
+
+\section{Bounds: fast and exact}
+
+We provide two classes of bounding for functions, fast and exact.
+Fast is merely guaranteed to contain the function, Exact is the
+smallest bound that completely contains the function.
+
+\chapter{Topological operations: sweepline, boolops, sanitize}
+\section{Layer 1: Path Intersection}
+
+\begin{verbatim}
+src/path-intersection.cpp
+src/path-intersection.h
+src/crossing.h
+src/crossing.h
+src/toys/winding-test.cpp
+\end{verbatim}
+
+\subsection{Winding Number:}
+ the number of times the path goes around a particular point. Since paths are closed, the result is always integral. A counter-clockwise rotation corresponds to a positive wind, whereas a clockwise rotation is represented as a negative wind. Here, and throughout, the terms clockwise and counter-clockwise refer to the standard mathematical coordinate system rather than the computer, inverted y-axis, coordinate system.
+
+\subsection{Crossing:}
+ a location where two paths cross. This is a subset of the intersections of the paths, with the requirement that the winding must change. Crossings are stored as two time values and a boolean indicating the relationship between the curves at the crossing. A particular time value corresponds to the point on curve i at f, where i is the integral part, and f is the fractional part. This boolean is useful for making decisions during a traversal of crossings. It is defined as the sign of the cross product of the derivatives. When the paths are regions with the same winding, this indicates that along B, A crosses 'outside'.
+
+\subsection{Winding functions:}
+
+\verb|int winding(Path, Point);|
+\verb|bool path_direction(Path);| — assumes the path is simple (like a region)
+\verb|bool contains(Path, Point, bool even-odd = true);| — convenience wrapper around winding
+
+There are a few convenience typedefs for the popular crossing collections:
+\verb|typedef std::vector<Crossing> Crossings;|
+\verb|typedef std::vector<Crossings> CrossingSet;|
+
+The intersection system is designed to be modular, so that different algorithms may be used. The current algorithms are generic, and apply to anything that implements Curve. A custom curve type might provide an algorithm with special cases, and default to the generic algorithms. The following crossing functions use the DefaultCrosser, which is currently a simple binary search.
+
+\verb|Crossings crossings(Path, Path);| — finds the crossings between two paths
+\verb|CrossingSet crossings(PathVector, PathVector);| — finds the crossings between two sets of paths.
+
+
+\section{Layer 2: Regions}
+
+\begin{verbatim}
+src/region.cpp
+src/region.h
+\end{verbatim}
+
+\subsection{Region:}
+A limited point-set, defined by a boundary. This boundary is stored as a simple (non-self-crossing) path. Regions with positive winding (counter-clockwise) include points which are inside the boundary (fills), whereas Regions with negative winding include all points outside the boundary (holes). It also caches information such as winding direction and bounding box.
+
+Most of the public api for region simply wraps and caches queries to a Path. It also provides various convenience functions such as \verb|as_fill| and \verb|as_hole|. Region should usually only be used by clients of this library when dealing with paths with the same invariants as regions.
+
+
+\section{Layer 3: Shapes}
+
+\begin{verbatim}
+src/shape.cpp
+src/shape.h
+src/toys/boolops.cpp
+\end{verbatim}
+
+\subsection{Shape:}
+A point-set upon which boolean operations may be performed. Shapes are defined by a list of regions, where the resulting point-set is the cumulative intersection. Though it may be figured out from the regions, shapes also store whether the most outer paths are fill or hole. In other words, it stores the value of all the points completely outside the boundary of all its constituent regions. Shapes have the following invariants:
+
+\begin{itemize}
+\item{ The regions must not cross, or intersect for more than a point.}
+\item{ Filled regions only contain 'hole' regions, and vice-versa.}
+\item{ The 'top level' regions are all the same type (fill or hole).}
+\end{itemize}
+
+The shape files really constitutes the main body of my work, while the other stuff is peripheral support. It contains the algorithms which perform boolean operations and sanitization on shapes.
+
+The most important function looks like this:
+
+\verb|Shape shape_boolean(bool, Shape, Shape, CrossingSet);|
+
+If the initial boolean is false, the function unions the shapes. If it is true, it intersects them. The CrossingSet contains the crossings between the regions of the two shapes (an overload of this function allows omission of this parameter). The functions operation is actually surprisingly simple. It traverses the crossings, keeping track of which it has hit, collecting portions of path as it goes, until it reaches a crossing it has already visited, at which point it stores the path and starts again at an unvisited crossing.
+
+The real magic happens in the traversal. It took me quite a long time to really figure it out, but eventually I realized that quite simple logic would allow for traversing intersecting sets of regions correctly. If the boolean input doesn't equal the direction of the crossing, path A is followed, if they are equal, the path B is followed. I actually figured this particular bit fairly early on, but it was with a pair of regions. At the time I had no idea it could also work for doing boolean operations on sets of regions.
+
+While the previous function contains the main code, the actual public function intended for public use is quite different.
+\verb|Shape boolop(Shape, Shape, unsigned)|
+
+It takes the two shapes, and a flag integer specifying which of 16 boolean operations to perform. These flags use the first 4 bits to represent a boolean truth table. This is the function which is used in the boolops toy. Half of the operations, the complements, deal with cases where the output shape is filled in areas where neither of the input shapes are. The binary complement operation(~) applied to the flags, will yield flags specifying the complementary boolean operation. 6 of the combinations of flags don't even call \verb|shape_boolean| - the various identity, inversion, and none/all operations.
+
+\begin{verbatim}
+src/sweep.cpp
+src/sweep.h
+src/toys/sweep.cpp
+src/tests/intersection-test.cpp
+\end{verbatim}
+
+These are some support algorithms which provide a sweep on bounds, for efficient intersections between sets of bounded objects.
+\section{Current Issues}
+
+The main current deficiency is the sanitizer. I've only recently managed to get a path uncrosser somewhat working. I have a variety of sanitization methods implemented to various degrees, yet have only recently figured out the real reasons I haven't yet gotten it fully functioning. I hope to fix it soon, so that boolops may be used as a live-path-effect within inkscape (the live-path-effects project is a Summer-of-Code project). I think this is a good way to introduce the new boolean operations, and I definitely want my work to be used.
+
+There's also some code which is currently unused:
+
+I have an implementation of intersection routines which splits paths into monotonic portions, and uses the properties of these sections to perform very fast intersection. It works somewhat, but there are bugs I haven't had time to work out, which make the current implementation unstable. This intersection algorithm is especially valuable for self intersections, as monotonic splits are already required.
+
+\section{Possible Future Design Changes}
+
+It may be possible for the boolean operations to return a set of portions. When the actual path data is required, the portion operations would be applied. The actual boolean operations would take no time to execute, as all the computational load would be in the crossing-finder and path synthesizer. This would also work for the sanitization step, saving many spurious portions. The portions found in the boolean operations could be composed onto those produced by the sanitization routine, so that the unnecessary intermediary paths between sanitization and boolean operations are never generated.
+
+The current representation of crossings between sets of paths is fairly awkward. The main core of the data structure, storing times and the direction boolean is fine, the main issue is the storage methods of collections of these crossings. I've come to realize it's actually one of the main reasons that sanitization is as hard as it has been. I've figured out that a better representation would be for each crossing to store pointers to the next and previous crossings along both its participating paths. This would form a connectivity graph, where each vertex has a degree of 4. A dedicated structure such as this would likely be worth it, as algorithms that use it would be more efficient, simpler, and less buggy.
+
+Without too much work, it should also be possible to do boolean operations with non-filled paths. The main change would be to have \verb|shape_boolean| handle coincident crossings, and to derive the resultant region fill from the fill of the contributing regions. This would allow all combinations of boolean operations between filled regions and paths.
+
+\chapter{Acknowledgements and history}
+
+2Geom is a group project, having many authors and contributors. The
+original code was sketched out by Nathan Hurst and Peter Moulder for
+the Inkscape vector graphics program to provide well typed, correct
+and easy to use C++ classes. Since then many people have refined and
+debugged the code. One of the earliest C++ification projects for
+inkscape was replacing NRPoint with NR::Point.
+
+A conspicuous absence was a Path datatype, and indeed Inkscape
+developed at least 3 different internal path datatypes, plus several
+others in related projects. Considering the core importance of path
+operations in vector graphics, this led to much re-implementation of
+algorithms, numerous bugs, and many round trips converting between
+forms.
+
+Many attempts have been made to try and develop a single path data
+structure, but all were fated to sit in random SCMs scattered across
+the web.
+
+Several unrelated projects had copied out various portions of the NR
+code from Inkscape and in 2006 MenTaLguY and Nathan felt that it was
+time to separate out the geometry portions of inkscape into a
+separate library for general use and improvement. The namespace was
+changed from NR to Geom and a prototype for paths sketched out.
+Nathan studied the state of the art for computational geometry whilst
+MenTaLguY focused on the design of Paths.
+
+Before the re-merging of 2Geom with the inkscape svn HEAD it was felt
+that a few smaller projects should be ported to use 2Geom. Michael
+Wybrow's libavoid advanced connector routing system was ported first.
+
+--now.
+
+\pagebreak
+
+\section{People who have contributed to 2Geom}
+\begin{description}
+\item[Aaron C.\ Spike]
+\item[Alex Mac]
+\item[Fred:] livarot
+\item[Javier Sanchez-Reyes]
+\item[Jean-Francois Barraud]
+\item[Johan B.\ C.\ Engelen]
+\item[Jonathon Wright]
+\item[Joshua Blocher]
+\item[Kim Marriott]
+\item[Marco Cecchetti]
+\item[MenTaLguY]
+\item[Michael J.\ Wybrow]
+\item[Michael G.\ Sloan]
+\item[Nathan J.\ Hurst]
+\item[Peter J.\ R.\ Moulder]
+\end{description}
+
+\chapter{Appendix}
+\renewcommand{\thesection}{\Alph{section}}
+
+\section{Geometric Points}
+In standard geometry, points and vectors are quite distinguished: a
+point is a location, whereas a vector is an unbased direction and
+magnitude. Allowed operations on vectors and points also vary:
+
+\begin{tabular}{r l}
+ $P - P$ & $= V$ \\
+
+ $P - P$ & $= V$ \\
+
+ $P + V$ & $= P$ \\
+
+ $P - V$ & $= P$ \\
+
+ $V + V$ & $= V$ \\
+
+ $V - V$ & $= V$ \\
+
+ $V \times S$ & $= V$ \\
+
+ $V \div S$ & $= V$ \\
+\end{tabular}
+
+Here, $P$ represents points, $V$ represents vectors, and $S$ scalars.
+
+Ideally we would render these restrictions in code, as they would
+reinforce algorithm correctness. This is because as far as arithmetic
+operations go, the above are all that you sanely require, unless you
+are prematurely optimizing.
+
+\section{Understanding Matrices}
+
+\section{S-Power-Basis Explanation}
+
+\section{Concepts}
+
+The C++ Standard Template Library\cite{stl} introduces the notion of
+\emph{concepts}\cite{stl_concepts}, which specify families of types related
+by a common interface. In template-based programming with the STL, concepts
+serve a similar purpose to type classes in Haskell. While, unlike Haskell's
+language-level support for type classes, concept-checking is not directly
+supported by the C++ compiler or language, C++ libraries have been written
+which use template techniques to provide compile-time checking and enforcement
+of concepts\cite{boost_concept_check}.
+
+There are several important lib2geom concepts in this sense:
+
+\subsection{ScalarFunction}
+
+\subsubsection{Description}
+
+Scalar functions are C++ function objects which behave like functions
+with type {\tt double (double)}. They take a single time value and return
+a scalar, and are defined over at least the interval $[0, 1]$.
+
+\subsubsection{Refinement of}
+
+\subsubsection{Associated types}
+
+\subsubsection{Notation}
+
+\begin{tabular}{r l}
+ {\tt X} & A type which models ScalarFunction \\
+ {\tt a} & An object of type {\tt X} \\
+ {\tt t} & A time value of type {\tt double} \\
+ {\tt n} & A count of type {\tt unsigned int} \\
+\end{tabular}
+
+\subsubsection{Definitions}
+
+\subsubsection{Valid expressions}
+
+\begin{tabularx}{300pt}{X l X l}
+ Name & Expression & Type requirements & Return type \\
+ Evaluate & {\tt a(t)} & & {\tt double} \\
+ Evaluate & {\tt a.valueAt(t)} & & {\tt double} \\
+ Evaluate with derivatives & {\tt a.valueAndDerivativesAt(t, n, out)} & {\tt out} should be a model of OutputIterator whose {\tt value\_type} is convertible to & {\tt void} \\
+ Range & {\tt a.fastRange()} & & {\tt Range} \\
+ Range & {\tt a.exactRange()} & & {\tt Range} \\
+ SBasis & {\tt a.sbasis()} & & {\tt SBasis} \\
+ Subdivide & {\tt a.subdivide(start, end)} & & {\tt Piecewise<X>} \\
+\end{tabularx}
+
+\subsubsection{Expression semantics}
+
+\begin{tabularx}{300pt}{X l l X l}
+ \bf{Name} & \bf{Expression} & \bf{Precondition} & \bf{Semantics} & \bf{Postcondition} \\
+ Evaluate & {\tt a(t)} & $0\le t\le 1$ & Returns the value of the function at $t$; the function must be exact at $t = 0$ and $t = 1$ and defined over the interval $0\le t\le 1$ & \\
+ Evaluate & {\tt a.valueAt(t)} & $0 \le t \le 1$ & Returns the value of the function at {\tt t}; the function must be exact at $t = 0$ and $t = 1$ and defined over the interval $0 \le t \le 1$ & \\
+ Evaluate with derivatives & {\tt a.valueAndDerivativesAt(t, n, out)} & $0 \le t \le 1$ & Evaluates the function and the first n derivatives at {\tt t}, writing them to {\tt out} & $n + 1$ values have been written to {\tt out} \\
+ Range & {\tt a.fastRange()} & & The result should be a {\tt Range} which includes the function's range & \\
+ Range & {\tt a.exactRange()} & & The result should be a {\tt Range} representing the exact range of the function & \\
+ SBasis conversion & {\tt a.sbasis()} & & The result should be an sbasis approximation of the function & \\
+\end{tabularx}
+
+\subsubsection{Complexity guarantees}
+
+\subsubsection{Invariants}
+
+\subsubsection{Models}
+
+\subsection{Curve}
+
+\section{Location Sequences}
+
+Many algorithms are more efficient on a sorted sequence of locations,
+than calling the function repeatedly for each. So we have algorithms
+that take a sequence of locations, assumed in order, and perform an
+action on those. For example, cutting a path at one location is
+basically linear in the number of path segments, but cutting a path in
+10 locations is still about the same amount of work. Similarly,
+working out the arc length for a location is about the same amount of
+work as working out the arc length for 1000 locations on that path.
+
+Many operations are best described as returning an ordered set of
+locations. For example, we have a function that returns
+intersections between two paths. Rather than return just one
+intersection, we might return all intersections, either in order along
+the path, or in order of distance along other path.
+% I don't understand the distinction between "order along the path" and "order
+% of distance along the path". As for the case of a path that goes back on
+% itself, e.g. a path whose y coordinate is unchanging and whose x coordinate
+% goes from 0 to 10 then back to 5 then to 15, I think most callers (including
+% the dashes case below) want it to behave the same as if the y coordinate did
+% change, i.e. want the same point reported three times if there's an
+% intersection at x=7. Whereas the case of a "stationary path" (bezier whose
+% control points are all coincident) is considered to have just one point and
+% zero length. -- pjrm.
+
+Think about dashes: a dash is a fixed arclength offset. So rather
+than getting the location for a point at arc length 1, at arc length
+2, 3, 4, $\ldots$, up to the length of the curve, instead we just ask for all of
+these, and the algorithm can chug along the curve outputting the
+answer for each. The reason it is faster is because to work out the
+location at arc length say 100, we basically need to work out the
+length for many spots up to 100.
+
+Perhaps we then want to split the curve at each of those points. To
+split a segment at a location first requires finding that segment,
+then splitting it and finally constructing a new path to output a whole
+new path so we can fit the two new segments in. If we started at the
+beginning, and split at the first location, then that would be $n+1$ steps: $n$
+segs in the original, plus an extra one. If we wanted to split at 100
+points, it would be $n+1$ steps for the first, $n+2$ for the
+second, $\ldots$, $n+100$ steps for the last, this would take a total of $100n +
+100*101/2$ steps! Whereas, if we split as we went along, it would take
+just $n+100$ steps.
+
+The downside is that I'll probably not provide a separate split
+routine that takes a single point, to discourage people from making
+exactly that mistake.
+
+\bibliographystyle{plain} % A good style to use with the Harvard package
+\bibliography{manual}\label{chapter:bibliography}
+
+\end{document}
diff --git a/doc/manual2/ack b/doc/manual2/ack
new file mode 100644
index 0000000..c6325fd
--- /dev/null
+++ b/doc/manual2/ack
@@ -0,0 +1,47 @@
+h1. Acknowledgements and History
+2Geom is a group project, having many authors and contributors. The
+original code was sketched out by Nathan Hurst and Peter Moulder for
+the Inkscape vector graphics program to provide well typed, correct
+and easy to use C++ classes. Since then many people have refined and
+debugged the code. One of the earliest C++ification projects for
+inkscape was replacing NRPoint with NR::Point.
+
+A conspicuous absence was a Path datatype, and indeed Inkscape
+developed at least 3 different internal path datatypes, plus several
+others in related projects. Considering the core importance of path
+operations in vector graphics, this led to much re-implementation of
+algorithms, numerous bugs, and many round trips converting between
+forms.
+
+Many attempts have been made to try and develop a single path data
+structure, but all were fated to sit in random SCMs scattered across
+the web.
+
+Several unrelated projects had copied out various portions of the NR
+code from Inkscape and in 2006 MenTaLguY and Nathan felt that it was
+time to separate out the geometry portions of inkscape into a
+separate library for general use and improvement. The namespace was
+changed from NR to Geom and a prototype for paths sketched out.
+Nathan studied the state of the art for computational geometry whilst
+MenTaLguY focused on the design of Paths.
+
+Before the re-merging of 2Geom with the inkscape svn HEAD it was felt
+that a few smaller projects should be ported to use 2Geom. Michael
+Wybrow's libavoid advanced connector routing system was ported first.
+
+(TODO: did this happen? also, add the rest of history..)
+
+h2. People who have contributed to 2Geom
+* Aaron C. Spike
+* Alex Mac
+* Fred: livarot
+* Javier Sanchez-Reyes
+* Jean-Francois Barraud
+* Jonathon Wright
+* Joshua Blocher
+* Kim Marriott
+* MenTaLguY
+* Michael J. Wybrow
+* Michael G. Sloan
+* Nathan J. Hurst
+* Peter J. R. Moulder
diff --git a/doc/manual2/concepts b/doc/manual2/concepts
new file mode 100644
index 0000000..e89bf55
--- /dev/null
+++ b/doc/manual2/concepts
@@ -0,0 +1,128 @@
+h1. Concept Checking
+
+The C++ Standard Template Library introduces the notion of _concepts_,
+which specify families of types related by a common interface. In
+template-based programming with the STL, concepts serve a similar
+purpose to type classes in Haskell. While, unlike Haskell's language-level
+support for type classes, concept-checking is not directly supported by the
+C++ compiler or language, C++ libraries have been written which use template
+techniques to provide compile-time checking and enforcement of concepts.
+We use the Boost Concept Checking library.
+
+h2. Lib2geom's 'Concepts'
+
+There are several important lib2geom 'concepts'.
+
+h3. *FragmentConcept*
+
+This is perhaps the most important concept within lib2geom, as it defines
+the interface for the basic, one-dimensional functions. Fragments are
+defined on the interval [0,1], which is referred to as the _intended domain_
+of the function. Functions may be well defined for all values (many are),
+but the 0-to-1 domain has significant semantic value. When the functions
+are used to represent a *Curve*, 0 is the start and 1 is the end.
+
+h4. @ T::output_type @
+
+Every fragment must typedef an *output_type*. This is usually *Coord*, however,
+in order to support considering @D2<T>@ a fragment, this typedef was added.
+This information is also used by the compiler to infer the proper bounds and
+sbasis types.
+
+h4. Value Query
+
+<pre><code>
+output_type T::valueAt(double);
+output_type T::operator()(double);
+output_type T::at0();
+output_type T::at1();
+</code></pre>
+
+*FragmentConcept* defines several methods for retrieving the value at a point.
+One method is to use the *valueAt* function, which returns output_type given
+a t-value. Fragments are also functors, which in C++ lingo means they look
+like function calls, as they overload the () operator. This is essentially
+the same as calling valueAt. The functions *at0* and *at1* are also
+provided, and should be used whenever the start or end of the function is
+required, as many functions directly store this information.
+
+h4. @ sbasis_type T::toSBasis() @
+
+As *SBasis* will be the main function representation, it is desirable to always
+be able to approximate and deal with other functions in this way. Therefore,
+the *toSBasis* function is required. When *output_type* is @double@,
+@sbasis_type@ is *SBasis*. When *output_type* is *Point*, @sbasis_type@ is
+*SBasisCurve*.
+
+(TODO: in writing this it occurs to me that toSBasis should take a tolerance)
+
+h4. @ T reverse(T) @
+
+As most of the implementors of fragment consider functions in a fairly
+symmetric way, the *reverse* function was included in the *FragmentConcept*.
+*reverse* flips the function's domain on 0.5, such that f'(t) = f(1-t).
+
+h4. Bounds
+
+<code><pre>
+bounds_type bounds_fast(T);
+bounds_type bounds_exact(T);
+bounds_type bounds_local(T, Interval);
+</pre></code>
+
+Finding the bounds of a function is essential for many optimizations and
+algorithms. This is why we provide 3 functions to do it. *bounds_fast*
+provides a quick bounds which contains the actual bounds of the function.
+This form is ideal for optimization, as it hopefully does not require too
+much computation. *bounds_exact*, on the other hand, provides the exact
+bounds of the function. *bounds_local* only returns the bounds of an
+interval on the function - at the moment it is unclear if this is exact.
+When *output_type* is @double@, @bounds_type@ is *Interval*. When
+*output_type* is @Point@, @bounds_type@ is *Rect*.
+
+See the linear.h code for an example of an implementation of *FragmentConcept*.
+
+h3. *OffsetableConcept*
+
+*OffsetableConcept* defines what it means to be offsetable. Like
+*FragmentConcept*, this concept requires an output_type, which is used
+as the offset type. This still makes since when the implementor is
+also a fragment, as in pretty much all cases you would want to offset
+a function using the same type it outputs.
+
+The following operators are defined by *OffsetableConcept*:
+
+@T + output_type, T - output_type, T += output_type, T -= output_type@,
+
+h3. *ScalableConcept*
+
+*ScalableConcept* defines what it means to be scalable. Like
+*OffsetableConcept*, it requires an output_type, which is used as the
+scalar-type. This is an assumption that may not pan out in the future,
+however, for all function types we've used this always applies.
+Technically points should not be multiplicable, however, they provide a
+convenient storage mechanism for non-uniform scaling. If this changes
+in the future, the implementations will remain the same, while the
+concept definitions are loosened.
+
+The following operators are defined by *ScalableConcept*:
+@T * scalar_type, T / scalar_type, T *= scalar_type, T /= scalar_type, -x@,
+
+h3. *AddableConcept*
+
+*AddableConcept* defines a concept for classes which are closed under
+addition (the classes may be added to themselves, and the result is the
+same type). The following operators are included:
+
+@x + y, x - y, x += y, x -= y@
+
+h3. *MultiplicableConcept*
+
+*MultiplicableConcept* defines a concept for classes which are closed under
+multiplication (the classes may be multiplied by themselves, and the result
+is the same type). The following operators are included:
+
+@x * y, x *= y@
+
+At some point a DividableConcept may be implemented, however, at the moment
+it is not very useful.
diff --git a/doc/manual2/d2 b/doc/manual2/d2
new file mode 100644
index 0000000..b4769e0
--- /dev/null
+++ b/doc/manual2/d2
@@ -0,0 +1,106 @@
+h1. Dealing with two Dimensions: *D2*
+
+After writing a few classes for two dimensional objects, we realized
+that there is a lot of boilerplate associated with what is essentially
+lifting one dimensional concepts into two. Instead of frequently
+rewriting this code, we instead created the *D2* template class.
+
+For example, a point in space might be represented by *D2<double>*.
+This may, in fact, become the actual representation for Point.
+We have not yet replaced Point with this, as not all of Points
+operations have been ported (or are applicable), and we are not
+yet sure if there is 0 performance loss.
+
+(TODO remove previous stuff if D2<double> becomes point repr)
+
+h2. Component Access
+
+One might expect such an object to have @.x@ and @.y@ fields, however,
+it instead consists of 2 element array. With LibNR, it was found that
+the availability of @.x@ and @.y@ encouraged people to attempt to
+inline operations rather than using the operators, perhaps in (vain)
+pursuit of a performance enhancement. By using an array, we encourage
+people to think about points as symmetric objects and discourage
+direct use of the components. However, we still provide direct access
+for the rare occasion that it is needed. Even in these cases, the array
+method reduces bugs by encouraging iteration over the array rather than
+explicit element reference.
+
+The components of a *D2* are accessed through the indexing operator, [].
+The input value to the index operator is the @enum@ *Dim2*, which
+defines *X* = 0 and *Y* = 1. This is to encourage using the
+@for(int d=0; i<2; i++)@ idiom when normal operations do not suffice.
+
+h2. Arithmetic Operators
+
+@D2<T>@ implements the *AddableConcept*, *OffsetableConcept*, and
+*ScalableConcept* (if @T@ implements them as well) yielding the
+following operators:
+
+<pre><code>
+AddableConcept: x + y, x - y, x += y, x -= y
+OffsetableConcept: x + p, x - p, x += p, x -= p
+ScalableConcept: x * p, x / p, x *= p, x /= p, -x
+ x * d, x / d, x *= d, x /= d
+</code></pre>
+
+(where @x@ and @y@ are *D2*, d is *Coord*, and @p@ is a *Point* and all
+return @D2<T>@)
+
+These operators all just apply the operation on @T@ to the components.
+So, @a + b@ just returns @D2<T>(a[X] + b[X], a[Y] + b[Y])@, though the
+actual code uses a loop (which is unrolled) in order to avoid
+bugs.
+
+h2. Geometric Operations
+
+<pre><code>
+T dot(D2<T> const &, D2<T> const &);
+T cross(D2<T> const &, D2<T> const &);
+</code></pre>
+
+The *dot*:http://en.wikipedia.org/wiki/Dot@product and
+*cross*:http://en.wikipedia.org/wiki/Cross@product products are defined
+on D2<T> when T implements *AddableConcept* and *MultiplicableConcept.
+The cross function returns the length of the resultant 3d vector
+perpendicular to the 2d plane.
+
+@ D2<T> operator*(D2<T> const &, Matrix const &)@
+
+This operation applies an affine transformation to the 2d object.
+
+h2. Fragment Lifting
+
+*D2<T>* also implements FragmentConcept if T implements it as well,
+allowing *D2* to lift one dimensional functions into two-dimensional
+parametric curves. As a fragment, a *D2* will represent a function
+from a double to a Point.
+
+h3. Fragment Operations
+
+In addition to the normal set of Fragment methods, D2 has the following
+functions:
+
+h4. @ D2<T> compose(D2<T> const &a, T const &b); @
+
+The *compose* function is defined when @T@ is a function representation which
+supports composition. The only forms in 2geom are *SBasis* and *SBasis2d*.
+The *D2* *compose* function composes @b@ on both components of @a@. This
+makes sense, as a D2<SBasis> is double -> D2<double> and the function for
+composition is double -> double. One way to think of composition is that
+the output is equivalent to applying @b@ to the input, and then applying a
+to @b@'s output.
+
+h4. @ D2<T> compose_each(D2<T> const &a, D2<T> &b); @
+
+The *compose_each* function is similar to the *compose* function, except that
+@b@ is also a *D2*, so instead of composing the same function on each component,
+the two functions in @b@ are used.
+
+
+h4. @ Point D2<T>::operator()(double x, double y) const @
+
+*D2* wraps this operator for when @T@ is a function taking a 2 component input.
+The only case of this currently within 2geom is SBasis2d.
+
+(TODO: derivative/integral)
diff --git a/doc/manual2/geometric primitives b/doc/manual2/geometric primitives
new file mode 100644
index 0000000..b78370e
--- /dev/null
+++ b/doc/manual2/geometric primitives
@@ -0,0 +1,65 @@
+h1. Geometric Primitives
+
+What good is a geometry library without geometric primitives? By this
+I mean the very basic stuff, Points/Vectors, Matrices, etc.
+
+2geom's primitives are descendant from libNR's geometric primitives.
+They have been modified quite a bit since that initial import.
+
+h2. Point
+
+!media/point.png!
+
+The mathematical concepts of points and vectors are merged into the
+2geom class called *Point*. See Appendix A for a further
+discussion of this decision.
+
+Point may be interpreted as a D2<double> with some additional operations.
+
+(TODO: document these ops.)
+
+\section{Transformations}
+
+Affine transformations are either represented with a canonical 6
+element matrix, or special forms.
+
+\subsection{Scale}
+
+\includegraphics[height=50mm]{media/scale.png}
+
+A \code{Scale} transformation stores a vector representing a scaling
+transformation.
+
+\subsection{Rotate}
+
+\includegraphics[height=50mm]{media/rotate.png}
+
+A \code{Rotate} transformation uses a vector(\code{Point}) to store
+a rotation about the origin.
+
+In correspondence with mathematical convention (y increasing upwards),
+0 degrees is encoded as a vector pointing along the x axis, and positive
+angles indicate anticlockwise rotation. So, for example, a vector along
+the y axis would encode a 90 degree anticlockwise rotation of 90 degrees.
+
+In the case that the computer convention of y increasing downwards,
+the \verb}Rotate} transformation works essentially the same, except
+that positive angles indicate clockwise rotation.
+
+\subsection{Translate}
+
+\includegraphics[height=70mm]{media/translate.png}
+
+A \code{Translate} transformation is a simple vector(\code{Point})
+which stores an offset.
+
+\subsection{Matrix}
+
+\includegraphics[height=70mm]{media/matrix.png}
+
+A \code{Matrix} is a general affine transform. Code is provided for
+various decompositions, constructions, and manipulations. A
+\code{Matrix} is composed of 6 coordinates, essentially storing the
+x axis, y axis, and offset of the transformation. A detailed
+explanation for matrices is given in Appendix B.
+
diff --git a/doc/manual2/introduction b/doc/manual2/introduction
new file mode 100644
index 0000000..f8c71fc
--- /dev/null
+++ b/doc/manual2/introduction
@@ -0,0 +1,41 @@
+h1. Introduction
+
+This manual focuses on the lib2geom computational geometry framework.
+The main goal of this framework is the eventual replacement of
+Inkscape's multiple and shoddy geometry frameworks. As with any decent
+module or lib, 2geom is designed to achieve the desired functionality
+while maintaining a generality encouraging usage within other
+applications. The focus on robust, accurate algorithms, as well as
+utilization of newer and better representations makes the lib
+very attractive for many applications.
+
+h2. Design Considerations
+
+2Geom is written with a functional programming style in mind.
+Generally data structures are considered immutable and rather than
+assignment we use labeling. However, C++ can become unwieldy if
+this is taken to extreme and so a certain amount of pragmatism is
+used in practice. In particular, usability is not forgotten in the
+mires of functional zeal.
+
+The code relies strongly on the type system and uses some of the more
+'tricky' elements of C++ to make the code more elegant and 'correct'.
+Despite this, the intended use of 2Geom is a serious vector graphics
+application. In such domains, performance is still used as a quality
+metric, and as such we consider inefficiency to be a bug, and have
+traded elegance for efficiency where it matters.
+
+In general the data structures used in 2Geom are relatively 'flat'
+and require little from the memory management. Currently most data
+structures are built on standard STL headers, and new and delete are
+used sparingly. It is intended for 2Geom to be fully compatible with
+Boehm garbage collector though this has not yet been tested.
+
+h2. Toy-Based Development
+
+We have managed to come up with a method of library development
+which is perfect for geometry - the development of toys exemplifying
+a feature while the feature is perfected. This has somewhat subsumed
+the role of tests, and provides immediate motivation/reward for work.
+
+!media/gear.png!
diff --git a/doc/manual2/piecewise b/doc/manual2/piecewise
new file mode 100644
index 0000000..9f1bd98
--- /dev/null
+++ b/doc/manual2/piecewise
@@ -0,0 +1,134 @@
+h1. *Piecewise*
+
+In order to represent functions with a complex shape, it is necessary
+to define functions in a piecewise manner. In the graphics world this
+sort of function, when parametric, is often referred to as a 'spline'.
+Even beyond the representation of paths, it is also often necessary
+for mathematical operations to return piecewise functions, as otherwise
+the single-fragment versions would require an inordinate degree to
+still be accurate. An example of this is the *inverse* function.
+
+In the world of lib2geom, this is implemented as the *Piecewise*
+template class. It manages a sequence of fragment 'segments' and the
+cuts between them. These cuts are the various t-values which separate
+the different segments.
+
+h2. Cuts
+
+The first and last cuts of a piecewise define it's intended range, and
+the intermediary cuts separate the segments. With indices, segment i
+is always bordered on the left with cut i and on the right with cut i+1.
+In general, c = s+1, where c is the number of cuts and s is the number
+of segments. These invariants are checked by the
+@bool Piecewise<T>::invariants();@ method.
+
+The cuts essentially define the position and scale of each segment.
+For example, if the left and right cuts are 0.5 apart, the segment is
+half its regular size; the derivative will be twice as big.
+
+h4. Cut Query Functions
+
+<pre><code>
+unsigned Piecewise<T>::segN(double, int low = 0, int high = -1) const;
+double Piecewise<T>::segT(double, int = -1) const;
+double mapToDomain(double t, unsigned i) const;
+</code></pre>
+
+These functions use the cut information to ascertain which segment a
+t-value lies within ( *segN* ), and what the t-value is for that segment
+at that particular point ( *segT* ). *segN* takes two optional parameters
+which limit the range of the search, and are used internally as it is
+defined as a recursive binary search. These may be used if you are sure
+that the desired segment index lies within the range. *segT* takes an
+optional parameter for the case where you already know the segment number.
+
+mapToDomain is the inverse of segT, as it takes a t-value for a particular
+segment, and returns the global piecewise time for that point.
+
+h4. @ Interval Piecewise<T>::domain() const; @
+
+The *domain* function returns the Interval of the intended domain of the
+function, from the first cut to the last cut.
+
+h4. Cut Modification Functions
+
+<pre><code>
+void Piecewise<T>::offsetDomain(double o)
+void Piecewise<T>::scaleDomain(double s)
+void Piecewise<T>::setDomain(Interval dom)
+</code></pre>
+
+These functions very simply transform the cuts with linear transformations.
+
+h3. Technical Details
+
+As the cuts are simply a public std::vector, they may also be accessed as
+@pw.cuts@.
+
+While the actual segments begin on the first cut and end on the last,
+the function is defined throughout all inputs by extending the first
+and last segments. The exact switching between segments is arbitrarily
+such that beginnings (t=0) have priority over endings (t=1). This only
+really matters if it is discontinuous at that location.
+
+In the context of 2d parametrically defined curves, the usefulness of cuts
+becomes less apparrent, as they make no real difference for the display
+of the curves. Rather, cuts become more of an agreement between various
+functions such that the proper data aligns.
+
+h2. Construction
+
+Most of the time there is no need for raw construction of *Piecewise*
+functions, as they are usually obtained from operations and other sources.
+
+The following constructors defined for *Piecewise*:
+* The blank constructor
+* A constructor which explicitly lifts a fragment to a *Piecewise* on [0,1]
+* A constructor which takes the *output_type*, and creates a constant function
+
+<pre><code>
+void Piecewise<T>::push_seg(T);
+void Piecewise<T>::push_cut(double);
+void Piecewise<T>::push(T, double);
+</code></pre>
+
+The usual method for raw construction is to construct a blank *Piecewise*
+function, and use these push methods to load the content. *push_seg* and
+*push_cut* simply add to the segment and cut lists, although *push_cut*
+also checks that the cut time is larger than the last cut. The current
+recommended method for calling these functions is to have one initial
+*push_cut*, followed by successive calls to *push*, as this will guarantee
+that the cuts and segments properly align.
+
+h2. Operations
+
+h3. Arithmetic
+
+*Piecewise* has many arithmetic operations, and implements
+*OffsetableConcept*, *ScalableConcept*, *AddableConcept*, and
+*MultiplicableConcept*. The operations which operate on two Piecewise
+functions (Addable and Multiplicable) work by interleaving the cuts using
+mutual *partition* calls, and iterating the resulting segments.
+
+h3. Fragment Wrapping
+
+While *Piecewise* is not a fragment (it does not have the [0,1] domain),
+it has many functions reminiscient of *FragmentConcept*, including the
+bounds functions, () and valueAt.
+
+(TODO: reverse function?)
+
+h3. Concatenation
+
+<pre><code>
+void Piecewise<T>::concat(const Piecewise<T> &other);
+void Piecewise<T>::continuousConcat(const Piecewise<T> &other);
+</code></pre>
+
+These functions efficiently append another *Piecewise* to the end of a
+*Piecewise*. They offset the _other_ *Piecewise* in time such that it is
+flush with the end of this *Piecewise*. *continuousConcat* is basically
+the same except that it also offsets in space so the functions also match
+in value.
+
+(TODO: compose/derivative/integral)
diff --git a/doc/manual2/s-basis b/doc/manual2/s-basis
new file mode 100644
index 0000000..5f2ddfb
--- /dev/null
+++ b/doc/manual2/s-basis
@@ -0,0 +1,91 @@
+h1. S-Power-Basis-Forms
+
+2Geom provides a very powerful algebra for modifying paths. Although
+paths are kept in an extended SVG native form where possible, many
+operations require a more mathematical form. Our prefferred form is
+a sequence of s-power basis polynomials, henceforth referred to as
+s-basis. We may convert to this form, perform the required operations
+and convert back, approximating to a requested tolerance as required.
+
+The precise details of the s-basis form are beyond the scope of this
+manual - the interested reader should consult \cite{SanchezReyes1997,SanchezReyes2000,SanchezReyes2001,SanchezReyes2003,SanchezReyes2004}.
+An elementary, functional description is given in Appendix C.
+
+(TODO: work out textile citations, math inclusion)
+
+Geometrically important properties:
+* exact representation of bezier segments
+* low condition number on bezier conversion.
+* strong convergence guarantees
+* $C^0$ continuity guarantee
+
+The following operations are directly implementable and are very efficient:
+* fast conversion from all svg elements
+* basic arithmetic - @+@, @-@, $\times$, $\div$
+* algebraic derivative and integral
+* elementary trigonometric functions: $\sqrt{\cdot}$, $\sin(\cdot)$, $\cos(\cdot)$, $\exp(\cdot)$
+* efficient degree elevation and reduction
+* function inversion
+* exact solutions for many non trivial operations
+* root finding
+* composition
+
+All of these operations are fast. For example, multiplication of two
+beziers by converting to s-basis form, multiplying and converting back
+takes roughly the same time as performing the bezier multiplication
+directly, and furthermore, subdivision and degree reduction are
+straightforward in this form.
+
+h2. Implementation
+
+h3. *Linear*
+
+The *Linear* class represents a linear function, mostly for use as a
+building block for *SBasis*. *Linear* fully implements *AddableConcept*,
+*OffsetableConcept*, and *ScalableConcept* yielding the following operators:
+
+<pre><code>
+ AddableConcept: x + y, x - y, x += y, x -= y
+ OffsetableConcept: x + d, x - d, x += d, x -= d
+ ScalableConcept: x * d, x / d, x *= d, x /= d, -x
+</code></pre>
+
+(where @x@ and @y@ are *Linear*, d is *Coord*, and all return *Linear*)
+
+As *Linear* is a basic function type, it also implements the *FragmentConcept*.
+
+The main *Linear* constructor accepts two *Coord* values, one for the Linear's
+value at 0, and one for its value at 1. These may then later be accessed and
+modified with the indexing operator, @[]@, with a value of 0 or 1.
+
+h3. *SBasis*
+
+The *SBasis* class provides the most basic function form,
+$f(t) \rightarrow y$. *SBasis* are made up of multiple *Linear* elements,
+which store to/from values for each polynomial coefficient.
+
+*SBasis*, like *Linear*, above, fully implements *AddableConcept*,
+*OffsetableConcept*, and *ScalableConcept*.
+
+As *SBasis* is a basic function type, it implements the *FragmentConcept*.
+
+Usually you do not have to directly construct SBasis, as they are obtained
+one way or another, and many of the operations are defined, however, *SBasis*
+may be constructed as an implicit *Linear* cast, as a copy, or as a blank.
+The class is actually an extension of @std::vector<Linear>@. This provides
+the primary method of raw *SBasis* construction -- @push_back(Linear)@, which
+adds another coefficient to the *SBasis*.
+
+*SBasis* also provides the indexing accessor/mutator, and due to its vector
+nature, iteration.
+
+(TODO: wouldn't the indexing be provided by vector any way?)
+
+h3. *SBasis2D*
+
+SBasis2D provides a multivariate form - functions of the form
+$f(u,v) \rightarrow z$. These can be used for arbitrary distortion
+functions (take a path $p(t) \rightarrow (u,v)$ and a pair of surfaces
+$f(u,v),g(u,v)$ and compose: $q(t) = (f(p(t)), g(p(t)))$.
+
+(TODO: flesh out this section)
diff --git a/doc/media/2geom-logo.png b/doc/media/2geom-logo.png
new file mode 100644
index 0000000..af9a33c
--- /dev/null
+++ b/doc/media/2geom-logo.png
Binary files differ
diff --git a/doc/media/Rect.svg b/doc/media/Rect.svg
new file mode 100644
index 0000000..dab8ebd
--- /dev/null
+++ b/doc/media/Rect.svg
@@ -0,0 +1,283 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="Rect.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="8"
+ inkscape:cx="84.25586"
+ inkscape:cy="337.26288"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="g2227"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ <g
+ id="g2227"
+ transform="matrix(1.448783,0,0,1.133904,-50.0431,-19.23223)">
+ <rect
+ style="fill:none;fill-opacity:1;stroke:black;stroke-width:0.24958529;stroke-miterlimit:4;stroke-dasharray:1.49751161, 1.49751161;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2208"
+ width="80.412262"
+ height="101.14787"
+ x="71.123871"
+ y="93.46669" />
+ <path
+ sodipodi:type="arc"
+ style="fill:black;fill-opacity:1;stroke:none;stroke-width:0.31989604;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2212"
+ sodipodi:cx="160"
+ sodipodi:cy="193"
+ sodipodi:rx="1.25"
+ sodipodi:ry="1.25"
+ d="M 161.25 193 A 1.25 1.25 0 1 1 158.75,193 A 1.25 1.25 0 1 1 161.25 193 z"
+ transform="matrix(0.690234,0,0,0.881909,41.08823,-76.69567)" />
+ <text
+ xml:space="preserve"
+ style="font-size:7.80207539px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="172.75986"
+ y="83.146294"
+ id="text3986"
+ transform="scale(0.88468,1.130352)"><tspan
+ sodipodi:role="line"
+ id="tspan3988"
+ x="172.75986"
+ y="83.146294">P<tspan
+ style="font-size:6.24165869px"
+ id="tspan3990"
+ dx="-1.7240311"
+ dy="1.9998763">max</tspan></tspan></text>
+ <path
+ transform="matrix(0.690234,0,0,0.881909,-39.39108,24.30456)"
+ d="M 161.25 193 A 1.25 1.25 0 1 1 158.75,193 A 1.25 1.25 0 1 1 161.25 193 z"
+ sodipodi:ry="1.25"
+ sodipodi:rx="1.25"
+ sodipodi:cy="193"
+ sodipodi:cx="160"
+ id="path3992"
+ style="fill:black;fill-opacity:1;stroke:none;stroke-width:0.31989604;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <text
+ transform="scale(0.88468,1.130352)"
+ id="text3994"
+ y="176.88782"
+ x="67.843575"
+ style="font-size:7.80207253px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ y="176.88782"
+ x="67.843575"
+ id="tspan3996"
+ sodipodi:role="line">P<tspan
+ dy="1.9998755"
+ dx="-1.7240307"
+ id="tspan3998"
+ style="font-size:6.24165726px">min</tspan></tspan></text>
+ </g>
+</svg>
diff --git a/doc/media/bezier-curve-evaluation.png b/doc/media/bezier-curve-evaluation.png
new file mode 100644
index 0000000..adb2476
--- /dev/null
+++ b/doc/media/bezier-curve-evaluation.png
Binary files differ
diff --git a/doc/media/convex.png b/doc/media/convex.png
new file mode 100644
index 0000000..a5eba70
--- /dev/null
+++ b/doc/media/convex.png
Binary files differ
diff --git a/doc/media/convex.svg b/doc/media/convex.svg
new file mode 100644
index 0000000..fea1694
--- /dev/null
+++ b/doc/media/convex.svg
@@ -0,0 +1,232 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="convex.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="93.656296"
+ inkscape:cy="405.77902"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="g2919"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 210.9464,14.577576 189.65976,11.948591 173.48938,20.230605 C 169.70978,22.166415 166.20969,24.698322 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccccscccscccsccccccccccccc" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:0.99999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 42.801407,152.5919 L 57.629203,134.09469 L 102.53048,88.663078 L 109.248,85.127547 L 116.31907,86.364976 L 164.40234,135.15534 L 182.61033,153.36334 L 166.1701,178.99597 L 153.61895,185.5367 L 148.31566,188.71869 L 119.58944,200.82789 L 105.27053,204.27504 L 81.58245,189.95612 L 71.594568,181.736 L 42.801407,152.5919 z "
+ id="path2213"
+ transform="matrix(3.126016,0,0,3.126016,-151.4725,-254.7792)"
+ sodipodi:nodetypes="ccccccccccccccc" />
+ </g>
+ <g
+ id="g2227"
+ transform="matrix(1.448783,0,0,1.133904,-50.0431,-19.23223)" />
+</svg>
diff --git a/doc/media/coords.png b/doc/media/coords.png
new file mode 100644
index 0000000..343a993
--- /dev/null
+++ b/doc/media/coords.png
Binary files differ
diff --git a/doc/media/coords.svg b/doc/media/coords.svg
new file mode 100644
index 0000000..c780df0
--- /dev/null
+++ b/doc/media/coords.svg
@@ -0,0 +1,142 @@
+<?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="151.71198"
+ height="124.79403"
+ id="svg59"
+ version="1.1"
+ inkscape:version="0.48+devel r10332"
+ sodipodi:docname="coords.svg"
+ inkscape:export-filename="/home/tweenk/src/2geom-bzr/doc/media/coords.png"
+ inkscape:export-xdpi="90.170868"
+ inkscape:export-ydpi="90.170868">
+ <defs
+ id="defs61">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path802"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker51"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path53"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="43.083365"
+ inkscape:cy="80.424336"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1105"
+ inkscape:window-height="815"
+ inkscape:window-x="1"
+ inkscape:window-y="31"
+ inkscape:window-maximized="0"
+ inkscape:object-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true" />
+ <metadata
+ id="metadata64">
+ <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"
+ transform="translate(-166.59375,-740.58868)">
+ <path
+ transform="matrix(1,0,0,-1,80.151798,912.09043)"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-end:url(#marker51);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 165.71428,146.6479 c 0,-16.18137 -7.27115,-30.66448 -18.72437,-40.36025"
+ id="path12"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 193.00893,743.22826 0,119.2857"
+ id="path14"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 166.58037,765.44253 144.99998,0"
+ id="path16"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial"
+ x="311.07623"
+ y="756.33673"
+ id="text3166"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="311.07623"
+ y="756.33673">X</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text3170"
+ y="862.76532"
+ x="208.93338"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial"
+ xml:space="preserve"><tspan
+ y="862.76532"
+ x="208.93338"
+ id="tspan3172"
+ sodipodi:role="line">Y</tspan></text>
+ <path
+ style="color:#000000;fill:#ff933d;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 26.41518,24.85385 69.642857,81.936886"
+ id="path4158"
+ inkscape:connector-curvature="0"
+ transform="translate(166.59375,740.58868)" />
+ </g>
+</svg>
diff --git a/doc/media/ellipse-angular-coordinates.png b/doc/media/ellipse-angular-coordinates.png
new file mode 100644
index 0000000..c79eb47
--- /dev/null
+++ b/doc/media/ellipse-angular-coordinates.png
Binary files differ
diff --git a/doc/media/ellipse-angular-coordinates.svg b/doc/media/ellipse-angular-coordinates.svg
new file mode 100644
index 0000000..e9311ec
--- /dev/null
+++ b/doc/media/ellipse-angular-coordinates.svg
@@ -0,0 +1,249 @@
+<?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="720"
+ height="320"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46+devel r21383"
+ version="1.0"
+ sodipodi:docname="ellipse-angular-coordinates.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/usr/src/2geom-svn/doc/media/ellipse-angular-coordinates.png"
+ inkscape:export-xdpi="66.050827"
+ inkscape:export-ydpi="66.050827">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path805"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(0.2,0,0,0.2,1.2,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path802"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Send"
+ style="overflow:visible">
+ <path
+ id="path808"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend"
+ style="overflow:visible">
+ <path
+ id="path796"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <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="0.7"
+ inkscape:cx="408.19305"
+ inkscape:cy="259.59627"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-bbox="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-global="false"
+ inkscape:window-width="1024"
+ inkscape:window-height="737"
+ inkscape:window-x="0"
+ inkscape:window-y="1" />
+ <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></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-732.36214)">
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:none;stroke:#558ada;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path12"
+ sodipodi:cx="112.85714"
+ sodipodi:cy="146.6479"
+ sodipodi:rx="52.857143"
+ sodipodi:ry="52.857143"
+ d="m 165.71428,146.6479 a 52.857143,52.857143 0 1 1 -105.714284,0 52.857143,52.857143 0 1 1 105.714284,0 z"
+ transform="translate(22.612039,728.7384)" />
+ <path
+ style="color:#000000;fill:none;stroke:#ed0000;stroke-width:3;stroke-linecap:square;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 135.46917,875.3863 44.82926,-28.01715"
+ id="path1992" />
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 135.46917,988.31486 0,-209.99999"
+ id="path14" />
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 35.469178,875.3863 218.571412,0"
+ id="path16" />
+ <path
+ transform="matrix(1.3986138,1.6285045,-0.49765802,0.42740527,477.84037,634.78699)"
+ d="m 165.71428,146.6479 a 52.857143,52.857143 0 1 1 -105.714284,0 52.857143,52.857143 0 1 1 105.714284,0 z"
+ sodipodi:ry="52.857143"
+ sodipodi:rx="52.857143"
+ sodipodi:cy="146.6479"
+ sodipodi:cx="112.85714"
+ id="path2192"
+ style="color:#000000;fill:none;stroke:#558ada;stroke-width:2.52805805;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <path
+ id="path2194"
+ d="m 562.70341,881.25342 76.64178,61.02997"
+ style="color:#000000;fill:none;stroke:#ed0000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-opacity:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 386.66677,790.99733 0,27.3541 -99.76009,0 0,119.77841 99.76009,0 0,27.3541 51.36989,-87.22345 -51.36989,-87.26316 z"
+ id="rect2196" />
+ <text
+ xml:space="preserve"
+ style="font-size:21.50658035px;text-align:start;text-anchor:start"
+ x="296.32678"
+ y="856.89478"
+ id="text2201"><tspan
+ sodipodi:role="line"
+ id="tspan2203"
+ x="296.32678"
+ y="856.89478">rotation,</tspan><tspan
+ sodipodi:role="line"
+ x="296.32678"
+ y="883.77802"
+ id="tspan2205">scaling,</tspan><tspan
+ sodipodi:role="line"
+ x="296.32678"
+ y="910.66125"
+ id="tspan2231">translation</tspan></text>
+ <text
+ id="text2211"
+ y="1011.1191"
+ x="134.7551"
+ style="font-size:21.50658035px;text-align:center;text-anchor:middle"
+ xml:space="preserve"><tspan
+ id="tspan2215"
+ y="1011.1191"
+ x="134.7551"
+ sodipodi:role="line">mapping angle to point</tspan><tspan
+ y="1038.0023"
+ x="134.7551"
+ sodipodi:role="line"
+ id="tspan2219">on the unit circle</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:21.50658035px;text-align:center;text-anchor:middle"
+ x="571.89789"
+ y="1011.1191"
+ id="text2221"><tspan
+ id="tspan2225"
+ sodipodi:role="line"
+ x="571.89789"
+ y="1011.1191">resulting point</tspan><tspan
+ sodipodi:role="line"
+ x="571.89789"
+ y="1038.0023"
+ id="tspan2229">on the ellipse</tspan></text>
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;marker-end:url(#Arrow1Mend)"
+ d="m 445.21287,881.25343 254.28572,0"
+ id="path2233" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path2235"
+ sodipodi:cx="580.09137"
+ sodipodi:cy="143.9436"
+ sodipodi:rx="19.989418"
+ sodipodi:ry="19.989418"
+ d="M 590.48259,161.01988 A 19.989418,19.989418 0 1 1 600.0786,143.6477"
+ sodipodi:start="1.0241371"
+ sodipodi:end="6.268382"
+ sodipodi:open="true"
+ transform="translate(-17.387962,737.30983)" />
+ <path
+ style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 575.81386,896.51884 -10.70183,1.36745 4.09106,2.2748 -0.34104,4.33235 6.95183,-7.9746 -2e-5,0 z"
+ id="path3214"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ id="path3406"
+ d="m 562.70342,982.92526 0,-211.09183"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;marker-end:url(#Arrow1Mend)" />
+ <path
+ style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#558ada;stroke-width:1.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 536.39864,171.48272 589.0082,126.29988"
+ id="path3980"
+ transform="translate(0,732.36214)" />
+ <path
+ style="color:#000000;fill:none;stroke:#558ada;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 488.77669,62.813201 636.63015,234.96939"
+ id="path3982"
+ transform="translate(0,732.36214)" />
+ </g>
+</svg>
diff --git a/doc/media/elliptical-arc-flags.png b/doc/media/elliptical-arc-flags.png
new file mode 100644
index 0000000..7776fc3
--- /dev/null
+++ b/doc/media/elliptical-arc-flags.png
Binary files differ
diff --git a/doc/media/elliptical-arc-flags.svg b/doc/media/elliptical-arc-flags.svg
new file mode 100644
index 0000000..d4116f2
--- /dev/null
+++ b/doc/media/elliptical-arc-flags.svg
@@ -0,0 +1,197 @@
+<?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="268.49316"
+ height="154.5769"
+ id="svg4007"
+ sodipodi:version="0.32"
+ inkscape:version="0.46+devel r21383"
+ version="1.0"
+ sodipodi:docname="elliptical-arc-flags.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/usr/src/2geom-svn/doc/media/elliptical-arc-flags.png"
+ inkscape:export-xdpi="93.85714"
+ inkscape:export-ydpi="93.85714">
+ <defs
+ id="defs4009">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path802"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend"
+ style="overflow:visible">
+ <path
+ id="path796"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <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="perspective4015" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4044"
+ inkscape:cx="111.98708"
+ inkscape:cy="73.679958"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-global="false"
+ inkscape:window-width="1024"
+ inkscape:window-height="737"
+ inkscape:window-x="0"
+ inkscape:window-y="1" />
+ <metadata
+ id="metadata4012">
+ <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"
+ transform="translate(-69.799637,-28.300815)">
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:none;stroke:#558ada;stroke-width:2.05404162;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path4017"
+ sodipodi:cx="166.17009"
+ sodipodi:cy="131.10306"
+ sodipodi:rx="124.75384"
+ sodipodi:ry="74.751289"
+ d="m 290.92393,131.10306 c 0,41.284 -55.8542,74.75129 -124.75384,74.75129 -68.899642,0 -124.753838,-33.46729 -124.753838,-74.75129 0,-41.283999 55.854196,-74.751292 124.753838,-74.751292 68.89964,0 124.75384,33.467293 124.75384,74.751292 z"
+ transform="matrix(0.61918419,0.38818562,-0.38761403,0.61827246,101.94315,-30.37653)" />
+ <path
+ transform="matrix(0.61918419,0.38818562,-0.38761403,0.61827246,170.12845,-49.569428)"
+ d="m 290.92393,131.10306 c 0,41.284 -55.8542,74.75129 -124.75384,74.75129 -68.899642,0 -124.753838,-33.46729 -124.753838,-74.75129 0,-41.283999 55.854196,-74.751292 124.753838,-74.751292 68.89964,0 124.75384,33.467293 124.75384,74.751292 z"
+ sodipodi:ry="74.751289"
+ sodipodi:rx="124.75384"
+ sodipodi:cy="131.10306"
+ sodipodi:cx="166.17009"
+ id="path4021"
+ style="color:#000000;fill:none;stroke:#558ada;stroke-width:2.05404162;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:#ed0000;stroke:none;stroke-width:1.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path4025"
+ sodipodi:cx="233.37674"
+ sodipodi:cy="159.67299"
+ sodipodi:rx="9.5633268"
+ sodipodi:ry="9.5633268"
+ d="m 242.94007,159.67299 c 0,5.28168 -4.28165,9.56333 -9.56333,9.56333 -5.28168,0 -9.56333,-4.28165 -9.56333,-9.56333 0,-5.28168 4.28165,-9.56333 9.56333,-9.56333 5.28168,0 9.56333,4.28165 9.56333,9.56333 z"
+ transform="matrix(0.55248859,0,0,0.55248859,104.43875,71.455484)" />
+ <path
+ transform="matrix(0.55248859,0,0,0.55248859,14.139401,-36.772691)"
+ d="m 242.94007,159.67299 c 0,5.28168 -4.28165,9.56333 -9.56333,9.56333 -5.28168,0 -9.56333,-4.28165 -9.56333,-9.56333 0,-5.28168 4.28165,-9.56333 9.56333,-9.56333 5.28168,0 9.56333,4.28165 9.56333,9.56333 z"
+ sodipodi:ry="9.5633268"
+ sodipodi:rx="9.5633268"
+ sodipodi:cy="159.67299"
+ sodipodi:cx="233.37674"
+ id="path4537"
+ style="color:#000000;fill:#ed0000;stroke:none;stroke-width:1.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <path
+ style="color:#000000;fill:#ed0000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:6, 6;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 147.5237,60.982766 226.57224,148.5907"
+ id="path4539" />
+ <text
+ xml:space="preserve"
+ style="text-align:center;text-anchor:middle"
+ x="319.7186"
+ y="88.171432"
+ id="text4915"><tspan
+ sodipodi:role="line"
+ id="tspan4917"
+ x="319.7186"
+ y="88.171432">sweep</tspan><tspan
+ sodipodi:role="line"
+ x="319.7186"
+ y="103.17143"
+ id="tspan4919">large</tspan></text>
+ <text
+ id="text4921"
+ y="88.171432"
+ x="242.18054"
+ style="text-align:center;text-anchor:middle"
+ xml:space="preserve"><tspan
+ y="88.171432"
+ x="242.18054"
+ id="tspan4923"
+ sodipodi:role="line">sweep</tspan><tspan
+ id="tspan4925"
+ y="103.17143"
+ x="242.18054"
+ sodipodi:role="line">!large</tspan></text>
+ <text
+ xml:space="preserve"
+ style="text-align:center;text-anchor:middle"
+ x="123.37082"
+ y="103.70818"
+ id="text4927"><tspan
+ sodipodi:role="line"
+ id="tspan4929"
+ x="123.37082"
+ y="103.70818">!sweep</tspan><tspan
+ sodipodi:role="line"
+ x="123.37082"
+ y="118.70818"
+ id="tspan4931">!large</tspan></text>
+ <text
+ id="text4933"
+ y="163.69551"
+ x="89.621902"
+ style="text-align:center;text-anchor:middle"
+ xml:space="preserve"><tspan
+ y="163.69551"
+ x="89.621902"
+ id="tspan4935"
+ sodipodi:role="line">!sweep</tspan><tspan
+ id="tspan4937"
+ y="178.69551"
+ x="89.621902"
+ sodipodi:role="line">large</tspan></text>
+ </g>
+</svg>
diff --git a/doc/media/gear.png b/doc/media/gear.png
new file mode 100644
index 0000000..3415df2
--- /dev/null
+++ b/doc/media/gear.png
Binary files differ
diff --git a/doc/media/involute.pdf b/doc/media/involute.pdf
new file mode 100644
index 0000000..f199a2f
--- /dev/null
+++ b/doc/media/involute.pdf
Binary files differ
diff --git a/doc/media/matrix.png b/doc/media/matrix.png
new file mode 100644
index 0000000..343a8e7
--- /dev/null
+++ b/doc/media/matrix.png
Binary files differ
diff --git a/doc/media/matrix.svg b/doc/media/matrix.svg
new file mode 100644
index 0000000..f331bb4
--- /dev/null
+++ b/doc/media/matrix.svg
@@ -0,0 +1,247 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="matrix.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="4"
+ inkscape:cx="97.22678"
+ inkscape:cy="409.27536"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ <g
+ id="g2227"
+ transform="matrix(1.448783,-7.650991e-2,-0.400046,1.15503,20.06051,-21.47177)">
+ <path
+ id="path2219"
+ d="M 111.36932,86.249996 L 111.36932,143.46385 L 169.5,143.46385"
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:0.78020775px;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#Arrow1Mstart);marker-mid:none;marker-end:url(#Arrow1Mend);stroke-opacity:1" />
+ <g
+ id="g2221"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2223"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-width:0.78020769;stroke-miterlimit:4;stroke-dasharray:4.68124631, 4.68124631;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path2225"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-width:0.78020769;stroke-miterlimit:4;stroke-dasharray:4.68124631, 4.68124631;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
+ d="M 111.36932,143.46385 L 123.75,136"
+ id="path2208" />
+</svg>
diff --git a/doc/media/point.png b/doc/media/point.png
new file mode 100644
index 0000000..d0e59b7
--- /dev/null
+++ b/doc/media/point.png
Binary files differ
diff --git a/doc/media/point.svg b/doc/media/point.svg
new file mode 100644
index 0000000..5c63137
--- /dev/null
+++ b/doc/media/point.svg
@@ -0,0 +1,156 @@
+<?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://web.resource.org/cc/"
+ 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="210mm"
+ height="297mm"
+ id="svg2160"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="point.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true">
+ <defs
+ id="defs3">
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path4102"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path4099"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path4105"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ inkscape:document-units="mm"
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="178.41166"
+ inkscape:cy="63.895144"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1280"
+ inkscape:window-height="949"
+ inkscape:window-x="1280"
+ inkscape:window-y="25"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="107.14286"
+ id="guide3050" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="56.428571"
+ id="guide3052" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ sodipodi:type="arc"
+ style="fill:black;fill-opacity:1;stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:0.5, 1;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2178"
+ sodipodi:cx="60.104076"
+ sodipodi:cy="962.45862"
+ sodipodi:rx="1.5152289"
+ sodipodi:ry="1.5152289"
+ d="M 61.619305 962.45862 A 1.5152289 1.5152289 0 1 1 58.588848,962.45862 A 1.5152289 1.5152289 0 1 1 61.619305 962.45862 z"
+ transform="matrix(1.199594,0,0,1.199594,34.99081,-158.1218)" />
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="110.59128"
+ y="1000.1426"
+ id="text2180"><tspan
+ sodipodi:role="line"
+ id="tspan2182"
+ x="110.59128"
+ y="1000.1426">(60,30)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:1.77165353;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;marker-start:url(#Arrow1Sstart);marker-end:url(#Arrow1Send)"
+ d="M 0,976.6479 L 0,1052.7193 L 167.06354,1052.7193"
+ id="path2160"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:1.77165413;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Send);stroke-miterlimit:4;stroke-dasharray:5.31496239,1.77165413;stroke-opacity:1;marker-start:none;stroke-dashoffset:0"
+ d="M -0.35714298,1052.3626 L 103.5899,998.42619"
+ id="path3054" />
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:italic;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ x="3.5714283"
+ y="982.36218"
+ id="text4123"><tspan
+ sodipodi:role="line"
+ id="tspan4125"
+ x="3.5714283"
+ y="982.36218"
+ style="font-style:italic">x</tspan></text>
+ <text
+ id="text4127"
+ y="1048.4336"
+ x="159.64285"
+ style="font-size:10px;font-style:italic;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Serif"
+ xml:space="preserve"><tspan
+ style="font-style:italic"
+ y="1048.4336"
+ x="159.64285"
+ id="tspan4129"
+ sodipodi:role="line">y</tspan></text>
+ </g>
+</svg>
diff --git a/doc/media/rect.png b/doc/media/rect.png
new file mode 100644
index 0000000..adb82f6
--- /dev/null
+++ b/doc/media/rect.png
Binary files differ
diff --git a/doc/media/rotate.png b/doc/media/rotate.png
new file mode 100644
index 0000000..8abea74
--- /dev/null
+++ b/doc/media/rotate.png
Binary files differ
diff --git a/doc/media/rotate.svg b/doc/media/rotate.svg
new file mode 100644
index 0000000..31a0f7c
--- /dev/null
+++ b/doc/media/rotate.svg
@@ -0,0 +1,239 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="rocate.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="77.313404"
+ inkscape:cy="436.13958"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="g2190"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ <g
+ transform="matrix(0.228764,0.223608,-0.223608,0.228764,109.769,54.85103)"
+ id="g2190">
+ <path
+ style="fill:none;stroke:black;stroke-width:0.99999995;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:5.99999975,5.99999975;stroke-dashoffset:0"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ id="path2192" />
+ <path
+ sodipodi:nodetypes="cccsssssscccc"
+ style="fill:none;stroke:black;stroke-width:0.99999995;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:5.99999975,5.99999975;stroke-dashoffset:0"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ id="path2194" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend);stroke-opacity:1"
+ d="M 111.36932,86.541753 L 111.36932,143.46385 L 169.35208,143.46385"
+ id="path2196"
+ transform="matrix(3.126016,0,0,3.126016,-151.4725,-254.7792)" />
+ </g>
+</svg>
diff --git a/doc/media/sbasis.png b/doc/media/sbasis.png
new file mode 100644
index 0000000..4e0aad1
--- /dev/null
+++ b/doc/media/sbasis.png
Binary files differ
diff --git a/doc/media/sbasis.svg b/doc/media/sbasis.svg
new file mode 100644
index 0000000..c5b4cbe
--- /dev/null
+++ b/doc/media/sbasis.svg
@@ -0,0 +1,1121 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="352.37646"
+ height="289.65378"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48+devel r10201 custom"
+ sodipodi:docname="sbasis.svg"
+ inkscape:export-filename="/home/tweenk/src/2geom-bzr/doc/media/sbasis.png"
+ inkscape:export-xdpi="150.07809"
+ inkscape:export-ydpi="150.07809">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path796"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path793"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath1898">
+ <rect
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.61399996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect1900"
+ width="22.223356"
+ height="23.991123"
+ x="269.76013"
+ y="174.68845"
+ transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath1910">
+ <rect
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.61399996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect1912"
+ width="33.214287"
+ height="22.857143"
+ x="-182.01929"
+ y="290.14496"
+ transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="210.52422"
+ inkscape:cy="131.46418"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="999"
+ inkscape:window-height="828"
+ inkscape:window-x="3"
+ inkscape:window-y="89"
+ inkscape:window-maximized="0" />
+ <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(28.575407,-95.703979)">
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.61399996;stroke-miterlimit:4;stroke-dasharray:none;marker:none;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 60,98.790754 0,254.999996 260.71429,0"
+ id="path10"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ xml:space="preserve"
+ style="font-size:31.43364906px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMMathItalic12;-inkscape-font-specification:LMMathItalic12 Bold Italic"
+ x="7.2479248"
+ y="123.86304"
+ id="text1415-0"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan1417-1"
+ x="7.2479234"
+ y="123.86304"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ id="tspan1440">(1–</tspan>t<tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ id="tspan2082">)</tspan><tspan
+ id="tspan2088"
+ style="font-size:65.00091553%;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;baseline-shift:super;font-family:LMMathItalic12;-inkscape-font-specification:LMMathItalic12 Bold Italic">n</tspan></tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text1446"
+ y="385.04343"
+ x="299.8241"
+ style="font-size:31.43364906px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMMathItalic12;-inkscape-font-specification:LMMathItalic12 Bold Italic"
+ xml:space="preserve"><tspan
+ y="385.04343"
+ x="299.8241"
+ id="tspan1448"
+ sodipodi:role="line">t<tspan
+ style="font-size:65.00091553%;baseline-shift:super"
+ id="tspan2084">n</tspan></tspan></text>
+ <rect
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.61399996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect1454"
+ width="28.571428"
+ height="28.571428"
+ x="60"
+ y="325.21933"
+ inkscape:tile-cx="40.040511"
+ inkscape:tile-cy="284.51535"
+ inkscape:tile-w="28.571428"
+ inkscape:tile-h="28.571428"
+ inkscape:tile-x0="25.754797"
+ inkscape:tile-y0="270.22964" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:#00ff00;stroke:#000000;stroke-width:2.30571437;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path1840"
+ sodipodi:cx="74.285713"
+ sodipodi:cy="139.50505"
+ sodipodi:rx="14.285713"
+ sodipodi:ry="14.285715"
+ d="m 88.571426,139.50505 c 0,7.88978 -6.395931,14.28572 -14.285713,14.28572 C 66.395932,153.79077 60,147.39483 60,139.50505 c 0,-7.88978 6.395932,-14.28571 14.285713,-14.28571 7.889782,0 14.285713,6.39593 14.285713,14.28571 z"
+ transform="matrix(0.69999998,0,0,0.69999998,22.285716,41.851518)" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1840"
+ id="use1846"
+ transform="translate(28.571428,28.571423)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(57.142857,57.142853)"
+ id="use1848"
+ xlink:href="#path1840"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1840"
+ id="use1850"
+ transform="translate(85.714285,85.714283)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(114.28571,114.28571)"
+ id="use1852"
+ xlink:href="#path1840"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1840"
+ id="use1854"
+ transform="translate(142.85714,142.85714)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(171.42857,171.42857)"
+ id="use1856"
+ xlink:href="#path1840"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1840"
+ id="use1858"
+ transform="translate(200,199.99999)"
+ width="744.09448"
+ height="1052.3622" />
+ <path
+ transform="matrix(0.69999998,0,0,0.69999998,22.285716,241.85151)"
+ d="m 88.571426,139.50505 c 0,7.88978 -6.395931,14.28572 -14.285713,14.28572 C 66.395932,153.79077 60,147.39483 60,139.50505 c 0,-7.88978 6.395932,-14.28571 14.285713,-14.28571 7.889782,0 14.285713,6.39593 14.285713,14.28571 z"
+ sodipodi:ry="14.285715"
+ sodipodi:rx="14.285713"
+ sodipodi:cy="139.50505"
+ sodipodi:cx="74.285713"
+ id="path1862"
+ style="color:#000000;fill:#ff0000;stroke:#000000;stroke-width:2.30571437;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1862"
+ id="use1864"
+ transform="translate(57.142857,-1.213469e-8)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(85.714285,-1.213469e-8)"
+ id="use1866"
+ xlink:href="#path1862"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1862"
+ id="use1868"
+ transform="translate(114.28571,-1.213469e-8)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(142.85714,-1.213469e-8)"
+ id="use1870"
+ xlink:href="#path1862"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1862"
+ id="use1872"
+ transform="translate(171.42857,-1.213469e-8)"
+ width="744.09448"
+ height="1052.3622" />
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:#0000ff;stroke:#000000;stroke-width:2.30571437;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path1874"
+ sodipodi:cx="74.285713"
+ sodipodi:cy="139.50505"
+ sodipodi:rx="14.285713"
+ sodipodi:ry="14.285715"
+ d="m 88.571426,139.50505 c 0,7.88978 -6.395931,14.28572 -14.285713,14.28572 C 66.395932,153.79077 60,147.39483 60,139.50505 c 0,-7.88978 6.395932,-14.28571 14.285713,-14.28571 7.889782,0 14.285713,6.39593 14.285713,14.28571 z"
+ transform="matrix(0.69999998,0,0,0.69999998,50.857144,241.85151)" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1874"
+ id="use1876"
+ transform="translate(-28.571428,-28.571427)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(3.9812881e-7,-57.142856)"
+ id="use1878"
+ xlink:href="#path1874"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1874"
+ id="use1880"
+ transform="translate(28.571429,-85.714284)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(28.571429,-28.571427)"
+ id="use1882"
+ xlink:href="#path1874"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1874"
+ id="use1884"
+ transform="translate(57.142857,-57.142856)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(28.574438,0.01913589)"
+ id="use1890"
+ xlink:href="#path1862"
+ y="0"
+ x="0"
+ clip-path="url(#clipPath1898)" />
+ <use
+ clip-path="url(#clipPath1898)"
+ x="0"
+ y="0"
+ xlink:href="#path1862"
+ id="use1902"
+ transform="translate(199.91558,-0.10527461)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(57.142857,-114.28571)"
+ id="use1906"
+ xlink:href="#path1874"
+ y="0"
+ x="0"
+ clip-path="url(#clipPath1910)" />
+ <use
+ clip-path="url(#clipPath1910)"
+ x="0"
+ y="0"
+ xlink:href="#path1874"
+ id="use1914"
+ transform="translate(85.714282,-85.714285)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ id="use2126"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-28.571428)"
+ id="use2128"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-57.142857)"
+ id="use2130"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-85.714285)"
+ id="use2132"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-114.28571)"
+ id="use2134"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-142.85714)"
+ id="use2136"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-171.42857)"
+ id="use2138"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(0,-200)"
+ id="use2140"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,0)"
+ id="use2142"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-28.571428)"
+ id="use2144"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-57.142857)"
+ id="use2146"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-85.714285)"
+ id="use2148"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-114.28571)"
+ id="use2150"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-142.85714)"
+ id="use2152"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-171.42857)"
+ id="use2154"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(28.571428,-200)"
+ id="use2156"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,0)"
+ id="use2158"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-28.571428)"
+ id="use2160"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-57.142857)"
+ id="use2162"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-85.714285)"
+ id="use2164"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-114.28571)"
+ id="use2166"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-142.85714)"
+ id="use2168"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-171.42857)"
+ id="use2170"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(57.142857,-200)"
+ id="use2172"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,0)"
+ id="use2174"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-28.571428)"
+ id="use2176"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-57.142857)"
+ id="use2178"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-85.714285)"
+ id="use2180"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-114.28571)"
+ id="use2182"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-142.85714)"
+ id="use2184"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-171.42857)"
+ id="use2186"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(85.714285,-200)"
+ id="use2188"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,0)"
+ id="use2190"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-28.571428)"
+ id="use2192"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-57.142857)"
+ id="use2194"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-85.714285)"
+ id="use2196"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-114.28571)"
+ id="use2198"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-142.85714)"
+ id="use2200"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-171.42857)"
+ id="use2202"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(114.28571,-200)"
+ id="use2204"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,0)"
+ id="use2206"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-28.571428)"
+ id="use2208"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-57.142857)"
+ id="use2210"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-85.714285)"
+ id="use2212"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-114.28571)"
+ id="use2214"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-142.85714)"
+ id="use2216"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-171.42857)"
+ id="use2218"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(142.85714,-200)"
+ id="use2220"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,0)"
+ id="use2222"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-28.571428)"
+ id="use2224"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-57.142857)"
+ id="use2226"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-85.714285)"
+ id="use2228"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-114.28571)"
+ id="use2230"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-142.85714)"
+ id="use2232"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-171.42857)"
+ id="use2234"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(171.42857,-200)"
+ id="use2236"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,0)"
+ id="use2238"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-28.571428)"
+ id="use2240"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-57.142857)"
+ id="use2242"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-85.714285)"
+ id="use2244"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-114.28571)"
+ id="use2246"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-142.85714)"
+ id="use2248"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-171.42857)"
+ id="use2250"
+ width="344.55585"
+ height="333.58237" />
+ <use
+ x="0"
+ y="0"
+ inkscape:tiled-clone-of="#rect1454"
+ xlink:href="#rect1454"
+ transform="translate(200,-200)"
+ id="use2252"
+ width="344.55585"
+ height="333.58237" />
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="47.198875"
+ y="345.9967"
+ id="text113"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan115"
+ x="47.198875"
+ y="345.9967">0</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text117"
+ y="317.42383"
+ x="47.198875"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="317.42383"
+ x="47.198875"
+ id="tspan119"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="47.198875"
+ y="288.85092"
+ id="text121"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan123"
+ x="47.198875"
+ y="288.85092">2</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text125"
+ y="260.27805"
+ x="47.198875"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="260.27805"
+ x="47.198875"
+ id="tspan127"
+ sodipodi:role="line">3</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="47.198875"
+ y="231.70517"
+ id="text129"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan131"
+ x="47.198875"
+ y="231.70517">4</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text133"
+ y="203.13228"
+ x="47.198875"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="203.13228"
+ x="47.198875"
+ id="tspan135"
+ sodipodi:role="line">5</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="47.198875"
+ y="174.5594"
+ id="text137"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan139"
+ x="47.198875"
+ y="174.5594">6</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text141"
+ y="145.98651"
+ x="47.198875"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="145.98651"
+ x="47.198875"
+ id="tspan143"
+ sodipodi:role="line">7</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text145"
+ y="372.78241"
+ x="74.341736"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="372.78241"
+ x="74.341736"
+ id="tspan147"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="102.86214"
+ y="372.78241"
+ id="text149"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan151"
+ x="102.86214"
+ y="372.78241">1</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text153"
+ y="372.78241"
+ x="131.38255"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="372.78241"
+ x="131.38255"
+ id="tspan155"
+ sodipodi:role="line">2</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="159.90297"
+ y="372.78241"
+ id="text157"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan159"
+ x="159.90297"
+ y="372.78241">3</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text161"
+ y="372.78241"
+ x="188.42337"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="372.78241"
+ x="188.42337"
+ id="tspan163"
+ sodipodi:role="line">4</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="216.94377"
+ y="372.78241"
+ id="text165"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan167"
+ x="216.94377"
+ y="372.78241">5</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text169"
+ y="372.78241"
+ x="245.46419"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ xml:space="preserve"><tspan
+ y="372.78241"
+ x="245.46419"
+ id="tspan171"
+ sodipodi:role="line">6</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18.89756584px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:LMRoman12;-inkscape-font-specification:LMRoman12"
+ x="273.98459"
+ y="372.78241"
+ id="text173"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan175"
+ x="273.98459"
+ y="372.78241">7</tspan></text>
+ </g>
+</svg>
diff --git a/doc/media/scale.png b/doc/media/scale.png
new file mode 100644
index 0000000..3d62b7e
--- /dev/null
+++ b/doc/media/scale.png
Binary files differ
diff --git a/doc/media/scale.svg b/doc/media/scale.svg
new file mode 100644
index 0000000..3d37be4
--- /dev/null
+++ b/doc/media/scale.svg
@@ -0,0 +1,243 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="scale.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="4"
+ inkscape:cx="97.22678"
+ inkscape:cy="404.19505"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="g2227"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ <g
+ id="g2227"
+ transform="matrix(1.448783,0,0,1.133904,-50.0431,-19.23223)">
+ <path
+ id="path2219"
+ d="M 111.36932,86.249996 L 111.36932,143.46385 L 169.5,143.46385"
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:0.78020775px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-mid:none;marker-end:url(#Arrow1Mend)" />
+ <g
+ id="g2221"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2223"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-width:0.78020771;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:4.68124631,4.68124631;stroke-dashoffset:0" />
+ <path
+ id="path2225"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-width:0.78020771;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:4.68124631,4.68124631;stroke-dashoffset:0"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ </g>
+</svg>
diff --git a/doc/media/translate.png b/doc/media/translate.png
new file mode 100644
index 0000000..56de688
--- /dev/null
+++ b/doc/media/translate.png
Binary files differ
diff --git a/doc/media/translate.svg b/doc/media/translate.svg
new file mode 100644
index 0000000..33bb817
--- /dev/null
+++ b/doc/media/translate.svg
@@ -0,0 +1,252 @@
+<?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://web.resource.org/cc/"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="777"
+ height="555"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/michael/2geom/trunk/doc/media"
+ sodipodi:docname="scale.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/michael/2geom/trunk/doc/media/scale.png"
+ inkscape:export-xdpi="200"
+ inkscape:export-ydpi="200"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata48">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="855"
+ inkscape:window-width="1113"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="132.09309"
+ inkscape:cy="435.9628"
+ inkscape:window-x="157"
+ inkscape:window-y="140"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="vertical"
+ position="111.36932"
+ id="guide4172" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="411.53615"
+ id="guide4174" />
+ <sodipodi:guide
+ orientation="vertical"
+ position="170.05918"
+ id="guide3076" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="460.68007"
+ id="guide3078" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path3249"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3212"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path3233"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible">
+ <path
+ id="path3227"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3224"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <linearGradient
+ id="linearGradient4094">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop4096" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop4098" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4062">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4064" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop4042" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:0;"
+ offset="1"
+ id="stop4044" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4062"
+ id="radialGradient4079"
+ gradientUnits="userSpaceOnUse"
+ cx="8"
+ cy="-16"
+ fx="8"
+ fy="-16"
+ r="108" />
+ <linearGradient
+ xlink:href="#linearGradient4040"
+ id="linearGradient4081"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(80,376)"
+ x1="328.5"
+ y1="-43.664978"
+ x2="328.5"
+ y2="116.68156" />
+ <linearGradient
+ xlink:href="#linearGradient4094"
+ id="linearGradient4116"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.35905,0,0,1.35905,203.6646,-185.2813)"
+ x1="95.105873"
+ y1="363.72418"
+ x2="95.105873"
+ y2="502.67734" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-2.121444)"
+ x1="426.27966"
+ y1="483.83633"
+ x2="426.27966"
+ y2="522.72052" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4094"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ x1="407.09628"
+ y1="374.48508"
+ x2="407.09628"
+ y2="485.16641" />
+ </defs>
+ <g
+ id="g2919"
+ transform="matrix(0.319896,0,0,0.319896,48.45544,81.50285)">
+ <path
+ id="path2921"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ style="fill:none;stroke:black;stroke-opacity:1" />
+ <path
+ id="path2925"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ style="fill:none;stroke:black;stroke-opacity:1"
+ sodipodi:nodetypes="cccsssssscccc" />
+ </g>
+ <g
+ transform="matrix(0.228764,0.223608,-0.223608,0.228764,109.769,54.85103)"
+ id="g2190">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:3.12601709px;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#Arrow1Mstart);marker-end:none;stroke-opacity:1"
+ d="M 220.61954,-37.561022 L 196.6698,193.69109"
+ id="path2196"
+ sodipodi:nodetypes="cc" />
+ </g>
+ <g
+ transform="matrix(0.319896,0,0,0.319896,106.8078,32.71248)"
+ id="g3080">
+ <path
+ style="fill:none;stroke:black;stroke-opacity:1;stroke-width:1.00000003;stroke-miterlimit:4;stroke-dasharray:6.00000025,6.00000025;stroke-dashoffset:0"
+ d="M 163.15,27.83 L 28.81,165.3 C -16.58,221.51 59.7,214.97 92.4,231.16 C 104.13,243.15 47.44,252 59.17,264 C 70.9,275.99 130.1,287.1 141.85,299.09 C 153.58,311.08 117.84,323.8 129.57,335.79 C 141.3,347.78 168.43,336.42 173.51,364.1 C 177.13,383.88 222.4,372.6 244.54,356.4 C 256.27,344.4 222.1,345.53 233.83,333.54 C 263,303.71 290.16,322.7 300.14,292.81 C 305.07,278.04 257.2,270.04 268.95,258.05 C 302.7,238.34 419.35,225.51 364,170.16 L 224.75,27.83 C 207.72,11.48 179.3,11.3 163.15,27.83 z M 130.99,238.57 C 134,238.57 231.54,251.61 193.9,261.92 C 179.72,265.8 113.53,238.57 130.99,238.57 z M 317.46,292.81 C 317.46,299.63 367.71,304.1 367.71,291.2 C 360.55,270.48 323.4,271.88 317.46,292.81 z M 91.1,329.05 C 103,339.34 121.38,326.49 126.89,312.13 C 115.36,296.81 72.2,312.68 91.1,329.05 z M 311.16,306.82 C 295.82,320.58 312.88,334.54 328,325.65 C 331.37,322.23 327.91,310.24 311.16,306.82 z "
+ id="path3082" />
+ <path
+ sodipodi:nodetypes="cccsssssscccc"
+ style="fill:none;stroke:black;stroke-opacity:1;stroke-width:1.00000003;stroke-miterlimit:4;stroke-dasharray:6.00000025,6.00000025;stroke-dashoffset:0"
+ d="M 216.63,37.47 L 269.78,91.45 C 274.82,96.6 275.91315,108.88098 271.93,109.45 C 266.29292,110.2553 260.02293,94.034313 251.80374,94.034313 C 242.7616,94.034313 245.43878,123.58602 238.07227,123.58602 C 230.29748,123.58602 227.72697,107.96 218.30004,107.96 C 210.67739,107.96 205.51399,129.13114 194.80001,129.13114 C 185.2927,129.13114 177.02987,83.24 171.29999,83.24 C 166.28062,83.24 163.47697,123.96 152.33,123.96 C 133.73263,123.96 105.14,123.84 105.14,123.84 C 95.7,123.82 97.27,114.63 106.4,104.78 C 125.16,84.53 161.15,49.43 172.85,37.47 C 184.61,25.45 205.1,25.79 216.63,37.47 z "
+ id="path3084" />
+ </g>
+</svg>
diff --git a/doc/patchwise.svg b/doc/patchwise.svg
new file mode 100644
index 0000000..986c565
--- /dev/null
+++ b/doc/patchwise.svg
@@ -0,0 +1,131 @@
+<?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://web.resource.org/cc/"
+ 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="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/njh/svn/lib2geom/doc"
+ sodipodi:docname="patchwise.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="3.3"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.6699525"
+ inkscape:cx="337.14286"
+ inkscape:cy="685.71428"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:object-bbox="true"
+ inkscape:object-points="true"
+ inkscape:object-nodes="true"
+ inkscape:window-width="910"
+ inkscape:window-height="626"
+ inkscape:window-x="0"
+ inkscape:window-y="25" />
+ <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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2160"
+ width="428.57144"
+ height="428.57144"
+ x="122.85714"
+ y="152.36218" />
+ <g
+ id="g3945">
+ <path
+ id="path3048"
+ d="M 302.91647,152.36218 C 302.91647,580.93359 302.91647,580.93359 302.91647,580.93359"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path3050"
+ d="M 122.85714,323.04064 C 551.42859,323.04064 551.42859,323.04064 551.42859,323.04064"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#580000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 535.16419,323.04064 C 535.16419,580.93359 535.16419,580.93359 535.16419,580.93359"
+ id="path3052" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#580000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 301.86101,479.24844 C 551.42859,479.24844 551.42859,479.24844 551.42859,479.24844"
+ id="path3054" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#dd0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 405.29591,324.0961 L 405.29591,480.3039"
+ id="path3056" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#dd0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 302.91647,390.58996 C 534.06179,390.58996 534.06179,390.58996 534.06179,390.58996"
+ id="path3058" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 122.85714,152.36218 C 120.21593,152.34091 117.57473,152.31964 114.93352,152.29837 C 97.522248,152.15814 80.103265,152.29837 62.691012,152.29837 C 44.201158,152.29837 21.137039,149.25307 2.9852863,153.79101 C -14.749469,158.2247 -35.837039,151.70403 -53.735153,155.28365 C -68.399793,158.21658 -77.097646,171.70273 -94.036518,171.70273 C -113.54478,171.70273 -132.81343,165.14146 -152.2496,161.25423 C -161.04516,159.49511 -169.96413,151.60134 -179.11718,149.31308 C -190.17129,146.54955 -204.84334,149.31308 -216.43326,149.31308 C -217.42835,149.31308 -218.42345,149.31308 -219.41854,149.31308"
+ id="path3973" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 123.88938,580.93359 C 97.091356,580.93359 59.650495,568.7458 26.867577,568.7458 C 10.139914,568.7458 -7.2780498,576.08558 -20.897004,585.16488 C -33.02407,593.24959 -44.192616,596.13321 -58.213083,592.6281 C -75.749282,588.24405 -82.500961,571.73109 -102.99238,571.73109 C -117.70994,571.73109 -138.65753,576.8762 -152.2496,583.67224 C -165.73255,590.41371 -183.38692,593.14188 -197.02889,598.59867 C -209.41442,603.55288 -216.56256,598.1967 -225.38911,591.13545 C -230.5282,587.02419 -236.32857,583.42676 -241.80819,580.68695"
+ id="path3975" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-170.16132"
+ y="359.77576"
+ id="text3977"
+ sodipodi:linespacing="100%"><tspan
+ sodipodi:role="line"
+ id="tspan3979"
+ x="-170.16132"
+ y="359.77576">Extend to infinity</tspan></text>
+ <rect
+ style="fill:#b5ffb0;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3985"
+ width="170.16132"
+ height="158.22017"
+ x="126.87466"
+ y="158.26894" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="140.72752"
+ y="210.51144"
+ id="text3987"
+ sodipodi:linespacing="100%"><tspan
+ sodipodi:role="line"
+ id="tspan3989"
+ x="140.72752"
+ y="210.51144">Defined by four borders</tspan></text>
+ </g>
+</svg>
diff --git a/doc/s-pb-thoughts.txt b/doc/s-pb-thoughts.txt
new file mode 100644
index 0000000..5b11215
--- /dev/null
+++ b/doc/s-pb-thoughts.txt
@@ -0,0 +1,78 @@
+ these s-power bases are exactly what I was trying to invent
+ did you read that paper?
+(13:11:50) mental@gristle.org: I didn't really understand it.
+ oh, well it's precisely what we need for lib2geom
+ it has a nice haskell connection, btw
+ rather than representing things as finite polynomials, we can store them as lazy lists and simply take enough terms at the end of the calculations
+ did you at least look at the examples?
+ namely, conversion of nurbs to beziers and offset curves?
+ the basic idea is that although polynomials (which are really linear combinations of 1, x, x^2, x^3...) are easy to work with
+ they are crap for two main reasons: a) though they are always correct for 0, any rounding or truncation will make the values at 1 fluctuate
+ b) converting between bezier and polynomial is mathematically inprecise
+ b) is subtle and I didn't understand it for quite a while
+ but a) I had already run into
+ anyway, basically s-pbs provide a robust arithmetic for doing paths
+ and there are simple, online algorithms for most operations
+ oh yes, and truncating a s-pb gives an approximation that is basically as good as possible with that number of terms
+ so you might work out the offset curve as a degree 6 pb (corresponding to a degree 11 bezier curve) then truncate to a 2 term (cubic)
+(13:21:05) mental@gristle.org: so, basically an s-pb is an alternate way of approximating functions which has nicer properties than polynomials, at least for our purpose
+ or you might subdivide first
+ an s-pb is an alternate way of approximating functions which has nicer properties than polynomials and nicer properties than beziers, at least for our purpose; at the cost of a little more work
+ for example, multiplying a polynomial is straightforward (poly.cpp has an implementation, e.g.), multiplying a bezier is horid
+ but polynomials don't give a nice control handle interpretation
+ whereas beziers and s-pbs do
+ that article basically shows that anything you can do with polynomials, you can do with s-pbs
+ (with a little extra work)
+ so I'll probably remove poly.h
+ every curve can be written as an infinite s-pb
+ including things like spirals
+ so we could do spiral offset directly
+ basically, if you can write an operation mathematically, you can do it symbolically on an s-pb
+ including differentiation and integration
+ lets say we have a function S(t) which is a lazy list s-pb of a single path elem
+ then we can compute arc length as int(sqrt(diff(S)^2))
+ and we can evaluate that at an arbitrary point on the curve to a require precision simply by lazy list operations
+ similarly, offset means S + d*(transpose(S')/sqrt(diff(S)^2))
+ and we can convert that back to a curve using a routine that takes a lazy list and either degree reduces (truncates) or subdivides until the require tol is achieved
+(13:27:22) mental@gristle.org: man, lazy evaluation without garbage collection, though :/
+ yeah, been pondering that
+ probably easier to string together online algorithms and use a vector cache
+(13:28:09) mental@gristle.org: vector cache?
+ but I thought you might like to think about that as an algorithm
+ std::vector<term>
+(13:28:34) mental@gristle.org: ah, so basically we accumulate stuff in a vector during the computation and discard the vector when complete?
+ we can do a lot simply by providing fixed length versions
+ yeah
+(13:28:44) mental@gristle.org: (using it as a memory pool, essentially)
+ no, not really
+ I was just thinking that for a lazy list we start with something like lazy ->lazy
+ then [1] lazy -> lazy
+ (I think my notation is wrong here)
+ then [1, 5, 3, 87] lazy -> lazy
+ etc
+ many algorithms are linear time online
+ i.e. they do a constant amount of work, looking at a single term or a few terms
+ then output another term
+ you could think of them as a production line
+ every time the caller asks for another term, each element in the chain requests as many terms as it needs
+ any point where we need more than one term, we keep a vector remembering all the bits (as we will need them again)
+ addition, for example, simply takes the two inputs term by term and adds them
+ scalar multiply similar takes a term, multiplies and chugs the answer
+ sqrt ditto (I think)
+ but multiply requires all the terms whose indices add to the required term
+ There are a few algorithms I haven't worked out yet - inverse function (which we could find using the lagrange inversion theorem, perhaps), converting to a beziergon to a specified tolerance, handling singularities correctly (if you get a pole in the complex plane inside a certain distance from your path you need to subdivide to get past it)
+ but what I like is the facts that you can increase precision at the caller's point rather than having to make a guess as to the required precision first
+ and with s-pb we might be able to create a true natural parameterisation accurate to tol
+ http://en.wikipedia.org/wiki/Lagrange_inversion_theorem
+ that would be really cool if it worked with s-pb
+ you could take any s-pb and get an inverse function
+ (think implicit plotter)
+ for inversion we would require that the function in question is monotonic with non-zero derivative
+ I wonder if that condition could be tested easily symbolically
+ we should probably also think about paths of s-pb functions
+ to handle subdivision techniques
+ oh yeah, and I should work out how to find the intersection of two s-pbs
+ I have that nice paper that solves an arbitrary pair of polynomials via eigen decomposition, I may be able to rewrite that in terms of s-pb
+ you can find the intersections of an arbitrary set of polynomials via the resultant (I think I sent you a link)
+ perhaps the result is expressible in s-pb
+ (well of course it is, what I mean is that perhaps you can find it without going via polynomials first) \ No newline at end of file
diff --git a/doc/shapeops.svg b/doc/shapeops.svg
new file mode 100644
index 0000000..19353dc
--- /dev/null
+++ b/doc/shapeops.svg
@@ -0,0 +1,653 @@
+<?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://web.resource.org/cc/"
+ 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="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45+devel"
+ sodipodi:docname="shapeops.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Lend"
+ style="overflow:visible;">
+ <path
+ id="path4202"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(1.1) rotate(180) translate(1,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;">
+ <path
+ id="path4190"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.32482718"
+ inkscape:cx="680.14564"
+ inkscape:cy="236.50338"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="787"
+ inkscape:window-height="1020"
+ inkscape:window-x="0"
+ inkscape:window-y="0" />
+ <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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -56.434431,402.42818 L 30.462129,402.42818 L 30.462129,556.4676 L -56.434431,556.4676 L -56.434431,402.42818 z"
+ id="rect2799" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -159.93198,363.56119 L 182.92514,363.56119 L 182.92514,542.13261 L -159.93198,542.13261 L -159.93198,363.56119 z"
+ id="rect3182" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -145.70904,497.60059 L 130.50958,496.27285 L 130.94124,426.5636 L 4.0769888,427.26388 L 2.9251588,387.60059 L 165.20484,387.60059 L 165.20484,521.85347 L -145.70904,521.85347 L -145.70904,497.60059 z"
+ id="rect3184"
+ sodipodi:nodetypes="ccccccccc" />
+ <text
+ id="text3206"
+ y="346.22397"
+ x="-175.31946"
+ style="font-size:32.92305374px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="346.22397"
+ x="-175.31946"
+ id="tspan3208"
+ sodipodi:role="line">Union</tspan></text>
+ <path
+ id="path3301"
+ d="M -109.376,420.18825 L -76.596301,420.18825 L -76.596301,480.13612 L -109.376,480.13612 L -109.376,420.18825 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -87.297935,372.34376 L -87.297935,405.12347 L -147.2458,405.12347 L -147.2458,372.34376 L -87.297935,372.34376 z"
+ id="path3303" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 109.80135,957.83853 L 109.80135,972.18228 L 196.7076,972.18228 L 196.7076,957.83853 L 109.80135,957.83853 z"
+ id="path3344" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 56.866249,835.89549 L 89.645949,835.89549 L 89.645949,895.84336 L 56.866249,895.84336 L 56.866249,835.89549 z"
+ id="path3350" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 18.988849,788.05728 L 18.988849,820.83853 L 36.301349,820.83853 L 36.301349,794.99478 L 78.957599,794.99478 L 78.957599,788.05728 L 18.988849,788.05728 z"
+ id="path3352" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 170.77092,818.12053 L 171.48967,842.96428 L 197.86467,842.80803 L 197.86467,818.12053 L 170.77092,818.12053 z M 197.86467,912.46428 L 110.95842,912.87053 L 110.95842,937.55803 L 197.86467,937.55803 L 197.86467,912.46428 z"
+ id="path3363" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -230.83893,1007.8247 L 21.000929,1110.7973"
+ id="path3370" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 169.37394,1003.2069 L 73.161389,1109.4514"
+ id="path3372" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -109.376,1197.6263 L -76.596301,1197.6263 L -76.596301,1257.5742 L -109.376,1257.5742 L -109.376,1197.6263 z"
+ id="path3384" />
+ <path
+ id="path3390"
+ d="M -56.440901,1319.5693 L -56.440901,1333.9131 L 30.465349,1333.9131 L 30.465349,1319.5693 L -56.440901,1319.5693 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path3394"
+ d="M -147.2534,1149.7881 L -147.2534,1182.5693 L -129.9409,1182.5693 L -129.9409,1156.7256 L -87.284651,1156.7256 L -87.284651,1149.7881 L -147.2534,1149.7881 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path3398"
+ d="M 4.9108788,1179.8513 L 5.6296288,1204.6951 L 32.004629,1204.5388 L 32.004629,1179.8513 L 4.9108788,1179.8513 z M 32.004629,1274.1951 L -54.901621,1274.6013 L -54.901621,1299.2888 L 32.004629,1299.2888 L 32.004629,1274.1951 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="opacity:0.60215053;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 709.38099,43.724366 L 709.38099,248.00562 L 986.67852,248.00562 L 1029.5566,248.00562 L 1030.0349,153.81897 L 1001.5703,153.97203 L 1001.9499,231.86749 L 987.22644,231.92592 L 987.22644,43.724366 L 709.38099,43.724366 z M 782.88099,66.880616 L 869.78724,66.880616 L 869.78724,220.91187 L 782.88099,220.91187 L 782.88099,66.880616 z M 729.94349,84.630619 L 762.72474,84.630619 L 762.72474,144.56812 L 729.94349,144.56812 L 729.94349,84.630619 z"
+ id="path3400"
+ sodipodi:nodetypes="cccccccccccccccccccc" />
+ <path
+ style="opacity:0.60215053;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 679.38099,28.005616 L 679.38099,206.56812 L 978.98214,206.56812 L 1022.256,206.56812 L 1022.256,158.04046 L 1022.256,28.005616 L 679.38099,28.005616 z M 692.06849,36.786866 L 752.03724,36.786866 L 752.03724,69.568116 L 692.06849,69.568116 L 692.06849,36.786866 z M 842.25599,52.036866 L 1004.5372,52.036866 L 1004.5372,186.28687 L 980.52142,186.28687 L 693.59974,186.28687 L 693.59974,162.03687 L 969.81849,160.72437 L 970.25599,91.005619 L 843.41224,91.693119 L 842.25599,52.036866 z"
+ id="path3404"
+ sodipodi:nodetypes="cccccccccccccccccccccc" />
+ <path
+ id="path5158"
+ d="M -35.253591,646.56646 L -218.00451,758.75729"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ id="path5160"
+ d="M -8.3903312,649.49025 L 157.576,783.23681"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="-206.51208"
+ y="683.30524"
+ id="text5164"><tspan
+ sodipodi:role="line"
+ id="tspan5166"
+ x="-206.51208"
+ y="683.30524"
+ dy="0 6.5306125 0 -4.3537416 0 0 4.3537416"
+ dx="0 -7.5755764e-08 0 0 0 0 -2.1768708">Ao + Bo</tspan></text>
+ <text
+ id="text5168"
+ y="681.76599"
+ x="46.533127"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="681.76599"
+ x="46.533127"
+ id="tspan5170"
+ sodipodi:role="line"
+ dy="0 8.7074833 -6.530612 0 0 0 4.3537416"
+ dx="0 -2.1768708 0 0 0 0 -2.1768708">Ah - Bo</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="79.49559"
+ y="713.51733"
+ id="text5172"><tspan
+ dx="0 -2.1768708 0 0 0 0 -2.1768708"
+ dy="0 7.6190481 -6.530612 0 0 0 4.3537416"
+ sodipodi:role="line"
+ id="tspan5174"
+ x="79.49559"
+ y="713.51733">Bh - Ao</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="12.668962"
+ y="652.90643"
+ id="text5176"><tspan
+ dx="0 -2.1768708 0 0 0 0"
+ dy="0 8.7074833 -6.530612 0 0 0"
+ sodipodi:role="line"
+ id="tspan5178"
+ x="12.668962"
+ y="652.90643">Ah x Bh</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:32.92305374px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="798.44019"
+ y="0.67824805"
+ id="text5590"><tspan
+ sodipodi:role="line"
+ id="tspan5592"
+ x="798.44019"
+ y="0.67824805">Input</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 313.34503,803.37606 L 313.34503,937.62606 L 330.65753,937.62606 L 330.65753,803.37606 L 313.34503,803.37606 z M 35.501279,913.31356 L 19.720029,913.37606 L 19.720029,937.62606 L 35.501279,937.62606 L 35.501279,913.31356 z"
+ id="path5604" />
+ <path
+ id="path5611"
+ d="M 145.72598,1166.9135 L 145.72598,1301.1635 L 163.03848,1301.1635 L 163.03848,1166.9135 L 145.72598,1166.9135 z M -132.11777,1276.851 L -147.89902,1276.9135 L -147.89902,1301.1635 L -132.11777,1301.1635 L -132.11777,1276.851 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path6029"
+ d="M 624.92611,402.42818 L 711.82267,402.42818 L 711.82267,556.4676 L 624.92611,556.4676 L 624.92611,402.42818 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path6031"
+ d="M 521.42857,363.56119 L 864.28569,363.56119 L 864.28569,542.13261 L 521.42857,542.13261 L 521.42857,363.56119 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="ccccccccc"
+ id="path6033"
+ d="M 535.6515,497.60059 L 811.87012,496.27285 L 812.30178,426.5636 L 685.43753,427.26388 L 684.2857,387.60059 L 846.56538,387.60059 L 846.56538,521.85347 L 535.6515,521.85347 L 535.6515,497.60059 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:32.92305374px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="506.04111"
+ y="346.22397"
+ id="text6035"><tspan
+ sodipodi:role="line"
+ id="tspan6037"
+ x="506.04111"
+ y="346.22397">Intersection</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 571.98454,420.18825 L 604.76424,420.18825 L 604.76424,480.13612 L 571.98454,480.13612 L 571.98454,420.18825 z"
+ id="path6039" />
+ <path
+ id="path6041"
+ d="M 594.06261,372.34376 L 594.06261,405.12347 L 534.11474,405.12347 L 534.11474,372.34376 L 594.06261,372.34376 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 806.22279,765.03279 L 806.22279,797.81404 L 866.16029,797.81404 L 866.16029,765.03279 L 806.22279,765.03279 z"
+ id="path6973" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 956.37904,780.31404 L 956.81664,795.12654 L 897.03534,795.12654 L 897.03534,889.87654 L 807.75404,890.31404 L 807.75404,914.56404 L 897.03534,914.56404 L 897.03534,949.15779 L 983.94164,949.15779 L 983.94164,914.56404 L 1118.6603,914.56404 L 1118.6603,780.31404 L 956.37904,780.31404 z"
+ id="path6971" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 844.09779,812.90779 L 844.09779,872.84529 L 876.87899,872.84529 L 876.87899,812.90779 L 844.09779,812.90779 z"
+ id="path6069" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1084.4116,819.29788 L 1083.9741,888.98538 L 983.94284,889.48538 L 983.94284,819.82913 L 1084.4116,819.29788 z"
+ id="path6052" />
+ <path
+ sodipodi:nodetypes="ccccccccc"
+ id="path6473"
+ d="M -2245.656,497.60059 L -1969.4374,496.27285 L -1969.0057,426.5636 L -2095.87,427.26388 L -2097.0218,387.60059 L -1934.7421,387.60059 L -1934.7421,521.85347 L -2245.656,521.85347 L -2245.656,497.60059 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path6469"
+ d="M -2156.3814,402.42818 L -2069.4848,402.42818 L -2069.4848,556.4676 L -2156.3814,556.4676 L -2156.3814,402.42818 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path6471"
+ d="M -2259.8789,363.56119 L -1917.0218,363.56119 L -1917.0218,542.13261 L -2259.8789,542.13261 L -2259.8789,363.56119 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:32.92305374px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="-2275.2664"
+ y="346.22397"
+ id="text6475"><tspan
+ sodipodi:role="line"
+ id="tspan6477"
+ x="-2275.2664"
+ y="346.22397">Subtraction</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -2209.3229,420.18825 L -2176.5432,420.18825 L -2176.5432,480.13612 L -2209.3229,480.13612 L -2209.3229,420.18825 z"
+ id="path6479" />
+ <path
+ id="path6481"
+ d="M -2187.2449,372.34376 L -2187.2449,405.12347 L -2247.1927,405.12347 L -2247.1927,372.34376 L -2187.2449,372.34376 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path6467"
+ d="M -2229.8789,379.27547 L -1952.0558,379.27547 L -1952.0558,583.56118 L -2229.8789,583.56118 L -2229.8789,379.27547 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-opacity:1"
+ d="M -2093.8401,646.56645 L -2973.6036,807.88598"
+ id="path6878" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-opacity:1"
+ d="M -2066.9767,649.49025 L -1901.0104,783.23681"
+ id="path6880" />
+ <text
+ id="text6882"
+ y="720.24799"
+ x="-2726.8826"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ dx="0 -7.5755764e-08 0 0 0 0 -2.1768708"
+ dy="0 6.5306125 0 -4.3537416 0 0 4.3537416"
+ y="720.24799"
+ x="-2726.8826"
+ id="tspan6884"
+ sodipodi:role="line">Ao + Bo</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="-1904.3037"
+ y="740.25861"
+ id="text6886"><tspan
+ dx="0 -2.1768708 0 0 0 0 -2.1768708"
+ dy="0 8.7074833 -6.530612 0 0 0 4.3537416"
+ sodipodi:role="line"
+ id="tspan6888"
+ x="-1904.3037"
+ y="740.25861">Ah - Bo</tspan></text>
+ <text
+ id="text6890"
+ y="765.85284"
+ x="-2471.9321"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="765.85284"
+ x="-2471.9321"
+ id="tspan6892"
+ sodipodi:role="line"
+ dy="0 7.6190481 -6.530612 0 0 0 4.3537416"
+ dx="0 -2.1768708 0 0 0 0 -2.1768708">Bh - Ao</tspan></text>
+ <text
+ id="text6894"
+ y="776.04883"
+ x="-2255.2595"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="776.04883"
+ x="-2255.2595"
+ id="tspan6896"
+ sodipodi:role="line"
+ dy="0 8.7074833 -6.530612 0 0 0"
+ dx="0 -2.1768708 0 0 0 0">Ah x Bh</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -3160.7763,814.17474 L -3160.7763,992.73724 L -3130.7763,992.73724 L -3130.7763,829.89349 L -2852.9326,829.89349 L -2852.9326,992.73724 L -2817.9013,992.73724 L -2817.9013,814.17474 L -3160.7763,814.17474 z"
+ id="path6901" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -2323.8213,841.56372 L -2323.3838,856.40747 L -2296.2901,856.40747 L -2296.2901,881.06372 L -2195.8213,880.53247 L -2196.2276,950.25122 L -2296.2901,950.71997 L -2296.2901,975.81372 L -2161.5401,975.81372 L -2161.5401,841.56372 L -2323.8213,841.56372 z M -2383.1963,951.12622 L -2472.4463,951.56372 L -2472.4463,975.81372 L -2383.1963,975.81372 L -2383.1963,951.12622 z"
+ id="path6908" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -2133.7904,820.70536 L -2133.7904,999.26786 L -2030.2904,999.26786 L -2030.2904,1013.6116 L -1943.3842,1013.6116 L -1943.3842,999.26786 L -1790.9154,999.26786 L -1790.9154,820.70536 L -2133.7904,820.70536 z"
+ id="path6910" />
+ <path
+ style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -2331.1534,1093.5194 L -2331.1534,1119.3631 L -2288.4971,1119.3631 L -2288.4971,1093.5194 L -2331.1534,1093.5194 z M -2198.2784,1101.8319 L -2197.8409,1116.6756 L -2170.7471,1116.6756 L -2170.7471,1141.3319 L -2070.2784,1140.8006 L -2070.7159,1210.5194 L -2170.7471,1210.9881 L -2170.7471,1236.0819 L -2053.3096,1236.0819 L -2053.3096,1101.8319 L -2198.2784,1101.8319 z M -2257.6534,1211.3944 L -2331.1534,1211.7694 L -2331.1534,1236.0819 L -2257.6534,1236.0819 L -2257.6534,1211.3944 z M -2331.1534,1256.3631 L -2331.1534,1297.8007 L -2053.3096,1297.8007 L -2053.3096,1256.3631 L -2170.7471,1256.3631 L -2170.7471,1270.7069 L -2257.6534,1270.7069 L -2257.6534,1256.3631 L -2331.1534,1256.3631 z"
+ id="path6925" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000035, 4.00000035;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -2774.488,821.625 L -2774.488,847.46875 L -2731.8317,847.46875 L -2731.8317,821.625 L -2774.488,821.625 z M -2644.988,830.90625 L -2643.8317,870.5625 L -2516.988,869.875 L -2517.3942,939.59375 L -2777.863,940.84375 L -2777.863,965.15625 L -2500.0192,965.15625 L -2500.0192,830.90625 L -2644.988,830.90625 z"
+ id="path6931" />
+ <path
+ id="path6936"
+ d="M -2093.84,646.56647 L -2542.9316,832.52418"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-opacity:1" />
+ <path
+ id="path6938"
+ d="M -2067.9768,649.49026 L -2323.4675,795.52171"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 694.05575,628.0951 L 511.30483,740.28593"
+ id="path6955" />
+ <text
+ id="text6957"
+ y="664.83386"
+ x="522.79724"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ dx="0 -7.5755764e-08 0 0 0 0 -2.1768708"
+ dy="0 6.5306125 0 0 0 0 4.3537416"
+ y="664.83386"
+ x="522.79724"
+ id="tspan6959"
+ sodipodi:role="line">Ao x Bo</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 730.15469,624.86177 L 896.12104,758.60833"
+ id="path6961" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="804.48547"
+ y="664.83386"
+ id="text6963"><tspan
+ sodipodi:role="line"
+ id="tspan6965"
+ x="804.48547"
+ y="664.83386"
+ dy="0 6.1571202 -6.1571202 0 0 0 6.1571202"
+ dx="0 -1.5392801 -1.5392801 0 0 0 -1.5392801">Ah + Bh</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 512.32394,932.29834 L 512.32394,1009.2623 L 801.70859,782.98817"
+ id="path6975" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 502.87904,1011.75 L 502.87904,1036.4062 L 457.84779,1036.4062 L 457.84779,1174.5938 L 735.66029,1174.5938 L 735.66029,1011.75 L 502.87904,1011.75 z"
+ id="path6977" />
+ <text
+ xml:space="preserve"
+ style="font-size:28px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="493.85257"
+ y="990.79102"
+ id="text6979"><tspan
+ sodipodi:role="line"
+ id="tspan6981"
+ x="493.85257"
+ y="990.79102">-</tspan></text>
+ <path
+ id="path6986"
+ d="M 512.32397,1188.1621 L 512.32397,1249.39 L 852.50489,919.98413"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="ccc" />
+ <text
+ id="text6988"
+ y="1230.9187"
+ x="493.85257"
+ style="font-size:28px;font-style:normal;font-weight:bold;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="1230.9187"
+ x="493.85257"
+ id="tspan6990"
+ sodipodi:role="line">-</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 502.87904,1253.417 L 502.87904,1278.0732 L 457.84779,1278.0732 L 457.84779,1372.042 L 532.22279,1371.667 L 532.22279,1276.917 L 592.00404,1276.917 L 591.56654,1262.1045 L 735.66029,1262.1045 L 735.66029,1253.417 L 502.87904,1253.417 z M 457.84779,1396.3545 L 457.84779,1416.2608 L 532.22279,1416.2608 L 532.22279,1396.3545 L 457.84779,1396.3545 z M 619.12904,1396.3545 L 619.12904,1416.2608 L 735.66029,1416.2608 L 735.66029,1396.3545 L 619.12904,1396.3545 z"
+ id="path6999" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 844.80843,852.25577 L 867.89759,1104.6977 L 1032.6006,890.73778"
+ id="path7004" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 867.89769,1103.1584 L 738.59811,1323.2755 L 878.67264,1324.8148"
+ id="path7006"
+ sodipodi:nodetypes="ccc" />
+ <path
+ id="path7794"
+ d="M 940.03464,1253.417 L 940.03464,1278.0732 L 895.00334,1278.0732 L 895.00334,1372.042 L 969.37834,1371.667 L 969.37834,1276.917 L 1029.1596,1276.917 L 1028.7221,1262.1045 L 1172.8158,1262.1045 L 1172.8158,1253.417 L 940.03464,1253.417 z M 895.00334,1396.3545 L 895.00334,1416.2608 L 969.37834,1416.2608 L 969.37834,1396.3545 L 895.00334,1396.3545 z M 1056.2846,1396.3545 L 1056.2846,1416.2608 L 1172.8158,1416.2608 L 1172.8158,1396.3545 L 1056.2846,1396.3545 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path7796"
+ d="M 917.98324,1293.1632 L 917.98324,1353.1007 L 950.76444,1353.1007 L 950.76444,1293.1632 L 917.98324,1293.1632 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path7798"
+ d="M 1156.7578,1299.5533 L 1156.3203,1369.2408 L 1056.289,1369.7408 L 1056.289,1300.0846 L 1156.7578,1299.5533 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000019, 4.00000019;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1442.2087,402.42818 L 1529.1052,402.42818 L 1529.1052,556.4676 L 1442.2087,556.4676 L 1442.2087,402.42818 z"
+ id="path7806" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1352.9341,497.60059 L 1629.1527,496.27285 L 1629.5844,426.5636 L 1502.7201,427.26388 L 1501.5683,387.60059 L 1663.848,387.60059 L 1663.848,521.85347 L 1352.9341,521.85347 L 1352.9341,497.60059 z"
+ id="path7810"
+ sodipodi:nodetypes="ccccccccc" />
+ <text
+ id="text7812"
+ y="346.22397"
+ x="1323.3237"
+ style="font-size:32.92305374px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ y="346.22397"
+ x="1323.3237"
+ id="tspan7814"
+ sodipodi:role="line">Subtraction</tspan></text>
+ <path
+ id="path7816"
+ d="M 1389.2671,420.18825 L 1422.0468,420.18825 L 1422.0468,480.13612 L 1389.2671,480.13612 L 1389.2671,420.18825 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4.00000028, 4.00000028;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1411.3452,372.34376 L 1411.3452,405.12347 L 1351.3973,405.12347 L 1351.3973,372.34376 L 1411.3452,372.34376 z"
+ id="path7818" />
+ <path
+ id="path7850"
+ d="M 1534.4275,603.46662 L 1536.9936,715.65745"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.99999976;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="1413.6588"
+ y="631.49792"
+ id="text7852"><tspan
+ sodipodi:role="line"
+ id="tspan7854"
+ x="1413.6588"
+ y="631.49792"
+ dy="0 6.5306125 -6.1571202 0 0 0 4.3537416"
+ dx="0 -7.5755764e-08 0 0 0 0 -2.1768708">Ao - Bo</tspan></text>
+ <path
+ id="path7856"
+ d="M 1530.2556,953.47868 L 1529.7129,1119.6459"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="1414.9861"
+ y="657.44348"
+ id="text7887"><tspan
+ sodipodi:role="line"
+ id="tspan7889"
+ x="1414.9861"
+ y="657.44348"
+ dy="0 6.5306125 0 -6.1571202 0 0 8.7074833"
+ dx="0 -7.5755764e-08 0 0 0 0 -2.1768708">Ao x Bh</tspan></text>
+ <text
+ id="text7932"
+ y="1036.9882"
+ x="1469.9934"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ xml:space="preserve"><tspan
+ dx="0 0"
+ dy="0 0 0 8.7074833"
+ y="1036.9882"
+ x="1469.9934"
+ id="tspan7934"
+ sodipodi:role="line">- Ah</tspan></text>
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -129.54136,380.68793 L -129.54136,584.96918 L 147.75617,584.96918 L 190.63429,584.96918 L 191.11257,490.78253 L 162.64795,490.93559 L 163.02759,568.83105 L 148.30409,568.88948 L 148.30409,380.68793 L -129.54136,380.68793 z"
+ id="path8337" />
+ <path
+ id="path8339"
+ d="M 552.35971,380.68793 L 552.35971,584.96918 L 829.65724,584.96918 L 872.53536,584.96918 L 873.01364,490.78253 L 844.54902,490.93559 L 844.92866,568.83105 L 830.20516,568.88948 L 830.20516,380.68793 L 552.35971,380.68793 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1369.7174,380.68793 L 1369.7174,584.96918 L 1647.015,584.96918 L 1689.8931,584.96918 L 1690.3714,490.78253 L 1661.9067,490.93559 L 1662.2864,568.83105 L 1647.5629,568.88948 L 1647.5629,380.68793 L 1369.7174,380.68793 z"
+ id="path8341" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dasharray:3.99999928, 3.99999928;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1338.7111,363.56119 L 1681.5683,363.56119 L 1681.5683,428.95375 L 1681.5683,542.13261 L 1338.7111,542.13261 L 1338.7111,363.56119 z"
+ id="path7808"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -364.66175,776.08956 L -364.66175,954.65206 L -334.2555,954.65206 L -334.2555,997.49581 L -56.974249,997.49581 L -14.099249,997.49581 L -13.599249,903.30831 L -21.786749,903.33956 L -21.786749,776.08956 L -364.66175,776.08956 z"
+ id="path8350" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;stroke-dasharray:4,4"
+ d="M -56.411749,954.65206 L -41.817999,954.65206 L -41.692999,981.37081 L -56.411749,981.40206 L -56.411749,954.65206 z"
+ id="path8343" />
+ <path
+ id="path8354"
+ d="M -159.9375,1140.8989 L -159.9375,1319.4614 L -129.53125,1319.4614 L -129.53125,1362.3052 L 147.75,1362.3052 L 190.625,1362.3052 L 191.125,1268.1177 L 182.9375,1268.1489 L 182.9375,1140.8989 L -159.9375,1140.8989 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ id="path8356"
+ d="M 148.3125,1319.4614 L 162.90625,1319.4614 L 163.03125,1346.1802 L 148.3125,1346.2114 L 148.3125,1319.4614 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 456.93964,767.0468 L 456.93964,928.4843 L 734.78339,928.4843 L 734.78339,767.0468 L 456.93964,767.0468 z"
+ id="path8365" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 768.84589,877.20305 L 749.12714,877.2968 L 749.37714,928.4843 L 768.84589,928.4843 L 768.84589,877.20305 z"
+ id="path8358" />
+ <path
+ id="path8367"
+ d="M 768.84589,1123.4878 L 749.12714,1123.5816 L 749.37714,1174.7691 L 768.84589,1174.7691 L 768.84589,1123.4878 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 768.84375 1365.4375 L 753.84375 1365.5 L 753.84375 1396.3438 L 749.28125 1396.3438 L 749.375 1416.7188 L 768.84375 1416.7188 L 768.84375 1365.4375 z "
+ id="path8369" />
+ <path
+ id="path8376"
+ d="M 1207.5386,1365.4375 L 1192.5386,1365.5 L 1192.5386,1396.3438 L 1187.9761,1396.3438 L 1188.0698,1416.7188 L 1207.5386,1416.7188 L 1207.5386,1365.4375 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:3.99999928;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1368.7188 727.3125 L 1368.7188 753.15625 L 1411.375 753.15625 L 1411.375 727.3125 L 1368.7188 727.3125 z M 1502.1562 735.28125 L 1503.3125 774.9375 L 1630.1562 774.25 L 1629.7188 843.96875 L 1369.2812 845.21875 L 1369.2812 869.53125 L 1647.125 869.53125 L 1647.125 735.28125 L 1502.1562 735.28125 z M 1690.375 834.03125 L 1681.5625 834.09375 L 1681.5625 885.375 L 1662.1562 885.375 L 1662.2812 912.09375 L 1647.5625 912.125 L 1647.5625 885.375 L 1369.7188 885.375 L 1369.7188 928.21875 L 1647 928.21875 L 1689.9062 928.21875 L 1690.375 834.03125 z M 1663.4062 838.125 L 1660.4375 838.15625 L 1660.5938 870.46875 L 1663.4062 870.46875 L 1663.4062 838.125 z "
+ id="path7892" />
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1368.2454,1469.8261 L 1368.2454,1495.6699 L 1410.9016,1495.6699 L 1410.9016,1469.8261 L 1368.2454,1469.8261 z M 1501.1204,1478.1386 L 1501.5579,1492.9824 L 1528.6516,1492.9824 L 1528.6516,1517.6386 L 1629.1204,1517.1074 L 1628.6829,1586.8261 L 1528.6516,1587.2949 L 1528.6516,1612.3886 L 1639.4016,1612.3886 L 1646.0891,1612.3886 L 1646.0891,1478.1386 L 1501.1204,1478.1386 z M 1688.9016,1579.9199 L 1681.1204,1579.9511 L 1681.1204,1584.1386 L 1681.1204,1632.6699 L 1660.6829,1632.6699 L 1660.8079,1657.9511 L 1646.0891,1658.0136 L 1646.0891,1632.6699 L 1637.8391,1632.6699 L 1528.6516,1632.6699 L 1528.6516,1647.0136 L 1441.7454,1647.0136 L 1441.7454,1632.6699 L 1368.2454,1632.6699 L 1368.2454,1674.1074 L 1645.5579,1674.1074 L 1688.4329,1674.1074 L 1688.9016,1579.9199 z M 1663.4016,1580.0449 L 1660.4329,1580.0761 L 1660.5891,1612.3886 L 1663.4016,1612.3886 L 1663.4016,1580.0449 z M 1441.7454,1587.7011 L 1368.2454,1588.0761 L 1368.2454,1612.3886 L 1441.7454,1612.3886 L 1441.7454,1587.7011 z"
+ id="path8411" />
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -161.22241,1457.4046 L -161.22241,1635.9671 L -131.22241,1635.9671 L -131.22241,1677.4046 L 146.09009,1677.4046 L 188.96509,1677.4046 L 189.43379,1583.2171 L 181.65259,1583.2484 L 181.65259,1457.4046 L -161.22241,1457.4046 z M -148.53491,1466.1859 L -88.566206,1466.1859 L -88.566206,1473.1234 L -131.22241,1473.1234 L -131.22241,1498.9671 L -148.53491,1498.9671 L -148.53491,1466.1859 z M 146.62129,1481.4359 L 163.93379,1481.4359 L 163.93379,1583.3421 L 160.96509,1583.3734 L 161.12129,1615.6859 L 146.62129,1615.6859 L 146.62129,1481.4359 z M 2.0900943,1496.2796 L 29.183794,1496.2796 L 29.183794,1520.9359 L 2.8087943,1521.0921 L 2.0900943,1496.2796 z M 29.183794,1590.5921 L 29.183794,1615.6859 L -57.722406,1615.6859 L -57.722406,1590.9984 L 29.183794,1590.5921 z M -131.22241,1591.3734 L -131.22241,1615.6859 L -147.00371,1615.6859 L -147.00371,1591.4359 L -131.22241,1591.3734 z M -57.722406,1635.9671 L 29.183794,1635.9671 L 29.183794,1650.3109 L -57.722406,1650.3109 L -57.722406,1635.9671 z M 146.62129,1635.9671 L 161.21509,1635.9671 L 161.34009,1661.2484 L 146.62129,1661.3109 L 146.62129,1635.9671 z"
+ id="path8416" />
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 936.10246,1469.7356 L 936.10246,1495.5793 L 893.44616,1495.5793 L 893.44616,1587.9856 L 966.94616,1587.6106 L 966.94616,1492.8918 L 1026.7586,1492.8918 L 1026.3211,1478.0481 L 1171.29,1478.0481 L 1171.29,1469.7356 L 936.10246,1469.7356 z M 914.00866,1510.6418 L 946.78996,1510.6418 L 946.78996,1570.5793 L 914.00866,1570.5793 L 914.00866,1510.6418 z M 1154.3212,1517.0168 L 1053.8524,1517.5481 L 1053.8524,1587.2043 L 1153.8837,1586.7356 L 1154.3212,1517.0168 z M 1206.3212,1579.8606 L 1188.6025,1579.9543 L 1188.6025,1612.2981 L 1185.79,1612.2981 L 1185.8837,1632.5793 L 1206.3212,1632.5793 L 1206.3212,1584.0481 L 1206.3212,1579.8606 z M 893.44616,1612.2981 L 893.44616,1632.5793 L 966.94616,1632.5793 L 966.94616,1612.2981 L 893.44616,1612.2981 z M 1053.8524,1612.2981 L 1053.8524,1632.5793 L 1163.04,1632.5793 L 1171.29,1632.5793 L 1171.29,1612.2981 L 1164.6025,1612.2981 L 1053.8524,1612.2981 z"
+ id="path8421" />
+ <path
+ id="path8406"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:4;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 1368.7188,1164.468 L 1368.7188,1190.3118 L 1411.375,1190.3118 L 1411.375,1164.468 L 1368.7188,1164.468 z M 1502.1562,1172.4368 L 1502.4688,1182.843 L 1529.0938,1182.843 L 1529.0938,1211.968 L 1630.1562,1211.4055 L 1629.7188,1281.1243 L 1529.0938,1281.593 L 1529.0938,1306.6868 L 1647.125,1306.6868 L 1647.125,1172.4368 L 1502.1562,1172.4368 z M 1690.375,1271.1868 L 1681.5625,1271.2493 L 1681.5625,1322.5305 L 1662.1562,1322.5305 L 1662.2812,1349.2493 L 1647.5625,1349.2805 L 1647.5625,1322.5305 L 1529.0938,1322.5305 L 1529.0938,1336.8743 L 1442.2188,1336.8743 L 1442.2188,1322.5305 L 1369.7188,1322.5305 L 1369.7188,1365.3743 L 1647,1365.3743 L 1689.9062,1365.3743 L 1690.375,1271.1868 z M 1442.2188,1282.0305 L 1369.2812,1282.3743 L 1369.2812,1306.6868 L 1442.2188,1306.6868 L 1442.2188,1282.0305 zM 1663.4016,1275.2675 L 1660.4329,1275.2987 L 1660.5891,1307.6112 L 1663.4016,1307.6112 L 1663.4016,1275.2675 z" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans"
+ x="181.63506"
+ y="73.380066"
+ id="text2348"><tspan
+ sodipodi:role="line"
+ id="tspan2350"
+ x="181.63506"
+ y="73.380066">Shape Boolops</tspan><tspan
+ sodipodi:role="line"
+ x="181.63506"
+ y="103.38007"
+ id="tspan2352">Michael Sloan</tspan></text>
+ </g>
+</svg>
diff --git a/doc/sweep.svg b/doc/sweep.svg
new file mode 100644
index 0000000..72920c9
--- /dev/null
+++ b/doc/sweep.svg
@@ -0,0 +1,430 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1052.3622"
+ height="744.09448"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.46+devel r21616 custom"
+ sodipodi:docname="sweep.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Send"
+ style="overflow:visible">
+ <path
+ id="path936"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
+ </marker>
+ <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" />
+ <inkscape:perspective
+ id="perspective859"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.70082296"
+ inkscape:cx="851.52284"
+ inkscape:cy="299.19226"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1680"
+ inkscape:window-height="977"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid12"
+ empspacing="4"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ units="mm"
+ spacingx="1mm"
+ spacingy="1mm" />
+ </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></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-308.2677)">
+ <path
+ id="path1413"
+ d="m 226.77166,712.2047 c 99.2126,-28.34646 368.50394,14.17323 481.88976,0"
+ style="fill:none;stroke:#000000;stroke-width:1px"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px"
+ d="m 240.94489,953.14958 127.55905,0"
+ id="path847" />
+ <path
+ id="path849"
+ d="m 637.79528,953.14958 127.55905,0"
+ style="fill:none;stroke:#000000;stroke-width:1px" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px"
+ d="m 226.77166,726.37793 c 85.03937,0 396.85039,0 396.85039,212.59842"
+ id="path843" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px"
+ d="m 432.28348,818.50391 42.51968,-28.34646 0,56.69291 -42.51968,-28.34645"
+ id="path845" />
+ <rect
+ y="471.25983"
+ x="141.73233"
+ height="28.346457"
+ width="42.519684"
+ id="rect793"
+ style="fill:#b3b3b3;stroke:none" />
+ <rect
+ y="471.25983"
+ x="255.11816"
+ height="28.346457"
+ width="42.519684"
+ id="rect797"
+ style="fill:#b3b3b3;stroke:none" />
+ <rect
+ y="471.25983"
+ x="368.504"
+ height="28.346457"
+ width="42.519684"
+ id="rect801"
+ style="fill:#b3b3b3;stroke:none" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect14"
+ width="42.519684"
+ height="28.346457"
+ x="481.8898"
+ y="471.25983" />
+ <path
+ id="path795"
+ d="m 127.55911,414.56689 0,155.90553"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0" />
+ <path
+ id="path799"
+ d="m 240.94494,414.5669 c 0,0 0,30.55294 0,56.69292 0,7.08661 3.54331,10.62992 7.08661,10.62992 8.89859,0 21.25985,0 21.25985,10.62992 0,21.97915 -21.25985,15.37432 -21.25985,7.08661 0,-7.08661 -7.08661,-6.84684 -7.08661,0 0,27.18227 0,70.86615 0,70.86615"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ sodipodi:nodetypes="cssssss" />
+ <path
+ sodipodi:nodetypes="cssssssssss"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ d="m 354.33077,414.56689 c 0,0 0,30.55294 0,56.69292 0,7.08661 3.54331,10.62992 7.08661,10.62992 5.30296,0 10.62992,1e-5 14.17323,1e-5 7.08661,0 4.51157,-14.57853 10.62992,-17.71654 5.14942,-2.64105 21.25984,-3.5433 28.34646,3.54331 7.08661,7.08662 7.08661,28.34646 0,35.43307 -7.08662,7.08661 -38.71107,5.28254 -46.063,3.54331 -3.44813,-0.81572 -7.08661,-3.07644 -7.08661,-7.08662 0,-7.08662 -7.08661,-6.84685 -7.08661,0 0,27.18227 0,70.86615 0,70.86615"
+ id="path805" />
+ <path
+ id="path791"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ d="m 467.71659,570.47242 0,-81.49607 7.08662,0 -10e-6,10.62993 c 0,3.92598 3.16063,7.08661 7.08661,7.08661 l 42.5197,0 c 3.92598,0 7.08661,-3.16063 7.08661,-7.08661 l 0,-28.34646 c 0,-3.92598 -3.16063,-7.08661 -7.08661,-7.08661 l -42.5197,0 c -3.92598,0 -7.08661,3.16063 -7.08661,7.08661 l 10e-6,10.62992 -7.08662,0 0,-67.32284"
+ sodipodi:nodetypes="cccccccccccccc" />
+ <rect
+ y="471.25983"
+ x="595.2757"
+ height="28.346457"
+ width="42.519684"
+ id="rect815"
+ style="fill:#b3b3b3;stroke:none" />
+ <path
+ id="path817"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ d="m 609.44888,513.7795 -14.17326,0 c -7.85197,0 -14.17323,-6.32126 -14.17323,-14.17323 l 3e-5,-10.62992 7.08663,10e-6 -10e-6,10.62992 c 0,3.92598 3.16063,7.08661 7.08661,7.08661 l 42.5197,0 c 3.92598,0 7.08661,-3.16063 7.08661,-7.08661 l 0,-28.34646 c 0,-3.92598 -3.16063,-7.08661 -7.08661,-7.08661 l -42.5197,0 c -3.92598,0 -7.08661,3.16063 -7.08661,7.08661 l 10e-6,10.62992 -7.08663,0 -3e-5,-10.62992 c 0,-7.85197 6.32126,-14.17323 14.17323,-14.17323 l 14.17326,0 m 0,0 0,-42.51969 m 0,155.90552 0,-56.69292"
+ sodipodi:nodetypes="cccccccccccccccccccccc" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect823"
+ width="42.519684"
+ height="28.346457"
+ x="708.66144"
+ y="471.25983" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccccccccc"
+ d="m 772.441,414.5669 0,67.32284 -7.08661,0 -3e-5,-10.62992 c 0,-7.85197 -6.32126,-14.17323 -14.17323,-14.17323 l -42.51971,0 c -7.85197,0 -14.17323,6.32126 -14.17323,14.17323 l 3e-5,10.62992 7.08663,10e-6 -10e-6,-10.62993 c 0,-3.92598 3.16063,-7.08661 7.08661,-7.08661 l 42.5197,0 c 3.92598,0 7.08661,3.16063 7.08661,7.08661 l 0,28.34646 c 0,3.92598 -3.16063,7.08661 -7.08661,7.08661 l -42.5197,0 c -3.92598,0 -7.08661,-3.16063 -7.08661,-7.08661 l 10e-6,-10.62992 -7.08663,0 -3e-5,10.62991 c 0,7.85197 6.32126,14.17323 14.17323,14.17323 l 42.51971,0 c 7.85197,0 14.17323,-6.32126 14.17323,-14.17323 l 3e-5,-10.62992 7.08661,0 0,81.49607"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ id="path825" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect831"
+ width="42.519684"
+ height="28.346457"
+ x="822.04724"
+ y="471.25983" />
+ <path
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ d="m 878.74021,414.5669 0,155.90553"
+ id="path833" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect835"
+ width="297.63779"
+ height="28.346457"
+ x="354.33069"
+ y="938.97632" />
+ <rect
+ y="783.07086"
+ x="467.71661"
+ height="14.173101"
+ width="14.173126"
+ id="rect841"
+ style="fill:#b3b3b3;stroke:none" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect837"
+ width="14.173126"
+ height="14.173101"
+ x="467.71652"
+ y="839.76385" />
+ <rect
+ style="fill:#b3b3b3;stroke:none"
+ id="rect839"
+ width="14.173228"
+ height="14.173239"
+ x="425.19684"
+ y="811.4173" />
+ <path
+ sodipodi:nodetypes="cccccccccccccc"
+ d="m 325.98426,1038.189 -1e-5,-92.12603 21.25985,0 0,24.80315 c 0,3.92598 3.16063,7.08661 7.08661,7.08661 l 297.6378,0 c 3.92598,0 10.62992,-3.16063 10.62992,-7.08661 l 0,-35.43308 c 0,-3.92598 -6.70394,-7.08661 -10.62992,-7.08661 l -297.63779,0 c -3.92598,0 -7.08661,3.16063 -7.08661,7.08661 l -10e-6,3.54331 -21.25985,0 1e-5,-255.11811"
+ style="fill:none;stroke:#0000ff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ id="path877" />
+ <path
+ transform="translate(116.92914,503.1496)"
+ d="m 212.59842,226.77164 a 3.5433071,3.5433071 0 1 1 -7.08661,0 3.5433071,3.5433071 0 1 1 7.08661,0 z"
+ sodipodi:ry="3.5433071"
+ sodipodi:rx="3.5433071"
+ sodipodi:cy="226.77164"
+ sodipodi:cx="209.05511"
+ id="use883"
+ style="fill:#00ff00;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ transform="translate(116.92914,726.37794)"
+ d="m 212.59842,226.77164 a 3.5433071,3.5433071 0 1 1 -7.08661,0 3.5433071,3.5433071 0 1 1 7.08661,0 z"
+ sodipodi:ry="3.5433071"
+ sodipodi:rx="3.5433071"
+ sodipodi:cy="226.77164"
+ sodipodi:cx="209.05511"
+ id="use891"
+ style="fill:#00ff00;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ transform="translate(453.54332,726.37794)"
+ d="m 212.59842,226.77164 a 3.5433071,3.5433071 0 1 1 -7.08661,0 3.5433071,3.5433071 0 1 1 7.08661,0 z"
+ sodipodi:ry="3.5433071"
+ sodipodi:rx="3.5433071"
+ sodipodi:cy="226.77164"
+ sodipodi:cx="209.05511"
+ id="use881"
+ style="fill:#00ff00;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ transform="translate(138.18898,726.37794)"
+ d="m 212.59842,226.77164 a 3.5433071,3.5433071 0 1 1 -7.08661,0 3.5433071,3.5433071 0 1 1 7.08661,0 z"
+ sodipodi:ry="3.5433071"
+ sodipodi:rx="3.5433071"
+ sodipodi:cy="226.77164"
+ sodipodi:cx="209.05511"
+ id="path887"
+ style="fill:#ff0000;stroke:none"
+ sodipodi:type="arc" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path887"
+ id="use893"
+ transform="translate(276.37795,-24.803144)"
+ width="744.09448"
+ height="1052.3622" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)"
+ d="m 127.55911,428.74013 28.34645,0"
+ id="path916" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(340.15748,1.7672246e-6)"
+ id="use1369"
+ xlink:href="#path916"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path916"
+ id="use1367"
+ transform="translate(481.88976,1.7672246e-6)"
+ width="744.09448"
+ height="1052.3622" />
+ <path
+ id="path1371"
+ d="m 262.20478,506.69289 c 10.03817,7.0722 19.2398,4.10591 28.34646,0"
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)"
+ d="m 388.22918,456.94784 c -12.15917,-1.71331 -18.9939,5.12449 -25.22416,12.93305"
+ id="path1373" />
+ <path
+ sodipodi:nodetypes="cc"
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)"
+ d="m 688.46073,501.78863 c 0.007,13.75525 8.4367,18.00622 20.95338,19.09129"
+ id="path1375" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path1375"
+ id="use1377"
+ transform="matrix(1,0,0,-1,-0.70939357,971.46722)"
+ width="744.09448"
+ height="1052.3622" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ x="623.62207"
+ y="783.07086"
+ id="text1379"><tspan
+ sodipodi:role="line"
+ id="tspan1381"
+ x="623.62207"
+ y="783.07086"
+ style="font-size:12px">this tile has its both ends</tspan><tspan
+ sodipodi:role="line"
+ x="623.62207"
+ y="798.07086"
+ id="tspan1383"
+ style="font-size:12px">in the &quot;known&quot; area, but still</tspan><tspan
+ sodipodi:role="line"
+ x="623.62207"
+ y="813.07086"
+ id="tspan1385"
+ style="font-size:12px">travels through the unknown.</tspan></text>
+ <path
+ id="path1387"
+ d="m 418.11024,818.50391 -85.03937,0"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 2;stroke-dashoffset:0;marker-end:url(#Arrow1Send)"
+ sodipodi:nodetypes="cc" />
+ <text
+ id="text1395"
+ y="839.76373"
+ x="340.15747"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ style="font-size:12px"
+ y="839.76373"
+ x="340.15747"
+ sodipodi:role="line"
+ id="tspan1407">this piece should </tspan><tspan
+ style="font-size:12px"
+ y="854.76373"
+ x="340.15747"
+ sodipodi:role="line"
+ id="tspan1419">be inserted here</tspan></text>
+ <path
+ sodipodi:nodetypes="cc"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker-end:url(#Arrow1Send)"
+ d="m 694.4882,825.59053 c 0,28.34645 -48.419,52.18112 -70.86615,56.69291"
+ id="path1411" />
+ <path
+ sodipodi:nodetypes="cc"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 2;stroke-dashoffset:0;marker-end:url(#Arrow1Send)"
+ d="M 446.46402,797.25871 C 435.54028,749.38028 382.88666,716.7021 332.35416,712.2047"
+ id="path1415" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#use883"
+ id="use1417"
+ transform="translate(4.0944874e-7,-28.346454)"
+ width="744.09448"
+ height="1052.3622" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ x="396.85037"
+ y="726.37793"
+ id="text1425"><tspan
+ id="tspan1429"
+ sodipodi:role="line"
+ x="396.85037"
+ y="726.37793"
+ style="font-size:12px">not there!</tspan></text>
+ <path
+ style="fill:none;stroke:#ff0000;stroke-width:4"
+ d="m 411.02363,768.89761 42.51968,0 m -14.17322,-14.17323 -14.17323,28.34646"
+ id="path1433"
+ sodipodi:nodetypes="cccc" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ x="212.59842"
+ y="357.87399"
+ id="text1435"><tspan
+ sodipodi:role="line"
+ x="212.59842"
+ y="357.87399"
+ id="tspan1439"
+ style="font-size:20px">Deformations of the sweep line when crossing a vertex box.</tspan></text>
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(751.18105,5.1777802e-6)"
+ id="use1457"
+ xlink:href="#path916"
+ y="0"
+ x="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ x="170.07874"
+ y="641.33856"
+ id="text1459"><tspan
+ sodipodi:role="line"
+ id="tspan1461"
+ x="170.07874"
+ y="641.33856"
+ style="font-size:20px">Why should tiles be kept in the context until the end of their last box.</tspan></text>
+ </g>
+</svg>
diff --git a/doc/tutorial.txt b/doc/tutorial.txt
new file mode 100644
index 0000000..9d0c81e
--- /dev/null
+++ b/doc/tutorial.txt
@@ -0,0 +1,291 @@
+(09:03:49) ACSpike: I tried to glean drawing a circle from conic-4 (I think) I'm either missing the drawing of the circle in the rest of the code, or it is just so short and simple that I don't get it
+(09:08:03) ACSpike: heh, oh "Define here various primatives, such as line, line segment, circle, bezier path etc."
+(09:18:29) njh: don't look at that
+(09:18:37) njh: that's done in a hacky way
+(09:19:09) njh: ok, lets plan what your program will do
+(09:19:23) njh: I'm thinking an 'on canvas' editor for gears
+(09:20:13) njh: the biggest problem is that you have lumpy parameters (for example number of teeth is a whole number)
+(09:20:54) ACSpike: lets start smaller
+(09:21:00) njh: ok
+(09:21:09) njh: howabout just drawing a circle
+(09:21:11) ACSpike: I need an entry point into the world of 2geom
+(09:21:31) ACSpike: can I get an svg path for a circle?
+(09:21:35) njh: well, what I was going to suggest was just making circles with handles
+(09:21:44) ACSpike: ie, no gtk gui stuff
+(09:21:47) njh: hehe, I still haven't done circles :)
+(09:21:52) njh: but you can use cairo
+(09:21:58) ACSpike: well
+(09:22:02) njh: cairo_arc
+(09:22:03) ACSpike: what can I get?
+(09:22:27) njh: lets make a program that just draws a single circle
+(09:23:00) ACSpike: ok. back in a bit
+(09:31:15) njh: ok
+(09:31:22) njh: I've committed a starting point for you
+(09:31:23) ACSpike: ok, I get it. 2geom doesn't do svg
+(09:31:29) ACSpike: it does beziers
+(09:31:37) ACSpike: and draws them on a cairo surface
+(09:32:27) njh: at this point, yeah
+(09:32:38) njh: actually, it doesn't even draw them :)
+(09:32:54) njh: that's done by path-cairo, which is demidetached :)
+(09:33:22) njh: so gear.cpp is a starting point for you
+(09:33:51) njh: one issue is that I haven't done elliptical arcs in Paths yet
+(09:34:04) njh: so we're going to not even use paths at this point
+(09:34:34) njh: just attempt to make a circle using sbasis rather than calling cairo_arc
+(09:35:03) verbalshadow [verbalshadow@gristle.org/Laptop] entered the room.
+(09:35:17) ACSpike: oy
+(09:35:19) njh: so a circle is parameterised by <cos(t), sin(t)> with t going form 0 to 2*pi
+(09:35:27) ACSpike: right
+(09:35:41) njh: does gear compile and run on your computer?
+(09:36:35) ACSpike: did you commit it?
+(09:39:27) njh: I spose I should add before commiting
+(09:39:37) njh: done
+(09:39:57) njh: ok, your second step will be to add two handles
+(09:48:04) njh: feel free to ask if you are stuck
+(09:48:50) ACSpike: oh, boy
+(09:49:04) ACSpike: so we have a handle for radius
+(09:50:01) Botty: the most elegant way to do this convex stuff would be to have a circular iterator
+(09:50:16) Botty: i suppose modulus works
+(09:51:32) njh: it does
+(09:51:55) njh: ACSpike: the longest journey starts with a single step
+(09:52:11) ACSpike: or a single grep
+(09:52:24) njh: I prefer emacs isearch
+(09:52:35) njh: so, does it compile?
+(09:52:39) ACSpike: ya
+(09:52:41) ACSpike: and runs
+(09:52:48) njh: and have yuo worked out how to add a handle
+(09:52:59) ACSpike: pushback
+(09:53:05) njh: yep
+(09:53:11) njh: just increase the loop
+(09:53:27) njh: generates random handles
+(09:53:51) njh: anyway, have you got two extra handles?
+(09:54:00) ACSpike: no
+(09:54:01) njh: please tell me when you have something working
+(09:54:16) ACSpike: I'm trying to grok the single handle
+(09:54:33) njh: just assume that handles can be moved anyway
+(09:54:40) njh: how they work is a bit fiddly
+(09:54:49) ACSpike: and raise kids :-)
+(09:54:51) njh: but all they are is a Geom::Point
+(09:57:54) njh: no
+(09:58:05) njh: how do I do that?
+(10:01:44) ACSpike: yes
+(10:05:04) ACSpike: norm ~ magnatude ~ distance?
+(10:06:07) njh: norms are like distance, yes
+(10:06:31) njh: but not just 'as the crow flies' distance
+(10:06:51) njh: another norm would be how long it takes you to get between places
+(10:07:17) njh: 2geom provides a few norms: L2 and Linfinity
+(10:07:28) njh: L2 = eucliean, as the crow flies distance
+(10:07:34) ACSpike: L1, L2 and infinity
+(10:07:47) ACSpike: l1 equals as the taxi drives
+(10:07:50) njh: Linfinity = maximum distance in x or y
+(10:07:56) njh: yeah l1 is taxi
+(10:08:07) Botty: (X+Y)
+(10:08:11) ACSpike: linfinity is x or y?
+(10:08:14) njh: no, |X| + |Y|
+(10:08:23) Botty: good point...
+(10:08:25) njh: Linfinity = max(|X|, |Y|)
+(10:08:30) ACSpike: right
+(10:08:34) ACSpike: interesting
+(10:08:40) ACSpike: thanks
+(12:02:59) ACSpike: what should I do with these two random handles?
+(12:03:19) ACSpike: pressure angle and number of teeth is what's needed
+(12:06:18) njh: lets start with a line
+(12:06:49) ACSpike: like constrain the movement of the handles?
+(12:06:52) njh: ok, SBasis functions map [0,1] to a value
+(12:06:57) njh: no, just darwing a line segment
+(12:07:20) ACSpike: each handle makes one endpoint?
+(12:07:21) njh: so we want to construct a function that maps [0,1] onto a line from handle 1 to handle 2
+(12:08:14) njh: do you have two new handles?
+(12:09:35) ACSpike: yes
+(12:09:59) njh: ok, so we're going to make a pair of sbasis functions, one for x, one for y
+(12:10:10) njh: to do this we need a multidim_sbasis<2>
+(12:10:18) njh: (one day I'll work out better names :)
+(12:10:28) ACSpike: which means, a second degree sbasis?
+(12:10:47) njh: multidim_sbasis<2> B;
+(12:10:56) ACSpike: this is global?
+(12:10:57) njh: it means a function which maps [0,1] onto a point
+(12:11:03) njh: no, put it in expose
+(12:11:07) njh: everything goes in expose
+(12:20:09) njh: anyway, so you have a function that maps [0,1] onto a point
+(12:20:08) ACSpike: I'm about to look for the definition of multidim_sbasis
+(12:20:15) njh: don't
+(12:20:23) njh: it's complicated and not necessary
+(12:20:27) ACSpike: ok
+(12:20:36) ACSpike: leaps with faith
+(12:21:21) njh: so we need to define what the functions are for X and Y
+(12:21:36) njh: just like a point, these are B[X] and B[Y]
+(12:21:47) ACSpike: ah
+(12:21:55) ACSpike: X and Y are defined somewhere?
+(12:22:00) njh: yeah, in point I think
+(12:22:06) njh: but I'm lazy and use 0 and 1
+(12:22:12) ACSpike: ah, good
+(12:23:33) njh: Now the simplest function maps [0,1] onto a constant value
+(12:23:53) njh: we could do this with B[0] = handles[1][0];
+(12:23:59) ACSpike: so all values between 0 and 1 are the same
+(12:24:03) njh: and similarly B[1] = handles[1][1];
+(12:24:07) njh: yep
+(12:24:11) njh: that would define a point
+(12:24:32) njh: (I'm not sure that would compile, due to missing code)
+(12:24:48) njh: I usually do everything in parallel like this:
+(12:24:55) njh: for(int im = 0; dim < 2; dim++)
+(12:25:04) njh: B[dim] = handles[1][dim];
+(12:25:25) njh: remember that handles[0] is the point on the gear we did already
+(12:25:51) njh: we're going to draw a line somewhere
+(12:25:57) njh: (you have to draw a line somewhere!)
+(12:26:27) njh: to do this we want to map [0,1] to points between handles[1] and handles[2]
+(12:27:06) njh: for technical(and not very good) reasons this means using BezOrds
+(12:27:11) ACSpike: pause for reflection
+(12:27:11) njh: like this:
+(12:27:30) njh: B[dim] = BezOrd(handles[1][dim], handles[2][dim]);
+(12:27:41) ACSpike: what are BezOrds?
+(12:27:50) njh: so try adding that code into expose
+(12:28:07) njh: BezOrd(a,b) maps 0,1 onto [a,b]
+(12:29:48) njh: the reason for BezOrds is they are the fundamental unit for all the maths
+(12:30:08) ACSpike: what does BezOrd mean though?
+(12:30:10) njh: just like points are the fundamental units for graphics
+(12:30:14) njh: Bezier Ordinal
+(12:30:16) ACSpike: I need to attach the idea to the name
+(12:30:27) njh: you can think of them as linear bezier segments
+(12:30:46) njh: add another poit and you have a quadratic, another, cubic
+(12:31:08) njh: a two point bezier is a line segment
+(12:31:13) Botty: so its like a parametric thing?
+(12:31:19) njh: Botty: correct
+(12:31:30) njh: parametric here means maps from [0,1] to a point
+(12:31:37) njh: <x(t), y(t)>
+(12:31:38) ACSpike: sbasis is all parametric vector squishyness
+(12:31:50) njh: yes, most computer graphics is parametric
+(12:31:59) ACSpike: and squishy
+(12:32:06) njh: sometimes
+(12:32:11) njh: sometimes it is all angular
+(12:39:28) ACSpike: do I need to draw the bezord out?
+(12:39:39) njh: draw it out?
+(12:39:41) njh: to the canvas?
+(12:40:08) njh: no, here is some boilerplate to draw a md_sb to the canvas
+(12:40:25) njh: void draw_cb(cairo_t *cr, multidim_sbasis<2> const &B) {
+ Geom::PathBuilder pb;
+ subpath_from_sbasis(pb, B, 0.1);
+ cairo_path(cr, pb.peek());
+}
+(12:40:37) njh: add that to gear
+(12:41:02) njh: perhaps change the name to draw_md_sb or something
+(12:41:11) njh: then to draw B, just use:
+(12:41:19) njh: draw_md_sb(cr, B);
+(12:41:26) njh: (cr is the cairo canvas)
+(12:41:38) njh: so paste what you have so far
+(12:41:50) njh: (I mean just your lines, not the whole file!)
+(12:46:12) ACSpike: random points are in the same spot on every execution?
+(12:46:31) njh: correct
+(12:46:36) ACSpike: neat
+(12:46:48) njh: that's just rand()
+(12:47:11) ACSpike: wow, the line. it moves.
+(12:47:16) njh: if you want different positions you start the random number generator in a different spot, using say the current time
+(12:47:28) njh: can you commit your changes?
+(12:49:09) ACSpike: yes
+(12:49:11) ACSpike: done
+(12:50:24) njh: ok, so we have a single line :)
+(12:50:35) ACSpike: and a single circle
+(12:50:46) njh: now the nice thing about lines in this form is we can perform arithmetic on them
+(12:50:46) ACSpike: but I don't know why we have a line
+(12:50:54) ACSpike: ok
+(12:52:14) njh: ok, so now you have some experience with lines, we're going to try to make an arc
+(12:52:30) njh: remember that a circle is just <cos, sin>
+(12:52:35) ACSpike: why would I perform arithmetic on a line?
+(12:52:51) njh: because all geometry is arithmetic
+(12:53:31) njh: so we're going to use two built in functions, sin and cos to make an arc from 0 to 1 radian
+(12:53:52) njh: SBasis sin(double a0, double a1, int k);
+SBasis cos(double a0, double a1, int k)
+(12:54:15) njh: these two functions take a range of angles (a0, a1) and a parameter k
+(12:54:23) njh: k is the accuracy
+(12:54:30) njh: for now lets just use k = 2
+(12:55:23) njh: so lets make B[0] = BezOrd(centre[0]) + 100*cos(0,1,2);
+(12:55:29) njh: and similarly Y
+(12:55:44) ***njh has never tried this before, it might not work :)
+(12:56:10) ACSpike: I realize you are taking really small really slow steps
+(12:56:21) ACSpike: but I'm loosing a lot of it
+(12:56:35) njh: that should make an arc centred at the centre with a radius 100
+(12:56:55) njh: perhaps we could convert this conversation into a tutorial when we're finished
+(12:57:01) ACSpike: do I replace the line?
+(12:57:06) njh: yeah
+(12:57:07) ACSpike: make a new arc?
+(12:57:09) ACSpike: ok
+(12:57:19) njh: just comment out the line if you like
+(12:57:25) njh: or you can overwrite it
+(12:58:02) ACSpike: compiling
+(13:00:05) ACSpike: http://rafb.net/paste/results/ZXudDC19.html
+(13:01:26) ACSpike: misplaced parens?
+(13:02:00) njh: no, missing defn
+(13:02:03) njh: try
+(13:02:16) njh: SBasis(BezOrd(centre[0])) + 100*cos(0,1,2);
+(13:02:40) njh: might be due to std::cos actually
+(13:02:48) njh: sin and cos are slightly crap
+(13:03:00) njh: ah, I've got an idea
+(13:05:37) njh: yep, looks like it will work
+(13:07:02) ACSpike: indeed it does
+(13:07:08) ACSpike: now I can draw arcs
+(13:07:29) ACSpike: ok
+(13:07:48) ACSpike: at this point I'm gonna copy the backlogs and go to bed
+(13:08:07) njh: ok!
+(13:08:09) njh: worked it out
+(13:08:21) ACSpike: worked what?
+(13:08:26) njh: I know all this sounds pedestrian
+(13:08:46) ACSpike: you mean this tutorial?
+(13:09:03) njh: but perhaps what you aren't realising is that when you write cos(0,1,2) you aren't just computing cos at a single point
+(13:09:10) ACSpike: right
+(13:09:12) njh: you are computing cos everywhere at the same time
+(13:09:16) ACSpike: it the whole sweep
+(13:09:19) njh: yep
+(13:09:23) ACSpike: I see that
+(13:09:31) ACSpike: but I don't "get" it at all :-)
+(13:09:38) njh: if you run conic-3 you'll see that it converts to beziers automagically
+(13:09:52) njh: well, do you understand how std::cos(t) works?
+(13:10:11) ACSpike: I don't even understand the question
+(13:10:31) njh: well, you wrote cos(x) in your gear program
+(13:10:36) njh: do you understand how it works?
+(13:10:38) ACSpike: my math is really rusty
+(13:10:46) njh: right, yet you managed to draw gears
+(13:11:02) njh: my point is that understanding how something works isn't entirely necessary to use it
+(13:11:05) ACSpike: I don't know the definition of the function, but I know the triangle soh cah toa thing
+(13:11:11) njh: yep
+(13:11:32) njh: I use floating point all the time. I know exactly how it works,because I once implemented my own version
+(13:11:41) njh: but 99.9999% of programmers don't
+(13:11:52) ACSpike: I read the spec once
+(13:11:53) njh: the same should be true of this new stuff
+(13:12:13) ACSpike: but I want to grok it because I want to help
+(13:12:19) njh: you should be able to make an involute without any more than a rough idea of how it works
+(13:12:29) Botty: I just remember that sin is Y (intuitively opposite), cos is X (intuitively adjacent), and tan is Y / X
+(13:12:32) njh: I think you will grok it, once you've got the hang of playing withit
+(13:12:49) njh: we'll get some nice circular arcs going
+(13:13:01) njh: maybe you can try and come up with a nice interface for circulat arcs
+(13:13:04) ACSpike: I think right now I could draw all the arcs from the gear
+(13:13:14) njh: yep, I think so too
+(13:13:26) njh: and you would get bezier curves at the end, rather than line segments
+(13:13:36) njh: and I think it would be a lot faster as well
+(13:13:40) ACSpike: right
+(13:13:44) njh: (actually, in this case, I doubt it metters :)
+(13:14:04) ACSpike: curveto or arcto?
+(13:14:19) njh: curveto, I'm afraid
+(13:14:31) njh: I would like to pick the best choice, but I haven't worked out how yet
+(13:14:56) ACSpike: so if I want to draw the involute I need to map that function in there somehow
+(13:15:04) njh: but you can't represent involutes with arcs anyway
+(13:15:07) njh: yeah
+(13:15:13) ACSpike: ah hah
+(13:15:15) njh: that is basically all there is to it
+(13:15:24) ACSpike: so this is crazy function plotting
+(13:15:38) njh: you should theoretically be able to just change the type of your equation to SBasis and use the old code
+(13:15:57) njh: the only reason you can't do that is because I haven't written all the operator*(,) type functions :)
+(13:16:19) njh: even more cool is you can compute the derivatives in the same way. that is something you simply can't do with point plotting
+(13:16:50) njh: for example, if you want the tangent to a bezier path, B, just write derivative(B)
+(13:17:10) njh: something I played with last night was trying to find the points of maximum and minimum curvature on paths
+(13:17:14) njh: ('corners')
+(13:17:29) njh: so I computed the curvature, took the derivative and found where that = 0
+(13:17:44) njh: SBasis curvature(multidim_sbasis<2> & B) {
+ multidim_sbasis<2> dB = derivative(B);
+ multidim_sbasis<2> ddB = derivative(dB);
+ SBasis n = multiply(dB[0], ddB[1]) - multiply(dB[1], ddB[0]);
+ SBasis den = multiply(dB[0], dB[0]) + multiply(dB[1], dB[1]);
+ den = multiply(den, den);
+ return divide(multiply(n, sqrt(den, 4)), den, 6);
+}
+
+(13:17:54) njh: that is pretty much the definition off wikipedia
+(13:18:16) njh: std::vector<double> r = roots(derivative(curvature(B)));
+(13:18:42) njh: gives r, a list of t values with maximum or minimum curvature
diff --git a/include/2geom/2geom.h b/include/2geom/2geom.h
new file mode 100644
index 0000000..7bf36ae
--- /dev/null
+++ b/include/2geom/2geom.h
@@ -0,0 +1,75 @@
+/**
+ * \file
+ * \brief Include everything
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2011 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 LIB2GEOM_SEEN_2GEOM_H
+#define LIB2GEOM_SEEN_2GEOM_H
+
+#include <2geom/forward.h>
+
+// primitives
+#include <2geom/coord.h>
+#include <2geom/point.h>
+#include <2geom/interval.h>
+#include <2geom/rect.h>
+#include <2geom/angle.h>
+#include <2geom/ray.h>
+#include <2geom/line.h>
+#include <2geom/affine.h>
+#include <2geom/transforms.h>
+
+// curves and paths
+#include <2geom/curves.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+
+// fragments
+#include <2geom/d2.h>
+#include <2geom/linear.h>
+#include <2geom/bezier.h>
+#include <2geom/sbasis.h>
+
+// others
+#include <2geom/math-utils.h>
+#include <2geom/utils.h>
+
+#endif // LIB2GEOM_SEEN_2GEOM_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/include/2geom/affine.h b/include/2geom/affine.h
new file mode 100644
index 0000000..470d5fc
--- /dev/null
+++ b/include/2geom/affine.h
@@ -0,0 +1,244 @@
+/**
+ * \file
+ * \brief 3x3 affine transformation matrix.
+ *//*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com> (Original NRAffine definition and related macros)
+ * Nathan Hurst <njh@mail.csse.monash.edu.au> (Geom::Affine class version of the above)
+ * Michael G. Sloan <mgsloan@gmail.com> (reorganization and additions)
+ * Krzysztof Kosiński <tweenk.pl@gmail.com> (removal of boilerplate, docs)
+ *
+ * This code is in public domain.
+ */
+
+#ifndef LIB2GEOM_SEEN_AFFINE_H
+#define LIB2GEOM_SEEN_AFFINE_H
+
+#include <boost/operators.hpp>
+#include <2geom/forward.h>
+#include <2geom/point.h>
+#include <2geom/utils.h>
+
+namespace Geom {
+
+/**
+ * @brief 3x3 matrix representing an affine transformation.
+ *
+ * Affine transformations on elements of a vector space are transformations which can be
+ * expressed in terms of matrix multiplication followed by addition
+ * (\f$x \mapsto A x + b\f$). They can be thought of as generalizations of linear functions
+ * (\f$y = a x + b\f$) to vector spaces. Affine transformations of points on a 2D plane preserve
+ * the following properties:
+ *
+ * - Colinearity: if three points lie on the same line, they will still be colinear after
+ * an affine transformation.
+ * - Ratios of distances between points on the same line are preserved
+ * - Parallel lines remain parallel.
+ *
+ * All affine transformations on 2D points can be written as combinations of scaling, rotation,
+ * shearing and translation. They can be represented as a combination of a vector and a 2x2 matrix,
+ * but this form is inconvenient to work with. A better solution is to represent all affine
+ * transformations on the 2D plane as 3x3 matrices, where the last column has fixed values.
+ * \f[ A = \left[ \begin{array}{ccc}
+ c_0 & c_1 & 0 \\
+ c_2 & c_3 & 0 \\
+ c_4 & c_5 & 1 \end{array} \right]\f]
+ *
+ * We then interpret points as row vectors of the form \f$[p_X, p_Y, 1]\f$. Applying a
+ * transformation to a point can be written as right-multiplication by a 3x3 matrix
+ * (\f$p' = pA\f$). This subset of matrices is closed under multiplication - combination
+ * of any two transforms can be expressed as the multiplication of their matrices.
+ * In this representation, the \f$c_4\f$ and \f$c_5\f$ coefficients represent
+ * the translation component of the transformation.
+ *
+ * Matrices can be multiplied by other more specific transformations. When multiplying,
+ * the transformations are applied from left to right, so for example <code>m = a * b</code>
+ * means: @a m first transforms by a, then by b.
+ *
+ * @ingroup Transforms
+ */
+class Affine
+ : boost::equality_comparable< Affine // generates operator!= from operator==
+ , boost::multipliable1< Affine
+ , MultipliableNoncommutative< Affine, Translate
+ , MultipliableNoncommutative< Affine, Scale
+ , MultipliableNoncommutative< Affine, Rotate
+ , MultipliableNoncommutative< Affine, HShear
+ , MultipliableNoncommutative< Affine, VShear
+ , MultipliableNoncommutative< Affine, Zoom
+ > > > > > > > >
+{
+ Coord _c[6];
+public:
+ Affine() {
+ _c[0] = _c[3] = 1;
+ _c[1] = _c[2] = _c[4] = _c[5] = 0;
+ }
+
+ /** @brief Create a matrix from its coefficient values.
+ * It's rather inconvenient to directly create matrices in this way. Use transform classes
+ * if your transformation has a geometric interpretation.
+ * @see Translate
+ * @see Scale
+ * @see Rotate
+ * @see HShear
+ * @see VShear
+ * @see Zoom */
+ Affine(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) {
+ _c[0] = c0; _c[1] = c1;
+ _c[2] = c2; _c[3] = c3;
+ _c[4] = c4; _c[5] = c5;
+ }
+
+ /** @brief Access a coefficient by its index. */
+ inline Coord operator[](unsigned i) const { return _c[i]; }
+ inline Coord &operator[](unsigned i) { return _c[i]; }
+
+ /// @name Combine with other transformations
+ /// @{
+ Affine &operator*=(Affine const &m);
+ // implemented in transforms.cpp
+ Affine &operator*=(Translate const &t);
+ Affine &operator*=(Scale const &s);
+ Affine &operator*=(Rotate const &r);
+ Affine &operator*=(HShear const &h);
+ Affine &operator*=(VShear const &v);
+ Affine &operator*=(Zoom const &);
+ /// @}
+
+ bool operator==(Affine const &o) const {
+ for(unsigned i = 0; i < 6; ++i) {
+ if ( _c[i] != o._c[i] ) return false;
+ }
+ return true;
+ }
+
+ /// @name Get the parameters of the matrix's transform
+ /// @{
+ Point xAxis() const;
+ Point yAxis() const;
+ Point translation() const;
+ Coord expansionX() const;
+ Coord expansionY() const;
+ Point expansion() const { return Point(expansionX(), expansionY()); }
+ /// @}
+
+ /// @name Modify the matrix
+ /// @{
+ void setXAxis(Point const &vec);
+ void setYAxis(Point const &vec);
+
+ void setTranslation(Point const &loc);
+
+ void setExpansionX(Coord val);
+ void setExpansionY(Coord val);
+ void setIdentity();
+ /// @}
+
+ /// @name Inspect the matrix's transform
+ /// @{
+ bool isIdentity(Coord eps = EPSILON) const;
+
+ bool isTranslation(Coord eps = EPSILON) const;
+ bool isScale(Coord eps = EPSILON) const;
+ bool isUniformScale(Coord eps = EPSILON) const;
+ bool isRotation(Coord eps = EPSILON) const;
+ bool isHShear(Coord eps = EPSILON) const;
+ bool isVShear(Coord eps = EPSILON) const;
+
+ bool isNonzeroTranslation(Coord eps = EPSILON) const;
+ bool isNonzeroScale(Coord eps = EPSILON) const;
+ bool isNonzeroUniformScale(Coord eps = EPSILON) const;
+ bool isNonzeroRotation(Coord eps = EPSILON) const;
+ bool isNonzeroNonpureRotation(Coord eps = EPSILON) const;
+ Point rotationCenter() const;
+ bool isNonzeroHShear(Coord eps = EPSILON) const;
+ bool isNonzeroVShear(Coord eps = EPSILON) const;
+
+ bool isZoom(Coord eps = EPSILON) const;
+ bool preservesArea(Coord eps = EPSILON) const;
+ bool preservesAngles(Coord eps = EPSILON) const;
+ bool preservesDistances(Coord eps = EPSILON) const;
+ bool flips() const;
+
+ bool isSingular(Coord eps = EPSILON) const;
+ /// @}
+
+ /// @name Compute other matrices
+ /// @{
+ Affine withoutTranslation() const {
+ Affine ret(*this);
+ ret.setTranslation(Point(0,0));
+ return ret;
+ }
+ Affine inverse() const;
+ /// @}
+
+ /// @name Compute scalar values
+ /// @{
+ Coord det() const;
+ Coord descrim2() const;
+ Coord descrim() const;
+ /// @}
+ inline static Affine identity();
+};
+
+/** @brief Print out the Affine (for debugging).
+ * @relates Affine */
+inline std::ostream &operator<< (std::ostream &out_file, const Geom::Affine &m) {
+ out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n";
+ out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n";
+ return out_file;
+}
+
+// Affine factories
+Affine from_basis(const Point &x_basis, const Point &y_basis, const Point &offset=Point(0,0));
+Affine elliptic_quadratic_form(Affine const &m);
+
+/** Given a matrix (ignoring the translation) this returns the eigen
+ * values and vectors. */
+class Eigen{
+public:
+ Point vectors[2];
+ double values[2];
+ Eigen(Affine const &m);
+ Eigen(double M[2][2]);
+};
+
+/** @brief Create an identity matrix.
+ * This is a convenience function identical to Affine::identity(). */
+inline Affine identity() {
+ Affine ret(Affine::identity());
+ return ret; // allow NRVO
+}
+
+/** @brief Create an identity matrix.
+ * @return The matrix
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ 0 & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$.
+ * @relates Affine */
+inline Affine Affine::identity() {
+ Affine ret(1.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 0.0);
+ return ret; // allow NRVO
+}
+
+bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON);
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_AFFINE_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/include/2geom/angle.h b/include/2geom/angle.h
new file mode 100644
index 0000000..f0caaba
--- /dev/null
+++ b/include/2geom/angle.h
@@ -0,0 +1,408 @@
+/**
+ * \file
+ * \brief Various trigoniometric helper functions
+ *//*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2007-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.
+ *
+ */
+
+#ifndef LIB2GEOM_SEEN_ANGLE_H
+#define LIB2GEOM_SEEN_ANGLE_H
+
+#include <cmath>
+#include <boost/operators.hpp>
+#include <2geom/exception.h>
+#include <2geom/coord.h>
+#include <2geom/point.h>
+
+namespace Geom {
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+#ifndef M_1_2PI
+# define M_1_2PI 0.159154943091895335768883763373
+#endif
+
+/** @brief Wrapper for angular values.
+ *
+ * This class is a convenience wrapper that implements the behavior generally expected of angles,
+ * like addition modulo \f$2\pi\f$. The value returned from the default conversion
+ * to <tt>double</tt> is in the range \f$[-\pi, \pi)\f$ - the convention used by C's
+ * math library.
+ *
+ * This class holds only a single floating point value, so passing it by value will generally
+ * be faster than passing it by const reference.
+ *
+ * @ingroup Primitives
+ */
+class Angle
+ : boost::additive< Angle
+ , boost::additive< Angle, Coord
+ , boost::equality_comparable< Angle
+ , boost::equality_comparable< Angle, Coord
+ > > > >
+{
+public:
+ Angle() : _angle(0) {}
+ Angle(Coord v) : _angle(v) { _normalize(); } // this can be called implicitly
+ explicit Angle(Point const &p) : _angle(atan2(p)) { _normalize(); }
+ Angle(Point const &a, Point const &b) : _angle(angle_between(a, b)) { _normalize(); }
+ operator Coord() const { return radians(); }
+ Angle &operator+=(Angle o) {
+ _angle += o._angle;
+ _normalize();
+ return *this;
+ }
+ Angle &operator-=(Angle o) {
+ _angle -= o._angle;
+ _normalize();
+ return *this;
+ }
+ Angle &operator+=(Coord a) {
+ *this += Angle(a);
+ return *this;
+ }
+ Angle &operator-=(Coord a) {
+ *this -= Angle(a);
+ return *this;
+ }
+ bool operator==(Angle o) const {
+ return _angle == o._angle;
+ }
+ bool operator==(Coord c) const {
+ return _angle == Angle(c)._angle;
+ }
+
+ /** @brief Get the angle as radians.
+ * @return Number in range \f$[-\pi, \pi)\f$. */
+ Coord radians() const {
+ return _angle >= M_PI ? _angle - 2*M_PI : _angle;
+ }
+ /** @brief Get the angle as positive radians.
+ * @return Number in range \f$[0, 2\pi)\f$. */
+ Coord radians0() const {
+ return _angle;
+ }
+ /** @brief Get the angle as degrees in math convention.
+ * @return Number in range [-180, 180) obtained by scaling the result of radians()
+ * by \f$180/\pi\f$. */
+ Coord degrees() const { return radians() * (180.0 / M_PI); }
+ /** @brief Get the angle as degrees in clock convention.
+ * This method converts the angle to the "clock convention": angles start from the +Y axis
+ * and grow clockwise. This means that 0 corresponds to \f$\pi/2\f$ radians,
+ * 90 to 0 radians, 180 to \f$-\pi/2\f$ radians, and 270 to \f$\pi\f$ radians.
+ * @return A number in the range [0, 360).
+ */
+ Coord degreesClock() const {
+ Coord ret = 90.0 - _angle * (180.0 / M_PI);
+ if (ret < 0) ret += 360;
+ return ret;
+ }
+ /** @brief Create an angle from its measure in radians. */
+ static Angle from_radians(Coord d) {
+ Angle a(d);
+ return a;
+ }
+ /** @brief Create an angle from its measure in degrees. */
+ static Angle from_degrees(Coord d) {
+ Angle a(d * (M_PI / 180.0));
+ return a;
+ }
+ /** @brief Create an angle from its measure in degrees in clock convention.
+ * @see Angle::degreesClock() */
+ static Angle from_degrees_clock(Coord d) {
+ // first make sure d is in [0, 360)
+ d = std::fmod(d, 360.0);
+ if (d < 0) d += 360.0;
+ Coord rad = M_PI/2 - d * (M_PI / 180.0);
+ if (rad < 0) rad += 2*M_PI;
+ Angle a;
+ a._angle = rad;
+ return a;
+ }
+private:
+
+ void _normalize() {
+ _angle = std::fmod(_angle, 2*M_PI);
+ if (_angle < 0) _angle += 2*M_PI;
+ //_angle -= floor(_angle * (1.0/(2*M_PI))) * 2*M_PI;
+ }
+ Coord _angle; // this is always in [0, 2pi)
+ friend class AngleInterval;
+};
+
+inline Angle distance(Angle const &a, Angle const &b) {
+ // the distance cannot be larger than M_PI.
+ Coord ac = a.radians0();
+ Coord bc = b.radians0();
+ Coord d = fabs(ac - bc);
+ return Angle(d > M_PI ? 2*M_PI - d : d);
+}
+
+/** @brief Directed angular interval.
+ *
+ * Wrapper for directed angles with defined start and end values. Useful e.g. for representing
+ * the portion of an ellipse in an elliptical arc. Both extreme angles are contained
+ * in the interval (it is a closed interval). Angular intervals can also be interptered
+ * as functions \f$f: [0, 1] \to [-\pi, \pi)\f$, which return the start angle for 0,
+ * the end angle for 1, and interpolate linearly for other values. Note that such functions
+ * are not continuous if the interval crosses the angle \f$\pi\f$.
+ *
+ * This class can represent all directed angular intervals, including empty ones.
+ * However, not all possible intervals can be created with the constructors.
+ * For full control, use the setInitial(), setFinal() and setAngles() methods.
+ *
+ * @ingroup Primitives
+ */
+class AngleInterval
+ : boost::equality_comparable< AngleInterval >
+{
+public:
+ AngleInterval() {}
+ /** @brief Create an angular interval from two angles and direction.
+ * If the initial and final angle are the same, a degenerate interval
+ * (containing only one angle) will be created.
+ * @param s Starting angle
+ * @param e Ending angle
+ * @param cw Which direction the interval goes. True means that it goes
+ * in the direction of increasing angles, while false means in the direction
+ * of decreasing angles. */
+ AngleInterval(Angle s, Angle e, bool cw = false)
+ : _start_angle(s), _end_angle(e), _sweep(cw), _full(false)
+ {}
+ AngleInterval(double s, double e, bool cw = false)
+ : _start_angle(s), _end_angle(e), _sweep(cw), _full(false)
+ {}
+ /** @brief Create an angular interval from three angles.
+ * If the inner angle is exactly equal to initial or final angle,
+ * the sweep flag will be set to true, i.e. the interval will go
+ * in the direction of increasing angles.
+ *
+ * If the initial and final angle are the same, but the inner angle
+ * is different, a full angle in the direction of increasing angles
+ * will be created.
+ *
+ * @param s Initial angle
+ * @param inner Angle contained in the interval
+ * @param e Final angle */
+ AngleInterval(Angle s, Angle inner, Angle e)
+ : _start_angle(s)
+ , _end_angle(e)
+ , _sweep((inner-s).radians0() <= (e-s).radians0())
+ , _full(s == e && s != inner)
+ {
+ if (_full) {
+ _sweep = true;
+ }
+ }
+
+ /// Get the start angle.
+ Angle initialAngle() const { return _start_angle; }
+ /// Get the end angle.
+ Angle finalAngle() const { return _end_angle; }
+ /// Check whether the interval goes in the direction of increasing angles.
+ bool sweep() const { return _sweep; }
+ /// Check whether the interval contains only a single angle.
+ bool isDegenerate() const {
+ return _start_angle == _end_angle && !_full;
+ }
+ /// Check whether the interval contains all angles.
+ bool isFull() const {
+ return _start_angle == _end_angle && _full;
+ }
+
+ /** @brief Set the initial angle.
+ * @param a Angle to set
+ * @param prefer_full Whether to set a full angular interval when
+ * the initial angle is set to the final angle */
+ void setInitial(Angle a, bool prefer_full = false) {
+ _start_angle = a;
+ _full = prefer_full && a == _end_angle;
+ }
+
+ /** @brief Set the final angle.
+ * @param a Angle to set
+ * @param prefer_full Whether to set a full angular interval when
+ * the initial angle is set to the final angle */
+ void setFinal(Angle a, bool prefer_full = false) {
+ _end_angle = a;
+ _full = prefer_full && a == _start_angle;
+ }
+ /** @brief Set both angles at once.
+ * The direction (sweep flag) is left unchanged.
+ * @param s Initial angle
+ * @param e Final angle
+ * @param prefer_full Whether to set a full interval when the passed
+ * initial and final angle are the same */
+ void setAngles(Angle s, Angle e, bool prefer_full = false) {
+ _start_angle = s;
+ _end_angle = e;
+ _full = prefer_full && s == e;
+ }
+ /// Set whether the interval goes in the direction of increasing angles.
+ void setSweep(bool s) { _sweep = s; }
+
+ /// Reverse the direction of the interval while keeping contained values the same.
+ void reverse() {
+ using std::swap;
+ swap(_start_angle, _end_angle);
+ _sweep = !_sweep;
+ }
+ /// Get a new interval with reversed direction.
+ AngleInterval reversed() const {
+ AngleInterval result(*this);
+ result.reverse();
+ return result;
+ }
+
+ /// Get an angle corresponding to the specified time value.
+ Angle angleAt(Coord t) const {
+ Coord span = extent();
+ Angle ret = _start_angle.radians0() + span * (_sweep ? t : -t);
+ return ret;
+ }
+ Angle operator()(Coord t) const { return angleAt(t); }
+
+ /** @brief Compute a time value that would evaluate to the given angle.
+ * If the start and end angle are exactly the same, NaN will be returned.
+ * Negative values will be returned for angles between the initial angle
+ * and the angle exactly opposite the midpoint of the interval. */
+ Coord timeAtAngle(Angle a) const {
+ if (_full) {
+ Angle ta = _sweep ? a - _start_angle : _start_angle - a;
+ return ta.radians0() / (2*M_PI);
+ }
+ Coord ex = extent();
+ Coord outex = 2*M_PI - ex;
+ if (_sweep) {
+ Angle midout = _start_angle - outex / 2;
+ Angle acmp = a - midout, scmp = _start_angle - midout;
+ if (acmp.radians0() >= scmp.radians0()) {
+ return (a - _start_angle).radians0() / ex;
+ } else {
+ return -(_start_angle - a).radians0() / ex;
+ }
+ } else {
+ Angle midout = _start_angle + outex / 2;
+ Angle acmp = a - midout, scmp = _start_angle - midout;
+ if (acmp.radians0() <= scmp.radians0()) {
+ return (_start_angle - a).radians0() / ex;
+ } else {
+ return -(a - _start_angle).radians0() / ex;
+ }
+ }
+ }
+
+ /// Check whether the interval includes the given angle.
+ bool contains(Angle a) const {
+ if (_full) return true;
+ Coord s = _start_angle.radians0();
+ Coord e = _end_angle.radians0();
+ Coord x = a.radians0();
+ if (_sweep) {
+ if (s < e) return x >= s && x <= e;
+ return x >= s || x <= e;
+ } else {
+ if (s > e) return x <= s && x >= e;
+ return x <= s || x >= e;
+ }
+ }
+ /** @brief Extent of the angle interval.
+ * Equivalent to the absolute value of the sweep angle.
+ * @return Extent in range \f$[0, 2\pi)\f$. */
+ Coord extent() const {
+ if (_full) return 2*M_PI;
+ return _sweep
+ ? (_end_angle - _start_angle).radians0()
+ : (_start_angle - _end_angle).radians0();
+ }
+ /** @brief Get the sweep angle of the interval.
+ * This is the value you need to add to the initial angle to get the final angle.
+ * It is positive when sweep is true. Denoted as \f$\Delta\theta\f$ in the SVG
+ * elliptical arc implementation notes. */
+ Coord sweepAngle() const {
+ if (_full) return _sweep ? 2*M_PI : -2*M_PI;
+ Coord sa = _end_angle.radians0() - _start_angle.radians0();
+ if (_sweep && sa < 0) sa += 2*M_PI;
+ if (!_sweep && sa > 0) sa -= 2*M_PI;
+ return sa;
+ }
+
+ /// Check another interval for equality.
+ bool operator==(AngleInterval const &other) const {
+ if (_start_angle != other._start_angle) return false;
+ if (_end_angle != other._end_angle) return false;
+ if (_sweep != other._sweep) return false;
+ if (_full != other._full) return false;
+ return true;
+ }
+
+ static AngleInterval create_full(Angle start, bool sweep = true) {
+ AngleInterval result;
+ result._start_angle = result._end_angle = start;
+ result._sweep = sweep;
+ result._full = true;
+ return result;
+ }
+
+private:
+ Angle _start_angle;
+ Angle _end_angle;
+ bool _sweep;
+ bool _full;
+};
+
+/** @brief Given an angle in degrees, return radians
+ * @relates Angle */
+inline Coord rad_from_deg(Coord deg) { return deg*M_PI/180.0;}
+/** @brief Given an angle in radians, return degrees
+ * @relates Angle */
+inline Coord deg_from_rad(Coord rad) { return rad*180.0/M_PI;}
+
+} // end namespace Geom
+
+namespace std {
+template <> class iterator_traits<Geom::Angle> {};
+}
+
+#endif // LIB2GEOM_SEEN_ANGLE_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/include/2geom/basic-intersection.h b/include/2geom/basic-intersection.h
new file mode 100644
index 0000000..2d0c00d
--- /dev/null
+++ b/include/2geom/basic-intersection.h
@@ -0,0 +1,151 @@
+/** @file
+ * @brief Basic intersection routines
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Jean-François Barraud <jf.barraud@gmail.com>
+ *
+ * Copyright 2008-2009 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 LIB2GEOM_SEEN_BASIC_INTERSECTION_H
+#define LIB2GEOM_SEEN_BASIC_INTERSECTION_H
+
+#include <2geom/point.h>
+#include <2geom/bezier.h>
+#include <2geom/sbasis.h>
+#include <2geom/d2.h>
+
+#include <vector>
+#include <utility>
+
+#define USE_RECURSIVE_INTERSECTOR 0
+
+
+namespace Geom {
+
+void find_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<Bezier> const &A,
+ D2<Bezier> const &B,
+ double precision = EPSILON);
+
+void find_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const &A,
+ D2<SBasis> const &B,
+ double precision = EPSILON);
+
+void find_intersections(std::vector< std::pair<double, double> > &xs,
+ std::vector<Point> const &A,
+ std::vector<Point> const &B,
+ double precision = EPSILON);
+
+void find_self_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const &A,
+ double precision = EPSILON);
+
+void find_self_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<Bezier> const &A,
+ double precision = EPSILON);
+
+/*
+ * find_intersection
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * output: xs - set of pairs of parameter values
+ * at which crossing happens
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping
+ */
+void find_intersections_bezier_clipping (std::vector< std::pair<double, double> > & xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision = EPSILON);
+//#endif
+
+void subdivide(D2<Bezier> const &a,
+ D2<Bezier> const &b,
+ std::vector< std::pair<double, double> > const &xs,
+ std::vector< D2<Bezier> > &av,
+ std::vector< D2<Bezier> > &bv);
+
+/*
+ * find_collinear_normal
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * output: xs - set of pairs of parameter values
+ * at which there are collinear normals
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping
+ */
+void find_collinear_normal (std::vector< std::pair<double, double> >& xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision = EPSILON);
+
+void polish_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const &A,
+ D2<SBasis> const &B);
+
+
+/**
+ * Compute the Hausdorf distance from A to B only.
+ */
+double hausdorfl(D2<SBasis>& A, D2<SBasis> const &B,
+ double m_precision,
+ double *a_t=NULL, double *b_t=NULL);
+
+/**
+ * Compute the symmetric Hausdorf distance.
+ */
+double hausdorf(D2<SBasis> &A, D2<SBasis> const &B,
+ double m_precision,
+ double *a_t=NULL, double *b_t=NULL);
+
+/**
+ * Check if two line segments intersect. If they are collinear, the result is undefined.
+ * @return True if line segments AB and CD intersect
+ */
+bool non_collinear_segments_intersect(const Point &A, const Point &B, const Point &C, const Point &D);
+}
+
+#endif // !LIB2GEOM_SEEN_BASIC_INTERSECTION_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/include/2geom/bezier-curve.h b/include/2geom/bezier-curve.h
new file mode 100644
index 0000000..754c9cc
--- /dev/null
+++ b/include/2geom/bezier-curve.h
@@ -0,0 +1,366 @@
+/**
+ * \file
+ * \brief Bezier curve
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-2011 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 LIB2GEOM_SEEN_BEZIER_CURVE_H
+#define LIB2GEOM_SEEN_BEZIER_CURVE_H
+
+#include <2geom/curve.h>
+#include <2geom/sbasis-curve.h> // for non-native winding method
+#include <2geom/bezier.h>
+#include <2geom/transforms.h>
+
+namespace Geom
+{
+
+class BezierCurve : public Curve {
+protected:
+ D2<Bezier> inner;
+ BezierCurve() {}
+ BezierCurve(Bezier const &x, Bezier const &y) : inner(x, y) {}
+ BezierCurve(std::vector<Point> const &pts);
+
+public:
+ explicit BezierCurve(D2<Bezier> const &b) : inner(b) {}
+
+ /// @name Access and modify control points
+ /// @{
+ /** @brief Get the order of the Bezier curve.
+ * A Bezier curve has order() + 1 control points. */
+ unsigned order() const { return inner[X].order(); }
+ /** @brief Get the number of control points. */
+ unsigned size() const { return inner[X].order() + 1; }
+ /** @brief Access control points of the curve.
+ * @param ix The (zero-based) index of the control point. Note that the caller is responsible for checking that this value is <= order().
+ * @return The control point. No-reference return, use setPoint() to modify control points. */
+ Point controlPoint(unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); }
+ Point operator[](unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); }
+ /** @brief Get the control points.
+ * @return Vector with order() + 1 control points. */
+ std::vector<Point> controlPoints() const { return bezier_points(inner); }
+ D2<Bezier> const &fragment() const { return inner; }
+
+ /** @brief Modify a control point.
+ * @param ix The zero-based index of the point to modify. Note that the caller is responsible for checking that this value is <= order().
+ * @param v The new value of the point */
+ void setPoint(unsigned ix, Point const &v) {
+ inner[X][ix] = v[X];
+ inner[Y][ix] = v[Y];
+ }
+ /** @brief Set new control points.
+ * @param ps Vector which must contain order() + 1 points.
+ * Note that the caller is responsible for checking the size of this vector.
+ * @throws LogicalError Thrown when the size of the vector does not match the order. */
+ virtual void setPoints(std::vector<Point> const &ps) {
+ // must be virtual, because HLineSegment will need to redefine it
+ if (ps.size() != order() + 1)
+ THROW_LOGICALERROR("BezierCurve::setPoints: incorrect number of points in vector");
+ for(unsigned i = 0; i <= order(); i++) {
+ setPoint(i, ps[i]);
+ }
+ }
+ /// @}
+
+ /// @name Construct a Bezier curve with runtime-determined order.
+ /// @{
+ /** @brief Construct a curve from a vector of control points.
+ * This will construct the appropriate specialization of BezierCurve (i.e. LineSegment,
+ * QuadraticBezier or Cubic Bezier) if the number of control points in the passed vector
+ * does not exceed 4. */
+ static BezierCurve *create(std::vector<Point> const &pts);
+ /// @}
+
+ // implementation of virtual methods goes here
+ Point initialPoint() const override { return inner.at0(); }
+ Point finalPoint() const override { return inner.at1(); }
+ bool isDegenerate() const override;
+ bool isLineSegment() const override;
+ void setInitial(Point const &v) override { setPoint(0, v); }
+ void setFinal(Point const &v) override { setPoint(order(), v); }
+ Rect boundsFast() const override { return *bounds_fast(inner); }
+ Rect boundsExact() const override { return *bounds_exact(inner); }
+ void expandToTransformed(Rect &bbox, Affine const &transform) const override;
+ OptRect boundsLocal(OptInterval const &i, unsigned deg) const override {
+ if (!i) return OptRect();
+ if(i->min() == 0 && i->max() == 1) return boundsFast();
+ if(deg == 0) return bounds_local(inner, i);
+ // TODO: UUUUUUGGGLLY
+ if(deg == 1 && order() > 1) return OptRect(bounds_local(Geom::derivative(inner[X]), i),
+ bounds_local(Geom::derivative(inner[Y]), i));
+ return OptRect();
+ }
+ Curve *duplicate() const override {
+ return new BezierCurve(*this);
+ }
+
+ Curve *portion(Coord f, Coord t) const override;
+
+ Curve *reverse() const override {
+ return new BezierCurve(Geom::reverse(inner));
+ }
+
+ using Curve::operator*=;
+ void operator*=(Translate const &tr) override {
+ for (unsigned i = 0; i < size(); ++i) {
+ inner[X][i] += tr[X];
+ inner[Y][i] += tr[Y];
+ }
+ }
+ void operator*=(Scale const &s) override {
+ for (unsigned i = 0; i < size(); ++i) {
+ inner[X][i] *= s[X];
+ inner[Y][i] *= s[Y];
+ }
+ }
+ void operator*=(Affine const &m) override {
+ for (unsigned i = 0; i < size(); ++i) {
+ setPoint(i, controlPoint(i) * m);
+ }
+ }
+
+ Curve *derivative() const override {
+ return new BezierCurve(Geom::derivative(inner[X]), Geom::derivative(inner[Y]));
+ }
+ int degreesOfFreedom() const override {
+ return 2 * (order() + 1);
+ }
+ std::vector<Coord> roots(Coord v, Dim2 d) const override {
+ return (inner[d] - v).roots();
+ }
+ Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const override;
+ Coord length(Coord tolerance) const override;
+ std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const override;
+ Point pointAt(Coord t) const override { return inner.pointAt(t); }
+ std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const override {
+ return inner.valueAndDerivatives(t, n);
+ }
+ Coord valueAt(Coord t, Dim2 d) const override { return inner[d].valueAt(t); }
+ D2<SBasis> toSBasis() const override {return inner.toSBasis(); }
+ bool isNear(Curve const &c, Coord precision) const override;
+ bool operator==(Curve const &c) const override;
+ void feed(PathSink &sink, bool) const override;
+};
+
+template <unsigned degree>
+class BezierCurveN
+ : public BezierCurve
+{
+ template <unsigned required_degree>
+ static void assert_degree(BezierCurveN<required_degree> const *) {}
+
+public:
+ /// @name Construct Bezier curves
+ /// @{
+ /** @brief Construct a Bezier curve of the specified order with all points zero. */
+ BezierCurveN() {
+ inner = D2<Bezier>(Bezier(Bezier::Order(degree)), Bezier(Bezier::Order(degree)));
+ }
+
+ /** @brief Construct from 2D Bezier polynomial. */
+ explicit BezierCurveN(D2<Bezier > const &x) {
+ inner = x;
+ }
+
+ /** @brief Construct from two 1D Bezier polynomials of the same order. */
+ BezierCurveN(Bezier x, Bezier y) {
+ inner = D2<Bezier > (x,y);
+ }
+
+ /** @brief Construct a Bezier curve from a vector of its control points. */
+ BezierCurveN(std::vector<Point> const &points) {
+ unsigned ord = points.size() - 1;
+ if (ord != degree) THROW_LOGICALERROR("BezierCurve<degree> does not match number of points");
+ for (unsigned d = 0; d < 2; ++d) {
+ inner[d] = Bezier(Bezier::Order(ord));
+ for(unsigned i = 0; i <= ord; i++)
+ inner[d][i] = points[i][d];
+ }
+ }
+
+ /** @brief Construct a linear segment from its endpoints. */
+ BezierCurveN(Point c0, Point c1) {
+ assert_degree<1>(this);
+ for(unsigned d = 0; d < 2; d++)
+ inner[d] = Bezier(c0[d], c1[d]);
+ }
+
+ /** @brief Construct a quadratic Bezier curve from its control points. */
+ BezierCurveN(Point c0, Point c1, Point c2) {
+ assert_degree<2>(this);
+ for(unsigned d = 0; d < 2; d++)
+ inner[d] = Bezier(c0[d], c1[d], c2[d]);
+ }
+
+ /** @brief Construct a cubic Bezier curve from its control points. */
+ BezierCurveN(Point c0, Point c1, Point c2, Point c3) {
+ assert_degree<3>(this);
+ for(unsigned d = 0; d < 2; d++)
+ inner[d] = Bezier(c0[d], c1[d], c2[d], c3[d]);
+ }
+
+ // default copy
+ // default assign
+
+ /// @}
+
+ /** @brief Divide a Bezier curve into two curves
+ * @param t Time value
+ * @return Pair of Bezier curves \f$(\mathbf{D}, \mathbf{E})\f$ such that
+ * \f$\mathbf{D}[ [0,1] ] = \mathbf{C}[ [0,t] ]\f$ and
+ * \f$\mathbf{E}[ [0,1] ] = \mathbf{C}[ [t,1] ]\f$ */
+ std::pair<BezierCurveN, BezierCurveN> subdivide(Coord t) const {
+ std::pair<Bezier, Bezier> sx = inner[X].subdivide(t), sy = inner[Y].subdivide(t);
+ return std::make_pair(
+ BezierCurveN(sx.first, sy.first),
+ BezierCurveN(sx.second, sy.second));
+ }
+
+ bool isDegenerate() const override {
+ return BezierCurve::isDegenerate();
+ }
+
+ bool isLineSegment() const override {
+ if constexpr (degree == 1) {
+ return true;
+ } else {
+ return BezierCurve::isLineSegment();
+ }
+ }
+
+ Curve *duplicate() const override {
+ return new BezierCurveN(*this);
+ }
+ Curve *portion(Coord f, Coord t) const override {
+ if (degree == 1) {
+ return new BezierCurveN<1>(pointAt(f), pointAt(t));
+ } else {
+ return new BezierCurveN(Geom::portion(inner, f, t));
+ }
+ }
+ Curve *reverse() const override {
+ if (degree == 1) {
+ return new BezierCurveN<1>(finalPoint(), initialPoint());
+ } else {
+ return new BezierCurveN(Geom::reverse(inner));
+ }
+ }
+ Curve *derivative() const override;
+
+ Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const override {
+ return BezierCurve::nearestTime(p, from, to);
+ }
+ std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const override {
+ // call super. this is implemented only to allow specializations
+ return BezierCurve::intersect(other, eps);
+ }
+ int winding(Point const &p) const override {
+ return Curve::winding(p);
+ }
+ void feed(PathSink &sink, bool moveto_initial) const override {
+ // call super. this is implemented only to allow specializations
+ BezierCurve::feed(sink, moveto_initial);
+ }
+ void expandToTransformed(Rect &bbox, Affine const &transform) const override {
+ // call super. this is implemented only to allow specializations
+ BezierCurve::expandToTransformed(bbox, transform);
+ }
+};
+
+// BezierCurveN<0> is meaningless; specialize it out
+template<> class BezierCurveN<0> : public BezierCurveN<1> { private: BezierCurveN();};
+
+/** @brief Line segment.
+ * Line segments are Bezier curves of order 1. They have only two control points,
+ * the starting point and the ending point.
+ * @ingroup Curves */
+typedef BezierCurveN<1> LineSegment;
+
+/** @brief Quadratic (order 2) Bezier curve.
+ * @ingroup Curves */
+typedef BezierCurveN<2> QuadraticBezier;
+
+/** @brief Cubic (order 3) Bezier curve.
+ * @ingroup Curves */
+typedef BezierCurveN<3> CubicBezier;
+
+template <unsigned degree>
+inline
+Curve *BezierCurveN<degree>::derivative() const {
+ return new BezierCurveN<degree-1>(Geom::derivative(inner[X]), Geom::derivative(inner[Y]));
+}
+
+// optimized specializations
+template <> inline bool BezierCurveN<1>::isDegenerate() const {
+ return inner[X][0] == inner[X][1] && inner[Y][0] == inner[Y][1];
+}
+template <> inline bool BezierCurveN<1>::isLineSegment() const { return true; }
+template <> Curve *BezierCurveN<1>::derivative() const;
+template <> Coord BezierCurveN<1>::nearestTime(Point const &, Coord, Coord) const;
+template <> std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &, Coord) const;
+template <> std::vector<CurveIntersection> BezierCurveN<2>::intersect(Curve const &, Coord) const;
+template <> std::vector<CurveIntersection> BezierCurveN<3>::intersect(Curve const &, Coord) const;
+template <> int BezierCurveN<1>::winding(Point const &) const;
+template <> void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const;
+template <> void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const;
+template <> void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const;
+template <> void BezierCurveN<1>::expandToTransformed(Rect &bbox, Affine const &transform) const;
+template <> void BezierCurveN<2>::expandToTransformed(Rect &bbox, Affine const &transform) const;
+template <> void BezierCurveN<3>::expandToTransformed(Rect &bbox, Affine const &transform) const;
+
+inline Point middle_point(LineSegment const& _segment) {
+ return ( _segment.initialPoint() + _segment.finalPoint() ) / 2;
+}
+
+inline Coord length(LineSegment const& seg) {
+ return distance(seg.initialPoint(), seg.finalPoint());
+}
+
+Coord bezier_length(std::vector<Point> const &points, Coord tolerance = 0.01);
+Coord bezier_length(Point p0, Point p1, Point p2, Coord tolerance = 0.01);
+Coord bezier_length(Point p0, Point p1, Point p2, Point p3, Coord tolerance = 0.01);
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_BEZIER_CURVE_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/include/2geom/bezier-to-sbasis.h b/include/2geom/bezier-to-sbasis.h
new file mode 100644
index 0000000..73c55d9
--- /dev/null
+++ b/include/2geom/bezier-to-sbasis.h
@@ -0,0 +1,94 @@
+/**
+ * \file
+ * \brief Conversion between Bezier control points and SBasis curves
+ *//*
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * 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 LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H
+#define LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H
+
+#include <2geom/coord.h>
+#include <2geom/point.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis-to-bezier.h>
+
+namespace Geom
+{
+
+#if 0
+inline SBasis bezier_to_sbasis(Coord const *handles, unsigned order) {
+ if(order == 0)
+ return Linear(handles[0]);
+ else if(order == 1)
+ return Linear(handles[0], handles[1]);
+ else
+ return multiply(Linear(1, 0), bezier_to_sbasis(handles, order-1)) +
+ multiply(Linear(0, 1), bezier_to_sbasis(handles+1, order-1));
+}
+
+
+template <typename T>
+inline D2<SBasis> handles_to_sbasis(T const &handles, unsigned order)
+{
+ double v[2][order+1];
+ for(unsigned i = 0; i <= order; i++)
+ for(unsigned j = 0; j < 2; j++)
+ v[j][i] = handles[i][j];
+ return D2<SBasis>(bezier_to_sbasis(v[0], order),
+ bezier_to_sbasis(v[1], order));
+}
+#endif
+
+
+template <typename T>
+inline
+D2<SBasis> handles_to_sbasis(T const& handles, unsigned order)
+{
+ D2<SBasis> sbc;
+ size_t sz = order + 1;
+ std::vector<Point> v;
+ v.reserve(sz);
+ for (size_t i = 0; i < sz; ++i)
+ v.push_back(handles[i]);
+ bezier_to_sbasis(sbc, v);
+ return sbc;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_BEZIER_TO_SBASIS_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/include/2geom/bezier-utils.h b/include/2geom/bezier-utils.h
new file mode 100644
index 0000000..3e56e6e
--- /dev/null
+++ b/include/2geom/bezier-utils.h
@@ -0,0 +1,99 @@
+/**
+ * \file
+ * \brief Bezier fitting algorithms
+ *//*
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * from "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * 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 LIB2GEOM_SEEN_BEZIER_UTILS_H
+#define LIB2GEOM_SEEN_BEZIER_UTILS_H
+
+#include <2geom/point.h>
+
+namespace Geom {
+
+Point bezier_pt(unsigned degree, Point const V[], double t);
+
+int bezier_fit_cubic(Point bezier[], Point const data[], int len, double error);
+
+int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error,
+ unsigned max_beziers);
+
+int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int len,
+ Point const &tHat1, Point const &tHat2,
+ double error, unsigned max_beziers);
+
+Point darray_left_tangent(Point const d[], unsigned const len);
+Point darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq);
+Point darray_right_tangent(Point const d[], unsigned const length, double const tolerance_sq);
+
+template <typename iterator>
+static void
+cubic_bezier_poly_coeff(iterator b, Point *pc) {
+ double c[10] = {1,
+ -3, 3,
+ 3, -6, 3,
+ -1, 3, -3, 1};
+
+ int cp = 0;
+
+ for(int i = 0; i < 4; i++) {
+ pc[i] = Point(0,0);
+ ++b;
+ }
+ for(int i = 0; i < 4; i++) {
+ --b;
+ for(int j = 0; j <= i; j++) {
+ pc[3 - j] += c[cp]*(*b);
+ cp++;
+ }
+ }
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_BEZIER_UTILS_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/include/2geom/bezier.h b/include/2geom/bezier.h
new file mode 100644
index 0000000..d65b3bd
--- /dev/null
+++ b/include/2geom/bezier.h
@@ -0,0 +1,394 @@
+/**
+ * @file
+ * @brief Bernstein-Bezier polynomial
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Michael Sloan <mgsloan@gmail.com>
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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.
+ *
+ */
+
+#ifndef LIB2GEOM_SEEN_BEZIER_H
+#define LIB2GEOM_SEEN_BEZIER_H
+
+#include <algorithm>
+#include <valarray>
+#include <2geom/coord.h>
+#include <2geom/d2.h>
+#include <2geom/math-utils.h>
+
+namespace Geom {
+
+/** @brief Compute the value of a Bernstein-Bezier polynomial.
+ * This method uses a Horner-like fast evaluation scheme.
+ * @param t Time value
+ * @param c_ Pointer to coefficients
+ * @param n Degree of the polynomial (number of coefficients minus one) */
+template <typename T>
+inline T bernstein_value_at(double t, T const *c_, unsigned n) {
+ double u = 1.0 - t;
+ double bc = 1;
+ double tn = 1;
+ T tmp = c_[0]*u;
+ for(unsigned i=1; i<n; i++){
+ tn = tn*t;
+ bc = bc*(n-i+1)/i;
+ tmp = (tmp + tn*bc*c_[i])*u;
+ }
+ return (tmp + tn*t*c_[n]);
+}
+
+/** @brief Perform Casteljau subdivision of a Bezier polynomial.
+ * Given an array of coefficients and a time value, computes two new Bernstein-Bezier basis
+ * polynomials corresponding to the \f$[0, t]\f$ and \f$[t, 1]\f$ intervals of the original one.
+ * @param t Time value
+ * @param v Array of input coordinates
+ * @param left Output polynomial corresponding to \f$[0, t]\f$
+ * @param right Output polynomial corresponding to \f$[t, 1]\f$
+ * @param order Order of the input polynomial, equal to one less the number of coefficients
+ * @return Value of the polynomial at @a t */
+template <typename T>
+inline T casteljau_subdivision(double t, T const *v, T *left, T *right, unsigned order) {
+ // The Horner-like scheme gives very slightly different results, but we need
+ // the result of subdivision to match exactly with Bezier's valueAt function.
+ T val = bernstein_value_at(t, v, order);
+
+ if (!left && !right) {
+ return val;
+ }
+
+ if (!right) {
+ if (left != v) {
+ std::copy(v, v + order + 1, left);
+ }
+ for (std::size_t i = order; i > 0; --i) {
+ for (std::size_t j = i; j <= order; ++j) {
+ left[j] = lerp(t, left[j-1], left[j]);
+ }
+ }
+ left[order] = val;
+ return left[order];
+ }
+
+ if (right != v) {
+ std::copy(v, v + order + 1, right);
+ }
+ for (std::size_t i = 1; i <= order; ++i) {
+ if (left) {
+ left[i-1] = right[0];
+ }
+ for (std::size_t j = i; j > 0; --j) {
+ right[j-1] = lerp(t, right[j-1], right[j]);
+ }
+ }
+ right[0] = val;
+ if (left) {
+ left[order] = right[0];
+ }
+ return right[0];
+}
+
+/**
+ * @brief Polynomial in Bernstein-Bezier basis
+ * @ingroup Fragments
+ */
+class Bezier
+ : boost::arithmetic< Bezier, double
+ , boost::additive< Bezier
+ > >
+{
+private:
+ std::valarray<Coord> c_;
+
+ friend Bezier portion(const Bezier & a, Coord from, Coord to);
+ friend OptInterval bounds_fast(Bezier const & b);
+ friend Bezier derivative(const Bezier & a);
+ friend class Bernstein;
+
+ void
+ find_bezier_roots(std::vector<double> & solutions,
+ double l, double r) const;
+
+protected:
+ Bezier(Coord const c[], unsigned ord)
+ : c_(c, ord+1)
+ {}
+
+public:
+ unsigned order() const { return c_.size()-1;}
+ unsigned degree() const { return order(); }
+ unsigned size() const { return c_.size();}
+
+ Bezier() {}
+ Bezier(const Bezier& b) :c_(b.c_) {}
+ Bezier &operator=(Bezier const &other) {
+ if ( c_.size() != other.c_.size() ) {
+ c_.resize(other.c_.size());
+ }
+ c_ = other.c_;
+ return *this;
+ }
+
+ bool operator==(Bezier const &other) const
+ {
+ if (degree() != other.degree()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < c_.size(); i++) {
+ if (c_[i] != other.c_[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool operator!=(Bezier const &other) const
+ {
+ return !(*this == other);
+ }
+
+ struct Order {
+ unsigned order;
+ explicit Order(Bezier const &b) : order(b.order()) {}
+ explicit Order(unsigned o) : order(o) {}
+ operator unsigned() const { return order; }
+ };
+
+ //Construct an arbitrary order bezier
+ Bezier(Order ord) : c_(0., ord.order+1) {
+ assert(ord.order == order());
+ }
+
+ /// @name Construct Bezier polynomials from their control points
+ /// @{
+ explicit Bezier(Coord c0) : c_(0., 1) {
+ c_[0] = c0;
+ }
+ Bezier(Coord c0, Coord c1) : c_(0., 2) {
+ c_[0] = c0; c_[1] = c1;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2) : c_(0., 3) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3) : c_(0., 4) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4) : c_(0., 5) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4,
+ Coord c5) : c_(0., 6) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ c_[5] = c5;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4,
+ Coord c5, Coord c6) : c_(0., 7) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ c_[5] = c5; c_[6] = c6;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4,
+ Coord c5, Coord c6, Coord c7) : c_(0., 8) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ c_[5] = c5; c_[6] = c6; c_[7] = c7;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4,
+ Coord c5, Coord c6, Coord c7, Coord c8) : c_(0., 9) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8;
+ }
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4,
+ Coord c5, Coord c6, Coord c7, Coord c8, Coord c9) : c_(0., 10) {
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4;
+ c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8; c_[9] = c9;
+ }
+
+ template <typename Iter>
+ Bezier(Iter first, Iter last) {
+ c_.resize(std::distance(first, last));
+ for (std::size_t i = 0; first != last; ++first, ++i) {
+ c_[i] = *first;
+ }
+ }
+ Bezier(std::vector<Coord> const &vec)
+ : c_(&vec[0], vec.size())
+ {}
+ /// @}
+
+ void resize (unsigned int n, Coord v = 0) {
+ c_.resize (n, v);
+ }
+ void clear() {
+ c_.resize(0);
+ }
+
+ //IMPL: FragmentConcept
+ typedef Coord output_type;
+ bool isZero(double eps=EPSILON) const {
+ for(unsigned i = 0; i <= order(); i++) {
+ if( ! are_near(c_[i], 0., eps) ) return false;
+ }
+ return true;
+ }
+ bool isConstant(double eps=EPSILON) const {
+ for(unsigned i = 1; i <= order(); i++) {
+ if( ! are_near(c_[i], c_[0], eps) ) return false;
+ }
+ return true;
+ }
+ bool isFinite() const {
+ for(unsigned i = 0; i <= order(); i++) {
+ if(!std::isfinite(c_[i])) return false;
+ }
+ return true;
+ }
+ Coord at0() const { return c_[0]; }
+ Coord &at0() { return c_[0]; }
+ Coord at1() const { return c_[order()]; }
+ Coord &at1() { return c_[order()]; }
+
+ Coord valueAt(double t) const {
+ return bernstein_value_at(t, &c_[0], order());
+ }
+ Coord operator()(double t) const { return valueAt(t); }
+
+ SBasis toSBasis() const;
+
+ Coord &operator[](unsigned ix) { return c_[ix]; }
+ Coord const &operator[](unsigned ix) const { return const_cast<std::valarray<Coord>&>(c_)[ix]; }
+
+ void setCoeff(unsigned ix, double val) { c_[ix] = val; }
+
+ // The size of the returned vector equals n_derivs+1.
+ std::vector<Coord> valueAndDerivatives(Coord t, unsigned n_derivs) const;
+
+ void subdivide(Coord t, Bezier *left, Bezier *right) const;
+ std::pair<Bezier, Bezier> subdivide(Coord t) const;
+
+ std::vector<Coord> roots() const;
+ std::vector<Coord> roots(Interval const &ivl) const;
+
+ Bezier forward_difference(unsigned k) const;
+ Bezier elevate_degree() const;
+ Bezier reduce_degree() const;
+ Bezier elevate_to_degree(unsigned newDegree) const;
+ Bezier deflate() const;
+
+ // basic arithmetic operators
+ Bezier &operator+=(double v) {
+ c_ += v;
+ return *this;
+ }
+ Bezier &operator-=(double v) {
+ c_ -= v;
+ return *this;
+ }
+ Bezier &operator*=(double v) {
+ c_ *= v;
+ return *this;
+ }
+ Bezier &operator/=(double v) {
+ c_ /= v;
+ return *this;
+ }
+ Bezier &operator+=(Bezier const &other);
+ Bezier &operator-=(Bezier const &other);
+
+ /// Unary minus
+ Bezier operator-() const
+ {
+ Bezier result;
+ result.c_ = -c_;
+ return result;
+ }
+};
+
+
+void bezier_to_sbasis (SBasis &sb, Bezier const &bz);
+
+Bezier operator*(Bezier const &f, Bezier const &g);
+inline Bezier multiply(Bezier const &f, Bezier const &g) {
+ Bezier result = f * g;
+ return result;
+}
+
+inline Bezier reverse(const Bezier & a) {
+ Bezier result = Bezier(Bezier::Order(a));
+ for(unsigned i = 0; i <= a.order(); i++)
+ result[i] = a[a.order() - i];
+ return result;
+}
+
+Bezier portion(const Bezier & a, double from, double to);
+
+// XXX Todo: how to handle differing orders
+inline std::vector<Point> bezier_points(const D2<Bezier > & a) {
+ std::vector<Point> result;
+ for(unsigned i = 0; i <= a[0].order(); i++) {
+ Point p;
+ for(unsigned d = 0; d < 2; d++) p[d] = a[d][i];
+ result.push_back(p);
+ }
+ return result;
+}
+
+Bezier derivative(Bezier const &a);
+Bezier integral(Bezier const &a);
+OptInterval bounds_fast(Bezier const &b);
+OptInterval bounds_exact(Bezier const &b);
+OptInterval bounds_local(Bezier const &b, OptInterval const &i);
+
+/// Expand an interval to the image of a Bézier-Bernstein polynomial, assuming it already contains the initial point x0.
+void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2);
+void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2, Coord x3);
+
+inline std::ostream &operator<< (std::ostream &os, const Bezier & b) {
+ os << "Bezier(";
+ for(unsigned i = 0; i < b.order(); i++) {
+ os << format_coord_nice(b[i]) << ", ";
+ }
+ os << format_coord_nice(b[b.order()]) << ")";
+ return os;
+}
+
+} // namespace Geom
+
+#endif // LIB2GEOM_SEEN_BEZIER_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/include/2geom/cairo-path-sink.h b/include/2geom/cairo-path-sink.h
new file mode 100644
index 0000000..3f7a044
--- /dev/null
+++ b/include/2geom/cairo-path-sink.h
@@ -0,0 +1,91 @@
+/**
+ * @file
+ * @brief Path sink for Cairo contexts
+ *//*
+ * Copyright 2014 Krzysztof Kosiński
+ *
+ * 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 LIB2GEOM_SEEN_CAIRO_PATH_SINK_H
+#define LIB2GEOM_SEEN_CAIRO_PATH_SINK_H
+
+#ifdef HAVE_CAIRO
+
+#include <2geom/path-sink.h>
+#include <cairo.h>
+
+namespace Geom {
+
+
+/** @brief Output paths to a Cairo drawing context
+ *
+ * This class converts from 2Geom path representation to the Cairo representation.
+ * Use it to simplify visualizing the results of 2Geom operations with the Cairo library,
+ * for example:
+ * @code
+ * CairoPathSink sink(cr);
+ * sink.feed(pv);
+ * cairo_stroke(cr);
+ * @endcode
+ *
+ * Currently the flush method is a no-op, but this is not guaranteed
+ * to hold forever.
+ */
+class CairoPathSink
+ : public PathSink
+{
+public:
+ CairoPathSink(cairo_t *cr);
+
+ void moveTo(Point const &p) override;
+ void lineTo(Point const &p) override;
+ void curveTo(Point const &c0, Point const &c1, Point const &p) override;
+ void quadTo(Point const &c, Point const &p) override;
+ void arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p) override;
+ void closePath() override;
+ void flush() override;
+
+private:
+ cairo_t *_cr;
+ Point _current_point;
+};
+
+}
+
+#endif
+
+#endif // !LIB2GEOM_SEEN_CAIRO_PATH_SINK_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/include/2geom/choose.h b/include/2geom/choose.h
new file mode 100644
index 0000000..106d04f
--- /dev/null
+++ b/include/2geom/choose.h
@@ -0,0 +1,147 @@
+/**
+ * \file
+ * \brief Calculation of binomial cefficients
+ *//*
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * 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 LIB2GEOM_SEEN_CHOOSE_H
+#define LIB2GEOM_SEEN_CHOOSE_H
+
+#include <vector>
+
+namespace Geom {
+
+/**
+ * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n + 1, k).
+ */
+template <typename T>
+constexpr void binomial_increment_n(T &b, int n, int k)
+{
+ b = b * (n + 1) / (n + 1 - k);
+}
+
+/**
+ * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n - 1, k).
+ */
+template <typename T>
+constexpr void binomial_decrement_n(T &b, int n, int k)
+{
+ b = b * (n - k) / n;
+}
+
+/**
+ * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n, k + 1).
+ */
+template <typename T>
+constexpr void binomial_increment_k(T &b, int n, int k)
+{
+ b = b * (n - k) / (k + 1);
+}
+
+/**
+ * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n, k - 1).
+ */
+template <typename T>
+constexpr void binomial_decrement_k(T &b, int n, int k)
+{
+ b = b * k / (n + 1 - k);
+}
+
+/**
+ * @brief Calculate the (n, k)th binomial coefficient.
+ */
+template <typename T>
+constexpr T choose(unsigned n, unsigned k)
+{
+ if (k > n) {
+ return 0;
+ }
+ T b = 1;
+ int max = std::min(k, n - k);
+ for (int i = 0; i < max; i++) {
+ binomial_increment_k(b, n, i);
+ }
+ return b;
+}
+
+/**
+ * @brief Class for calculating and accessing a row of Pascal's triangle.
+ */
+template <typename ValueType>
+class BinomialCoefficient
+{
+public:
+ using value_type = ValueType;
+ using container_type = std::vector<value_type>;
+
+ BinomialCoefficient(unsigned int _n)
+ : n(_n)
+ {
+ coefficients.reserve(n / 2 + 1);
+ coefficients.emplace_back(1);
+ value_type b = 1;
+ for (int i = 0; i < n / 2; i++) {
+ binomial_increment_k(b, n, i);
+ coefficients.emplace_back(b);
+ }
+ }
+
+ unsigned int size() const
+ {
+ return degree() + 1;
+ }
+
+ unsigned int degree() const
+ {
+ return n;
+ }
+
+ value_type operator[](unsigned int k) const
+ {
+ return coefficients[std::min(k, n - k)];
+ }
+
+private:
+ int const n;
+ container_type coefficients;
+};
+
+} // namespace Geom
+
+#endif // LIB2GEOM_SEEN_CHOOSE_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/include/2geom/circle.h b/include/2geom/circle.h
new file mode 100644
index 0000000..a4d5f20
--- /dev/null
+++ b/include/2geom/circle.h
@@ -0,0 +1,165 @@
+/** @file
+ * @brief Circle shape
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2008-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_CIRCLE_H
+#define LIB2GEOM_SEEN_CIRCLE_H
+
+#include <2geom/forward.h>
+#include <2geom/intersection.h>
+#include <2geom/point.h>
+#include <2geom/rect.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+class EllipticalArc;
+
+/** @brief Set of all points at a fixed distance from the center
+ * @ingroup Shapes */
+class Circle
+ : boost::equality_comparable1< Circle
+ , MultipliableNoncommutative< Circle, Translate
+ , MultipliableNoncommutative< Circle, Rotate
+ , MultipliableNoncommutative< Circle, Zoom
+ > > > >
+{
+ Point _center;
+ Coord _radius;
+
+public:
+ Circle() {}
+ Circle(Coord cx, Coord cy, Coord r)
+ : _center(cx, cy), _radius(r)
+ {}
+ Circle(Point const &center, Coord r)
+ : _center(center), _radius(r)
+ {}
+
+ Circle(Coord A, Coord B, Coord C, Coord D) {
+ setCoefficients(A, B, C, D);
+ }
+
+ // Construct the unique circle passing through three points.
+ //Circle(Point const &a, Point const &b, Point const &c);
+
+ Point center() const { return _center; }
+ Coord center(Dim2 d) const { return _center[d]; }
+ Coord radius() const { return _radius; }
+ Coord area() const { return M_PI * _radius * _radius; }
+ bool isDegenerate() const { return _radius == 0; }
+
+ void setCenter(Point const &p) { _center = p; }
+ void setRadius(Coord c) { _radius = c; }
+
+ Rect boundsFast() const;
+ Rect boundsExact() const { return boundsFast(); }
+
+ Point initialPoint() const;
+ Point finalPoint() const { return initialPoint(); }
+ Point pointAt(Coord t) const;
+ Coord valueAt(Coord t, Dim2 d) const;
+ Coord timeAt(Point const &p) const;
+ Coord nearestTime(Point const &p) const;
+
+ bool contains(Point const &p) const { return distance(p, _center) <= _radius; }
+ bool contains(Rect const &other) const;
+ bool contains(Circle const &other) const;
+
+ bool intersects(Line const &l) const;
+ bool intersects(LineSegment const &l) const;
+ bool intersects(Circle const &other) const;
+
+ std::vector<ShapeIntersection> intersect(Line const &other) const;
+ std::vector<ShapeIntersection> intersect(LineSegment const &other) const;
+ std::vector<ShapeIntersection> intersect(Circle const &other) const;
+
+ // build a circle by its implicit equation:
+ // Ax^2 + Ay^2 + Bx + Cy + D = 0
+ void setCoefficients(Coord A, Coord B, Coord C, Coord D);
+ void coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const;
+ std::vector<Coord> coefficients() const;
+
+ Zoom unitCircleTransform() const;
+ Zoom inverseUnitCircleTransform() const;
+
+ EllipticalArc *
+ arc(Point const& initial, Point const& inner, Point const& final) const;
+
+ D2<SBasis> toSBasis() const;
+
+ Circle &operator*=(Translate const &t) {
+ _center *= t;
+ return *this;
+ }
+ Circle &operator*=(Rotate const &) {
+ return *this;
+ }
+ Circle &operator*=(Zoom const &z) {
+ _center *= z;
+ _radius *= z.scale();
+ return *this;
+ }
+
+ bool operator==(Circle const &other) const;
+
+ /** @brief Fit the circle to the passed points using the least squares method.
+ * @param points Samples at the perimeter of the circle */
+ void fit(std::vector<Point> const &points);
+};
+
+bool are_near(Circle const &a, Circle const &b, Coord eps=EPSILON);
+
+std::ostream &operator<<(std::ostream &out, Circle const &c);
+
+template <>
+struct ShapeTraits<Circle> {
+ typedef Coord TimeType;
+ typedef Interval IntervalType;
+ typedef Ellipse AffineClosureType;
+ typedef Intersection<> IntersectionType;
+};
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_CIRCLE_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/include/2geom/concepts.h b/include/2geom/concepts.h
new file mode 100644
index 0000000..de76d0f
--- /dev/null
+++ b/include/2geom/concepts.h
@@ -0,0 +1,209 @@
+/**
+ * \file
+ * \brief Template concepts used by 2Geom
+ *//*
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * 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, output 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 LIB2GEOM_SEEN_CONCEPTS_H
+#define LIB2GEOM_SEEN_CONCEPTS_H
+
+#include <2geom/sbasis.h>
+#include <2geom/interval.h>
+#include <2geom/point.h>
+#include <2geom/rect.h>
+#include <2geom/intersection.h>
+#include <vector>
+#include <boost/concept/assert.hpp>
+#include <2geom/forward.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+//forward decls
+template <typename T> struct ResultTraits;
+
+template <> struct ResultTraits<double> {
+ typedef OptInterval bounds_type;
+ typedef SBasis sb_type;
+};
+
+template <> struct ResultTraits<Point> {
+ typedef OptRect bounds_type;
+ typedef D2<SBasis> sb_type;
+};
+
+//A concept for one-dimensional functions defined on [0,1]
+template <typename T>
+struct FragmentConcept {
+ typedef typename T::output_type OutputType;
+ typedef typename ResultTraits<OutputType>::bounds_type BoundsType;
+ typedef typename ResultTraits<OutputType>::sb_type SbType;
+ T t;
+ double d;
+ OutputType o;
+ bool b;
+ BoundsType i;
+ Interval dom;
+ std::vector<OutputType> v;
+ unsigned u;
+ SbType sb;
+ void constraints() {
+ t = T(o);
+ b = t.isZero(d);
+ b = t.isConstant(d);
+ b = t.isFinite();
+ o = t.at0();
+ o = t.at1();
+ t.at0() = o;
+ t.at1() = o;
+ o = t.valueAt(d);
+ o = t(d);
+ v = t.valueAndDerivatives(d, u-1);
+ //Is a pure derivative (ignoring others) accessor ever much faster?
+ //u = number of values returned. first val is value.
+ sb = t.toSBasis();
+ t = reverse(t);
+ i = bounds_fast(t);
+ i = bounds_exact(t);
+ i = bounds_local(t, dom);
+ /*With portion, Interval makes some sense, but instead I'm opting for
+ doubles, for the following reasons:
+ A) This way a reversed portion may be specified
+ B) Performance might be a bit better for piecewise and such
+ C) Interval version provided below
+ */
+ t = portion(t, d, d);
+ }
+};
+
+template <typename T>
+struct ShapeConcept {
+ typedef typename ShapeTraits<T>::TimeType Time;
+ typedef typename ShapeTraits<T>::IntervalType Interval;
+ typedef typename ShapeTraits<T>::AffineClosureType AffineClosure;
+ typedef typename ShapeTraits<T>::IntersectionType Isect;
+
+ T shape, other;
+ Time t;
+ Point p;
+ AffineClosure ac;
+ Affine m;
+ Translate tr;
+ Coord c;
+ bool bool_;
+ std::vector<Isect> ivec;
+
+ void constraints() {
+ p = shape.pointAt(t);
+ c = shape.valueAt(t, X);
+ ivec = shape.intersect(other);
+ t = shape.nearestTime(p);
+ shape *= tr;
+ ac = shape;
+ ac *= m;
+ bool_ = (shape == shape);
+ bool_ = (shape != other);
+ bool_ = shape.isDegenerate();
+ //bool_ = are_near(shape, other, c);
+ }
+};
+
+template <typename T>
+inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); }
+
+template <typename T>
+struct EqualityComparableConcept {
+ T a, b;
+ bool bool_;
+ void constraints() {
+ bool_ = (a == b);
+ bool_ = (a != b);
+ }
+};
+
+template <typename T>
+struct NearConcept {
+ T a, b;
+ double tol;
+ bool res;
+ void constraints() {
+ res = are_near(a, b, tol);
+ }
+};
+
+template <typename T>
+struct OffsetableConcept {
+ T t;
+ typename T::output_type d;
+ void constraints() {
+ t = t + d; t += d;
+ t = t - d; t -= d;
+ }
+};
+
+template <typename T>
+struct ScalableConcept {
+ T t;
+ typename T::output_type d;
+ void constraints() {
+ t = -t;
+ t = t * d; t *= d;
+ t = t / d; t /= d;
+ }
+};
+
+template <typename T>
+struct AddableConcept {
+ T i, j;
+ void constraints() {
+ i += j; i = i + j;
+ i -= j; i = i - j;
+ }
+};
+
+template <typename T>
+struct MultiplicableConcept {
+ T i, j;
+ void constraints() {
+ i *= j; i = i * j;
+ }
+};
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_CONCEPTS_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/include/2geom/conic_section_clipper.h b/include/2geom/conic_section_clipper.h
new file mode 100644
index 0000000..38bba33
--- /dev/null
+++ b/include/2geom/conic_section_clipper.h
@@ -0,0 +1,58 @@
+/** @file
+ * @brief Conic section clipping with respect to a rectangle
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail>
+ *
+ * Copyright 2009 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 LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H
+#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H
+
+
+#undef CLIP_WITH_CAIRO_SUPPORT
+#include <2geom/conic_section_clipper_impl.h>
+
+
+#endif // _2GEOM_CONIC_SECTION_CLIPPER_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/include/2geom/conic_section_clipper_cr.h b/include/2geom/conic_section_clipper_cr.h
new file mode 100644
index 0000000..6c62494
--- /dev/null
+++ b/include/2geom/conic_section_clipper_cr.h
@@ -0,0 +1,64 @@
+/** @file
+ * @brief Conic section clipping with respect to a rectangle
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail>
+ *
+ * Copyright 2009 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.
+ */
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// This header should be used for graphical debugging purpuse only. //
+////////////////////////////////////////////////////////////////////////////////
+
+
+#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H
+#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H
+
+
+#define CLIP_WITH_CAIRO_SUPPORT
+#include "conic_section_clipper_impl.h"
+#include "conic_section_clipper_impl.cpp"
+
+
+#endif // _2GEOM_CONIC_SECTION_CLIPPER_CR_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/include/2geom/conic_section_clipper_impl.h b/include/2geom/conic_section_clipper_impl.h
new file mode 100644
index 0000000..ee67df1
--- /dev/null
+++ b/include/2geom/conic_section_clipper_impl.h
@@ -0,0 +1,346 @@
+/** @file
+ * @brief Conic section clipping with respect to a rectangle
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail>
+ *
+ * Copyright 2009 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 LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H
+#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H
+
+
+#include <2geom/conicsec.h>
+#include <2geom/line.h>
+
+#include <list>
+#include <map>
+
+
+
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ #include <2geom/toys/path-cairo.h>
+ #define CLIPPER_CLASS clipper_cr
+#else
+ #define CLIPPER_CLASS clipper
+#endif
+
+//#define CLIPDBG
+
+#ifdef CLIPDBG
+#include <2geom/toys/path-cairo.h>
+#define DBGINFO(msg) \
+ std::cerr << msg << std::endl;
+#define DBGPRINT(msg, var) \
+ std::cerr << msg << var << std::endl;
+#define DBGPRINTIF(cond, msg, var) \
+ if (cond) \
+ std::cerr << msg << var << std::endl;
+
+#define DBGPRINT2(msg1, var1, msg2, var2) \
+ std::cerr << msg1 << var1 << msg2 << var2 << std::endl;
+
+#define DBGPRINTCOLL(msg, coll) \
+ if (coll.size() != 0) \
+ std::cerr << msg << ":\n"; \
+ for (size_t i = 0; i < coll.size(); ++i) \
+ { \
+ std::cerr << i << ": " << coll[i] << "\n"; \
+ }
+
+#else
+#define DBGINFO(msg)
+#define DBGPRINT(msg, var)
+#define DBGPRINTIF(cond, msg, var)
+#define DBGPRINT2(msg1, var1, msg2, var2)
+#define DBGPRINTCOLL(msg, coll)
+#endif
+
+
+
+
+namespace Geom
+{
+
+class CLIPPER_CLASS
+{
+
+ public:
+
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ clipper_cr (cairo_t* _cr, const xAx & _cs, const Rect & _R)
+ : cr(_cr), cs(_cs), R(_R)
+ {
+ DBGPRINT ("CLIP: right side: ", R.right())
+ DBGPRINT ("CLIP: top side: ", R.top())
+ DBGPRINT ("CLIP: left side: ", R.left())
+ DBGPRINT ("CLIP: bottom side: ", R.bottom())
+ }
+#else
+ clipper (const xAx & _cs, const Rect & _R)
+ : cs(_cs), R(_R)
+ {
+ }
+#endif
+
+ bool clip (std::vector<RatQuad> & arcs);
+
+ bool found_any_isolated_point() const
+ {
+ return ( !single_points.empty() );
+ }
+
+ const std::vector<Point> & isolated_points() const
+ {
+ return single_points;
+ }
+
+
+ private:
+ bool intersect (std::vector<Point> & crossing_points) const;
+
+ bool are_paired (Point & M, const Point & P1, const Point & P2) const;
+ void pairing (std::vector<Point> & paired_points,
+ std::vector<Point> & inner_points,
+ const std::vector<Point> & crossing_points);
+
+ Point find_inner_point_by_bisector_line (const Point & P,
+ const Point & Q) const;
+ Point find_inner_point (const Point & P, const Point & Q) const;
+
+ std::list<Point>::iterator split (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp) const;
+ void rsplit (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp,
+ size_t k) const;
+
+ void rsplit (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp,
+ double length) const;
+
+ private:
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ cairo_t* cr;
+#endif
+ const xAx & cs;
+ const Rect & R;
+ std::vector<Point> single_points;
+};
+
+
+
+
+/*
+ * Given two point "P", "Q" on the conic section the method computes
+ * a third point inner to the arc with end-point "P", "Q".
+ * The new point is found by intersecting the conic with the bisector line
+ * of the PQ line segment.
+ */
+inline
+Point CLIPPER_CLASS::find_inner_point_by_bisector_line (const Point & P,
+ const Point & Q) const
+{
+ DBGPRINT ("CLIP: find_inner_point_by_bisector_line: P = ", P)
+ DBGPRINT ("CLIP: find_inner_point_by_bisector_line: Q = ", Q)
+ Line bl = make_bisector_line (LineSegment (P, Q));
+ std::vector<double> rts = cs.roots (bl);
+ //DBGPRINT ("CLIP: find_inner_point: rts.size = ", rts.size())
+ double t;
+ if (rts.size() == 0)
+ {
+ THROW_LOGICALERROR ("clipper::find_inner_point_by_bisector_line: "
+ "no conic-bisector line intersection point");
+ }
+ if (rts.size() == 2)
+ {
+ // we suppose that the searched point is the nearest
+ // to the line segment PQ
+ t = (std::fabs(rts[0]) < std::fabs(rts[1])) ? rts[0] : rts[1];
+ }
+ else
+ {
+ t = rts[0];
+ }
+ return bl.pointAt (t);
+}
+
+
+/*
+ * Given two point "P", "Q" on the conic section the method computes
+ * a third point inner to the arc with end-point "P", "Q".
+ * The new point is found by intersecting the conic with the line
+ * passing through the middle point of the PQ line segment and
+ * the intersection point of the tangent lines at points P and Q.
+ */
+inline
+Point CLIPPER_CLASS::find_inner_point (const Point & P, const Point & Q) const
+{
+
+ Line l1 = cs.tangent (P);
+ Line l2 = cs.tangent (Q);
+ Line l;
+ // in case we fail to find a crossing point we fall back to the bisector
+ // method
+ try
+ {
+ OptCrossing oc = intersection(l1, l2);
+ if (!oc)
+ {
+ return find_inner_point_by_bisector_line (P, Q);
+ }
+ l.setPoints (l1.pointAt (oc->ta), middle_point (P, Q));
+ }
+ catch (Geom::InfiniteSolutions const &e)
+ {
+ return find_inner_point_by_bisector_line (P, Q);
+ }
+
+ std::vector<double> rts = cs.roots (l);
+ double t;
+ if (rts.size() == 0)
+ {
+ return find_inner_point_by_bisector_line (P, Q);
+ }
+ // the line "l" origin is set to the tangent crossing point so in case
+ // we find two intersection points only the nearest belongs to the given arc
+ // pay attention: in case we are dealing with an hyperbola (remember that
+ // end points are on the same branch, because they are paired) the tangent
+ // crossing point belongs to the angle delimited by hyperbola asymptotes
+ // and containing the given hyperbola branch, so the previous statement is
+ // still true
+ if (rts.size() == 2)
+ {
+ t = (std::fabs(rts[0]) < std::fabs(rts[1])) ? rts[0] : rts[1];
+ }
+ else
+ {
+ t = rts[0];
+ }
+ return l.pointAt (t);
+}
+
+
+/*
+ * Given a list of points on the conic section, and given two consecutive
+ * points belonging to the list and passed by two list iterators, the method
+ * finds a new point that is inner to the conic arc which has the two passed
+ * points as initial and final point. This new point is inserted into the list
+ * between the two passed points and an iterator pointing to the new point
+ * is returned.
+ */
+inline
+std::list<Point>::iterator CLIPPER_CLASS::split (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp) const
+{
+ Point new_point = find_inner_point (*sp, *fp);
+ std::list<Point>::iterator ip = points.insert (fp, new_point);
+ //std::cerr << "CLIP: split: [" << *sp << ", " << *ip << ", "
+ // << *fp << "]" << std::endl;
+ return ip;
+}
+
+
+/*
+ * Given a list of points on the conic section, and given two consecutive
+ * points belonging to the list and passed by two list iterators, the method
+ * recursively finds new points that are inner to the conic arc which has
+ * the two passed points as initial and final point. The recursion stop after
+ * "k" recursive calls. These new points are inserted into the list between
+ * the two passed points, and in the order we cross them going from
+ * the initial to the final arc point.
+ */
+inline
+void CLIPPER_CLASS::rsplit (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp,
+ size_t k) const
+{
+ if (k == 0)
+ {
+ //DBGINFO("CLIP: split: no further split")
+ return;
+ }
+
+ std::list<Point>::iterator ip = split (points, sp, fp);
+ --k;
+ rsplit (points, sp, ip, k);
+ rsplit (points, ip, fp, k);
+}
+
+
+/*
+ * Given a list of points on the conic section, and given two consecutive
+ * points belonging to the list and passed by two list iterators, the method
+ * recursively finds new points that are inner to the conic arc which has
+ * the two passed points as initial and final point. The recursion stop when
+ * the max distance between the new computed inner point and the two passed
+ * arc end-points is less then the value specified by the "length" parameter.
+ * These new points are inserted into the list between the two passed points,
+ * and in the order we cross them going from the initial to the final arc point.
+ */
+inline
+void CLIPPER_CLASS::rsplit (std::list<Point> & points,
+ std::list<Point>::iterator sp,
+ std::list<Point>::iterator fp,
+ double length) const
+{
+ std::list<Point>::iterator ip = split (points, sp, fp);
+ double d1 = distance (*sp, *ip);
+ double d2 = distance (*ip, *fp);
+ double mdist = std::max (d1, d2);
+
+ if (mdist < length)
+ {
+ //DBGINFO("CLIP: split: no further split")
+ return;
+ }
+
+ // they have to be called both to keep the number of points in the list
+ // in the form 2k+1 where k are the sub-arcs the initial arc is split in.
+ rsplit (points, sp, ip, length);
+ rsplit (points, ip, fp, length);
+}
+
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_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/include/2geom/conicsec.h b/include/2geom/conicsec.h
new file mode 100644
index 0000000..bfd5f36
--- /dev/null
+++ b/include/2geom/conicsec.h
@@ -0,0 +1,537 @@
+/** @file
+ * @brief Conic Section
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ *
+ * Copyright 2009 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 LIB2GEOM_SEEN_CONICSEC_H
+#define LIB2GEOM_SEEN_CONICSEC_H
+
+#include <2geom/exception.h>
+#include <2geom/angle.h>
+#include <2geom/rect.h>
+#include <2geom/affine.h>
+#include <2geom/point.h>
+#include <2geom/line.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/numeric/linear_system.h>
+#include <2geom/numeric/symmetric-matrix-fs.h>
+#include <2geom/numeric/symmetric-matrix-fs-operation.h>
+
+#include <optional>
+
+#include <string>
+#include <vector>
+#include <ostream>
+
+
+
+
+namespace Geom
+{
+
+class RatQuad{
+ /**
+ * A curve of the form B02*A + B12*B*w + B22*C/(B02 + B12*w + B22)
+ * These curves can exactly represent a piece conic section less than a certain angle (find out)
+ *
+ **/
+
+public:
+ Point P[3];
+ double w;
+ RatQuad() {}
+ RatQuad(Point a, Point b, Point c, double w) : w(w) {
+ P[0] = a;
+ P[1] = b;
+ P[2] = c;
+ }
+ double lambda() const;
+
+ static RatQuad fromPointsTangents(Point P0, Point dP0,
+ Point P,
+ Point P2, Point dP2);
+ static RatQuad circularArc(Point P0, Point P1, Point P2);
+
+ CubicBezier toCubic() const;
+ CubicBezier toCubic(double lam) const;
+
+ Point pointAt(double t) const;
+ Point at0() const {return P[0];}
+ Point at1() const {return P[2];}
+
+ void split(RatQuad &a, RatQuad &b) const;
+
+ D2<SBasis> hermite() const;
+ std::vector<SBasis> homogeneous() const;
+};
+
+
+
+
+class xAx{
+public:
+ double c[6];
+
+ enum kind_t
+ {
+ PARABOLA,
+ CIRCLE,
+ REAL_ELLIPSE,
+ IMAGINARY_ELLIPSE,
+ RECTANGULAR_HYPERBOLA,
+ HYPERBOLA,
+ DOUBLE_LINE,
+ TWO_REAL_PARALLEL_LINES,
+ TWO_IMAGINARY_PARALLEL_LINES,
+ TWO_REAL_CROSSING_LINES,
+ TWO_IMAGINARY_CROSSING_LINES, // crossing at a real point
+ SINGLE_POINT = TWO_IMAGINARY_CROSSING_LINES,
+ UNKNOWN
+ };
+
+
+ xAx() {}
+
+ /*
+ * Define the conic section by its algebraic equation coefficients
+ *
+ * c0, .., c5: equation coefficients
+ */
+ xAx (double c0, double c1, double c2, double c3, double c4, double c5)
+ {
+ set (c0, c1, c2, c3, c4, c5);
+ }
+
+ /*
+ * Define a conic section by its related symmetric matrix
+ */
+ xAx (const NL::ConstSymmetricMatrixView<3> & C)
+ {
+ set(C);
+ }
+
+ /*
+ * Define a conic section by computing the one that fits better with
+ * N points.
+ *
+ * points: points to fit
+ *
+ * precondition: there must be at least 5 non-overlapping points
+ */
+ xAx (std::vector<Point> const& points)
+ {
+ set (points);
+ }
+
+ /*
+ * Define a section conic by providing the coordinates of one of its
+ * vertex,the major axis inclination angle and the coordinates of its foci
+ * with respect to the unidimensional system defined by the major axis with
+ * origin set at the provided vertex.
+ *
+ * _vertex : section conic vertex V
+ * _angle : section conic major axis angle
+ * _dist1: +/-distance btw V and nearest focus
+ * _dist2: +/-distance btw V and farest focus
+ *
+ * prerequisite: _dist1 <= _dist2
+ */
+ xAx (const Point& _vertex, double _angle, double _dist1, double _dist2)
+ {
+ set (_vertex, _angle, _dist1, _dist2);
+ }
+
+ /*
+ * Define a conic section by providing one of its vertex and its foci.
+ *
+ * _vertex: section conic vertex
+ * _focus1: section conic focus
+ * _focus2: section conic focus
+ */
+ xAx (const Point& _vertex, const Point& _focus1, const Point& _focus2)
+ {
+ set(_vertex, _focus1, _focus2);
+ }
+
+ /*
+ * Define a conic section by passing a focus, the related directrix,
+ * and the eccentricity (e)
+ * (e < 1 -> ellipse; e = 1 -> parabola; e > 1 -> hyperbola)
+ *
+ * _focus: a focus of the conic section
+ * _directrix: the directrix related to the given focus
+ * _eccentricity: the eccentricity parameter of the conic section
+ */
+ xAx (const Point & _focus, const Line & _directrix, double _eccentricity)
+ {
+ set (_focus, _directrix, _eccentricity);
+ }
+
+ /*
+ * Made up a degenerate conic section as a pair of lines
+ *
+ * l1, l2: lines that made up the conic section
+ */
+ xAx (const Line& l1, const Line& l2)
+ {
+ set (l1, l2);
+ }
+
+ /*
+ * Define the conic section by its algebraic equation coefficients
+ * c0, ..., c5: equation coefficients
+ */
+ void set (double c0, double c1, double c2, double c3, double c4, double c5)
+ {
+ c[0] = c0; c[1] = c1; c[2] = c2; // xx, xy, yy
+ c[3] = c3; c[4] = c4; // x, y
+ c[5] = c5; // 1
+ }
+
+ /*
+ * Define a conic section by its related symmetric matrix
+ */
+ void set (const NL::ConstSymmetricMatrixView<3> & C)
+ {
+ set(C(0,0), 2*C(1,0), C(1,1), 2*C(2,0), 2*C(2,1), C(2,2));
+ }
+
+ void set (std::vector<Point> const& points);
+
+ void set (const Point& _vertex, double _angle, double _dist1, double _dist2);
+
+ void set (const Point& _vertex, const Point& _focus1, const Point& _focus2);
+
+ void set (const Point & _focus, const Line & _directrix, double _eccentricity);
+
+ void set (const Line& l1, const Line& l2);
+
+
+ static xAx fromPoint(Point p);
+ static xAx fromDistPoint(Point p, double d);
+ static xAx fromLine(Point n, double d);
+ static xAx fromLine(Line l);
+ static xAx fromPoints(std::vector<Point> const &pts);
+
+
+ template<typename T>
+ T evaluate_at(T x, T y) const {
+ return c[0]*x*x + c[1]*x*y + c[2]*y*y + c[3]*x + c[4]*y + c[5];
+ }
+
+ double valueAt(Point P) const;
+
+ std::vector<double> implicit_form_coefficients() const {
+ return std::vector<double>(c, c+6);
+ }
+
+ template<typename T>
+ T evaluate_at(T x, T y, T w) const {
+ return c[0]*x*x + c[1]*x*y + c[2]*y*y + c[3]*x*w + c[4]*y*w + c[5]*w*w;
+ }
+
+ xAx scale(double sx, double sy) const;
+
+ Point gradient(Point p) const;
+
+ xAx operator-(xAx const &b) const;
+ xAx operator+(xAx const &b) const;
+ xAx operator+(double const &b) const;
+ xAx operator*(double const &b) const;
+
+ std::vector<Point> crossings(Rect r) const;
+ std::optional<RatQuad> toCurve(Rect const & bnd) const;
+ std::vector<double> roots(Point d, Point o) const;
+
+ std::vector<double> roots(Line const &l) const;
+
+ static Interval quad_ex(double a, double b, double c, Interval ivl);
+
+ Geom::Affine hessian() const;
+
+ std::optional<Point> bottom() const;
+
+ Interval extrema(Rect r) const;
+
+
+ /*
+ * Return the symmetric matrix related to the conic section.
+ * Modifying the matrix does not modify the conic section
+ */
+ NL::SymmetricMatrix<3> get_matrix() const
+ {
+ NL::SymmetricMatrix<3> C(c);
+ C(1,0) *= 0.5; C(2,0) *= 0.5; C(2,1) *= 0.5;
+ return C;
+ }
+
+ /*
+ * Return the i-th coefficient of the conic section algebraic equation
+ * Modifying the returned value does not modify the conic section coefficient
+ */
+ double coeff (size_t i) const
+ {
+ return c[i];
+ }
+
+ /*
+ * Return the i-th coefficient of the conic section algebraic equation
+ * Modifying the returned value modifies the conic section coefficient
+ */
+ double& coeff (size_t i)
+ {
+ return c[i];
+ }
+
+ kind_t kind () const;
+
+ std::string categorise() const;
+
+ /*
+ * Return true if the equation:
+ * c0*x^2 + c1*xy + c2*y^2 + c3*x + c4*y +c5 == 0
+ * really defines a conic, false otherwise
+ */
+ bool is_quadratic() const
+ {
+ return (coeff(0) != 0 || coeff(1) != 0 || coeff(2) != 0);
+ }
+
+ /*
+ * Return true if the conic is degenerate, i.e. if the related matrix
+ * determinant is null, false otherwise
+ */
+ bool isDegenerate() const
+ {
+ return (det_sgn (get_matrix()) == 0);
+ }
+
+ /*
+ * Compute the centre of symmetry of the conic section when it exists,
+ * else it return an uninitialized std::optional<Point> instance.
+ */
+ std::optional<Point> centre() const
+ {
+ typedef std::optional<Point> opt_point_t;
+
+ double d = coeff(1) * coeff(1) - 4 * coeff(0) * coeff(2);
+ if (are_near (d, 0)) return opt_point_t();
+ NL::Matrix Q(2, 2);
+ Q(0,0) = coeff(0);
+ Q(1,1) = coeff(2);
+ Q(0,1) = Q(1,0) = coeff(1) * 0.5;
+ NL::Vector T(2);
+ T[0] = - coeff(3) * 0.5;
+ T[1] = - coeff(4) * 0.5;
+
+ NL::LinearSystem ls (Q, T);
+ NL::Vector sol = ls.SV_solve();
+ Point C;
+ C[0] = sol[0];
+ C[1] = sol[1];
+
+ return opt_point_t(C);
+ }
+
+ double axis_angle() const;
+
+ void roots (std::vector<double>& sol, Coord v, Dim2 d) const;
+
+ xAx translate (const Point & _offset) const;
+
+ xAx rotate (double angle) const;
+
+ /*
+ * Rotate the conic section by the given angle wrt the provided point.
+ *
+ * _rot_centre: the rotation centre
+ * _angle: the rotation angle
+ */
+ xAx rotate (const Point & _rot_centre, double _angle) const
+ {
+ xAx result
+ = translate (-_rot_centre).rotate (_angle).translate (_rot_centre);
+ return result;
+ }
+
+ /*
+ * Compute the tangent line of the conic section at the provided point
+ *
+ * _point: the conic section point the tangent line pass through
+ */
+ Line tangent (const Point & _point) const
+ {
+ NL::Vector pp(3);
+ pp[0] = _point[0]; pp[1] = _point[1]; pp[2] = 1;
+ NL::SymmetricMatrix<3> C = get_matrix();
+ NL::Vector line = C * pp;
+ return Line(line[0], line[1], line[2]);
+ }
+
+ /*
+ * For a non degenerate conic compute the dual conic.
+ * TODO: investigate degenerate case
+ */
+ xAx dual () const
+ {
+ //assert (! isDegenerate());
+ NL::SymmetricMatrix<3> C = get_matrix();
+ NL::SymmetricMatrix<3> D = adj(C);
+ xAx dc(D);
+ return dc;
+ }
+
+ bool decompose (Line& l1, Line& l2) const;
+
+ /**
+ * @brief Division-free decomposition of a degenerate conic section, without
+ * degeneration test.
+ *
+ * When the conic is degenerate, it consists of 0, 1 or 2 lines in the xy-plane.
+ * This function returns these lines. But it does not check whether the conic
+ * is really degenerate, so calling it on a non-degenerate conic produces a
+ * meaningless result.
+ *
+ * If the number of lines is less than two, the trailing lines in the returned
+ * array will be degenerate. Use Line::isDegenerate() to test for that.
+ *
+ * This version of the decomposition is division-free, which improves numerical
+ * stability compared to decompose().
+ *
+ * @param epsilon The numerical threshold for floating point comparison of discriminants.
+ */
+ std::array<Line, 2> decompose_df(Coord epsilon = EPSILON) const;
+
+ /*
+ * Generate a RatQuad object from a conic arc.
+ *
+ * p0: the initial point of the arc
+ * p1: the inner point of the arc
+ * p2: the final point of the arc
+ */
+ RatQuad toRatQuad (const Point & p0,
+ const Point & p1,
+ const Point & p2) const
+ {
+ Point dp0 = gradient (p0);
+ Point dp2 = gradient (p2);
+ return
+ RatQuad::fromPointsTangents (p0, rot90 (dp0), p1, p2, rot90 (dp2));
+ }
+
+ /*
+ * Return the angle related to the normal gradient computed at the passed
+ * point.
+ *
+ * _point: the point at which computes the angle
+ *
+ * prerequisite: the passed point must lie on the conic
+ */
+ double angle_at (const Point & _point) const
+ {
+ double angle = atan2 (gradient (_point));
+ if (angle < 0) angle += (2*M_PI);
+ return angle;
+ }
+
+ /*
+ * Return true if the given point is contained in the conic arc determined
+ * by the passed points.
+ *
+ * _point: the point to be tested
+ * _initial: the initial point of the arc
+ * _inner: an inner point of the arc
+ * _final: the final point of the arc
+ *
+ * prerequisite: the passed points must lie on the conic, the inner point
+ * has to be strictly contained in the arc, except when the
+ * initial and final points are equal: in such a case if the
+ * inner point is also equal to them, then they define an arc
+ * made up by a single point.
+ *
+ */
+ bool arc_contains (const Point & _point, const Point & _initial,
+ const Point & _inner, const Point & _final) const
+ {
+ AngleInterval ai(angle_at(_initial), angle_at(_inner), angle_at(_final));
+ return ai.contains(angle_at(_point));
+ }
+
+ Rect arc_bound (const Point & P1, const Point & Q, const Point & P2) const;
+
+ std::vector<Point> allNearestTimes (const Point &P) const;
+
+ /*
+ * Return the point on the conic section nearest to the passed point "P".
+ *
+ * P: the point to compute the nearest one
+ */
+ Point nearestTime (const Point &P) const
+ {
+ std::vector<Point> points = allNearestTimes (P);
+ if ( !points.empty() )
+ {
+ return points.front();
+ }
+ // else
+ THROW_LOGICALERROR ("nearestTime: no nearest point found");
+ return Point();
+ }
+
+};
+
+std::vector<Point> intersect(const xAx & C1, const xAx & C2);
+
+bool clip (std::vector<RatQuad> & rq, const xAx & cs, const Rect & R);
+
+inline std::ostream &operator<< (std::ostream &out_file, const xAx &x) {
+ for(double i : x.c) {
+ out_file << i << ", ";
+ }
+ return out_file;
+}
+
+};
+
+
+#endif // LIB2GEOM_SEEN_CONICSEC_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/include/2geom/convex-hull.h b/include/2geom/convex-hull.h
new file mode 100644
index 0000000..4dff9c2
--- /dev/null
+++ b/include/2geom/convex-hull.h
@@ -0,0 +1,346 @@
+/** @file
+ * @brief Convex hull data structures
+ *//*
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_CONVEX_HULL_H
+#define LIB2GEOM_SEEN_CONVEX_HULL_H
+
+#include <2geom/point.h>
+#include <2geom/rect.h>
+#include <vector>
+#include <algorithm>
+#include <boost/operators.hpp>
+#include <optional>
+#include <boost/range/iterator_range.hpp>
+
+namespace Geom {
+
+namespace {
+
+/** @brief Iterator for the lower convex hull.
+ * This iterator allows us to avoid duplicating any points in the hull
+ * boundary and still express most algorithms in a concise way. */
+class ConvexHullLowerIterator
+ : public boost::random_access_iterator_helper
+ < ConvexHullLowerIterator
+ , Point
+ , std::ptrdiff_t
+ , Point const *
+ , Point const &
+ >
+{
+public:
+ typedef ConvexHullLowerIterator Self;
+ ConvexHullLowerIterator()
+ : _data(NULL)
+ , _size(0)
+ , _x(0)
+ {}
+ ConvexHullLowerIterator(std::vector<Point> const &pts, std::size_t x)
+ : _data(&pts[0])
+ , _size(pts.size())
+ , _x(x)
+ {}
+
+ Self &operator++() {
+ *this += 1;
+ return *this;
+ }
+ Self &operator--() {
+ *this -= 1;
+ return *this;
+ }
+ Self &operator+=(std::ptrdiff_t d) {
+ _x += d;
+ return *this;
+ }
+ Self &operator-=(std::ptrdiff_t d) {
+ _x -= d;
+ return *this;
+ }
+ std::ptrdiff_t operator-(Self const &other) const {
+ return _x - other._x;
+ }
+ Point const &operator*() const {
+ if (_x < _size) {
+ return _data[_x];
+ } else {
+ return *_data;
+ }
+ }
+ bool operator==(Self const &other) const {
+ return _data == other._data && _x == other._x;
+ }
+ bool operator<(Self const &other) const {
+ return _data == other._data && _x < other._x;
+ }
+
+private:
+ Point const *_data;
+ std::size_t _size;
+ std::size_t _x;
+};
+
+} // end anonymous namespace
+
+/**
+ * @brief Convex hull based on the Andrew's monotone chain algorithm.
+ * @ingroup Shapes
+ */
+class ConvexHull {
+public:
+ typedef std::vector<Point>::const_iterator iterator;
+ typedef std::vector<Point>::const_iterator const_iterator;
+ typedef std::vector<Point>::const_iterator UpperIterator;
+ typedef ConvexHullLowerIterator LowerIterator;
+
+ /// @name Construct a convex hull.
+ /// @{
+
+ /// Create an empty convex hull.
+ ConvexHull() {}
+ /// Construct a singular convex hull.
+ explicit ConvexHull(Point const &a)
+ : _boundary(1, a)
+ , _lower(1)
+ {}
+ /// Construct a convex hull of two points.
+ ConvexHull(Point const &a, Point const &b);
+ /// Construct a convex hull of three points.
+ ConvexHull(Point const &a, Point const &b, Point const &c);
+ /// Construct a convex hull of four points.
+ ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d);
+ /// Create a convex hull of a vector of points.
+ ConvexHull(std::vector<Point> const &pts);
+
+ /// Create a convex hull of a range of points.
+ template <typename Iter>
+ ConvexHull(Iter first, Iter last)
+ : _lower(0)
+ {
+ _prune(first, last, _boundary);
+ _construct();
+ }
+ /// @}
+
+ /// @name Inspect basic properties.
+ /// @{
+
+ /// Check for emptiness.
+ bool empty() const { return _boundary.empty(); }
+ /// Get the number of points in the hull.
+ size_t size() const { return _boundary.size(); }
+ /// Check whether the hull contains only one point.
+ bool isSingular() const { return _boundary.size() == 1; }
+ /// Check whether the hull is a line.
+ bool isLinear() const { return _boundary.size() == 2; }
+ /// Check whether the hull has zero area.
+ bool isDegenerate() const { return _boundary.size() < 3; }
+ /// Calculate the area of the convex hull.
+ double area() const;
+ //Point centroid() const;
+ //double areaAndCentroid(Point &c);
+ //FatLine maxDiameter() const;
+ //FatLine minDiameter() const;
+ /// @}
+
+ /// @name Inspect bounds and extreme points.
+ /// @{
+
+ /// Compute the bounding rectangle of the convex hull.
+ OptRect bounds() const;
+
+ /// Get the leftmost (minimum X) coordinate of the hull.
+ Coord left() const { return _boundary[0][X]; }
+ /// Get the rightmost (maximum X) coordinate of the hull.
+ Coord right() const { return _boundary[_lower-1][X]; }
+ /// Get the topmost (minimum Y) coordinate of the hull.
+ Coord top() const { return topPoint()[Y]; }
+ /// Get the bottommost (maximum Y) coordinate of the hull.
+ Coord bottom() const { return bottomPoint()[Y]; }
+
+ /// Get the leftmost (minimum X) point of the hull.
+ /// If the leftmost edge is vertical, the top point of the edge is returned.
+ Point leftPoint() const { return _boundary[0]; }
+ /// Get the rightmost (maximum X) point of the hull.
+ /// If the rightmost edge is vertical, the bottom point edge is returned.
+ Point rightPoint() const { return _boundary[_lower-1]; }
+ /// Get the topmost (minimum Y) point of the hull.
+ /// If the topmost edge is horizontal, the right point of the edge is returned.
+ Point topPoint() const;
+ /// Get the bottommost (maximum Y) point of the hull.
+ /// If the bottommost edge is horizontal, the left point of the edge is returned.
+ Point bottomPoint() const;
+ ///@}
+
+ /// @name Iterate over points.
+ /// @{
+ /** @brief Get the begin iterator to the points that form the hull.
+ * Points are returned beginning with the leftmost one, going along
+ * the upper (minimum Y) side, and then along the bottom.
+ * Thus the points are always ordered clockwise. No point is
+ * repeated. */
+ iterator begin() const { return _boundary.begin(); }
+ /// Get the end iterator to the points that form the hull.
+ iterator end() const { return _boundary.end(); }
+ /// Get the first, leftmost point in the hull.
+ Point const &front() const { return _boundary.front(); }
+ /// Get the penultimate point of the lower hull.
+ Point const &back() const { return _boundary.back(); }
+ Point const &operator[](std::size_t i) const {
+ return _boundary[i];
+ }
+
+ /** @brief Get an iterator range to the upper part of the hull.
+ * This returns a range that includes the leftmost point,
+ * all points of the upper hull, and the rightmost point. */
+ boost::iterator_range<UpperIterator> upperHull() const {
+ boost::iterator_range<UpperIterator> r(_boundary.begin(), _boundary.begin() + _lower);
+ return r;
+ }
+
+ /** @brief Get an iterator range to the lower part of the hull.
+ * This returns a range that includes the leftmost point,
+ * all points of the lower hull, and the rightmost point. */
+ boost::iterator_range<LowerIterator> lowerHull() const {
+ if (_boundary.empty()) {
+ boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0),
+ LowerIterator(_boundary, 0));
+ return r;
+ }
+ if (_boundary.size() == 1) {
+ boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0),
+ LowerIterator(_boundary, 1));
+ return r;
+ }
+ boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, _lower - 1),
+ LowerIterator(_boundary, _boundary.size() + 1));
+ return r;
+ }
+ /// @}
+
+ /// @name Check for containment and intersection.
+ /// @{
+ /** @brief Check whether the given point is inside the hull.
+ * This takes logarithmic time. */
+ bool contains(Point const &p) const;
+ /** @brief Check whether the given axis-aligned rectangle is inside the hull.
+ * A rectangle is inside the hull if all of its corners are inside. */
+ bool contains(Rect const &r) const;
+ /// Check whether the given convex hull is completely contained in this one.
+ bool contains(ConvexHull const &other) const;
+ //bool interiorContains(Point const &p) const;
+ //bool interiorContains(Rect const &r) const;
+ //bool interiorContains(ConvexHull const &other) const;
+ //bool intersects(Rect const &r) const;
+ //bool intersects(ConvexHull const &other) const;
+
+ //ConvexHull &operator|=(ConvexHull const &other);
+ //ConvexHull &operator&=(ConvexHull const &other);
+ //ConvexHull &operator*=(Affine const &m);
+
+ //ConvexHull &expand(Point const &p);
+ //void unifyWith(ConvexHull const &other);
+ //void intersectWith(ConvexHull const &other);
+ /// @}
+
+ void swap(ConvexHull &other);
+ void swap(std::vector<Point> &pts);
+
+private:
+ void _construct();
+ static bool _is_clockwise_turn(Point const &a, Point const &b, Point const &c);
+
+ /// Take a vector of points and produce a pruned sorted vector.
+ template <typename Iter>
+ static void _prune(Iter first, Iter last, std::vector<Point> &out) {
+ std::optional<Point> ymin, ymax, xmin, xmax;
+ for (Iter i = first; i != last; ++i) {
+ Point p = *i;
+ if (!ymin || Point::LexLess<Y>()(p, *ymin)) {
+ ymin = p;
+ }
+ if (!xmin || Point::LexLess<X>()(p, *xmin)) {
+ xmin = p;
+ }
+ if (!ymax || Point::LexGreater<Y>()(p, *ymax)) {
+ ymax = p;
+ }
+ if (!xmax || Point::LexGreater<X>()(p, *xmax)) {
+ xmax = p;
+ }
+ }
+ if (!ymin) return;
+
+ ConvexHull qhull(*xmin, *xmax, *ymin, *ymax);
+ for (Iter i = first; i != last; ++i) {
+ if (qhull.contains(*i)) continue;
+ out.push_back(*i);
+ }
+
+ out.push_back(*xmin);
+ out.push_back(*xmax);
+ out.push_back(*ymin);
+ out.push_back(*ymax);
+ std::sort(out.begin(), out.end(), Point::LexLess<X>());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
+ }
+
+ /// Sequence of points forming the convex hull polygon.
+ std::vector<Point> _boundary;
+ /// Index one past the rightmost point, where the lower part of the boundary starts.
+ std::size_t _lower;
+};
+
+/** @brief Output operator for convex hulls.
+ * Prints out all the coordinates. */
+inline std::ostream &operator<< (std::ostream &out_file, const Geom::ConvexHull &in_cvx) {
+ out_file << "ConvexHull(";
+ for(auto i : in_cvx) {
+ out_file << i << ", ";
+ }
+ out_file << ")";
+ return out_file;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_CONVEX_HULL_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/include/2geom/coord.h b/include/2geom/coord.h
new file mode 100644
index 0000000..40db84e
--- /dev/null
+++ b/include/2geom/coord.h
@@ -0,0 +1,208 @@
+/** @file
+ * @brief Integral and real coordinate types and some basic utilities
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2006-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.
+ *
+ */
+
+#ifndef LIB2GEOM_SEEN_COORD_H
+#define LIB2GEOM_SEEN_COORD_H
+
+#include <cmath>
+#include <limits>
+#include <string>
+#include <functional>
+#include <boost/operators.hpp>
+#include <2geom/forward.h>
+
+namespace Geom {
+
+/** @brief 2D axis enumeration (X or Y).
+ * @ingroup Primitives */
+enum Dim2 { X=0, Y=1 };
+
+/** @brief Get the other (perpendicular) dimension.
+ * @ingroup Primitives */
+inline constexpr Dim2 other_dimension(Dim2 d) { return Dim2(int(d) ^ 1); }
+
+// TODO: make a smarter implementation with C++11
+template <typename T>
+struct D2Traits {
+ using D1Value = typename T::D1Value;
+ using D1Reference = typename T::D1Reference;
+ using D1ConstReference = typename T::D1ConstReference;
+};
+
+/** @brief Axis extraction functor.
+ * For use with things such as Boost's transform_iterator.
+ * @ingroup Utilities */
+template <Dim2 D, typename T>
+struct GetAxis {
+ using result_type = typename D2Traits<T>::D1Value;
+ using argument_type = T;
+ typename D2Traits<T>::D1Value operator()(T const &a) const {
+ return a[D];
+ }
+};
+
+/** @brief Floating point type used to store coordinates.
+ * @ingroup Primitives */
+using Coord = double;
+
+/** @brief Type used for integral coordinates.
+ * @ingroup Primitives */
+using IntCoord = int;
+
+/** @brief Default "acceptably small" value.
+ * @ingroup Primitives */
+constexpr Coord EPSILON = 1e-6;
+
+/** @brief Get a value representing infinity.
+ * @ingroup Primitives */
+inline constexpr Coord infinity() { return std::numeric_limits<Coord>::infinity(); }
+
+/** @brief Nearness predicate for values.
+ * @ingroup Primitives */
+inline constexpr bool are_near(Coord a, Coord b, double eps=EPSILON) { return std::abs(a-b) <= eps; }
+inline constexpr bool rel_error_bound(Coord a, Coord b, double eps=EPSILON) { return std::abs(a) <= eps*b; }
+
+/** @brief Numerically stable linear interpolation.
+ * @ingroup Primitives */
+inline constexpr Coord lerp(Coord t, Coord a, Coord b) {
+ return (1 - t) * a + t * b;
+}
+
+/** @brief Traits class used with coordinate types.
+ * Defines point, interval and rectangle types for the given coordinate type.
+ * @ingroup Utilities */
+template <typename C>
+struct CoordTraits {
+ using PointType = D2<C>;
+ using IntervalType = GenericInterval<C>;
+ using OptIntervalType = GenericOptInterval<C>;
+ using RectType = GenericRect<C>;
+ using OptRectType = GenericOptRect<C>;
+
+ using IntervalOps =
+ boost::equality_comparable< IntervalType
+ , boost::orable< IntervalType
+ >>;
+
+ using RectOps =
+ boost::equality_comparable< RectType
+ , boost::orable< RectType
+ , boost::orable< RectType, OptRectType
+ >>>;
+};
+
+// NOTE: operator helpers for Rect and Interval are defined here.
+// This is to avoid increasing their size through multiple inheritance.
+
+template<>
+struct CoordTraits<IntCoord> {
+ using PointType = IntPoint;
+ using IntervalType = IntInterval;
+ using OptIntervalType = OptIntInterval;
+ using RectType = IntRect;
+ using OptRectType = OptIntRect;
+
+ using IntervalOps =
+ boost::equality_comparable< IntInterval
+ , boost::additive< IntInterval
+ , boost::additive< IntInterval, IntCoord
+ , boost::orable< IntInterval
+ >>>>;
+
+ using RectOps =
+ boost::equality_comparable< IntRect
+ , boost::orable< IntRect
+ , boost::orable< IntRect, OptIntRect
+ , boost::additive< IntRect, IntPoint
+ >>>>;
+};
+
+template<>
+struct CoordTraits<Coord> {
+ using PointType = Point;
+ using IntervalType = Interval;
+ using OptIntervalType = OptInterval;
+ using RectType = Rect;
+ using OptRectType = OptRect;
+
+ using IntervalOps =
+ boost::equality_comparable< Interval
+ , boost::equality_comparable< Interval, IntInterval
+ , boost::additive< Interval
+ , boost::multipliable< Interval
+ , boost::orable< Interval
+ , boost::arithmetic< Interval, Coord
+ >>>>>>;
+
+ using RectOps =
+ boost::equality_comparable< Rect
+ , boost::equality_comparable< Rect, IntRect
+ , boost::orable< Rect
+ , boost::orable< Rect, OptRect
+ , boost::additive< Rect, Point
+ , boost::multipliable< Rect, Affine
+ >>>>>>;
+};
+
+/** @brief Convert coordinate to shortest possible string.
+ * @return The shortest string that parses back to the original value.
+ * @relates Coord */
+std::string format_coord_shortest(Coord x);
+
+/** @brief Convert coordinate to human-readable string.
+ * Unlike format_coord_shortest, this function will not omit a leading zero
+ * before a decimal point or use small negative exponents. The output format
+ * is similar to Javascript functions.
+ * @relates Coord */
+std::string format_coord_nice(Coord x);
+
+/** @brief Parse coordinate string.
+ * When using this function in conjunction with format_coord_shortest()
+ * or format_coord_nice(), the value is guaranteed to be preserved exactly.
+ * @relates Coord */
+Coord parse_coord(std::string const &s);
+
+} // namespace Geom
+
+#endif // LIB2GEOM_SEEN_COORD_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/include/2geom/crossing.h b/include/2geom/crossing.h
new file mode 100644
index 0000000..7ca273b
--- /dev/null
+++ b/include/2geom/crossing.h
@@ -0,0 +1,213 @@
+/**
+ * @file
+ * @brief Structure representing the intersection of two curves
+ *//*
+ * Authors:
+ * Michael Sloan <mgsloan@gmail.com>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2006-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 LIB2GEOM_SEEN_CROSSING_H
+#define LIB2GEOM_SEEN_CROSSING_H
+
+#include <vector>
+#include <2geom/rect.h>
+#include <2geom/sweep-bounds.h>
+#include <optional>
+#include <2geom/pathvector.h>
+
+namespace Geom {
+
+//Crossing between one or two paths
+struct Crossing {
+ bool dir; //True: along a, a becomes outside.
+ double ta, tb; //time on a and b of crossing
+ unsigned a, b; //storage of indices
+ Crossing() : dir(false), ta(0), tb(1), a(0), b(1) {}
+ Crossing(double t_a, double t_b, bool direction) : dir(direction), ta(t_a), tb(t_b), a(0), b(1) {}
+ Crossing(double t_a, double t_b, unsigned ai, unsigned bi, bool direction) : dir(direction), ta(t_a), tb(t_b), a(ai), b(bi) {}
+ bool operator==(const Crossing & other) const { return a == other.a && b == other.b && dir == other.dir && ta == other.ta && tb == other.tb; }
+ bool operator!=(const Crossing & other) const { return !(*this == other); }
+
+ unsigned getOther(unsigned cur) const { return a == cur ? b : a; }
+ double getTime(unsigned cur) const { return a == cur ? ta : tb; }
+ double getOtherTime(unsigned cur) const { return a == cur ? tb : ta; }
+ bool onIx(unsigned ix) const { return a == ix || b == ix; }
+};
+
+typedef std::optional<Crossing> OptCrossing;
+
+
+/*
+struct Edge {
+ unsigned node, path;
+ double time;
+ bool reverse;
+ Edge(unsigned p, double t, bool r) : path(p), time(t), reverse(r) {}
+ bool operator==(Edge const &other) const { return other.path == path && other.time == time && other.reverse == reverse; }
+};
+
+struct CrossingNode {
+ std::vector<Edge> edges;
+ CrossingNode() : edges(std::vector<Edge>()) {}
+ explicit CrossingNode(std::vector<Edge> es) : edges(es) {}
+ void add_edge(Edge const &e) {
+ if(std::find(edges.begin(), edges.end(), e) == edges.end())
+ edges.push_back(e);
+ }
+ double time_on(unsigned p) {
+ for(unsigned i = 0; i < edges.size(); i++)
+ if(edges[i].path == p) return edges[i].time;
+ std::cout << "CrossingNode time_on failed\n";
+ return 0;
+ }
+};
+
+
+typedef std::vector<CrossingNode> CrossingGraph;
+
+struct TimeOrder {
+ bool operator()(Edge a, Edge b) {
+ return a.time < b.time;
+ }
+};
+
+class Path;
+CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs);
+*/
+
+/*inline bool are_near(Crossing a, Crossing b) {
+ return are_near(a.ta, b.ta) && are_near(a.tb, b.tb);
+}
+
+struct NearF { bool operator()(Crossing a, Crossing b) { return are_near(a, b); } };
+*/
+
+struct CrossingOrder {
+ unsigned ix;
+ bool rev;
+ CrossingOrder(unsigned i, bool r = false) : ix(i), rev(r) {}
+ bool operator()(Crossing a, Crossing b) {
+ if(rev)
+ return (ix == a.a ? a.ta : a.tb) <
+ (ix == b.a ? b.ta : b.tb);
+ else
+ return (ix == a.a ? a.ta : a.tb) >
+ (ix == b.a ? b.ta : b.tb);
+ }
+};
+
+typedef std::vector<Crossing> Crossings;
+
+typedef std::vector<Crossings> CrossingSet;
+
+template<typename C>
+std::vector<Rect> bounds(C const &a) {
+ std::vector<Rect> rs;
+ for (unsigned i = 0; i < a.size(); i++) {
+ OptRect bb = a[i].boundsFast();
+ if (bb) {
+ rs.push_back(*bb);
+ }
+ }
+ return rs;
+}
+// provide specific method for Paths because paths can be closed or open. Path::size() is named somewhat wrong...
+std::vector<Rect> bounds(Path const &a);
+
+inline void sort_crossings(Crossings &cr, unsigned ix) { std::sort(cr.begin(), cr.end(), CrossingOrder(ix)); }
+
+template <typename T>
+struct CrossingTraits {
+ typedef std::vector<T> VectorT;
+ static inline VectorT init(T const &x) { return VectorT(1, x); }
+};
+template <>
+struct CrossingTraits<Path> {
+ typedef PathVector VectorT;
+ static inline VectorT vector_one(Path const &x) { return VectorT(x); }
+};
+
+template<typename T>
+struct Crosser {
+ typedef typename CrossingTraits<T>::VectorT VectorT;
+ virtual ~Crosser() {}
+ virtual Crossings crossings(T const &a, T const &b) {
+ return crossings(CrossingTraits<T>::vector_one(a), CrossingTraits<T>::vector_one(b))[0]; }
+ virtual CrossingSet crossings(VectorT const &a, VectorT const &b) {
+ CrossingSet results(a.size() + b.size(), Crossings());
+
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(a), bounds(b));
+ for(unsigned i = 0; i < cull.size(); i++) {
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+ unsigned jc = j + a.size();
+ Crossings cr = crossings(a[i], b[j]);
+ for(auto & k : cr) { k.a = i; k.b = jc; }
+
+ //Sort & add A-sorted crossings
+ sort_crossings(cr, i);
+ Crossings n(results[i].size() + cr.size());
+ std::merge(results[i].begin(), results[i].end(), cr.begin(), cr.end(), n.begin(), CrossingOrder(i));
+ results[i] = n;
+
+ //Sort & add B-sorted crossings
+ sort_crossings(cr, jc);
+ n.resize(results[jc].size() + cr.size());
+ std::merge(results[jc].begin(), results[jc].end(), cr.begin(), cr.end(), n.begin(), CrossingOrder(jc));
+ results[jc] = n;
+ }
+ }
+ return results;
+ }
+};
+void merge_crossings(Crossings &a, Crossings &b, unsigned i);
+void offset_crossings(Crossings &cr, double a, double b);
+
+Crossings reverse_ta(Crossings const &cr, std::vector<double> max);
+Crossings reverse_tb(Crossings const &cr, unsigned split, std::vector<double> max);
+CrossingSet reverse_ta(CrossingSet const &cr, unsigned split, std::vector<double> max);
+CrossingSet reverse_tb(CrossingSet const &cr, unsigned split, std::vector<double> max);
+
+void clean(Crossings &cr_a, Crossings &cr_b);
+void delete_duplicates(Crossings &crs);
+
+} // end namespace Geom
+
+#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/include/2geom/curve.h b/include/2geom/curve.h
new file mode 100644
index 0000000..9519144
--- /dev/null
+++ b/include/2geom/curve.h
@@ -0,0 +1,375 @@
+/**
+ * \file
+ * \brief Abstract curve type
+ *
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-2009 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 LIB2GEOM_SEEN_CURVE_H
+#define LIB2GEOM_SEEN_CURVE_H
+
+#include <vector>
+#include <boost/operators.hpp>
+#include <2geom/coord.h>
+#include <2geom/point.h>
+#include <2geom/interval.h>
+#include <2geom/sbasis.h>
+#include <2geom/d2.h>
+#include <2geom/affine.h>
+#include <2geom/intersection.h>
+
+namespace Geom {
+
+class PathSink;
+typedef Intersection<> CurveIntersection;
+
+/**
+ * @brief Abstract continuous curve on a plane defined on [0,1].
+ *
+ * Formally, a curve in 2Geom is defined as a function
+ * \f$\mathbf{C}: [0, 1] \to \mathbb{R}^2\f$
+ * (a function that maps the unit interval to points on a 2D plane). Its image (the set of points
+ * the curve passes through) will be denoted \f$\mathcal{C} = \mathbf{C}[ [0, 1] ]\f$.
+ * All curve types available in 2Geom are continuous and differentiable on their
+ * interior, e.g. \f$(0, 1)\f$. Sometimes the curve's image (value set) is referred to as the curve
+ * itself for simplicity, but keep in mind that it's not strictly correct.
+ *
+ * It is common to think of the parameter as time. The curve can then be interpreted as
+ * describing the position of some moving object from time \f$t=0\f$ to \f$t=1\f$.
+ * Because of this, the parameter is frequently called the time value.
+ *
+ * Some methods return pointers to newly allocated curves. They are expected to be freed
+ * by the caller when no longer used. Default implementations are provided for some methods.
+ *
+ * @ingroup Curves
+ */
+class Curve
+ : boost::equality_comparable<Curve>
+{
+public:
+ virtual ~Curve() {}
+
+ /// @name Evaluate the curve
+ /// @{
+ /** @brief Retrieve the start of the curve.
+ * @return The point corresponding to \f$\mathbf{C}(0)\f$. */
+ virtual Point initialPoint() const = 0;
+
+ /** Retrieve the end of the curve.
+ * @return The point corresponding to \f$\mathbf{C}(1)\f$. */
+ virtual Point finalPoint() const = 0;
+
+ /** @brief Check whether the curve has exactly zero length.
+ * @return True if the curve's initial point is exactly the same as its final point, and it contains
+ * no other points (its value set contains only one element). */
+ virtual bool isDegenerate() const = 0;
+
+ /// Check whether the curve is a line segment.
+ virtual bool isLineSegment() const { return false; }
+
+ /** @brief Get the interval of allowed time values.
+ * @return \f$[0, 1]\f$ */
+ virtual Interval timeRange() const {
+ Interval tr(0, 1);
+ return tr;
+ }
+
+ /** @brief Evaluate the curve at a specified time value.
+ * @param t Time value
+ * @return \f$\mathbf{C}(t)\f$ */
+ virtual Point pointAt(Coord t) const { return pointAndDerivatives(t, 0).front(); }
+
+ /** @brief Evaluate one of the coordinates at the specified time value.
+ * @param t Time value
+ * @param d The dimension to evaluate
+ * @return The specified coordinate of \f$\mathbf{C}(t)\f$ */
+ virtual Coord valueAt(Coord t, Dim2 d) const { return pointAt(t)[d]; }
+
+ /** @brief Evaluate the function at the specified time value. Allows curves to be used
+ * as functors. */
+ virtual Point operator() (Coord t) const { return pointAt(t); }
+
+ /** @brief Evaluate the curve and its derivatives.
+ * This will return a vector that contains the value of the curve and the specified number
+ * of derivatives. However, the returned vector might contain less elements than specified
+ * if some derivatives do not exist.
+ * @param t Time value
+ * @param n The number of derivatives to compute
+ * @return Vector of at most \f$n+1\f$ elements of the form \f$[\mathbf{C}(t),
+ \mathbf{C}'(t), \mathbf{C}''(t), \ldots]\f$ */
+ virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const = 0;
+ /// @}
+
+ /// @name Change the curve's endpoints
+ /// @{
+ /** @brief Change the starting point of the curve.
+ * After calling this method, it is guaranteed that \f$\mathbf{C}(0) = \mathbf{p}\f$,
+ * and the curve is still continuous. The precise new shape of the curve varies with curve
+ * type.
+ * @param p New starting point of the curve */
+ virtual void setInitial(Point const &v) = 0;
+
+ /** @brief Change the ending point of the curve.
+ * After calling this method, it is guaranteed that \f$\mathbf{C}(0) = \mathbf{p}\f$,
+ * and the curve is still continuous. The precise new shape of the curve varies
+ * with curve type.
+ * @param p New ending point of the curve */
+ virtual void setFinal(Point const &v) = 0;
+ /// @}
+
+ /// @name Compute the bounding box
+ /// @{
+ /** @brief Quickly compute the curve's approximate bounding box.
+ * The resulting rectangle is guaranteed to contain all points belonging to the curve,
+ * but it might not be the smallest such rectangle. This method is usually fast.
+ * @return A rectangle that contains all points belonging to the curve. */
+ virtual Rect boundsFast() const = 0;
+
+ /** @brief Compute the curve's exact bounding box.
+ * This method can be dramatically slower than boundsFast() depending on the curve type.
+ * @return The smallest possible rectangle containing all of the curve's points. */
+ virtual Rect boundsExact() const = 0;
+
+ /** @brief Expand the given rectangle to include the transformed curve,
+ * assuming it already contains its initial point.
+ * @param bbox[in,out] bbox The rectangle to expand; it is assumed to already contain (initialPoint() * transform).
+ * @param transform The transform to apply to the curve before taking its bounding box. */
+ virtual void expandToTransformed(Rect &bbox, Affine const &transform) const = 0;
+
+ // I have no idea what the 'deg' parameter is for, so this is undocumented for now.
+ virtual OptRect boundsLocal(OptInterval const &i, unsigned deg) const = 0;
+
+ /** @brief Compute the bounding box of a part of the curve.
+ * Since this method returns the smallest possible bounding rectangle of the specified portion,
+ * it can also be rather slow.
+ * @param a An interval specifying a part of the curve, or nothing.
+ * If \f$[0, 1] \subseteq a\f$, then the bounding box for the entire curve
+ * is calculated.
+ * @return The smallest possible rectangle containing all points in \f$\mathbf{C}[a]\f$,
+ * or nothing if the supplied interval is empty. */
+ OptRect boundsLocal(OptInterval const &a) const { return boundsLocal(a, 0); }
+ /// @}
+
+ /// @name Create new curves based on this one
+ /// @{
+ /** @brief Create an exact copy of this curve.
+ * @return Pointer to a newly allocated curve, identical to the original */
+ virtual Curve *duplicate() const = 0;
+
+ /** @brief Transform this curve by an affine transformation.
+ * Because of this method, all curve types must be closed under affine
+ * transformations.
+ * @param m Affine describing the affine transformation */
+ void transform(Affine const &m) {
+ *this *= m;
+ }
+
+ virtual void operator*=(Translate const &tr) { *this *= Affine(tr); }
+ virtual void operator*=(Scale const &s) { *this *= Affine(s); }
+ virtual void operator*=(Rotate const &r) { *this *= Affine(r); }
+ virtual void operator*=(HShear const &hs) { *this *= Affine(hs); }
+ virtual void operator*=(VShear const &vs) { *this *= Affine(vs); }
+ virtual void operator*=(Zoom const &z) { *this *= Affine(z); }
+ virtual void operator*=(Affine const &m) = 0;
+
+ /** @brief Create a curve transformed by an affine transformation.
+ * This method returns a new curve instead modifying the existing one.
+ * @param m Affine describing the affine transformation
+ * @return Pointer to a new, transformed curve */
+ virtual Curve *transformed(Affine const &m) const {
+ Curve *ret = duplicate();
+ ret->transform(m);
+ return ret;
+ }
+
+ /** @brief Create a curve that corresponds to a part of this curve.
+ * For \f$a > b\f$, the returned portion will be reversed with respect to the original.
+ * The returned curve will always be of the same type.
+ * @param a Beginning of the interval specifying the portion of the curve
+ * @param b End of the interval
+ * @return New curve \f$\mathbf{D}\f$ such that:
+ * - \f$\mathbf{D}(0) = \mathbf{C}(a)\f$
+ * - \f$\mathbf{D}(1) = \mathbf{C}(b)\f$
+ * - \f$\mathbf{D}[ [0, 1] ] = \mathbf{C}[ [a?b] ]\f$,
+ * where \f$[a?b] = [\min(a, b), \max(a, b)]\f$ */
+ virtual Curve *portion(Coord a, Coord b) const = 0;
+
+ /** @brief A version of that accepts an Interval. */
+ Curve *portion(Interval const &i) const { return portion(i.min(), i.max()); }
+
+ /** @brief Create a reversed version of this curve.
+ * The result corresponds to <code>portion(1, 0)</code>, but this method might be faster.
+ * @return Pointer to a new curve \f$\mathbf{D}\f$ such that
+ * \f$\forall_{x \in [0, 1]} \mathbf{D}(x) = \mathbf{C}(1-x)\f$ */
+ virtual Curve *reverse() const { return portion(1, 0); }
+
+ /** @brief Create a derivative of this curve.
+ * It's best to think of the derivative in physical terms: if the curve describes
+ * the position of some object on the plane from time \f$t=0\f$ to \f$t=1\f$ as said in the
+ * introduction, then the curve's derivative describes that object's speed at the same times.
+ * The second derivative refers to its acceleration, the third to jerk, etc.
+ * @return New curve \f$\mathbf{D} = \mathbf{C}'\f$. */
+ virtual Curve *derivative() const = 0;
+ /// @}
+
+ /// @name Advanced operations
+ /// @{
+ /** @brief Compute a time value at which the curve comes closest to a specified point.
+ * The first value with the smallest distance is returned if there are multiple such points.
+ * @param p Query point
+ * @param a Minimum time value to consider
+ * @param b Maximum time value to consider; \f$a < b\f$
+ * @return \f$q \in [a, b]: ||\mathbf{C}(q) - \mathbf{p}|| =
+ \inf(\{r \in \mathbb{R} : ||\mathbf{C}(r) - \mathbf{p}||\})\f$ */
+ virtual Coord nearestTime( Point const& p, Coord a = 0, Coord b = 1 ) const;
+
+ /** @brief A version that takes an Interval. */
+ Coord nearestTime(Point const &p, Interval const &i) const {
+ return nearestTime(p, i.min(), i.max());
+ }
+
+ /** @brief Compute time values at which the curve comes closest to a specified point.
+ * @param p Query point
+ * @param a Minimum time value to consider
+ * @param b Maximum time value to consider; \f$a < b\f$
+ * @return Vector of points closest and equally far away from the query point */
+ virtual std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0,
+ Coord to = 1 ) const;
+
+ /** @brief A version that takes an Interval. */
+ std::vector<Coord> allNearestTimes(Point const &p, Interval const &i) {
+ return allNearestTimes(p, i.min(), i.max());
+ }
+
+ /** @brief Compute the arc length of this curve.
+ * For a curve \f$\mathbf{C}(t) = (C_x(t), C_y(t))\f$, arc length is defined for 2D curves as
+ * \f[ \ell = \int_{0}^{1} \sqrt { [C_x'(t)]^2 + [C_y'(t)]^2 }\, \text{d}t \f]
+ * In other words, we divide the curve into infinitely small linear segments
+ * and add together their lengths. Of course we can't subdivide the curve into
+ * infinitely many segments on a computer, so this method returns an approximation.
+ * Not that there is usually no closed form solution to such integrals, so this
+ * method might be slow.
+ * @param tolerance Maximum allowed error
+ * @return Total distance the curve's value travels on the plane when going from 0 to 1 */
+ virtual Coord length(Coord tolerance=0.01) const;
+
+ /** @brief Computes time values at which the curve intersects an axis-aligned line.
+ * @param v The coordinate of the line
+ * @param d Which axis the coordinate is on. X means a vertical line, Y a horizontal line. */
+ virtual std::vector<Coord> roots(Coord v, Dim2 d) const = 0;
+
+ /** @brief Compute the partial winding number of this curve.
+ * The partial winding number is equal to the difference between the number
+ * of roots at which the curve goes in the +Y direction and the number of roots
+ * at which the curve goes in the -Y direction. This method is mainly useful
+ * for implementing path winding calculation. It will ignore roots which
+ * are local maxima on the Y axis.
+ * @param p Point where the winding number should be determined
+ * @return Winding number contribution at p */
+ virtual int winding(Point const &p) const;
+
+ /// Compute intersections with another curve.
+ virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const;
+
+ /// Compute intersections of this curve with itself.
+ virtual std::vector<CurveIntersection> intersectSelf(Coord eps = EPSILON) const;
+
+ /** @brief Compute a vector tangent to the curve.
+ * This will return an unit vector (a Point with length() equal to 1) that denotes a vector
+ * tangent to the curve. This vector is defined as
+ * \f$ \mathbf{v}(t) = \frac{\mathbf{C}'(t)}{||\mathbf{C}'(t)||} \f$. It is pointed
+ * in the direction of increasing \f$t\f$, at the specified time value. The method uses
+ * l'Hopital's rule when the derivative is zero. A zero vector is returned if no non-zero
+ * derivative could be found.
+ * @param t Time value
+ * @param n The maximum order of derivative to consider
+ * @return Unit tangent vector \f$\mathbf{v}(t)\f$ */
+ virtual Point unitTangentAt(Coord t, unsigned n = 3) const;
+
+ /** @brief Convert the curve to a symmetric power basis polynomial.
+ * Symmetric power basis polynomials (S-basis for short) are numerical representations
+ * of curves with excellent numerical properties. Most high level operations provided by 2Geom
+ * are implemented in terms of S-basis operations, so every curve has to provide a method
+ * to convert it to an S-basis polynomial on two variables. See SBasis class reference
+ * for more information. */
+ virtual D2<SBasis> toSBasis() const = 0;
+ /// @}
+
+ /// @name Miscellaneous
+ /// @{
+ /** Return the number of independent parameters required to represent all variations
+ * of this curve. For example, for Bezier curves it returns the curve's order
+ * multiplied by 2. */
+ virtual int degreesOfFreedom() const { return 0;}
+
+ /** @brief Test equality of two curves.
+ * Equality means that for any time value, the evaluation of either curve will yield
+ * the same value. This means non-degenerate curves are not equal to their reverses.
+ * Note that this tests for exact equality.
+ * @return True if the curves are identical, false otherwise */
+ virtual bool operator==(Curve const &c) const = 0;
+
+ /** @brief Test whether two curves are approximately the same. */
+ virtual bool isNear(Curve const &c, Coord precision) const = 0;
+
+ /** @brief Feed the curve to a PathSink */
+ virtual void feed(PathSink &sink, bool moveto_initial) const;
+ /// @}
+};
+
+inline
+Coord nearest_time(Point const& p, Curve const& c) {
+ return c.nearestTime(p);
+}
+
+// for make benefit glorious library of Boost Pointer Container
+inline
+Curve *new_clone(Curve const &c) {
+ return c.duplicate();
+}
+
+} // end namespace Geom
+
+
+#endif // _2GEOM_CURVE_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/include/2geom/curves.h b/include/2geom/curves.h
new file mode 100644
index 0000000..46fb6d9
--- /dev/null
+++ b/include/2geom/curves.h
@@ -0,0 +1,54 @@
+/** @file
+ * @brief Include all curve types
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2007-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 LIB2GEOM_SEEN_CURVES_H
+#define LIB2GEOM_SEEN_CURVES_H
+
+#include <2geom/curve.h>
+#include <2geom/sbasis-curve.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/elliptical-arc.h>
+
+#endif // LIB2GEOM_SEEN_CURVES_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/include/2geom/d2.h b/include/2geom/d2.h
new file mode 100644
index 0000000..45f036b
--- /dev/null
+++ b/include/2geom/d2.h
@@ -0,0 +1,564 @@
+/**
+ * \file
+ * \brief Lifts one dimensional objects into 2D
+ *//*
+ * Authors:
+ * Michael Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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, output 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 LIB2GEOM_SEEN_D2_H
+#define LIB2GEOM_SEEN_D2_H
+
+#include <iterator>
+#include <boost/concept/assert.hpp>
+#include <boost/iterator/transform_iterator.hpp>
+#include <2geom/point.h>
+#include <2geom/interval.h>
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+#include <2geom/concepts.h>
+
+namespace Geom {
+/**
+ * @brief Adaptor that creates 2D functions from 1D ones.
+ * @ingroup Fragments
+ */
+template <typename T>
+class D2
+{
+private:
+ T f[2];
+
+public:
+ typedef T D1Value;
+ typedef T &D1Reference;
+ typedef T const &D1ConstReference;
+
+ D2() {f[X] = f[Y] = T();}
+ explicit D2(Point const &a) {
+ f[X] = T(a[X]); f[Y] = T(a[Y]);
+ }
+
+ D2(T const &a, T const &b) {
+ f[X] = a;
+ f[Y] = b;
+ }
+
+ template <typename Iter>
+ D2(Iter first, Iter last) {
+ typedef typename std::iterator_traits<Iter>::value_type V;
+ typedef typename boost::transform_iterator<GetAxis<X,V>, Iter> XIter;
+ typedef typename boost::transform_iterator<GetAxis<Y,V>, Iter> YIter;
+
+ XIter xfirst(first, GetAxis<X,V>()), xlast(last, GetAxis<X,V>());
+ f[X] = T(xfirst, xlast);
+ YIter yfirst(first, GetAxis<Y,V>()), ylast(last, GetAxis<Y,V>());
+ f[Y] = T(yfirst, ylast);
+ }
+
+ D2(std::vector<Point> const &vec) {
+ typedef Point V;
+ typedef std::vector<Point>::const_iterator Iter;
+ typedef boost::transform_iterator<GetAxis<X,V>, Iter> XIter;
+ typedef boost::transform_iterator<GetAxis<Y,V>, Iter> YIter;
+
+ XIter xfirst(vec.begin(), GetAxis<X,V>()), xlast(vec.end(), GetAxis<X,V>());
+ f[X] = T(xfirst, xlast);
+ YIter yfirst(vec.begin(), GetAxis<Y,V>()), ylast(vec.end(), GetAxis<Y,V>());
+ f[Y] = T(yfirst, ylast);
+ }
+
+ //TODO: ask MenTaLguY about operator= as seen in Point
+
+ T& operator[](unsigned i) { return f[i]; }
+ T const & operator[](unsigned i) const { return f[i]; }
+ Point point(unsigned i) const {
+ Point ret(f[X][i], f[Y][i]);
+ return ret;
+ }
+
+ //IMPL: FragmentConcept
+ typedef Point output_type;
+ bool isZero(double eps=EPSILON) const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return f[X].isZero(eps) && f[Y].isZero(eps);
+ }
+ bool isConstant(double eps=EPSILON) const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return f[X].isConstant(eps) && f[Y].isConstant(eps);
+ }
+ bool isFinite() const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return f[X].isFinite() && f[Y].isFinite();
+ }
+ Point at0() const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return Point(f[X].at0(), f[Y].at0());
+ }
+ Point at1() const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return Point(f[X].at1(), f[Y].at1());
+ }
+ Point pointAt(double t) const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return (*this)(t);
+ }
+ Point valueAt(double t) const {
+ // TODO: remove this alias
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return (*this)(t);
+ }
+ std::vector<Point > valueAndDerivatives(double t, unsigned n) const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ std::vector<Coord> x = f[X].valueAndDerivatives(t, n),
+ y = f[Y].valueAndDerivatives(t, n); // always returns a vector of size n+1
+ std::vector<Point> res(n+1);
+ for(unsigned i = 0; i <= n; i++) {
+ res[i] = Point(x[i], y[i]);
+ }
+ return res;
+ }
+ D2<SBasis> toSBasis() const {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return D2<SBasis>(f[X].toSBasis(), f[Y].toSBasis());
+ }
+
+ Point operator()(double t) const;
+ Point operator()(double x, double y) const;
+};
+template <typename T>
+inline D2<T> reverse(const D2<T> &a) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return D2<T>(reverse(a[X]), reverse(a[Y]));
+}
+
+template <typename T>
+inline D2<T> portion(const D2<T> &a, Coord f, Coord t) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return D2<T>(portion(a[X], f, t), portion(a[Y], f, t));
+}
+
+template <typename T>
+inline D2<T> portion(const D2<T> &a, Interval i) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return D2<T>(portion(a[X], i), portion(a[Y], i));
+}
+
+//IMPL: EqualityComparableConcept
+template <typename T>
+inline bool
+operator==(D2<T> const &a, D2<T> const &b) {
+ BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>));
+ return a[0]==b[0] && a[1]==b[1];
+}
+template <typename T>
+inline bool
+operator!=(D2<T> const &a, D2<T> const &b) {
+ BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>));
+ return a[0]!=b[0] || a[1]!=b[1];
+}
+
+//IMPL: NearConcept
+template <typename T>
+inline bool
+are_near(D2<T> const &a, D2<T> const &b, double tol) {
+ BOOST_CONCEPT_ASSERT((NearConcept<T>));
+ return are_near(a[0], b[0], tol) && are_near(a[1], b[1], tol);
+}
+
+//IMPL: AddableConcept
+template <typename T>
+inline D2<T>
+operator+(D2<T> const &a, D2<T> const &b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] + b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator-(D2<T> const &a, D2<T> const &b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] - b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator+=(D2<T> &a, D2<T> const &b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] += b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator-=(D2<T> &a, D2<T> const & b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] -= b[i];
+ return a;
+}
+
+//IMPL: ScalableConcept
+template <typename T>
+inline D2<T>
+operator-(D2<T> const & a) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = -a[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator*(D2<T> const & a, Point const & b) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] * b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator/(D2<T> const & a, Point const & b) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ //TODO: b==0?
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] / b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator*=(D2<T> &a, Point const & b) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] *= b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator/=(D2<T> &a, Point const & b) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ //TODO: b==0?
+ for(unsigned i = 0; i < 2; i++)
+ a[i] /= b[i];
+ return a;
+}
+
+template <typename T>
+inline D2<T> operator*(D2<T> const & a, double b) { return D2<T>(a[0]*b, a[1]*b); }
+template <typename T>
+inline D2<T> operator*=(D2<T> & a, double b) { a[0] *= b; a[1] *= b; return a; }
+template <typename T>
+inline D2<T> operator/(D2<T> const & a, double b) { return D2<T>(a[0]/b, a[1]/b); }
+template <typename T>
+inline D2<T> operator/=(D2<T> & a, double b) { a[0] /= b; a[1] /= b; return a; }
+
+template<typename T>
+D2<T> operator*(D2<T> const &v, Affine const &m) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ D2<T> ret;
+ for(unsigned i = 0; i < 2; i++)
+ ret[i] = v[X] * m[i] + v[Y] * m[i + 2] + m[i + 4];
+ return ret;
+}
+
+//IMPL: MultiplicableConcept
+template <typename T>
+inline D2<T>
+operator*(D2<T> const & a, T const & b) {
+ BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>));
+ D2<T> ret;
+ for(unsigned i = 0; i < 2; i++)
+ ret[i] = a[i] * b;
+ return ret;
+}
+
+//IMPL:
+
+//IMPL: OffsetableConcept
+template <typename T>
+inline D2<T>
+operator+(D2<T> const & a, Point b) {
+ BOOST_CONCEPT_ASSERT((OffsetableConcept<T>));
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] + b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator-(D2<T> const & a, Point b) {
+ BOOST_CONCEPT_ASSERT((OffsetableConcept<T>));
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] - b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator+=(D2<T> & a, Point b) {
+ BOOST_CONCEPT_ASSERT((OffsetableConcept<T>));
+ for(unsigned i = 0; i < 2; i++)
+ a[i] += b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator-=(D2<T> & a, Point b) {
+ BOOST_CONCEPT_ASSERT((OffsetableConcept<T>));
+ for(unsigned i = 0; i < 2; i++)
+ a[i] -= b[i];
+ return a;
+}
+
+template <typename T>
+inline T
+dot(D2<T> const & a, D2<T> const & b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+ BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>));
+
+ T r;
+ for(unsigned i = 0; i < 2; i++)
+ r += a[i] * b[i];
+ return r;
+}
+
+/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b
+ * @return \f$a \bullet b = a_X b_X + a_Y b_Y\f$.
+ * @relates D2 */
+template <typename T>
+inline T
+dot(D2<T> const & a, Point const & b) {
+ BOOST_CONCEPT_ASSERT((AddableConcept<T>));
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+
+ T r;
+ for(unsigned i = 0; i < 2; i++) {
+ r += a[i] * b[i];
+ }
+ return r;
+}
+
+/** @brief Calculates the 'cross product' or 'outer product' of \c a and \c b
+ * @return \f$a \times b = a_Y b_X - a_X b_Y\f$.
+ * @relates D2 */
+template <typename T>
+inline T
+cross(D2<T> const & a, D2<T> const & b) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>));
+
+ return a[1] * b[0] - a[0] * b[1];
+}
+
+
+//equivalent to cw/ccw, for use in situations where rotation direction doesn't matter.
+template <typename T>
+inline D2<T>
+rot90(D2<T> const & a) {
+ BOOST_CONCEPT_ASSERT((ScalableConcept<T>));
+ return D2<T>(-a[Y], a[X]);
+}
+
+//TODO: concepterize the following functions
+template <typename T>
+inline D2<T>
+compose(D2<T> const & a, T const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a[i],b);
+ return r;
+}
+
+template <typename T>
+inline D2<T>
+compose_each(D2<T> const & a, D2<T> const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a[i],b[i]);
+ return r;
+}
+
+template <typename T>
+inline D2<T>
+compose_each(T const & a, D2<T> const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a,b[i]);
+ return r;
+}
+
+
+template<typename T>
+inline Point
+D2<T>::operator()(double t) const {
+ Point p;
+ for(unsigned i = 0; i < 2; i++)
+ p[i] = (*this)[i](t);
+ return p;
+}
+
+//TODO: we might want to have this take a Point as the parameter.
+template<typename T>
+inline Point
+D2<T>::operator()(double x, double y) const {
+ Point p;
+ for(unsigned i = 0; i < 2; i++)
+ p[i] = (*this)[i](x, y);
+ return p;
+}
+
+
+template<typename T>
+D2<T> derivative(D2<T> const & a) {
+ return D2<T>(derivative(a[X]), derivative(a[Y]));
+}
+template<typename T>
+D2<T> integral(D2<T> const & a) {
+ return D2<T>(integral(a[X]), integral(a[Y]));
+}
+
+/** A function to print out the Point. It just prints out the coords
+ on the given output stream */
+template <typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const Geom::D2<T> &in_d2) {
+ out_file << "X: " << in_d2[X] << " Y: " << in_d2[Y];
+ return out_file;
+}
+
+//Some D2 Fragment implementation which requires rect:
+template <typename T>
+OptRect bounds_fast(const D2<T> &a) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return OptRect(bounds_fast(a[X]), bounds_fast(a[Y]));
+}
+template <typename T>
+OptRect bounds_exact(const D2<T> &a) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return OptRect(bounds_exact(a[X]), bounds_exact(a[Y]));
+}
+template <typename T>
+OptRect bounds_local(const D2<T> &a, const OptInterval &t) {
+ BOOST_CONCEPT_ASSERT((FragmentConcept<T>));
+ return OptRect(bounds_local(a[X], t), bounds_local(a[Y], t));
+}
+
+
+
+// SBasis-specific declarations
+
+inline D2<SBasis> compose(D2<SBasis> const & a, SBasis const & b) {
+ return D2<SBasis>(compose(a[X], b), compose(a[Y], b));
+}
+
+SBasis L2(D2<SBasis> const & a, unsigned k);
+double L2(D2<double> const & a);
+
+D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b);
+inline D2<SBasis> operator*(Linear const & a, D2<SBasis> const & b) { return multiply(a, b); }
+D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b);
+inline D2<SBasis> operator*(SBasis const & a, D2<SBasis> const & b) { return multiply(a, b); }
+D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms);
+
+unsigned sbasis_size(D2<SBasis> const & a);
+double tail_error(D2<SBasis> const & a, unsigned tail);
+
+//Piecewise<D2<SBasis> > specific declarations
+
+Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a);
+D2<Piecewise<SBasis> > make_cuts_independent(Piecewise<D2<SBasis> > const &a);
+Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &a);
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b);
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b);
+Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b);
+
+Piecewise<D2<SBasis> > operator*(Piecewise<D2<SBasis> > const &a, Affine const &m);
+
+Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f, double tol=0, bool closed=false);
+
+std::vector<Piecewise<D2<SBasis> > > fuse_nearby_ends(std::vector<Piecewise<D2<SBasis> > > const &f, double tol=0);
+
+std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > split_at_discontinuities (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwsbin, double tol = .0001);
+
+Point unitTangentAt(D2<SBasis> const & a, Coord t, unsigned n = 3);
+
+//bounds specializations with order
+inline OptRect bounds_fast(D2<SBasis> const & s, unsigned order=0) {
+ OptRect retval;
+ OptInterval xint = bounds_fast(s[X], order);
+ if (xint) {
+ OptInterval yint = bounds_fast(s[Y], order);
+ if (yint) {
+ retval = Rect(*xint, *yint);
+ }
+ }
+ return retval;
+}
+inline OptRect bounds_local(D2<SBasis> const & s, OptInterval i, unsigned order=0) {
+ OptRect retval;
+ OptInterval xint = bounds_local(s[X], i, order);
+ OptInterval yint = bounds_local(s[Y], i, order);
+ if (xint && yint) {
+ retval = Rect(*xint, *yint);
+ }
+ return retval;
+}
+
+std::vector<Interval> level_set( D2<SBasis> const &f, Rect region);
+std::vector<Interval> level_set( D2<SBasis> const &f, Point p, double tol);
+std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Rect> regions);
+std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Point> pts, double tol);
+
+
+} // end namespace Geom
+
+#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/include/2geom/ellipse.h b/include/2geom/ellipse.h
new file mode 100644
index 0000000..0d1567a
--- /dev/null
+++ b/include/2geom/ellipse.h
@@ -0,0 +1,260 @@
+/** @file
+ * @brief Ellipse shape
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@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 LIB2GEOM_SEEN_ELLIPSE_H
+#define LIB2GEOM_SEEN_ELLIPSE_H
+
+#include <vector>
+#include <2geom/angle.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/exception.h>
+#include <2geom/forward.h>
+#include <2geom/line.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+class EllipticalArc;
+class Circle;
+
+/** @brief Set of points with a constant sum of distances from two foci.
+ *
+ * An ellipse can be specified in several ways. Internally, 2Geom uses
+ * the SVG style representation: center, rays and angle between the +X ray
+ * and the +X axis. Another popular way is to use an implicit equation,
+ * which is as follows:
+ * \f$Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0\f$
+ *
+ * @ingroup Shapes */
+class Ellipse
+ : boost::multipliable< Ellipse, Translate
+ , boost::multipliable< Ellipse, Scale
+ , boost::multipliable< Ellipse, Rotate
+ , boost::multipliable< Ellipse, Zoom
+ , boost::multipliable< Ellipse, Affine
+ , boost::equality_comparable< Ellipse
+ > > > > > >
+{
+ Point _center;
+ Point _rays;
+ Angle _angle;
+public:
+ Ellipse() {}
+ Ellipse(Point const &c, Point const &r, Coord angle)
+ : _center(c)
+ , _rays(r)
+ , _angle(angle)
+ {}
+ Ellipse(Coord cx, Coord cy, Coord rx, Coord ry, Coord angle)
+ : _center(cx, cy)
+ , _rays(rx, ry)
+ , _angle(angle)
+ {}
+ Ellipse(double A, double B, double C, double D, double E, double F) {
+ setCoefficients(A, B, C, D, E, F);
+ }
+ /// Construct ellipse from a circle.
+ Ellipse(Geom::Circle const &c);
+
+ /// Set center, rays and angle.
+ void set(Point const &c, Point const &r, Coord angle) {
+ _center = c;
+ _rays = r;
+ _angle = angle;
+ }
+ /// Set center, rays and angle as constituent values.
+ void set(Coord cx, Coord cy, Coord rx, Coord ry, Coord a) {
+ _center[X] = cx;
+ _center[Y] = cy;
+ _rays[X] = rx;
+ _rays[Y] = ry;
+ _angle = a;
+ }
+ /// Set an ellipse by solving its implicit equation.
+ void setCoefficients(double A, double B, double C, double D, double E, double F);
+ /// Set the center.
+ void setCenter(Point const &p) { _center = p; }
+ /// Set the center by coordinates.
+ void setCenter(Coord cx, Coord cy) { _center[X] = cx; _center[Y] = cy; }
+ /// Set both rays of the ellipse.
+ void setRays(Point const &p) { _rays = p; }
+ /// Set both rays of the ellipse as coordinates.
+ void setRays(Coord x, Coord y) { _rays[X] = x; _rays[Y] = y; }
+ /// Set one of the rays of the ellipse.
+ void setRay(Coord r, Dim2 d) { _rays[d] = r; }
+ /// Set the angle the X ray makes with the +X axis.
+ void setRotationAngle(Angle a) { _angle = a; }
+
+ Point center() const { return _center; }
+ Coord center(Dim2 d) const { return _center[d]; }
+ /// Get both rays as a point.
+ Point rays() const { return _rays; }
+ /// Get one ray of the ellipse.
+ Coord ray(Dim2 d) const { return _rays[d]; }
+ /// Get the angle the X ray makes with the +X axis.
+ Angle rotationAngle() const { return _angle; }
+ /// Get the point corresponding to the +X ray of the ellipse.
+ Point initialPoint() const;
+ /// Get the point corresponding to the +X ray of the ellipse.
+ Point finalPoint() const { return initialPoint(); }
+
+ /** @brief Create an ellipse passing through the specified points
+ * At least five points have to be specified. */
+ void fit(std::vector<Point> const& points);
+
+ /** @brief Create an elliptical arc from a section of the ellipse.
+ * This is mainly useful to determine the flags of the new arc.
+ * The passed points should lie on the ellipse, otherwise the results
+ * will be undefined.
+ * @param ip Initial point of the arc
+ * @param inner Point in the middle of the arc, used to pick one of two possibilities
+ * @param fp Final point of the arc
+ * @return Newly allocated arc, delete when no longer used */
+ EllipticalArc *arc(Point const &ip, Point const &inner, Point const &fp);
+
+ /** @brief Return an ellipse with less degrees of freedom.
+ * The canonical form always has the angle less than \f$\frac{\pi}{2}\f$,
+ * and zero if the rays are equal (i.e. the ellipse is a circle). */
+ Ellipse canonicalForm() const;
+ void makeCanonical();
+
+ /** @brief Compute the transform that maps the unit circle to this ellipse.
+ * Each ellipse can be interpreted as a translated, scaled and rotate unit circle.
+ * This function returns the transform that maps the unit circle to this ellipse.
+ * @return Transform from unit circle to the ellipse */
+ Affine unitCircleTransform() const;
+ /** @brief Compute the transform that maps this ellipse to the unit circle.
+ * This may be a little more precise and/or faster than simply using
+ * unitCircleTransform().inverse(). An exception will be thrown for
+ * degenerate ellipses. */
+ Affine inverseUnitCircleTransform() const;
+
+ LineSegment majorAxis() const { return ray(X) >= ray(Y) ? axis(X) : axis(Y); }
+ LineSegment minorAxis() const { return ray(X) < ray(Y) ? axis(X) : axis(Y); }
+ LineSegment semimajorAxis(int sign = 1) const {
+ return ray(X) >= ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign);
+ }
+ LineSegment semiminorAxis(int sign = 1) const {
+ return ray(X) < ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign);
+ }
+ LineSegment axis(Dim2 d) const;
+ LineSegment semiaxis(Dim2 d, int sign = 1) const;
+
+ /// Get the tight-fitting bounding box of the ellipse.
+ Rect boundsExact() const;
+
+ /** @brief Get a fast to compute bounding box which contains the ellipse.
+ *
+ * The returned rectangle engulfs the ellipse but it may not be the smallest
+ * axis-aligned rectangle with this property.
+ */
+ Rect boundsFast() const;
+
+ /// Get the coefficients of the ellipse's implicit equation.
+ std::vector<double> coefficients() const;
+ void coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const;
+
+ /** @brief Evaluate a point on the ellipse.
+ * The parameter range is \f$[0, 2\pi)\f$; larger and smaller values
+ * wrap around. */
+ Point pointAt(Coord t) const;
+ /// Evaluate a single coordinate of a point on the ellipse.
+ Coord valueAt(Coord t, Dim2 d) const;
+
+ /** @brief Find the time value of a point on an ellipse.
+ * If the point is not on the ellipse, the returned time value will correspond
+ * to an intersection with a ray from the origin passing through the point
+ * with the ellipse. Note that this is NOT the nearest point on the ellipse. */
+ Coord timeAt(Point const &p) const;
+
+ /// Get the value of the derivative at time t normalized to unit length.
+ Point unitTangentAt(Coord t) const;
+
+ /// Check whether the ellipse contains the given point.
+ bool contains(Point const &p) const;
+
+ /// Compute intersections with an infinite line.
+ std::vector<ShapeIntersection> intersect(Line const &line) const;
+ /// Compute intersections with a line segment.
+ std::vector<ShapeIntersection> intersect(LineSegment const &seg) const;
+ /// Compute intersections with another ellipse.
+ std::vector<ShapeIntersection> intersect(Ellipse const &other) const;
+ /// Compute intersections with a 2D Bezier polynomial.
+ std::vector<ShapeIntersection> intersect(D2<Bezier> const &other) const;
+
+ Ellipse &operator*=(Translate const &t) {
+ _center *= t;
+ return *this;
+ }
+ Ellipse &operator*=(Scale const &s) {
+ _center *= s;
+ _rays *= s;
+ return *this;
+ }
+ Ellipse &operator*=(Zoom const &z) {
+ _center *= z;
+ _rays *= z.scale();
+ return *this;
+ }
+ Ellipse &operator*=(Rotate const &r);
+ Ellipse &operator*=(Affine const &m);
+
+ /// Compare ellipses for exact equality.
+ bool operator==(Ellipse const &other) const;
+};
+
+/** @brief Test whether two ellipses are approximately the same.
+ * This will check whether no point on ellipse a is further away from
+ * the corresponding point on ellipse b than precision.
+ * @relates Ellipse */
+bool are_near(Ellipse const &a, Ellipse const &b, Coord precision = EPSILON);
+
+/** @brief Outputs ellipse data, useful for debugging.
+ * @relates Ellipse */
+std::ostream &operator<<(std::ostream &out, Ellipse const &e);
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_ELLIPSE_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/include/2geom/elliptical-arc.h b/include/2geom/elliptical-arc.h
new file mode 100644
index 0000000..567e207
--- /dev/null
+++ b/include/2geom/elliptical-arc.h
@@ -0,0 +1,344 @@
+/**
+ * \file
+ * \brief Elliptical arc curve
+ *
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-2009 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 LIB2GEOM_SEEN_ELLIPTICAL_ARC_H
+#define LIB2GEOM_SEEN_ELLIPTICAL_ARC_H
+
+#include <algorithm>
+#include <2geom/affine.h>
+#include <2geom/angle.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/curve.h>
+#include <2geom/ellipse.h>
+#include <2geom/sbasis-curve.h> // for non-native methods
+#include <2geom/utils.h>
+
+namespace Geom
+{
+
+class EllipticalArc : public Curve
+{
+public:
+ /** @brief Creates an arc with all variables set to zero. */
+ EllipticalArc()
+ : _initial_point(0,0)
+ , _final_point(0,0)
+ , _large_arc(false)
+ {}
+ /** @brief Create a new elliptical arc.
+ * @param ip Initial point of the arc
+ * @param r Rays of the ellipse as a point
+ * @param rot Angle of rotation of the X axis of the ellipse in radians
+ * @param large If true, the large arc is chosen (always >= 180 degrees), otherwise
+ * the smaller arc is chosen
+ * @param sweep If true, the clockwise arc is chosen, otherwise the counter-clockwise
+ * arc is chosen
+ * @param fp Final point of the arc */
+ EllipticalArc( Point const &ip, Point const &r,
+ Coord rot_angle, bool large_arc, bool sweep,
+ Point const &fp
+ )
+ : _initial_point(ip)
+ , _final_point(fp)
+ , _ellipse(0, 0, r[X], r[Y], rot_angle)
+ , _angles(0, 0, sweep)
+ , _large_arc(large_arc)
+ {
+ _updateCenterAndAngles();
+ }
+
+ /// Create a new elliptical arc, giving the ellipse's rays as separate coordinates.
+ EllipticalArc( Point const &ip, Coord rx, Coord ry,
+ Coord rot_angle, bool large_arc, bool sweep,
+ Point const &fp
+ )
+ : _initial_point(ip)
+ , _final_point(fp)
+ , _ellipse(0, 0, rx, ry, rot_angle)
+ , _angles(0, 0, sweep)
+ , _large_arc(large_arc)
+ {
+ _updateCenterAndAngles();
+ }
+
+ /// @name Retrieve basic information
+ /// @{
+
+ /** @brief Get a coordinate of the elliptical arc's center.
+ * @param d The dimension to retrieve
+ * @return The selected coordinate of the center */
+ Coord center(Dim2 d) const { return _ellipse.center(d); }
+
+ /** @brief Get the arc's center
+ * @return The arc's center, situated on the intersection of the ellipse's rays */
+ Point center() const { return _ellipse.center(); }
+
+ /** @brief Get one of the ellipse's rays
+ * @param d Dimension to retrieve
+ * @return The selected ray of the ellipse */
+ Coord ray(Dim2 d) const { return _ellipse.ray(d); }
+
+ /** @brief Get both rays as a point
+ * @return Point with X equal to the X ray and Y to Y ray */
+ Point rays() const { return _ellipse.rays(); }
+
+ /** @brief Get the defining ellipse's rotation
+ * @return Angle between the +X ray of the ellipse and the +X axis */
+ Angle rotationAngle() const {
+ return _ellipse.rotationAngle();
+ }
+
+ /** @brief Whether the arc is larger than half an ellipse.
+ * @return True if the arc is larger than \f$\pi\f$, false otherwise */
+ bool largeArc() const { return _large_arc; }
+
+ /** @brief Whether the arc turns clockwise
+ * @return True if the arc makes a clockwise turn when going from initial to final
+ * point, false otherwise */
+ bool sweep() const { return _angles.sweep(); }
+
+ Angle initialAngle() const { return _angles.initialAngle(); }
+ Angle finalAngle() const { return _angles.finalAngle(); }
+ /// @}
+
+ /// @name Modify parameters
+ /// @{
+
+ /// Change all of the arc's parameters.
+ void set( Point const &ip, double rx, double ry,
+ double rot_angle, bool large_arc, bool sweep,
+ Point const &fp
+ )
+ {
+ _initial_point = ip;
+ _final_point = fp;
+ _ellipse.setRays(rx, ry);
+ _ellipse.setRotationAngle(rot_angle);
+ _angles.setSweep(sweep);
+ _large_arc = large_arc;
+ _updateCenterAndAngles();
+ }
+
+ /// Change all of the arc's parameters.
+ void set( Point const &ip, Point const &r,
+ Angle rot_angle, bool large_arc, bool sweep,
+ Point const &fp
+ )
+ {
+ _initial_point = ip;
+ _final_point = fp;
+ _ellipse.setRays(r);
+ _ellipse.setRotationAngle(rot_angle);
+ _angles.setSweep(sweep);
+ _large_arc = large_arc;
+ _updateCenterAndAngles();
+ }
+
+ /** @brief Change the initial and final point in one operation.
+ * This method exists because modifying any of the endpoints causes rather costly
+ * recalculations of the center and extreme angles.
+ * @param ip New initial point
+ * @param fp New final point */
+ void setEndpoints(Point const &ip, Point const &fp) {
+ _initial_point = ip;
+ _final_point = fp;
+ _updateCenterAndAngles();
+ }
+ /// @}
+
+ /// @name Evaluate the arc as a function
+ /// @{
+ /** Check whether the arc contains the given angle
+ * @param t The angle to check
+ * @return True if the arc contains the angle, false otherwise */
+ bool containsAngle(Angle angle) const { return _angles.contains(angle); }
+
+ /** @brief Evaluate the arc at the specified angular coordinate
+ * @param t Angle
+ * @return Point corresponding to the given angle */
+ Point pointAtAngle(Coord t) const;
+
+ /** @brief Evaluate one of the arc's coordinates at the specified angle
+ * @param t Angle
+ * @param d The dimension to retrieve
+ * @return Selected coordinate of the arc at the specified angle */
+ Coord valueAtAngle(Coord t, Dim2 d) const;
+
+ /// Compute the curve time value corresponding to the given angular value.
+ Coord timeAtAngle(Angle a) const { return _angles.timeAtAngle(a); }
+
+ /// Compute the angular domain value corresponding to the given time value.
+ Angle angleAt(Coord t) const { return _angles.angleAt(t); }
+
+ /** @brief Compute the amount by which the angle parameter changes going from start to end.
+ * This has range \f$(-2\pi, 2\pi)\f$ and thus cannot be represented as instance
+ * of the class Angle. Add this to the initial angle to obtain the final angle. */
+ Coord sweepAngle() const { return _angles.sweepAngle(); }
+
+ /** @brief Get the elliptical angle spanned by the arc.
+ * This is basically the absolute value of sweepAngle(). */
+ Coord angularExtent() const { return _angles.extent(); }
+
+ /// Get the angular interval of the arc.
+ AngleInterval angularInterval() const { return _angles; }
+
+ /// Evaluate the arc in the curve domain, i.e. \f$[0, 1]\f$.
+ Point pointAt(Coord t) const override;
+
+ /// Evaluate a single coordinate on the arc in the curve domain.
+ Coord valueAt(Coord t, Dim2 d) const override;
+
+ /** @brief Compute a transform that maps the unit circle to the arc's ellipse.
+ * Each ellipse can be interpreted as a translated, scaled and rotate unit circle.
+ * This function returns the transform that maps the unit circle to the arc's ellipse.
+ * @return Transform from unit circle to the arc's ellipse */
+ Affine unitCircleTransform() const {
+ Affine result = _ellipse.unitCircleTransform();
+ return result;
+ }
+
+ /** @brief Compute a transform that maps the arc's ellipse to the unit circle. */
+ Affine inverseUnitCircleTransform() const {
+ Affine result = _ellipse.inverseUnitCircleTransform();
+ return result;
+ }
+ /// @}
+
+ /// @name Deal with degenerate ellipses.
+ /// @{
+ /** @brief Check whether both rays are nonzero.
+ * If they are not, the arc is represented as a line segment instead. */
+ bool isChord() const {
+ return ray(X) == 0 || ray(Y) == 0;
+ }
+
+ /** @brief Get the line segment connecting the arc's endpoints.
+ * @return A linear segment with initial and final point corresponding to those of the arc. */
+ LineSegment chord() const { return LineSegment(_initial_point, _final_point); }
+ /// @}
+
+ // implementation of overloads goes here
+ Point initialPoint() const override { return _initial_point; }
+ Point finalPoint() const override { return _final_point; }
+ Curve* duplicate() const override { return new EllipticalArc(*this); }
+ void setInitial(Point const &p) override {
+ _initial_point = p;
+ _updateCenterAndAngles();
+ }
+ void setFinal(Point const &p) override {
+ _final_point = p;
+ _updateCenterAndAngles();
+ }
+ bool isDegenerate() const override {
+ return _initial_point == _final_point;
+ }
+ bool isLineSegment() const override { return isChord(); }
+ Rect boundsFast() const override {
+ return boundsExact();
+ }
+ Rect boundsExact() const override;
+ void expandToTransformed(Rect &bbox, Affine const &transform) const override;
+ // TODO: native implementation of the following methods
+ OptRect boundsLocal(OptInterval const &i, unsigned int deg) const override {
+ return SBasisCurve(toSBasis()).boundsLocal(i, deg);
+ }
+ std::vector<double> roots(double v, Dim2 d) const override;
+#ifdef HAVE_GSL
+ std::vector<double> allNearestTimes( Point const& p, double from = 0, double to = 1 ) const override;
+ double nearestTime( Point const& p, double from = 0, double to = 1 ) const override {
+ if ( are_near(ray(X), ray(Y)) && are_near(center(), p) ) {
+ return from;
+ }
+ return allNearestTimes(p, from, to).front();
+ }
+#endif
+ std::vector<CurveIntersection> intersect(Curve const &other, Coord eps=EPSILON) const override;
+ int degreesOfFreedom() const override { return 7; }
+ Curve *derivative() const override;
+
+ using Curve::operator*=;
+ void operator*=(Translate const &tr) override;
+ void operator*=(Scale const &s) override;
+ void operator*=(Rotate const &r) override;
+ void operator*=(Zoom const &z) override;
+ void operator*=(Affine const &m) override;
+
+ std::vector<Point> pointAndDerivatives(Coord t, unsigned int n) const override;
+ D2<SBasis> toSBasis() const override;
+ Curve *portion(double f, double t) const override;
+ Curve *reverse() const override;
+ bool operator==(Curve const &c) const override;
+ bool isNear(Curve const &other, Coord precision) const override;
+ void feed(PathSink &sink, bool moveto_initial) const override;
+ int winding(Point const &p) const override;
+
+private:
+ void _updateCenterAndAngles();
+ std::vector<ShapeIntersection> _filterIntersections(std::vector<ShapeIntersection> &&xs, bool is_first) const;
+ bool _validateIntersection(ShapeIntersection &xing, bool is_first) const;
+ std::vector<ShapeIntersection> _intersectSameEllipse(EllipticalArc const *other) const;
+
+ Point _initial_point, _final_point;
+ Ellipse _ellipse;
+ AngleInterval _angles;
+ bool _large_arc;
+}; // end class EllipticalArc
+
+
+// implemented in elliptical-arc-from-sbasis.cpp
+/** @brief Fit an elliptical arc to an SBasis fragment.
+ * @relates EllipticalArc */
+bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in,
+ double tolerance = EPSILON, unsigned num_samples = 20);
+
+/** @brief Debug output for elliptical arcs.
+ * @relates EllipticalArc */
+std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea);
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_ELLIPTICAL_ARC_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/include/2geom/exception.h b/include/2geom/exception.h
new file mode 100644
index 0000000..b472aae
--- /dev/null
+++ b/include/2geom/exception.h
@@ -0,0 +1,157 @@
+/**
+ * \file
+ * \brief Defines the different types of exceptions that 2geom can throw.
+ *
+ * There are two main exception classes: LogicalError and RangeError.
+ * Logical errors are 2geom faults/bugs; RangeErrors are 'user' faults,
+ * e.g. invalid arguments to lib2geom methods.
+ * This way, the 'user' can distinguish between groups of exceptions
+ * ('user' is the coder that uses lib2geom)
+ *
+ * Several macro's are defined for easily throwing exceptions
+ * (e.g. THROW_CONTINUITYERROR).
+ */
+/* Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * 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 LIB2GEOM_SEEN_EXCEPTION_H
+#define LIB2GEOM_SEEN_EXCEPTION_H
+
+#include <exception>
+#include <sstream>
+#include <string>
+
+namespace Geom {
+
+/**
+ * Base exception class, all 2geom exceptions should be derived from this one.
+ */
+class Exception : public std::exception {
+public:
+ Exception(const char * message, const char *file, const int line) {
+ std::ostringstream os;
+ os << "lib2geom exception: " << message << " (" << file << ":" << line << ")";
+ msgstr = os.str();
+ }
+
+ ~Exception() noexcept override {} // necessary to destroy the string object!!!
+
+ const char* what() const noexcept override {
+ return msgstr.c_str();
+ }
+protected:
+ std::string msgstr;
+};
+#define THROW_EXCEPTION(message) throw(Geom::Exception(message, __FILE__, __LINE__))
+
+//-----------------------------------------------------------------------
+
+class LogicalError : public Exception {
+public:
+ LogicalError(const char * message, const char *file, const int line)
+ : Exception(message, file, line) {}
+};
+#define THROW_LOGICALERROR(message) throw(LogicalError(message, __FILE__, __LINE__))
+
+class RangeError : public Exception {
+public:
+ RangeError(const char * message, const char *file, const int line)
+ : Exception(message, file, line) {}
+};
+#define THROW_RANGEERROR(message) throw(RangeError(message, __FILE__, __LINE__))
+
+//-----------------------------------------------------------------------
+// Special case exceptions. Best used with the defines :)
+
+class NotImplemented : public LogicalError {
+public:
+ NotImplemented(const char *file, const int line)
+ : LogicalError("Method not implemented", file, line) {}
+};
+#define THROW_NOTIMPLEMENTED(i) throw(NotImplemented(__FILE__, __LINE__))
+
+class InvariantsViolation : public LogicalError {
+public:
+ InvariantsViolation(const char *file, const int line)
+ : LogicalError("Invariants violation", file, line) {}
+};
+#define THROW_INVARIANTSVIOLATION(i) throw(InvariantsViolation(__FILE__, __LINE__))
+#define ASSERT_INVARIANTS(e) ((e) ? (void)0 : THROW_INVARIANTSVIOLATION())
+
+class NotInvertible : public RangeError {
+public:
+ NotInvertible(const char *file, const int line)
+ : RangeError("Function does not have a unique inverse", file, line) {}
+};
+#define THROW_NOTINVERTIBLE(i) throw(NotInvertible(__FILE__, __LINE__))
+
+class InfiniteSolutions : public RangeError {
+public:
+ InfiniteSolutions(const char *file, const int line)
+ : RangeError("There are infinite solutions", file, line) {}
+};
+#define THROW_INFINITESOLUTIONS(i) throw(InfiniteSolutions(__FILE__, __LINE__))
+
+class InfinitelyManySolutions : public RangeError {
+private:
+ char const *const _message;
+public:
+ InfinitelyManySolutions(const char *file, const int line, char const *message)
+ : RangeError("There are infinitely many solutions", file, line)
+ , _message{message}
+ {}
+ char const *what() const noexcept override { return _message; }
+};
+#define THROW_INFINITELY_MANY_SOLUTIONS(msg) throw(InfinitelyManySolutions(__FILE__, __LINE__, msg))
+
+class ContinuityError : public RangeError {
+public:
+ ContinuityError(const char *file, const int line)
+ : RangeError("Non-contiguous path", file, line) {}
+};
+#define THROW_CONTINUITYERROR(i) throw(ContinuityError(__FILE__, __LINE__))
+
+struct SVGPathParseError : public std::exception {
+ char const *what() const noexcept override { return "parse error"; }
+};
+
+
+} // namespace Geom
+
+#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/include/2geom/forward.h b/include/2geom/forward.h
new file mode 100644
index 0000000..2790924
--- /dev/null
+++ b/include/2geom/forward.h
@@ -0,0 +1,127 @@
+/**
+ * \file
+ * \brief Contains forward declarations of 2geom types
+ *//*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2008-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_FORWARD_H
+#define LIB2GEOM_SEEN_FORWARD_H
+
+namespace Geom {
+
+// primitives
+typedef double Coord;
+typedef int IntCoord;
+class Point;
+class IntPoint;
+class Line;
+class Ray;
+template <typename> class GenericInterval;
+template <typename> class GenericOptInterval;
+class Interval;
+class OptInterval;
+typedef GenericInterval<IntCoord> IntInterval;
+typedef GenericOptInterval<IntCoord> OptIntInterval;
+template <typename> class GenericRect;
+template <typename> class GenericOptRect;
+class Rect;
+class OptRect;
+typedef GenericRect<IntCoord> IntRect;
+typedef GenericOptRect<IntCoord> OptIntRect;
+
+// fragments
+class Linear;
+class Bezier;
+class SBasis;
+class Poly;
+
+// shapes
+class Circle;
+class Ellipse;
+class ConvexHull;
+
+// curves
+class Curve;
+class SBasisCurve;
+class BezierCurve;
+template <unsigned degree> class BezierCurveN;
+typedef BezierCurveN<1> LineSegment;
+typedef BezierCurveN<2> QuadraticBezier;
+typedef BezierCurveN<3> CubicBezier;
+class EllipticalArc;
+
+// paths and path sequences
+class Path;
+class PathVector;
+struct PathTime;
+class PathInterval;
+struct PathVectorTime;
+
+// errors
+class Exception;
+class LogicalError;
+class RangeError;
+class NotImplemented;
+class InvariantsViolation;
+class NotInvertible;
+class ContinuityError;
+
+// transforms
+class Affine;
+class Translate;
+class Rotate;
+class Scale;
+class HShear;
+class VShear;
+class Zoom;
+
+// templates
+template <typename> class D2;
+template <typename> class Piecewise;
+
+// misc
+class SVGPathSink;
+template <typename> class SVGPathGenerator;
+
+}
+
+#endif // SEEN_GEOM_FORWARD_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/include/2geom/generic-interval.h b/include/2geom/generic-interval.h
new file mode 100644
index 0000000..1d3cfdb
--- /dev/null
+++ b/include/2geom/generic-interval.h
@@ -0,0 +1,374 @@
+/**
+ * @file
+ * @brief Closed interval of generic values
+ *//*
+ * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_GENERIC_INTERVAL_H
+#define LIB2GEOM_SEEN_GENERIC_INTERVAL_H
+
+#include <cassert>
+#include <iostream>
+#include <optional>
+#include <2geom/coord.h>
+
+namespace Geom {
+
+template <typename C>
+class GenericOptInterval;
+
+/**
+ * @brief A range of numbers which is never empty.
+ * @ingroup Primitives
+ */
+template <typename C>
+class GenericInterval
+ : CoordTraits<C>::IntervalOps
+{
+ typedef typename CoordTraits<C>::IntervalType CInterval;
+ typedef GenericInterval<C> Self;
+protected:
+ C _b[2];
+public:
+ /// @name Create intervals.
+ /// @{
+ /** @brief Create an interval that contains only zero. */
+ GenericInterval() { _b[0] = 0; _b[1] = 0; }
+ /** @brief Create an interval that contains a single point. */
+ explicit GenericInterval(C u) { _b[0] = _b[1] = u; }
+ /** @brief Create an interval that contains all points between @c u and @c v. */
+ GenericInterval(C u, C v) {
+ if (u <= v) {
+ _b[0] = u; _b[1] = v;
+ } else {
+ _b[0] = v; _b[1] = u;
+ }
+ }
+
+ /** @brief Create an interval containing a range of values.
+ * The resulting interval will contain all values from the given range.
+ * The return type of iterators must be convertible to C. The given range
+ * must not be empty. For potentially empty ranges, see GenericOptInterval.
+ * @param start Beginning of the range
+ * @param end End of the range
+ * @return Interval that contains all values from [start, end). */
+ template <typename InputIterator>
+ static CInterval from_range(InputIterator start, InputIterator end) {
+ assert(start != end);
+ CInterval result(*start++);
+ for (; start != end; ++start) result.expandTo(*start);
+ return result;
+ }
+ /** @brief Create an interval from a C-style array of values it should contain. */
+ static CInterval from_array(C const *c, unsigned n) {
+ CInterval result = from_range(c, c+n);
+ return result;
+ }
+ /// @}
+
+ /// @name Inspect contained values.
+ /// @{
+ C min() const { return _b[0]; }
+ C max() const { return _b[1]; }
+ C extent() const { return max() - min(); }
+ C middle() const { return (max() + min()) / 2; }
+ bool isSingular() const { return min() == max(); }
+ C operator[](unsigned i) const { assert(i < 2); return _b[i]; }
+ C clamp(C val) const {
+ if (val < min()) return min();
+ if (val > max()) return max();
+ return val;
+ }
+ /// Return the closer end of the interval.
+ C nearestEnd(C val) const {
+ C dmin = std::abs(val - min()), dmax = std::abs(val - max());
+ return dmin <= dmax ? min() : max();
+ }
+ /// @}
+
+ /// @name Test coordinates and other intervals for inclusion.
+ /// @{
+ /** @brief Check whether the interval includes this number. */
+ bool contains(C val) const {
+ return min() <= val && val <= max();
+ }
+ /** @brief Check whether the interval includes the given interval. */
+ bool contains(CInterval const &val) const {
+ return min() <= val.min() && val.max() <= max();
+ }
+ /** @brief Check whether the intervals have any common elements. */
+ bool intersects(CInterval const &val) const {
+ return contains(val.min()) || contains(val.max()) || val.contains(*this);
+ }
+ /// @}
+
+ /// @name Modify the interval.
+ /// @{
+ //TODO: NaN handleage for the next two?
+ /** @brief Set the lower boundary of the interval.
+ * When the given number is larger than the interval's largest element,
+ * it will be reduced to the single number @c val. */
+ void setMin(C val) {
+ if(val > _b[1]) {
+ _b[0] = _b[1] = val;
+ } else {
+ _b[0] = val;
+ }
+ }
+ /** @brief Set the upper boundary of the interval.
+ * When the given number is smaller than the interval's smallest element,
+ * it will be reduced to the single number @c val. */
+ void setMax(C val) {
+ if(val < _b[0]) {
+ _b[1] = _b[0] = val;
+ } else {
+ _b[1] = val;
+ }
+ }
+ /// Set both ends of the interval simultaneously
+ void setEnds(C a, C b) {
+ if (a <= b) {
+ _b[0] = a;
+ _b[1] = b;
+ } else {
+ _b[0] = b;
+ _b[1] = a;
+ }
+ }
+ /** @brief Extend the interval to include the given number. */
+ void expandTo(C val) {
+ if(val < _b[0]) _b[0] = val;
+ if(val > _b[1]) _b[1] = val; //no else, as we want to handle NaN
+ }
+ /** @brief Expand or shrink the interval in both directions by the given amount.
+ * After this method, the interval's length (extent) will be increased by
+ * <code>amount * 2</code>. Negative values can be given; they will shrink the interval.
+ * Shrinking by a value larger than half the interval's length will create a degenerate
+ * interval containing only the midpoint of the original. */
+ void expandBy(C amount) {
+ _b[0] -= amount;
+ _b[1] += amount;
+ if (_b[0] > _b[1]) {
+ C halfway = (_b[0]+_b[1])/2;
+ _b[0] = _b[1] = halfway;
+ }
+ }
+ /** @brief Union the interval with another one.
+ * The resulting interval will contain all points of both intervals.
+ * It might also contain some points which didn't belong to either - this happens
+ * when the intervals did not have any common elements. */
+ void unionWith(CInterval const &a) {
+ if(a._b[0] < _b[0]) _b[0] = a._b[0];
+ if(a._b[1] > _b[1]) _b[1] = a._b[1];
+ }
+ /// @}
+
+ /// @name Operators
+ /// @{
+ //IMPL: OffsetableConcept
+ //TODO: rename output_type to something else in the concept
+ typedef C output_type;
+ /** @brief Offset the interval by a specified amount */
+ Self &operator+=(C amnt) {
+ _b[0] += amnt; _b[1] += amnt;
+ return *this;
+ }
+ /** @brief Offset the interval by the negation of the specified amount */
+ Self &operator-=(C amnt) {
+ _b[0] -= amnt; _b[1] -= amnt;
+ return *this;
+ }
+
+ /** @brief Return an interval mirrored about 0 */
+ Self operator-() const { Self r(-_b[1], -_b[0]); return r; }
+ // IMPL: AddableConcept
+ /** @brief Add two intervals.
+ * Sum is defined as the set of points that can be obtained by adding any two values
+ * from both operands: \f$S = \{x \in A, y \in B: x + y\}\f$ */
+ Self &operator+=(CInterval const &o) {
+ _b[0] += o._b[0];
+ _b[1] += o._b[1];
+ return *this;
+ }
+ /** @brief Subtract two intervals.
+ * Difference is defined as the set of points that can be obtained by subtracting
+ * any value from the second operand from any value from the first operand:
+ * \f$S = \{x \in A, y \in B: x - y\}\f$ */
+ Self &operator-=(CInterval const &o) {
+ // equal to *this += -o
+ _b[0] -= o._b[1];
+ _b[1] -= o._b[0];
+ return *this;
+ }
+ /** @brief Union two intervals.
+ * Note that the intersection-and-assignment operator is not defined,
+ * because the result of an intersection can be empty, while Interval cannot. */
+ Self &operator|=(CInterval const &o) {
+ unionWith(o);
+ return *this;
+ }
+ /** @brief Test for interval equality. */
+ bool operator==(CInterval const &other) const {
+ return min() == other.min() && max() == other.max();
+ }
+ /// @}
+};
+
+/** @brief Union two intervals
+ * @relates GenericInterval */
+template <typename C>
+inline GenericInterval<C> unify(GenericInterval<C> const &a, GenericInterval<C> const &b) {
+ return a | b;
+}
+
+/**
+ * @brief A range of numbers that can be empty.
+ * @ingroup Primitives
+ */
+template <typename C>
+class GenericOptInterval
+ : public std::optional<typename CoordTraits<C>::IntervalType>
+ , boost::orable< GenericOptInterval<C>
+ , boost::andable< GenericOptInterval<C>
+ > >
+{
+ typedef typename CoordTraits<C>::IntervalType CInterval;
+ typedef typename CoordTraits<C>::OptIntervalType OptCInterval;
+ typedef std::optional<CInterval> Base;
+public:
+ /// @name Create optionally empty intervals.
+ /// @{
+ /** @brief Create an empty interval. */
+ GenericOptInterval() : Base() {}
+ /** @brief Wrap an existing interval. */
+ GenericOptInterval(GenericInterval<C> const &a) : Base(CInterval(a)) {}
+ /** @brief Create an interval containing a single point. */
+ GenericOptInterval(C u) : Base(CInterval(u)) {}
+ /** @brief Create an interval containing a range of numbers. */
+ GenericOptInterval(C u, C v) : Base(CInterval(u,v)) {}
+
+ /** @brief Create a possibly empty interval containing a range of values.
+ * The resulting interval will contain all values from the given range.
+ * The return type of iterators must be convertible to C. The given range
+ * may be empty.
+ * @param start Beginning of the range
+ * @param end End of the range
+ * @return Interval that contains all values from [start, end), or nothing if the range
+ * is empty. */
+ template <typename InputIterator>
+ static GenericOptInterval<C> from_range(InputIterator start, InputIterator end) {
+ if (start == end) {
+ GenericOptInterval<C> ret;
+ return ret;
+ }
+ GenericOptInterval<C> ret(CInterval::from_range(start, end));
+ return ret;
+ }
+ /// @}
+
+ /** @brief Check whether this interval is empty. */
+ bool empty() { return !*this; }
+
+ /** @brief Union with another interval, gracefully handling empty ones. */
+ void unionWith(GenericOptInterval<C> const &a) {
+ if (a) {
+ if (*this) { // check that we are not empty
+ (*this)->unionWith(*a);
+ } else {
+ *this = *a;
+ }
+ }
+ }
+ void intersectWith(GenericOptInterval<C> const &o) {
+ if (o && *this) {
+ if (!*this) return;
+ C u = std::max((*this)->min(), o->min());
+ C v = std::min((*this)->max(), o->max());
+ if (u <= v) {
+ *this = CInterval(u, v);
+ return;
+ }
+ }
+ (*static_cast<Base*>(this)) = std::nullopt;
+ }
+ GenericOptInterval<C> &operator|=(OptCInterval const &o) {
+ unionWith(o);
+ return *this;
+ }
+ GenericOptInterval<C> &operator&=(OptCInterval const &o) {
+ intersectWith(o);
+ return *this;
+ }
+
+ // The equality operators inherited from std::optional don't work with derived types, because
+ // the template overload ignores that the devived type is also an optional. It would result in
+ // `GenericInterval() != GenericInterval()` being true.
+ template <typename U, typename = std::enable_if_t<std::is_base_of_v<Base, U>>>
+ bool operator==(U const &other) const
+ {
+ return static_cast<Base const &>(*this) == static_cast<Base const &>(other);
+ }
+ template <typename U, typename = std::enable_if_t<std::is_base_of_v<Base, U>>>
+ bool operator!=(U const &other) const
+ {
+ return static_cast<Base const &>(*this) != static_cast<Base const &>(other);
+ }
+};
+
+/** @brief Intersect two intervals and return a possibly empty range of numbers
+ * @relates GenericOptInterval */
+template <typename C>
+inline GenericOptInterval<C> intersect(GenericInterval<C> const &a, GenericInterval<C> const &b) {
+ return GenericOptInterval<C>(a) & GenericOptInterval<C>(b);
+}
+/** @brief Intersect two intervals and return a possibly empty range of numbers
+ * @relates GenericOptInterval */
+template <typename C>
+inline GenericOptInterval<C> operator&(GenericInterval<C> const &a, GenericInterval<C> const &b) {
+ return GenericOptInterval<C>(a) & GenericOptInterval<C>(b);
+}
+
+template <typename C>
+inline std::ostream &operator<< (std::ostream &os,
+ Geom::GenericInterval<C> const &I) {
+ os << "Interval("<<I.min() << ", "<<I.max() << ")";
+ return os;
+}
+
+} // namespace Geom
+#endif // !LIB2GEOM_SEEN_GENERIC_INTERVAL_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/include/2geom/generic-rect.h b/include/2geom/generic-rect.h
new file mode 100644
index 0000000..4524d43
--- /dev/null
+++ b/include/2geom/generic-rect.h
@@ -0,0 +1,547 @@
+/**
+ * \file
+ * \brief Axis-aligned rectangle
+ *//*
+ * Authors:
+ * Michael Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2007-2011 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, output 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.
+ *
+ * Authors of original rect class:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ */
+
+#ifndef LIB2GEOM_SEEN_GENERIC_RECT_H
+#define LIB2GEOM_SEEN_GENERIC_RECT_H
+
+#include <limits>
+#include <iostream>
+#include <optional>
+#include <2geom/coord.h>
+
+namespace Geom {
+
+template <typename C>
+class GenericOptRect;
+
+/**
+ * @brief Axis aligned, non-empty, generic rectangle.
+ * @ingroup Primitives
+ */
+template <typename C>
+class GenericRect
+ : CoordTraits<C>::RectOps
+{
+ typedef typename CoordTraits<C>::IntervalType CInterval;
+ typedef typename CoordTraits<C>::PointType CPoint;
+ typedef typename CoordTraits<C>::RectType CRect;
+ typedef typename CoordTraits<C>::OptRectType OptCRect;
+protected:
+ CInterval f[2];
+public:
+ typedef CInterval D1Value;
+ typedef CInterval &D1Reference;
+ typedef CInterval const &D1ConstReference;
+
+ /// @name Create rectangles.
+ /// @{
+ /** @brief Create a rectangle that contains only the point at (0,0). */
+ GenericRect() { f[X] = f[Y] = CInterval(); }
+ /** @brief Create a rectangle from X and Y intervals. */
+ GenericRect(CInterval const &a, CInterval const &b) {
+ f[X] = a;
+ f[Y] = b;
+ }
+ /** @brief Create a rectangle from two points. */
+ GenericRect(CPoint const &a, CPoint const &b) {
+ f[X] = CInterval(a[X], b[X]);
+ f[Y] = CInterval(a[Y], b[Y]);
+ }
+ /** @brief Create rectangle from coordinates of two points. */
+ GenericRect(C x0, C y0, C x1, C y1) {
+ f[X] = CInterval(x0, x1);
+ f[Y] = CInterval(y0, y1);
+ }
+ /** @brief Create a rectangle from a range of points.
+ * The resulting rectangle will contain all points from the range.
+ * The return type of iterators must be convertible to Point.
+ * The range must not be empty. For possibly empty ranges, see OptRect.
+ * @param start Beginning of the range
+ * @param end End of the range
+ * @return Rectangle that contains all points from [start, end). */
+ template <typename InputIterator>
+ static CRect from_range(InputIterator start, InputIterator end) {
+ assert(start != end);
+ CPoint p1 = *start++;
+ CRect result(p1, p1);
+ for (; start != end; ++start) {
+ result.expandTo(*start);
+ }
+ return result;
+ }
+ /** @brief Create a rectangle from a C-style array of points it should contain. */
+ static CRect from_array(CPoint const *c, unsigned n) {
+ CRect result = GenericRect<C>::from_range(c, c+n);
+ return result;
+ }
+ /** @brief Create rectangle from origin and dimensions. */
+ static CRect from_xywh(C x, C y, C w, C h) {
+ CPoint xy(x, y);
+ CPoint wh(w, h);
+ CRect result(xy, xy + wh);
+ return result;
+ }
+ /** @brief Create rectangle from origin and dimensions. */
+ static CRect from_xywh(CPoint const &xy, CPoint const &wh) {
+ CRect result(xy, xy + wh);
+ return result;
+ }
+ /// Create infinite rectangle.
+ static CRect infinite() {
+ CPoint p0(std::numeric_limits<C>::min(), std::numeric_limits<C>::min());
+ CPoint p1(std::numeric_limits<C>::max(), std::numeric_limits<C>::max());
+ CRect result(p0, p1);
+ return result;
+ }
+ /// @}
+
+ /// @name Inspect dimensions.
+ /// @{
+ CInterval &operator[](unsigned i) { return f[i]; }
+ CInterval const &operator[](unsigned i) const { return f[i]; }
+ CInterval &operator[](Dim2 d) { return f[d]; }
+ CInterval const &operator[](Dim2 d) const { return f[d]; }
+
+ /** @brief Get the corner of the rectangle with smallest coordinate values.
+ * In 2Geom standard coordinate system, this means upper left. */
+ CPoint min() const { CPoint p(f[X].min(), f[Y].min()); return p; }
+ /** @brief Get the corner of the rectangle with largest coordinate values.
+ * In 2Geom standard coordinate system, this means lower right. */
+ CPoint max() const { CPoint p(f[X].max(), f[Y].max()); return p; }
+ /** @brief Return the n-th corner of the rectangle.
+ * Returns corners in the direction of growing angles, starting from
+ * the one given by min(). For the standard coordinate system used
+ * in 2Geom (+Y downwards), this means clockwise starting from
+ * the upper left. */
+ CPoint corner(unsigned i) const {
+ switch(i % 4) {
+ case 0: return CPoint(f[X].min(), f[Y].min());
+ case 1: return CPoint(f[X].max(), f[Y].min());
+ case 2: return CPoint(f[X].max(), f[Y].max());
+ default: return CPoint(f[X].min(), f[Y].max());
+ }
+ }
+
+ //We should probably remove these - they're coord sys gnostic
+ /** @brief Return top coordinate of the rectangle (+Y is downwards). */
+ C top() const { return f[Y].min(); }
+ /** @brief Return bottom coordinate of the rectangle (+Y is downwards). */
+ C bottom() const { return f[Y].max(); }
+ /** @brief Return leftmost coordinate of the rectangle (+X is to the right). */
+ C left() const { return f[X].min(); }
+ /** @brief Return rightmost coordinate of the rectangle (+X is to the right). */
+ C right() const { return f[X].max(); }
+
+ /** @brief Get the horizontal extent of the rectangle. */
+ C width() const { return f[X].extent(); }
+ /** @brief Get the vertical extent of the rectangle. */
+ C height() const { return f[Y].extent(); }
+ /** @brief Get the ratio of width to height of the rectangle. */
+ Coord aspectRatio() const { return Coord(width()) / Coord(height()); }
+
+ /** @brief Get rectangle's width and height as a point.
+ * @return Point with X coordinate corresponding to the width and the Y coordinate
+ * corresponding to the height of the rectangle. */
+ CPoint dimensions() const { return CPoint(f[X].extent(), f[Y].extent()); }
+ /** @brief Get the point in the geometric center of the rectangle. */
+ CPoint midpoint() const { return CPoint(f[X].middle(), f[Y].middle()); }
+
+ /** @brief Compute rectangle's area. */
+ C area() const { return f[X].extent() * f[Y].extent(); }
+ /** @brief Check whether the rectangle has zero area. */
+ bool hasZeroArea() const { return f[X].isSingular() || f[Y].isSingular(); }
+
+ /** @brief Get the larger extent (width or height) of the rectangle. */
+ C maxExtent() const { return std::max(f[X].extent(), f[Y].extent()); }
+ /** @brief Get the smaller extent (width or height) of the rectangle. */
+ C minExtent() const { return std::min(f[X].extent(), f[Y].extent()); }
+
+ /** @brief Get rectangle's distance SQUARED away from the given point **/
+ C distanceSq(const CPoint pt) const {
+ auto v = clamp(pt) - pt;
+ return v.x() * v.x() + v.y() * v.y();
+ }
+
+ /** @brief Clamp point to the rectangle. */
+ CPoint clamp(CPoint const &p) const {
+ CPoint result(f[X].clamp(p[X]), f[Y].clamp(p[Y]));
+ return result;
+ }
+ /** @brief Get the nearest point on the edge of the rectangle. */
+ CPoint nearestEdgePoint(CPoint const &p) const {
+ CPoint result = p;
+ if (!contains(p)) {
+ result = clamp(p);
+ } else {
+ C cx = f[X].nearestEnd(p[X]);
+ C cy = f[Y].nearestEnd(p[Y]);
+ if (std::abs(cx - p[X]) <= std::abs(cy - p[Y])) {
+ result[X] = cx;
+ } else {
+ result[Y] = cy;
+ }
+ }
+ return result;
+ }
+ /// @}
+
+ /// @name Test other rectangles and points for inclusion.
+ /// @{
+ /** @brief Check whether the rectangles have any common points. */
+ bool intersects(GenericRect<C> const &r) const {
+ return f[X].intersects(r[X]) && f[Y].intersects(r[Y]);
+ }
+ /** @brief Check whether the rectangle includes all points in the given rectangle. */
+ bool contains(GenericRect<C> const &r) const {
+ return f[X].contains(r[X]) && f[Y].contains(r[Y]);
+ }
+
+ /** @brief Check whether the rectangles have any common points.
+ * Empty rectangles will not intersect with any other rectangle. */
+ inline bool intersects(OptCRect const &r) const;
+ /** @brief Check whether the rectangle includes all points in the given rectangle.
+ * Empty rectangles will be contained in any non-empty rectangle. */
+ inline bool contains(OptCRect const &r) const;
+
+ /** @brief Check whether the given point is within the rectangle. */
+ bool contains(CPoint const &p) const {
+ return f[X].contains(p[X]) && f[Y].contains(p[Y]);
+ }
+ /// @}
+
+ /// @name Modify the rectangle.
+ /// @{
+ /** @brief Set the minimum X coordinate of the rectangle. */
+ void setLeft(C val) {
+ f[X].setMin(val);
+ }
+ /** @brief Set the maximum X coordinate of the rectangle. */
+ void setRight(C val) {
+ f[X].setMax(val);
+ }
+ /** @brief Set the minimum Y coordinate of the rectangle. */
+ void setTop(C val) {
+ f[Y].setMin(val);
+ }
+ /** @brief Set the maximum Y coordinate of the rectangle. */
+ void setBottom(C val) {
+ f[Y].setMax(val);
+ }
+ /** @brief Set the upper left point of the rectangle. */
+ void setMin(CPoint const &p) {
+ f[X].setMin(p[X]);
+ f[Y].setMin(p[Y]);
+ }
+ /** @brief Set the lower right point of the rectangle. */
+ void setMax(CPoint const &p) {
+ f[X].setMax(p[X]);
+ f[Y].setMax(p[Y]);
+ }
+ /** @brief Enlarge the rectangle to contain the given point. */
+ void expandTo(CPoint const &p) {
+ f[X].expandTo(p[X]); f[Y].expandTo(p[Y]);
+ }
+ /** @brief Enlarge the rectangle to contain the argument. */
+ void unionWith(CRect const &b) {
+ f[X].unionWith(b[X]); f[Y].unionWith(b[Y]);
+ }
+ /** @brief Enlarge the rectangle to contain the argument.
+ * Unioning with an empty rectangle results in no changes. */
+ void unionWith(OptCRect const &b);
+
+ /** @brief Expand the rectangle in both directions by the specified amount.
+ * Note that this is different from scaling. Negative values will shrink the
+ * rectangle. If <code>-amount</code> is larger than
+ * half of the width, the X interval will contain only the X coordinate
+ * of the midpoint; same for height. */
+ void expandBy(C amount) {
+ expandBy(amount, amount);
+ }
+ /** @brief Expand the rectangle in both directions.
+ * Note that this is different from scaling. Negative values will shrink the
+ * rectangle. If <code>-x</code> is larger than
+ * half of the width, the X interval will contain only the X coordinate
+ * of the midpoint; same for height. */
+ void expandBy(C x, C y) {
+ f[X].expandBy(x); f[Y].expandBy(y);
+ }
+ /** @brief Expand the rectangle by the coordinates of the given point.
+ * This will expand the width by the X coordinate of the point in both directions
+ * and the height by Y coordinate of the point. Negative coordinate values will
+ * shrink the rectangle. If <code>-p[X]</code> is larger than half of the width,
+ * the X interval will contain only the X coordinate of the midpoint;
+ * same for height. */
+ void expandBy(CPoint const &p) {
+ expandBy(p[X], p[Y]);
+ }
+ /// @}
+
+ /// @name Operators
+ /// @{
+ /** @brief Offset the rectangle by a vector. */
+ GenericRect<C> &operator+=(CPoint const &p) {
+ f[X] += p[X];
+ f[Y] += p[Y];
+ return *this;
+ }
+ /** @brief Offset the rectangle by the negation of a vector. */
+ GenericRect<C> &operator-=(CPoint const &p) {
+ f[X] -= p[X];
+ f[Y] -= p[Y];
+ return *this;
+ }
+ /** @brief Union two rectangles. */
+ GenericRect<C> &operator|=(CRect const &o) {
+ unionWith(o);
+ return *this;
+ }
+ GenericRect<C> &operator|=(OptCRect const &o) {
+ unionWith(o);
+ return *this;
+ }
+ /** @brief Test for equality of rectangles. */
+ bool operator==(CRect const &o) const { return f[X] == o[X] && f[Y] == o[Y]; }
+ /// @}
+};
+
+/**
+ * @brief Axis-aligned generic rectangle that can be empty.
+ * @ingroup Primitives
+ */
+template <typename C>
+class GenericOptRect
+ : public std::optional<typename CoordTraits<C>::RectType>
+ , boost::equality_comparable< typename CoordTraits<C>::OptRectType
+ , boost::equality_comparable< typename CoordTraits<C>::OptRectType, typename CoordTraits<C>::RectType
+ , boost::orable< typename CoordTraits<C>::OptRectType
+ , boost::andable< typename CoordTraits<C>::OptRectType
+ , boost::andable< typename CoordTraits<C>::OptRectType, typename CoordTraits<C>::RectType
+ > > > > >
+{
+ typedef typename CoordTraits<C>::IntervalType CInterval;
+ typedef typename CoordTraits<C>::OptIntervalType OptCInterval;
+ typedef typename CoordTraits<C>::PointType CPoint;
+ typedef typename CoordTraits<C>::RectType CRect;
+ typedef typename CoordTraits<C>::OptRectType OptCRect;
+ typedef std::optional<CRect> Base;
+public:
+ typedef CInterval D1Value;
+ typedef CInterval &D1Reference;
+ typedef CInterval const &D1ConstReference;
+
+ /// @name Create potentially empty rectangles.
+ /// @{
+ GenericOptRect() : Base() {}
+ GenericOptRect(GenericRect<C> const &a) : Base(CRect(a)) {}
+ GenericOptRect(CPoint const &a, CPoint const &b) : Base(CRect(a, b)) {}
+ GenericOptRect(C x0, C y0, C x1, C y1) : Base(CRect(x0, y0, x1, y1)) {}
+ /// Creates an empty OptRect when one of the argument intervals is empty.
+ GenericOptRect(OptCInterval const &x_int, OptCInterval const &y_int) {
+ if (x_int && y_int) {
+ *this = CRect(*x_int, *y_int);
+ }
+ // else, stay empty.
+ }
+
+ /** @brief Create a rectangle from a range of points.
+ * The resulting rectangle will contain all points from the range.
+ * If the range contains no points, the result will be an empty rectangle.
+ * The return type of iterators must be convertible to the corresponding
+ * point type (Point or IntPoint).
+ * @param start Beginning of the range
+ * @param end End of the range
+ * @return Rectangle that contains all points from [start, end). */
+ template <typename InputIterator>
+ static OptCRect from_range(InputIterator start, InputIterator end) {
+ OptCRect result;
+ for (; start != end; ++start) {
+ result.expandTo(*start);
+ }
+ return result;
+ }
+ /// @}
+
+ /// @name Check other rectangles and points for inclusion.
+ /// @{
+ /** @brief Check for emptiness. */
+ inline bool empty() const { return !*this; };
+ /** @brief Check whether the rectangles have any common points.
+ * Empty rectangles will not intersect with any other rectangle. */
+ bool intersects(CRect const &r) const { return r.intersects(*this); }
+ /** @brief Check whether the rectangle includes all points in the given rectangle.
+ * Empty rectangles will be contained in any non-empty rectangle. */
+ bool contains(CRect const &r) const { return *this && (*this)->contains(r); }
+
+ /** @brief Check whether the rectangles have any common points.
+ * Empty rectangles will not intersect with any other rectangle.
+ * Two empty rectangles will not intersect each other. */
+ bool intersects(OptCRect const &r) const { return *this && (*this)->intersects(r); }
+ /** @brief Check whether the rectangle includes all points in the given rectangle.
+ * Empty rectangles will be contained in any non-empty rectangle.
+ * An empty rectangle will not contain other empty rectangles. */
+ bool contains(OptCRect const &r) const { return *this && (*this)->contains(r); }
+
+ /** @brief Check whether the given point is within the rectangle.
+ * An empty rectangle will not contain any points. */
+ bool contains(CPoint const &p) const { return *this && (*this)->contains(p); }
+ /// @}
+
+ /** @brief Returns an empty optional (testing false) if the rectangle has zero area. */
+ OptCRect regularized() const {
+ return *this && !(*this)->hasZeroArea() ? *this : OptCRect();
+ }
+
+ /// @name Modify the potentially empty rectangle.
+ /// @{
+ /** @brief Enlarge the rectangle to contain the argument.
+ * If this rectangle is empty, after callng this method it will
+ * be equal to the argument. */
+ void unionWith(CRect const &b) {
+ if (*this) {
+ (*this)->unionWith(b);
+ } else {
+ *this = b;
+ }
+ }
+ /** @brief Enlarge the rectangle to contain the argument.
+ * Unioning with an empty rectangle results in no changes.
+ * If this rectangle is empty, after calling this method it will
+ * be equal to the argument. */
+ void unionWith(OptCRect const &b) {
+ if (b) unionWith(*b);
+ }
+ /** @brief Leave only the area overlapping with the argument.
+ * If the rectangles do not have any points in common, after calling
+ * this method the rectangle will be empty. */
+ void intersectWith(CRect const &b) {
+ if (!*this) return;
+ OptCInterval x = (**this)[X] & b[X], y = (**this)[Y] & b[Y];
+ if (x && y) {
+ *this = CRect(*x, *y);
+ } else {
+ *(static_cast<Base*>(this)) = std::nullopt;
+ }
+ }
+ /** @brief Leave only the area overlapping with the argument.
+ * If the argument is empty or the rectangles do not have any points
+ * in common, after calling this method the rectangle will be empty. */
+ void intersectWith(OptCRect const &b) {
+ if (b) {
+ intersectWith(*b);
+ } else {
+ *(static_cast<Base*>(this)) = std::nullopt;
+ }
+ }
+ /** @brief Create or enlarge the rectangle to contain the given point.
+ * If the rectangle is empty, after calling this method it will be non-empty
+ * and it will contain only the given point. */
+ void expandTo(CPoint const &p) {
+ if (*this) {
+ (*this)->expandTo(p);
+ } else {
+ *this = CRect(p, p);
+ }
+ }
+ /// @}
+
+ /// @name Operators
+ /// @{
+ /** @brief Union with @a b */
+ GenericOptRect<C> &operator|=(OptCRect const &b) {
+ unionWith(b);
+ return *this;
+ }
+ /** @brief Intersect with @a b */
+ GenericOptRect<C> &operator&=(CRect const &b) {
+ intersectWith(b);
+ return *this;
+ }
+ /** @brief Intersect with @a b */
+ GenericOptRect<C> &operator&=(OptCRect const &b) {
+ intersectWith(b);
+ return *this;
+ }
+ /** @brief Test for equality.
+ * All empty rectangles are equal. */
+ bool operator==(OptCRect const &other) const {
+ if (!*this != !other) return false;
+ return *this ? (**this == *other) : true;
+ }
+ bool operator==(CRect const &other) const {
+ if (!*this) return false;
+ return **this == other;
+ }
+ /// @}
+};
+
+template <typename C>
+inline void GenericRect<C>::unionWith(OptCRect const &b) {
+ if (b) {
+ unionWith(*b);
+ }
+}
+template <typename C>
+inline bool GenericRect<C>::intersects(OptCRect const &r) const {
+ return r && intersects(*r);
+}
+template <typename C>
+inline bool GenericRect<C>::contains(OptCRect const &r) const {
+ return !r || contains(*r);
+}
+
+template <typename C>
+inline std::ostream &operator<<(std::ostream &out, GenericRect<C> const &r) {
+ out << "Rect " << r[X] << " x " << r[Y];
+ return out;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_RECT_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/include/2geom/geom.h b/include/2geom/geom.h
new file mode 100644
index 0000000..7393ff4
--- /dev/null
+++ b/include/2geom/geom.h
@@ -0,0 +1,66 @@
+/**
+ * \file
+ * \brief Various geometrical calculations
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * Copyright (C) 1999-2002 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 LIB2GEOM_SEEN_GEOM_H
+#define LIB2GEOM_SEEN_GEOM_H
+
+//TODO: move somewhere else
+
+#include <vector>
+#include <2geom/forward.h>
+#include <optional>
+#include <2geom/bezier-curve.h>
+#include <2geom/line.h>
+
+namespace Geom {
+
+std::optional<Geom::LineSegment>
+rect_line_intersect(Geom::Rect &r,
+ Geom::LineSegment ls);
+
+int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &area);
+
+}
+
+#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/include/2geom/int-interval.h b/include/2geom/int-interval.h
new file mode 100644
index 0000000..0faf48d
--- /dev/null
+++ b/include/2geom/int-interval.h
@@ -0,0 +1,63 @@
+/**
+ * \file
+ * \brief Closed interval of integer values
+ *//*
+ * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_INT_INTERVAL_H
+#define LIB2GEOM_SEEN_INT_INTERVAL_H
+
+#include <2geom/coord.h>
+#include <2geom/generic-interval.h>
+
+namespace Geom {
+
+/**
+ * @brief Range of integers that is never empty.
+ * @ingroup Primitives
+ */
+typedef GenericInterval<IntCoord> IntInterval;
+
+/**
+ * @brief Range of integers that can be empty.
+ * @ingroup Primitives
+ */
+typedef GenericOptInterval<IntCoord> OptIntInterval;
+
+} // namespace Geom
+#endif // !LIB2GEOM_SEEN_INT_INTERVAL_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/include/2geom/int-point.h b/include/2geom/int-point.h
new file mode 100644
index 0000000..6dbed11
--- /dev/null
+++ b/include/2geom/int-point.h
@@ -0,0 +1,202 @@
+/**
+ * \file
+ * \brief Cartesian point / 2D vector with integer coordinates
+ *//*
+ * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_INT_POINT_H
+#define LIB2GEOM_SEEN_INT_POINT_H
+
+#include <stdexcept>
+#include <boost/operators.hpp>
+#include <2geom/coord.h>
+
+namespace Geom {
+
+/**
+ * @brief Two-dimensional point with integer coordinates.
+ *
+ * This class is an exact equivalent of Point, except it stores integer coordinates.
+ * Integer points are useful in contexts related to rasterized graphics, for example
+ * for bounding boxes when rendering SVG.
+ *
+ * @see Point
+ * @ingroup Primitives */
+class IntPoint
+ : boost::additive< IntPoint
+ , boost::totally_ordered< IntPoint
+ , boost::multiplicative< IntPoint, IntCoord
+ , boost::multiplicative< IntPoint
+ > > > >
+{
+ IntCoord _pt[2] = { 0, 0 };
+public:
+ /// @name Create integer points
+ /// @{
+ /** Construct a point at the origin. */
+ IntPoint() = default;
+ /** Construct a point from its coordinates. */
+ IntPoint(IntCoord x, IntCoord y)
+ : _pt{ x, y }
+ {}
+ /// @}
+
+ /// @name Access the coordinates of a point
+ /// @{
+ IntCoord operator[](unsigned i) const {
+ if ( i > Y ) throw std::out_of_range("index out of range");
+ return _pt[i];
+ }
+ IntCoord &operator[](unsigned i) {
+ if ( i > Y ) throw std::out_of_range("index out of range");
+ return _pt[i];
+ }
+ IntCoord operator[](Dim2 d) const { return _pt[d]; }
+ IntCoord &operator[](Dim2 d) { return _pt[d]; }
+
+ IntCoord x() const noexcept { return _pt[X]; }
+ IntCoord &x() noexcept { return _pt[X]; }
+ IntCoord y() const noexcept { return _pt[Y]; }
+ IntCoord &y() noexcept { return _pt[Y]; }
+ /// @}
+
+ /// @name Vector-like arithmetic operations
+ /// @{
+ IntPoint operator-() const {
+ return IntPoint(-_pt[X], -_pt[Y]);
+ }
+ IntPoint &operator+=(IntPoint const &o) {
+ _pt[X] += o._pt[X];
+ _pt[Y] += o._pt[Y];
+ return *this;
+ }
+ IntPoint &operator-=(IntPoint const &o) {
+ _pt[X] -= o._pt[X];
+ _pt[Y] -= o._pt[Y];
+ return *this;
+ }
+ IntPoint &operator*=(IntPoint const &o) {
+ _pt[X] *= o._pt[X];
+ _pt[Y] *= o._pt[Y];
+ return *this;
+ }
+ IntPoint &operator*=(IntCoord o) {
+ _pt[X] *= o;
+ _pt[Y] *= o;
+ return *this;
+ }
+ IntPoint &operator/=(IntPoint const &o) {
+ _pt[X] /= o._pt[X];
+ _pt[Y] /= o._pt[Y];
+ return *this;
+ }
+ IntPoint &operator/=(IntCoord o) {
+ _pt[X] /= o;
+ _pt[Y] /= o;
+ return *this;
+ }
+ /// @}
+
+ /// @name Various utilities
+ /// @{
+ /** @brief Equality operator. */
+ bool operator==(IntPoint const &in_pnt) const {
+ return ((_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y]));
+ }
+ /** @brief Lexicographical ordering for points.
+ * Y coordinate is regarded as more significant. When sorting according to this
+ * ordering, the points will be sorted according to the Y coordinate, and within
+ * points with the same Y coordinate according to the X coordinate. */
+ bool operator<(IntPoint const &p) const {
+ return ( ( _pt[Y] < p[Y] ) ||
+ (( _pt[Y] == p[Y] ) && ( _pt[X] < p[X] )));
+ }
+ /// @}
+
+ /** @brief Lexicographical ordering functor.
+ * @param d The more significant dimension */
+ template <Dim2 d> struct LexLess;
+ /** @brief Lexicographical ordering functor.
+ * @param d The more significant dimension */
+ template <Dim2 d> struct LexGreater;
+ /** @brief Lexicographical ordering functor with runtime dimension. */
+ struct LexLessRt {
+ LexLessRt(Dim2 d) : dim(d) {}
+ inline bool operator()(IntPoint const &a, IntPoint const &b) const;
+ private:
+ Dim2 dim;
+ };
+ /** @brief Lexicographical ordering functor with runtime dimension. */
+ struct LexGreaterRt {
+ LexGreaterRt(Dim2 d) : dim(d) {}
+ inline bool operator()(IntPoint const &a, IntPoint const &b) const;
+ private:
+ Dim2 dim;
+ };
+};
+
+template<> struct IntPoint::LexLess<X> {
+ bool operator()(IntPoint const &a, IntPoint const &b) const {
+ return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]);
+ }
+};
+template<> struct IntPoint::LexLess<Y> {
+ bool operator()(IntPoint const &a, IntPoint const &b) const {
+ return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]);
+ }
+};
+template<> struct IntPoint::LexGreater<X> {
+ bool operator()(IntPoint const &a, IntPoint const &b) const {
+ return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]);
+ }
+};
+template<> struct IntPoint::LexGreater<Y> {
+ bool operator()(IntPoint const &a, IntPoint const &b) const {
+ return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]);
+ }
+};
+inline bool IntPoint::LexLessRt::operator()(IntPoint const &a, IntPoint const &b) const {
+ return dim ? IntPoint::LexLess<Y>()(a, b) : IntPoint::LexLess<X>()(a, b);
+}
+inline bool IntPoint::LexGreaterRt::operator()(IntPoint const &a, IntPoint const &b) const {
+ return dim ? IntPoint::LexGreater<Y>()(a, b) : IntPoint::LexGreater<X>()(a, b);
+}
+
+} // namespace Geom
+
+#endif // !SEEN_GEOM_INT_POINT_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/include/2geom/int-rect.h b/include/2geom/int-rect.h
new file mode 100644
index 0000000..567d42d
--- /dev/null
+++ b/include/2geom/int-rect.h
@@ -0,0 +1,75 @@
+/**
+ * \file
+ * \brief Axis-aligned rectangle with integer coordinates
+ *//*
+ * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_INT_RECT_H
+#define LIB2GEOM_SEEN_INT_RECT_H
+
+#include <2geom/coord.h>
+#include <2geom/int-interval.h>
+#include <2geom/generic-rect.h>
+
+namespace Geom {
+
+typedef GenericRect<IntCoord> IntRect;
+typedef GenericOptRect<IntCoord> OptIntRect;
+
+// the functions below do not work when defined generically
+inline OptIntRect operator&(IntRect const &a, IntRect const &b) {
+ OptIntRect ret(a);
+ ret.intersectWith(b);
+ return ret;
+}
+inline OptIntRect intersect(IntRect const &a, IntRect const &b) {
+ return a & b;
+}
+inline OptIntRect intersect(OptIntRect const &a, OptIntRect const &b) {
+ return a & b;
+}
+inline IntRect unify(IntRect const &a, IntRect const &b) {
+ return a | b;
+}
+inline OptIntRect unify(OptIntRect const &a, OptIntRect const &b) {
+ return a | b;
+}
+
+} // end namespace Geom
+
+#endif // !LIB2GEOM_SEEN_INT_RECT_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/include/2geom/intersection-graph.h b/include/2geom/intersection-graph.h
new file mode 100644
index 0000000..940c43c
--- /dev/null
+++ b/include/2geom/intersection-graph.h
@@ -0,0 +1,259 @@
+/**
+ * \file
+ * \brief Path intersection graph
+ *//*
+ * 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.
+ */
+
+#ifndef SEEN_LIB2GEOM_INTERSECTION_GRAPH_H
+#define SEEN_LIB2GEOM_INTERSECTION_GRAPH_H
+
+#include <set>
+#include <vector>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/intrusive/list.hpp>
+#include <2geom/forward.h>
+#include <2geom/pathvector.h>
+
+namespace Geom {
+
+/** @class PathIntersectionGraph
+ * @brief Intermediate data for computing Boolean operations on paths.
+ *
+ * This class implements the Greiner-Hormann clipping algorithm,
+ * with improvements inspired by Foster and Overfelt as well as some
+ * original contributions.
+ *
+ * For the purposes of boolean operations, a shape is defined as a PathVector
+ * using the "even-odd" rule, i.e., regions with odd winding are considered part
+ * of the shape, whereas regions with even winding are not.
+ *
+ * For this reason, the two path-vectors are sometimes called "shapes" or "operands" of
+ * the boolean operation. Each path-vector may contain several paths, which are called
+ * either "paths" or "components" in the documentation.
+ *
+ * @ingroup Paths
+ */
+class PathIntersectionGraph
+{
+ // this is called PathIntersectionGraph so that we can also have a class for polygons,
+ // e.g. PolygonIntersectionGraph, which is going to be significantly faster
+public:
+ /** @brief Construct a path intersection graph for two shapes described via their boundaries.
+ * The boundaries are passed as path-vectors.
+ *
+ * @param a – the first operand, also referred to as operand A.
+ * @param b – the second operand, also referred to as operand B.
+ * @param precision – precision setting used for intersection calculations.
+ */
+ PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision = EPSILON);
+
+ /**
+ * @brief Get the union of the shapes, A ∪ B.
+ *
+ * A point belongs to the union if and only if it belongs to at least one of the operands.
+ *
+ * @return A path-vector describing the union of the operands A and B.
+ */
+ PathVector getUnion();
+
+ /**
+ * @brief Get the intersection of the shapes, A ∩ B.
+ *
+ * A point belongs to the intersection if and only if it belongs to both shapes.
+ *
+ * @return A path-vector describing the intersection of the operands A and B.
+ */
+ PathVector getIntersection();
+
+ /**
+ * @brief Get the difference of the shapes, A ∖ B.
+ *
+ * A point belongs to the difference if and only if it belongs to A but not to B.
+ *
+ * @return A path-vector describing the difference of the operands A and B.
+ */
+ PathVector getAminusB();
+
+ /**
+ * @brief Get the opposite difference of the shapes, B ∖ A.
+ *
+ * A point belongs to the difference if and only if it belongs to B but not to A.
+ *
+ * @return A path-vector describing the difference of the operands B and A.
+ */
+ PathVector getBminusA();
+
+ /**
+ * @brief Get the symmetric difference of the shapes, A ∆ B.
+ *
+ * A point belongs to the symmetric difference if and only if it belongs to one of the two
+ * shapes A or B, but not both. This is equivalent to the logical XOR operation: the elements
+ * of A ∆ B are points which are in A XOR in B.
+ *
+ * @return A path-vector describing the symmetric difference of the operands A and B.
+ */
+ PathVector getXOR();
+
+ /// Returns the number of intersections used when computing Boolean operations.
+ std::size_t size() const;
+
+ /**
+ * @brief Get the geometric points where the two path-vectors intersect.
+ *
+ * Degenerate intersection points, where the shapes merely "kiss", are not retured.
+ *
+ * @param defective – whether to return only the defective crossings or only the true crossings.
+ * @return If defective is true, returns a vector containing all defective intersection points,
+ * i.e., points that are neither true transverse intersections nor degenerate intersections.
+ * If defective is false, returns all true transverse intersections.
+ */
+ std::vector<Point> intersectionPoints(bool defective = false) const;
+
+ /**
+ * @brief Get the geometric points located on path portions between consecutive intersections.
+ *
+ * These points were used for the winding number calculations which determined which path portions
+ * lie inside the other shape and which lie outside.
+ *
+ * @return A vector containing all sample points used for winding calculations.
+ */
+ std::vector<Point> windingPoints() const {
+ return _winding_points;
+ }
+
+ void fragments(PathVector &in, PathVector &out) const;
+
+
+ bool valid() const { return _graph_valid; }
+
+private:
+ enum InOutFlag {
+ INSIDE,
+ OUTSIDE,
+ BOTH
+ };
+
+ struct IntersectionVertex {
+ boost::intrusive::list_member_hook<> _hook;
+ boost::intrusive::list_member_hook<> _proc_hook;
+ PathVectorTime pos; ///< Intersection time.
+ Point p; ///< Geometric position of the intersection point; guarantees that endpoints are exact.
+ IntersectionVertex *neighbor; ///< A pointer to the corresponding vertex on the other shape.
+ /** Tells us whether the edge originating at this intersection lies inside or outside of
+ * the shape given by the other path-vector. The "edge originating" at this intersection is
+ * the portion of the path between this intersection and the next intersection, in the
+ * direction of increasing path time. */
+ InOutFlag next_edge;
+ unsigned which; ///< Index of the operand path-vector that this intersection vertex lies on.
+ /** Whether the intersection is defective, which means that for some reason the paths
+ * neither cross transversally through each other nor "kiss" at a common tangency point.
+ */
+ bool defective;
+ };
+
+ typedef boost::intrusive::list
+ < IntersectionVertex
+ , boost::intrusive::member_hook
+ < IntersectionVertex
+ , boost::intrusive::list_member_hook<>
+ , &IntersectionVertex::_hook
+ >
+ > IntersectionList;
+
+ typedef boost::intrusive::list
+ < IntersectionVertex
+ , boost::intrusive::member_hook
+ < IntersectionVertex
+ , boost::intrusive::list_member_hook<>
+ , &IntersectionVertex::_proc_hook
+ >
+ > UnprocessedList;
+
+ /// Stores processed intersection information for a single path in an operand path-vector.
+ struct PathData {
+ IntersectionList xlist; ///< List of crossings on this particular path.
+ std::size_t path_index; ///< Index of the path in its path-vector.
+ int which; ///< Index of the path-vector (in PathIntersectionGraph::_pv) that the path belongs to.
+ /** Whether this path as a whole is contained INSIDE or OUTSIDE relative to the other path-vector.
+ * The value BOTH means that some portions of the path are inside while others are outside.
+ */
+ InOutFlag status;
+
+ PathData(int w, std::size_t pi)
+ : path_index(pi)
+ , which(w)
+ , status(BOTH)
+ {}
+ };
+
+ struct IntersectionVertexLess;
+ typedef IntersectionList::iterator ILIter;
+ typedef IntersectionList::const_iterator CILIter;
+
+ PathVector _getResult(bool enter_a, bool enter_b);
+ void _handleNonintersectingPaths(PathVector &result, unsigned which, bool inside);
+ void _prepareArguments();
+ bool _prepareIntersectionLists(Coord precision);
+ void _assignEdgeWindingParities(Coord precision);
+ void _assignComponentStatusFromDegenerateIntersections();
+ void _removeDegenerateIntersections();
+ void _verify();
+
+ ILIter _getNeighbor(ILIter iter);
+ PathData &_getPathData(ILIter iter);
+
+ PathVector _pv[2]; ///< Stores the two operand path-vectors, A at _pv[0] and B at _pv[1].
+ boost::ptr_vector<IntersectionVertex> _xs; ///< Stores all crossings between the two shapes.
+ boost::ptr_vector<PathData> _components[2]; ///< Stores the crossing information for the operands.
+ UnprocessedList _ulist; ///< Temporarily holds all unprocessed during a boolean operation.
+ bool _graph_valid; ///< Whether all intersections are regular.
+ /** Stores sample points located on paths of the operand path-vectors,
+ * between consecutive intersections.
+ */
+ std::vector<Point> _winding_points;
+
+ friend std::ostream &operator<<(std::ostream &, PathIntersectionGraph const &);
+};
+
+std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig);
+
+} // namespace Geom
+
+#endif // SEEN_LIB2GEOM_PATH_GRAPH_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/include/2geom/intersection.h b/include/2geom/intersection.h
new file mode 100644
index 0000000..8a23811
--- /dev/null
+++ b/include/2geom/intersection.h
@@ -0,0 +1,147 @@
+/**
+ * \file
+ * \brief Intersection utilities
+ *//*
+ * 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.
+ */
+
+#ifndef SEEN_LIB2GEOM_INTERSECTION_H
+#define SEEN_LIB2GEOM_INTERSECTION_H
+
+#include <2geom/coord.h>
+#include <2geom/point.h>
+
+namespace Geom {
+
+
+/** @brief Intersection between two shapes.
+ */
+template <typename TimeA = Coord, typename TimeB = TimeA>
+class Intersection
+ : boost::totally_ordered< Intersection<TimeA, TimeB> >
+{
+public:
+ /** @brief Construct from shape references and time values.
+ * By default, the intersection point will be halfway between the evaluated
+ * points on the two shapes. */
+ template <typename TA, typename TB>
+ Intersection(TA const &sa, TB const &sb, TimeA const &ta, TimeB const &tb)
+ : first(ta)
+ , second(tb)
+ , _point(lerp(0.5, sa.pointAt(ta), sb.pointAt(tb)))
+ {}
+
+ /// Additionally report the intersection point.
+ Intersection(TimeA const &ta, TimeB const &tb, Point const &p)
+ : first(ta)
+ , second(tb)
+ , _point(p)
+ {}
+
+ /// Intersection point, as calculated by the intersection algorithm.
+ Point point() const {
+ return _point;
+ }
+ /// Implicit conversion to Point.
+ operator Point() const {
+ return _point;
+ }
+
+ friend inline void swap(Intersection &a, Intersection &b) {
+ using std::swap;
+ swap(a.first, b.first);
+ swap(a.second, b.second);
+ swap(a._point, b._point);
+ }
+
+ bool operator==(Intersection const &other) const {
+ if (first != other.first) return false;
+ if (second != other.second) return false;
+ return true;
+ }
+ bool operator<(Intersection const &other) const {
+ if (first < other.first) return true;
+ if (first == other.first && second < other.second) return true;
+ return false;
+ }
+
+public:
+ /// First shape and time value.
+ TimeA first;
+ /// Second shape and time value.
+ TimeB second;
+private:
+ // Recalculation of the intersection point from the time values is in many cases
+ // less precise than the value obtained directly from the intersection algorithm,
+ // so we need to store it.
+ Point _point;
+};
+
+
+// TODO: move into new header?
+template <typename T>
+struct ShapeTraits {
+ typedef Coord TimeType;
+ typedef Interval IntervalType;
+ typedef T AffineClosureType;
+ typedef Intersection<> IntersectionType;
+};
+
+template <typename A, typename B> inline
+std::vector< Intersection<A, B> > transpose(std::vector< Intersection<B, A> > const &in) {
+ std::vector< Intersection<A, B> > result;
+ for (std::size_t i = 0; i < in.size(); ++i) {
+ result.push_back(Intersection<A, B>(in[i].second, in[i].first, in[i].point()));
+ }
+ return result;
+}
+
+template <typename T> inline
+void transpose_in_place(std::vector< Intersection<T, T> > &xs) {
+ for (std::size_t i = 0; i < xs.size(); ++i) {
+ std::swap(xs[i].first, xs[i].second);
+ }
+}
+
+typedef Intersection<> ShapeIntersection;
+
+
+} // namespace Geom
+
+#endif // SEEN_LIB2GEOM_INTERSECTION_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/include/2geom/interval.h b/include/2geom/interval.h
new file mode 100644
index 0000000..11c8f28
--- /dev/null
+++ b/include/2geom/interval.h
@@ -0,0 +1,245 @@
+/**
+ * \file
+ * \brief Simple closed interval class
+ *//*
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * Original Rect/Range code by:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * 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, output 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 LIB2GEOM_SEEN_INTERVAL_H
+#define LIB2GEOM_SEEN_INTERVAL_H
+
+#include <boost/none.hpp>
+#include <boost/operators.hpp>
+#include <2geom/coord.h>
+#include <2geom/math-utils.h>
+#include <2geom/generic-interval.h>
+#include <2geom/int-interval.h>
+
+namespace Geom {
+
+/**
+ * @brief Range of real numbers that is never empty.
+ *
+ * Intervals are closed ranges \f$[a, b]\f$, which means they include their endpoints.
+ * To use them as open ranges, you can use the interiorContains() methods.
+ *
+ * @ingroup Primitives
+ */
+class Interval
+ : public GenericInterval<Coord>
+{
+ typedef GenericInterval<Coord> Base;
+public:
+ /// @name Create intervals.
+ /// @{
+ /** @brief Create an interval that contains only zero. */
+ Interval() {}
+ /** @brief Create an interval that contains a single point. */
+ explicit Interval(Coord u) : Base(u) {}
+ /** @brief Create an interval that contains all points between @c u and @c v. */
+ Interval(Coord u, Coord v) : Base(u,v) {}
+ /** @brief Convert from integer interval */
+ Interval(IntInterval const &i) : Base(i.min(), i.max()) {}
+ Interval(Base const &b) : Base(b) {}
+
+ /** @brief Create an interval containing a range of values.
+ * The resulting interval will contain all values from the given range.
+ * The return type of iterators must be convertible to Coord. The given range
+ * must not be empty. For potentially empty ranges, see OptInterval.
+ * @param start Beginning of the range
+ * @param end End of the range
+ * @return Interval that contains all values from [start, end). */
+ template <typename InputIterator>
+ static Interval from_range(InputIterator start, InputIterator end) {
+ Interval result = Base::from_range(start, end);
+ return result;
+ }
+ /** @brief Create an interval from a C-style array of values it should contain. */
+ static Interval from_array(Coord const *c, unsigned n) {
+ Interval result = from_range(c, c+n);
+ return result;
+ }
+ /// @}
+
+ /// @name Inspect contained values.
+ /// @{
+ /// Check whether both endpoints are finite.
+ bool isFinite() const {
+ return std::isfinite(min()) && std::isfinite(max());
+ }
+ /** @brief Map the interval [0,1] onto this one.
+ * This method simply performs 1D linear interpolation between endpoints. */
+ Coord valueAt(Coord t) const {
+ return lerp(t, min(), max());
+ }
+ /** @brief Compute a time value that maps to the given value.
+ * The supplied value does not need to be in the interval for this method to work. */
+ Coord timeAt(Coord v) const {
+ return (v - min()) / extent();
+ }
+ /// Find closest time in [0,1] that maps to the given value. */
+ Coord nearestTime(Coord v) const {
+ if (v <= min()) return 0;
+ if (v >= max()) return 1;
+ return timeAt(v);
+ }
+ /// @}
+
+ /// @name Test coordinates and other intervals for inclusion.
+ /// @{
+ /** @brief Check whether the interior of the interval includes this number.
+ * Interior means all numbers in the interval except its ends. */
+ bool interiorContains(Coord val) const { return min() < val && val < max(); }
+ /** @brief Check whether the interior of the interval includes the given interval.
+ * Interior means all numbers in the interval except its ends. */
+ bool interiorContains(Interval const &val) const { return min() < val.min() && val.max() < max(); }
+ /// Check whether the number is contained in the union of the interior and the lower boundary.
+ bool lowerContains(Coord val) const { return min() <= val && val < max(); }
+ /// Check whether the given interval is contained in the union of the interior and the lower boundary.
+ bool lowerContains(Interval const &val) const { return min() <= val.min() && val.max() < max(); }
+ /// Check whether the number is contained in the union of the interior and the upper boundary.
+ bool upperContains(Coord val) { return min() < val && val <= max(); }
+ /// Check whether the given interval is contained in the union of the interior and the upper boundary.
+ bool upperContains(Interval const &val) const { return min() < val.min() && val.max() <= max(); }
+ /** @brief Check whether the interiors of the intervals have any common elements.
+ * A single point in common is not considered an intersection. */
+ bool interiorIntersects(Interval const &val) const {
+ return std::max(min(), val.min()) < std::min(max(), val.max());
+ }
+ /// @}
+
+ /// @name Operators
+ /// @{
+ // IMPL: ScalableConcept
+ /** @brief Scale an interval */
+ Interval &operator*=(Coord s) {
+ using std::swap;
+ _b[0] *= s;
+ _b[1] *= s;
+ if(s < 0) swap(_b[0], _b[1]);
+ return *this;
+ }
+ /** @brief Scale an interval by the inverse of the specified value */
+ Interval &operator/=(Coord s) {
+ using std::swap;
+ _b[0] /= s;
+ _b[1] /= s;
+ if(s < 0) swap(_b[0], _b[1]);
+ return *this;
+ }
+ /** @brief Multiply two intervals.
+ * Product is defined as the set of points that can be obtained by multiplying
+ * any value from the second operand by any value from the first operand:
+ * \f$S = \{x \in A, y \in B: x * y\}\f$ */
+ Interval &operator*=(Interval const &o) {
+ // TODO implement properly
+ Coord mn = min(), mx = max();
+ expandTo(mn * o.min());
+ expandTo(mn * o.max());
+ expandTo(mx * o.min());
+ expandTo(mx * o.max());
+ return *this;
+ }
+ bool operator==(IntInterval const &ii) const {
+ return min() == Coord(ii.min()) && max() == Coord(ii.max());
+ }
+ bool operator==(Interval const &other) const {
+ return Base::operator==(other);
+ }
+ /// @}
+
+ /// @name Rounding to integer values
+ /// @{
+ /** @brief Return the smallest integer interval which contains this one. */
+ IntInterval roundOutwards() const {
+ IntInterval ret(floor(min()), ceil(max()));
+ return ret;
+ }
+ /** @brief Return the largest integer interval which is contained in this one. */
+ OptIntInterval roundInwards() const {
+ IntCoord u = ceil(min()), v = floor(max());
+ if (u > v) { OptIntInterval e; return e; }
+ IntInterval ret(u, v);
+ return ret;
+ }
+ /// @}
+};
+
+/**
+ * @brief Range of real numbers that can be empty.
+ * @ingroup Primitives
+ */
+class OptInterval
+ : public GenericOptInterval<Coord>
+{
+ typedef GenericOptInterval<Coord> Base;
+public:
+ using Base::Base;
+ using Base::operator==;
+ using Base::operator!=;
+
+ OptInterval(Base const &b) : Base(b) {}
+
+ /** @brief Promote from IntInterval. */
+ OptInterval(IntInterval const &i) : Base(Interval(i)) {}
+ /** @brief Promote from OptIntInterval. */
+ OptInterval(OptIntInterval const &i) : Base() {
+ if (i) *this = Interval(*i);
+ }
+};
+
+// functions required for Python bindings
+inline Interval unify(Interval const &a, Interval const &b)
+{
+ Interval r = a | b;
+ return r;
+}
+inline OptInterval intersect(Interval const &a, Interval const &b)
+{
+ OptInterval r = a & b;
+ return r;
+}
+
+} // end namespace Geom
+
+#endif //SEEN_INTERVAL_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/include/2geom/intervaltree/interval_tree.h b/include/2geom/intervaltree/interval_tree.h
new file mode 100644
index 0000000..85f91f9
--- /dev/null
+++ b/include/2geom/intervaltree/interval_tree.h
@@ -0,0 +1,126 @@
+#ifndef E_INTERVAL_TREE
+#define E_INTERVAL_TREE
+
+// From Emin Martinian, licenced LGPL and MPL with permission
+
+#include <vector>
+#include <math.h>
+#include <limits>
+#include <iostream>
+
+using std::vector;
+
+// The interval_tree.h and interval_tree.cc files contain code for
+// interval trees implemented using red-black-trees as described in
+// the book _Introduction_To_Algorithms_ by Cormen, Leisserson,
+// and Rivest.
+
+// CONVENTIONS:
+// Function names: Each word in a function name begins with
+// a capital letter. An example funcntion name is
+// CreateRedTree(a,b,c). Furthermore, each function name
+// should begin with a capital letter to easily distinguish
+// them from variables.
+//
+// Variable names: Each word in a variable name begins with
+// a capital letter EXCEPT the first letter of the variable
+// name. For example, int newLongInt. Global variables have
+// names beginning with "g". An example of a global
+// variable name is gNewtonsConstant.
+
+
+#ifndef MAX_INT
+#define MAX_INT INT_MAX // some architechturs define INT_MAX not MAX_INT
+#endif
+
+// The Interval class is an Abstract Base Class. This means that no
+// instance of the Interval class can exist. Only classes which
+// inherit from the Interval class can exist. Furthermore any class
+// which inherits from the Interval class must define the member
+// functions GetLowPoint and GetHighPoint.
+//
+// The GetLowPoint should return the lowest point of the interval and
+// the GetHighPoint should return the highest point of the interval.
+
+class Interval {
+public:
+ Interval();
+ virtual ~Interval();
+ virtual int GetLowPoint() const = 0;
+ virtual int GetHighPoint() const = 0;
+ virtual void Print() const;
+};
+
+class IntervalTreeNode {
+ friend class IntervalTree;
+public:
+ void Print(IntervalTreeNode*,
+ IntervalTreeNode*) const;
+ IntervalTreeNode();
+ IntervalTreeNode(Interval *);
+ ~IntervalTreeNode();
+protected:
+ Interval * storedInterval;
+ int key;
+ int high;
+ int maxHigh;
+ bool red; /* if red=0 then the node is black */
+ IntervalTreeNode * left;
+ IntervalTreeNode * right;
+ IntervalTreeNode * parent;
+};
+
+struct it_recursion_node {
+public:
+ /* this structure stores the information needed when we take the */
+ /* right branch in searching for intervals but possibly come back */
+ /* and check the left branch as well. */
+
+ IntervalTreeNode * start_node;
+ unsigned int parentIndex;
+ bool tryRightBranch;
+} ;
+
+
+class IntervalTree {
+public:
+ IntervalTree();
+ ~IntervalTree();
+ void Print() const;
+ Interval * DeleteNode(IntervalTreeNode *);
+ IntervalTreeNode * Insert(Interval *);
+ IntervalTreeNode * GetPredecessorOf(IntervalTreeNode *) const;
+ IntervalTreeNode * GetSuccessorOf(IntervalTreeNode *) const;
+ vector<void *> Enumerate(int low, int high) ;
+ void CheckAssumptions() const;
+protected:
+ /* A sentinel is used for root and for nil. These sentinels are */
+ /* created when ITTreeCreate is caled. root->left should always */
+ /* point to the node which is the root of the tree. nil points to a */
+ /* node which should always be black but has arbitrary children and */
+ /* parent and no key or info. The point of using these sentinels is so */
+ /* that the root and nil nodes do not require special cases in the code */
+ IntervalTreeNode * root;
+ IntervalTreeNode * nil;
+ void LeftRotate(IntervalTreeNode *);
+ void RightRotate(IntervalTreeNode *);
+ void TreeInsertHelp(IntervalTreeNode *);
+ void TreePrintHelper(IntervalTreeNode *) const;
+ void FixUpMaxHigh(IntervalTreeNode *);
+ void DeleteFixUp(IntervalTreeNode *);
+ void CheckMaxHighFields(IntervalTreeNode *) const;
+ int CheckMaxHighFieldsHelper(IntervalTreeNode * y,
+ const int currentHigh,
+ int match) const;
+private:
+ unsigned int recursionNodeStackSize;
+ it_recursion_node * recursionNodeStack;
+ unsigned int currentParent;
+ unsigned int recursionNodeStackTop;
+};
+
+
+#endif
+
+
+
diff --git a/include/2geom/line.h b/include/2geom/line.h
new file mode 100644
index 0000000..9a56602
--- /dev/null
+++ b/include/2geom/line.h
@@ -0,0 +1,605 @@
+/**
+ * \file
+ * \brief Infinite straight line
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2008-2011 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 LIB2GEOM_SEEN_LINE_H
+#define LIB2GEOM_SEEN_LINE_H
+
+#include <cmath>
+#include <optional>
+#include <2geom/bezier-curve.h> // for LineSegment
+#include <2geom/rect.h>
+#include <2geom/crossing.h>
+#include <2geom/exception.h>
+#include <2geom/ray.h>
+#include <2geom/angle.h>
+#include <2geom/intersection.h>
+
+namespace Geom
+{
+
+// class docs in cpp file
+class Line
+ : boost::equality_comparable< Line >
+{
+private:
+ Point _initial;
+ Point _final;
+public:
+ /// @name Creating lines.
+ /// @{
+ /** @brief Create a default horizontal line.
+ * Creates a line with unit speed going in +X direction. */
+ Line()
+ : _initial(0,0), _final(1,0)
+ {}
+ /** @brief Create a line with the specified inclination.
+ * @param origin One of the points on the line
+ * @param angle Angle of the line in mathematical convention */
+ Line(Point const &origin, Coord angle)
+ : _initial(origin)
+ {
+ Point v;
+ sincos(angle, v[Y], v[X]);
+ _final = _initial + v;
+ }
+
+ /** @brief Create a line going through two points.
+ * The first point will be at time 0, while the second one
+ * will be at time 1.
+ * @param a Initial point
+ * @param b First point */
+ Line(Point const &a, Point const &b)
+ : _initial(a)
+ , _final(b)
+ {}
+
+ /** @brief Create a line based on the coefficients of its equation.
+ @see Line::setCoefficients() */
+ Line(double a, double b, double c) {
+ setCoefficients(a, b, c);
+ }
+
+ /// Create a line by extending a line segment.
+ explicit Line(LineSegment const &seg)
+ : _initial(seg.initialPoint())
+ , _final(seg.finalPoint())
+ {}
+
+ /// Create a line by extending a ray.
+ explicit Line(Ray const &r)
+ : _initial(r.origin())
+ , _final(r.origin() + r.vector())
+ {}
+
+ /// Create a line normal to a vector at a specified distance from origin.
+ static Line from_normal_distance(Point const &n, Coord c) {
+ Point start = c * n.normalized();
+ Line l(start, start + rot90(n));
+ return l;
+ }
+ /** @brief Create a line from origin and unit vector.
+ * Note that each line direction has two possible unit vectors.
+ * @param o Point through which the line will pass
+ * @param v Unit vector of the line's direction */
+ static Line from_origin_and_vector(Point const &o, Point const &v) {
+ Line l(o, o + v);
+ return l;
+ }
+
+ Line* duplicate() const {
+ return new Line(*this);
+ }
+ /// @}
+
+ /// @name Retrieve and set the line's parameters.
+ /// @{
+
+ /// Get the line's origin point.
+ Point origin() const { return _initial; }
+ /** @brief Get the line's raw direction vector.
+ * The length of the retrieved vector is equal to the length of a segment parametrized by
+ * a time interval of length 1. */
+ Point vector() const { return _final - _initial; }
+ /** @brief Get the line's normalized direction vector.
+ * The retrieved vector is normalized to unit length. */
+ Point versor() const { return (_final - _initial).normalized(); }
+ /// Angle the line makes with the X axis, in mathematical convention.
+ Coord angle() const {
+ Point d = _final - _initial;
+ double a = std::atan2(d[Y], d[X]);
+ if (a < 0) a += M_PI;
+ if (a == M_PI) a = 0;
+ return a;
+ }
+
+ /** @brief Set the point at zero time.
+ * The orientation remains unchanged, modulo numeric errors during addition. */
+ void setOrigin(Point const &p) {
+ Point d = p - _initial;
+ _initial = p;
+ _final += d;
+ }
+ /** @brief Set the speed of the line.
+ * Origin remains unchanged. */
+ void setVector(Point const &v) {
+ _final = _initial + v;
+ }
+
+ /** @brief Set the angle the line makes with the X axis.
+ * Origin remains unchanged. */
+ void setAngle(Coord angle) {
+ Point v;
+ sincos(angle, v[Y], v[X]);
+ v *= distance(_initial, _final);
+ _final = _initial + v;
+ }
+
+ /// Set a line based on two points it should pass through.
+ void setPoints(Point const &a, Point const &b) {
+ _initial = a;
+ _final = b;
+ }
+
+ /** @brief Set the coefficients of the line equation.
+ * The line equation is: \f$ax + by = c\f$. Points that satisfy the equation
+ * are on the line. */
+ void setCoefficients(double a, double b, double c);
+
+ /** @brief Get the coefficients of the line equation as a vector.
+ * @return STL vector @a v such that @a v[0] contains \f$a\f$, @a v[1] contains \f$b\f$,
+ * and @a v[2] contains \f$c\f$. */
+ std::vector<double> coefficients() const;
+
+ /// Get the coefficients of the line equation by reference.
+ void coefficients(Coord &a, Coord &b, Coord &c) const;
+
+ /** @brief Check if the line has more than one point.
+ * A degenerate line can be created if the line is created from a line equation
+ * that has no solutions.
+ * @return True if the line has no points or exactly one point */
+ bool isDegenerate() const {
+ return _initial == _final;
+ }
+ /// Check if the line is horizontal (y is constant).
+ bool isHorizontal() const {
+ return _initial[Y] == _final[Y];
+ }
+ /// Check if the line is vertical (x is constant).
+ bool isVertical() const {
+ return _initial[X] == _final[X];
+ }
+
+ /** @brief Reparametrize the line so that it has unit speed.
+ * Note that the direction of the line may also change. */
+ void normalize() {
+ // this helps with the nasty case of a line that starts somewhere far
+ // and ends very close to the origin
+ if (L2sq(_final) < L2sq(_initial)) {
+ std::swap(_initial, _final);
+ }
+ Point v = _final - _initial;
+ v.normalize();
+ _final = _initial + v;
+ }
+ /** @brief Return a new line reparametrized for unit speed. */
+ Line normalized() const {
+ Point v = _final - _initial;
+ v.normalize();
+ Line ret(_initial, _initial + v);
+ return ret;
+ }
+ /// @}
+
+ /// @name Evaluate the line as a function.
+ ///@{
+ Point initialPoint() const {
+ return _initial;
+ }
+ Point finalPoint() const {
+ return _final;
+ }
+ Point pointAt(Coord t) const {
+ return lerp(t, _initial, _final);;
+ }
+
+ Coord valueAt(Coord t, Dim2 d) const {
+ return lerp(t, _initial[d], _final[d]);
+ }
+
+ Coord timeAt(Point const &p) const;
+
+ /** @brief Get a time value corresponding to a projection of a point on the line.
+ * @param p Arbitrary point.
+ * @return Time value corresponding to a point closest to @c p. */
+ Coord timeAtProjection(Point const& p) const {
+ if ( isDegenerate() ) return 0;
+ Point v = vector();
+ return dot(p - _initial, v) / dot(v, v);
+ }
+
+ /** @brief Find a point on the line closest to the query point.
+ * This is an alias for timeAtProjection(). */
+ Coord nearestTime(Point const &p) const {
+ return timeAtProjection(p);
+ }
+
+ std::vector<Coord> roots(Coord v, Dim2 d) const;
+ Coord root(Coord v, Dim2 d) const;
+ /// @}
+
+ /// @name Create other objects based on this line.
+ /// @{
+ void reverse() {
+ std::swap(_final, _initial);
+ }
+ /** @brief Create a line containing the same points, but in opposite direction.
+ * @return Line \f$g\f$ such that \f$g(t) = f(1-t)\f$ */
+ Line reversed() const {
+ Line result(_final, _initial);
+ return result;
+ }
+
+ /** @brief Same as segment(), but allocate the line segment dynamically. */
+ // TODO remove this?
+ Curve* portion(Coord f, Coord t) const {
+ LineSegment* seg = new LineSegment(pointAt(f), pointAt(t));
+ return seg;
+ }
+
+ /** @brief Create a segment of this line.
+ * @param f Time value for the initial point of the segment
+ * @param t Time value for the final point of the segment
+ * @return Created line segment */
+ LineSegment segment(Coord f, Coord t) const {
+ return LineSegment(pointAt(f), pointAt(t));
+ }
+
+ /// Return the portion of the line that is inside the given rectangle
+ std::optional<LineSegment> clip(Rect const &r) const;
+
+ /** @brief Create a ray starting at the specified time value.
+ * The created ray will go in the direction of the line's vector (in the direction
+ * of increasing time values).
+ * @param t Time value where the ray should start
+ * @return Ray starting at t and going in the direction of the vector */
+ Ray ray(Coord t) {
+ Ray result;
+ result.setOrigin(pointAt(t));
+ result.setVector(vector());
+ return result;
+ }
+
+ /** @brief Create a derivative of the line.
+ * The new line will always be degenerate. Its origin will be equal to this
+ * line's vector. */
+ Line derivative() const {
+ Point v = vector();
+ Line result(v, v);
+ return result;
+ }
+
+ /// Create a line transformed by an affine transformation.
+ Line transformed(Affine const& m) const {
+ Line l(_initial * m, _final * m);
+ return l;
+ }
+
+ /** @brief Get a unit vector normal to the line.
+ * If Y grows upwards, then this is the left normal. If Y grows downwards,
+ * then this is the right normal. */
+ Point normal() const {
+ return rot90(vector()).normalized();
+ }
+
+ // what does this do?
+ Point normalAndDist(double & dist) const {
+ Point n = normal();
+ dist = -dot(n, _initial);
+ return n;
+ }
+
+ /// Compute an affine matrix representing a reflection about the line.
+ Affine reflection() const {
+ Point v = versor();
+ Coord x2 = v[X]*v[X], y2 = v[Y]*v[Y], xy = v[X]*v[Y];
+ Affine m(x2-y2, 2.*xy,
+ 2.*xy, y2-x2,
+ _initial[X], _initial[Y]);
+ m = Translate(-_initial) * m;
+ return m;
+ }
+
+ /** @brief Compute an affine which transforms all points on the line to zero X or Y coordinate.
+ * This operation is useful in reducing intersection problems to root-finding problems.
+ * There are many affines which do this transformation. This function returns one that
+ * preserves angles, areas and distances - a rotation combined with a translation, and
+ * additionally moves the initial point of the line to (0,0). This way it works without
+ * problems even for lines perpendicular to the target, though may in some cases have
+ * lower precision than e.g. a shear transform.
+ * @param d Which coordinate of points on the line should be zero after the transformation */
+ Affine rotationToZero(Dim2 d) const {
+ Point v = vector();
+ if (d == X) {
+ std::swap(v[X], v[Y]);
+ } else {
+ v[Y] = -v[Y];
+ }
+ Affine m = Translate(-_initial) * Rotate(v);
+ return m;
+ }
+ /** @brief Compute a rotation affine which transforms the line to one of the axes.
+ * @param d Which line should be the axis */
+ Affine rotationToAxis(Dim2 d) const {
+ Affine m = rotationToZero(other_dimension(d));
+ return m;
+ }
+
+ Affine transformTo(Line const &other) const;
+ /// @}
+
+ std::vector<ShapeIntersection> intersect(Line const &other) const;
+ std::vector<ShapeIntersection> intersect(Ray const &r) const;
+ std::vector<ShapeIntersection> intersect(LineSegment const &ls) const;
+
+ template <typename T>
+ Line &operator*=(T const &tr) {
+ BOOST_CONCEPT_ASSERT((TransformConcept<T>));
+ _initial *= tr;
+ _final *= tr;
+ return *this;
+ }
+
+ bool operator==(Line const &other) const {
+ if (distance(pointAt(nearestTime(other._initial)), other._initial) != 0) return false;
+ if (distance(pointAt(nearestTime(other._final)), other._final) != 0) return false;
+ return true;
+ }
+
+ template <typename T>
+ friend Line operator*(Line const &l, T const &tr) {
+ BOOST_CONCEPT_ASSERT((TransformConcept<T>));
+ Line result(l);
+ result *= tr;
+ return result;
+ }
+}; // end class Line
+
+/** @brief Removes intersections outside of the unit interval.
+ * A helper used to implement line segment intersections.
+ * @param xs Line intersections
+ * @param a Whether the first time value has to be in the unit interval
+ * @param b Whether the second time value has to be in the unit interval
+ * @return Appropriately filtered intersections */
+void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true);
+void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true);
+
+/// @brief Compute distance from point to line.
+/// @relates Line
+inline
+double distance(Point const &p, Line const &line)
+{
+ if (line.isDegenerate()) {
+ return ::Geom::distance(p, line.initialPoint());
+ } else {
+ Coord t = line.nearestTime(p);
+ return ::Geom::distance(line.pointAt(t), p);
+ }
+}
+
+inline
+bool are_near(Point const &p, Line const &line, double eps = EPSILON)
+{
+ return are_near(distance(p, line), 0, eps);
+}
+
+inline
+bool are_parallel(Line const &l1, Line const &l2, double eps = EPSILON)
+{
+ return are_near(cross(l1.vector(), l2.vector()), 0, eps);
+}
+
+/** @brief Test whether two lines are approximately the same.
+ * This tests for being parallel and the origin of one line being close to the other,
+ * so it tests whether the images of the lines are similar, not whether the same time values
+ * correspond to similar points. For example a line from (1,1) to (2,2) and a line from
+ * (-1,-1) to (0,0) will be the same, because their images match, even though there is
+ * no time value for which the lines give similar points.
+ * @relates Line */
+inline
+bool are_same(Line const &l1, Line const &l2, double eps = EPSILON)
+{
+ return are_parallel(l1, l2, eps) && are_near(l1.origin(), l2, eps);
+}
+
+/// Test whether two lines are perpendicular.
+/// @relates Line
+inline
+bool are_orthogonal(Line const &l1, Line const &l2, double eps = EPSILON)
+{
+ return are_near(dot(l1.vector(), l2.vector()), 0, eps);
+}
+
+// evaluate the angle between l1 and l2 rotating l1 in cw direction
+// until it overlaps l2
+// the returned value is an angle in the interval [0, PI[
+inline
+double angle_between(Line const& l1, Line const& l2)
+{
+ double angle = angle_between(l1.vector(), l2.vector());
+ if (angle < 0) angle += M_PI;
+ if (angle == M_PI) angle = 0;
+ return angle;
+}
+
+inline
+double distance(Point const &p, LineSegment const &seg)
+{
+ double t = seg.nearestTime(p);
+ return distance(p, seg.pointAt(t));
+}
+
+inline
+bool are_near(Point const &p, LineSegment const &seg, double eps = EPSILON)
+{
+ return are_near(distance(p, seg), 0, eps);
+}
+
+// build a line passing by _point and orthogonal to _line
+inline
+Line make_orthogonal_line(Point const &p, Line const &line)
+{
+ Point d = line.vector().cw();
+ Line l(p, p + d);
+ return l;
+}
+
+// build a line passing by _point and parallel to _line
+inline
+Line make_parallel_line(Point const &p, Line const &line)
+{
+ Line result(line);
+ result.setOrigin(p);
+ return result;
+}
+
+// build a line passing by the middle point of _segment and orthogonal to it.
+inline
+Line make_bisector_line(LineSegment const& _segment)
+{
+ return make_orthogonal_line( middle_point(_segment), Line(_segment) );
+}
+
+// build the bisector line of the angle between ray(O,A) and ray(O,B)
+inline
+Line make_angle_bisector_line(Point const &A, Point const &O, Point const &B)
+{
+ AngleInterval ival(Angle(A-O), Angle(B-O));
+ Angle bisect = ival.angleAt(0.5);
+ return Line(O, bisect);
+}
+
+// prj(P) = rot(v, Point( rot(-v, P-O)[X], 0 )) + O
+inline
+Point projection(Point const &p, Line const &line)
+{
+ return line.pointAt(line.nearestTime(p));
+}
+
+inline
+LineSegment projection(LineSegment const &seg, Line const &line)
+{
+ return line.segment(line.nearestTime(seg.initialPoint()),
+ line.nearestTime(seg.finalPoint()));
+}
+
+inline
+std::optional<LineSegment> clip(Line const &l, Rect const &r) {
+ return l.clip(r);
+}
+
+
+namespace detail
+{
+
+OptCrossing intersection_impl(Ray const& r1, Line const& l2, unsigned int i);
+OptCrossing intersection_impl( LineSegment const& ls1,
+ Line const& l2,
+ unsigned int i );
+OptCrossing intersection_impl( LineSegment const& ls1,
+ Ray const& r2,
+ unsigned int i );
+}
+
+
+inline
+OptCrossing intersection(Ray const& r1, Line const& l2)
+{
+ return detail::intersection_impl(r1, l2, 0);
+
+}
+
+inline
+OptCrossing intersection(Line const& l1, Ray const& r2)
+{
+ return detail::intersection_impl(r2, l1, 1);
+}
+
+inline
+OptCrossing intersection(LineSegment const& ls1, Line const& l2)
+{
+ return detail::intersection_impl(ls1, l2, 0);
+}
+
+inline
+OptCrossing intersection(Line const& l1, LineSegment const& ls2)
+{
+ return detail::intersection_impl(ls2, l1, 1);
+}
+
+inline
+OptCrossing intersection(LineSegment const& ls1, Ray const& r2)
+{
+ return detail::intersection_impl(ls1, r2, 0);
+
+}
+
+inline
+OptCrossing intersection(Ray const& r1, LineSegment const& ls2)
+{
+ return detail::intersection_impl(ls2, r1, 1);
+}
+
+
+OptCrossing intersection(Line const& l1, Line const& l2);
+
+OptCrossing intersection(Ray const& r1, Ray const& r2);
+
+OptCrossing intersection(LineSegment const& ls1, LineSegment const& ls2);
+
+
+} // end namespace Geom
+
+
+#endif // LIB2GEOM_SEEN_LINE_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/include/2geom/linear.h b/include/2geom/linear.h
new file mode 100644
index 0000000..75c6e01
--- /dev/null
+++ b/include/2geom/linear.h
@@ -0,0 +1,167 @@
+/**
+ * \file
+ * \brief Linear fragment function class
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2006-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_LINEAR_H
+#define LIB2GEOM_SEEN_LINEAR_H
+
+#include <2geom/interval.h>
+#include <2geom/math-utils.h>
+
+namespace Geom {
+
+class SBasis;
+
+/**
+ * @brief Function that interpolates linearly between two values.
+ * @ingroup Fragments
+ */
+class Linear
+ : boost::additive< Linear
+ , boost::arithmetic< Linear, Coord
+ , boost::equality_comparable< Linear
+ > > >
+{
+public:
+ Coord a[2];
+ Linear() {a[0]=0; a[1]=0;}
+ Linear(Coord aa, Coord b) {a[0] = aa; a[1] = b;}
+ Linear(Coord aa) {a[0] = aa; a[1] = aa;}
+
+ Coord operator[](unsigned i) const {
+ assert(i < 2);
+ return a[i];
+ }
+ Coord &operator[](unsigned i) {
+ assert(i < 2);
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef Coord output_type;
+ bool isZero(Coord eps=EPSILON) const { return are_near(a[0], 0., eps) && are_near(a[1], 0., eps); }
+ bool isConstant(Coord eps=EPSILON) const { return are_near(a[0], a[1], eps); }
+ bool isFinite() const { return std::isfinite(a[0]) && std::isfinite(a[1]); }
+
+ Coord at0() const { return a[0]; }
+ Coord &at0() { return a[0]; }
+ Coord at1() const { return a[1]; }
+ Coord &at1() { return a[1]; }
+
+ Coord valueAt(Coord t) const { return lerp(t, a[0], a[1]); }
+ Coord operator()(Coord t) const { return valueAt(t); }
+
+ // not very useful, but required for FragmentConcept
+ std::vector<Coord> valueAndDerivatives(Coord t, unsigned n) {
+ std::vector<Coord> result(n+1, 0.0);
+ result[0] = valueAt(t);
+ if (n >= 1) {
+ result[1] = a[1] - a[0];
+ }
+ return result;
+ }
+
+ //defined in sbasis.h
+ inline SBasis toSBasis() const;
+
+ OptInterval bounds_exact() const { return Interval(a[0], a[1]); }
+ OptInterval bounds_fast() const { return bounds_exact(); }
+ OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+
+ double tri() const {
+ return a[1] - a[0];
+ }
+ double hat() const {
+ return (a[1] + a[0])/2;
+ }
+
+ // addition of other Linears
+ Linear &operator+=(Linear const &other) {
+ a[0] += other.a[0];
+ a[1] += other.a[1];
+ return *this;
+ }
+ Linear &operator-=(Linear const &other) {
+ a[0] -= other.a[0];
+ a[1] -= other.a[1];
+ return *this;
+ }
+
+ //
+ Linear &operator+=(Coord x) {
+ a[0] += x; a[1] += x;
+ return *this;
+ }
+ Linear &operator-=(Coord x) {
+ a[0] -= x; a[1] -= x;
+ return *this;
+ }
+ Linear &operator*=(Coord x) {
+ a[0] *= x; a[1] *= x;
+ return *this;
+ }
+ Linear &operator/=(Coord x) {
+ a[0] /= x; a[1] /= x;
+ return *this;
+ }
+ Linear operator-() const {
+ Linear ret(-a[0], -a[1]);
+ return ret;
+ }
+
+ bool operator==(Linear const &other) const {
+ return a[0] == other.a[0] && a[1] == other.a[1];
+ }
+};
+
+inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); }
+inline Linear portion(Linear const &a, Coord from, Coord to) {
+ Linear result(a.valueAt(from), a.valueAt(to));
+ return result;
+}
+
+} // end namespace Geom
+
+#endif //LIB2GEOM_SEEN_LINEAR_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/include/2geom/math-utils.h b/include/2geom/math-utils.h
new file mode 100644
index 0000000..4c35a80
--- /dev/null
+++ b/include/2geom/math-utils.h
@@ -0,0 +1,140 @@
+/**
+ * \file
+ * \brief Low level math functions and compatibility wrappers
+ *//*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Michael G. Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2006-2009 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 LIB2GEOM_SEEN_MATH_UTILS_H
+#define LIB2GEOM_SEEN_MATH_UTILS_H
+
+#include <math.h> // sincos is usually only available in math.h
+#include <array>
+#include <cmath>
+#include <utility> // for std::pair
+#include <boost/math/special_functions/fpclassify.hpp>
+
+namespace Geom {
+
+/** @brief Sign function - indicates the sign of a numeric type.
+ * Mathsy people will know this is basically the derivative of abs, except for the fact
+ * that it is defined on 0.
+ * @return -1 when x is negative, 1 when positive, and 0 if equal to 0. */
+template <class T> inline int sgn(const T& x) {
+ return (x < 0 ? -1 : (x > 0 ? 1 : 0) );
+// can we 'optimize' this with:
+// return ( (T(0) < x) - (x < T(0)) );
+}
+
+template <class T> inline T sqr(const T& x) {return x * x;}
+template <class T> inline T cube(const T& x) {return x * x * x;}
+
+/** Between function - returns true if a number x is within a range: (min < x) && (max > x).
+ * The values delimiting the range and the number must have the same type.
+ */
+template <class T> inline const T& between (const T& min, const T& max, const T& x)
+ { return (min < x) && (max > x); }
+
+/** @brief Returns @a x rounded to the nearest multiple of \f$10^{p}\f$.
+
+ Implemented in terms of round, i.e. we make no guarantees as to what happens if x is
+ half way between two rounded numbers.
+
+ Note: places is the number of decimal places without using scientific (e) notation, not the
+ number of significant figures. This function may not be suitable for values of x whose
+ magnitude is so far from 1 that one would want to use scientific (e) notation.
+
+ places may be negative: e.g. places = -2 means rounding to a multiple of .01
+**/
+inline double decimal_round(double x, int p) {
+ //TODO: possibly implement with modulus instead?
+ double const multiplier = ::pow(10.0, p);
+ return ::round( x * multiplier ) / multiplier;
+}
+
+/** @brief Simultaneously compute a sine and a cosine of the same angle.
+ * This function can be up to 2 times faster than separate computation, depending
+ * on the platform. It uses the standard library function sincos() if available.
+ * @param angle Angle
+ * @param sin_ Variable that will store the sine
+ * @param cos_ Variable that will store the cosine */
+inline void sincos(double angle, double &sin_, double &cos_) {
+#ifdef HAVE_SINCOS
+ ::sincos(angle, &sin_, &cos_);
+#else
+ sin_ = ::sin(angle);
+ cos_ = ::cos(angle);
+#endif
+}
+
+/** @brief Scale the doubles in the passed array to make them "reasonably large".
+ *
+ * All doubles in the passed array will get scaled by the same power of 2 (which is
+ * a lossless operation) in such a way that their geometric average gets closer to 1.
+ *
+ * @tparam N The size of the passed array.
+ * @param[in,out] values The doubles to be rescaled in place.
+ * @return The exponent in the power of two by which the doubles got scaled.
+ */
+template <size_t N>
+inline int rescale_homogenous(std::array<double, N> &values)
+{
+ if constexpr (N == 0) {
+ return 0;
+ }
+ std::array<int, N> exponents;
+ std::array<double, N> mantissas;
+ int average = 0;
+ for (size_t i = 0; i < N; i++) {
+ mantissas[i] = std::frexp(values[i], &exponents[i]);
+ average += exponents[i];
+ }
+ average /= (int)N;
+ for (size_t i = 0; i < N; i++) {
+ values[i] = std::ldexp(mantissas[i], exponents[i] - average);
+ }
+ return -average;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_MATH_UTILS_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/include/2geom/nearest-time.h b/include/2geom/nearest-time.h
new file mode 100644
index 0000000..007cd27
--- /dev/null
+++ b/include/2geom/nearest-time.h
@@ -0,0 +1,141 @@
+/** @file
+ * @brief Nearest time routines for D2<SBasis> and Piecewise<D2<SBasis>>
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2007-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 LIB2GEOM_SEEN_NEAREST_TIME_H
+#define LIB2GEOM_SEEN_NEAREST_TIME_H
+
+
+#include <vector>
+
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+#include <2geom/exception.h>
+#include <2geom/bezier.h>
+
+
+namespace Geom
+{
+
+/*
+ * Given a line L specified by a point A and direction vector v,
+ * return the point on L nearest to p. Note that the returned value
+ * is with respect to the _normalized_ direction of v!
+ */
+inline double nearest_time(Point const &p, Point const &A, Point const &v)
+{
+ Point d(p - A);
+ return d[0] * v[0] + d[1] * v[1];
+}
+
+Coord nearest_time(Point const &p, D2<Bezier> const &bez, Coord from = 0, Coord to = 1);
+
+////////////////////////////////////////////////////////////////////////////////
+// D2<SBasis> versions
+
+/*
+ * Return the parameter t of a nearest point on the portion of the curve "c",
+ * related to the interval [from, to], to the point "p".
+ * The needed curve derivative "deriv" is passed as parameter.
+ * The function return the first nearest point to "p" that is found.
+ */
+double nearest_time(Point const &p,
+ D2<SBasis> const &c, D2<SBasis> const &deriv,
+ double from = 0, double to = 1);
+
+inline
+double nearest_time(Point const &p,
+ D2<SBasis> const &c,
+ double from = 0, double to = 1 )
+{
+ return nearest_time(p, c, Geom::derivative(c), from, to);
+}
+
+/*
+ * Return the parameters t of all the nearest times on the portion of
+ * the curve "c", related to the interval [from, to], to the point "p".
+ * The needed curve derivative "dc" is passed as parameter.
+ */
+std::vector<double>
+all_nearest_times(Point const& p,
+ D2<SBasis> const& c, D2<SBasis> const& dc,
+ double from = 0, double to = 1 );
+
+inline
+std::vector<double>
+all_nearest_times(Point const &p,
+ D2<SBasis> const &c,
+ double from = 0, double to = 1)
+{
+ return all_nearest_times(p, c, Geom::derivative(c), from, to);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Piecewise< D2<SBasis> > versions
+
+double nearest_time(Point const &p,
+ Piecewise< D2<SBasis> > const &c,
+ double from, double to);
+
+inline
+double nearest_time(Point const& p, Piecewise< D2<SBasis> > const &c)
+{
+ return nearest_time(p, c, c.cuts[0], c.cuts[c.size()]);
+}
+
+
+std::vector<double>
+all_nearest_times(Point const &p,
+ Piecewise< D2<SBasis> > const &c,
+ double from, double to);
+
+inline
+std::vector<double>
+all_nearest_times( Point const& p, Piecewise< D2<SBasis> > const& c )
+{
+ return all_nearest_times(p, c, c.cuts[0], c.cuts[c.size()]);
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_NEAREST_TIME_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/include/2geom/numeric/fitting-model.h b/include/2geom/numeric/fitting-model.h
new file mode 100644
index 0000000..0316f57
--- /dev/null
+++ b/include/2geom/numeric/fitting-model.h
@@ -0,0 +1,521 @@
+/*
+ * Fitting Models for Geom Types
+ *
+ * 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 _NL_FITTING_MODEL_H_
+#define _NL_FITTING_MODEL_H_
+
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/polynomial.h>
+#include <2geom/ellipse.h>
+#include <2geom/circle.h>
+#include <2geom/utils.h>
+#include <2geom/conicsec.h>
+
+
+namespace Geom { namespace NL {
+
+/*
+ * A model is an abstraction for an expression dependent from a parameter where
+ * the coefficients of this expression are the unknowns of the fitting problem.
+ * For a ceratain number of parameter values we know the related values
+ * the expression evaluates to: from each parameter value we get a row of
+ * the matrix of the fitting problem, from each expression value we get
+ * the related constant term.
+ * Example: given the model a*x^2 + b*x + c = 0; from x = 1 we get
+ * the equation a + b + c = 0, in this example the constant term is always
+ * the same for each parameter value.
+ *
+ * A model is required to implement 3 methods:
+ *
+ * - size : returns the number of unknown coefficients that appear in
+ * the expression of the fitting problem;
+ * - feed : its input is a parameter value and the related expression value,
+ * it generates a matrix row and a new entry of the constant vector
+ * of the fitting problem;
+ * - instance : it has an input parameter represented by the raw vector
+ * solution of the fitting problem and an output parameter
+ * of type InstanceType that return a specific object that is
+ * generated using the fitting problem solution, in the example
+ * above the object could be a Poly type.
+ */
+
+/*
+ * completely unknown models must inherit from this template class;
+ * example: the model a*x^2 + b*x + c = 0 to be solved wrt a, b, c;
+ * example: the model A(t) = known_sample_value_at(t) to be solved wrt
+ * the coefficients of the curve A(t) expressed in S-Basis form;
+ * parameter type: the type of x and t variable in the examples above;
+ * value type: the type of the known sample values (in the first example
+ * is constant )
+ * instance type: the type of the objects produced by using
+ * the fitting raw data solution
+ */
+
+
+
+
+template< typename ParameterType, typename ValueType, typename InstanceType >
+class LinearFittingModel
+{
+ public:
+ typedef ParameterType parameter_type;
+ typedef ValueType value_type;
+ typedef InstanceType instance_type;
+
+ static const bool WITH_FIXED_TERMS = false;
+
+ /*
+ * a LinearFittingModel must implement the following methods:
+ *
+ * void feed( VectorView & vector,
+ * parameter_type const& sample_parameter ) const;
+ *
+ * size_t size() const;
+ *
+ * void instance(instance_type &, raw_type const& raw_data) const;
+ *
+ */
+};
+
+
+/*
+ * partially known models must inherit from this template class
+ * example: the model a*x^2 + 2*x + c = 0 to be solved wrt a and c
+ */
+template< typename ParameterType, typename ValueType, typename InstanceType >
+class LinearFittingModelWithFixedTerms
+{
+ public:
+ typedef ParameterType parameter_type;
+ typedef ValueType value_type;
+ typedef InstanceType instance_type;
+
+ static const bool WITH_FIXED_TERMS = true;
+
+ /*
+ * a LinearFittingModelWithFixedTerms must implement the following methods:
+ *
+ * void feed( VectorView & vector,
+ * value_type & fixed_term,
+ * parameter_type const& sample_parameter ) const;
+ *
+ * size_t size() const;
+ *
+ * void instance(instance_type &, raw_type const& raw_data) const;
+ *
+ */
+
+
+};
+
+
+// incomplete model, it can be inherited to make up different kinds of
+// instance type; the raw data is a vector of coefficients of a polynomial
+// represented in standard power basis
+template< typename InstanceType >
+class LFMPowerBasis
+ : public LinearFittingModel<double, double, InstanceType>
+{
+ public:
+ LFMPowerBasis(size_t degree)
+ : m_size(degree + 1)
+ {
+ }
+
+ void feed( VectorView & coeff, double sample_parameter ) const
+ {
+ coeff[0] = 1;
+ double x_i = 1;
+ for (size_t i = 1; i < coeff.size(); ++i)
+ {
+ x_i *= sample_parameter;
+ coeff[i] = x_i;
+ }
+ }
+
+ size_t size() const
+ {
+ return m_size;
+ }
+
+ private:
+ size_t m_size;
+};
+
+
+// this model generates Geom::Poly objects
+class LFMPoly
+ : public LFMPowerBasis<Poly>
+{
+ public:
+ LFMPoly(size_t degree)
+ : LFMPowerBasis<Poly>(degree)
+ {
+ }
+
+ void instance(Poly & poly, ConstVectorView const& raw_data) const
+ {
+ poly.clear();
+ poly.resize(size());
+ for (size_t i = 0; i < raw_data.size(); ++i)
+ {
+ poly[i] = raw_data[i];
+ }
+ }
+};
+
+
+// incomplete model, it can be inherited to make up different kinds of
+// instance type; the raw data is a vector of coefficients of a polynomial
+// represented in standard power basis with leading term coefficient equal to 1
+template< typename InstanceType >
+class LFMNormalizedPowerBasis
+ : public LinearFittingModelWithFixedTerms<double, double, InstanceType>
+{
+ public:
+ LFMNormalizedPowerBasis(size_t _degree)
+ : m_model( _degree - 1)
+ {
+ assert(_degree > 0);
+ }
+
+
+ void feed( VectorView & coeff,
+ double & known_term,
+ double sample_parameter ) const
+ {
+ m_model.feed(coeff, sample_parameter);
+ known_term = coeff[m_model.size()-1] * sample_parameter;
+ }
+
+ size_t size() const
+ {
+ return m_model.size();
+ }
+
+ private:
+ LFMPowerBasis<InstanceType> m_model;
+};
+
+
+// incomplete model, it can be inherited to make up different kinds of
+// instance type; the raw data is a vector of coefficients of the equation
+// of an ellipse curve
+//template< typename InstanceType >
+//class LFMEllipseEquation
+// : public LinearFittingModelWithFixedTerms<Point, double, InstanceType>
+//{
+// public:
+// void feed( VectorView & coeff, double & fixed_term, Point const& p ) const
+// {
+// coeff[0] = p[X] * p[Y];
+// coeff[1] = p[Y] * p[Y];
+// coeff[2] = p[X];
+// coeff[3] = p[Y];
+// coeff[4] = 1;
+// fixed_term = p[X] * p[X];
+// }
+//
+// size_t size() const
+// {
+// return 5;
+// }
+//};
+
+// incomplete model, it can be inherited to make up different kinds of
+// instance type; the raw data is a vector of coefficients of the equation
+// of a conic section
+template< typename InstanceType >
+class LFMConicEquation
+ : public LinearFittingModelWithFixedTerms<Point, double, InstanceType>
+{
+ public:
+ void feed( VectorView & coeff, double & fixed_term, Point const& p ) const
+ {
+ coeff[0] = p[X] * p[Y];
+ coeff[1] = p[Y] * p[Y];
+ coeff[2] = p[X];
+ coeff[3] = p[Y];
+ coeff[4] = 1;
+ fixed_term = p[X] * p[X];
+ }
+
+ size_t size() const
+ {
+ return 5;
+ }
+};
+
+// this model generates Ellipse curves
+class LFMConicSection
+ : public LFMConicEquation<xAx>
+{
+ public:
+ void instance(xAx & c, ConstVectorView const& coeff) const
+ {
+ c.set(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]);
+ }
+};
+
+// this model generates Ellipse curves
+class LFMEllipse
+ : public LFMConicEquation<Ellipse>
+{
+ public:
+ void instance(Ellipse & e, ConstVectorView const& coeff) const
+ {
+ e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]);
+ }
+};
+
+
+// incomplete model, it can be inherited to make up different kinds of
+// instance type; the raw data is a vector of coefficients of the equation
+// of a circle curve
+template< typename InstanceType >
+class LFMCircleEquation
+ : public LinearFittingModelWithFixedTerms<Point, double, InstanceType>
+{
+ public:
+ void feed( VectorView & coeff, double & fixed_term, Point const& p ) const
+ {
+ coeff[0] = p[X];
+ coeff[1] = p[Y];
+ coeff[2] = 1;
+ fixed_term = p[X] * p[X] + p[Y] * p[Y];
+ }
+
+ size_t size() const
+ {
+ return 3;
+ }
+};
+
+
+// this model generates Ellipse curves
+class LFMCircle
+ : public LFMCircleEquation<Circle>
+{
+ public:
+ void instance(Circle & c, ConstVectorView const& coeff) const
+ {
+ c.setCoefficients(1, coeff[0], coeff[1], coeff[2]);
+ }
+};
+
+
+// this model generates SBasis objects
+class LFMSBasis
+ : public LinearFittingModel<double, double, SBasis>
+{
+ public:
+ LFMSBasis( size_t _order )
+ : m_size( 2*(_order+1) ),
+ m_order(_order)
+ {
+ }
+
+ void feed( VectorView & coeff, double t ) const
+ {
+ double u0 = 1-t;
+ double u1 = t;
+ double s = u0 * u1;
+ coeff[0] = u0;
+ coeff[1] = u1;
+ for (size_t i = 2; i < size(); i+=2)
+ {
+ u0 *= s;
+ u1 *= s;
+ coeff[i] = u0;
+ coeff[i+1] = u1;
+ }
+ }
+
+ size_t size() const
+ {
+ return m_size;
+ }
+
+ void instance(SBasis & sb, ConstVectorView const& raw_data) const
+ {
+ sb.resize(m_order+1);
+ for (unsigned int i = 0, k = 0; i < raw_data.size(); i+=2, ++k)
+ {
+ sb[k][0] = raw_data[i];
+ sb[k][1] = raw_data[i+1];
+ }
+ }
+
+ private:
+ size_t m_size;
+ size_t m_order;
+};
+
+
+// this model generates D2<SBasis> objects
+class LFMD2SBasis
+ : public LinearFittingModel< double, Point, D2<SBasis> >
+{
+ public:
+ LFMD2SBasis( size_t _order )
+ : mosb(_order)
+ {
+ }
+
+ void feed( VectorView & coeff, double t ) const
+ {
+ mosb.feed(coeff, t);
+ }
+
+ size_t size() const
+ {
+ return mosb.size();
+ }
+
+ void instance(D2<SBasis> & d2sb, ConstMatrixView const& raw_data) const
+ {
+ mosb.instance(d2sb[X], raw_data.column_const_view(X));
+ mosb.instance(d2sb[Y], raw_data.column_const_view(Y));
+ }
+
+ private:
+ LFMSBasis mosb;
+};
+
+
+// this model generates Bezier objects
+class LFMBezier
+ : public LinearFittingModel<double, double, Bezier>
+{
+ public:
+ LFMBezier( size_t _order )
+ : m_size(_order + 1),
+ m_order(_order)
+ {
+ binomial_coefficients(m_bc, m_order);
+ }
+
+ void feed( VectorView & coeff, double t ) const
+ {
+ double s = 1;
+ for (size_t i = 0; i < size(); ++i)
+ {
+ coeff[i] = s * m_bc[i];
+ s *= t;
+ }
+ double u = 1-t;
+ s = 1;
+ for (size_t i = size()-1; i > 0; --i)
+ {
+ coeff[i] *= s;
+ s *= u;
+ }
+ coeff[0] *= s;
+ }
+
+ size_t size() const
+ {
+ return m_size;
+ }
+
+ void instance(Bezier & b, ConstVectorView const& raw_data) const
+ {
+ assert(b.size() == raw_data.size());
+ for (unsigned int i = 0; i < raw_data.size(); ++i)
+ {
+ b[i] = raw_data[i];
+ }
+ }
+
+ private:
+ size_t m_size;
+ size_t m_order;
+ std::vector<size_t> m_bc;
+};
+
+
+// this model generates Bezier curves
+template <unsigned degree>
+class LFMBezierCurveN
+ : public LinearFittingModel< double, Point, BezierCurveN<degree> >
+{
+ public:
+ LFMBezierCurveN()
+ : mob(degree+1)
+ {
+ }
+
+ void feed( VectorView & coeff, double t ) const
+ {
+ mob.feed(coeff, t);
+ }
+
+ size_t size() const
+ {
+ return mob.size();
+ }
+
+ void instance(BezierCurveN<degree> & bc, ConstMatrixView const& raw_data) const
+ {
+ Bezier bx(degree);
+ Bezier by(degree);
+ mob.instance(bx, raw_data.column_const_view(X));
+ mob.instance(by, raw_data.column_const_view(Y));
+ bc = BezierCurveN<degree>(bx, by);
+ }
+
+ private:
+ LFMBezier mob;
+};
+
+} // end namespace NL
+} // end namespace Geom
+
+
+#endif // _NL_FITTING_MODEL_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/include/2geom/numeric/fitting-tool.h b/include/2geom/numeric/fitting-tool.h
new file mode 100644
index 0000000..78d66ca
--- /dev/null
+++ b/include/2geom/numeric/fitting-tool.h
@@ -0,0 +1,562 @@
+/*
+ * Fitting Tools
+ *
+ * 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 _NL_FITTING_TOOL_H_
+#define _NL_FITTING_TOOL_H_
+
+
+#include <2geom/numeric/vector.h>
+#include <2geom/numeric/matrix.h>
+
+#include <2geom/point.h>
+
+#include <vector>
+
+
+/*
+ * The least_square_fitter class represents a tool for solving a fitting
+ * problem with respect to a given model that represents an expression
+ * dependent from a parameter where the coefficients of this expression
+ * are the unknowns of the fitting problem.
+ * The minimizing solution is found by computing the pseudo-inverse
+ * of the problem matrix
+ */
+
+
+namespace Geom { namespace NL {
+
+namespace detail {
+
+template< typename ModelT>
+class lsf_base
+{
+ public:
+ typedef ModelT model_type;
+ typedef typename model_type::parameter_type parameter_type;
+ typedef typename model_type::value_type value_type;
+
+ lsf_base( model_type const& _model, size_t forecasted_samples )
+ : m_model(_model),
+ m_total_samples(0),
+ m_matrix(forecasted_samples, m_model.size()),
+ m_psdinv_matrix(NULL)
+ {
+ }
+
+ // compute pseudo inverse
+ void update()
+ {
+ if (total_samples() == 0) return;
+ if (m_psdinv_matrix != NULL)
+ {
+ delete m_psdinv_matrix;
+ }
+ MatrixView mv(m_matrix, 0, 0, total_samples(), m_matrix.columns());
+ m_psdinv_matrix = new Matrix( pseudo_inverse(mv) );
+ assert(m_psdinv_matrix != NULL);
+ }
+
+ size_t total_samples() const
+ {
+ return m_total_samples;
+ }
+
+ bool is_full() const
+ {
+ return (total_samples() == m_matrix.rows());
+ }
+
+ void clear()
+ {
+ m_total_samples = 0;
+ }
+
+ virtual
+ ~lsf_base()
+ {
+ if (m_psdinv_matrix != NULL)
+ {
+ delete m_psdinv_matrix;
+ }
+ }
+
+ protected:
+ const model_type & m_model;
+ size_t m_total_samples;
+ Matrix m_matrix;
+ Matrix* m_psdinv_matrix;
+
+}; // end class lsf_base
+
+
+
+
+template< typename ModelT, typename ValueType = typename ModelT::value_type>
+class lsf_solution
+{
+};
+
+// a fitting process on samples with value of type double
+// produces a solution of type Vector
+template< typename ModelT>
+class lsf_solution<ModelT, double>
+ : public lsf_base<ModelT>
+{
+public:
+ typedef ModelT model_type;
+ typedef typename model_type::parameter_type parameter_type;
+ typedef typename model_type::value_type value_type;
+ typedef Vector solution_type;
+ typedef lsf_base<model_type> base_type;
+
+ using base_type::m_model;
+ using base_type::m_psdinv_matrix;
+ using base_type::total_samples;
+
+public:
+ lsf_solution<ModelT, double>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples),
+ m_solution(_model.size())
+ {
+ }
+
+ template< typename VectorT >
+ solution_type& result(VectorT const& sample_values)
+ {
+ assert(sample_values.size() == total_samples());
+ ConstVectorView sv(sample_values);
+ m_solution = (*m_psdinv_matrix) * sv;
+ return m_solution;
+ }
+
+ // a comparison between old sample values and the new ones is performed
+ // in order to minimize computation
+ // prerequisite:
+ // old_sample_values.size() == new_sample_values.size()
+ // no update() call can be performed between two result invocations
+ template< typename VectorT >
+ solution_type& result( VectorT const& old_sample_values,
+ VectorT const& new_sample_values )
+ {
+ assert(old_sample_values.size() == total_samples());
+ assert(new_sample_values.size() == total_samples());
+ Vector diff(total_samples());
+ for (size_t i = 0; i < diff.size(); ++i)
+ {
+ diff[i] = new_sample_values[i] - old_sample_values[i];
+ }
+ Vector column(m_model.size());
+ Vector delta(m_model.size(), 0.0);
+ for (size_t i = 0; i < diff.size(); ++i)
+ {
+ if (diff[i] != 0)
+ {
+ column = m_psdinv_matrix->column_view(i);
+ column.scale(diff[i]);
+ delta += column;
+ }
+ }
+ m_solution += delta;
+ return m_solution;
+ }
+
+ solution_type& result()
+ {
+ return m_solution;
+ }
+
+private:
+ solution_type m_solution;
+
+}; // end class lsf_solution<ModelT, double>
+
+
+// a fitting process on samples with value of type Point
+// produces a solution of type Matrix (with 2 columns)
+template< typename ModelT>
+class lsf_solution<ModelT, Point>
+ : public lsf_base<ModelT>
+{
+public:
+ typedef ModelT model_type;
+ typedef typename model_type::parameter_type parameter_type;
+ typedef typename model_type::value_type value_type;
+ typedef Matrix solution_type;
+ typedef lsf_base<model_type> base_type;
+
+ using base_type::m_model;
+ using base_type::m_psdinv_matrix;
+ using base_type::total_samples;
+
+public:
+ lsf_solution<ModelT, Point>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples),
+ m_solution(_model.size(), 2)
+ {
+ }
+
+ solution_type& result(std::vector<Point> const& sample_values)
+ {
+ assert(sample_values.size() == total_samples());
+ Matrix svm(total_samples(), 2);
+ for (size_t i = 0; i < total_samples(); ++i)
+ {
+ svm(i, X) = sample_values[i][X];
+ svm(i, Y) = sample_values[i][Y];
+ }
+ m_solution = (*m_psdinv_matrix) * svm;
+ return m_solution;
+ }
+
+ // a comparison between old sample values and the new ones is performed
+ // in order to minimize computation
+ // prerequisite:
+ // old_sample_values.size() == new_sample_values.size()
+ // no update() call can to be performed between two result invocations
+ solution_type& result( std::vector<Point> const& old_sample_values,
+ std::vector<Point> const& new_sample_values )
+ {
+ assert(old_sample_values.size() == total_samples());
+ assert(new_sample_values.size() == total_samples());
+ Matrix diff(total_samples(), 2);
+ for (size_t i = 0; i < total_samples(); ++i)
+ {
+ diff(i, X) = new_sample_values[i][X] - old_sample_values[i][X];
+ diff(i, Y) = new_sample_values[i][Y] - old_sample_values[i][Y];
+ }
+ Vector column(m_model.size());
+ Matrix delta(m_model.size(), 2, 0.0);
+ VectorView deltax = delta.column_view(X);
+ VectorView deltay = delta.column_view(Y);
+ for (size_t i = 0; i < total_samples(); ++i)
+ {
+ if (diff(i, X) != 0)
+ {
+ column = m_psdinv_matrix->column_view(i);
+ column.scale(diff(i, X));
+ deltax += column;
+ }
+ if (diff(i, Y) != 0)
+ {
+ column = m_psdinv_matrix->column_view(i);
+ column.scale(diff(i, Y));
+ deltay += column;
+ }
+ }
+ m_solution += delta;
+ return m_solution;
+ }
+
+ solution_type& result()
+ {
+ return m_solution;
+ }
+
+private:
+ solution_type m_solution;
+
+}; // end class lsf_solution<ModelT, Point>
+
+
+
+
+template< typename ModelT,
+ bool WITH_FIXED_TERMS = ModelT::WITH_FIXED_TERMS >
+class lsf_with_fixed_terms
+{
+};
+
+
+// fitting tool for completely unknown models
+template< typename ModelT>
+class lsf_with_fixed_terms<ModelT, false>
+ : public lsf_solution<ModelT>
+{
+ public:
+ typedef ModelT model_type;
+ typedef typename model_type::parameter_type parameter_type;
+ typedef typename model_type::value_type value_type;
+ typedef lsf_solution<model_type> base_type;
+ typedef typename base_type::solution_type solution_type;
+
+ using base_type::total_samples;
+ using base_type::is_full;
+ using base_type::m_matrix;
+ using base_type::m_total_samples;
+ using base_type::m_model;
+
+ public:
+ lsf_with_fixed_terms<ModelT, false>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples)
+ {
+ }
+
+ void append(parameter_type const& sample_parameter)
+ {
+ assert(!is_full());
+ VectorView row = m_matrix.row_view(total_samples());
+ m_model.feed(row, sample_parameter);
+ ++m_total_samples;
+ }
+
+ void append_copy(size_t sample_index)
+ {
+ assert(!is_full());
+ assert(sample_index < total_samples());
+ VectorView dest_row = m_matrix.row_view(total_samples());
+ VectorView source_row = m_matrix.row_view(sample_index);
+ dest_row = source_row;
+ ++m_total_samples;
+ }
+
+}; // end class lsf_with_fixed_terms<ModelT, false>
+
+
+// fitting tool for partially known models
+template< typename ModelT>
+class lsf_with_fixed_terms<ModelT, true>
+ : public lsf_solution<ModelT>
+{
+ public:
+ typedef ModelT model_type;
+ typedef typename model_type::parameter_type parameter_type;
+ typedef typename model_type::value_type value_type;
+ typedef lsf_solution<model_type> base_type;
+ typedef typename base_type::solution_type solution_type;
+
+ using base_type::total_samples;
+ using base_type::is_full;
+ using base_type::m_matrix;
+ using base_type::m_total_samples;
+ using base_type::m_model;
+
+ public:
+ lsf_with_fixed_terms<ModelT, true>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples),
+ m_vector(forecasted_samples),
+ m_vector_view(NULL)
+ {
+ }
+ void append(parameter_type const& sample_parameter)
+ {
+ assert(!is_full());
+ VectorView row = m_matrix.row_view(total_samples());
+ m_model.feed(row, m_vector[total_samples()], sample_parameter);
+ ++m_total_samples;
+ }
+
+ void append_copy(size_t sample_index)
+ {
+ assert(!is_full());
+ assert(sample_index < total_samples());
+ VectorView dest_row = m_matrix.row_view(total_samples());
+ VectorView source_row = m_matrix.row_view(sample_index);
+ dest_row = source_row;
+ m_vector[total_samples()] = m_vector[sample_index];
+ ++m_total_samples;
+ }
+
+ void update()
+ {
+ base_type::update();
+ if (total_samples() == 0) return;
+ if (m_vector_view != NULL)
+ {
+ delete m_vector_view;
+ }
+ m_vector_view = new VectorView(m_vector, base_type::total_samples());
+ assert(m_vector_view != NULL);
+ }
+
+
+ ~lsf_with_fixed_terms<model_type, true>() override
+ {
+ if (m_vector_view != NULL)
+ {
+ delete m_vector_view;
+ }
+ }
+
+ protected:
+ Vector m_vector;
+ VectorView* m_vector_view;
+
+}; // end class lsf_with_fixed_terms<ModelT, true>
+
+
+} // end namespace detail
+
+
+
+
+template< typename ModelT,
+ typename ValueType = typename ModelT::value_type,
+ bool WITH_FIXED_TERMS = ModelT::WITH_FIXED_TERMS >
+class least_squeares_fitter
+{
+};
+
+
+template< typename ModelT, typename ValueType >
+class least_squeares_fitter<ModelT, ValueType, false>
+ : public detail::lsf_with_fixed_terms<ModelT>
+{
+ public:
+ typedef ModelT model_type;
+ typedef detail::lsf_with_fixed_terms<model_type> base_type;
+ typedef typename base_type::parameter_type parameter_type;
+ typedef typename base_type::value_type value_type;
+ typedef typename base_type::solution_type solution_type;
+
+ public:
+ least_squeares_fitter<ModelT, ValueType, false>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples)
+ {
+ }
+}; // end class least_squeares_fitter<ModelT, ValueType, true>
+
+
+template< typename ModelT>
+class least_squeares_fitter<ModelT, double, true>
+ : public detail::lsf_with_fixed_terms<ModelT>
+{
+ public:
+ typedef ModelT model_type;
+ typedef detail::lsf_with_fixed_terms<model_type> base_type;
+ typedef typename base_type::parameter_type parameter_type;
+ typedef typename base_type::value_type value_type;
+ typedef typename base_type::solution_type solution_type;
+
+ using base_type::m_vector_view;
+ //using base_type::result; // VSC legacy support
+ solution_type& result( std::vector<Point> const& old_sample_values,
+ std::vector<Point> const& new_sample_values )
+ {
+ return base_type::result(old_sample_values, new_sample_values);
+ }
+
+ solution_type& result()
+ {
+ return base_type::result();
+ }
+
+ public:
+ least_squeares_fitter<ModelT, double, true>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples)
+ {
+ }
+
+ template< typename VectorT >
+ solution_type& result(VectorT const& sample_values)
+ {
+ assert(sample_values.size() == m_vector_view->size());
+ Vector sv(sample_values.size());
+ for (size_t i = 0; i < sv.size(); ++i)
+ sv[i] = sample_values[i] - (*m_vector_view)[i];
+ return base_type::result(sv);
+ }
+
+}; // end class least_squeares_fitter<ModelT, double, true>
+
+
+template< typename ModelT>
+class least_squeares_fitter<ModelT, Point, true>
+ : public detail::lsf_with_fixed_terms<ModelT>
+{
+ public:
+ typedef ModelT model_type;
+ typedef detail::lsf_with_fixed_terms<model_type> base_type;
+ typedef typename base_type::parameter_type parameter_type;
+ typedef typename base_type::value_type value_type;
+ typedef typename base_type::solution_type solution_type;
+
+ using base_type::m_vector_view;
+ //using base_type::result; // VCS legacy support
+ solution_type& result( std::vector<Point> const& old_sample_values,
+ std::vector<Point> const& new_sample_values )
+ {
+ return base_type::result(old_sample_values, new_sample_values);
+ }
+
+ solution_type& result()
+ {
+ return base_type::result();
+ }
+
+
+ public:
+ least_squeares_fitter<ModelT, Point, true>( model_type const& _model,
+ size_t forecasted_samples )
+ : base_type(_model, forecasted_samples)
+ {
+ }
+
+ solution_type& result(std::vector<Point> const& sample_values)
+ {
+ assert(sample_values.size() == m_vector_view->size());
+ NL::Matrix sv(sample_values.size(), 2);
+ for (size_t i = 0; i < sample_values.size(); ++i)
+ {
+ sv(i, X) = sample_values[i][X] - (*m_vector_view)[i];
+ sv(i, Y) = sample_values[i][Y] - (*m_vector_view)[i];
+ }
+ return base_type::result(sv);
+ }
+
+}; // end class least_squeares_fitter<ModelT, Point, true>
+
+
+} // end namespace NL
+} // end namespace Geom
+
+
+
+#endif // _NL_FITTING_TOOL_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/include/2geom/numeric/linear_system.h b/include/2geom/numeric/linear_system.h
new file mode 100644
index 0000000..f793e20
--- /dev/null
+++ b/include/2geom/numeric/linear_system.h
@@ -0,0 +1,138 @@
+/*
+ * LinearSystem class wraps some gsl routines for solving linear systems
+ *
+ * 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 _NL_LINEAR_SYSTEM_H_
+#define _NL_LINEAR_SYSTEM_H_
+
+
+#include <cassert>
+
+#include <gsl/gsl_linalg.h>
+
+#include <2geom/numeric/matrix.h>
+#include <2geom/numeric/vector.h>
+
+
+namespace Geom { namespace NL {
+
+
+class LinearSystem
+{
+public:
+ LinearSystem(MatrixView & _matrix, VectorView & _vector)
+ : m_matrix(_matrix), m_vector(_vector), m_solution(_matrix.columns())
+ {
+ }
+
+ LinearSystem(Matrix & _matrix, Vector & _vector)
+ : m_matrix(_matrix), m_vector(_vector), m_solution(_matrix.columns())
+ {
+ }
+
+ const Vector & LU_solve()
+ {
+ assert( matrix().rows() == matrix().columns()
+ && matrix().rows() == vector().size() );
+ int s;
+ gsl_permutation * p = gsl_permutation_alloc(matrix().rows());
+ gsl_linalg_LU_decomp (matrix().get_gsl_matrix(), p, &s);
+ gsl_linalg_LU_solve( matrix().get_gsl_matrix(),
+ p,
+ vector().get_gsl_vector(),
+ m_solution.get_gsl_vector()
+ );
+ gsl_permutation_free(p);
+ return solution();
+ }
+
+ const Vector & SV_solve()
+ {
+ assert( matrix().rows() >= matrix().columns()
+ && matrix().rows() == vector().size() );
+
+ gsl_matrix* U = matrix().get_gsl_matrix();
+ gsl_matrix* V = gsl_matrix_alloc(matrix().columns(), matrix().columns());
+ gsl_vector* S = gsl_vector_alloc(matrix().columns());
+ gsl_vector* work = gsl_vector_alloc(matrix().columns());
+
+ gsl_linalg_SV_decomp( U, V, S, work );
+
+ gsl_vector* b = vector().get_gsl_vector();
+ gsl_vector* x = m_solution.get_gsl_vector();
+
+ gsl_linalg_SV_solve( U, V, S, b, x);
+
+ gsl_matrix_free(V);
+ gsl_vector_free(S);
+ gsl_vector_free(work);
+
+ return solution();
+ }
+
+ MatrixView & matrix()
+ {
+ return m_matrix;
+ }
+
+ VectorView & vector()
+ {
+ return m_vector;
+ }
+
+ const Vector & solution() const
+ {
+ return m_solution;
+ }
+
+private:
+ MatrixView m_matrix;
+ VectorView m_vector;
+ Vector m_solution;
+};
+
+
+} } // end namespaces
+
+
+#endif /*_NL_LINEAR_SYSTEM_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/include/2geom/numeric/matrix.h b/include/2geom/numeric/matrix.h
new file mode 100644
index 0000000..02851b4
--- /dev/null
+++ b/include/2geom/numeric/matrix.h
@@ -0,0 +1,603 @@
+/*
+ * Matrix, MatrixView, ConstMatrixView classes wrap the gsl matrix routines;
+ * "views" mimic the semantic of C++ references: any operation performed
+ * on a "view" is actually performed on the "viewed object"
+ *
+ * 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 _NL_MATRIX_H_
+#define _NL_MATRIX_H_
+
+#include <2geom/exception.h>
+#include <2geom/numeric/vector.h>
+
+#include <cassert>
+#include <utility> // for std::pair
+#include <algorithm> // for std::swap
+#include <sstream>
+#include <string>
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_linalg.h>
+
+
+namespace Geom { namespace NL {
+
+namespace detail
+{
+
+class BaseMatrixImpl
+{
+ public:
+ virtual ~BaseMatrixImpl()
+ {
+ }
+
+ ConstVectorView row_const_view(size_t i) const
+ {
+ return ConstVectorView(gsl_matrix_const_row(m_matrix, i));
+ }
+
+ ConstVectorView column_const_view(size_t i) const
+ {
+ return ConstVectorView(gsl_matrix_const_column(m_matrix, i));
+ }
+
+ const double & operator() (size_t i, size_t j) const
+ {
+ return *gsl_matrix_const_ptr(m_matrix, i, j);
+ }
+
+ const gsl_matrix* get_gsl_matrix() const
+ {
+ return m_matrix;
+ }
+
+ bool is_zero() const
+ {
+ return gsl_matrix_isnull(m_matrix);
+ }
+
+ bool is_positive() const
+ {
+ for ( unsigned int i = 0; i < rows(); ++i )
+ {
+ for ( unsigned int j = 0; j < columns(); ++j )
+ {
+ if ( (*this)(i,j) <= 0 ) return false;
+ }
+ }
+ return true;
+ }
+
+ bool is_negative() const
+ {
+ for ( unsigned int i = 0; i < rows(); ++i )
+ {
+ for ( unsigned int j = 0; j < columns(); ++j )
+ {
+ if ( (*this)(i,j) >= 0 ) return false;
+ }
+ }
+ return true;
+ }
+
+ bool is_non_negative() const
+ {
+ for ( unsigned int i = 0; i < rows(); ++i )
+ {
+ for ( unsigned int j = 0; j < columns(); ++j )
+ {
+ if ( (*this)(i,j) < 0 ) return false;
+ }
+ }
+ return true;
+ }
+
+ double max() const
+ {
+ return gsl_matrix_max(m_matrix);
+ }
+
+ double min() const
+ {
+ return gsl_matrix_min(m_matrix);
+ }
+
+ std::pair<size_t, size_t>
+ max_index() const
+ {
+ std::pair<size_t, size_t> indices;
+ gsl_matrix_max_index(m_matrix, &(indices.first), &(indices.second));
+ return indices;
+ }
+
+ std::pair<size_t, size_t>
+ min_index() const
+ {
+ std::pair<size_t, size_t> indices;
+ gsl_matrix_min_index(m_matrix, &(indices.first), &(indices.second));
+ return indices;
+ }
+
+ size_t rows() const
+ {
+ return m_rows;
+ }
+
+ size_t columns() const
+ {
+ return m_columns;
+ }
+
+ std::string str() const;
+
+ protected:
+ size_t m_rows, m_columns;
+ gsl_matrix* m_matrix;
+
+}; // end class BaseMatrixImpl
+
+
+inline
+bool operator== (BaseMatrixImpl const& m1, BaseMatrixImpl const& m2)
+{
+ if (m1.rows() != m2.rows() || m1.columns() != m2.columns()) return false;
+
+ for (size_t i = 0; i < m1.rows(); ++i)
+ for (size_t j = 0; j < m1.columns(); ++j)
+ if (m1(i,j) != m2(i,j)) return false;
+
+ return true;
+}
+
+template< class charT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const BaseMatrixImpl & _matrix)
+{
+ if (_matrix.rows() == 0 || _matrix.columns() == 0) return os;
+
+ os << "[[" << _matrix(0,0);
+ for (size_t j = 1; j < _matrix.columns(); ++j)
+ {
+ os << ", " << _matrix(0,j);
+ }
+ os << "]";
+
+ for (size_t i = 1; i < _matrix.rows(); ++i)
+ {
+ os << ", [" << _matrix(i,0);
+ for (size_t j = 1; j < _matrix.columns(); ++j)
+ {
+ os << ", " << _matrix(i,j);
+ }
+ os << "]";
+ }
+ os << "]";
+ return os;
+}
+
+inline
+std::string BaseMatrixImpl::str() const
+{
+ std::ostringstream oss;
+ oss << (*this);
+ return oss.str();
+}
+
+
+class MatrixImpl : public BaseMatrixImpl
+{
+ public:
+
+ typedef BaseMatrixImpl base_type;
+
+ void set_all( double x )
+ {
+ gsl_matrix_set_all(m_matrix, x);
+ }
+
+ void set_identity()
+ {
+ gsl_matrix_set_identity(m_matrix);
+ }
+
+ using base_type::operator(); // VSC legacy support
+ const double & operator() (size_t i, size_t j) const
+ {
+ return base_type::operator ()(i, j);
+ }
+
+ double & operator() (size_t i, size_t j)
+ {
+ return *gsl_matrix_ptr(m_matrix, i, j);
+ }
+
+ using base_type::get_gsl_matrix;
+
+ gsl_matrix* get_gsl_matrix()
+ {
+ return m_matrix;
+ }
+
+ VectorView row_view(size_t i)
+ {
+ return VectorView(gsl_matrix_row(m_matrix, i));
+ }
+
+ VectorView column_view(size_t i)
+ {
+ return VectorView(gsl_matrix_column(m_matrix, i));
+ }
+
+ void swap_rows(size_t i, size_t j)
+ {
+ gsl_matrix_swap_rows(m_matrix, i, j);
+ }
+
+ void swap_columns(size_t i, size_t j)
+ {
+ gsl_matrix_swap_columns(m_matrix, i, j);
+ }
+
+ MatrixImpl & transpose()
+ {
+ assert(columns() == rows());
+ gsl_matrix_transpose(m_matrix);
+ return (*this);
+ }
+
+ MatrixImpl & scale(double x)
+ {
+ gsl_matrix_scale(m_matrix, x);
+ return (*this);
+ }
+
+ MatrixImpl & translate(double x)
+ {
+ gsl_matrix_add_constant(m_matrix, x);
+ return (*this);
+ }
+
+ MatrixImpl & operator+=(base_type const& _matrix)
+ {
+ gsl_matrix_add(m_matrix, _matrix.get_gsl_matrix());
+ return (*this);
+ }
+
+ MatrixImpl & operator-=(base_type const& _matrix)
+ {
+ gsl_matrix_sub(m_matrix, _matrix.get_gsl_matrix());
+ return (*this);
+ }
+
+}; // end class MatrixImpl
+
+} // end namespace detail
+
+
+using detail::operator==;
+using detail::operator<<;
+
+
+template <size_t N>
+class ConstBaseSymmetricMatrix;
+
+
+class Matrix: public detail::MatrixImpl
+{
+ public:
+ typedef detail::MatrixImpl base_type;
+
+ public:
+ // the matrix is not initialized
+ Matrix(size_t n1, size_t n2)
+ {
+ m_rows = n1;
+ m_columns = n2;
+ m_matrix = gsl_matrix_alloc(n1, n2);
+ }
+
+ Matrix(size_t n1, size_t n2, double x)
+ {
+ m_rows = n1;
+ m_columns = n2;
+ m_matrix = gsl_matrix_alloc(n1, n2);
+ gsl_matrix_set_all(m_matrix, x);
+ }
+
+ Matrix(Matrix const& _matrix)
+ : base_type()
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix = gsl_matrix_alloc(rows(), columns());
+ gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix());
+ }
+
+ explicit
+ Matrix(base_type::base_type const& _matrix)
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix = gsl_matrix_alloc(rows(), columns());
+ gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix());
+ }
+
+ template <size_t N>
+ explicit
+ Matrix(ConstBaseSymmetricMatrix<N> const& _smatrix)
+ {
+ m_rows = N;
+ m_columns = N;
+ m_matrix = gsl_matrix_alloc(N, N);
+ for (size_t i = 0; i < N; ++i)
+ for (size_t j = 0; j < N ; ++j)
+ (*gsl_matrix_ptr(m_matrix, i, j)) = _smatrix(i,j);
+ }
+
+ Matrix & operator=(Matrix const& _matrix)
+ {
+ assert( rows() == _matrix.rows() && columns() == _matrix.columns() );
+ gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix());
+ return *this;
+ }
+
+ Matrix & operator=(base_type::base_type const& _matrix)
+ {
+ assert( rows() == _matrix.rows() && columns() == _matrix.columns() );
+ gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix());
+ return *this;
+ }
+
+ template <size_t N>
+ Matrix & operator=(ConstBaseSymmetricMatrix<N> const& _smatrix)
+ {
+ assert (rows() == N && columns() == N);
+ for (size_t i = 0; i < N; ++i)
+ for (size_t j = 0; j < N ; ++j)
+ (*this)(i,j) = _smatrix(i,j);
+ return *this;
+ }
+
+ ~Matrix() override
+ {
+ gsl_matrix_free(m_matrix);
+ }
+
+ Matrix & transpose()
+ {
+ return static_cast<Matrix &>( base_type::transpose() );
+ }
+
+ Matrix & scale(double x)
+ {
+ return static_cast<Matrix &>( base_type::scale(x) );
+ }
+
+ Matrix & translate(double x)
+ {
+ return static_cast<Matrix &>( base_type::translate(x) );
+ }
+
+ Matrix & operator+=(base_type::base_type const& _matrix)
+ {
+ return static_cast<Matrix &>( base_type::operator+=(_matrix) );
+ }
+
+ Matrix & operator-=(base_type::base_type const& _matrix)
+ {
+ return static_cast<Matrix &>( base_type::operator-=(_matrix) );
+ }
+
+ friend
+ void swap(Matrix & m1, Matrix & m2);
+ friend
+ void swap_any(Matrix & m1, Matrix & m2);
+
+}; // end class Matrix
+
+
+// warning! this operation invalidates any view of the passed matrix objects
+inline
+void swap(Matrix & m1, Matrix & m2)
+{
+ assert(m1.rows() == m2.rows() && m1.columns() == m2.columns());
+ using std::swap;
+ swap(m1.m_matrix, m2.m_matrix);
+}
+
+inline void swap_any(Matrix &m1, Matrix &m2)
+{
+ using std::swap;
+ swap(m1.m_matrix, m2.m_matrix);
+ swap(m1.m_rows, m2.m_rows);
+ swap(m1.m_columns, m2.m_columns);
+}
+
+
+
+class ConstMatrixView : public detail::BaseMatrixImpl
+{
+ public:
+ typedef detail::BaseMatrixImpl base_type;
+
+ public:
+ ConstMatrixView(const base_type & _matrix, size_t k1, size_t k2, size_t n1, size_t n2)
+ : m_matrix_view( gsl_matrix_const_submatrix(_matrix.get_gsl_matrix(), k1, k2, n1, n2) )
+ {
+ m_rows = n1;
+ m_columns = n2;
+ m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) );
+ }
+
+ ConstMatrixView(const ConstMatrixView & _matrix)
+ : base_type(),
+ m_matrix_view(_matrix.m_matrix_view)
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) );
+ }
+
+ ConstMatrixView(const base_type & _matrix)
+ : m_matrix_view(gsl_matrix_const_submatrix(_matrix.get_gsl_matrix(), 0, 0, _matrix.rows(), _matrix.columns()))
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) );
+ }
+
+ private:
+ gsl_matrix_const_view m_matrix_view;
+
+}; // end class ConstMatrixView
+
+
+
+
+class MatrixView : public detail::MatrixImpl
+{
+ public:
+ typedef detail::MatrixImpl base_type;
+
+ public:
+ MatrixView(base_type & _matrix, size_t k1, size_t k2, size_t n1, size_t n2)
+ {
+ m_rows = n1;
+ m_columns = n2;
+ m_matrix_view
+ = gsl_matrix_submatrix(_matrix.get_gsl_matrix(), k1, k2, n1, n2);
+ m_matrix = &(m_matrix_view.matrix);
+ }
+
+ MatrixView(const MatrixView & _matrix)
+ : base_type()
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix_view = _matrix.m_matrix_view;
+ m_matrix = &(m_matrix_view.matrix);
+ }
+
+ MatrixView(Matrix & _matrix)
+ {
+ m_rows = _matrix.rows();
+ m_columns = _matrix.columns();
+ m_matrix_view
+ = gsl_matrix_submatrix(_matrix.get_gsl_matrix(), 0, 0, rows(), columns());
+ m_matrix = &(m_matrix_view.matrix);
+ }
+
+ MatrixView & operator=(MatrixView const& _matrix)
+ {
+ assert( rows() == _matrix.rows() && columns() == _matrix.columns() );
+ gsl_matrix_memcpy(m_matrix, _matrix.m_matrix);
+ return *this;
+ }
+
+ MatrixView & operator=(base_type::base_type const& _matrix)
+ {
+ assert( rows() == _matrix.rows() && columns() == _matrix.columns() );
+ gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix());
+ return *this;
+ }
+
+ MatrixView & transpose()
+ {
+ return static_cast<MatrixView &>( base_type::transpose() );
+ }
+
+ MatrixView & scale(double x)
+ {
+ return static_cast<MatrixView &>( base_type::scale(x) );
+ }
+
+ MatrixView & translate(double x)
+ {
+ return static_cast<MatrixView &>( base_type::translate(x) );
+ }
+
+ MatrixView & operator+=(base_type::base_type const& _matrix)
+ {
+ return static_cast<MatrixView &>( base_type::operator+=(_matrix) );
+ }
+
+ MatrixView & operator-=(base_type::base_type const& _matrix)
+ {
+ return static_cast<MatrixView &>( base_type::operator-=(_matrix) );
+ }
+
+ friend
+ void swap_view(MatrixView & m1, MatrixView & m2);
+
+ private:
+ gsl_matrix_view m_matrix_view;
+
+}; // end class MatrixView
+
+
+inline
+void swap_view(MatrixView & m1, MatrixView & m2)
+{
+ assert(m1.rows() == m2.rows() && m1.columns() == m2.columns());
+ using std::swap;
+ swap(m1.m_matrix_view, m2.m_matrix_view);
+}
+
+Vector operator*( detail::BaseMatrixImpl const& A,
+ detail::BaseVectorImpl const& v );
+
+Matrix operator*( detail::BaseMatrixImpl const& A,
+ detail::BaseMatrixImpl const& B );
+
+Matrix pseudo_inverse(detail::BaseMatrixImpl const& A);
+
+double trace (detail::BaseMatrixImpl const& A);
+
+double det (detail::BaseMatrixImpl const& A);
+
+} } // end namespaces
+
+#endif /*_NL_MATRIX_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/include/2geom/numeric/symmetric-matrix-fs-operation.h b/include/2geom/numeric/symmetric-matrix-fs-operation.h
new file mode 100644
index 0000000..c5aaa72
--- /dev/null
+++ b/include/2geom/numeric/symmetric-matrix-fs-operation.h
@@ -0,0 +1,102 @@
+/*
+ * SymmetricMatrix basic operation
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2009 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 _NL_SYMMETRIC_MATRIX_FS_OPERATION_H_
+#define _NL_SYMMETRIC_MATRIX_FS_OPERATION_H_
+
+
+#include <2geom/numeric/symmetric-matrix-fs.h>
+#include <2geom/numeric/symmetric-matrix-fs-trace.h>
+
+
+
+
+namespace Geom { namespace NL {
+
+template <size_t N>
+SymmetricMatrix<N> adj(const ConstBaseSymmetricMatrix<N> & S);
+
+template <>
+inline
+SymmetricMatrix<2> adj(const ConstBaseSymmetricMatrix<2> & S)
+{
+ SymmetricMatrix<2> result;
+ result.get<0,0>() = S.get<1,1>();
+ result.get<1,0>() = -S.get<1,0>();
+ result.get<1,1>() = S.get<0,0>();
+ return result;
+}
+
+template <>
+inline
+SymmetricMatrix<3> adj(const ConstBaseSymmetricMatrix<3> & S)
+{
+ SymmetricMatrix<3> result;
+
+ result.get<0,0>() = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>();
+ result.get<1,0>() = S.get<0,2>() * S.get<2,1>() - S.get<0,1>() * S.get<2,2>();
+ result.get<1,1>() = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>();
+ result.get<2,0>() = S.get<0,1>() * S.get<1,2>() - S.get<0,2>() * S.get<1,1>();
+ result.get<2,1>() = S.get<0,2>() * S.get<1,0>() - S.get<0,0>() * S.get<1,2>();
+ result.get<2,2>() = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>();
+ return result;
+}
+
+template <size_t N>
+inline
+SymmetricMatrix<N> inverse(const ConstBaseSymmetricMatrix<N> & S)
+{
+ SymmetricMatrix<N> result = adj(S);
+ double d = det(S);
+ assert (d != 0);
+ result.scale (1/d);
+ return result;
+}
+
+} /* end namespace NL*/ } /* end namespace Geom*/
+
+
+#endif // _NL_SYMMETRIC_MATRIX_FS_OPERATION_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/include/2geom/numeric/symmetric-matrix-fs-trace.h b/include/2geom/numeric/symmetric-matrix-fs-trace.h
new file mode 100644
index 0000000..0e7a28c
--- /dev/null
+++ b/include/2geom/numeric/symmetric-matrix-fs-trace.h
@@ -0,0 +1,427 @@
+/*
+ * SymmetricMatrix trace
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2009 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 _NL_TRACE_H_
+#define _NL_TRACE_H_
+
+
+#include <2geom/numeric/matrix.h>
+#include <2geom/numeric/symmetric-matrix-fs.h>
+
+
+
+
+
+namespace Geom { namespace NL {
+
+
+namespace detail
+{
+
+/*
+ * helper routines
+ */
+
+inline
+int sgn_prod (int x, int y)
+{
+ if (x == 0 || y == 0) return 0;
+ if (x == y) return 1;
+ return -1;
+}
+
+inline
+bool abs_less (double x, double y)
+{
+ return (std::fabs(x) < std::fabs(y));
+}
+
+
+/*
+ * trace K-th of symmetric matrix S of order N
+ */
+template <size_t K, size_t N>
+struct trace
+{
+ static double evaluate(const ConstBaseSymmetricMatrix<N> &S);
+};
+
+template <size_t N>
+struct trace<1,N>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<N> & S)
+ {
+ double t = 0;
+ for (size_t i = 0; i < N; ++i)
+ {
+ t += S(i,i);
+ }
+ return t;
+ }
+};
+
+template <size_t N>
+struct trace<N,N>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<N> & S)
+ {
+ Matrix M(S);
+ return det(M);
+ }
+};
+
+/*
+ * trace for symmetric matrix of order 2
+ */
+template <>
+struct trace<1,2>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<2> & S)
+ {
+ return (S.get<0,0>() + S.get<1,1>());
+ }
+};
+
+template <>
+struct trace<2,2>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<2> & S)
+ {
+ return (S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>());
+ }
+};
+
+
+/*
+ * trace for symmetric matrix of order 3
+ */
+template <>
+struct trace<1,3>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<3> & S)
+ {
+ return (S.get<0,0>() + S.get<1,1>() + S.get<2,2>());
+ }
+};
+
+template <>
+struct trace<2,3>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<3> & S)
+ {
+ double a00 = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>();
+ double a11 = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>();
+ double a22 = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>();
+ return (a00 + a11 + a22);
+ }
+};
+
+template <>
+struct trace<3,3>
+{
+ static
+ double evaluate (const ConstBaseSymmetricMatrix<3> & S)
+ {
+ double d = S.get<0,0>() * S.get<1,1>() * S.get<2,2>();
+ d += (2 * S.get<1,0>() * S.get<2,0>() * S.get<2,1>());
+ d -= (S.get<0,0>() * S.get<2,1>() * S.get<2,1>());
+ d -= (S.get<1,1>() * S.get<2,0>() * S.get<2,0>());
+ d -= (S.get<2,2>() * S.get<1,0>() * S.get<1,0>());
+ return d;
+ }
+};
+
+
+/*
+ * sign of trace K-th
+ */
+template <size_t K, size_t N>
+struct trace_sgn
+{
+ static
+ int evaluate (const ConstBaseSymmetricMatrix<N> & S)
+ {
+ double d = trace<K, N>::evaluate(S);
+ return sgn(d);
+ }
+};
+
+
+/*
+ * sign of trace for symmetric matrix of order 2
+ */
+template <>
+struct trace_sgn<2,2>
+{
+ static
+ int evaluate (const ConstBaseSymmetricMatrix<2> & S)
+ {
+ double m00 = S.get<0,0>();
+ double m10 = S.get<1,0>();
+ double m11 = S.get<1,1>();
+
+ int sm00 = sgn (m00);
+ int sm10 = sgn (m10);
+ int sm11 = sgn (m11);
+
+ if (sm10 == 0)
+ {
+ return sgn_prod (sm00, sm11);
+ }
+ else
+ {
+ int sm00m11 = sgn_prod (sm00, sm11);
+ if (sm00m11 == 1)
+ {
+ int e00, e10, e11;
+ double f00 = std::frexp (m00, &e00);
+ double f10 = std::frexp (m10, &e10);
+ double f11 = std::frexp (m11, &e11);
+
+ int e0011 = e00 + e11;
+ int e1010 = e10 << 1;
+ int ed = e0011 - e1010;
+
+ if (ed > 1)
+ {
+ return 1;
+ }
+ else if (ed < -1)
+ {
+ return -1;
+ }
+ else
+ {
+ double d = std::ldexp (f00 * f11, ed) - f10 * f10;
+ //std::cout << "trace_sgn<2,2>: det = " << d << std::endl;
+ double eps = std::ldexp (1, -50);
+ if (std::fabs(d) < eps) return 0;
+ return sgn (d);
+ }
+ }
+ return -1;
+ }
+ }
+};
+
+
+/*
+ * sign of trace for symmetric matrix of order 3
+ */
+template <>
+struct trace_sgn<2,3>
+{
+ static
+ int evaluate (const ConstBaseSymmetricMatrix<3> & S)
+ {
+ double eps = std::ldexp (1, -50);
+ double t[6];
+
+ t[0] = S.get<1,1>() * S.get<2,2>();
+ t[1] = - S.get<1,2>() * S.get<2,1>();
+ t[2] = S.get<0,0>() * S.get<2,2>();
+ t[3] = - S.get<0,2>() * S.get<2,0>();
+ t[4] = S.get<0,0>() * S.get<1,1>();
+ t[5] = - S.get<0,1>() * S.get<1,0>();
+
+
+ double* maxp = std::max_element (t, t+6, abs_less);
+ int em;
+ std::frexp(*maxp, &em);
+ double d = 0;
+ for (double i : t)
+ {
+ d += i;
+ }
+ double r = std::fabs (std::ldexp (d, -em)); // relative error
+ //std::cout << "trace_sgn<2,3>: d = " << d << std::endl;
+ //std::cout << "trace_sgn<2,3>: r = " << r << std::endl;
+ if (r < eps) return 0;
+ if (d > 0) return 1;
+ return -1;
+ }
+};
+
+template <>
+struct trace_sgn<3,3>
+{
+ static
+ int evaluate (const ConstBaseSymmetricMatrix<3> & S)
+ {
+
+ double eps = std::ldexp (1, -48);
+ double t[5];
+
+ t[0] = S.get<0,0>() * S.get<1,1>() * S.get<2,2>();
+ t[1] = 2 * S.get<1,0>() * S.get<2,0>() * S.get<2,1>();
+ t[2] = -(S.get<0,0>() * S.get<2,1>() * S.get<2,1>());
+ t[3] = -(S.get<1,1>() * S.get<2,0>() * S.get<2,0>());
+ t[4] = -(S.get<2,2>() * S.get<1,0>() * S.get<1,0>());
+
+ double* maxp = std::max_element (t, t+5, abs_less);
+ int em;
+ std::frexp(*maxp, &em);
+ double d = 0;
+ for (double i : t)
+ {
+ d += i;
+ }
+ //std::cout << "trace_sgn<3,3>: d = " << d << std::endl;
+ double r = std::fabs (std::ldexp (d, -em)); // relative error
+ //std::cout << "trace_sgn<3,3>: r = " << r << std::endl;
+
+ if (r < eps) return 0;
+ if (d > 0) return 1;
+ return -1;
+ }
+}; // end struct trace_sgn<3,3>
+
+} // end namespace detail
+
+
+template <size_t K, size_t N>
+inline
+double trace (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace<K, N>::evaluate(_matrix);
+}
+
+template <size_t N>
+inline
+double trace (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace<1, N>::evaluate(_matrix);
+}
+
+template <size_t N>
+inline
+double det (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace<N, N>::evaluate(_matrix);
+}
+
+
+template <size_t K, size_t N>
+inline
+int trace_sgn (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace_sgn<K, N>::evaluate(_matrix);
+}
+
+template <size_t N>
+inline
+int trace_sgn (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace_sgn<1, N>::evaluate(_matrix);
+}
+
+template <size_t N>
+inline
+int det_sgn (const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ return detail::trace_sgn<N, N>::evaluate(_matrix);
+}
+
+/*
+template <size_t N>
+inline
+size_t rank (const ConstBaseSymmetricMatrix<N> & S)
+{
+ THROW_NOTIMPLEMENTED();
+ return 0;
+}
+
+template <>
+inline
+size_t rank<2> (const ConstBaseSymmetricMatrix<2> & S)
+{
+ if (S.is_zero()) return 0;
+ double d = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>();
+ if (d != 0) return 2;
+ return 1;
+}
+
+template <>
+inline
+size_t rank<3> (const ConstBaseSymmetricMatrix<3> & S)
+{
+ if (S.is_zero()) return 0;
+
+ double a20 = S.get<0,1>() * S.get<1,2>() - S.get<0,2>() * S.get<1,1>();
+ double a21 = S.get<0,2>() * S.get<1,0>() - S.get<0,0>() * S.get<1,2>();
+ double a22 = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>();
+ double d = a20 * S.get<2,0>() + a21 * S.get<2,1>() + a22 * S.get<2,2>();
+
+ if (d != 0) return 3;
+
+ if (a20 != 0 || a21 != 0 || a22 != 0) return 2;
+
+ double a00 = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>();
+ if (a00 != 0) return 2;
+
+ double a10 = S.get<0,2>() * S.get<2,1>() - S.get<0,1>() * S.get<2,2>();
+ if (a10 != 0) return 2;
+
+ double a11 = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>();
+ if (a11 != 0) return 2;
+
+ return 1;
+}
+*/
+
+} /* end namespace NL*/ } /* end namespace Geom*/
+
+
+
+
+#endif // _NL_TRACE_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/include/2geom/numeric/symmetric-matrix-fs.h b/include/2geom/numeric/symmetric-matrix-fs.h
new file mode 100644
index 0000000..2fadd69
--- /dev/null
+++ b/include/2geom/numeric/symmetric-matrix-fs.h
@@ -0,0 +1,733 @@
+/*
+ * SymmetricMatrix, ConstSymmetricMatrixView, SymmetricMatrixView template
+ * classes implement fixed size symmetric matrix; "views" mimic the semantic
+ * of C++ references: any operation performed on a "view" is actually performed
+ * on the "viewed object"
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2009 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 _NL_SYMMETRIC_MATRIX_FS_H_
+#define _NL_SYMMETRIC_MATRIX_FS_H_
+
+
+#include <2geom/numeric/vector.h>
+#include <2geom/numeric/matrix.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+#include <boost/static_assert.hpp>
+
+#include <cassert>
+#include <utility> // for std::pair
+#include <algorithm> // for std::swap, std::copy
+#include <sstream>
+#include <string>
+
+
+
+namespace Geom { namespace NL {
+
+
+namespace detail
+{
+
+template <size_t I, size_t J, bool B = (I < J)>
+struct index
+{
+ static const size_t K = index<J, I>::K;
+};
+
+template <size_t I, size_t J>
+struct index<I, J, false>
+{
+ static const size_t K = (((I+1) * I) >> 1) + J;
+};
+
+} // end namespace detail
+
+
+
+
+template <size_t N>
+class ConstBaseSymmetricMatrix;
+
+template <size_t N>
+class BaseSymmetricMatrix;
+
+template <size_t N>
+class SymmetricMatrix;
+
+template <size_t N>
+class ConstSymmetricMatrixView;
+
+template <size_t N>
+class SymmetricMatrixView;
+
+
+
+// declaration needed for friend clause
+template <size_t N>
+bool operator== (ConstBaseSymmetricMatrix<N> const& _smatrix1,
+ ConstBaseSymmetricMatrix<N> const& _smatrix2);
+
+
+
+
+template <size_t N>
+class ConstBaseSymmetricMatrix
+{
+ public:
+ const static size_t DIM = N;
+ const static size_t DATA_SIZE = ((DIM+1) * DIM) / 2;
+
+ public:
+
+ ConstBaseSymmetricMatrix (VectorView const& _data)
+ : m_data(_data)
+ {
+ }
+
+ double operator() (size_t i, size_t j) const
+ {
+ return m_data[get_index(i,j)];
+ }
+
+ template <size_t I, size_t J>
+ double get() const
+ {
+ BOOST_STATIC_ASSERT ((I < N && J < N));
+ return m_data[detail::index<I, J>::K];
+ }
+
+
+ size_t rows() const
+ {
+ return DIM;
+ }
+
+ size_t columns() const
+ {
+ return DIM;
+ }
+
+ bool is_zero() const
+ {
+ return m_data.is_zero();
+ }
+
+ bool is_positive() const
+ {
+ return m_data.is_positive();
+ }
+
+ bool is_negative() const
+ {
+ return m_data.is_negative();
+ }
+
+ bool is_non_negative() const
+ {
+ return m_data.is_non_negative();
+ }
+
+ double min() const
+ {
+ return m_data.min();
+ }
+
+ double max() const
+ {
+ return m_data.max();
+ }
+
+ std::pair<size_t, size_t>
+ min_index() const
+ {
+ std::pair<size_t, size_t> indices(0,0);
+ double min_value = m_data[0];
+ for (size_t i = 1; i < DIM; ++i)
+ {
+ for (size_t j = 0; j <= i; ++j)
+ {
+ if (min_value > (*this)(i,j))
+ {
+ min_value = (*this)(i,j);
+ indices.first = i;
+ indices.second = j;
+ }
+ }
+ }
+ return indices;
+ }
+
+ std::pair<size_t, size_t>
+ max_index() const
+ {
+ std::pair<size_t, size_t> indices(0,0);
+ double max_value = m_data[0];
+ for (size_t i = 1; i < DIM; ++i)
+ {
+ for (size_t j = 0; j <= i; ++j)
+ {
+ if (max_value < (*this)(i,j))
+ {
+ max_value = (*this)(i,j);
+ indices.first = i;
+ indices.second = j;
+ }
+ }
+ }
+ return indices;
+ }
+
+ size_t min_on_row_index (size_t i) const
+ {
+ size_t idx = 0;
+ double min_value = (*this)(i,0);
+ for (size_t j = 1; j < DIM; ++j)
+ {
+ if (min_value > (*this)(i,j))
+ {
+ min_value = (*this)(i,j);
+ idx = j;
+ }
+ }
+ return idx;
+ }
+
+ size_t max_on_row_index (size_t i) const
+ {
+ size_t idx = 0;
+ double max_value = (*this)(i,0);
+ for (size_t j = 1; j < DIM; ++j)
+ {
+ if (max_value < (*this)(i,j))
+ {
+ max_value = (*this)(i,j);
+ idx = j;
+ }
+ }
+ return idx;
+ }
+
+ size_t min_on_column_index (size_t j) const
+ {
+ return min_on_row_index(j);
+ }
+
+ size_t max_on_column_index (size_t j) const
+ {
+ return max_on_row_index(j);
+ }
+
+ size_t min_on_diag_index () const
+ {
+ size_t idx = 0;
+ double min_value = (*this)(0,0);
+ for (size_t i = 1; i < DIM; ++i)
+ {
+ if (min_value > (*this)(i,i))
+ {
+ min_value = (*this)(i,i);
+ idx = i;
+ }
+ }
+ return idx;
+ }
+
+ size_t max_on_diag_index () const
+ {
+ size_t idx = 0;
+ double max_value = (*this)(0,0);
+ for (size_t i = 1; i < DIM; ++i)
+ {
+ if (max_value < (*this)(i,i))
+ {
+ max_value = (*this)(i,i);
+ idx = i;
+ }
+ }
+ return idx;
+ }
+
+ std::string str() const;
+
+ ConstSymmetricMatrixView<N-1> main_minor_const_view() const;
+
+ SymmetricMatrix<N> operator- () const;
+
+ Vector operator* (ConstVectorView _vector) const
+ {
+ assert (_vector.size() == DIM);
+ Vector result(DIM, 0.0);
+
+ for (size_t i = 0; i < DIM; ++i)
+ {
+ for (size_t j = 0; j < DIM; ++j)
+ {
+ result[i] += (*this)(i,j) * _vector[j];
+ }
+ }
+ return result;
+ }
+
+ protected:
+ static size_t get_index (size_t i, size_t j)
+ {
+ if (i < j) return get_index (j, i);
+ size_t k = (i+1) * i;
+ k >>= 1;
+ k += j;
+ return k;
+ }
+
+ protected:
+ ConstVectorView get_data() const
+ {
+ return m_data;
+ }
+
+ friend
+ bool operator==<N> (ConstBaseSymmetricMatrix const& _smatrix1,
+ ConstBaseSymmetricMatrix const& _smatrix2);
+
+ protected:
+ VectorView m_data;
+
+}; //end ConstBaseSymmetricMatrix
+
+
+template <size_t N>
+class BaseSymmetricMatrix : public ConstBaseSymmetricMatrix<N>
+{
+ public:
+ typedef ConstBaseSymmetricMatrix<N> base_type;
+
+
+ public:
+
+ BaseSymmetricMatrix (VectorView const& _data)
+ : base_type(_data)
+ {
+ }
+
+ double operator() (size_t i, size_t j) const
+ {
+ return m_data[base_type::get_index(i,j)];
+ }
+
+ double& operator() (size_t i, size_t j)
+ {
+ return m_data[base_type::get_index(i,j)];
+ }
+
+ template <size_t I, size_t J>
+ double& get()
+ {
+ BOOST_STATIC_ASSERT ((I < N && J < N));
+ return m_data[detail::index<I, J>::K];
+ }
+
+ void set_all (double x)
+ {
+ m_data.set_all(x);
+ }
+
+ SymmetricMatrixView<N-1> main_minor_view();
+
+ BaseSymmetricMatrix& transpose() const
+ {
+ return (*this);
+ }
+
+ BaseSymmetricMatrix& translate (double c)
+ {
+ m_data.translate(c);
+ return (*this);
+ }
+
+ BaseSymmetricMatrix& scale (double c)
+ {
+ m_data.scale(c);
+ return (*this);
+ }
+
+ BaseSymmetricMatrix& operator+= (base_type const& _smatrix)
+ {
+ m_data += (static_cast<const BaseSymmetricMatrix &>(_smatrix).m_data);
+ return (*this);
+ }
+
+ BaseSymmetricMatrix& operator-= (base_type const& _smatrix)
+ {
+ m_data -= (static_cast<const BaseSymmetricMatrix &>(_smatrix).m_data);
+ return (*this);
+ }
+
+ using base_type::DIM;
+ using base_type::DATA_SIZE;
+ using base_type::m_data;
+ using base_type::operator-;
+ using base_type::operator*;
+
+}; //end BaseSymmetricMatrix
+
+
+template <size_t N>
+class SymmetricMatrix : public BaseSymmetricMatrix<N>
+{
+ public:
+ typedef BaseSymmetricMatrix<N> base_type;
+ typedef typename base_type::base_type base_base_type;
+
+ using base_type::DIM;
+ using base_type::DATA_SIZE;
+ using base_type::m_data;
+
+ public:
+ SymmetricMatrix ()
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ }
+
+ explicit
+ SymmetricMatrix (ConstVectorView _data)
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ assert (_data.size() == DATA_SIZE);
+ m_data = _data;
+ }
+
+ explicit
+ SymmetricMatrix (const double _data[DATA_SIZE])
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ std::copy (_data, _data + DATA_SIZE, m_adata);
+ }
+
+ SymmetricMatrix (SymmetricMatrix const& _smatrix)
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ m_data = _smatrix.m_data;
+ }
+
+ explicit
+ SymmetricMatrix (base_base_type const& _smatrix)
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data;
+ }
+
+ explicit
+ SymmetricMatrix (ConstMatrixView const& _matrix)
+ : base_type (VectorView(m_adata, DATA_SIZE))
+ {
+ assert (_matrix.rows() == N && _matrix.columns() == N);
+ for (size_t i = 0; i < N; ++i)
+ for (size_t j = 0; j <= i ; ++j)
+ (*this)(i,j) = _matrix(i,j);
+ }
+
+ SymmetricMatrix& operator= (SymmetricMatrix const& _smatrix)
+ {
+ m_data = _smatrix.m_data;
+ return (*this);
+ }
+
+ SymmetricMatrix& operator= (base_base_type const& _smatrix)
+ {
+
+ m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data;
+ return (*this);
+ }
+
+ SymmetricMatrix& operator= (ConstMatrixView const& _matrix)
+ {
+ assert (_matrix.rows() == N && _matrix.columns() == N);
+ for (size_t i = 0; i < N; ++i)
+ for (size_t j = 0; j <= i ; ++j)
+ (*this)(i,j) = _matrix(i,j);
+
+ return (*this);
+ }
+
+ // needed for accessing m_adata
+ friend class ConstSymmetricMatrixView<DIM>;
+ friend class SymmetricMatrixView<DIM>;
+ private:
+ double m_adata[DATA_SIZE];
+}; //end SymmetricMatrix
+
+
+template <size_t N>
+class ConstSymmetricMatrixView : public ConstBaseSymmetricMatrix<N>
+{
+ public:
+ typedef ConstBaseSymmetricMatrix<N> base_type;
+
+ using base_type::DIM;
+ using base_type::DATA_SIZE;
+ using base_type::m_data;
+
+
+ public:
+
+ explicit
+ ConstSymmetricMatrixView (ConstVectorView _data)
+ : base_type (const_vector_view_cast(_data))
+ {
+ assert (_data.size() == DATA_SIZE);
+ }
+
+ explicit
+ ConstSymmetricMatrixView (const double _data[DATA_SIZE])
+ : base_type (const_vector_view_cast (ConstVectorView (_data, DATA_SIZE)))
+ {
+ }
+
+ ConstSymmetricMatrixView (const ConstSymmetricMatrixView & _smatrix)
+ : base_type (_smatrix.m_data)
+ {
+ }
+
+ ConstSymmetricMatrixView (const base_type & _smatrix)
+ : base_type (static_cast<const ConstSymmetricMatrixView &>(_smatrix).m_data)
+ {
+ }
+
+}; //end SymmetricMatrix
+
+
+// declaration needed for friend clause
+template <size_t N>
+void swap_view(SymmetricMatrixView<N> & m1, SymmetricMatrixView<N> & m2);
+
+
+template <size_t N>
+class SymmetricMatrixView : public BaseSymmetricMatrix<N>
+{
+ public:
+ typedef BaseSymmetricMatrix<N> base_type;
+ typedef typename base_type::base_type base_base_type;
+
+ using base_type::DIM;
+ using base_type::DATA_SIZE;
+ using base_type::m_data;
+
+ public:
+
+ explicit
+ SymmetricMatrixView (VectorView _data)
+ : base_type (_data)
+ {
+ assert (_data.size() == DATA_SIZE);
+ }
+
+ explicit
+ SymmetricMatrixView (double _data[DATA_SIZE])
+ : base_type (VectorView (_data, DATA_SIZE))
+ {
+ }
+
+ SymmetricMatrixView (const SymmetricMatrixView & _smatrix)
+ : base_type (_smatrix.m_data)
+ {
+ }
+
+ SymmetricMatrixView (SymmetricMatrix<DIM> & _smatrix)
+ : base_type (VectorView (_smatrix.m_adata, DATA_SIZE))
+ {
+ }
+
+ SymmetricMatrixView& operator= (const SymmetricMatrixView & _smatrix)
+ {
+ m_data = _smatrix.m_data;
+ return (*this);
+ }
+
+ SymmetricMatrixView& operator= (const base_base_type & _smatrix)
+ {
+ m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data;
+ return (*this);
+ }
+
+ friend
+ void swap_view<N>(SymmetricMatrixView & m1, SymmetricMatrixView & m2);
+
+}; //end SymmetricMatrix
+
+
+
+
+/*
+ * class ConstBaseSymmetricMatrix methods
+ */
+
+template <size_t N>
+inline
+std::string ConstBaseSymmetricMatrix<N>::str() const
+{
+ std::ostringstream oss;
+ oss << (*this);
+ return oss.str();
+}
+
+template <size_t N>
+inline
+ConstSymmetricMatrixView<N-1>
+ConstBaseSymmetricMatrix<N>::main_minor_const_view() const
+{
+ ConstVectorView data(m_data.get_gsl_vector()->data, DATA_SIZE - DIM);
+ ConstSymmetricMatrixView<N-1> mm(data);
+ return mm;
+}
+
+template <size_t N>
+inline
+SymmetricMatrix<N> ConstBaseSymmetricMatrix<N>::operator- () const
+{
+ SymmetricMatrix<N> result;
+ for (size_t i = 0; i < DATA_SIZE; ++i)
+ {
+ result.m_data[i] = -m_data[i];
+ }
+ return result;
+}
+
+
+/*
+ * class ConstBaseSymmetricMatrix friend free functions
+ */
+
+template <size_t N>
+inline
+bool operator== (ConstBaseSymmetricMatrix<N> const& _smatrix1,
+ ConstBaseSymmetricMatrix<N> const& _smatrix2)
+{
+ return (_smatrix1.m_data == _smatrix2.m_data);
+}
+
+/*
+ * class ConstBaseSymmetricMatrix related free functions
+ */
+
+template< size_t N, class charT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os,
+ const ConstBaseSymmetricMatrix<N> & _matrix)
+{
+ os << "[[" << _matrix(0,0);
+ for (size_t j = 1; j < N; ++j)
+ {
+ os << ", " << _matrix(0,j);
+ }
+ os << "]";
+ for (size_t i = 1; i < N; ++i)
+ {
+ os << "\n [" << _matrix(i,0);
+ for (size_t j = 1; j < N; ++j)
+ {
+ os << ", " << _matrix(i,j);
+ }
+ os << "]";
+ }
+ os << "]";
+ return os;
+}
+
+
+/*
+ * class ConstBaseSymmetricMatrix specialized methods
+ */
+
+template<>
+inline
+size_t ConstBaseSymmetricMatrix<2>::get_index (size_t i, size_t j)
+{
+ return (i+j);
+}
+
+template<>
+inline
+size_t ConstBaseSymmetricMatrix<3>::get_index (size_t i, size_t j)
+{
+ size_t k = i + j;
+ if (i == 2 || j == 2) ++k;
+ return k;
+}
+
+
+/*
+ * class BaseSymmetricMatrix methods
+ */
+
+template <size_t N>
+inline
+SymmetricMatrixView<N-1> BaseSymmetricMatrix<N>::main_minor_view()
+{
+ VectorView data(m_data.get_gsl_vector()->data, DATA_SIZE - DIM);
+ SymmetricMatrixView<N-1> mm(data);
+ return mm;
+}
+
+
+/*
+ * class SymmetricMatrixView friend free functions
+ */
+
+template <size_t N>
+inline
+void swap_view(SymmetricMatrixView<N> & m1, SymmetricMatrixView<N> & m2)
+{
+ swap_view(m1.m_data, m2.m_data);
+}
+
+} /* end namespace NL*/ } /* end namespace Geom*/
+
+
+
+
+#endif // _NL_SYMMETRIC_MATRIX_FS_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/include/2geom/numeric/vector.h b/include/2geom/numeric/vector.h
new file mode 100644
index 0000000..b66115b
--- /dev/null
+++ b/include/2geom/numeric/vector.h
@@ -0,0 +1,594 @@
+/*
+ * Vector, VectorView, ConstVectorView classes wrap the gsl vector routines;
+ * "views" mimic the semantic of C++ references: any operation performed
+ * on a "view" is actually performed on the "viewed object"
+ *
+ * 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 _NL_VECTOR_H_
+#define _NL_VECTOR_H_
+
+#include <cassert>
+#include <algorithm> // for std::swap
+#include <vector>
+#include <sstream>
+#include <string>
+
+
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_blas.h>
+
+
+namespace Geom { namespace NL {
+
+namespace detail
+{
+
+class BaseVectorImpl
+{
+ public:
+ double const& operator[](size_t i) const
+ {
+ return *gsl_vector_const_ptr(m_vector, i);
+ }
+
+ const gsl_vector* get_gsl_vector() const
+ {
+ return m_vector;
+ }
+ bool is_zero() const
+ {
+ return gsl_vector_isnull(m_vector);
+ }
+
+ bool is_positive() const
+ {
+ for ( size_t i = 0; i < size(); ++i )
+ {
+ if ( (*this)[i] <= 0 ) return false;
+ }
+ return true;
+ }
+
+ bool is_negative() const
+ {
+ for ( size_t i = 0; i < size(); ++i )
+ {
+ if ( (*this)[i] >= 0 ) return false;
+ }
+ return true;
+ }
+
+ bool is_non_negative() const
+ {
+ for ( size_t i = 0; i < size(); ++i )
+ {
+ if ( (*this)[i] < 0 ) return false;
+ }
+ return true;
+ }
+
+ double max() const
+ {
+ return gsl_vector_max(m_vector);
+ }
+
+ double min() const
+ {
+ return gsl_vector_min(m_vector);
+ }
+
+ size_t max_index() const
+ {
+ return gsl_vector_max_index(m_vector);
+ }
+
+ size_t min_index() const
+ {
+ return gsl_vector_min_index(m_vector);
+ }
+
+ size_t size() const
+ {
+ return m_size;
+ }
+
+ std::string str() const;
+
+ virtual ~BaseVectorImpl()
+ {
+ }
+
+ protected:
+ size_t m_size;
+ gsl_vector* m_vector;
+
+}; // end class BaseVectorImpl
+
+
+inline
+bool operator== (BaseVectorImpl const& v1, BaseVectorImpl const& v2)
+{
+ if (v1.size() != v2.size()) return false;
+
+ for (size_t i = 0; i < v1.size(); ++i)
+ {
+ if (v1[i] != v2[i]) return false;
+ }
+ return true;
+}
+
+template< class charT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const BaseVectorImpl & _vector)
+{
+ if (_vector.size() == 0 ) return os;
+ os << "[" << _vector[0];
+ for (unsigned int i = 1; i < _vector.size(); ++i)
+ {
+ os << ", " << _vector[i];
+ }
+ os << "]";
+ return os;
+}
+
+inline
+std::string BaseVectorImpl::str() const
+{
+ std::ostringstream oss;
+ oss << (*this);
+ return oss.str();
+}
+
+inline
+double dot(BaseVectorImpl const& v1, BaseVectorImpl const& v2)
+{
+ double result;
+ gsl_blas_ddot(v1.get_gsl_vector(), v2.get_gsl_vector(), &result);
+ return result;
+}
+
+
+class VectorImpl : public BaseVectorImpl
+{
+ public:
+ typedef BaseVectorImpl base_type;
+
+ public:
+ void set_all(double x)
+ {
+ gsl_vector_set_all(m_vector, x);
+ }
+
+ void set_basis(size_t i)
+ {
+ gsl_vector_set_basis(m_vector, i);
+ }
+
+ using base_type::operator[];
+
+ double & operator[](size_t i)
+ {
+ return *gsl_vector_ptr(m_vector, i);
+ }
+
+ using base_type::get_gsl_vector;
+
+ gsl_vector* get_gsl_vector()
+ {
+ return m_vector;
+ }
+
+ void swap_elements(size_t i, size_t j)
+ {
+ gsl_vector_swap_elements(m_vector, i, j);
+ }
+
+ void reverse()
+ {
+ gsl_vector_reverse(m_vector);
+ }
+
+ VectorImpl & scale(double x)
+ {
+ gsl_vector_scale(m_vector, x);
+ return (*this);
+ }
+
+ VectorImpl & translate(double x)
+ {
+ gsl_vector_add_constant(m_vector, x);
+ return (*this);
+ }
+
+ VectorImpl & operator+=(base_type const& _vector)
+ {
+ gsl_vector_add(m_vector, _vector.get_gsl_vector());
+ return (*this);
+ }
+
+ VectorImpl & operator-=(base_type const& _vector)
+ {
+ gsl_vector_sub(m_vector, _vector.get_gsl_vector());
+ return (*this);
+ }
+
+}; // end class VectorImpl
+
+} // end namespace detail
+
+
+using detail::operator==;
+using detail::operator<<;
+
+class Vector : public detail::VectorImpl
+{
+ public:
+ typedef detail::VectorImpl base_type;
+
+ public:
+ Vector(size_t n)
+ {
+ m_size = n;
+ m_vector = gsl_vector_alloc(n);
+ }
+
+ Vector(size_t n, double x)
+ {
+ m_size = n;
+ m_vector = gsl_vector_alloc(n);
+ gsl_vector_set_all(m_vector, x);
+ }
+
+ // create a vector with n elements all set to zero
+ // but the i-th that is set to 1
+ Vector(size_t n, size_t i)
+ {
+ m_size = n;
+ m_vector = gsl_vector_alloc(n);
+ gsl_vector_set_basis(m_vector, i);
+ }
+
+ Vector(Vector const& _vector)
+ : base_type()
+ {
+ m_size = _vector.size();
+ m_vector = gsl_vector_alloc(size());
+ gsl_vector_memcpy(m_vector, _vector.m_vector);
+ }
+
+ explicit
+ Vector(base_type::base_type const& _vector)
+ {
+ m_size = _vector.size();
+ m_vector = gsl_vector_alloc(size());
+ gsl_vector_memcpy(m_vector, _vector.get_gsl_vector());
+ }
+
+ ~Vector() override
+ {
+ gsl_vector_free(m_vector);
+ }
+
+
+ Vector & operator=(Vector const& _vector)
+ {
+ assert( size() == _vector.size() );
+ gsl_vector_memcpy(m_vector, _vector.m_vector);
+ return (*this);
+ }
+
+ Vector & operator=(base_type::base_type const& _vector)
+ {
+ assert( size() == _vector.size() );
+ gsl_vector_memcpy(m_vector, _vector.get_gsl_vector());
+ return (*this);
+ }
+
+ Vector & scale(double x)
+ {
+ return static_cast<Vector&>( base_type::scale(x) );
+ }
+
+ Vector & translate(double x)
+ {
+ return static_cast<Vector&>( base_type::translate(x) );
+ }
+
+ Vector & operator+=(base_type::base_type const& _vector)
+ {
+ return static_cast<Vector&>( base_type::operator+=(_vector) );
+ }
+
+ Vector & operator-=(base_type::base_type const& _vector)
+ {
+ return static_cast<Vector&>( base_type::operator-=(_vector) );
+ }
+
+ friend
+ void swap(Vector & v1, Vector & v2);
+ friend
+ void swap_any(Vector & v1, Vector & v2);
+
+}; // end class Vector
+
+
+// warning! these operations invalidate any view of the passed vector objects
+inline
+void swap(Vector & v1, Vector & v2)
+{
+ assert(v1.size() == v2.size());
+ using std::swap;
+ swap(v1.m_vector, v2.m_vector);
+}
+
+inline
+void swap_any(Vector & v1, Vector & v2)
+{
+ using std::swap;
+ swap(v1.m_vector, v2.m_vector);
+ swap(v1.m_size, v2.m_size);
+}
+
+
+class ConstVectorView : public detail::BaseVectorImpl
+{
+ public:
+ typedef detail::BaseVectorImpl base_type;
+
+ public:
+ ConstVectorView(const base_type & _vector, size_t n, size_t offset = 0)
+ : m_vector_view( gsl_vector_const_subvector(_vector.get_gsl_vector(), offset, n) )
+ {
+ m_size = n;
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ ConstVectorView(const base_type & _vector, size_t n, size_t offset , size_t stride)
+ : m_vector_view( gsl_vector_const_subvector_with_stride(_vector.get_gsl_vector(), offset, stride, n) )
+ {
+ m_size = n;
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ ConstVectorView(const double* _vector, size_t n, size_t offset = 0)
+ : m_vector_view( gsl_vector_const_view_array(_vector + offset, n) )
+ {
+ m_size = n;
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ ConstVectorView(const double* _vector, size_t n, size_t offset, size_t stride)
+ : m_vector_view( gsl_vector_const_view_array_with_stride(_vector + offset, stride, n) )
+ {
+ m_size = n;
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ explicit
+ ConstVectorView(gsl_vector_const_view _gsl_vector_view)
+ : m_vector_view(_gsl_vector_view)
+ {
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ m_size = m_vector->size;
+ }
+
+ explicit
+ ConstVectorView(const std::vector<double>& _vector)
+ : m_vector_view( gsl_vector_const_view_array(&(_vector[0]), _vector.size()) )
+ {
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ m_size = _vector.size();
+ }
+
+ ConstVectorView(const ConstVectorView & _vector)
+ : base_type(),
+ m_vector_view(_vector.m_vector_view)
+ {
+ m_size = _vector.size();
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ ConstVectorView(const base_type & _vector)
+ : m_vector_view(gsl_vector_const_subvector(_vector.get_gsl_vector(), 0, _vector.size()))
+ {
+ m_size = _vector.size();
+ m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) );
+ }
+
+ private:
+ gsl_vector_const_view m_vector_view;
+
+}; // end class ConstVectorView
+
+
+
+
+class VectorView : public detail::VectorImpl
+{
+ public:
+ typedef detail::VectorImpl base_type;
+
+ public:
+ VectorView(base_type & _vector, size_t n, size_t offset = 0, size_t stride = 1)
+ {
+ m_size = n;
+ if (stride == 1)
+ {
+ m_vector_view
+ = gsl_vector_subvector(_vector.get_gsl_vector(), offset, n);
+ m_vector = &(m_vector_view.vector);
+ }
+ else
+ {
+ m_vector_view
+ = gsl_vector_subvector_with_stride(_vector.get_gsl_vector(), offset, stride, n);
+ m_vector = &(m_vector_view.vector);
+ }
+ }
+
+ VectorView(double* _vector, size_t n, size_t offset = 0, size_t stride = 1)
+ {
+ m_size = n;
+ if (stride == 1)
+ {
+ m_vector_view
+ = gsl_vector_view_array(_vector + offset, n);
+ m_vector = &(m_vector_view.vector);
+ }
+ else
+ {
+ m_vector_view
+ = gsl_vector_view_array_with_stride(_vector + offset, stride, n);
+ m_vector = &(m_vector_view.vector);
+ }
+
+ }
+
+ VectorView(const VectorView & _vector)
+ : base_type()
+ {
+ m_size = _vector.size();
+ m_vector_view = _vector.m_vector_view;
+ m_vector = &(m_vector_view.vector);
+ }
+
+ VectorView(Vector & _vector)
+ {
+ m_size = _vector.size();
+ m_vector_view = gsl_vector_subvector(_vector.get_gsl_vector(), 0, size());
+ m_vector = &(m_vector_view.vector);
+ }
+
+ explicit
+ VectorView(gsl_vector_view _gsl_vector_view)
+ : m_vector_view(_gsl_vector_view)
+ {
+ m_vector = &(m_vector_view.vector);
+ m_size = m_vector->size;
+ }
+
+ explicit
+ VectorView(std::vector<double> & _vector)
+ {
+ m_size = _vector.size();
+ m_vector_view = gsl_vector_view_array(&(_vector[0]), _vector.size());
+ m_vector = &(m_vector_view.vector);
+ }
+
+ VectorView & operator=(VectorView const& _vector)
+ {
+ assert( size() == _vector.size() );
+ gsl_vector_memcpy(m_vector, _vector.get_gsl_vector());
+ return (*this);
+ }
+
+ VectorView & operator=(base_type::base_type const& _vector)
+ {
+ assert( size() == _vector.size() );
+ gsl_vector_memcpy(m_vector, _vector.get_gsl_vector());
+ return (*this);
+ }
+
+ VectorView & scale(double x)
+ {
+ return static_cast<VectorView&>( base_type::scale(x) );
+ }
+
+ VectorView & translate(double x)
+ {
+ return static_cast<VectorView&>( base_type::translate(x) );
+ }
+
+ VectorView & operator+=(base_type::base_type const& _vector)
+ {
+ return static_cast<VectorView&>( base_type::operator+=(_vector) );
+ }
+
+ VectorView & operator-=(base_type::base_type const& _vector)
+ {
+ return static_cast<VectorView&>( base_type::operator-=(_vector) );
+ }
+
+ friend
+ void swap_view(VectorView & v1, VectorView & v2);
+
+ private:
+ gsl_vector_view m_vector_view;
+
+}; // end class VectorView
+
+
+inline
+void swap_view(VectorView & v1, VectorView & v2)
+{
+ assert( v1.size() == v2.size() );
+ using std::swap;
+ swap(v1.m_vector_view, v2.m_vector_view); // not swap m_vector too
+}
+
+inline
+const VectorView & const_vector_view_cast (const ConstVectorView & view)
+{
+ const detail::BaseVectorImpl & bvi
+ = static_cast<const detail::BaseVectorImpl &>(view);
+ const VectorView & vv = reinterpret_cast<const VectorView &>(bvi);
+ return vv;
+}
+
+inline
+VectorView & const_vector_view_cast (ConstVectorView & view)
+{
+ detail::BaseVectorImpl & bvi
+ = static_cast<detail::BaseVectorImpl &>(view);
+ VectorView & vv = reinterpret_cast<VectorView &>(bvi);
+ return vv;
+}
+
+
+} } // end namespaces
+
+
+#endif /*_NL_VECTOR_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/include/2geom/ord.h b/include/2geom/ord.h
new file mode 100644
index 0000000..e190a4a
--- /dev/null
+++ b/include/2geom/ord.h
@@ -0,0 +1,80 @@
+/** @file
+ * @brief Comparator template
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_ORD_H
+#define LIB2GEOM_SEEN_ORD_H
+
+namespace {
+
+enum Cmp {
+ LESS_THAN=-1,
+ GREATER_THAN=1,
+ EQUAL_TO=0
+};
+
+static inline Cmp operator-(Cmp x) {
+ switch(x) {
+ case LESS_THAN:
+ return GREATER_THAN;
+ case GREATER_THAN:
+ return LESS_THAN;
+ case EQUAL_TO:
+ return EQUAL_TO;
+ }
+}
+
+template <typename T1, typename T2>
+inline Cmp cmp(T1 const &a, T2 const &b) {
+ if ( a < b ) {
+ return LESS_THAN;
+ } else if ( b < a ) {
+ return GREATER_THAN;
+ } else {
+ return EQUAL_TO;
+ }
+}
+
+}
+
+#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/include/2geom/orphan-code/arc-length.h b/include/2geom/orphan-code/arc-length.h
new file mode 100644
index 0000000..8029f04
--- /dev/null
+++ b/include/2geom/orphan-code/arc-length.h
@@ -0,0 +1,58 @@
+/**
+ * \file arc-length.h
+ * \brief Arc length computations for paths
+ *//*
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * 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 __2GEOM_ARC_LENGTH_H
+#define __2GEOM_ARC_LENGTH_H
+
+#include <2geom/path.h>
+
+/* Routines in this group return a path that looks the same, but
+ * include extra knots for certain points of interest. */
+
+/* find_vector_extreme_points
+ * extreme points . dir.
+ */
+
+double arc_length_subdividing(Geom::Path const & p, double tol);
+double arc_length_integrating(Geom::Path const & p, double tol);
+
+#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/include/2geom/orphan-code/chebyshev.h b/include/2geom/orphan-code/chebyshev.h
new file mode 100644
index 0000000..f729e1f
--- /dev/null
+++ b/include/2geom/orphan-code/chebyshev.h
@@ -0,0 +1,30 @@
+#ifndef _CHEBYSHEV
+#define _CHEBYSHEV
+
+#include <2geom/sbasis.h>
+#include <2geom/interval.h>
+
+/*** Conversion between Chebyshev approximation and SBasis.
+ *
+ */
+
+namespace Geom{
+
+SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p=0);
+SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), int order, Interval in, void* p=0);
+SBasis chebyshev(unsigned n);
+
+};
+
+/*
+ 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 :
+
+#endif
diff --git a/include/2geom/orphan-code/intersection-by-smashing.h b/include/2geom/orphan-code/intersection-by-smashing.h
new file mode 100644
index 0000000..996ec99
--- /dev/null
+++ b/include/2geom/orphan-code/intersection-by-smashing.h
@@ -0,0 +1,78 @@
+/*
+ * Diffeomorphism-based intersector: given two curves
+ * M(t)=(x(t),y(t)) and N(u)=(X(u),Y(u))
+ * and supposing M is a graph over the x-axis, we compute y(x) and the
+ * transformation:
+ * X <- X
+ * Y <- Y - y(X)
+ * smashes M on the x axis. The intersections are then given by the roots of:
+ * Y(u) - y(X(u)) = 0
+ *//*
+ * Authors:
+ * J.-F. Barraud <jfbarraud at 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.
+ */
+
+#ifndef SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_H
+#define SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_H
+
+#include <2geom/d2.h>
+#include <2geom/interval.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <cstdlib>
+#include <vector>
+#include <algorithm>
+
+
+namespace Geom{
+
+struct SmashIntersection {
+ Rect times;
+ Rect bbox;
+};
+
+std::vector<SmashIntersection> smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol);
+std::vector<SmashIntersection> monotonic_smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol);
+//std::vector<Intersection> monotonic_smash_intersect( Curve const &a, double a_from, double a_to,
+// Curve const &b, double b_from, double b_to, double tol);
+
+std::vector<Interval> monotonicSplit(D2<SBasis> const &p);
+
+} // end namespace Geom
+
+#endif // !SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_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/include/2geom/orphan-code/linear-of.h b/include/2geom/orphan-code/linear-of.h
new file mode 100644
index 0000000..9ba1fb2
--- /dev/null
+++ b/include/2geom/orphan-code/linear-of.h
@@ -0,0 +1,269 @@
+/**
+ * \file
+ * \brief Linear fragment function class
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 SEEN_LINEAR_OF_H
+#define SEEN_LINEAR_OF_H
+#include <2geom/interval.h>
+#include <2geom/math-utils.h>
+
+namespace Geom{
+
+template <typename T>
+inline T lerp(double t, T a, T b) { return a*(1-t) + b*t; }
+
+template <typename T>
+class SBasisOf;
+
+template <typename T>
+class HatOf{
+public:
+ HatOf () {}
+ HatOf(T d) :d(d) {}
+ operator T() const { return d; }
+ T d;
+};
+
+template <typename T>
+class TriOf{
+public:
+ TriOf () {}
+ TriOf(double d) :d(d) {}
+ operator T() const { return d; }
+ T d;
+};
+
+
+//--------------------------------------------------------------------------
+#ifdef USE_SBASIS_OF
+template <typename T>
+class LinearOf;
+typedef Geom::LinearOf<double> Linear;
+#endif
+//--------------------------------------------------------------------------
+
+template <typename T>
+class LinearOf{
+public:
+ T a[2];
+ LinearOf() {}
+ LinearOf(T aa, T b) {a[0] = aa; a[1] = b;}
+ //LinearOf(double aa, double b) {a[0] = T(aa); a[1] = T(b);}
+ LinearOf(HatOf<T> h, TriOf<T> t) {
+ a[0] = T(h) - T(t)/2;
+ a[1] = T(h) + T(t)/2;
+ }
+
+ LinearOf(HatOf<T> h) {
+ a[0] = T(h);
+ a[1] = T(h);
+ }
+
+ unsigned input_dim(){return T::input_dim() + 1;}
+
+ T operator[](const int i) const {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+ T& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef T output_type;
+ inline bool isZero() const { return a[0].isZero() && a[1].isZero(); }
+ inline bool isConstant() const { return a[0] == a[1]; }
+ inline bool isFinite() const { return std::isfinite(a[0]) && std::isfinite(a[1]); }
+
+ inline T at0() const { return a[0]; }
+ inline T at1() const { return a[1]; }
+
+ inline T valueAt(double t) const { return lerp(t, a[0], a[1]); }
+ inline T operator()(double t) const { return valueAt(t); }
+
+ //defined in sbasis.h
+ inline SBasisOf<T> toSBasis() const;
+
+//This is specific for T=double!!
+ inline OptInterval bounds_exact() const { return Interval(a[0], a[1]); }
+ inline OptInterval bounds_fast() const { return bounds_exact(); }
+ inline OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+
+ operator TriOf<T>() const {
+ return a[1] - a[0];
+ }
+ operator HatOf<T>() const {
+ return (a[1] + a[0])/2;
+ }
+};
+
+template <>
+unsigned LinearOf<double>::input_dim(){return 1;}
+template <>
+inline OptInterval LinearOf<double>::bounds_exact() const { return Interval(a[0], a[1]); }
+template <>
+inline OptInterval LinearOf<double>::bounds_fast() const { return bounds_exact(); }
+template <>
+inline OptInterval LinearOf<double>::bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+template <>
+inline bool LinearOf<double>::isZero() const { return a[0]==0 && a[1]==0; }
+
+template <typename T>
+inline LinearOf<T> reverse(LinearOf<T> const &a) { return LinearOf<T>(a[1], a[0]); }
+
+//IMPL: AddableConcept
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return LinearOf<T>(a[0] + b[0], a[1] + b[1]);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return LinearOf<T>(a[0] - b[0], a[1] - b[1]);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, LinearOf<T> const & b) {
+ a[0] += b[0]; a[1] += b[1];
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, LinearOf<T> const & b) {
+ a[0] -= b[0]; a[1] -= b[1];
+ return a;
+}
+//IMPL: OffsetableConcept
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0] + b, a[1] + b);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0] - b, a[1] - b);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, double b) {
+ a[0] += b; a[1] += b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, double b) {
+ a[0] -= b; a[1] -= b;
+ return a;
+}
+/*
+//We can in fact offset in coeff ring T...
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0] + b, a[1] + b);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0] - b, a[1] - b);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, T b) {
+ a[0] += b; a[1] += b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, T b) {
+ a[0] -= b; a[1] -= b;
+ return a;
+}
+*/
+
+//IMPL: boost::EqualityComparableConcept
+template <typename T>
+inline bool operator==(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return a[0] == b[0] && a[1] == b[1];
+}
+template <typename T>
+inline bool operator!=(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return a[0] != b[0] || a[1] != b[1];
+}
+//IMPL: ScalableConcept
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const &a) {
+ return LinearOf<T>(-a[0], -a[1]);
+}
+template <typename T>
+inline LinearOf<T> operator*(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0]*b, a[1]*b);
+}
+template <typename T>
+inline LinearOf<T> operator/(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0]/b, a[1]/b);
+}
+template <typename T>
+inline LinearOf<T> operator*=(LinearOf<T> & a, double b) {
+ a[0] *= b; a[1] *= b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T> operator/=(LinearOf<T> & a, double b) {
+ a[0] /= b; a[1] /= b;
+ return a;
+}
+/*
+//We can in fact rescale in coeff ring T... (but not divide!)
+template <typename T>
+inline LinearOf<T> operator*(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0]*b, a[1]*b);
+}
+template <typename T>
+inline LinearOf<T> operator/(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0]/b, a[1]/b);
+}
+template <typename T>
+inline LinearOf<T> operator*=(LinearOf<T> & a, T b) {
+ a[0] *= b; a[1] *= b;
+ return a;
+}
+*/
+
+};
+
+#endif //SEEN_LINEAR_OF_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/include/2geom/orphan-code/linearN.h b/include/2geom/orphan-code/linearN.h
new file mode 100644
index 0000000..bb27c30
--- /dev/null
+++ b/include/2geom/orphan-code/linearN.h
@@ -0,0 +1,363 @@
+/**
+ * @file
+ * @brief LinearN fragment function class
+ *//*
+ * Authors:
+ * JF Barraud <jf.barraud@gmail.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 SEEN_LINEARN_H
+#define SEEN_LINEARN_H
+#include <2geom/interval.h>
+#include <2geom/math-utils.h>
+#include <2geom/linear.h> //for conversion purpose ( + lerp() )
+
+#include <iostream>
+
+namespace Geom{
+
+//TODO: define this only once!! (see linear.h)
+inline double lerpppp(double t, double a, double b) { return a*(1-t) + b*t; }
+
+template<unsigned n>
+class SBasisN;
+
+template<unsigned n>
+class LinearN{
+public:
+ double a[1<<n];// 1<<n is 2^n
+ LinearN() {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] = 0.;
+ }
+ }
+ LinearN(double aa[]) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] = aa[i];
+ }
+ }
+ LinearN(double c) {
+ for (unsigned i=0; i<(1<<n); i++){
+ a[i] = c;
+ }
+ }
+ LinearN(LinearN<n-1> const &aa, LinearN<n-1> const &b, unsigned var=0) {
+// for (unsigned i=0; i<(1<<n-1); i++){
+// a[i] = aa[i];
+// a[i+(1<<(n-1))] = b[i];
+// }
+ unsigned mask = (1<<var)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx0 = (high_i<<1)|low_i;
+ unsigned idx1 = (high_i<<1)|(1<<var)|low_i;
+ a[idx0] = aa[i];
+ a[idx1] = b[i];
+ }
+
+ }
+ double operator[](const int i) const {
+ assert( i >= 0 );
+ assert( i < (1<<n) );
+ return a[i];
+ }
+ double& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < (1<<n) );
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+ unsigned input_dim() const {return n;}
+ inline bool isZero() const {
+ for (unsigned i=0; i < (1<<n); i++){
+ if (a[i] != 0) return false;
+ }
+ return true; }
+ inline bool isConstant() const {
+ for (unsigned i=1; i < (1<<n); i++){
+ if (a[i] != a[0]) return false;
+ }
+ return true; }
+ inline bool isConstant(unsigned var) const {
+ unsigned mask = (1<<var)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx0 = (high_i<<1)|low_i;
+ unsigned idx1 = (high_i<<1)|(1<<var)|low_i;
+ if (a[idx0] != a[idx1]) return false;
+ }
+ return true;
+ }
+ inline bool isFinite() const {
+ for (unsigned i=0; i < (1<<n); i++){
+ if ( !std::isfinite(a[i]) ) return false;
+ }
+ return true; }
+ //value if k-th variable is set to 0.
+ inline LinearN<n-1> at0(unsigned k=0) const {
+ LinearN<n-1> res;
+ unsigned mask = (1<<k)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx = (high_i<<1)|low_i;
+ res[i] = a[idx];
+ }
+ return res;
+ }
+ //value if k-th variable is set to 1.
+ inline LinearN<n-1> at1(unsigned k=0) const {
+ LinearN<n-1> res;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned mask = (1<<k)-1;
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx = (high_i<<1)|(1<<k)|low_i;
+ res[i] = a[idx];
+ }
+ return res;
+ }
+ inline double atCorner(unsigned k) const {
+ assert( k < (1<<n) );
+ return a[k];
+ }
+ inline double atCorner(double t[]) const {
+ unsigned k=0;
+ for(unsigned i=0; i<n; i++){
+ if (t[i] == 1.) k = k | (1<<i);
+ else assert( t[i] == 0. );
+ }
+ return atCorner(k);
+ }
+ inline LinearN<n-1> partialEval(double t, unsigned var=0 ) const {
+ LinearN<n-1> res;
+ res = at0(var)*(1-t) + at1(var)*t;
+ return res;
+ }
+
+ //fixed and flags are used for recursion.
+ inline double valueAt(double t[], unsigned fixed=0, unsigned flags=0 ) const {
+ if (fixed == n) {
+ return a[flags];
+ }else{
+ double a0 = valueAt(t, fixed+1, flags);
+ double a1 = valueAt(t, fixed+1, flags|(1<<fixed));
+ return lerpppp( t[fixed], a0, a1 );
+ }
+ }
+ inline double operator()(double t[]) const { return valueAt(t); }
+
+ //defined in sbasisN.h
+ inline SBasisN<n> toSBasisN() const;
+
+ inline OptInterval bounds_exact() const {
+ double min=a[0], max=a[0];
+ for (unsigned i=1; i < (1<<n); i++){
+ if (a[i] < min) min = a[i];
+ if (a[i] > max) max = a[i];
+ }
+ return Interval(min, max);
+ }
+ inline OptInterval bounds_fast() const { return bounds_exact(); }
+ //inline OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+};
+
+//LinearN<0> are doubles. Specialize them out.
+template<>
+class LinearN<0>{
+public:
+ double d;
+ LinearN () {}
+ LinearN(double d) :d(d) {}
+ operator double() const { return d; }
+ double operator[](const int i) const {assert (i==0); return d;}
+ double& operator[](const int i) {assert (i==0); return d;}
+ typedef double output_type;
+ unsigned input_dim() const {return 0;}
+ inline bool isZero() const { return d==0; }
+ inline bool isConstant() const { return true; }
+ inline bool isFinite() const { return std::isfinite(d); }
+};
+
+//LinearN<1> are usual Linear. Allow conversion.
+Linear toLinear(LinearN<1> f){
+ return Linear(f[0],f[1]);
+}
+
+
+
+//inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); }
+
+//IMPL: AddableConcept
+template<unsigned n>
+inline LinearN<n> operator+(LinearN<n> const & a, LinearN<n> const & b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] + b[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const & a, LinearN<n> const & b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] - b[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n>& operator+=(LinearN<n> & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] += b[i];
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n>& operator-=(LinearN<n> & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] -= b[i];
+ }
+ return a;
+}
+//IMPL: OffsetableConcept
+template<unsigned n>
+inline LinearN<n> operator+(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] + b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] - b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n>& operator+=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] += b;
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n>& operator-=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] -= b;
+ }
+ return a;
+}
+//IMPL: boost::EqualityComparableConcept
+template<unsigned n>
+inline bool operator==(LinearN<n> const & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+}
+template<unsigned n>
+inline bool operator!=(LinearN<n> const & a, LinearN<n> const & b) {
+ return !(a==b);
+}
+//IMPL: ScalableConcept
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const &a) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = -a[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator*(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] * b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator/(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] / b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator*=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] *= b;
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n> operator/=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] /= b;
+ }
+ return a;
+}
+
+template<unsigned n>
+void setToVariable(LinearN<n> &x, unsigned k){;
+ x = LinearN<n>(0.);
+ unsigned mask = 1<<k;
+ for (unsigned i=0; i < (1<<n); i++){
+ if ( i & mask ) x[i] = 1;
+ }
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearN<n> &bo) {
+ out_file << "{";
+ for (unsigned i=0; i < (1<<n); i++){
+ out_file << bo[i]<<(i == (1<<n)-1 ? "}" : ",");
+ }
+ return out_file;
+}
+
+
+}
+#endif //SEEN_LINEAR_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/include/2geom/orphan-code/redblacktree.h b/include/2geom/orphan-code/redblacktree.h
new file mode 100644
index 0000000..9d79342
--- /dev/null
+++ b/include/2geom/orphan-code/redblacktree.h
@@ -0,0 +1,121 @@
+/**
+ * \file
+ * \brief
+ * Implementation of Red-Black Tree as described in
+ * Intorduction to Algorithms. Cormen et al. Mc Grow Hill. 1990. pp 263-280
+ *
+ * The intention is to implement interval trees mentioned in the same book, after the red-black.
+ * Interval are heavily based on red-black trees (most operations are the same). So, we begin first
+ * with implementing red-black!
+ *
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright 2009-2009 Evangelos Katsikaros
+ *
+ * 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 SEEN_LIB2GEOM_REDBLACKTREE_H
+#define SEEN_LIB2GEOM_REDBLACKTREE_H
+
+#include <vector>
+//#include <cassert>
+#include <limits>
+#include <cfloat>
+
+#include <2geom/d2.h>
+#include <2geom/interval.h>
+
+namespace Geom{
+
+class RedBlack{
+public:
+ Interval interval; // Key of the redblack tree will be the min of the interval
+ RedBlack *left, *right, *parent;
+ bool isRed;
+ Coord subtree_max; // subtree_max = max( x->left->subtree_max, x->right->subtree_max, x->high )
+ int data;
+
+ RedBlack(): left(0), right(0), parent(0), isRed(false), subtree_max(0.0), data(0) {
+ Interval interval(0.0, 0.0);
+ }
+/*
+ RedBlack(Coord min, Coord max): left(0), right(0), parent(0), isRed(false), subtree_max(0.0), data(0) {
+ Interval interval( min, max );
+ }
+*/
+ inline Coord key(){ return interval.min(); };
+ inline Coord high(){ return interval.max(); };
+};
+
+
+class RedBlackTree{
+public:
+ RedBlack* root;
+
+ RedBlackTree(): root(0) {}
+
+ void insert(Rect const &r, int shape, int dimension);
+ void insert(Coord dimension_min, Coord dimension_max, int shape);
+
+ void erase(Rect const &r);
+ void erase(int shape);
+
+ RedBlack* search(Rect const &r, int dimension);
+ RedBlack* search(Interval i);
+ RedBlack* search(Coord a, Coord b);
+
+ void print_tree();
+private:
+ void inorder_tree_walk(RedBlack* x);
+ RedBlack* tree_minimum(RedBlack* x);
+ RedBlack* tree_successor(RedBlack* x);
+
+ void left_rotate(RedBlack* x);
+ void right_rotate(RedBlack* x);
+ void tree_insert(RedBlack* x);
+
+ void update_max(RedBlack* x);
+
+ RedBlack* erase(RedBlack* x); // TODO why rerutn pointer? to collect garbage ???
+ void erase_fixup(RedBlack* x);
+
+};
+
+} // end namespace Geom
+
+#endif // !SEEN_LIB2GEOM_REDBLACKTREE_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/include/2geom/orphan-code/rtree.h b/include/2geom/orphan-code/rtree.h
new file mode 100644
index 0000000..3ffae8e
--- /dev/null
+++ b/include/2geom/orphan-code/rtree.h
@@ -0,0 +1,241 @@
+/**
+ * \file
+ * \brief
+ * Implementation of Red-Black Tree as described in
+ * Intorduction to Algorithms. Cormen et al. Mc Grow Hill. 1990. pp 263-280
+ *
+ * The intention is to implement interval trees mentioned in the same book, after the red-black.
+ * Interval are heavily based on red-black trees (most operations are the same). So, we begin first
+ * with implementing red-black!
+ *
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright 2009-2009 Evangelos Katsikaros
+ *
+ * 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 SEEN_LIB2GEOM_RTREE_H
+#define SEEN_LIB2GEOM_RTREE_H
+
+#include <vector>
+#include <utility>
+
+
+#include <2geom/d2.h>
+#include <2geom/interval.h>
+
+namespace Geom{
+
+// used only in pick_next( )
+enum enum_add_to_group {
+ ADD_TO_GROUP_A = 0,
+ ADD_TO_GROUP_B
+};
+
+
+enum enum_split_strategy {
+ QUADRATIC_SPIT = 0,
+ LINEAR_COST,
+ TOTAL_STRATEGIES // this one must be the last item
+};
+
+
+
+template <typename T>
+class pedantic_vector:public std::vector<T> {
+public:
+ pedantic_vector(size_t s=0) : std::vector<T>(s) {}
+ T& operator[](unsigned i) {
+ //assert(i >= 0);
+ assert(i < std::vector<T>::size());
+ return std::vector<T>::operator[](i);
+ }
+ T const& operator[](unsigned i) const {
+ //assert(i >= 0);
+ assert(i < std::vector<T>::size());
+ return std::vector<T>::operator[](i);
+ }
+/*
+ erase( std::vector<T>::iterator it ) {
+ //assert(i >= 0);
+ assert( it < std::vector<T>::size());
+ return std::vector<T>::erase(it);
+ }
+*/
+};
+
+class RTreeNode;
+
+class RTreeRecord_Leaf{
+public:
+ Rect bounding_box;
+ int data;
+
+ RTreeRecord_Leaf(): bounding_box(), data(0)
+ {}
+
+ RTreeRecord_Leaf(Rect bb, int d): bounding_box(bb), data(d)
+ {}
+};
+
+class RTreeRecord_NonLeaf{
+public:
+ Rect bounding_box;
+ RTreeNode* data;
+
+ RTreeRecord_NonLeaf(): bounding_box(), data(0)
+ {}
+
+ RTreeRecord_NonLeaf(Rect bb, RTreeNode* d): bounding_box(bb), data(d)
+ {}
+};
+
+/*
+R-Tree has 2 kinds of nodes
+* Leaves which store:
+ - the actual data
+ - the bounding box of the data
+
+* Non-Leaves which store:
+ - a child node (data)
+ - the bounding box of the child node
+
+This causes some code duplication in rtree.cpp. There are 2 cases:
+- we care whether we touch a leaf/non-leaf node, since we write data in the node, so we want to
+ write the correct thing (int or RTreeNode*)
+- we do NOT care whether we touch a leaf/non-leaf node, because we only read/write the bounding
+ boxes which is the same in both cases.
+
+TODO:
+A better design would eliminate the duplication in the 2nd case, but we can't avoid the 1st probably.
+*/
+class RTreeNode{
+public:
+ // first: bounding box
+ // second: "data" (leaf-node) or node (NON leaf-node)
+ //pedantic_vector< RTreeRecord_Leaf > children_leaves; // if this is empty, then node is leaf-node
+ //pedantic_vector< RTreeRecord_NonLeaf > children_nodes; // if this is empty, then node is NON-leaf node
+
+ std::vector< RTreeRecord_Leaf > children_leaves; // if this is empty, then node is leaf-node
+ std::vector< RTreeRecord_NonLeaf > children_nodes; // if this is empty, then node is NON-leaf node
+
+ RTreeNode(): children_leaves(0), children_nodes(0)
+ {}
+
+};
+
+
+class RTree{
+public:
+ RTreeNode* root;
+
+ // min/max records per node
+ unsigned min_records;
+ unsigned max_records; // allow +1 (used during insert)
+
+ enum_split_strategy split_strategy;
+
+
+ RTree( unsigned n, unsigned m, enum_split_strategy split_s ):
+ root(0), min_records( n ), max_records( m ), split_strategy( split_s ),
+ tree_height(0)
+ {}
+
+ void insert( Rect const &r, unsigned shape);
+ void search( const Rect &search_area, std::vector< int >* result, const RTreeNode* subtree ) const;
+ //int erase( const RTreeRecord_Leaf & search );
+ int erase( const Rect &search_area, const int shape_to_delete );
+
+// update
+
+ void print_tree(RTreeNode* subtree_root, int depth ) const;
+
+private:
+ unsigned tree_height; // 0 is the root level
+
+ void insert( //Rect const &r,
+ //int shape,
+ const RTreeRecord_Leaf &leaf_record,
+ const bool &insert_high = false,
+ const unsigned &stop_height = 0,
+ const RTreeRecord_NonLeaf &nonleaf_record = RTreeRecord_NonLeaf()
+ );
+ // I1
+ RTreeNode* choose_node( const Rect &r, const bool &insert_high = false, const unsigned &stop_height=0 ) const;
+ double find_waste_area( const Rect &a, const Rect &b ) const;
+ double find_enlargement( const Rect &a, const Rect &b ) const;
+
+ // I2
+ std::pair<RTreeNode*, RTreeNode*> split_node( RTreeNode *s );
+ // QUADRATIC_SPIT
+ std::pair<RTreeNode*, RTreeNode*> quadratic_split( RTreeNode* s );
+ std::pair<unsigned, unsigned> pick_seeds( RTreeNode* s ) const;
+ std::pair<unsigned, enum_add_to_group> pick_next( RTreeNode* group_a, RTreeNode* group_b, RTreeNode* s, std::vector<bool> &assigned_v );
+ // others...
+
+ // I3
+ bool adjust_tree( RTreeNode* position,
+ std::pair<RTreeNode*, RTreeNode*> &node_division,
+ bool split_performed
+ );
+ std::pair< RTreeNode*, bool > find_parent( RTreeNode* subtree_root, Rect search_area, RTreeNode* wanted ) const;
+
+ void recalculate_bounding_box( RTreeNode* parent, RTreeNode* child, unsigned &child_in_parent );
+
+ void copy_group_a_to_existing_node( RTreeNode *position, RTreeNode* group_a );
+ RTreeRecord_NonLeaf create_nonleaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode *rtreenode );
+ RTreeRecord_Leaf create_leaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode *rtreenode );
+
+ // erase
+// RTreeNode* find_leaf( RTreeNode* subtree, const RTreeRecord_Leaf &search ) const;
+ RTreeNode* find_leaf( RTreeNode* subtree, const Rect &search_area, const int shape_to_delete ) const;
+
+ bool condense_tree( RTreeNode* position
+ // std::pair<RTreeNode*, RTreeNode*> &node_division, // modified: it holds the last split group
+ // bool initial_split_performed,
+ // const unsigned min_nodes
+ // const unsigned max_nodes
+ );
+ int remove_record_from_parent( RTreeNode* parent, RTreeNode* child );
+ void sanity_check(RTreeNode* subtree_root, int depth, bool used_during_insert = false ) const;
+
+};
+
+} // end namespace Geom
+
+#endif // !SEEN_LIB2GEOM_RTREE_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/include/2geom/orphan-code/sbasis-of.h b/include/2geom/orphan-code/sbasis-of.h
new file mode 100644
index 0000000..e5b76d6
--- /dev/null
+++ b/include/2geom/orphan-code/sbasis-of.h
@@ -0,0 +1,638 @@
+/**
+ * \file
+ * \brief Defines S-power basis function class
+ * with coefficient in arbitrary ring
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 SEEN_SBASIS_OF_H
+#define SEEN_SBASIS_OF_H
+#include <vector>
+#include <cassert>
+#include <iostream>
+
+#include <2geom/interval.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+#include <2geom/orphan-code/linear-of.h>
+
+namespace Geom {
+
+template<typename T>
+class SBasisOf;
+
+#ifdef USE_SBASIS_OF
+typedef Geom::SBasisOf<double> SBasis;
+#endif
+
+/*** An empty SBasisOf<T> is identically 0. */
+template<typename T>
+class SBasisOf : public std::vector<LinearOf<T> >{
+public:
+ SBasisOf() {}
+ explicit SBasisOf(T a) {
+ this->push_back(LinearOf<T>(a,a));
+ }
+ SBasisOf(SBasisOf<T> const & a) :
+ std::vector<LinearOf<T> >(a)
+ {}
+ SBasisOf(LinearOf<T> const & bo) {
+ this->push_back(bo);
+ }
+ SBasisOf(LinearOf<T>* bo) {
+ this->push_back(*bo);
+ }
+ //static unsigned input_dim(){return T::input_dim()+1;}
+
+ //IMPL: FragmentConcept
+ typedef T output_type;
+ inline bool isZero() const {
+ if(this->empty()) return true;
+ for(unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+ inline bool isConstant() const {
+ if (this->empty()) return true;
+ for (unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isConstant()) return false;
+ }
+ return true;
+ }
+
+ //TODO: code this...
+ bool isFinite() const;
+
+ inline T at0() const {
+ if(this->empty()) return T(0); else return (*this)[0][0];
+ }
+ inline T at1() const{
+ if(this->empty()) return T(0); else return (*this)[0][1];
+ }
+
+ T valueAt(double t) const {
+ double s = t*(1-t);
+ T p0 = T(0.), p1 = T(0.);
+ for(unsigned k = this->size(); k > 0; k--) {
+ const LinearOf<T> &lin = (*this)[k-1];
+ p0 = p0*s + lin[0];
+ p1 = p1*s + lin[1];
+ }
+ return p0*(1-t) + p1*t;
+ }
+
+ T operator()(double t) const {
+ return valueAt(t);
+ }
+
+ /**
+ * The size of the returned vector equals n+1.
+ */
+ std::vector<T> valueAndDerivatives(double t, unsigned n) const{
+ std::vector<T> ret(n+1);
+ ret[0] = valueAt(t);
+ SBasisOf<T> tmp = *this;
+ for(unsigned i = 0; i < n; i++) {
+ tmp.derive();
+ ret[i+1] = tmp.valueAt(t);
+ }
+ return ret;
+ }
+
+ //The following lines only makes sens if T=double!
+ SBasisOf<T> toSBasis() const { return SBasisOf<T>(*this); }
+ double tailError(unsigned tail) const{
+ Interval bs = *bounds_fast(*this, tail);
+ return std::max(fabs(bs.min()),fabs(bs.max()));
+ }
+
+// compute f(g)
+ SBasisOf<T> operator()(SBasisOf<T> const & g) const;
+
+ LinearOf<T> operator[](unsigned i) const {
+ assert(i < this->size());
+ return std::vector<LinearOf<T> >::operator[](i);
+ }
+
+//MUTATOR PRISON
+ LinearOf<T>& operator[](unsigned i) { return this->at(i); }
+
+ //remove extra zeros
+ void normalize() {
+ while(!this->empty() && this->back().isZero())
+ this->pop_back();
+ }
+
+ void truncate(unsigned k) { if(k < this->size()) this->resize(k); }
+private:
+ void derive(); // in place version
+ unsigned dim;
+};
+
+//template<>
+//inline unsigned SBasisOf<double>::input_dim() { return 1; }
+
+//--------------------------------------------------------------------------
+#ifdef USE_SBASIS_OF
+
+//implemented in sbasis-roots.cpp
+OptInterval bounds_exact(SBasis const &a);
+OptInterval bounds_fast(SBasis const &a, int order = 0);
+OptInterval bounds_local(SBasis const &a, const OptInterval &t, int order = 0);
+
+std::vector<double> roots(SBasis const & s);
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+#endif
+//--------------------------------------------------------------------------
+
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+template<typename T>
+inline SBasisOf<T> LinearOf<T>::toSBasis() const { return SBasisOf<T>(*this); }
+
+template<typename T>
+inline SBasisOf<T> reverse(SBasisOf<T> const &a) {
+ SBasisOf<T> result;
+ result.reserve(a.size());
+ for(unsigned k = 0; k < a.size(); k++)
+ result.push_back(reverse(a[k]));
+ return result;
+}
+
+//IMPL: ScalableConcept
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T>& p) {
+ if(p.isZero()) return SBasisOf<T>();
+ SBasisOf<T> result;
+ result.reserve(p.size());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+
+template<typename T>
+SBasisOf<T> operator*(SBasisOf<T> const &a, double k){
+ SBasisOf<T> c;
+ //TODO: what does this mean for vectors of vectors??
+ //c.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++)
+ c.push_back(a[i] * k);
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> operator*(double k, SBasisOf<T> const &a) { return a*k; }
+template<typename T>
+inline SBasisOf<T> operator/(SBasisOf<T> const &a, double k) { return a*(1./k); }
+template<typename T>
+SBasisOf<T>& operator*=(SBasisOf<T>& a, double b){
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+
+template<typename T>
+inline SBasisOf<T>& operator/=(SBasisOf<T>& a, double b) { return (a*=(1./b)); }
+
+/*
+//We can also multiply by element of ring coeff T:
+template<typename T>
+SBasisOf<T> operator*(SBasisOf<T> const &a, T k){
+ SBasisOf<T> c;
+ //TODO: what does this mean for vectors of vectors??
+ //c.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++)
+ c.push_back(a[i] * k);
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> operator*(T k, SBasisOf<T> const &a) { return a*k; }
+template<typename T>
+SBasisOf<T>& operator*=(SBasisOf<T>& a, T b){
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+*/
+
+//IMPL: AddableConcept
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T>& a, const SBasisOf<T>& b){
+ SBasisOf<T> result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vector<vector>;
+ //result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] + b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+template<typename T>
+SBasisOf<T> operator-(const SBasisOf<T>& a, const SBasisOf<T>& b){
+ SBasisOf<T> result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vector<vector>;
+ //result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] - b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(-b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+template<typename T>
+SBasisOf<T>& operator+=(SBasisOf<T>& a, const SBasisOf<T>& b){
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vectors of vectors
+ //a.reserve(out_size);
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] += b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+template<typename T>
+SBasisOf<T>& operator-=(SBasisOf<T>& a, const SBasisOf<T>& b){
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vectors of vectors
+ //a.reserve(out_size);
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] -= b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(-b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+//TODO: remove?
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, LinearOf<T> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, LinearOf<T> const & b) {
+ if(b.isZero()) return a;
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, const LinearOf<T>& b) {
+ if(a.isZero())
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, const LinearOf<T>& b) {
+ if(a.isZero())
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+//IMPL: OffsetableConcept
+/*
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, double b) {
+ if(a.isZero()) return LinearOf<T>(b, b);
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, double b) {
+ if(a.isZero()) return LinearOf<T>(-b, -b);
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, double b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, double b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+*/
+//We can also offset by elements of coeff ring T
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, T b) {
+ if(a.isZero()) return LinearOf<T>(b, b);
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, T b) {
+ if(a.isZero()) return LinearOf<T>(-b, -b);
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, T b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, T b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+
+template<typename T>
+SBasisOf<T> shift(SBasisOf<T> const &a, int sh){
+ SBasisOf<T> c = a;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, LinearOf<T>(0,0));
+ } else {
+ //TODO: truncate
+ }
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> shift(LinearOf<T> const &a, int sh) {
+ SBasisOf<T> c;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, LinearOf<T>(0,0));
+ c.push_back(a);
+ }
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> truncate(SBasisOf<T> const &a, unsigned terms) {
+ SBasisOf<T> c;
+ c.insert(c.begin(), a.begin(), a.begin() + std::min(terms, (unsigned)a.size()));
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> multiply_add(SBasisOf<T> const &a, SBasisOf<T> const &b, SBasisOf<T> c) {
+ if(a.isZero() || b.isZero())
+ return c;
+ c.resize(a.size() + b.size(), LinearOf<T>(T(0.),T(0.)));
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ T tri = (b[j][1]-b[j][0])*(a[i-j][1]-a[i-j][0]);
+ c[i+1/*shift*/] += LinearOf<T>(-tri);
+ }
+ }
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ for(unsigned dim = 0; dim < 2; dim++)
+ c[i][dim] += b[j][dim]*a[i-j][dim];
+ }
+ }
+ c.normalize();
+ //assert(!(0 == c.back()[0] && 0 == c.back()[1]));
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> multiply(SBasisOf<T> const &a, SBasisOf<T> const &b) {
+ SBasisOf<T> c;
+ if(a.isZero() || b.isZero())
+ return c;
+ return multiply_add(a, b, c);
+}
+
+template<typename T>
+SBasisOf<T> integral(SBasisOf<T> const &c){
+ SBasisOf<T> a;
+ T aTri = T(0.);
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (HatOf<T>(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+
+template<typename T>
+SBasisOf<T> derivative(SBasisOf<T> const &a){
+ SBasisOf<T> c;
+ c.resize(a.size(), LinearOf<T>());
+ if(a.isZero())
+ return c;
+
+ for(unsigned k = 0; k < a.size()-1; k++) {
+ T d = (2*k+1)*(a[k][1] - a[k][0]);
+
+ c[k][0] = d + (k+1)*a[k+1][0];
+ c[k][1] = d - (k+1)*a[k+1][1];
+ }
+ int k = a.size()-1;
+ T d = (2*k+1)*(a[k][1] - a[k][0]);
+ //TODO: do a real test to know if d==0!
+ if(d == T(0.0))
+ c.pop_back();
+ else {
+ c[k][0] = d;
+ c[k][1] = d;
+ }
+
+ return c;
+}
+
+template<typename T>
+void SBasisOf<T>::derive() { // in place version
+ if(isZero()) return;
+ for(unsigned k = 0; k < this->size()-1; k++) {
+ T d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+
+ (*this)[k][0] = d + (k+1)*(*this)[k+1][0];
+ (*this)[k][1] = d - (k+1)*(*this)[k+1][1];
+ }
+ int k = this->size()-1;
+ T d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+ if(d == 0)//TODO: give this a meaning for general coeff ring.
+ this->pop_back();
+ else {
+ (*this)[k][0] = d;
+ (*this)[k][1] = d;
+ }
+}
+
+
+template<typename T>
+inline SBasisOf<T> operator*(SBasisOf<T> const & a, SBasisOf<T> const & b) {
+ return multiply(a, b);
+}
+
+template<typename T>
+inline SBasisOf<T>& operator*=(SBasisOf<T>& a, SBasisOf<T> const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+// a(b(t))
+//TODO: compose all compatibles types!
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, SBasisOf<T> const &b){
+ SBasisOf<double> s = multiply((SBasisOf<T>(LinearOf<T>(1,1))-b), b);
+ SBasisOf<T> r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasisOf<T>(LinearOf<T>(HatOf<T>(a[i][0]))) - b*a[i][0] + b*a[i][1]);
+ }
+ return r;
+}
+
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, SBasisOf<T> const &b, unsigned k){
+ SBasisOf<T> s = multiply((SBasisOf<T>(LinearOf<T>(1,1))-b), b);
+ SBasisOf<T> r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasisOf<T>(LinearOf<T>(HatOf<T>(a[i][0]))) - b*a[i][0] + b*a[i][1]);
+ }
+ r.truncate(k);
+ return r;
+}
+template<typename T>
+SBasisOf<T> compose(LinearOf<T> const &a, SBasisOf<T> const &b){
+ return compose(SBasisOf<T>(a),b);
+}
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, LinearOf<T> const &b){
+ return compose(a,SBasisOf<T>(b));
+}
+template<typename T>//TODO: don't be so lazy!!
+SBasisOf<T> compose(LinearOf<T> const &a, LinearOf<T> const &b){
+ return compose(SBasisOf<T>(a),SBasisOf<T>(b));
+}
+
+
+
+template<typename T>
+inline SBasisOf<T> portion(const SBasisOf<T> &t, double from, double to) { return compose(t, LinearOf<T>(from, to)); }
+
+// compute f(g)
+template<typename T>
+inline SBasisOf<T>
+SBasisOf<T>::operator()(SBasisOf<T> const & g) const {
+ return compose(*this, g);
+}
+
+template<typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearOf<T> &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+template<typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisOf<T> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+};
+#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/include/2geom/orphan-code/sbasisN.h b/include/2geom/orphan-code/sbasisN.h
new file mode 100644
index 0000000..0b5a48f
--- /dev/null
+++ b/include/2geom/orphan-code/sbasisN.h
@@ -0,0 +1,1123 @@
+/**
+ * \file
+ * \brief Multi-dimensional symmetric power basis function.
+ * A SBasisN<n> is a polynomial f of n variables (t0,...,tn-1),
+ * written in a particular form. Let si = ti(1-t_i). f is written as
+ *
+ * f = sum_p s^p a_{p}(t0,...,t_{n-1})
+ *
+ * where p=(p0,...,p_{n-1}) is a multi index (called MultiDegree<n> in the code)
+ * s^p = prod_i si^pi, and a_p is a LinearN<n>.
+ * Recall a LinearN<n> is sum over all choices xi = ti or (1-ti) of terms of form
+ * a * x0*...*x_{n-1}
+ *
+ * Caution: degrees are expressed as degrees of s=t*(1-t). The real degree
+ * (with respect to t) of the polynomial is twice that + 0 or 1 depending
+ * whether the relevant LinearN<n> coeff is constant or not.
+ *//*
+ *
+ * Authors:
+ * JF Barraud <jf.barraud@gmail.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 SEEN_SBASISN_H
+#define SEEN_SBASISN_H
+#include <vector>
+#include <cassert>
+#include <iostream>
+
+#include <2geom/orphan-code/linearN.h>
+#include <2geom/linear.h>//for conversion purpose
+#include <2geom/sbasis.h>//for conversion purpose
+#include <2geom/interval.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+
+namespace Geom{
+
+/** MultiDegree:
+ * \brief multi-degree (p0,...,p^{n-1}) of a s0^p0...s_{n-1}p^{n-1} monomial.
+ *
+ * a "Multi_deg" is a sequence p={p0,...,p_n-1} representing the monomial
+ * s^p = s_0^{p_0}*...*s_{n-1}^{p_{n-1}}.
+ * Caution: the degrees are expressed with respect to si! ( in SBasis code
+ * below, si = ti*(1-ti) ).
+ */
+
+template<unsigned n>
+class MultiDegree{
+public:
+ unsigned p[n];
+ MultiDegree(){
+ for (unsigned i = 0; i <n; i++) {
+ p[i]=0;
+ }
+ }
+ MultiDegree( unsigned const other_p[] ){
+ p = other_p;
+ }
+ MultiDegree(unsigned const idx, unsigned const sizes[]){
+ unsigned q = idx;
+ for (unsigned i = n-1; i >0; i--) {
+ div_t d = std::div(int(q), int(sizes[i]));
+ p[i] = d.rem;
+ q = d.quot;
+ }
+ p[0] = q;
+ }
+ unsigned operator[](const unsigned i) const {
+ assert(i < n); return p[i];
+ }
+ unsigned& operator[](const unsigned i) {
+ assert(i < n); return p[i];
+ }
+
+ unsigned asIdx(unsigned const sizes[]) const{
+ unsigned ret = p[0];
+ bool in_range = (p[0]<sizes[0]);
+ for (unsigned i = 1; i < n; i++) {
+ in_range = in_range && (p[i]<sizes[i]);
+ ret = ret*sizes[i] + p[i];
+ }
+ if (in_range) return ret;
+ //TODO: find a better warning than returning out of range idx!
+ ret =1;
+ for (unsigned i = 0; i < n; i++) {
+ ret *= sizes[i];
+ }
+ return ret;
+ }
+ bool stepUp(unsigned const sizes[], unsigned frozen_mask = 0){
+ unsigned i = 0;
+ while ( i < n && ( (1<<i) & frozen_mask ) ) i++;
+ while ( i <n && p[i] == sizes[i]-1 ) {
+ i++;
+ while (i<n && ( (1<<i) & frozen_mask ) ) i++;
+ }
+ if (i<n){
+ p[i]+=1;
+ for (unsigned j = 0; j < i; j++) {
+ if ( !( (1<<j) & frozen_mask ) ) p[j] = 0;
+ }
+ return true;
+ }else{
+ return false;
+ }
+ }
+ bool stepDown(unsigned const sizes[], unsigned frozen_mask = 0){
+ int i = n-1;
+ while (i>=0 && ( (1<<i) & frozen_mask ) ) i--;
+ while ( i >= 0 && p[i] == 0 ) {
+ i--;
+ while (i>=0 && ( (1<<i) & frozen_mask ) ) i--;
+ }
+ if ( i >= 0 ){
+ p[i]-=1;
+ for (unsigned j = i+1; j < n; j++) {
+ if ( !( (1<<j) & frozen_mask ) ) p[j] = sizes[j]-1;
+ }
+ return true;
+ }else{
+ return false;
+ }
+ }
+};
+
+/**
+ * Returns the maximal degree appearing in the two arguments for each variables.
+ */
+template <unsigned n>
+MultiDegree<n> max(MultiDegree<n> const &p, MultiDegree<n> const &q){
+ MultiDegree<n> ret;
+ for (unsigned i = 0; i <n; i++) {
+ ret.p[i] = (p[i]>q[i] ? p[i] : q[i]);
+ }
+ return ret;
+}
+
+template <unsigned n>
+MultiDegree<n> operator + (MultiDegree<n> const &p, MultiDegree<n> const &q){
+ MultiDegree<n> ret;
+ for (unsigned i = 0; i <n; i++) {
+ ret.p[i] = p[i] + q[i];
+ }
+ return ret;
+}
+template <unsigned n>
+MultiDegree<n> operator += (MultiDegree<n> const &p, MultiDegree<n> const &q){
+ for (unsigned i = 0; i <n; i++) {
+ p[i] += q[i];
+ }
+ return p;
+}
+
+/**
+ * \brief MultiDegree comparison.
+ * A MultiDegree \param p is smaller than another \param q
+ * if all it's smaller for all variables.
+ *
+ * In particular, p<=q and q<=p can both be false!
+ */
+template<unsigned n>
+bool operator<=(MultiDegree<n> const &p, MultiDegree<n> const &q){
+ for (unsigned i = 0; i <n; i++) {
+ if (p[i]>q[i]) return false;
+ }
+ return true;
+}
+
+
+/**
+ * \brief Polynomials of n variables, written in SBasis form.
+ * An SBasisN<n> f is internaly represented as a vector of LinearN<n>.
+ * It should be thought of as an n-dimensional vector: the coef of s0^p0...s_{n-1}p^{n-1}
+ * is soterd in f[p0,...,p_{n-1}]. The sizes of each dimension is stored in "sizes".
+ * Note: an empty SBasis is identically 0.
+ */
+template<unsigned n>
+class SBasisN : public std::vector<LinearN<n> >{
+public:
+ unsigned sizes[n];
+ SBasisN() {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 0;
+ }
+ }
+ explicit SBasisN(double a) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(LinearN<n>(a));
+ }
+ SBasisN(SBasisN<n> const & a) : std::vector<LinearN<n> >(a){
+ //TODO: efficient array copy??
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = a.sizes[i];
+ }
+ }
+ SBasisN(LinearN<n> const & bo) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(bo);
+ }
+ SBasisN(LinearN<n>* bo) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(*bo);
+ }
+
+//----------------------------------------------
+//-- Degree/Sizing facilities ------------------
+//----------------------------------------------
+/**
+ * Internal recursive function used to compute partial degrees.
+ */
+ bool find_non_empty_level(unsigned var, MultiDegree<n> &fixed_degrees)const{
+ if (this->size()==0){
+ for (unsigned i = 0; i < n; i++) {
+ fixed_degrees[i] = 0;//FIXME this should be -infty!!
+ }
+ return false;
+ }
+ if ( !((*this)[fixed_degrees.asIdx(sizes)].isZero()) ) return true;
+
+ unsigned frozen = (1<<var);
+ if ( fixed_degrees.stepDown(sizes, frozen) ){
+ if ( find_non_empty_level(var, fixed_degrees) ) return true;
+ }
+ if ( fixed_degrees[var] > 0 ){
+ fixed_degrees[var] -= 1;
+ for (unsigned i = 0; i < n; i++) {
+ if (i!=var) fixed_degrees[i] = sizes[i]-1;
+ }
+ if (find_non_empty_level(var, fixed_degrees)) return true;
+ }
+ return false;//FIXME: this should return -infty in all variables!
+ }
+
+/**
+ * Returns the degree of an SBasisN<n> with respect to a given variable form its sizes.
+ * All terms are taken into account, even eventual trailing zeros.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned quick_degree(unsigned var) const{
+ return ( sizes[var] > 0 ? sizes[var]-1 : 0 );//this should be -infty.
+ }
+/**
+ * Computes the multi degree of the SBasis from it's sizes.
+ * All terms are taken into account, even eventual trailing zeros.
+ * Note: degrees are expressed with respect to s = t*(1-t), not t itself.
+ */
+ MultiDegree<n> quick_multi_degree() const{
+ MultiDegree<n> ret;
+ if (this->size()==0) return ret;//should be -infty for all vars.
+ for (unsigned i = 0; i < n; i++) {
+ assert( sizes[i]>0 );
+ ret.p[i] = sizes[i]-1;
+ }
+ return ret;
+ }
+/**
+ * Returns the degree of an SBasisN<n> with respect to a given variable.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned degree(unsigned var)const{
+ MultiDegree<n> degrees;
+ for(unsigned i = 0; i < n; i++) {
+ degrees[i] = sizes[i]-1;
+ }
+ if ( find_non_empty_level(var, degrees) ) return degrees[var];
+ else return 0;//should be -infty.
+ }
+/**
+ * Returns the *real* degree of an SBasisN<n> with respect to a given variable.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to t itself, not s = t*(1-t).
+ * In particular: real_t_degree() = 2*degree() + 0 or 1.
+ */
+ unsigned real_t_degree(unsigned var)const{
+ unsigned deg = 0;
+ bool even = true;
+ bool notfound = true;
+ unsigned frozen = (1<<var);
+ MultiDegree<n> degrees;
+ for(unsigned i = 0; i < n; i++) {
+ degrees[i] = sizes[i]-1;
+ }
+ while( notfound ){
+ if ( find_non_empty_level(var, degrees) && degrees[var]>= deg ){
+ deg = degrees[var];
+ even = (*this)[degrees.asIdx(sizes)].isConstant(var);
+ }
+ notfound = even && degrees.stepDown(sizes, frozen);
+ }
+ return 2*deg + ( even ? 0 : 1 );
+ }
+/**
+ * Returns the *real* degrees of an SBasisN<n>.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to t itself, not s = t*(1-t).
+ * In particular: real_t_degree() = 2*degree() + 0 or 1.
+ */
+ MultiDegree<n> real_t_degrees()const{
+ MultiDegree<n>res;
+ for(unsigned i = 0; i < n; i++) {
+ res[i] = real_t_degree(i);
+ }
+ return res;
+ }
+/**
+ * Computes the multi degree of the SBasis.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ MultiDegree<n> multi_degree() const{
+ MultiDegree<n> ret;
+ if (this->size()==0) return ret;//should be -infty for all vars.
+ for (unsigned i = 0; i < n; i++) {
+ ret[i] = this->degree(i);
+ }
+ return ret;
+ }
+/**
+ * Returns the highest degree over all variables.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned max_degree() const {
+ if (this->size()==0) return 0;//should be -infty!
+ unsigned d=0;
+ for (unsigned i = 0; i < n; i++) {
+ assert( sizes[i]>0 );
+ if (d < sizes[i]-1) d = sizes[i]-1;
+ }
+ return d;
+ }
+
+/**
+ * Resize an SBasisN<n> to match new sizes.
+ *
+ * Caution: if a new size is smaller, the corresponding coefficients are discarded.
+ */
+ void multi_resize(unsigned new_sizes[], LinearN<n> def_value = LinearN<n>(0.)){
+ SBasisN<n> result;
+ bool nothing_todo = true;
+ unsigned tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ nothing_todo = nothing_todo && (sizes[i] == new_sizes[i]);
+ result.sizes[i] = new_sizes[i];
+ tot_size *= new_sizes[i];
+ }
+ if (nothing_todo) return;
+ result.resize(tot_size, def_value);
+ for(unsigned i = 0; i < tot_size; i++) {
+ MultiDegree<n> d( i, result.sizes );
+ unsigned j = d.asIdx(sizes);
+ if ( j < this->size() ){
+ result[i] = (*this)[j];
+ }
+ }
+ *this = result;
+ }
+
+ //remove extra zeros
+ void normalize() {
+ MultiDegree<n> max_p = multi_degree();
+ unsigned new_sizes[n];
+ for (unsigned i=0; i<n; i++){
+ new_sizes[i] = max_p[i]+1;
+ }
+ multi_resize(new_sizes);
+ }
+
+//-----------------------------
+//-- Misc. --------------------
+//-----------------------------
+
+/**
+ * Returns the number of variables this function takes as input: n.
+ */
+ unsigned input_dim(){return n;};
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+
+ inline bool isZero() const {
+ if(this->size()==0) return true;
+ for(unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+ inline bool isConstant() const {
+ if (this->size()==0) return true;
+ if(!(*this)[0].isConstant()) return false;
+ for (unsigned i = 1; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+
+ bool isFinite() const{
+ for (unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isFinite()) return false;
+ }
+ return true;
+ }
+
+
+//------------------------------------------
+//-- Evaluation methods --------------------
+//------------------------------------------
+/**
+ * Returns the value of the SBasis at a given corner of [0,1]^n.
+ * \param k describes the corner: if i-th bit is 0, ti=0, otherwise ti=1.
+ */
+ inline double atCorner(unsigned k) const {
+ if(this->size()==0) return 0.;
+ return (*this)[0].atCorner(k);
+ }
+/**
+ * Returns the value of the SBasis at a given corner of [0,1]^n.
+ * \param t[n] describes the corner: the values should be 0's and 1's.
+ */
+ inline double atCorner(double t[]) const {
+ if(this->size()==0) return 0.;
+ return (*this)[0].atCorner(t);
+ }
+/**
+ * Returns a "slice" of the array.
+ * Returns an SBasis containing all the coeff of (s-)degree \param deg in variable \param var
+ */
+ //TODO: move by bigger blocks (?) but they are broken into pieces in general...
+ SBasisN<n> slice(unsigned const var, unsigned const deg) const{
+ if (deg >= sizes[var] ) return SBasisN<n>();
+ SBasisN<n> res;
+ unsigned tot_size = 1;
+ for (unsigned i = 0; i < n; i++) {
+ res.sizes[i] = (i==var ? 1 : sizes[i]);
+ tot_size *= res.sizes[i];
+ }
+ res.resize( tot_size, LinearN<n>(0.));
+ for (unsigned i = 0; i < tot_size; i++) {
+ MultiDegree<n> d(i,res.sizes);
+ d.p[var] = deg;
+ res[i] = (*this)[d.asIdx(sizes)];
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to 0.
+ */
+ inline SBasisN<n-1> at0(unsigned var=0, unsigned deg=0) const {
+ SBasisN<n> sl = slice(var,deg);
+ SBasisN<n-1> res;
+ res.reserve(sl.size());
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res.push_back( sl[i].at0(var) );
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to 1.
+ */
+ inline SBasisN<n-1> at1(unsigned var=0, unsigned deg=0) const {
+ SBasisN<n> sl = slice(var,deg);
+ SBasisN<n-1> res;
+ res.reserve(sl.size());
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res.push_back( sl[i].at1(var) );
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to \param t.
+ */
+ inline SBasisN<n-1> partialEval(double t, unsigned var=0 ) const {
+ SBasisN<n> sl;
+ double s = t*(1-t);
+ double si = 1;
+ for (unsigned i = 0; i <sizes[var]; i++) {
+ sl = sl + slice(var, i)*si;
+ si *= s;
+ }
+ SBasisN<n-1> res;
+ res.resize(sl.size(), LinearN<n-1>(0.));
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res[i] = sl[i].partialEval(t,var);
+ }
+ return res;
+ }
+
+/**
+ * \brief Internal recursive function.
+ * Replace each variable by it's value in the 's=t*(1-t)' factor
+ * but not in the LinearN<n> coeffs. Then sum up all coefficients.
+ * \param t[n]: values of the variables.
+ */
+ LinearN<n> sumCoefs( double t[], unsigned const k, unsigned const idx) const{
+ LinearN<n> a;
+ if (k == n){
+ a = (*this)[idx];
+ return (*this)[idx];
+ }
+ double s = t[k]*(1-t[k]);
+ double si=1;
+ for (unsigned i=0; i<sizes[k]; i++){
+ a += sumCoefs(t,k+1,idx*sizes[k]+i)*si;;
+ si *= s;
+ }
+ return a;
+ }
+/**
+ * Evaluate at given n-dimensional point.
+ * \param t[n]: values of the variables.
+ */
+ double valueAt(double t[]) const {
+ LinearN<n> a = sumCoefs(t,0,0);
+ return a.valueAt(t);
+ }
+
+ double operator()(double t[]) const {
+ return valueAt(t);
+ }
+
+ //double valueAndDerivative(double t, double &der) const;
+ //std::vector<double> valueAndDerivatives(double t, unsigned n) const;
+ //SBasisN toSBasisN() const { return SBasisN(*this); }
+ //double tailError(unsigned tail) const;
+
+
+//--------------------------------------------------
+//-- Coeff. manipulation ---------------------------
+//--------------------------------------------------
+
+/**
+ * Accessing the SBasisN<n> coefficients.
+ */
+ LinearN<n> operator[](unsigned i) const {
+ assert(i < this->size());
+ return std::vector<LinearN<n> >::operator[](i);
+ }
+ LinearN<n> operator[](MultiDegree<n> const &p) const {
+ unsigned i = p.asIdx(sizes);
+ assert(i < this->size());
+ return std::vector<LinearN<n> >::operator[](i);
+ }
+
+//MUTATOR PRISON
+ LinearN<n>& operator[](unsigned i) { return this->at(i); }
+// LinearN<n>& operator[](MultiDegree const &p) {
+// unsigned i = p.asIdx(sizes);
+// return this->at(i);
+// }
+
+ void appendCoef(const SBasisN<n-1> &a, const SBasisN<n-1> &b, unsigned var=0){
+ unsigned new_sizes[n];
+ MultiDegree<n-1> deg_a = a.multi_degree(), deg_b = b.multi_degree();
+ MultiDegree<n-1> dcoef = max( deg_a, deg_b );
+ for (unsigned i=0; i<n; i++){
+ if ( i == var ){
+ new_sizes[var] = sizes[var] + 1;
+ }else{
+ unsigned coef_size = dcoef[(i<var?i:i-1)] + 1;
+ new_sizes[i] = ( sizes[i]>coef_size ? sizes[i] : coef_size );
+ }
+ }
+ multi_resize(new_sizes);
+
+ MultiDegree<n> d;
+ d[var] = sizes[var]-1;
+ unsigned frozen_mask = (1<<var);
+ do{
+ for (unsigned i=0; i<n-1; i++){
+ dcoef.p[i] = d.p[ ( i<var ? i : i+1) ];
+ }
+ LinearN<n-1> a_d,b_d;
+ unsigned ia = dcoef.asIdx(a.sizes);
+ if ( ia < a.size() ) a_d = a[ia];
+ unsigned ib = dcoef.asIdx(b.sizes);
+ if ( ib < b.size() ) b_d = b[ib];
+ (*this)[d.asIdx(sizes)] = LinearN<n>(a_d,b_d);
+ }while (d.stepUp(sizes,frozen_mask));
+ }
+
+//private:
+ //void derive(); // in place version
+};
+
+//SBasisN<0> is a double. Specialize it out.
+template<>
+class SBasisN<0>{
+public:
+ double d;
+ SBasisN () {}
+ SBasisN(double d) :d(d) {}
+ operator double() const { return d; }
+};
+
+
+//SBasisN<1> are usual SBasis. Allow conversion.
+SBasis toSBasis(SBasisN<1> f){
+ SBasis res(f.size(), Linear());
+ for (unsigned i = 0; i < f.size(); i++) {
+ res[i] = toLinear(f[i]);
+ }
+ return res;
+}
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+template<unsigned n>
+inline SBasisN<n> LinearN<n>::toSBasisN() const { return SBasisN<n>(*this); }
+
+
+
+
+//implemented in sbasis-roots.cpp
+//OptInterval bounds_exact(SBasisN const &a);
+//OptInterval bounds_fast(SBasisN const &a, int order = 0);
+//OptInterval bounds_local(SBasisN const &a, const OptInterval &t, int order = 0);
+
+/** Returns a function which reverses the domain of a.
+ \param a sbasis function
+
+useful for reversing a parameteric curve.
+*/
+//template<unsigned n>
+//inline SBasisN<n> reverse(SBasisN<n> const &a);
+
+//IMPL: ScalableConcept
+template<unsigned n>
+inline SBasisN<n> operator-(const SBasisN<n>& p) {
+ if(p.isZero()) return SBasisN<n>();
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = p.sizes[i];
+ }
+ result.reserve(p.size());
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> operator*(SBasisN<n> const &a, double c){
+ if(a.isZero()) return SBasisN<n>();
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = a.sizes[i];
+ }
+ result.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++) {
+ result.push_back(a[i] * c);
+ }
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n> operator*(double k, SBasisN<n> const &a) { return a*k; }
+template<unsigned n>
+inline SBasisN<n> operator/(SBasisN<n> const &a, double k) { return a*(1./k); }
+template<unsigned n>
+SBasisN<n>& operator*=(SBasisN<n>& a, double c){
+ for(unsigned i = 0; i < a.size(); i++) a[i] *= c;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator/=(SBasisN<n>& a, double b) { return (a*=(1./b)); }
+
+//IMPL: AddableConcept
+template<unsigned n>
+SBasisN<n> operator + (const SBasisN<n>& a, const SBasisN<n>& b){
+ if( a.isZero() ) return b;
+ if( b.isZero() ) return a;
+ SBasisN<n> result;
+ MultiDegree<n> deg = max(a.quick_multi_degree(),b.quick_multi_degree());
+ unsigned max_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = deg[i]+1;
+ max_size *= result.sizes[i];
+ }
+ result.resize( max_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < result.size(); i++) {
+ MultiDegree<n> p(i,result.sizes);
+ unsigned ia = p.asIdx(a.sizes);
+ unsigned ib = p.asIdx(b.sizes);
+ if (ia<a.size()) {
+ result[i] += a[ia];
+ }
+ if (ib<b.size()) {
+ result[i] += b[ib];
+ }
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> operator-(const SBasisN<n>& a, const SBasisN<n>& b){return a+(-b);}
+template<unsigned n>
+SBasisN<n>& operator+=(SBasisN<n>& a, const SBasisN<n>& b){
+ if(b.isZero()) return a;
+ a = a + b;
+ return a;
+}
+template<unsigned n>
+SBasisN<n>& operator-=(SBasisN<n>& a, const SBasisN<n>& b){
+ a += -b;
+ return a;
+}
+
+//TODO: remove?
+template<unsigned n>
+inline SBasisN<n> operator+(const SBasisN<n> & a, LinearN<n> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasisN<n> result(a);
+ result[0] += b;
+ return result;
+}
+template<unsigned n>
+
+inline SBasisN<n> operator-(const SBasisN<n> & a, LinearN<n> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return -b;
+ SBasisN<n> result(a);
+ result[0] -= b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n>& operator+=(SBasisN<n>& a, const LinearN<n>& b) {
+ if(a.size()==0)
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator-=(SBasisN<n>& a, const LinearN<n>& b) {
+ if(a.size()==0)
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+//IMPL: OffsetableConcept
+template<unsigned n>
+inline SBasisN<n> operator+(const SBasisN<n> & a, double b) {
+ if(a.isZero()) return LinearN<n>(b);
+ SBasisN<n> result(a);
+ result[0] += b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n> operator-(const SBasisN<n> & a, double b) {
+ if(a.isZero()) return LinearN<n>(-b);
+ SBasisN<n> result(a);
+ result[0] -= b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n>& operator+=(SBasisN<n>& a, double b) {
+ if(a.size()==0)
+ a.push_back(LinearN<n>(b));
+ else
+ a[0] += b;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator-=(SBasisN<n>& a, double b) {
+ if(a.size()==0)
+ a.push_back(LinearN<n>(-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+template<unsigned n>
+SBasisN<n> shift(SBasisN<n> const &a, MultiDegree<n> sh){
+ SBasisN<n> result;
+ MultiDegree<n> deg = a.quick_multi_degree() + sh;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = deg[i]+1;
+ }
+ unsigned max_size = deg.asIdx(result.sizes);
+ result.resize( max_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < a.size(); i++) {
+ MultiDegree<n> p(i,a.sizes);
+ p+=sh;
+ result[p.asIdx(result.sizes)]=a[i];
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> shift(LinearN<n> const &a, MultiDegree<n> sh){
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = sh[i]+1;
+ }
+ unsigned max_size = sh.asIdx(result.sizes);
+ result.resize( max_size, LinearN<n>(0.) );
+ result[max_size-1]=a;
+ return result;
+}
+//shift only in one variable
+template<unsigned n>
+SBasisN<n> shift(LinearN<n> const &a, unsigned sh, unsigned var){
+ assert( var < n );
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = 1;
+ }
+ result.sizes[var] = sh+1;
+ result.resize( sh+1, LinearN<n>(0.) );
+ result[sh]=a;
+ return result;
+}
+
+//truncate only in first variable
+template<unsigned n>
+inline SBasisN<n> truncate(SBasisN<n> const &a, unsigned first_size) {
+ if ( first_size <= a.sizes[0] ) return a;
+ SBasisN<n> c;
+ for (unsigned i = 0; i < n; i++) {
+ c.sizes[i] = a.sizes[i];
+ }
+ c.sizes[0] = first_size;
+ unsigned tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ tot_size*=c.sizes[i];
+ }
+ c.insert(c.begin(), a.begin(), a.begin() + tot_size);
+ return c;
+}
+
+template<unsigned n>
+SBasisN<n> multiply(SBasisN<n> const &a, SBasisN<n> const &b){
+ SBasisN<n> c;
+ MultiDegree<n> d;
+ MultiDegree<n> t_deg = a.real_t_degrees() + b.real_t_degrees();
+ for(unsigned i = 0; i < n; i++) {
+ d[i] = ( t_deg[i]%2 == 0 ? t_deg[i]/2 : (t_deg[i]-1)/2 ) ;
+ }
+ unsigned new_sizes[n], tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ //c.sizes[i] = d[i] + 1+1;//product of linears might give 1 more s in each dir!!
+ new_sizes[i] = d[i] + 1;
+ tot_size*=new_sizes[i];
+ }
+ c.resize( tot_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < n; i++) {
+ c.sizes[i] = new_sizes[i];
+ }
+
+ for(unsigned ai = 0; ai < a.size(); ai++) {
+ for(unsigned bj = 0; bj < b.size(); bj++) {
+ MultiDegree<n> di( ai, a.sizes );
+ MultiDegree<n> dj( bj, b.sizes );
+ //compute a[ai]*b[bj]:
+ for(unsigned p = 0; p < (1<<n); p++) {
+ for(unsigned q = 0; q < (1<<n); q++) {
+
+ //compute a[ai][p]*b[bj][q]:
+ unsigned m = p^q;//m has ones for factors s, 0 for (t-s) or ((1-t)-s).
+ for(unsigned r = 0; r < (1<<n); r++) {
+ if (!(r&m)) {// a 1 in r means take t (or (1-t)), otherwise take -s.
+ int sign = 1;
+ MultiDegree<n> dr;
+ unsigned t0 = 0, t1 = 0;
+ for (unsigned var = 0; var < n; var++) {
+ //if var is in mask m, no choice, take s
+ if ( m & (1<<var) ){
+ dr.p[var] = 1;
+ }//if var is in mask r, take t or (1-t)
+ else if ( r & (1<<var) ){
+ dr.p[var] = 0;
+ if ( p&(1<<var) ) {
+ t0 = t0 | (1<<var);
+ }else{
+ t1 = t1 | (1<<var);
+ }
+ }//ohterwise take -s
+ else{
+ dr.p[var] = 1;
+ sign *= -1;
+ }
+ }
+ unsigned idx = (di+dj+dr).asIdx(c.sizes);
+ if (idx < c.size()){
+ for(unsigned s = 0; s < (1<<n); s++) {
+ if ( (t0 & ~s) || (t1 & s) ){
+ c[idx][s] += 0;
+ }else{
+ c[idx][s] += sign * a[ai][p] * b[bj][q];
+ }
+ }
+ }
+ }
+ }//r loop: all choices have been expanded in the product a[ai][p]*b[bj][q]
+ }//q loop
+ }//p loop: all products a[ai][p]*b[bj][q] have been computed.
+ }//bj loop
+ }//ai loop: all a[ai]b[bj] have been computed.
+
+ //TODO: normalize c, or even better, compute with the right size from scratch
+ return c;
+}
+
+
+template<unsigned n>
+inline SBasisN<n> operator*(SBasisN<n> const & a, SBasisN<n> const & b) {
+ return multiply(a, b);
+}
+
+template<unsigned n>
+inline SBasisN<n>& operator*=(SBasisN<n>& a, SBasisN<n> const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+template<unsigned m,unsigned n>
+SBasisN<m> compose(LinearN<n> const &f, std::vector<SBasisN<m> > const &t, unsigned fixed=0, unsigned flags=0 ){
+ assert (t.size() == n );
+ if (fixed == n) {
+ return SBasisN<m>(1.) * f[flags];
+ }else{
+ SBasisN<m> a0 = compose(f, t, fixed+1, flags);
+ SBasisN<m> a1 = compose(f, t, fixed+1, flags|(1<<fixed));
+ return (-t[fixed]+1) * a0 + t[fixed] * a1;
+ }
+}
+
+template<unsigned m,unsigned n>
+SBasisN<m> compose(SBasisN<n> const &f, std::vector<SBasisN<m> > const &t, unsigned const k=0, unsigned const idx = 0){
+ assert (t.size() == n );
+ if (k == n){
+ return compose( f[idx], t);
+ }
+ SBasisN<m> a;
+ SBasisN<m> s = multiply( t[k], (-t[k]+1.) );
+ SBasisN<m> si= SBasisN<m>(1.);
+ for (unsigned i=0; i<f.sizes[k]; i++){
+ a += compose(f, t,k+1,idx*f.sizes[k]+i)*si;;
+ si *= s;
+ }
+ return a;
+}
+
+template <unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const MultiDegree<n> & d) {
+ out_file << "s^{";
+ for(unsigned i = 0; i < n; i++) {
+ out_file << d[i] << (i == n-1 ? "}" : ",");
+ }
+ return out_file;
+}
+template <unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisN<n> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ MultiDegree<n> d(i, p.sizes);
+ out_file << d << " " << p[i] << " + ";
+ }
+ return out_file;
+}
+
+
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+
+#if 0
+
+
+// This performs a multiply and accumulate operation in about the same time as multiply. return a*b + c
+template<unsigned n>
+SBasisN<n> multiply_add(SBasisN<n> const &a, SBasisN<n> const &b, SBasisN<n> c);
+
+template<unsigned n>
+SBasisN<n> integral(SBasisN<n> const &c);
+template<unsigned n>
+SBasisN<n> derivative(SBasisN<n> const &a);
+
+template<unsigned n>
+SBasisN<n> sqrt(SBasisN<n> const &a, int k);
+
+// return a kth order approx to 1/a)
+template<unsigned n>
+SBasisN<n> reciprocal(LinearN<n> const &a, int k);
+template<unsigned n>
+SBasisN<n> divide(SBasisN<n> const &a, SBasisN<n> const &b, int k);
+
+
+/** Returns the degree of the first non zero coefficient.
+ \param a sbasis function
+ \param tol largest abs val considered 0
+ \returns first non zero coefficient
+*/
+template<unsigned n>
+inline unsigned
+valuation(SBasisN<n> const &a, double tol=0){
+ unsigned val=0;
+ while( val<a.size() &&
+ fabs(a[val][0])<tol &&
+ fabs(a[val][1])<tol )
+ val++;
+ return val;
+}
+
+// a(b(t))
+template<unsigned n>
+SBasisN<n> compose(SBasisN<n> const &a, SBasisN<n> const &b);
+template<unsigned n>
+SBasisN<n> compose(SBasisN<n> const &a, SBasisN<n> const &b, unsigned k);
+template<unsigned n>
+SBasisN<n> inverse(SBasisN<n> a, int k);
+//compose_inverse(f,g)=compose(f,inverse(g)), but is numerically more stable in some good cases...
+//TODO: requires g(0)=0 & g(1)=1 atm. generalization should be obvious.
+template<unsigned n>
+SBasisN<n> compose_inverse(SBasisN<n> const &f, SBasisN<n> const &g, unsigned order=2, double tol=1e-3);
+
+/** Returns the sbasis on domain [0,1] that was t on [from, to]
+ \param a sbasis function
+ \param from,to interval
+ \returns sbasis
+
+*/
+template<unsigned n>
+inline SBasisN<n> portion(const SBasisN<n> &t, double from, double to) { return compose(t, LinearN<n>(from, to)); }
+
+// compute f(g)
+template<unsigned n>
+inline SBasisN<n>
+SBasisN<n>::operator()(SBasisN<n> const & g) const {
+ return compose(*this, g);
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearN<n> &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisN<n> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+// These are deprecated, use sbasis-math.h versions if possible
+template<unsigned n>
+SBasisN<n> sin(LinearN<n> bo, int k);
+template<unsigned n>
+SBasisN<n> cos(LinearN<n> bo, int k);
+
+template<unsigned n>
+std::vector<double> roots(SBasisN<n> const & s);
+template<unsigned n>
+std::vector<std::vector<double> > multi_roots(SBasisN<n> const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+
+#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 :
+#endif
diff --git a/include/2geom/parallelogram.h b/include/2geom/parallelogram.h
new file mode 100644
index 0000000..0c7134f
--- /dev/null
+++ b/include/2geom/parallelogram.h
@@ -0,0 +1,83 @@
+/*
+ * Authors:
+ * Thomas Holder
+ * Sergei Izmailov
+ *
+ * Copyright 2020 Authors
+ *
+ * SPDX-License-Identifier: LGPL-2.1 or MPL-1.1
+ */
+
+#ifndef LIB2GEOM_SEEN_PARALLELOGRAM_H
+#define LIB2GEOM_SEEN_PARALLELOGRAM_H
+
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+
+namespace Geom {
+
+/**
+ * Paralellogram, representing a linear transformation of a rectangle.
+ *
+ * Implements efficient "contains" and "intersects" operations.
+ */
+class Parallelogram {
+ Affine m_affine;
+
+ /// Transformed unit rectangle
+ Parallelogram(Affine const &affine)
+ : m_affine(affine)
+ {
+ }
+
+ public:
+ explicit Parallelogram(Rect const &rect)
+ : m_affine(rect.width(), 0, 0, rect.height(), rect.left(), rect.top())
+ {
+ }
+
+ Point corner(unsigned i) const;
+
+ Point midpoint() const { return Point(0.5, 0.5) * m_affine; }
+
+ /// Area (non-negative)
+ Coord area() const { return m_affine.descrim2(); }
+
+ /// Axis-aligned bounding box
+ Rect bounds() const;
+
+ bool intersects(Parallelogram const &) const;
+ bool intersects(Rect const &rect) const { return intersects(Parallelogram(rect)); }
+
+ bool contains(Point const &) const;
+ bool contains(Parallelogram const &) const;
+ bool contains(Rect const &rect) const { return contains(Parallelogram(rect)); }
+
+ /// Returns shorter side length
+ Coord minExtent() const;
+
+ /// Returns longer side length
+ Coord maxExtent() const;
+
+ /// Return a new transformed parallelogram
+ Parallelogram operator*(Affine const &affine) const { return m_affine * affine; }
+ Parallelogram &operator*=(Affine const &affine) { m_affine *= affine; return *this; }
+
+ /// True if this parallelogram does not have right angles
+ bool isSheared(Coord eps = EPSILON) const;
+};
+
+} // namespace Geom
+
+#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/include/2geom/path-intersection.h b/include/2geom/path-intersection.h
new file mode 100644
index 0000000..7d7cec9
--- /dev/null
+++ b/include/2geom/path-intersection.h
@@ -0,0 +1,118 @@
+/**
+ * \file
+ * \brief Path intersection
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_PATH_INTERSECTION_H
+#define LIB2GEOM_SEEN_PATH_INTERSECTION_H
+
+#include <2geom/crossing.h>
+#include <2geom/path.h>
+#include <2geom/sweep-bounds.h>
+
+namespace Geom {
+
+int winding(Path const &path, Point const &p);
+bool path_direction(Path const &p);
+
+inline bool contains(Path const & p, Point const &i, bool evenodd = true) {
+ return (evenodd ? winding(p, i) % 2 : winding(p, i)) != 0;
+}
+
+template<typename T>
+Crossings curve_sweep(Path const &a, Path const &b) {
+ T t;
+ Crossings ret;
+ std::vector<Rect> bounds_a = bounds(a), bounds_b = bounds(b);
+ std::vector<std::vector<unsigned> > ixs = sweep_bounds(bounds_a, bounds_b);
+ for(unsigned i = 0; i < a.size(); i++) {
+ for(std::vector<unsigned>::iterator jp = ixs[i].begin(); jp != ixs[i].end(); ++jp) {
+ Crossings cc = t.crossings(a[i], b[*jp]);
+ offset_crossings(cc, i, *jp);
+ ret.insert(ret.end(), cc.begin(), cc.end());
+ }
+ }
+ return ret;
+}
+
+Crossings pair_intersect(Curve const & A, Interval const &Ad,
+ Curve const & B, Interval const &Bd);
+Crossings mono_intersect(Curve const & A, Interval const &Ad,
+ Curve const & B, Interval const &Bd);
+
+struct SimpleCrosser : public Crosser<Path> {
+ Crossings crossings(Curve const &a, Curve const &b);
+ Crossings crossings(Path const &a, Path const &b) override { return curve_sweep<SimpleCrosser>(a, b); }
+ CrossingSet crossings(PathVector const &a, PathVector const &b) override { return Crosser<Path>::crossings(a, b); }
+};
+
+struct MonoCrosser : public Crosser<Path> {
+ Crossings crossings(Path const &a, Path const &b) override { return crossings(PathVector(a), PathVector(b))[0]; }
+ CrossingSet crossings(PathVector const &a, PathVector const &b) override;
+};
+
+typedef SimpleCrosser DefaultCrosser;
+
+std::vector<double> path_mono_splits(Path const &p);
+
+CrossingSet crossings_among(PathVector const & p);
+Crossings self_crossings(Path const & a);
+
+inline Crossings crossings(Curve const & a, Curve const & b) {
+ DefaultCrosser c = DefaultCrosser();
+ return c.crossings(a, b);
+}
+
+inline Crossings crossings(Path const & a, Path const & b) {
+ DefaultCrosser c = DefaultCrosser();
+ return c.crossings(a, b);
+}
+
+inline CrossingSet crossings(PathVector const & a, PathVector const & b) {
+ DefaultCrosser c = DefaultCrosser();
+ return c.crossings(a, b);
+}
+
+}
+
+#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/include/2geom/path-sink.h b/include/2geom/path-sink.h
new file mode 100644
index 0000000..35bd1cd
--- /dev/null
+++ b/include/2geom/path-sink.h
@@ -0,0 +1,253 @@
+/**
+ * \file
+ * \brief callback interface for SVG path data
+ *//*
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * 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 LIB2GEOM_SEEN_PATH_SINK_H
+#define LIB2GEOM_SEEN_PATH_SINK_H
+
+#include <2geom/forward.h>
+#include <2geom/pathvector.h>
+#include <2geom/curves.h>
+#include <iterator>
+
+namespace Geom {
+
+
+/** @brief Callback interface for processing path data.
+ *
+ * PathSink provides an interface that allows one to easily write
+ * code which processes path data, for instance when converting
+ * between path formats used by different graphics libraries.
+ * It is also useful for writing algorithms which must do something
+ * for each curve in the path.
+ *
+ * To store a path in a new format, implement the virtual methods
+ * for segments in a derived class and call feed().
+ *
+ * @ingroup Paths
+ */
+class PathSink {
+public:
+ /** @brief Move to a different point without creating a segment.
+ * Usually starts a new subpath. */
+ virtual void moveTo(Point const &p) = 0;
+ /// Output a line segment.
+ virtual void lineTo(Point const &p) = 0;
+ /// Output a quadratic Bezier segment.
+ virtual void curveTo(Point const &c0, Point const &c1, Point const &p) = 0;
+ /// Output a cubic Bezier segment.
+ virtual void quadTo(Point const &c, Point const &p) = 0;
+ /** @brief Output an elliptical arc segment.
+ * See the EllipticalArc class for the documentation of parameters. */
+ virtual void arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p) = 0;
+
+ /// Close the current path with a line segment.
+ virtual void closePath() = 0;
+ /** @brief Flush any internal state of the generator.
+ * This call should implicitly finish the current subpath.
+ * Calling this method should be idempotent, because the default
+ * implementations of path() and pathvector() will call it
+ * multiple times in a row. */
+ virtual void flush() = 0;
+ // Get the current point, e.g. where the initial point of the next segment will be.
+ //virtual Point currentPoint() const = 0;
+
+ /** @brief Undo the last segment.
+ * This method is optional.
+ * @return true true if a segment was erased, false otherwise. */
+ virtual bool backspace() { return false; }
+
+ // these have a default implementation
+ virtual void feed(Curve const &c, bool moveto_initial = true);
+ /** @brief Output a subpath.
+ * Calls the appropriate segment methods according to the contents
+ * of the passed subpath. You can override this function.
+ * NOTE: if you override only some of the feed() functions,
+ * always write this in the derived class:
+ * @code
+ using PathSink::feed;
+ @endcode
+ * Otherwise the remaining methods will be hidden. */
+ virtual void feed(Path const &p);
+ /** @brief Output a path.
+ * Calls feed() on each path in the vector. You can override this function. */
+ virtual void feed(PathVector const &v);
+ /// Output an axis-aligned rectangle, using moveTo, lineTo and closePath.
+ virtual void feed(Rect const &);
+ /// Output a circle as two elliptical arcs.
+ virtual void feed(Circle const &e);
+ /// Output an ellipse as two elliptical arcs.
+ virtual void feed(Ellipse const &e);
+
+ virtual ~PathSink() {}
+};
+
+/** @brief Store paths to an output iterator
+ * @ingroup Paths */
+template <typename OutputIterator>
+class PathIteratorSink : public PathSink {
+public:
+ explicit PathIteratorSink(OutputIterator out)
+ : _in_path(false), _out(out) {}
+
+ void moveTo(Point const &p) override {
+ flush();
+ _path.start(p);
+ _start_p = p;
+ _in_path = true;
+ }
+//TODO: what if _in_path = false?
+
+ /** @brief Detect if the builder is in a path and thus will NOT
+ create a new moveTo command when given the next line
+ @return true if the builder is inside a subpath.
+ */
+ bool inPath() const {
+ return _in_path;
+ }
+
+ void lineTo(Point const &p) override {
+ // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z"
+ if (!_in_path) {
+ moveTo(_start_p);
+ }
+ _path.template appendNew<LineSegment>(p);
+ }
+
+ void quadTo(Point const &c, Point const &p) override {
+ // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z"
+ if (!_in_path) {
+ moveTo(_start_p);
+ }
+ _path.template appendNew<QuadraticBezier>(c, p);
+ }
+
+ void curveTo(Point const &c0, Point const &c1, Point const &p) override {
+ // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z"
+ if (!_in_path) {
+ moveTo(_start_p);
+ }
+ _path.template appendNew<CubicBezier>(c0, c1, p);
+ }
+
+ void arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p) override
+ {
+ // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z"
+ if (!_in_path) {
+ moveTo(_start_p);
+ }
+ _path.template appendNew<EllipticalArc>(rx, ry, angle,
+ large_arc, sweep, p);
+ }
+
+ bool backspace() override
+ {
+ if (_in_path && _path.size() > 0) {
+ _path.erase_last();
+ return true;
+ }
+ return false;
+ }
+
+ void append(Path const &other)
+ {
+ if (!_in_path) {
+ moveTo(other.initialPoint());
+ }
+ _path.append(other);
+ }
+
+ void closePath() override {
+ if (_in_path) {
+ _path.close();
+ flush();
+ }
+ }
+
+ void flush() override {
+ if (_in_path) {
+ _in_path = false;
+ *_out++ = _path;
+ _path.clear();
+ }
+ }
+
+ void setStitching(bool s) {
+ _path.setStitching(s);
+ }
+
+ using PathSink::feed;
+ void feed(Path const &other) override
+ {
+ flush();
+ *_out++ = other;
+ }
+
+protected:
+ bool _in_path;
+ OutputIterator _out;
+ Path _path;
+ Point _start_p;
+};
+
+typedef std::back_insert_iterator<PathVector> SubpathInserter;
+
+/** @brief Store paths to a PathVector
+ * @ingroup Paths */
+class PathBuilder : public PathIteratorSink<SubpathInserter> {
+private:
+ PathVector _pathset;
+public:
+ /// Create a builder that outputs to an internal pathvector.
+ PathBuilder() : PathIteratorSink<SubpathInserter>(SubpathInserter(_pathset)) {}
+ /// Create a builder that outputs to pathvector given by reference.
+ PathBuilder(PathVector &pv) : PathIteratorSink<SubpathInserter>(SubpathInserter(pv)) {}
+
+ /// Retrieve the path
+ PathVector const &peek() const {return _pathset;}
+ /// Clear the stored path vector
+ void clear() { _pathset.clear(); }
+};
+
+}
+
+#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/include/2geom/path.h b/include/2geom/path.h
new file mode 100644
index 0000000..f3042d7
--- /dev/null
+++ b/include/2geom/path.h
@@ -0,0 +1,917 @@
+/** @file
+ * @brief Path - a sequence of contiguous curves
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_PATH_H
+#define LIB2GEOM_SEEN_PATH_H
+
+#include <cstddef>
+#include <iterator>
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <utility>
+#include <vector>
+
+#include <boost/operators.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+#include <2geom/intersection.h>
+#include <2geom/curve.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+class Path;
+class ConvexHull;
+
+namespace PathInternal {
+
+typedef boost::ptr_vector<Curve> Sequence;
+
+struct PathData {
+ Sequence curves;
+ OptRect fast_bounds;
+};
+
+template <typename P>
+class BaseIterator
+ : public boost::random_access_iterator_helper
+ < BaseIterator<P>
+ , Curve const
+ , std::ptrdiff_t
+ , Curve const *
+ , Curve const &
+ >
+{
+ protected:
+ BaseIterator(P &p, unsigned i) : path(&p), index(i) {}
+ // default copy, default assign
+ typedef BaseIterator<P> Self;
+
+ public:
+ BaseIterator() : path(NULL), index(0) {}
+
+ bool operator<(BaseIterator const &other) const {
+ return path == other.path && index < other.index;
+ }
+ bool operator==(BaseIterator const &other) const {
+ return path == other.path && index == other.index;
+ }
+ Curve const &operator*() const {
+ return (*path)[index];
+ }
+
+ Self &operator++() {
+ ++index;
+ return *this;
+ }
+ Self &operator--() {
+ --index;
+ return *this;
+ }
+ Self &operator+=(std::ptrdiff_t d) {
+ index += d;
+ return *this;
+ }
+ Self &operator-=(std::ptrdiff_t d) {
+ index -= d;
+ return *this;
+ }
+ std::ptrdiff_t operator-(Self const &other) const {
+ assert(path == other.path);
+ return (std::ptrdiff_t)index - (std::ptrdiff_t)other.index;
+ }
+
+ private:
+ P *path;
+ unsigned index;
+
+ friend class ::Geom::Path;
+};
+
+}
+
+/** @brief Generalized time value in the path.
+ *
+ * This class exists because when mapping the range of multiple curves onto the same interval
+ * as the curve index, we lose some precision. For instance, a path with 16 curves will
+ * have 4 bits less precision than a path with 1 curve. If you need high precision results
+ * in long paths, either use this class and related methods instead of the standard methods
+ * pointAt(), nearestTime() and so on, or use curveAt() to first obtain the curve, then
+ * call the method again to obtain a high precision result.
+ *
+ * @ingroup Paths */
+struct PathTime
+ : boost::totally_ordered<PathTime>
+{
+ typedef PathInternal::Sequence::size_type size_type;
+
+ Coord t; ///< Time value in the curve
+ size_type curve_index; ///< Index of the curve in the path
+
+ PathTime() : t(0), curve_index(0) {}
+ PathTime(size_type idx, Coord tval) : t(tval), curve_index(idx) {}
+
+ bool operator<(PathTime const &other) const {
+ if (curve_index < other.curve_index) return true;
+ if (curve_index == other.curve_index) {
+ return t < other.t;
+ }
+ return false;
+ }
+ bool operator==(PathTime const &other) const {
+ return curve_index == other.curve_index && t == other.t;
+ }
+ /// Convert times at or beyond 1 to 0 on the next curve.
+ void normalizeForward(size_type path_size) {
+ if (t >= 1) {
+ curve_index = (curve_index + 1) % path_size;
+ t = 0;
+ }
+ }
+ /// Convert times at or before 0 to 1 on the previous curve.
+ void normalizeBackward(size_type path_size) {
+ if (t <= 0) {
+ curve_index = (curve_index - 1) % path_size;
+ t = 1;
+ }
+ }
+
+ Coord asFlatTime() const { return curve_index + t; }
+};
+
+inline std::ostream &operator<<(std::ostream &os, PathTime const &pos) {
+ os << pos.curve_index << ": " << format_coord_nice(pos.t);
+ return os;
+}
+
+
+/** @brief Contiguous subset of the path's parameter domain.
+ * This is a directed interval, which allows one to specify any contiguous subset
+ * of the path's domain, including subsets that wrap around the initial point
+ * of the path.
+ * @ingroup Paths */
+class PathInterval {
+public:
+ typedef PathInternal::Sequence::size_type size_type;
+
+ /** @brief Default interval.
+ * Default-constructed PathInterval includes only the initial point of the initial segment. */
+ PathInterval();
+
+ /** @brief Construct an interval in the path's parameter domain.
+ * @param from Initial time
+ * @param to Final time
+ * @param cross_start If true, the interval will proceed from the initial to final
+ * time through the initial point of the path, wrapping around the closing segment;
+ * otherwise it will not wrap around the closing segment.
+ * @param path_size Size of the path to which this interval applies, required
+ * to clean up degenerate cases */
+ PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size);
+
+ /// Get the time value of the initial point.
+ PathTime const &initialTime() const { return _from; }
+ /// Get the time value of the final point.
+ PathTime const &finalTime() const { return _to; }
+
+ PathTime const &from() const { return _from; }
+ PathTime const &to() const { return _to; }
+
+ /// Check whether the interval has only one point.
+ bool isDegenerate() const { return _from == _to; }
+ /// True if the interval goes in the direction of decreasing time values.
+ bool reverse() const { return _reverse; }
+ /// True if the interior of the interval contains the initial point of the path.
+ bool crossesStart() const { return _cross_start; }
+
+ /// Test a path time for inclusion.
+ bool contains(PathTime const &pos) const;
+
+ /// Get a time at least @a min_dist away in parameter space from the ends.
+ /// If no such time exists, the middle point is returned.
+ PathTime inside(Coord min_dist = EPSILON) const;
+
+ /// Select one of two intervals with given endpoints by parameter direction.
+ static PathInterval from_direction(PathTime const &from, PathTime const &to,
+ bool reversed, size_type path_size);
+
+ /// Select one of two intervals with given endpoints by whether it includes the initial point.
+ static PathInterval from_start_crossing(PathTime const &from, PathTime const &to,
+ bool cross_start, size_type path_size) {
+ PathInterval result(from, to, cross_start, path_size);
+ return result;
+ }
+
+ size_type pathSize() const { return _path_size; }
+ size_type curveCount() const;
+
+private:
+ PathTime _from, _to;
+ size_type _path_size;
+ bool _cross_start, _reverse;
+};
+
+/// Create an interval in the direction of increasing time value.
+/// @relates PathInterval
+inline PathInterval forward_interval(PathTime const &from, PathTime const &to,
+ PathInterval::size_type path_size)
+{
+ PathInterval result = PathInterval::from_direction(from, to, false, path_size);
+ return result;
+}
+
+/// Create an interval in the direction of decreasing time value.
+/// @relates PathInterval
+inline PathInterval backward_interval(PathTime const &from, PathTime const &to,
+ PathInterval::size_type path_size)
+{
+ PathInterval result = PathInterval::from_direction(from, to, true, path_size);
+ return result;
+}
+
+/// Output an interval in the path's domain.
+/// @relates PathInterval
+inline std::ostream &operator<<(std::ostream &os, PathInterval const &ival) {
+ os << "PathInterval[";
+ if (ival.crossesStart()) {
+ os << ival.from() << " -> 0: 0.0 -> " << ival.to();
+ } else {
+ os << ival.from() << " -> " << ival.to();
+ }
+ os << "]";
+ return os;
+}
+
+typedef Intersection<PathTime> PathIntersection;
+
+template <>
+struct ShapeTraits<Path> {
+ typedef PathTime TimeType;
+ typedef PathInterval IntervalType;
+ typedef Path AffineClosureType;
+ typedef PathIntersection IntersectionType;
+};
+
+/** @brief Stores information about the extremum points on a path, with respect
+ * to one of the coordinate axes.
+ * @relates Path::extrema()
+ */
+struct PathExtrema {
+ /** Points with the minimum and maximum value of a coordinate. */
+ Point min_point, max_point;
+
+ /** Directions in which the OTHER coordinates change at the extremum points.
+ *
+ * - equals +1.0 if the other coordinate increases,
+ * - equals 0.0 if the other coordinate is constant (e.g., for an axis-aligned segment),
+ * - equals -1.0 if the other coordinate decreases.
+ */
+ float glance_direction_at_min, glance_direction_at_max;
+
+ /** Path times corresponding to minimum and maximum points. */
+ PathTime min_time, max_time;
+};
+
+/** @brief Sequence of contiguous curves, aka spline.
+ *
+ * Path represents a sequence of contiguous curves, also known as a spline.
+ * It corresponds to a "subpath" in SVG terminology. It can represent both
+ * open and closed subpaths. The final point of each curve is exactly
+ * equal to the initial point of the next curve.
+ *
+ * The path always contains a linear closing segment that connects
+ * the final point of the last "real" curve to the initial point of the
+ * first curve. This way the curves form a closed loop even for open paths.
+ * If the closing segment has nonzero length and the path is closed, it is
+ * considered a normal part of the path data. There are three distinct sets
+ * of end iterators one can use to iterate over the segments:
+ *
+ * - Iterating between @a begin() and @a end() will iterate over segments
+ * which are part of the path.
+ * - Iterating between @a begin() and @a end_closed()
+ * will always iterate over a closed loop of segments.
+ * - Iterating between @a begin() and @a end_open() will always skip
+ * the final linear closing segment.
+ *
+ * If the final point of the last "real" segment coincides exactly with the initial
+ * point of the first segment, the closing segment will be absent from both
+ * [begin(), end_open()) and [begin(), end_closed()).
+ *
+ * Normally, an exception will be thrown when you try to insert a curve
+ * that makes the path non-continuous. If you are working with unsanitized
+ * curve data, you can call setStitching(true), which will insert line segments
+ * to make the path continuous.
+ *
+ * Internally, Path uses copy-on-write data. This is done for two reasons: first,
+ * copying a Curve requires calling a virtual function, so it's a little more expensive
+ * that normal copying; and second, it reduces the memory cost of copying the path.
+ * Therefore you can return Path and PathVector from functions without worrying
+ * about temporary copies.
+ *
+ * Note that this class cannot represent arbitrary shapes, which may contain holes.
+ * To do that, use PathVector, which is more generic.
+ *
+ * It's not very convenient to create a Path directly. To construct paths more easily,
+ * use PathBuilder.
+ *
+ * @ingroup Paths */
+class Path
+ : boost::equality_comparable< Path >
+{
+public:
+ typedef PathInternal::PathData PathData;
+ typedef PathInternal::Sequence Sequence;
+ typedef PathInternal::BaseIterator<Path> iterator;
+ typedef PathInternal::BaseIterator<Path const> const_iterator;
+ typedef Sequence::size_type size_type;
+ typedef Sequence::difference_type difference_type;
+
+ class ClosingSegment : public LineSegment {
+ public:
+ ClosingSegment() : LineSegment() {}
+ ClosingSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {}
+ Curve *duplicate() const override { return new ClosingSegment(*this); }
+ Curve *reverse() const override { return new ClosingSegment((*this)[1], (*this)[0]); }
+ };
+
+ class StitchSegment : public LineSegment {
+ public:
+ StitchSegment() : LineSegment() {}
+ StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {}
+ Curve *duplicate() const override { return new StitchSegment(*this); }
+ Curve *reverse() const override { return new StitchSegment((*this)[1], (*this)[0]); }
+ };
+
+ // Path(Path const &other) - use default copy constructor
+
+ /// Construct an empty path starting at the specified point.
+ explicit Path(Point const &p = Point())
+ : _data(new PathData())
+ , _closing_seg(new ClosingSegment(p, p))
+ , _closed(false)
+ , _exception_on_stitch(true)
+ {
+ _data->curves.push_back(_closing_seg);
+ }
+
+ /// Construct a path containing a range of curves.
+ template <typename Iter>
+ Path(Iter first, Iter last, bool closed = false, bool stitch = false)
+ : _data(new PathData())
+ , _closed(closed)
+ , _exception_on_stitch(!stitch)
+ {
+ for (Iter i = first; i != last; ++i) {
+ _data->curves.push_back(i->duplicate());
+ }
+ if (!_data->curves.empty()) {
+ _closing_seg = new ClosingSegment(_data->curves.back().finalPoint(),
+ _data->curves.front().initialPoint());
+ } else {
+ _closing_seg = new ClosingSegment();
+ }
+ _data->curves.push_back(_closing_seg);
+ }
+
+ /// Create a path from a rectangle.
+ explicit Path(Rect const &r);
+ /// Create a path from a convex hull.
+ explicit Path(ConvexHull const &);
+ /// Create a path from a circle, using two elliptical arcs.
+ explicit Path(Circle const &c);
+ /// Create a path from an ellipse, using two elliptical arcs.
+ explicit Path(Ellipse const &e);
+
+ virtual ~Path() {}
+
+ // Path &operator=(Path const &other) - use default assignment operator
+
+ /** @brief Swap contents with another path
+ * @todo Add noexcept specifiers for C++11 */
+ void swap(Path &other) noexcept {
+ using std::swap;
+ swap(other._data, _data);
+ swap(other._closing_seg, _closing_seg);
+ swap(other._closed, _closed);
+ swap(other._exception_on_stitch, _exception_on_stitch);
+ }
+ /** @brief Swap contents of two paths.
+ * @relates Path */
+ friend inline void swap(Path &a, Path &b) noexcept { a.swap(b); }
+
+ /** @brief Access a curve by index */
+ Curve const &operator[](size_type i) const { return _data->curves[i]; }
+ /** @brief Access a curve by index */
+ Curve const &at(size_type i) const { return _data->curves.at(i); }
+
+ /** @brief Access the first curve in the path.
+ * Since the curve always contains at least a degenerate closing segment,
+ * it is always safe to use this method. */
+ Curve const &front() const { return _data->curves.front(); }
+ /// Alias for front().
+ Curve const &initialCurve() const { return _data->curves.front(); }
+ /** @brief Access the last curve in the path. */
+ Curve const &back() const { return back_default(); }
+ Curve const &back_open() const {
+ if (empty()) return _data->curves.back();
+ return _data->curves[_data->curves.size() - 2];
+ }
+ Curve const &back_closed() const {
+ return _closing_seg->isDegenerate()
+ ? _data->curves[_data->curves.size() - 2]
+ : _data->curves[_data->curves.size() - 1];
+ }
+ Curve const &back_default() const {
+ return _includesClosingSegment()
+ ? back_closed()
+ : back_open();
+ }
+ Curve const &finalCurve() const { return back_default(); }
+
+ const_iterator begin() const { return const_iterator(*this, 0); }
+ const_iterator end() const { return end_default(); }
+ const_iterator end_default() const { return const_iterator(*this, size_default()); }
+ const_iterator end_open() const { return const_iterator(*this, size_open()); }
+ const_iterator end_closed() const { return const_iterator(*this, size_closed()); }
+ iterator begin() { return iterator(*this, 0); }
+ iterator end() { return end_default(); }
+ iterator end_default() { return iterator(*this, size_default()); }
+ iterator end_open() { return iterator(*this, size_open()); }
+ iterator end_closed() { return iterator(*this, size_closed()); }
+
+ /// Size without the closing segment, even if the path is closed.
+ size_type size_open() const { return _data->curves.size() - 1; }
+
+ /** @brief Size with the closing segment, if it makes a difference.
+ * If the closing segment is degenerate, i.e. its initial and final points
+ * are exactly equal, then it is not included in this size. */
+ size_type size_closed() const {
+ return _closing_seg->isDegenerate() ? _data->curves.size() - 1 : _data->curves.size();
+ }
+
+ /// Natural size of the path.
+ size_type size_default() const {
+ return _includesClosingSegment() ? size_closed() : size_open();
+ }
+ /// Natural size of the path.
+ size_type size() const { return size_default(); }
+
+ size_type max_size() const { return _data->curves.max_size() - 1; }
+
+ /** @brief Check whether path is empty.
+ * The path is empty if it contains only the closing segment, which according
+ * to the continuity invariant must be degenerate. Note that unlike standard
+ * containers, two empty paths are not necessarily identical, because the
+ * degenerate closing segment may be at a different point, affecting the operation
+ * of methods such as appendNew(). */
+ bool empty() const { return (_data->curves.size() == 1); }
+
+ /// Check whether the path is closed.
+ bool closed() const { return _closed; }
+
+ /** @brief Set whether the path is closed.
+ * When closing a path where the last segment can be represented as a closing
+ * segment, the last segment will be removed. When opening a path, the closing
+ * segment will be erased. This means that closing and then opening a path
+ * will not always give back the original path. */
+ void close(bool closed = true);
+
+ /** @brief Remove all curves from the path.
+ * The initial and final points of the closing segment are set to (0,0).
+ * The stitching flag remains unchanged. */
+ void clear();
+
+ /** @brief Get the approximate bounding box.
+ * The rectangle returned by this method will contain all the curves, but it's not
+ * guaranteed to be the smallest possible one */
+ OptRect boundsFast() const;
+
+ /** @brief Get a tight-fitting bounding box.
+ * This will return the smallest possible axis-aligned rectangle containing
+ * all the curves in the path. */
+ OptRect boundsExact() const;
+
+ Piecewise<D2<SBasis> > toPwSb() const;
+
+ /// Test paths for exact equality.
+ bool operator==(Path const &other) const;
+
+ /// Apply a transform to each curve.
+ template <typename T>
+ Path &operator*=(T const &tr) {
+ BOOST_CONCEPT_ASSERT((TransformConcept<T>));
+ _unshare();
+ for (std::size_t i = 0; i < _data->curves.size(); ++i) {
+ _data->curves[i] *= tr;
+ }
+ return *this;
+ }
+
+ template <typename T>
+ friend Path operator*(Path const &path, T const &tr) {
+ BOOST_CONCEPT_ASSERT((TransformConcept<T>));
+ Path result(path);
+ result *= tr;
+ return result;
+ }
+
+ /** @brief Get the allowed range of time values.
+ * @return Values for which pointAt() and valueAt() yield valid results. */
+ Interval timeRange() const;
+
+ /** Get the curve at the specified time value.
+ * @param t Time value
+ * @param rest Optional storage for the corresponding time value in the curve */
+ Curve const &curveAt(Coord t, Coord *rest = NULL) const;
+
+ /// Get the closing segment of the path.
+ LineSegment const &closingSegment() const { return *_closing_seg; }
+
+ /** @brief Get the point at the specified time value.
+ * Note that this method has reduced precision with respect to calling pointAt()
+ * directly on the curve. If you want high precision results, use the version
+ * that takes a PathTime parameter.
+ *
+ * Allowed time values range from zero to the number of curves; you can retrieve
+ * the allowed range of values with timeRange(). */
+ Point pointAt(Coord t) const;
+
+ /// Get one coordinate (X or Y) at the specified time value.
+ Coord valueAt(Coord t, Dim2 d) const;
+
+ /// Get the curve at the specified path time.
+ Curve const &curveAt(PathTime const &pos) const;
+ /// Get the point at the specified path time.
+ Point pointAt(PathTime const &pos) const;
+ /// Get one coordinate at the specified path time.
+ Coord valueAt(PathTime const &pos, Dim2 d) const;
+
+ Point operator()(Coord t) const { return pointAt(t); }
+
+ /** @brief Find the extrema of the specified coordinate.
+ *
+ * Returns a PathExtrema struct describing "witness" points on the path
+ * where the specified coordinate attains its minimum and maximum values.
+ */
+ PathExtrema extrema(Dim2 d) const;
+
+ /// Compute intersections with axis-aligned line.
+ std::vector<PathTime> roots(Coord v, Dim2 d) const;
+
+ /// Compute intersections with another path.
+ std::vector<PathIntersection> intersect(Path const &other, Coord precision = EPSILON) const;
+
+ /// Compute intersections of the path with itself.
+ std::vector<PathIntersection> intersectSelf(Coord precision = EPSILON) const;
+
+ /** @brief Determine the winding number at the specified point.
+ *
+ * The winding number is the number of full turns made by a ray that connects the passed
+ * point and the path's value (i.e. the result of the pointAt() method) as the time increases
+ * from 0 to the maximum valid value. Positive numbers indicate turns in the direction
+ * of increasing angles.
+ *
+ * Winding numbers are often used as the definition of what is considered "inside"
+ * the shape. Typically points with either nonzero winding or odd winding are
+ * considered to be inside the path. */
+ int winding(Point const &p) const;
+
+ std::vector<Coord> allNearestTimes(Point const &p, Coord from, Coord to) const;
+ std::vector<Coord> allNearestTimes(Point const &p) const {
+ return allNearestTimes(p, 0, size_default());
+ }
+
+ PathTime nearestTime(Point const &p, Coord *dist = NULL) const;
+ std::vector<Coord> nearestTimePerCurve(Point const &p) const;
+
+ std::vector<Point> nodes() const;
+
+ void appendPortionTo(Path &p, Coord f, Coord t) const;
+
+ /** @brief Append a subset of this path to another path.
+ * An extra stitching segment will be inserted if the start point of the portion
+ * and the final point of the target path do not match exactly.
+ * The closing segment of the target path will be modified. */
+ void appendPortionTo(Path &p, PathTime const &from, PathTime const &to, bool cross_start = false) const {
+ PathInterval ival(from, to, cross_start, size_closed());
+ appendPortionTo(p, ival, std::nullopt, std::nullopt);
+ }
+
+ /** @brief Append a subset of this path to another path.
+ * This version allows you to explicitly pass a PathInterval. */
+ void appendPortionTo(Path &p, PathInterval const &ival) const {
+ appendPortionTo(p, ival, std::nullopt, std::nullopt);
+ }
+
+ /** @brief Append a subset of this path to another path, specifying endpoints.
+ * This method is for use in situations where endpoints of the portion segments
+ * have to be set exactly, for instance when computing Boolean operations. */
+ void appendPortionTo(Path &p, PathInterval const &ival,
+ std::optional<Point> const &p_from, std::optional<Point> const &p_to) const;
+
+ Path portion(Coord f, Coord t) const {
+ Path ret;
+ ret.close(false);
+ appendPortionTo(ret, f, t);
+ return ret;
+ }
+
+ Path portion(Interval const &i) const { return portion(i.min(), i.max()); }
+
+ /** @brief Get a subset of the current path with full precision.
+ * When @a from is larger (later in the path) than @a to, the returned portion
+ * will be reversed. If @a cross_start is true, the portion will be reversed
+ * and will cross the initial point of the path. Therefore, when @a from is larger
+ * than @a to and @a cross_start is true, the returned portion will not be reversed,
+ * but will "wrap around" the end of the path. */
+ Path portion(PathTime const &from, PathTime const &to, bool cross_start = false) const {
+ Path ret;
+ ret.close(false);
+ appendPortionTo(ret, from, to, cross_start);
+ return ret;
+ }
+
+ /** @brief Get a subset of the current path with full precision.
+ * This version allows you to explicitly pass a PathInterval. */
+ Path portion(PathInterval const &ival) const {
+ Path ret;
+ ret.close(false);
+ appendPortionTo(ret, ival);
+ return ret;
+ }
+
+ /** @brief Obtain a reversed version of the current path.
+ * The final point of the current path will become the initial point
+ * of the reversed path, unless it is closed and has a non-degenerate
+ * closing segment. In that case, the new initial point will be the final point
+ * of the last "real" segment. */
+ Path reversed() const;
+
+ void insert(iterator pos, Curve const &curve);
+
+ template <typename Iter>
+ void insert(iterator pos, Iter first, Iter last) {
+ _unshare();
+ Sequence::iterator seq_pos(seq_iter(pos));
+ Sequence source;
+ for (; first != last; ++first) {
+ source.push_back(first->duplicate());
+ }
+ do_update(seq_pos, seq_pos, source);
+ }
+
+ void erase(iterator pos);
+ void erase(iterator first, iterator last);
+
+ // erase last segment of path
+ void erase_last() { erase(iterator(*this, size() - 1)); }
+
+ void start(Point const &p);
+
+ /** @brief Get the first point in the path. */
+ Point initialPoint() const { return (*_closing_seg)[1]; }
+
+ /** @brief Get the last point in the path.
+ * If the path is closed, this is always the same as the initial point. */
+ Point finalPoint() const { return (*_closing_seg)[_closed ? 1 : 0]; }
+
+ /** @brief Get the unit tangent vector at the start of the path,
+ * or the zero vector if undefined. */
+ Point initialUnitTangent() const {
+ for (auto const &curve : *this) {
+ if (!curve.isDegenerate()) {
+ return curve.unitTangentAt(0.0);
+ }
+ }
+ return Point();
+ }
+
+ /** @brief Get the unit tangent vector at the end of the path,
+ * or the zero vector if undefined. */
+ Point finalUnitTangent() const {
+ for (auto it = end(); it != begin();) {
+ --it;
+ if (!it->isDegenerate()) {
+ return it->unitTangentAt(1.0);
+ }
+ }
+ return Point();
+ }
+
+ void setInitial(Point const &p) {
+ _unshare();
+ _closed = false;
+ _data->curves.front().setInitial(p);
+ _closing_seg->setFinal(p);
+ }
+ void setFinal(Point const &p) {
+ _unshare();
+ _closed = false;
+ _data->curves[size_open() - 1].setFinal(p);
+ _closing_seg->setInitial(p);
+ }
+
+ /** @brief Add a new curve to the end of the path.
+ * This inserts the new curve right before the closing segment.
+ * The path takes ownership of the passed pointer, which should not be freed. */
+ void append(Curve *curve) {
+ _unshare();
+ stitchTo(curve->initialPoint());
+ do_append(curve);
+ }
+
+ void append(Curve const &curve) {
+ _unshare();
+ stitchTo(curve.initialPoint());
+ do_append(curve.duplicate());
+ }
+ void append(D2<SBasis> const &curve) {
+ _unshare();
+ stitchTo(Point(curve[X][0][0], curve[Y][0][0]));
+ do_append(new SBasisCurve(curve));
+ }
+ void append(Path const &other) {
+ replace(end_open(), other.begin(), other.end());
+ }
+
+ void replace(iterator replaced, Curve const &curve);
+ void replace(iterator first, iterator last, Curve const &curve);
+ void replace(iterator replaced, Path const &path);
+ void replace(iterator first, iterator last, Path const &path);
+
+ template <typename Iter>
+ void replace(iterator replaced, Iter first, Iter last) {
+ replace(replaced, replaced + 1, first, last);
+ }
+
+ template <typename Iter>
+ void replace(iterator first_replaced, iterator last_replaced, Iter first, Iter last) {
+ _unshare();
+ Sequence::iterator seq_first_replaced(seq_iter(first_replaced));
+ Sequence::iterator seq_last_replaced(seq_iter(last_replaced));
+ Sequence source;
+ for (; first != last; ++first) {
+ source.push_back(first->duplicate());
+ }
+ do_update(seq_first_replaced, seq_last_replaced, source);
+ }
+
+ /** @brief Append a new curve to the path.
+ *
+ * This family of methods will automatically use the current final point of the path
+ * as the first argument of the new curve's constructor. To call this method,
+ * you'll need to write e.g.:
+ * @code
+ path.template appendNew<CubicBezier>(control1, control2, end_point);
+ @endcode
+ * It is important to note that the coordinates passed to appendNew should be finite!
+ * If one of the coordinates is infinite, 2geom will throw a ContinuityError exception.
+ */
+ template <typename CurveType, typename... Args>
+ void appendNew(Args&&... args) {
+ _unshare();
+ do_append(new CurveType(finalPoint(), std::forward<Args>(args)...));
+ }
+
+ /** @brief Reduce the closing segment to a point if it's shorter than precision.
+ * Do this by moving the final point. */
+ void snapEnds(Coord precision = EPSILON);
+
+ /// Append a stitching segment ending at the specified point.
+ void stitchTo(Point const &p);
+
+ /** @brief Return a copy of the path without degenerate curves, except possibly for a
+ * degenerate closing segment. */
+ Path withoutDegenerateCurves() const;
+
+ /** @brief Verify the continuity invariant.
+ * If the path is not contiguous, this will throw a CountinuityError. */
+ void checkContinuity() const;
+
+ /** @brief Enable or disable the throwing of exceptions when stitching discontinuities.
+ * Normally stitching will cause exceptions, but when you are working with unsanitized
+ * curve data, you can disable these exceptions. */
+ void setStitching(bool x) {
+ _exception_on_stitch = !x;
+ }
+
+private:
+ static Sequence::iterator seq_iter(iterator const &iter) {
+ return iter.path->_data->curves.begin() + iter.index;
+ }
+ static Sequence::const_iterator seq_iter(const_iterator const &iter) {
+ return iter.path->_data->curves.begin() + iter.index;
+ }
+
+ // whether the closing segment is part of the path
+ bool _includesClosingSegment() const {
+ return _closed && !_closing_seg->isDegenerate();
+ }
+ void _unshare() {
+ // Called before every mutation.
+ // Ensure we have our own copy of curve data and reset cached values
+ if (!_data.unique()) {
+ _data.reset(new PathData(*_data));
+ _closing_seg = static_cast<ClosingSegment*>(&_data->curves.back());
+ }
+ _data->fast_bounds = OptRect();
+ }
+ PathTime _factorTime(Coord t) const;
+
+ void stitch(Sequence::iterator first_replaced, Sequence::iterator last_replaced, Sequence &sequence);
+ void do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source);
+
+ // n.b. takes ownership of curve object
+ void do_append(Curve *curve);
+
+ std::shared_ptr<PathData> _data;
+ ClosingSegment *_closing_seg;
+ bool _closed;
+ bool _exception_on_stitch;
+}; // end class Path
+
+Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths);
+
+inline Coord nearest_time(Point const &p, Path const &c) {
+ PathTime pt = c.nearestTime(p);
+ return pt.curve_index + pt.t;
+}
+
+bool are_near(Path const &a, Path const &b, Coord precision = EPSILON);
+
+/**
+ * @brief Find the first point where two paths diverge away from one another.
+ *
+ * If the two paths have a common starting point, the algorithm follows them for as long as the
+ * images of the paths coincide and finds the first point where they stop coinciding. Note that
+ * only the images of paths in the plane are compared, and not their parametrizations, so this
+ * is not a functional (parametric) coincidence. If you want to test parametric coincidence, use
+ * bool are_near(Path const&, Path const&, Coord) instead.
+ *
+ * The function returns the point where the traces of the two paths finally diverge up to the
+ * specified precision. If the traces (images) of the paths are nearly identical until the end,
+ * the returned point is their (almost) common endpoint. If however the image of one of the paths
+ * is completely contained in the image of the other path, the returned point is the endpoint of
+ * the shorter path.
+ *
+ * If the paths have different starting points, then the returned intersection has the special
+ * time values of -1.0 on both paths and the returned intersection point is the midpoint of the
+ * line segment connecting the two starting points.
+ *
+ * @param first The first path to follow; corresponds to .first in the return value.
+ * @param second The second path to follow; corresponds to .second in the return value.
+ * @param precision How close the paths' images need to be in order to be considered as overlapping.
+ * @return A path intersection specifying the point and path times where the two paths part ways.
+ */
+PathIntersection parting_point(Path const &first, Path const &second, Coord precision = EPSILON);
+
+std::ostream &operator<<(std::ostream &out, Path const &path);
+
+} // end namespace Geom
+
+
+#endif // LIB2GEOM_SEEN_PATH_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/include/2geom/pathvector.h b/include/2geom/pathvector.h
new file mode 100644
index 0000000..3fd7d36
--- /dev/null
+++ b/include/2geom/pathvector.h
@@ -0,0 +1,304 @@
+/** @file
+ * @brief PathVector - a sequence of subpaths
+ *//*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2008-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_PATHVECTOR_H
+#define LIB2GEOM_SEEN_PATHVECTOR_H
+
+#include <optional>
+#include <boost/concept/requires.hpp>
+#include <boost/range/algorithm/equal.hpp>
+#include <2geom/forward.h>
+#include <2geom/path.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+/** @brief Generalized time value in the path vector.
+ *
+ * This class exists because mapping the range of multiple curves onto the same interval
+ * as the curve index, we lose some precision. For instance, a path with 16 curves will
+ * have 4 bits less precision than a path with 1 curve. If you need high precision results
+ * in long paths, use this class and related methods instead of the standard methods
+ * pointAt(), nearestTime() and so on.
+ *
+ * @ingroup Paths */
+struct PathVectorTime
+ : public PathTime
+ , boost::totally_ordered<PathVectorTime>
+{
+ size_type path_index; ///< Index of the path in the vector
+
+ PathVectorTime() : PathTime(0, 0), path_index(0) {}
+ PathVectorTime(size_type _i, size_type _c, Coord _t)
+ : PathTime(_c, _t), path_index(_i) {}
+ PathVectorTime(size_type _i, PathTime const &pos)
+ : PathTime(pos), path_index(_i) {}
+
+ bool operator<(PathVectorTime const &other) const {
+ if (path_index < other.path_index) return true;
+ if (path_index == other.path_index) {
+ return static_cast<PathTime const &>(*this) < static_cast<PathTime const &>(other);
+ }
+ return false;
+ }
+ bool operator==(PathVectorTime const &other) const {
+ return path_index == other.path_index
+ && static_cast<PathTime const &>(*this) == static_cast<PathTime const &>(other);
+ }
+
+ PathTime const &asPathTime() const {
+ return *static_cast<PathTime const *>(this);
+ }
+};
+
+inline std::ostream &operator<<(std::ostream &os, PathVectorTime const &pvt) {
+ os << pvt.path_index << ": " << pvt.asPathTime();
+ return os;
+}
+
+typedef Intersection<PathVectorTime> PathVectorIntersection;
+typedef PathVectorIntersection PVIntersection; ///< Alias to save typing
+
+template <>
+struct ShapeTraits<PathVector> {
+ typedef PathVectorTime TimeType;
+ //typedef PathVectorInterval IntervalType;
+ typedef PathVector AffineClosureType;
+ typedef PathVectorIntersection IntersectionType;
+};
+
+/** @brief Sequence of subpaths.
+ *
+ * This class corresponds to the SVG notion of a path:
+ * a sequence of any number of open or closed contiguous subpaths.
+ * Unlike Path, this class is closed under boolean operations.
+ *
+ * If you want to represent an arbitrary shape, this is the best class to use.
+ * Shapes with a boundary that is composed of only a single contiguous
+ * component can be represented with Path instead.
+ *
+ * @ingroup Paths
+ */
+class PathVector
+ : MultipliableNoncommutative< PathVector, Affine
+ , MultipliableNoncommutative< PathVector, Translate
+ , MultipliableNoncommutative< PathVector, Scale
+ , MultipliableNoncommutative< PathVector, Rotate
+ , MultipliableNoncommutative< PathVector, HShear
+ , MultipliableNoncommutative< PathVector, VShear
+ , MultipliableNoncommutative< PathVector, Zoom
+ , boost::equality_comparable< PathVector
+ > > > > > > > >
+{
+ typedef std::vector<Path> Sequence;
+public:
+ typedef PathVectorTime Position;
+ typedef Sequence::iterator iterator;
+ typedef Sequence::const_iterator const_iterator;
+ typedef Sequence::size_type size_type;
+ typedef Path value_type;
+ typedef Path &reference;
+ typedef Path const &const_reference;
+ typedef Path *pointer;
+ typedef std::ptrdiff_t difference_type;
+
+ PathVector() {}
+ PathVector(Path const &p)
+ : _data(1, p)
+ {}
+ template <typename InputIter>
+ PathVector(InputIter first, InputIter last)
+ : _data(first, last)
+ {}
+
+ /// Check whether the vector contains any paths.
+ bool empty() const { return _data.empty(); }
+ /// Get the number of paths in the vector.
+ size_type size() const { return _data.size(); }
+ /// Get the total number of curves in the vector.
+ size_type curveCount() const;
+
+ iterator begin() { return _data.begin(); }
+ iterator end() { return _data.end(); }
+ const_iterator begin() const { return _data.begin(); }
+ const_iterator end() const { return _data.end(); }
+ Path &operator[](size_type index) {
+ return _data[index];
+ }
+ Path const &operator[](size_type index) const {
+ return _data[index];
+ }
+ Path &at(size_type index) {
+ return _data.at(index);
+ }
+ Path const &at(size_type index) const {
+ return _data.at(index);
+ }
+ Path &front() { return _data.front(); }
+ Path const &front() const { return _data.front(); }
+ Path &back() { return _data.back(); }
+ Path const &back() const { return _data.back(); }
+ /// Append a path at the end.
+ void push_back(Path const &path) {
+ _data.push_back(path);
+ }
+ /// Remove the last path.
+ void pop_back() {
+ _data.pop_back();
+ }
+ iterator insert(iterator pos, Path const &p) {
+ return _data.insert(pos, p);
+ }
+ template <typename InputIter>
+ void insert(iterator out, InputIter first, InputIter last) {
+ _data.insert(out, first, last);
+ }
+ /// Remove a path from the vector.
+ iterator erase(iterator i) {
+ return _data.erase(i);
+ }
+ /// Remove a range of paths from the vector.
+ iterator erase(iterator first, iterator last) {
+ return _data.erase(first, last);
+ }
+ /// Remove all paths from the vector.
+ void clear() { _data.clear(); }
+ /** @brief Change the number of paths.
+ * If the vector size increases, it is passed with paths that contain only
+ * a degenerate closing segment at (0,0). */
+ void resize(size_type n) { _data.resize(n); }
+ /** @brief Reverse the direction of paths in the vector.
+ * @param reverse_paths If this is true, the order of paths is reversed as well;
+ * otherwise each path is reversed, but their order in the
+ * PathVector stays the same */
+ void reverse(bool reverse_paths = true);
+ /** @brief Get a new vector with reversed direction of paths.
+ * @param reverse_paths If this is true, the order of paths is reversed as well;
+ * otherwise each path is reversed, but their order in the
+ * PathVector stays the same */
+ PathVector reversed(bool reverse_paths = true) const;
+
+ /// Get the range of allowed time values.
+ Interval timeRange() const {
+ Interval ret(0, curveCount()); return ret;
+ }
+ /** @brief Get the first point in the first path of the vector.
+ * This method will throw an exception if the vector doesn't contain any paths. */
+ Point initialPoint() const {
+ return _data.front().initialPoint();
+ }
+ /** @brief Get the last point in the last path of the vector.
+ * This method will throw an exception if the vector doesn't contain any paths. */
+ Point finalPoint() const {
+ return _data.back().finalPoint();
+ }
+ /** @brief Get all intersections of the path-vector with itself. This includes both
+ * self-intersections of constituent paths and intersections between different paths. */
+ std::vector<PathVectorIntersection> intersectSelf(Coord precision = EPSILON) const;
+ Path &pathAt(Coord t, Coord *rest = NULL);
+ Path const &pathAt(Coord t, Coord *rest = NULL) const;
+ Curve const &curveAt(Coord t, Coord *rest = NULL) const;
+ Coord valueAt(Coord t, Dim2 d) const;
+ Point pointAt(Coord t) const;
+
+ Path &pathAt(PathVectorTime const &pos) {
+ return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(pos));
+ }
+ Path const &pathAt(PathVectorTime const &pos) const {
+ return at(pos.path_index);
+ }
+ Curve const &curveAt(PathVectorTime const &pos) const {
+ return at(pos.path_index).at(pos.curve_index);
+ }
+ Point pointAt(PathVectorTime const &pos) const {
+ return at(pos.path_index).at(pos.curve_index).pointAt(pos.t);
+ }
+ Coord valueAt(PathVectorTime const &pos, Dim2 d) const {
+ return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d);
+ }
+
+ OptRect boundsFast() const;
+ OptRect boundsExact() const;
+
+ template <typename T>
+ BOOST_CONCEPT_REQUIRES(((TransformConcept<T>)), (PathVector &))
+ operator*=(T const &t) {
+ if (empty()) return *this;
+ for (auto & i : *this) {
+ i *= t;
+ }
+ return *this;
+ }
+
+ bool operator==(PathVector const &other) const {
+ return boost::range::equal(_data, other._data);
+ }
+
+ void snapEnds(Coord precision = EPSILON);
+
+ std::vector<PVIntersection> intersect(PathVector const &other, Coord precision = EPSILON) const;
+
+ /** @brief Determine the winding number at the specified point.
+ * This is simply the sum of winding numbers for constituent paths. */
+ int winding(Point const &p) const;
+
+ std::optional<PathVectorTime> nearestTime(Point const &p, Coord *dist = NULL) const;
+ std::vector<PathVectorTime> allNearestTimes(Point const &p, Coord *dist = NULL) const;
+
+ std::vector<Point> nodes() const;
+
+private:
+ PathVectorTime _factorTime(Coord t) const;
+
+ Sequence _data;
+};
+
+inline OptRect bounds_fast(PathVector const &pv) { return pv.boundsFast(); }
+inline OptRect bounds_exact(PathVector const &pv) { return pv.boundsExact(); }
+
+std::ostream &operator<<(std::ostream &out, PathVector const &pv);
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_PATHVECTOR_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/include/2geom/piecewise.h b/include/2geom/piecewise.h
new file mode 100644
index 0000000..e34df15
--- /dev/null
+++ b/include/2geom/piecewise.h
@@ -0,0 +1,945 @@
+/** @file
+ * @brief Piecewise function class
+ *//*
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * 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, output 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 LIB2GEOM_SEEN_PIECEWISE_H
+#define LIB2GEOM_SEEN_PIECEWISE_H
+
+#include <vector>
+#include <map>
+#include <utility>
+#include <boost/concept_check.hpp>
+#include <2geom/concepts.h>
+#include <2geom/math-utils.h>
+#include <2geom/sbasis.h>
+
+
+namespace Geom {
+/**
+ * @brief Function defined as discrete pieces.
+ *
+ * The Piecewise class manages a sequence of elements of a type as segments and
+ * the ’cuts’ between them. These cuts are time values which separate the pieces.
+ * This function representation allows for more interesting functions, as it provides
+ * a viable output for operations such as inversion, which may require multiple
+ * SBasis to properly invert the original.
+ *
+ * As for technical details, while the actual SBasis segments begin on the first
+ * cut and end on the last, the function is defined throughout all inputs by ex-
+ * tending the first and last segments. The exact switching between segments is
+ * arbitrarily such that beginnings (t=0) have preference over endings (t=1). This
+ * only matters if it is discontinuous at the location.
+ * \f[
+ * f(t) \rightarrow \left\{
+ * \begin{array}{cc}
+ * s_1,& t <= c_2 \\
+ * s_2,& c_2 <= t <= c_3\\
+ * \ldots \\
+ * s_n,& c_n <= t
+ * \end{array}\right.
+ * \f]
+ *
+ * @ingroup Fragments
+ */
+template <typename T>
+class Piecewise {
+ BOOST_CLASS_REQUIRE(T, Geom, FragmentConcept);
+
+ public:
+ std::vector<double> cuts;
+ std::vector<T> segs;
+ //segs[i] stretches from cuts[i] to cuts[i+1].
+
+ Piecewise() {}
+
+ explicit Piecewise(const T &s) {
+ push_cut(0.);
+ push_seg(s);
+ push_cut(1.);
+ }
+
+ unsigned input_dim(){return 1;}
+
+ typedef typename T::output_type output_type;
+
+ explicit Piecewise(const output_type & v) {
+ push_cut(0.);
+ push_seg(T(v));
+ push_cut(1.);
+ }
+
+ inline void reserve(unsigned i) { segs.reserve(i); cuts.reserve(i + 1); }
+
+ inline T const& operator[](unsigned i) const { return segs[i]; }
+ inline T& operator[](unsigned i) { return segs[i]; }
+ inline output_type operator()(double t) const { return valueAt(t); }
+ inline output_type valueAt(double t) const {
+ unsigned n = segN(t);
+ return segs[n](segT(t, n));
+ }
+ inline output_type firstValue() const {
+ return valueAt(cuts.front());
+ }
+ inline output_type lastValue() const {
+ return valueAt(cuts.back());
+ }
+
+ /**
+ * The size of the returned vector equals n_derivs+1.
+ */
+ std::vector<output_type> valueAndDerivatives(double t, unsigned n_derivs) const {
+ unsigned n = segN(t);
+ std::vector<output_type> ret, val = segs[n].valueAndDerivatives(segT(t, n), n_derivs);
+ double mult = 1;
+ for(unsigned i = 0; i < val.size(); i++) {
+ ret.push_back(val[i] * mult);
+ mult /= cuts[n+1] - cuts[n];
+ }
+ return ret;
+ }
+
+ //TODO: maybe it is not a good idea to have this?
+ Piecewise<T> operator()(SBasis f);
+ Piecewise<T> operator()(Piecewise<SBasis>f);
+
+ inline unsigned size() const { return segs.size(); }
+ inline bool empty() const { return segs.empty(); }
+ inline void clear() {
+ segs.clear();
+ cuts.clear();
+ }
+
+ /**Convenience/implementation hiding function to add segment/cut pairs.
+ * Asserts that basic size and order invariants are correct
+ */
+ inline void push(const T &s, double to) {
+ assert(cuts.size() - segs.size() == 1);
+ push_seg(s);
+ push_cut(to);
+ }
+ inline void push(T &&s, double to) {
+ assert(cuts.size() - segs.size() == 1);
+ push_seg(s);
+ push_cut(to);
+ }
+ //Convenience/implementation hiding function to add cuts.
+ inline void push_cut(double c) {
+ ASSERT_INVARIANTS(cuts.empty() || c > cuts.back());
+ cuts.push_back(c);
+ }
+ //Convenience/implementation hiding function to add segments.
+ inline void push_seg(const T &s) { segs.push_back(s); }
+ inline void push_seg(T &&s) { segs.emplace_back(s); }
+
+ /**Returns the segment index which corresponds to a 'global' piecewise time.
+ * Also takes optional low/high parameters to expedite the search for the segment.
+ */
+ inline unsigned segN(double t, int low = 0, int high = -1) const {
+ high = (high == -1) ? size() : high;
+ if(t < cuts[0]) return 0;
+ if(t >= cuts[size()]) return size() - 1;
+ while(low < high) {
+ int mid = (high + low) / 2; //Lets not plan on having huge (> INT_MAX / 2) cut sequences
+ double mv = cuts[mid];
+ if(mv < t) {
+ if(t < cuts[mid + 1]) return mid; else low = mid + 1;
+ } else if(t < mv) {
+ if(cuts[mid - 1] < t) return mid - 1; else high = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+ return low;
+ }
+
+ /**Returns the time within a segment, given the 'global' piecewise time.
+ * Also takes an optional index parameter which may be used for efficiency or to find the time on a
+ * segment outside its range. If it is left to its default, -1, it will call segN to find the index.
+ */
+ inline double segT(double t, int i = -1) const {
+ if(i == -1) i = segN(t);
+ assert(i >= 0);
+ return (t - cuts[i]) / (cuts[i+1] - cuts[i]);
+ }
+
+ inline double mapToDomain(double t, unsigned i) const {
+ return (1-t)*cuts[i] + t*cuts[i+1]; //same as: t * (cuts[i+1] - cuts[i]) + cuts[i]
+ }
+
+ //Offsets the piecewise domain
+ inline void offsetDomain(double o) {
+ assert(std::isfinite(o));
+ if(o != 0)
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] += o;
+ }
+
+ //Scales the domain of the function by a value. 0 will result in an empty Piecewise.
+ inline void scaleDomain(double s) {
+ assert(s > 0);
+ if(s == 0) {
+ cuts.clear(); segs.clear();
+ return;
+ }
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] *= s;
+ }
+
+ //Retrieves the domain in interval form
+ inline Interval domain() const { return Interval(cuts.front(), cuts.back()); }
+
+ //Transforms the domain into another interval
+ inline void setDomain(Interval dom) {
+ if(empty()) return;
+ /* dom can not be empty
+ if(dom.empty()) {
+ cuts.clear(); segs.clear();
+ return;
+ }*/
+ double cf = cuts.front();
+ double o = dom.min() - cf, s = dom.extent() / (cuts.back() - cf);
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] = (cuts[i] - cf) * s + o;
+ //fix floating point precision errors.
+ cuts[0] = dom.min();
+ cuts[size()] = dom.max();
+ }
+
+ //Concatenates this Piecewise function with another, offsetting time of the other to match the end.
+ inline void concat(const Piecewise<T> &other) {
+ if(other.empty()) return;
+
+ if(empty()) {
+ cuts = other.cuts; segs = other.segs;
+ return;
+ }
+
+ segs.insert(segs.end(), other.segs.begin(), other.segs.end());
+ double t = cuts.back() - other.cuts.front();
+ cuts.reserve(cuts.size() + other.size());
+ for(unsigned i = 0; i < other.size(); i++)
+ push_cut(other.cuts[i + 1] + t);
+ }
+
+ //Like concat, but ensures continuity.
+ inline void continuousConcat(const Piecewise<T> &other) {
+ boost::function_requires<AddableConcept<typename T::output_type> >();
+ if(other.empty()) return;
+
+ if(empty()) { segs = other.segs; cuts = other.cuts; return; }
+
+ typename T::output_type y = segs.back().at1() - other.segs.front().at0();
+ double t = cuts.back() - other.cuts.front();
+ reserve(size() + other.size());
+ for(unsigned i = 0; i < other.size(); i++)
+ push(other[i] + y, other.cuts[i + 1] + t);
+ }
+
+ //returns true if the Piecewise<T> meets some basic invariants.
+ inline bool invariants() const {
+ // segs between cuts
+ if(!(segs.size() + 1 == cuts.size() || (segs.empty() && cuts.empty())))
+ return false;
+ // cuts in order
+ for(unsigned i = 0; i < segs.size(); i++)
+ if(cuts[i] >= cuts[i+1])
+ return false;
+ return true;
+ }
+
+};
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+inline typename FragmentConcept<T>::BoundsType bounds_fast(const Piecewise<T> &f) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty()) return typename FragmentConcept<T>::BoundsType();
+ typename FragmentConcept<T>::BoundsType ret(bounds_fast(f[0]));
+ for(unsigned i = 1; i < f.size(); i++)
+ ret.unionWith(bounds_fast(f[i]));
+ return ret;
+}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+inline typename FragmentConcept<T>::BoundsType bounds_exact(const Piecewise<T> &f) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty()) return typename FragmentConcept<T>::BoundsType();
+ typename FragmentConcept<T>::BoundsType ret(bounds_exact(f[0]));
+ for(unsigned i = 1; i < f.size(); i++)
+ ret.unionWith(bounds_exact(f[i]));
+ return ret;
+}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+inline typename FragmentConcept<T>::BoundsType bounds_local(const Piecewise<T> &f, const OptInterval &_m) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty() || !_m) return typename FragmentConcept<T>::BoundsType();
+ Interval const &m = *_m;
+ if(m.isSingular()) return typename FragmentConcept<T>::BoundsType(f(m.min()));
+
+ unsigned fi = f.segN(m.min()), ti = f.segN(m.max());
+ double ft = f.segT(m.min(), fi), tt = f.segT(m.max(), ti);
+
+ if(fi == ti) return bounds_local(f[fi], Interval(ft, tt));
+
+ typename FragmentConcept<T>::BoundsType ret(bounds_local(f[fi], Interval(ft, 1.)));
+ for(unsigned i = fi + 1; i < ti; i++)
+ ret.unionWith(bounds_exact(f[i]));
+ if(tt != 0.) ret.unionWith(bounds_local(f[ti], Interval(0., tt)));
+
+ return ret;
+}
+
+/**
+ * Returns a portion of a piece of a Piecewise<T>, given the piece's index and a to/from time.
+ * \relates Piecewise
+ */
+template<typename T>
+T elem_portion(const Piecewise<T> &a, unsigned i, double from, double to) {
+ assert(i < a.size());
+ double rwidth = 1 / (a.cuts[i+1] - a.cuts[i]);
+ return portion( a[i], (from - a.cuts[i]) * rwidth, (to - a.cuts[i]) * rwidth );
+}
+
+/**Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c);
+ * Further subdivides the Piecewise<T> such that there is a cut at every value in c.
+ * Precondition: c sorted lower to higher.
+ *
+ * //Given Piecewise<T> a and b:
+ * Piecewise<T> ac = a.partition(b.cuts);
+ * Piecewise<T> bc = b.partition(a.cuts);
+ * //ac.cuts should be equivalent to bc.cuts
+ *
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c) {
+ assert(pw.invariants());
+ if(c.empty()) return Piecewise<T>(pw);
+
+ Piecewise<T> ret = Piecewise<T>();
+ ret.reserve(c.size() + pw.cuts.size() - 1);
+
+ if(pw.empty()) {
+ ret.cuts = c;
+ for(unsigned i = 0; i < c.size() - 1; i++)
+ ret.push_seg(T());
+ return ret;
+ }
+
+ unsigned si = 0, ci = 0; //Segment index, Cut index
+
+ //if the cuts have something earlier than the Piecewise<T>, add portions of the first segment
+ while(ci < c.size() && c[ci] < pw.cuts.front()) {
+ bool isLast = (ci == c.size()-1 || c[ci + 1] >= pw.cuts.front());
+ ret.push_cut(c[ci]);
+ ret.push_seg( elem_portion(pw, 0, c[ci], isLast ? pw.cuts.front() : c[ci + 1]) );
+ ci++;
+ }
+
+ ret.push_cut(pw.cuts[0]);
+ double prev = pw.cuts[0]; //previous cut
+ //Loop which handles cuts within the Piecewise<T> domain
+ //Should have the cuts = segs + 1 invariant
+ while(si < pw.size() && ci <= c.size()) {
+ if(ci == c.size() && prev <= pw.cuts[si]) { //cuts exhausted, straight copy the rest
+ ret.segs.insert(ret.segs.end(), pw.segs.begin() + si, pw.segs.end());
+ ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + si + 1, pw.cuts.end());
+ return ret;
+ }else if(ci == c.size() || c[ci] >= pw.cuts[si + 1]) { //no more cuts within this segment, finalize
+ if(prev > pw.cuts[si]) { //segment already has cuts, so portion is required
+ ret.push_seg(portion(pw[si], pw.segT(prev, si), 1.0));
+ } else { //plain copy is fine
+ ret.push_seg(pw[si]);
+ }
+ ret.push_cut(pw.cuts[si + 1]);
+ prev = pw.cuts[si + 1];
+ si++;
+ } else if(c[ci] == pw.cuts[si]){ //coincident
+ //Already finalized the seg with the code immediately above
+ ci++;
+ } else { //plain old subdivision
+ ret.push(elem_portion(pw, si, prev, c[ci]), c[ci]);
+ prev = c[ci];
+ ci++;
+ }
+ }
+
+ //input cuts extend further than this Piecewise<T>, extend the last segment.
+ while(ci < c.size()) {
+ if(c[ci] > prev) {
+ ret.push(elem_portion(pw, pw.size() - 1, prev, c[ci]), c[ci]);
+ prev = c[ci];
+ }
+ ci++;
+ }
+ return ret;
+}
+
+/**
+ * Returns a Piecewise<T> with a defined domain of [min(from, to), max(from, to)].
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> portion(const Piecewise<T> &pw, double from, double to) {
+ if(pw.empty() || from == to) return Piecewise<T>();
+
+ Piecewise<T> ret;
+
+ double temp = from;
+ from = std::min(from, to);
+ to = std::max(temp, to);
+
+ unsigned i = pw.segN(from);
+ ret.push_cut(from);
+ if(i == pw.size() - 1 || to <= pw.cuts[i + 1]) { //to/from inhabit the same segment
+ ret.push(elem_portion(pw, i, from, to), to);
+ return ret;
+ }
+ ret.push_seg(portion( pw[i], pw.segT(from, i), 1.0 ));
+ i++;
+ unsigned fi = pw.segN(to, i);
+ ret.reserve(fi - i + 1);
+ if (to == pw.cuts[fi]) fi-=1;
+
+ ret.segs.insert(ret.segs.end(), pw.segs.begin() + i, pw.segs.begin() + fi); //copy segs
+ ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + i, pw.cuts.begin() + fi + 1); //and their cuts
+
+ ret.push_seg( portion(pw[fi], 0.0, pw.segT(to, fi)));
+ if(to != ret.cuts.back()) ret.push_cut(to);
+ ret.invariants();
+ return ret;
+}
+
+//TODO: seems like these should be mutating
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> remove_short_cuts(Piecewise<T> const &f, double tol) {
+ if(f.empty()) return f;
+ Piecewise<T> ret;
+ ret.reserve(f.size());
+ ret.push_cut(f.cuts[0]);
+ for(unsigned i=0; i<f.size(); i++){
+ if (f.cuts[i+1]-f.cuts[i] >= tol || i==f.size()-1) {
+ ret.push(f[i], f.cuts[i+1]);
+ }
+ }
+ return ret;
+}
+
+//TODO: seems like these should be mutating
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> remove_short_cuts_extending(Piecewise<T> const &f, double tol) {
+ if(f.empty()) return f;
+ Piecewise<T> ret;
+ ret.reserve(f.size());
+ ret.push_cut(f.cuts[0]);
+ double last = f.cuts[0]; // last cut included
+ for(unsigned i=0; i<f.size(); i++){
+ if (f.cuts[i+1]-f.cuts[i] >= tol) {
+ ret.push(elem_portion(f, i, last, f.cuts[i+1]), f.cuts[i+1]);
+ last = f.cuts[i+1];
+ }
+ }
+ return ret;
+}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+std::vector<double> roots(const Piecewise<T> &pw) {
+ std::vector<double> ret;
+ for(unsigned i = 0; i < pw.size(); i++) {
+ std::vector<double> sr = roots(pw[i]);
+ for (double & j : sr) ret.push_back(j * (pw.cuts[i + 1] - pw.cuts[i]) + pw.cuts[i]);
+
+ }
+ return ret;
+}
+
+//IMPL: OffsetableConcept
+/**
+ * ...
+ * \return \f$ a + b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator+(Piecewise<T> const &a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+//TODO:empty
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] + b);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+//TODO: empty
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] - b);
+ return ret;
+}
+template<typename T>
+Piecewise<T>& operator+=(Piecewise<T>& a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+
+ if(a.empty()) { a.push_cut(0.); a.push(T(b), 1.); return a; }
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] += b;
+ return a;
+}
+template<typename T>
+Piecewise<T>& operator-=(Piecewise<T>& a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+
+ if(a.empty()) { a.push_cut(0.); a.push(T(-b), 1.); return a; }
+
+ for(unsigned i = 0;i < a.size();i++)
+ a[i] -= b;
+ return a;
+}
+
+//IMPL: ScalableConcept
+/**
+ * ...
+ * \return \f$ -a = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(- a[i]);
+ return ret;
+}
+/**
+ * ...
+ * \return \f$ a * b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator*(Piecewise<T> const &a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ if(a.empty()) return Piecewise<T>();
+
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] * b);
+ return ret;
+}
+/**
+ * ...
+ * \return \f$ a * b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator*(Piecewise<T> const &a, T b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ if(a.empty()) return Piecewise<T>();
+
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] * b);
+ return ret;
+}
+/**
+ * ...
+ * \return \f$ a / b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator/(Piecewise<T> const &a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ //FIXME: b == 0?
+ if(a.empty()) return Piecewise<T>();
+
+ Piecewise<T> ret;
+ ret.segs.reserve(a.size());
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] / b);
+ return ret;
+}
+template<typename T>
+Piecewise<T>& operator*=(Piecewise<T>& a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] *= b;
+ return a;
+}
+template<typename T>
+Piecewise<T>& operator/=(Piecewise<T>& a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ //FIXME: b == 0?
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] /= b;
+ return a;
+}
+
+//IMPL: AddableConcept
+/**
+ * ...
+ * \return \f$ a + b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator+(Piecewise<T> const &a, Piecewise<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<T> ret;
+ assert(pa.size() == pb.size());
+ ret.segs.reserve(pa.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] + pb[i]);
+ return ret;
+}
+/**
+ * ...
+ * \return \f$ a - b = \f$
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a, Piecewise<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<T> ret = Piecewise<T>();
+ assert(pa.size() == pb.size());
+ ret.segs.reserve(pa.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] - pb[i]);
+ return ret;
+}
+template<typename T>
+inline Piecewise<T>& operator+=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a+b;
+ return a;
+}
+template<typename T>
+inline Piecewise<T>& operator-=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a-b;
+ return a;
+}
+
+/**
+ * ...
+ * \return \f$ a \cdot b = \f$
+ * \relates Piecewise
+ */
+template<typename T1,typename T2>
+Piecewise<T2> operator*(Piecewise<T1> const &a, Piecewise<T2> const &b) {
+ //function_requires<MultiplicableConcept<T1> >();
+ //function_requires<MultiplicableConcept<T2> >();
+
+ Piecewise<T1> pa = partition(a, b.cuts);
+ Piecewise<T2> pb = partition(b, a.cuts);
+ Piecewise<T2> ret = Piecewise<T2>();
+ assert(pa.size() == pb.size());
+ ret.segs.reserve(pa.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] * pb[i]);
+ return ret;
+}
+
+/**
+ * ...
+ * \return \f$ a \cdot b \f$
+ * \relates Piecewise
+ */
+template<typename T>
+inline Piecewise<T>& operator*=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a * b;
+ return a;
+}
+
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k);
+//TODO: replace divide(a,b,k) by divide(a,b,tol,k)?
+//TODO: atm, relative error is ~(tol/a)%. Find a way to make it independent of a.
+//Nota: the result is 'truncated' where b is smaller than 'zero': ~ a/max(b,zero).
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3);
+
+//Composition: functions called compose_* are pieces of compose that are factored out in pw.cpp.
+std::map<double,unsigned> compose_pullback(std::vector<double> const &cuts, SBasis const &g);
+int compose_findSegIdx(std::map<double,unsigned>::iterator const &cut,
+ std::map<double,unsigned>::iterator const &next,
+ std::vector<double> const &levels,
+ SBasis const &g);
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> compose(Piecewise<T> const &f, SBasis const &g){
+ /// \todo add concept check
+ Piecewise<T> result;
+ if (f.empty()) return result;
+ if (g.isZero()) return Piecewise<T>(f(0));
+ if (f.size()==1){
+ double t0 = f.cuts[0], width = f.cuts[1] - t0;
+ return (Piecewise<T>) compose(f.segs[0],compose(Linear(-t0 / width, (1-t0) / width), g));
+ }
+
+ //first check bounds...
+ Interval bs = *bounds_fast(g);
+ if (f.cuts.front() > bs.max() || bs.min() > f.cuts.back()){
+ int idx = (bs.max() < f.cuts[1]) ? 0 : f.cuts.size()-2;
+ double t0 = f.cuts[idx], width = f.cuts[idx+1] - t0;
+ return (Piecewise<T>) compose(f.segs[idx],compose(Linear(-t0 / width, (1-t0) / width), g));
+ }
+
+ std::vector<double> levels;//we can forget first and last cuts...
+ levels.insert(levels.begin(),f.cuts.begin()+1,f.cuts.end()-1);
+ //TODO: use a std::vector<pairs<double,unsigned> > instead of a map<double,unsigned>.
+ std::map<double,unsigned> cuts_pb = compose_pullback(levels,g);
+
+ //-- Compose each piece of g with the relevant seg of f.
+ result.cuts.push_back(0.);
+ std::map<double,unsigned>::iterator cut=cuts_pb.begin();
+ std::map<double,unsigned>::iterator next=cut; next++;
+ while(next!=cuts_pb.end()){
+ //assert(std::abs(int((*cut).second-(*next).second))<1);
+ //TODO: find a way to recover from this error? the root finder missed some root;
+ // the levels/variations of f might be too close/fast...
+ int idx = compose_findSegIdx(cut,next,levels,g);
+ double t0=(*cut).first;
+ double t1=(*next).first;
+
+ if (!are_near(t0,t1,EPSILON*EPSILON)) { // prevent adding cuts that are extremely close together and that may cause trouble with rounding e.g. when reversing the path
+ SBasis sub_g=compose(g, Linear(t0,t1));
+ sub_g=compose(Linear(-f.cuts[idx]/(f.cuts[idx+1]-f.cuts[idx]),
+ (1-f.cuts[idx])/(f.cuts[idx+1]-f.cuts[idx])),sub_g);
+ result.push(compose(f[idx],sub_g),t1);
+ }
+
+ cut++;
+ next++;
+ }
+ return(result);
+}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> compose(Piecewise<T> const &f, Piecewise<SBasis> const &g){
+/// \todo add concept check
+ Piecewise<T> result;
+ for(unsigned i = 0; i < g.segs.size(); i++){
+ Piecewise<T> fgi=compose(f, g.segs[i]);
+ fgi.setDomain(Interval(g.cuts[i], g.cuts[i+1]));
+ result.concat(fgi);
+ }
+ return result;
+}
+
+/*
+Piecewise<D2<SBasis> > compose(D2<SBasis2d> const &sb2d, Piecewise<D2<SBasis> > const &pwd2sb){
+/// \todo add concept check
+ Piecewise<D2<SBasis> > result;
+ result.push_cut(0.);
+ for(unsigned i = 0; i < pwd2sb.size(); i++){
+ result.push(compose_each(sb2d,pwd2sb[i]),i+1);
+ }
+ return result;
+}*/
+
+/** Compose an SBasis with the inverse of another.
+ * WARNING: It's up to the user to check that the second SBasis is indeed
+ * invertible (i.e. strictly increasing or decreasing).
+ * \return \f$ f \cdot g^{-1} \f$
+ * \relates Piecewise
+ */
+Piecewise<SBasis> pw_compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero);
+
+
+
+template <typename T>
+Piecewise<T> Piecewise<T>::operator()(SBasis f){return compose((*this),f);}
+template <typename T>
+Piecewise<T> Piecewise<T>::operator()(Piecewise<SBasis>f){return compose((*this),f);}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> integral(Piecewise<T> const &a) {
+ Piecewise<T> result;
+ result.segs.resize(a.segs.size());
+ result.cuts = a.cuts;
+ typename T::output_type c = a.segs[0].at0();
+ for(unsigned i = 0; i < a.segs.size(); i++){
+ result.segs[i] = integral(a.segs[i])*(a.cuts[i+1]-a.cuts[i]);
+ result.segs[i]+= c-result.segs[i].at0();
+ c = result.segs[i].at1();
+ }
+ return result;
+}
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> derivative(Piecewise<T> const &a) {
+ Piecewise<T> result;
+ result.segs.resize(a.segs.size());
+ result.cuts = a.cuts;
+ for(unsigned i = 0; i < a.segs.size(); i++){
+ result.segs[i] = derivative(a.segs[i])/(a.cuts[i+1]-a.cuts[i]);
+ }
+ return result;
+}
+
+std::vector<double> roots(Piecewise<SBasis> const &f);
+
+std::vector<std::vector<double> >multi_roots(Piecewise<SBasis> const &f, std::vector<double> const &values);
+
+//TODO: implement level_sets directly for pwsb instead of sb (and derive it fo sb).
+//It should be faster than the reverse as the algorithm may jump over full cut intervals.
+std::vector<Interval> level_set(Piecewise<SBasis> const &f, Interval const &level, double tol=1e-5);
+std::vector<Interval> level_set(Piecewise<SBasis> const &f, double v, double vtol, double tol=1e-5);
+//std::vector<Interval> level_sets(Piecewise<SBasis> const &f, std::vector<Interval> const &levels, double tol=1e-5);
+//std::vector<Interval> level_sets(Piecewise<SBasis> const &f, std::vector<double> &v, double vtol, double tol=1e-5);
+
+
+/**
+ * ...
+ * \return ...
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> reverse(Piecewise<T> const &f) {
+ Piecewise<T> ret = Piecewise<T>();
+ ret.reserve(f.size());
+ double start = f.cuts[0];
+ double end = f.cuts.back();
+ for (unsigned i = 0; i < f.cuts.size(); i++) {
+ double x = f.cuts[f.cuts.size() - 1 - i];
+ ret.push_cut(end - (x - start));
+ }
+ for (unsigned i = 0; i < f.segs.size(); i++)
+ ret.push_seg(reverse(f[f.segs.size() - i - 1]));
+ return ret;
+}
+
+/**
+ * Interpolates between a and b.
+ * \return a if t = 0, b if t = 1, or an interpolation between a and b for t in [0,1]
+ * \relates Piecewise
+ */
+template<typename T>
+Piecewise<T> lerp(double t, Piecewise<T> const &a, Piecewise<T> b) {
+ // Make sure both paths have the same number of segments and cuts at the same locations
+ b.setDomain(a.domain());
+ Piecewise<T> pA = partition(a, b.cuts);
+ Piecewise<T> pB = partition(b, a.cuts);
+
+ return (pA*(1-t) + pB*t);
+}
+
+}
+#endif //LIB2GEOM_SEEN_PIECEWISE_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/include/2geom/point.h b/include/2geom/point.h
new file mode 100644
index 0000000..3a29066
--- /dev/null
+++ b/include/2geom/point.h
@@ -0,0 +1,449 @@
+/** @file
+ * @brief Cartesian point / 2D vector and related operations
+ *//*
+ * Authors:
+ * Michael G. Sloan <mgsloan@gmail.com>
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2006-2009 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 LIB2GEOM_SEEN_POINT_H
+#define LIB2GEOM_SEEN_POINT_H
+
+#include <iostream>
+#include <iterator>
+#include <boost/operators.hpp>
+#include <2geom/forward.h>
+#include <2geom/coord.h>
+#include <2geom/int-point.h>
+#include <2geom/math-utils.h>
+#include <2geom/utils.h>
+
+namespace Geom {
+
+class Point
+ : boost::additive< Point
+ , boost::totally_ordered< Point
+ , boost::multiplicative< Point, Coord
+ , boost::multiplicative< Point
+ , boost::multiplicative< Point, IntPoint
+ , MultipliableNoncommutative< Point, Affine
+ , MultipliableNoncommutative< Point, Translate
+ , MultipliableNoncommutative< Point, Rotate
+ , MultipliableNoncommutative< Point, Scale
+ , MultipliableNoncommutative< Point, HShear
+ , MultipliableNoncommutative< Point, VShear
+ , MultipliableNoncommutative< Point, Zoom
+ > > > > > > > > > > > > // base class chaining, see documentation for Boost.Operator
+{
+ Coord _pt[2] = { 0, 0 };
+public:
+ using D1Value = Coord;
+ using D1Reference = Coord &;
+ using D1ConstReference = Coord const &;
+
+ /// @name Create points
+ /// @{
+ /** Construct a point at the origin. */
+ Point() = default;
+ /** Construct a point from its coordinates. */
+ Point(Coord x, Coord y)
+ : _pt{ x, y }
+ {}
+ /** Construct from integer point. */
+ Point(IntPoint const &p)
+ : Point(p[X], p[Y])
+ {}
+ /** @brief Construct a point from its polar coordinates.
+ * The angle is specified in radians, in the mathematical convention (increasing
+ * counter-clockwise from +X). */
+ static Point polar(Coord angle, Coord radius) {
+ Point ret(polar(angle));
+ ret *= radius;
+ return ret;
+ }
+ /** @brief Construct an unit vector from its angle.
+ * The angle is specified in radians, in the mathematical convention (increasing
+ * counter-clockwise from +X). */
+ static Point polar(Coord angle);
+ /// @}
+
+ /// @name Access the coordinates of a point
+ /// @{
+ Coord operator[](unsigned i) const { return _pt[i]; }
+ Coord &operator[](unsigned i) { return _pt[i]; }
+
+ Coord operator[](Dim2 d) const noexcept { return _pt[d]; }
+ Coord &operator[](Dim2 d) noexcept { return _pt[d]; }
+
+ Coord x() const noexcept { return _pt[X]; }
+ Coord &x() noexcept { return _pt[X]; }
+ Coord y() const noexcept { return _pt[Y]; }
+ Coord &y() noexcept { return _pt[Y]; }
+ /// @}
+
+ /// @name Vector operations
+ /// @{
+ /** @brief Compute the distance from origin.
+ * @return Length of the vector from origin to this point */
+ Coord length() const { return std::hypot(_pt[0], _pt[1]); }
+ void normalize();
+ Point normalized() const {
+ Point ret(*this);
+ ret.normalize();
+ return ret;
+ }
+
+ /** @brief Return a point like this point but rotated -90 degrees.
+ * If the y axis grows downwards and the x axis grows to the
+ * right, then this is 90 degrees counter-clockwise. */
+ Point ccw() const {
+ return Point(_pt[Y], -_pt[X]);
+ }
+
+ /** @brief Return a point like this point but rotated +90 degrees.
+ * If the y axis grows downwards and the x axis grows to the
+ * right, then this is 90 degrees clockwise. */
+ Point cw() const {
+ return Point(-_pt[Y], _pt[X]);
+ }
+ /// @}
+
+ /// @name Vector-like arithmetic operations
+ /// @{
+ Point operator-() const {
+ return Point(-_pt[X], -_pt[Y]);
+ }
+ Point &operator+=(Point const &o) {
+ _pt[X] += o._pt[X];
+ _pt[Y] += o._pt[Y];
+ return *this;
+ }
+ Point &operator-=(Point const &o) {
+ _pt[X] -= o._pt[X];
+ _pt[Y] -= o._pt[Y];
+ return *this;
+ }
+ Point &operator*=(Coord s) {
+ for (double & i : _pt) i *= s;
+ return *this;
+ }
+ Point &operator*=(Point const &o) {
+ _pt[X] *= o._pt[X];
+ _pt[Y] *= o._pt[Y];
+ return *this;
+ }
+ Point &operator*=(IntPoint const &o) {
+ _pt[X] *= o.x();
+ _pt[Y] *= o.y();
+ return *this;
+ }
+ Point &operator/=(Coord s) {
+ //TODO: s == 0?
+ for (double & i : _pt) i /= s;
+ return *this;
+ }
+ Point &operator/=(Point const &o) {
+ _pt[X] /= o._pt[X];
+ _pt[Y] /= o._pt[Y];
+ return *this;
+ }
+ Point &operator/=(IntPoint const &o) {
+ _pt[X] /= o.x();
+ _pt[Y] /= o.y();
+ return *this;
+ }
+ /// @}
+
+ /// @name Affine transformations
+ /// @{
+ Point &operator*=(Affine const &m);
+ // implemented in transforms.cpp
+ Point &operator*=(Translate const &t);
+ Point &operator*=(Scale const &s);
+ Point &operator*=(Rotate const &r);
+ Point &operator*=(HShear const &s);
+ Point &operator*=(VShear const &s);
+ Point &operator*=(Zoom const &z);
+ /// @}
+
+ /// @name Conversion to integer points
+ /// @{
+ /** @brief Round to nearest integer coordinates. */
+ IntPoint round() const {
+ IntPoint ret(::round(_pt[X]), ::round(_pt[Y]));
+ return ret;
+ }
+ /** @brief Round coordinates downwards. */
+ IntPoint floor() const {
+ IntPoint ret(::floor(_pt[X]), ::floor(_pt[Y]));
+ return ret;
+ }
+ /** @brief Round coordinates upwards. */
+ IntPoint ceil() const {
+ IntPoint ret(::ceil(_pt[X]), ::ceil(_pt[Y]));
+ return ret;
+ }
+ /// @}
+
+ /// @name Various utilities
+ /// @{
+ /** @brief Check whether both coordinates are finite. */
+ bool isFinite() const {
+ for (double i : _pt) {
+ if(!std::isfinite(i)) return false;
+ }
+ return true;
+ }
+ /** @brief Check whether both coordinates are zero. */
+ bool isZero() const {
+ return _pt[X] == 0 && _pt[Y] == 0;
+ }
+ /** @brief Check whether the length of the vector is close to 1. */
+ bool isNormalized(Coord eps=EPSILON) const {
+ return are_near(length(), 1.0, eps);
+ }
+ /** @brief Equality operator.
+ * This tests for exact identity (as opposed to are_near()). Note that due to numerical
+ * errors, this test might return false even if the points should be identical. */
+ bool operator==(const Point &in_pnt) const {
+ return (_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y]);
+ }
+ /** @brief Lexicographical ordering for points.
+ * Y coordinate is regarded as more significant. When sorting according to this
+ * ordering, the points will be sorted according to the Y coordinate, and within
+ * points with the same Y coordinate according to the X coordinate. */
+ bool operator<(const Point &p) const {
+ return _pt[Y] < p[Y] || (_pt[Y] == p[Y] && _pt[X] < p[X]);
+ }
+ /// @}
+
+ /** @brief Lexicographical ordering functor.
+ * @param d The dimension with higher significance */
+ template <Dim2 DIM> struct LexLess;
+ template <Dim2 DIM> struct LexGreater;
+ //template <Dim2 DIM, typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder;
+ /** @brief Lexicographical ordering functor with runtime dimension. */
+ struct LexLessRt {
+ LexLessRt(Dim2 d) : dim(d) {}
+ inline bool operator()(Point const &a, Point const &b) const;
+ private:
+ Dim2 dim;
+ };
+ struct LexGreaterRt {
+ LexGreaterRt(Dim2 d) : dim(d) {}
+ inline bool operator()(Point const &a, Point const &b) const;
+ private:
+ Dim2 dim;
+ };
+ //template <typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder
+};
+
+/** @brief Output operator for points.
+ * Prints out the coordinates.
+ * @relates Point */
+std::ostream &operator<<(std::ostream &out, const Geom::Point &p);
+
+template<> struct Point::LexLess<X> {
+ typedef std::less<Coord> Primary;
+ typedef std::less<Coord> Secondary;
+ typedef std::less<Coord> XOrder;
+ typedef std::less<Coord> YOrder;
+ bool operator()(Point const &a, Point const &b) const {
+ return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]);
+ }
+};
+template<> struct Point::LexLess<Y> {
+ typedef std::less<Coord> Primary;
+ typedef std::less<Coord> Secondary;
+ typedef std::less<Coord> XOrder;
+ typedef std::less<Coord> YOrder;
+ bool operator()(Point const &a, Point const &b) const {
+ return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]);
+ }
+};
+template<> struct Point::LexGreater<X> {
+ typedef std::greater<Coord> Primary;
+ typedef std::greater<Coord> Secondary;
+ typedef std::greater<Coord> XOrder;
+ typedef std::greater<Coord> YOrder;
+ bool operator()(Point const &a, Point const &b) const {
+ return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]);
+ }
+};
+template<> struct Point::LexGreater<Y> {
+ typedef std::greater<Coord> Primary;
+ typedef std::greater<Coord> Secondary;
+ typedef std::greater<Coord> XOrder;
+ typedef std::greater<Coord> YOrder;
+ bool operator()(Point const &a, Point const &b) const {
+ return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]);
+ }
+};
+inline bool Point::LexLessRt::operator()(Point const &a, Point const &b) const {
+ return dim ? Point::LexLess<Y>()(a, b) : Point::LexLess<X>()(a, b);
+}
+inline bool Point::LexGreaterRt::operator()(Point const &a, Point const &b) const {
+ return dim ? Point::LexGreater<Y>()(a, b) : Point::LexGreater<X>()(a, b);
+}
+
+/** @brief Compute the second (Euclidean) norm of @a p.
+ * This corresponds to the length of @a p. The result will not overflow even if
+ * \f$p_X^2 + p_Y^2\f$ is larger that the maximum value that can be stored
+ * in a <code>double</code>.
+ * @return \f$\sqrt{p_X^2 + p_Y^2}\f$
+ * @relates Point */
+inline Coord L2(Point const &p) {
+ return p.length();
+}
+
+/** @brief Compute the square of the Euclidean norm of @a p.
+ * Warning: this can overflow where L2 won't.
+ * @return \f$p_X^2 + p_Y^2\f$
+ * @relates Point */
+inline Coord L2sq(Point const &p) {
+ return p[0]*p[0] + p[1]*p[1];
+}
+
+/** @brief Returns p * Geom::rotate_degrees(90), but more efficient.
+ *
+ * Angle direction in 2Geom: If you use the traditional mathematics convention that y
+ * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If
+ * you take the common non-mathematical convention that y increases downwards, then positive angles
+ * are clockwise, as is common outside of mathematics.
+ *
+ * There is no function to rotate by -90 degrees: use -rot90(p) instead.
+ * @relates Point */
+inline Point rot90(Point const &p) {
+ return Point(-p[Y], p[X]);
+}
+
+/** @brief Linear interpolation between two points.
+ * @param t Time value
+ * @param a First point
+ * @param b Second point
+ * @return Point on a line between a and b. The ratio of its distance from a
+ * and the distance between a and b will be equal to t.
+ * @relates Point */
+inline Point lerp(Coord t, Point const &a, Point const &b) {
+ return (1 - t) * a + t * b;
+}
+
+/** @brief Return a point halfway between the specified ones.
+ * @relates Point */
+inline Point middle_point(Point const &p1, Point const &p2) {
+ return lerp(0.5, p1, p2);
+}
+
+/** @brief Compute the dot product of a and b.
+ * Dot product can be interpreted as a measure of how parallel the vectors are.
+ * For perpendicular vectors, it is zero. For parallel ones, its absolute value is highest,
+ * and the sign depends on whether they point in the same direction (+) or opposite ones (-).
+ * @return \f$a \cdot b = a_X b_X + a_Y b_Y\f$.
+ * @relates Point */
+inline Coord dot(Point const &a, Point const &b) {
+ return a[X] * b[X] + a[Y] * b[Y];
+}
+
+/** @brief Compute the 2D cross product.
+ * This is also known as "perp dot product". It will be zero for parallel vectors,
+ * and the absolute value will be highest for perpendicular vectors.
+ * @return \f$a \times b = a_X b_Y - a_Y b_X\f$.
+ * @relates Point*/
+inline Coord cross(Point const &a, Point const &b)
+{
+ // equivalent implementation:
+ // return dot(a, b.ccw());
+ return a[X] * b[Y] - a[Y] * b[X];
+}
+
+/// Compute the (Euclidean) distance between points.
+/// @relates Point
+inline Coord distance (Point const &a, Point const &b) {
+ return (a - b).length();
+}
+
+/// Compute the square of the distance between points.
+/// @relates Point
+inline Coord distanceSq (Point const &a, Point const &b) {
+ return L2sq(a - b);
+}
+
+//IMPL: NearConcept
+/// Test whether two points are no further apart than some threshold.
+/// @relates Point
+inline bool are_near(Point const &a, Point const &b, double eps = EPSILON) {
+ // do not use an unqualified calls to distance before the empty
+ // specialization of iterator_traits is defined - see end of file
+ return are_near((a - b).length(), 0, eps);
+}
+
+/// Test whether the relative distance between two points is less than some threshold.
+inline bool are_near_rel(Point const &a, Point const &b, double eps = EPSILON) {
+ return (a - b).length() <= eps * (a.length() + b.length()) / 2;
+}
+
+/// Test whether three points lie approximately on the same line.
+/// @relates Point
+inline bool are_collinear(Point const& p1, Point const& p2, Point const& p3,
+ double eps = EPSILON)
+{
+ return are_near( cross(p3, p2) - cross(p3, p1) + cross(p2, p1), 0, eps);
+}
+
+Point unit_vector(Point const &a);
+Coord L1(Point const &p);
+Coord LInfty(Point const &p);
+bool is_zero(Point const &p);
+bool is_unit_vector(Point const &p, Coord eps = EPSILON);
+double atan2(Point const &p);
+double angle_between(Point const &a, Point const &b);
+Point abs(Point const &b);
+Point constrain_angle(Point const &A, Point const &B, unsigned int n = 4, Geom::Point const &dir = Geom::Point(1,0));
+
+} // end namespace Geom
+
+// This is required to fix a bug in GCC 4.3.3 (and probably others) that causes the compiler
+// to try to instantiate the iterator_traits template and fail. Probably it thinks that Point
+// is an iterator and tries to use std::distance instead of Geom::distance.
+namespace std {
+template <> class iterator_traits<Geom::Point> {};
+}
+
+#endif // LIB2GEOM_SEEN_POINT_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/include/2geom/polynomial.h b/include/2geom/polynomial.h
new file mode 100644
index 0000000..640cab6
--- /dev/null
+++ b/include/2geom/polynomial.h
@@ -0,0 +1,264 @@
+/**
+ * \file
+ * \brief Polynomial in canonical (monomial) basis
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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.
+ */
+
+#ifndef LIB2GEOM_SEEN_POLY_H
+#define LIB2GEOM_SEEN_POLY_H
+#include <assert.h>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+#include <complex>
+#include <2geom/coord.h>
+#include <2geom/utils.h>
+
+namespace Geom {
+
+/** @brief Polynomial in canonical (monomial) basis.
+ * @ingroup Fragments */
+class Poly : public std::vector<double>{
+public:
+ // coeff; // sum x^i*coeff[i]
+
+ //unsigned size() const { return coeff.size();}
+ unsigned degree() const { return size()-1;}
+
+ //double operator[](const int i) const { return (*this)[i];}
+ //double& operator[](const int i) { return (*this)[i];}
+
+ Poly operator+(const Poly& p) const {
+ Poly result;
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back((*this)[i] + p[i]);
+ }
+ for(unsigned i = min_size; i < size(); i++)
+ result.push_back((*this)[i]);
+ for(unsigned i = min_size; i < p.size(); i++)
+ result.push_back(p[i]);
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator-(const Poly& p) const {
+ Poly result;
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back((*this)[i] - p[i]);
+ }
+ for(unsigned i = min_size; i < size(); i++)
+ result.push_back((*this)[i]);
+ for(unsigned i = min_size; i < p.size(); i++)
+ result.push_back(-p[i]);
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator-=(const Poly& p) {
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ resize(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ (*this)[i] -= p[i];
+ }
+ for(unsigned i = min_size; i < out_size; i++)
+ (*this)[i] = -p[i];
+ return *this;
+ }
+ Poly operator-(const double k) const {
+ Poly result;
+ const unsigned out_size = size();
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < out_size; i++) {
+ result.push_back((*this)[i]);
+ }
+ result[0] -= k;
+ return result;
+ }
+ Poly operator-() const {
+ Poly result;
+ result.resize(size());
+
+ for(unsigned i = 0; i < size(); i++) {
+ result[i] = -(*this)[i];
+ }
+ return result;
+ }
+ Poly operator*(const double p) const {
+ Poly result;
+ const unsigned out_size = size();
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < out_size; i++) {
+ result.push_back((*this)[i]*p);
+ }
+ assert(result.size() == out_size);
+ return result;
+ }
+ // equivalent to multiply by x^terms, negative terms are disallowed
+ Poly shifted(unsigned const terms) const {
+ Poly result;
+ size_type const out_size = size() + terms;
+ result.reserve(out_size);
+
+ result.resize(terms, 0.0);
+ result.insert(result.end(), this->begin(), this->end());
+
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator*(const Poly& p) const;
+
+ template <typename T>
+ T eval(T x) const {
+ T r = 0;
+ for(int k = size()-1; k >= 0; k--) {
+ r = r*x + T((*this)[k]);
+ }
+ return r;
+ }
+
+ template <typename T>
+ T operator()(T t) const { return (T)eval(t);}
+
+ void normalize();
+
+ void monicify();
+ Poly() {}
+ Poly(const Poly& p) : std::vector<double>(p) {}
+ Poly(const double a) {push_back(a);}
+
+public:
+ template <class T, class U>
+ void val_and_deriv(T x, U &pd) const {
+ pd[0] = back();
+ int nc = size() - 1;
+ int nd = pd.size() - 1;
+ for(unsigned j = 1; j < pd.size(); j++)
+ pd[j] = 0.0;
+ for(int i = nc -1; i >= 0; i--) {
+ int nnd = std::min(nd, nc-i);
+ for(int j = nnd; j >= 1; j--)
+ pd[j] = pd[j]*x + operator[](i);
+ pd[0] = pd[0]*x + operator[](i);
+ }
+ double cnst = 1;
+ for(int i = 2; i <= nd; i++) {
+ cnst *= i;
+ pd[i] *= cnst;
+ }
+ }
+
+ static Poly linear(double ax, double b) {
+ Poly p;
+ p.push_back(b);
+ p.push_back(ax);
+ return p;
+ }
+};
+
+inline Poly operator*(double a, Poly const & b) { return b * a;}
+
+Poly integral(Poly const & p);
+Poly derivative(Poly const & p);
+Poly divide_out_root(Poly const & p, double x);
+Poly compose(Poly const & a, Poly const & b);
+Poly divide(Poly const &a, Poly const &b, Poly &r);
+Poly gcd(Poly const &a, Poly const &b, const double tol=1e-10);
+
+/*** solve(Poly p)
+ * find all p.degree() roots of p.
+ * This function can take a long time with suitably crafted polynomials, but in practice it should be fast. Should we provide special forms for degree() <= 4?
+ */
+std::vector<std::complex<double> > solve(const Poly & p);
+
+#ifdef HAVE_GSL
+/*** solve_reals(Poly p)
+ * find all real solutions to Poly p.
+ * currently we just use solve and pick out the suitably real looking values, there may be a better algorithm.
+ */
+std::vector<double> solve_reals(const Poly & p);
+#endif
+double polish_root(Poly const & p, double guess, double tol);
+
+
+/** @brief Analytically solve quadratic equation.
+ * The equation is given in the standard form: ax^2 + bx + c = 0.
+ * Only real roots are returned. */
+std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c);
+
+/** @brief Analytically solve cubic equation.
+ * The equation is given in the standard form: ax^3 + bx^2 + cx + d = 0.
+ * Only real roots are returned. */
+std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d);
+
+
+inline std::ostream &operator<< (std::ostream &out_file, const Poly &in_poly) {
+ if(in_poly.size() == 0)
+ out_file << "0";
+ else {
+ for(int i = (int)in_poly.size()-1; i >= 0; --i) {
+ if(i == 1) {
+ out_file << "" << in_poly[i] << "*x";
+ out_file << " + ";
+ } else if(i) {
+ out_file << "" << in_poly[i] << "*x^" << i;
+ out_file << " + ";
+ } else
+ out_file << in_poly[i];
+
+ }
+ }
+ return out_file;
+}
+
+} // namespace Geom
+
+#endif //LIB2GEOM_SEEN_POLY_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/include/2geom/ray.h b/include/2geom/ray.h
new file mode 100644
index 0000000..4e60fd8
--- /dev/null
+++ b/include/2geom/ray.h
@@ -0,0 +1,192 @@
+/**
+ * \file
+ * \brief Infinite straight ray
+ *//*
+ * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_RAY_H
+#define LIB2GEOM_SEEN_RAY_H
+
+#include <vector>
+#include <2geom/point.h>
+#include <2geom/bezier-curve.h> // for LineSegment
+#include <2geom/exception.h>
+#include <2geom/math-utils.h>
+#include <2geom/transforms.h>
+#include <2geom/angle.h>
+
+namespace Geom
+{
+
+/**
+ * @brief Straight ray from a specific point to infinity.
+ *
+ * Rays are "half-lines" - they begin at some specific point and extend in a straight line
+ * to infinity.
+ *
+ * @ingroup Primitives
+ */
+class Ray {
+private:
+ Point _origin;
+ Point _vector;
+
+public:
+ Ray() : _origin(0,0), _vector(1,0) {}
+ Ray(Point const& origin, Coord angle)
+ : _origin(origin)
+ {
+ sincos(angle, _vector[Y], _vector[X]);
+ }
+ Ray(Point const& A, Point const& B) {
+ setPoints(A, B);
+ }
+ Point origin() const { return _origin; }
+ Point vector() const { return _vector; }
+ Point versor() const { return _vector.normalized(); }
+ void setOrigin(Point const &o) { _origin = o; }
+ void setVector(Point const& v) { _vector = v; }
+ Coord angle() const { return std::atan2(_vector[Y], _vector[X]); }
+ void setAngle(Coord a) { sincos(a, _vector[Y], _vector[X]); }
+ void setPoints(Point const &a, Point const &b) {
+ _origin = a;
+ _vector = b - a;
+ if (are_near(_vector, Point(0,0)) )
+ _vector = Point(0,0);
+ else
+ _vector.normalize();
+ }
+ bool isDegenerate() const {
+ return ( _vector[X] == 0 && _vector[Y] == 0 );
+ }
+ Point pointAt(Coord t) const {
+ return _origin + _vector * t;
+ }
+ Coord valueAt(Coord t, Dim2 d) const {
+ return _origin[d] + _vector[d] * t;
+ }
+ std::vector<Coord> roots(Coord v, Dim2 d) const {
+ std::vector<Coord> result;
+ if ( _vector[d] != 0 ) {
+ double t = (v - _origin[d]) / _vector[d];
+ if (t >= 0) result.push_back(t);
+ } else if (_vector[(d+1)%2] == v) {
+ THROW_INFINITESOLUTIONS();
+ }
+ return result;
+ }
+ Coord nearestTime(Point const& point) const {
+ if ( isDegenerate() ) return 0;
+ double t = dot(point - _origin, _vector);
+ if (t < 0) t = 0;
+ return t;
+ }
+ Ray reverse() const {
+ Ray result;
+ result.setOrigin(_origin);
+ result.setVector(-_vector);
+ return result;
+ }
+ Curve *portion(Coord f, Coord t) const {
+ return new LineSegment(pointAt(f), pointAt(t));
+ }
+ LineSegment segment(Coord f, Coord t) const {
+ return LineSegment(pointAt(f), pointAt(t));
+ }
+ Ray transformed(Affine const& m) const {
+ return Ray(_origin * m, (_origin + _vector) * m);
+ }
+}; // end class Ray
+
+inline
+double distance(Point const& _point, Ray const& _ray) {
+ double t = _ray.nearestTime(_point);
+ return ::Geom::distance(_point, _ray.pointAt(t));
+}
+
+inline
+bool are_near(Point const& _point, Ray const& _ray, double eps = EPSILON) {
+ return are_near(distance(_point, _ray), 0, eps);
+}
+
+inline
+bool are_same(Ray const& r1, Ray const& r2, double eps = EPSILON) {
+ return are_near(r1.vector(), r2.vector(), eps)
+ && are_near(r1.origin(), r2.origin(), eps);
+}
+
+// evaluate the angle between r1 and r2 rotating r1 in cw or ccw direction on r2
+// the returned value is an angle in the interval [0, 2PI[
+inline
+double angle_between(Ray const& r1, Ray const& r2, bool cw = true) {
+ double angle = angle_between(r1.vector(), r2.vector());
+ if (angle < 0) angle += 2*M_PI;
+ if (!cw) angle = 2*M_PI - angle;
+ return angle;
+}
+
+/**
+ * @brief Returns the angle bisector for the two given rays.
+ *
+ * @a r1 is rotated half the way to @a r2 in either clockwise or counter-clockwise direction.
+ *
+ * @pre Both passed rays must have the same origin.
+ *
+ * @remarks If the versors of both given rays point in the same direction, the direction of the
+ * angle bisector ray depends on the third parameter:
+ * - If @a cw is set to @c true, the returned ray will equal the passed rays @a r1 and @a r2.
+ * - If @a cw is set to @c false, the returned ray will go in the opposite direction.
+ *
+ * @throws RangeError if the given rays do not have the same origins
+ */
+inline
+Ray make_angle_bisector_ray(Ray const& r1, Ray const& r2, bool cw = true)
+{
+ if ( !are_near(r1.origin(), r2.origin()) )
+ {
+ THROW_RANGEERROR("passed rays do not have the same origin");
+ }
+
+ Ray bisector(r1.origin(), r1.origin() + r1.vector() * Rotate(angle_between(r1, r2) / 2.0));
+
+ return (cw ? bisector : bisector.reverse());
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_RAY_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/include/2geom/rect.h b/include/2geom/rect.h
new file mode 100644
index 0000000..5edfc95
--- /dev/null
+++ b/include/2geom/rect.h
@@ -0,0 +1,263 @@
+/**
+ * \file
+ * \brief Axis-aligned rectangle
+ *//*
+ * Authors:
+ * Michael Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2007-2011 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, output 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.
+ *
+ * Authors of original rect class:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ */
+
+#ifndef LIB2GEOM_SEEN_RECT_H
+#define LIB2GEOM_SEEN_RECT_H
+
+#include <2geom/affine.h>
+#include <2geom/interval.h>
+#include <2geom/int-rect.h>
+
+namespace Geom {
+
+/** Values for the <align> parameter of preserveAspectRatio.
+ * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */
+enum Align {
+ ALIGN_NONE,
+ ALIGN_XMIN_YMIN,
+ ALIGN_XMID_YMIN,
+ ALIGN_XMAX_YMIN,
+ ALIGN_XMIN_YMID,
+ ALIGN_XMID_YMID,
+ ALIGN_XMAX_YMID,
+ ALIGN_XMIN_YMAX,
+ ALIGN_XMID_YMAX,
+ ALIGN_XMAX_YMAX
+};
+
+/** Values for the <meetOrSlice> parameter of preserveAspectRatio.
+ * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */
+enum Expansion {
+ EXPANSION_MEET,
+ EXPANSION_SLICE
+};
+
+/// Convert an align specification to coordinate fractions.
+Point align_factors(Align align);
+
+/** @brief Structure that specifies placement of within a viewport.
+ * Use this to create transformations that preserve aspect. */
+struct Aspect {
+ Align align;
+ Expansion expansion;
+ bool deferred; ///< for SVG compatibility
+
+ Aspect(Align a = ALIGN_NONE, Expansion ex = EXPANSION_MEET)
+ : align(a), expansion(ex), deferred(false)
+ {}
+};
+
+/**
+ * @brief Axis aligned, non-empty rectangle.
+ * @ingroup Primitives
+ */
+class Rect
+ : public GenericRect<Coord>
+{
+ typedef GenericRect<Coord> Base;
+public:
+ /// @name Create rectangles.
+ /// @{
+ /** @brief Create a rectangle that contains only the point at (0,0). */
+ Rect() {}
+ /** @brief Create a rectangle from X and Y intervals. */
+ Rect(Interval const &a, Interval const &b) : Base(a,b) {}
+ /** @brief Create a rectangle from two points. */
+ Rect(Point const &a, Point const &b) : Base(a,b) {}
+ Rect(Coord x0, Coord y0, Coord x1, Coord y1) : Base(x0, y0, x1, y1) {}
+ Rect(Base const &b) : Base(b) {}
+ Rect(IntRect const &ir) : Base(ir.min(), ir.max()) {}
+ /// @}
+
+ /// @name Inspect dimensions.
+ /// @{
+ /** @brief Check whether the rectangle has zero area up to specified tolerance.
+ * @param eps Maximum value of the area to consider empty
+ * @return True if rectangle has an area smaller than tolerance, false otherwise */
+ bool hasZeroArea(Coord eps = EPSILON) const { return (area() <= eps); }
+ /// Check whether the rectangle has finite area
+ bool isFinite() const { return (*this)[X].isFinite() && (*this)[Y].isFinite(); }
+ /// Calculate the diameter of the smallest circle that would contain the rectangle.
+ Coord diameter() const { return distance(corner(0), corner(2)); }
+ /// @}
+
+ /// @name Test other rectangles and points for inclusion.
+ /// @{
+ /** @brief Check whether the interiors of the rectangles have any common points. */
+ bool interiorIntersects(Rect const &r) const {
+ return f[X].interiorIntersects(r[X]) && f[Y].interiorIntersects(r[Y]);
+ }
+ /** @brief Check whether the interior includes the given point. */
+ bool interiorContains(Point const &p) const {
+ return f[X].interiorContains(p[X]) && f[Y].interiorContains(p[Y]);
+ }
+ /** @brief Check whether the interior includes all points in the given rectangle.
+ * Interior of the rectangle is the entire rectangle without its borders. */
+ bool interiorContains(Rect const &r) const {
+ return f[X].interiorContains(r[X]) && f[Y].interiorContains(r[Y]);
+ }
+ inline bool interiorContains(OptRect const &r) const;
+ /// @}
+
+ /// @name Rounding to integer coordinates
+ /// @{
+ /** @brief Return the smallest integer rectangle which contains this one. */
+ IntRect roundOutwards() const {
+ IntRect ir(f[X].roundOutwards(), f[Y].roundOutwards());
+ return ir;
+ }
+ /** @brief Return the largest integer rectangle which is contained in this one. */
+ OptIntRect roundInwards() const {
+ OptIntRect oir(f[X].roundInwards(), f[Y].roundInwards());
+ return oir;
+ }
+ /// @}
+
+ /// @name SVG viewbox functionality.
+ /// @{
+ /** @brief Transform contents to viewport.
+ * Computes an affine that transforms the contents of this rectangle
+ * to the specified viewport. The aspect parameter specifies how to
+ * to the transformation (whether the aspect ratio of content
+ * should be kept and where it should be placed in the viewport). */
+ Affine transformTo(Rect const &viewport, Aspect const &aspect = Aspect()) const;
+ /// @}
+
+ /// @name Operators
+ /// @{
+ Rect &operator*=(Affine const &m);
+ bool operator==(IntRect const &ir) const {
+ return f[X] == ir[X] && f[Y] == ir[Y];
+ }
+ bool operator==(Rect const &other) const {
+ return Base::operator==(other);
+ }
+ /// @}
+};
+
+/**
+ * @brief Axis-aligned rectangle that can be empty.
+ * @ingroup Primitives
+ */
+class OptRect
+ : public GenericOptRect<Coord>
+{
+ typedef GenericOptRect<Coord> Base;
+public:
+ OptRect() : Base() {}
+ OptRect(Rect const &a) : Base(a) {}
+ OptRect(Point const &a, Point const &b) : Base(a, b) {}
+ OptRect(Coord x0, Coord y0, Coord x1, Coord y1) : Base(x0, y0, x1, y1) {}
+ OptRect(OptInterval const &x_int, OptInterval const &y_int) : Base(x_int, y_int) {}
+ OptRect(Base const &b) : Base(b) {}
+
+ OptRect(IntRect const &r) : Base(Rect(r)) {}
+ OptRect(OptIntRect const &r) : Base() {
+ if (r) *this = Rect(*r);
+ }
+
+ Affine transformTo(Rect const &viewport, Aspect const &aspect = Aspect()) {
+ Affine ret = Affine::identity();
+ if (empty()) return ret;
+ ret = (*this)->transformTo(viewport, aspect);
+ return ret;
+ }
+
+ bool operator==(OptRect const &other) const {
+ return Base::operator==(other);
+ }
+ bool operator==(Rect const &other) const {
+ return Base::operator==(other);
+ }
+};
+
+Coord distanceSq(Point const &p, Rect const &rect);
+Coord distance(Point const &p, Rect const &rect);
+/// Minimum square of distance to rectangle, or infinity if empty.
+Coord distanceSq(Point const &p, OptRect const &rect);
+/// Minimum distance to rectangle, or infinity if empty.
+Coord distance(Point const &p, OptRect const &rect);
+
+inline bool Rect::interiorContains(OptRect const &r) const {
+ return !r || interiorContains(static_cast<Rect const &>(*r));
+}
+
+// the functions below do not work when defined generically
+inline OptRect operator&(Rect const &a, Rect const &b) {
+ OptRect ret(a);
+ ret.intersectWith(b);
+ return ret;
+}
+inline OptRect intersect(Rect const &a, Rect const &b) {
+ return a & b;
+}
+inline OptRect intersect(OptRect const &a, OptRect const &b) {
+ return a & b;
+}
+inline Rect unify(Rect const &a, Rect const &b) {
+ return a | b;
+}
+inline OptRect unify(OptRect const &a, OptRect const &b) {
+ return a | b;
+}
+
+/** @brief Union a list of rectangles
+ * @deprecated Use OptRect::from_range instead */
+inline Rect union_list(std::vector<Rect> const &r) {
+ if(r.empty()) return Rect(Interval(0,0), Interval(0,0));
+ Rect ret = r[0];
+ for(unsigned i = 1; i < r.size(); i++)
+ ret.unionWith(r[i]);
+ return ret;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_RECT_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/include/2geom/sbasis-2d.h b/include/2geom/sbasis-2d.h
new file mode 100644
index 0000000..98dec67
--- /dev/null
+++ b/include/2geom/sbasis-2d.h
@@ -0,0 +1,371 @@
+/**
+ * \file
+ * \brief Obsolete 2D SBasis function class
+ *//*
+ * Authors:
+ * Nathan Hurst <?@?.?>
+ * JFBarraud <?@?.?>
+ *
+ * Copyright 2006-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 LIB2GEOM_SEEN_SBASIS_2D_H
+#define LIB2GEOM_SEEN_SBASIS_2D_H
+#include <vector>
+#include <cassert>
+#include <algorithm>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <iostream>
+
+namespace Geom{
+
+class Linear2d{
+public:
+ /*
+ u 0,1
+ v 0,2
+ */
+ double a[4];
+ Linear2d() {
+ a[0] = 0;
+ a[1] = 0;
+ a[2] = 0;
+ a[3] = 0;
+ }
+ Linear2d(double aa) {
+ for(double & i : a)
+ i = aa;
+ }
+ Linear2d(double a00, double a01, double a10, double a11)
+ {
+ a[0] = a00;
+ a[1] = a01;
+ a[2] = a10;
+ a[3] = a11;
+ }
+
+ double operator[](const int i) const {
+ assert(i >= 0);
+ assert(i < 4);
+ return a[i];
+ }
+ double& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < 4);
+ return a[i];
+ }
+ double apply(double u, double v) {
+ return (a[0]*(1-u)*(1-v) +
+ a[1]*u*(1-v) +
+ a[2]*(1-u)*v +
+ a[3]*u*v);
+ }
+};
+
+inline Linear extract_u(Linear2d const &a, double u) {
+ return Linear(a[0]*(1-u) +
+ a[1]*u,
+ a[2]*(1-u) +
+ a[3]*u);
+}
+inline Linear extract_v(Linear2d const &a, double v) {
+ return Linear(a[0]*(1-v) +
+ a[2]*v,
+ a[1]*(1-v) +
+ a[3]*v);
+}
+inline Linear2d operator-(Linear2d const &a) {
+ return Linear2d(-a.a[0], -a.a[1],
+ -a.a[2], -a.a[3]);
+}
+inline Linear2d operator+(Linear2d const & a, Linear2d const & b) {
+ return Linear2d(a[0] + b[0],
+ a[1] + b[1],
+ a[2] + b[2],
+ a[3] + b[3]);
+}
+inline Linear2d operator-(Linear2d const & a, Linear2d const & b) {
+ return Linear2d(a[0] - b[0],
+ a[1] - b[1],
+ a[2] - b[2],
+ a[3] - b[3]);
+}
+inline Linear2d& operator+=(Linear2d & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] += b[i];
+ return a;
+}
+inline Linear2d& operator-=(Linear2d & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] -= b[i];
+ return a;
+}
+inline Linear2d& operator*=(Linear2d & a, double b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] *= b;
+ return a;
+}
+
+inline bool operator==(Linear2d const & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ if(a[i] != b[i])
+ return false;
+ return true;
+}
+inline bool operator!=(Linear2d const & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ if(a[i] == b[i])
+ return false;
+ return true;
+}
+inline Linear2d operator*(double const a, Linear2d const & b) {
+ return Linear2d(a*b[0], a*b[1],
+ a*b[2], a*b[3]);
+}
+
+class SBasis2d : public std::vector<Linear2d>{
+public:
+ // vector in u,v
+ unsigned us, vs; // number of u terms, v terms
+ SBasis2d() {}
+ SBasis2d(Linear2d const & bo)
+ : us(1), vs(1) {
+ push_back(bo);
+ }
+ SBasis2d(SBasis2d const & a)
+ : std::vector<Linear2d>(a), us(a.us), vs(a.vs) {}
+
+ Linear2d& index(unsigned ui, unsigned vi) {
+ assert(ui < us);
+ assert(vi < vs);
+ return (*this)[ui + vi*us];
+ }
+
+ Linear2d index(unsigned ui, unsigned vi) const {
+ if(ui >= us)
+ return Linear2d(0);
+ if(vi >= vs)
+ return Linear2d(0);
+ return (*this)[ui + vi*us];
+ }
+
+ double apply(double u, double v) const {
+ double s = u*(1-u);
+ double t = v*(1-v);
+ Linear2d p;
+ double tk = 1;
+// XXX rewrite as horner
+ for(unsigned vi = 0; vi < vs; vi++) {
+ double sk = 1;
+ for(unsigned ui = 0; ui < us; ui++) {
+ p += (sk*tk)*index(ui, vi);
+ sk *= s;
+ }
+ tk *= t;
+ }
+ return p.apply(u,v);
+ }
+
+ void clear() {
+ fill(begin(), end(), Linear2d(0));
+ }
+
+ void normalize(); // remove extra zeros
+
+ double tail_error(unsigned tail) const;
+
+ void truncate(unsigned k);
+};
+
+inline SBasis2d operator-(const SBasis2d& p) {
+ SBasis2d result;
+ result.reserve(p.size());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+
+inline SBasis2d operator+(const SBasis2d& a, const SBasis2d& b) {
+ SBasis2d result;
+ result.us = std::max(a.us, b.us);
+ result.vs = std::max(a.vs, b.vs);
+ const unsigned out_size = result.us*result.vs;
+ result.resize(out_size);
+
+ for(unsigned vi = 0; vi < result.vs; vi++) {
+ for(unsigned ui = 0; ui < result.us; ui++) {
+ Linear2d bo;
+ if(ui < a.us && vi < a.vs)
+ bo += a.index(ui, vi);
+ if(ui < b.us && vi < b.vs)
+ bo += b.index(ui, vi);
+ result.index(ui, vi) = bo;
+ }
+ }
+ return result;
+}
+
+inline SBasis2d operator-(const SBasis2d& a, const SBasis2d& b) {
+ SBasis2d result;
+ result.us = std::max(a.us, b.us);
+ result.vs = std::max(a.vs, b.vs);
+ const unsigned out_size = result.us*result.vs;
+ result.resize(out_size);
+
+ for(unsigned vi = 0; vi < result.vs; vi++) {
+ for(unsigned ui = 0; ui < result.us; ui++) {
+ Linear2d bo;
+ if(ui < a.us && vi < a.vs)
+ bo += a.index(ui, vi);
+ if(ui < b.us && vi < b.vs)
+ bo -= b.index(ui, vi);
+ result.index(ui, vi) = bo;
+ }
+ }
+ return result;
+}
+
+
+inline SBasis2d& operator+=(SBasis2d& a, const Linear2d& b) {
+ if(a.size() < 1)
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+
+inline SBasis2d& operator-=(SBasis2d& a, const Linear2d& b) {
+ if(a.size() < 1)
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+inline SBasis2d& operator+=(SBasis2d& a, double b) {
+ if(a.size() < 1)
+ a.push_back(Linear2d(b));
+ else {
+ for(unsigned i = 0; i < 4; i++)
+ a[0] += double(b);
+ }
+ return a;
+}
+
+inline SBasis2d& operator-=(SBasis2d& a, double b) {
+ if(a.size() < 1)
+ a.push_back(Linear2d(-b));
+ else {
+ a[0] -= b;
+ }
+ return a;
+}
+
+inline SBasis2d& operator*=(SBasis2d& a, double b) {
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+
+inline SBasis2d& operator/=(SBasis2d& a, double b) {
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= (1./b);
+ return a;
+}
+
+SBasis2d operator*(double k, SBasis2d const &a);
+SBasis2d operator*(SBasis2d const &a, SBasis2d const &b);
+
+SBasis2d shift(SBasis2d const &a, int sh);
+
+SBasis2d shift(Linear2d const &a, int sh);
+
+SBasis2d truncate(SBasis2d const &a, unsigned terms);
+
+SBasis2d multiply(SBasis2d const &a, SBasis2d const &b);
+
+SBasis2d integral(SBasis2d const &c);
+
+SBasis2d partial_derivative(SBasis2d const &a, int dim);
+
+SBasis2d sqrt(SBasis2d const &a, int k);
+
+// return a kth order approx to 1/a)
+SBasis2d reciprocal(Linear2d const &a, int k);
+
+SBasis2d divide(SBasis2d const &a, SBasis2d const &b, int k);
+
+// a(b(t))
+SBasis2d compose(SBasis2d const &a, SBasis2d const &b);
+SBasis2d compose(SBasis2d const &a, SBasis2d const &b, unsigned k);
+SBasis2d inverse(SBasis2d const &a, int k);
+
+// these two should probably be replaced with compose
+SBasis extract_u(SBasis2d const &a, double u);
+SBasis extract_v(SBasis2d const &a, double v);
+
+SBasis compose(Linear2d const &a, D2<SBasis> const &p);
+
+SBasis compose(SBasis2d const &fg, D2<SBasis> const &p);
+
+D2<SBasis> compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p);
+
+inline std::ostream &operator<< (std::ostream &out_file, const Linear2d &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}, ";
+ out_file << "{" << bo[2] << ", " << bo[3] << "}";
+ return out_file;
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const SBasis2d & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+D2<SBasis>
+sb2dsolve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B, unsigned degmax=2);
+
+D2<SBasis>
+sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B);
+
+} // end namespace Geom
+
+#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/include/2geom/sbasis-curve.h b/include/2geom/sbasis-curve.h
new file mode 100644
index 0000000..93d6772
--- /dev/null
+++ b/include/2geom/sbasis-curve.h
@@ -0,0 +1,160 @@
+/**
+ * \file
+ * \brief Symmetric power basis curve
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-2009 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 LIB2GEOM_SEEN_SBASIS_CURVE_H
+#define LIB2GEOM_SEEN_SBASIS_CURVE_H
+
+#include <2geom/curve.h>
+#include <2geom/exception.h>
+#include <2geom/nearest-time.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/transforms.h>
+
+namespace Geom
+{
+
+/** @brief Symmetric power basis curve.
+ *
+ * Symmetric power basis (S-basis for short) polynomials are a versatile numeric
+ * representation of arbitrary continuous curves. They are the main representation of curves
+ * in 2Geom.
+ *
+ * S-basis is defined for odd degrees and composed of the following polynomials:
+ * \f{align*}{
+ P_k^0(t) &= t^k (1-t)^{k+1} \\
+ P_k^1(t) &= t^{k+1} (1-t)^k \f}
+ * This can be understood more easily with the help of the chart below. Each square
+ * represents a product of a specific number of \f$t\f$ and \f$(1-t)\f$ terms. Red dots
+ * are the canonical (monomial) basis, the green dots are the Bezier basis, and the blue
+ * dots are the S-basis, all of them of degree 7.
+ *
+ * @image html sbasis.png "Illustration of the monomial, Bezier and symmetric power bases"
+ *
+ * The S-Basis has several important properties:
+ * - S-basis polynomials are closed under multiplication.
+ * - Evaluation is fast, using a modified Horner scheme.
+ * - Degree change is as trivial as in the monomial basis. To elevate, just add extra
+ * zero coefficients. To reduce the degree, truncate the terms in the highest powers.
+ * Compare this with Bezier curves, where degree change is complicated.
+ * - Conversion between S-basis and Bezier basis is numerically stable.
+ *
+ * More in-depth information can be found in the following paper:
+ * J Sanchez-Reyes, "The symmetric analogue of the polynomial power basis".
+ * ACM Transactions on Graphics, Vol. 16, No. 3, July 1997, pages 319--357.
+ * http://portal.acm.org/citation.cfm?id=256162
+ *
+ * @ingroup Curves
+ */
+class SBasisCurve : public Curve {
+private:
+ D2<SBasis> inner;
+
+public:
+ explicit SBasisCurve(D2<SBasis> const &sb) : inner(sb) {}
+ explicit SBasisCurve(Curve const &other) : inner(other.toSBasis()) {}
+
+ Curve *duplicate() const override { return new SBasisCurve(*this); }
+ Point initialPoint() const override { return inner.at0(); }
+ Point finalPoint() const override { return inner.at1(); }
+ bool isDegenerate() const override { return inner.isConstant(0); }
+ bool isLineSegment() const override { return inner[X].size() == 1; }
+ Point pointAt(Coord t) const override { return inner.valueAt(t); }
+ std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const override {
+ return inner.valueAndDerivatives(t, n);
+ }
+ Coord valueAt(Coord t, Dim2 d) const override { return inner[d].valueAt(t); }
+ void setInitial(Point const &v) override {
+ for (unsigned d = 0; d < 2; d++) { inner[d][0][0] = v[d]; }
+ }
+ void setFinal(Point const &v) override {
+ for (unsigned d = 0; d < 2; d++) { inner[d][0][1] = v[d]; }
+ }
+ Rect boundsFast() const override { return *bounds_fast(inner); }
+ Rect boundsExact() const override { return *bounds_exact(inner); }
+ void expandToTransformed(Rect &bbox, Affine const &transform) const override {
+ bbox |= bounds_exact(inner * transform);
+ }
+ OptRect boundsLocal(OptInterval const &i, unsigned deg) const override {
+ return bounds_local(inner, i, deg);
+ }
+ std::vector<Coord> roots(Coord v, Dim2 d) const override { return Geom::roots(inner[d] - v); }
+ Coord nearestTime( Point const& p, Coord from = 0, Coord to = 1 ) const override {
+ return nearest_time(p, inner, from, to);
+ }
+ std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0,
+ Coord to = 1 ) const override
+ {
+ return all_nearest_times(p, inner, from, to);
+ }
+ Coord length(Coord tolerance) const override { return ::Geom::length(inner, tolerance); }
+ Curve *portion(Coord f, Coord t) const override {
+ return new SBasisCurve(Geom::portion(inner, f, t));
+ }
+
+ using Curve::operator*=;
+ void operator*=(Affine const &m) override { inner = inner * m; }
+
+ Curve *derivative() const override {
+ return new SBasisCurve(Geom::derivative(inner));
+ }
+ D2<SBasis> toSBasis() const override { return inner; }
+ bool operator==(Curve const &c) const override {
+ SBasisCurve const *other = dynamic_cast<SBasisCurve const *>(&c);
+ if (!other) return false;
+ return inner == other->inner;
+ }
+ bool isNear(Curve const &/*c*/, Coord /*eps*/) const override {
+ THROW_NOTIMPLEMENTED();
+ return false;
+ }
+ int degreesOfFreedom() const override {
+ return inner[0].degreesOfFreedom() + inner[1].degreesOfFreedom();
+ }
+};
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_SBASIS_CURVE_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/include/2geom/sbasis-geometric.h b/include/2geom/sbasis-geometric.h
new file mode 100644
index 0000000..7f1e8aa
--- /dev/null
+++ b/include/2geom/sbasis-geometric.h
@@ -0,0 +1,146 @@
+/**
+ * \file
+ * \brief two-dimensional geometric operators.
+ *
+ * These operators are built on a more 'polynomially robust'
+ * transformation to map a function that takes a [0,1] parameter to a
+ * 2d vector into a function that takes the same [0,1] parameter to a
+ * unit vector with the same direction.
+ *
+ * Rather that using (X/sqrt(X))(t) which involves two unstable
+ * operations, sqrt and divide, this approach forms a curve directly
+ * from the various tangent directions at each end (angular jet). As
+ * a result, the final path has a convergence behaviour derived from
+ * that of the sin and cos series. -- njh
+ *//*
+ * Copyright 2007, JFBarraud
+ * Copyright 2007, njh
+ *
+ * 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 LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H
+#define LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H
+
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+#include <vector>
+
+namespace Geom {
+
+Piecewise<D2<SBasis> >
+cutAtRoots(Piecewise<D2<SBasis> > const &M, double tol=1e-4);
+
+Piecewise<SBasis>
+atan2(D2<SBasis> const &vect,
+ double tol=.01, unsigned order=3);
+
+Piecewise<SBasis>
+atan2(Piecewise<D2<SBasis> >const &vect,
+ double tol=.01, unsigned order=3);
+
+D2<Piecewise<SBasis> >
+tan2(SBasis const &angle,
+ double tol=.01, unsigned order=3);
+
+D2<Piecewise<SBasis> >
+tan2(Piecewise<SBasis> const &angle,
+ double tol=.01, unsigned order=3);
+
+Piecewise<D2<SBasis> >
+unitVector(D2<SBasis> const &vect,
+ double tol=.01, unsigned order=3);
+Piecewise<D2<SBasis> >
+unitVector(Piecewise<D2<SBasis> > const &vect,
+ double tol=.01, unsigned order=3);
+
+// Piecewise<D2<SBasis> >
+// uniform_speed(D2<SBasis> const M,
+// double tol=.1);
+
+Piecewise<SBasis> curvature( D2<SBasis> const &M, double tol=.01);
+Piecewise<SBasis> curvature(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+Piecewise<SBasis> arcLengthSb( D2<SBasis> const &M, double tol=.01);
+Piecewise<SBasis> arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+double length( D2<SBasis> const &M, double tol=.01);
+double length(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+void length_integrating(D2<SBasis> const &B, double &result, double &abs_error, double tol);
+
+Piecewise<D2<SBasis> >
+arc_length_parametrization(D2<SBasis> const &M,
+ unsigned order=3,
+ double tol=.01);
+Piecewise<D2<SBasis> >
+arc_length_parametrization(Piecewise<D2<SBasis> > const &M,
+ unsigned order=3,
+ double tol=.01);
+
+
+unsigned centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area);
+
+std::vector<D2<SBasis> >
+cubics_fitting_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ double d2M0xdM0, double d2M1xdM1,
+ int insist_on_speed_signs = 1,
+ double epsilon = 1e-5);
+
+std::vector<D2<SBasis> >
+cubics_fitting_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ Point const &d2M0, Point const &d2M1,
+ int insist_on_speed_signs = 1,
+ double epsilon = 1e-5);
+
+std::vector<D2<SBasis> >
+cubics_with_prescribed_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ double k0, double k1,
+ int insist_on_speed_signs = 1,
+ double error = 1e-5);
+
+
+std::vector<double> find_tangents(Point P, D2<SBasis> const &A);
+std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A);
+std::vector<double> find_normals(Point P, D2<SBasis> const &A);
+std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A);
+
+};
+
+#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/include/2geom/sbasis-math.h b/include/2geom/sbasis-math.h
new file mode 100644
index 0000000..e191dae
--- /dev/null
+++ b/include/2geom/sbasis-math.h
@@ -0,0 +1,99 @@
+/** @file
+ * @brief some std functions to work with (pw)s-basis
+ *//*
+ * Authors:
+ * Jean-Francois Barraud
+ *
+ * Copyright (C) 2006-2007 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.
+ */
+
+//this a first try to define sqrt, cos, sin, etc...
+//TODO: define a truncated compose(sb,sb, order) and extend it to pw<sb>.
+//TODO: in all these functions, compute 'order' according to 'tol'.
+//TODO: use template to define the pw version automatically from the sb version?
+
+#ifndef LIB2GEOM_SEEN_SBASIS_MATH_H
+#define LIB2GEOM_SEEN_SBASIS_MATH_H
+
+
+#include <2geom/sbasis.h>
+#include <2geom/piecewise.h>
+#include <2geom/d2.h>
+
+namespace Geom{
+//-|x|---------------------------------------------------------------
+Piecewise<SBasis> abs( SBasis const &f);
+Piecewise<SBasis> abs(Piecewise<SBasis>const &f);
+
+//- max(f,g), min(f,g) ----------------------------------------------
+Piecewise<SBasis> max( SBasis const &f, SBasis const &g);
+Piecewise<SBasis> max(Piecewise<SBasis> const &f, SBasis const &g);
+Piecewise<SBasis> max( SBasis const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> max(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> min( SBasis const &f, SBasis const &g);
+Piecewise<SBasis> min(Piecewise<SBasis> const &f, SBasis const &g);
+Piecewise<SBasis> min( SBasis const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> min(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g);
+
+//-sign(x)---------------------------------------------------------------
+Piecewise<SBasis> signSb( SBasis const &f);
+Piecewise<SBasis> signSb(Piecewise<SBasis>const &f);
+
+//-Sqrt---------------------------------------------------------------
+Piecewise<SBasis> sqrt( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sqrt(Piecewise<SBasis>const &f, double tol=1e-3, int order=3);
+
+//-sin/cos--------------------------------------------------------------
+Piecewise<SBasis> cos( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sin( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol=1e-3, int order=3);
+//-Log---------------------------------------------------------------
+Piecewise<SBasis> log( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> log(Piecewise<SBasis>const &f, double tol=1e-3, int order=3);
+
+//--1/x------------------------------------------------------------
+//TODO: change this...
+Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol=1e-3);
+Piecewise<SBasis> reciprocal( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> reciprocal(Piecewise<SBasis>const &f, double tol=1e-3, int order=3);
+
+//--interpolate------------------------------------------------------------
+Piecewise<SBasis> interpolate( std::vector<double> times, std::vector<double> values, unsigned smoothness = 1);
+}
+
+#endif //SEEN_GEOM_PW_SB_CALCULUS_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/include/2geom/sbasis-poly.h b/include/2geom/sbasis-poly.h
new file mode 100644
index 0000000..d18bc36
--- /dev/null
+++ b/include/2geom/sbasis-poly.h
@@ -0,0 +1,56 @@
+/** @file
+ * @brief Conversion between SBasis and Poly. Not recommended for general use due to instability.
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_SBASIS_POLY_H
+#define LIB2GEOM_SEEN_SBASIS_POLY_H
+
+#include <2geom/polynomial.h>
+#include <2geom/sbasis.h>
+
+namespace Geom{
+
+SBasis poly_to_sbasis(Poly const & p);
+Poly sbasis_to_poly(SBasis const & s);
+
+};
+
+#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/include/2geom/sbasis-to-bezier.h b/include/2geom/sbasis-to-bezier.h
new file mode 100644
index 0000000..eadb47b
--- /dev/null
+++ b/include/2geom/sbasis-to-bezier.h
@@ -0,0 +1,87 @@
+/**
+ * \file
+ * \brief Conversion between SBasis and Bezier basis polynomials
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H
+#define LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H
+
+#include <2geom/d2.h>
+#include <2geom/pathvector.h>
+
+#include <vector>
+
+namespace Geom {
+
+class PathBuilder;
+
+void sbasis_to_bezier (Bezier &bz, SBasis const &sb, size_t sz = 0);
+void sbasis_to_bezier (D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz = 0);
+void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz = 0);
+void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb);
+void bezier_to_sbasis (SBasis & sb, Bezier const& bz);
+void bezier_to_sbasis (D2<SBasis> & sb, std::vector<Point> const& bz);
+void build_from_sbasis(PathBuilder &pb, D2<SBasis> const &B, double tol, bool only_cubicbeziers);
+
+#if 0
+// this produces a degree k bezier from a degree k sbasis
+Bezier
+sbasis_to_bezier(SBasis const &B, unsigned q = 0);
+
+// inverse
+SBasis bezier_to_sbasis(Bezier const &B);
+
+
+std::vector<Geom::Point>
+sbasis_to_bezier(D2<SBasis> const &B, unsigned q = 0);
+#endif
+
+
+PathVector path_from_piecewise(Piecewise<D2<SBasis> > const &B, double tol, bool only_cubicbeziers = false);
+
+Path path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers = false);
+inline Path cubicbezierpath_from_sbasis(D2<SBasis> const &B, double tol)
+ { return path_from_sbasis(B, tol, true); }
+
+} // end namespace Geom
+
+#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/include/2geom/sbasis.h b/include/2geom/sbasis.h
new file mode 100644
index 0000000..5cb0e93
--- /dev/null
+++ b/include/2geom/sbasis.h
@@ -0,0 +1,530 @@
+/** @file
+ * @brief Polynomial in symmetric power basis (S-basis)
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 LIB2GEOM_SEEN_SBASIS_H
+#define LIB2GEOM_SEEN_SBASIS_H
+#include <cassert>
+#include <iostream>
+#include <utility>
+#include <vector>
+
+#include <2geom/linear.h>
+#include <2geom/interval.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+//#define USE_SBASISN 1
+
+
+#if defined(USE_SBASIS_OF)
+
+#include "sbasis-of.h"
+
+#elif defined(USE_SBASISN)
+
+#include "sbasisN.h"
+namespace Geom{
+
+/*** An empty SBasis is identically 0. */
+class SBasis : public SBasisN<1>;
+
+};
+#else
+
+namespace Geom {
+
+/**
+ * @brief Polynomial in symmetric power basis
+ * @ingroup Fragments
+ */
+class SBasis {
+ std::vector<Linear> d;
+ void push_back(Linear const&l) { d.push_back(l); }
+
+public:
+ // As part of our migration away from SBasis isa vector we provide this minimal set of vector interface methods.
+ size_t size() const {return d.size();}
+ typedef std::vector<Linear>::iterator iterator;
+ typedef std::vector<Linear>::const_iterator const_iterator;
+ Linear operator[](unsigned i) const {
+ return d[i];
+ }
+ Linear& operator[](unsigned i) { return d.at(i); }
+ const_iterator begin() const { return d.begin();}
+ const_iterator end() const { return d.end();}
+ iterator begin() { return d.begin();}
+ iterator end() { return d.end();}
+ bool empty() const { return d.size() == 1 && d[0][0] == 0 && d[0][1] == 0; }
+ Linear &back() {return d.back();}
+ Linear const &back() const {return d.back();}
+ void pop_back() {
+ if (d.size() > 1) {
+ d.pop_back();
+ } else {
+ d[0][0] = 0;
+ d[0][1] = 0;
+ }
+ }
+ void resize(unsigned n) { d.resize(std::max<unsigned>(n, 1));}
+ void resize(unsigned n, Linear const& l) { d.resize(std::max<unsigned>(n, 1), l);}
+ void reserve(unsigned n) { d.reserve(n);}
+ void clear() {
+ d.resize(1);
+ d[0][0] = 0;
+ d[0][1] = 0;
+ }
+ void insert(iterator before, const_iterator src_begin, const_iterator src_end) { d.insert(before, src_begin, src_end);}
+ Linear& at(unsigned i) { return d.at(i);}
+ //void insert(Linear* before, int& n, Linear const &l) { d.insert(std::vector<Linear>::iterator(before), n, l);}
+ bool operator==(SBasis const&B) const { return d == B.d;}
+ bool operator!=(SBasis const&B) const { return d != B.d;}
+
+ SBasis()
+ : d(1, Linear(0, 0))
+ {}
+ explicit SBasis(double a)
+ : d(1, Linear(a, a))
+ {}
+ explicit SBasis(double a, double b)
+ : d(1, Linear(a, b))
+ {}
+ SBasis(SBasis const &a)
+ : d(a.d)
+ {}
+ SBasis(std::vector<Linear> ls)
+ : d(std::move(ls))
+ {}
+ SBasis(Linear const &bo)
+ : d(1, bo)
+ {}
+ SBasis(Linear* bo)
+ : d(1, bo ? *bo : Linear(0, 0))
+ {}
+ explicit SBasis(size_t n, Linear const&l) : d(n, l) {}
+
+ SBasis(Coord c0, Coord c1, Coord c2, Coord c3)
+ : d(2)
+ {
+ d[0][0] = c0;
+ d[1][0] = c1;
+ d[1][1] = c2;
+ d[0][1] = c3;
+ }
+ SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5)
+ : d(3)
+ {
+ d[0][0] = c0;
+ d[1][0] = c1;
+ d[2][0] = c2;
+ d[2][1] = c3;
+ d[1][1] = c4;
+ d[0][1] = c5;
+ }
+ SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5,
+ Coord c6, Coord c7)
+ : d(4)
+ {
+ d[0][0] = c0;
+ d[1][0] = c1;
+ d[2][0] = c2;
+ d[3][0] = c3;
+ d[3][1] = c4;
+ d[2][1] = c5;
+ d[1][1] = c6;
+ d[0][1] = c7;
+ }
+ SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5,
+ Coord c6, Coord c7, Coord c8, Coord c9)
+ : d(5)
+ {
+ d[0][0] = c0;
+ d[1][0] = c1;
+ d[2][0] = c2;
+ d[3][0] = c3;
+ d[4][0] = c4;
+ d[4][1] = c5;
+ d[3][1] = c6;
+ d[2][1] = c7;
+ d[1][1] = c8;
+ d[0][1] = c9;
+ }
+
+ // construct from a sequence of coefficients
+ template <typename Iter>
+ SBasis(Iter first, Iter last) {
+ assert(std::distance(first, last) % 2 == 0);
+ assert(std::distance(first, last) >= 2);
+ for (; first != last; ++first) {
+ --last;
+ push_back(Linear(*first, *last));
+ }
+ }
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+ inline bool isZero(double eps=EPSILON) const {
+ assert(size() > 0);
+ for(unsigned i = 0; i < size(); i++) {
+ if(!(*this)[i].isZero(eps)) return false;
+ }
+ return true;
+ }
+ inline bool isConstant(double eps=EPSILON) const {
+ assert(size() > 0);
+ if(!(*this)[0].isConstant(eps)) return false;
+ for (unsigned i = 1; i < size(); i++) {
+ if(!(*this)[i].isZero(eps)) return false;
+ }
+ return true;
+ }
+
+ bool isFinite() const;
+ inline Coord at0() const { return (*this)[0][0]; }
+ inline Coord &at0() { return (*this)[0][0]; }
+ inline Coord at1() const { return (*this)[0][1]; }
+ inline Coord &at1() { return (*this)[0][1]; }
+
+ int degreesOfFreedom() const { return size()*2;}
+
+ double valueAt(double t) const {
+ assert(size() > 0);
+ double s = t*(1-t);
+ double p0 = 0, p1 = 0;
+ for(unsigned k = size(); k > 0; k--) {
+ const Linear &lin = (*this)[k-1];
+ p0 = p0*s + lin[0];
+ p1 = p1*s + lin[1];
+ }
+ return (1-t)*p0 + t*p1;
+ }
+ //double valueAndDerivative(double t, double &der) const {
+ //}
+ double operator()(double t) const {
+ return valueAt(t);
+ }
+
+ std::vector<double> valueAndDerivatives(double t, unsigned n) const;
+
+ SBasis toSBasis() const { return SBasis(*this); }
+
+ double tailError(unsigned tail) const;
+
+// compute f(g)
+ SBasis operator()(SBasis const & g) const;
+
+//MUTATOR PRISON
+ //remove extra zeros
+ void normalize() {
+ while(size() > 1 && back().isZero(0))
+ pop_back();
+ }
+
+ void truncate(unsigned k) { if(k < size()) resize(std::max<size_t>(k, 1)); }
+private:
+ void derive(); // in place version
+};
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+inline SBasis Linear::toSBasis() const { return SBasis(*this); }
+
+//implemented in sbasis-roots.cpp
+OptInterval bounds_exact(SBasis const &a);
+OptInterval bounds_fast(SBasis const &a, int order = 0);
+OptInterval bounds_local(SBasis const &a, const OptInterval &t, int order = 0);
+
+/** Returns a function which reverses the domain of a.
+ \param a sbasis function
+ \relates SBasis
+
+useful for reversing a parameteric curve.
+*/
+inline SBasis reverse(SBasis const &a) {
+ SBasis result(a.size(), Linear());
+
+ for(unsigned k = 0; k < a.size(); k++)
+ result[k] = reverse(a[k]);
+ return result;
+}
+
+//IMPL: ScalableConcept
+inline SBasis operator-(const SBasis& p) {
+ if(p.isZero()) return SBasis();
+ SBasis result(p.size(), Linear());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result[i] = -p[i];
+ }
+ return result;
+}
+SBasis operator*(SBasis const &a, double k);
+inline SBasis operator*(double k, SBasis const &a) { return a*k; }
+inline SBasis operator/(SBasis const &a, double k) { return a*(1./k); }
+SBasis& operator*=(SBasis& a, double b);
+inline SBasis& operator/=(SBasis& a, double b) { return (a*=(1./b)); }
+
+//IMPL: AddableConcept
+SBasis operator+(const SBasis& a, const SBasis& b);
+SBasis operator-(const SBasis& a, const SBasis& b);
+SBasis& operator+=(SBasis& a, const SBasis& b);
+SBasis& operator-=(SBasis& a, const SBasis& b);
+
+//TODO: remove?
+/*inline SBasis operator+(const SBasis & a, Linear const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasis result(a);
+ result[0] += b;
+ return result;
+}
+inline SBasis operator-(const SBasis & a, Linear const & b) {
+ if(b.isZero()) return a;
+ SBasis result(a);
+ result[0] -= b;
+ return result;
+}
+inline SBasis& operator+=(SBasis& a, const Linear& b) {
+ if(a.isZero())
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+inline SBasis& operator-=(SBasis& a, const Linear& b) {
+ if(a.isZero())
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+ }*/
+
+//IMPL: OffsetableConcept
+inline SBasis operator+(const SBasis & a, double b) {
+ if(a.isZero()) return Linear(b, b);
+ SBasis result(a);
+ result[0] += b;
+ return result;
+}
+inline SBasis operator-(const SBasis & a, double b) {
+ if(a.isZero()) return Linear(-b, -b);
+ SBasis result(a);
+ result[0] -= b;
+ return result;
+}
+inline SBasis& operator+=(SBasis& a, double b) {
+ if(a.isZero())
+ a = SBasis(Linear(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+inline SBasis& operator-=(SBasis& a, double b) {
+ if(a.isZero())
+ a = SBasis(Linear(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+SBasis shift(SBasis const &a, int sh);
+SBasis shift(Linear const &a, int sh);
+
+inline SBasis truncate(SBasis const &a, unsigned terms) {
+ SBasis c;
+ c.insert(c.begin(), a.begin(), a.begin() + std::min(terms, (unsigned)a.size()));
+ return c;
+}
+
+SBasis multiply(SBasis const &a, SBasis const &b);
+// This performs a multiply and accumulate operation in about the same time as multiply. return a*b + c
+SBasis multiply_add(SBasis const &a, SBasis const &b, SBasis c);
+
+SBasis integral(SBasis const &c);
+SBasis derivative(SBasis const &a);
+
+SBasis sqrt(SBasis const &a, int k);
+
+// return a kth order approx to 1/a)
+SBasis reciprocal(Linear const &a, int k);
+SBasis divide(SBasis const &a, SBasis const &b, int k);
+
+inline SBasis operator*(SBasis const & a, SBasis const & b) {
+ return multiply(a, b);
+}
+
+inline SBasis& operator*=(SBasis& a, SBasis const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+/** Returns the degree of the first non zero coefficient.
+ \param a sbasis function
+ \param tol largest abs val considered 0
+ \return first non zero coefficient
+ \relates SBasis
+*/
+inline unsigned
+valuation(SBasis const &a, double tol=0){
+ unsigned val=0;
+ while( val<a.size() &&
+ fabs(a[val][0])<tol &&
+ fabs(a[val][1])<tol )
+ val++;
+ return val;
+}
+
+// a(b(t))
+SBasis compose(SBasis const &a, SBasis const &b);
+SBasis compose(SBasis const &a, SBasis const &b, unsigned k);
+SBasis inverse(SBasis a, int k);
+//compose_inverse(f,g)=compose(f,inverse(g)), but is numerically more stable in some good cases...
+//TODO: requires g(0)=0 & g(1)=1 atm. generalization should be obvious.
+SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order=2, double tol=1e-3);
+
+/** Returns the sbasis on domain [0,1] that was t on [from, to]
+ \param t sbasis function
+ \param from,to interval
+ \return sbasis
+ \relates SBasis
+*/
+SBasis portion(const SBasis &t, double from, double to);
+inline SBasis portion(const SBasis &t, Interval const &ivl) { return portion(t, ivl.min(), ivl.max()); }
+
+// compute f(g)
+inline SBasis
+SBasis::operator()(SBasis const & g) const {
+ return compose(*this, g);
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const Linear &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const SBasis & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ if (i != 0) {
+ out_file << " + ";
+ }
+ out_file << p[i] << "s^" << i;
+ }
+ return out_file;
+}
+
+// These are deprecated, use sbasis-math.h versions if possible
+SBasis sin(Linear bo, int k);
+SBasis cos(Linear bo, int k);
+
+std::vector<double> roots(SBasis const & s);
+std::vector<double> roots(SBasis const & s, Interval const inside);
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+
+//--------- Levelset like functions -----------------------------------------------------
+
+/** Solve f(t) = v +/- tolerance. The collection of intervals where
+ * v - vtol <= f(t) <= v+vtol
+ * is returned (with a precision tol on the boundaries).
+ \param f sbasis function
+ \param level the value of v.
+ \param vtol: error tolerance on v.
+ \param a, b limit search on domain [a,b]
+ \param tol: tolerance on the result bounds.
+ \returns a vector of intervals.
+*/
+std::vector<Interval> level_set (SBasis const &f,
+ double level,
+ double vtol = 1e-5,
+ double a=0.,
+ double b=1.,
+ double tol = 1e-5);
+
+/** Solve f(t)\in I=[u,v], which defines a collection of intervals (J_k). More precisely,
+ * a collection (J'_k) is returned with J'_k = J_k up to a given tolerance.
+ \param f sbasis function
+ \param level: the given interval of deisred values for f.
+ \param a, b limit search on domain [a,b]
+ \param tol: tolerance on the bounds of the result.
+ \returns a vector of intervals.
+*/
+std::vector<Interval> level_set (SBasis const &f,
+ Interval const &level,
+ double a=0.,
+ double b=1.,
+ double tol = 1e-5);
+
+/** 'Solve' f(t) = v +/- tolerance for several values of v at once.
+ \param f sbasis function
+ \param levels vector of values, that should be sorted.
+ \param vtol: error tolerance on v.
+ \param a, b limit search on domain [a,b]
+ \param tol: the bounds of the returned intervals are exact up to that tolerance.
+ \returns a vector of vectors of intervals.
+*/
+std::vector<std::vector<Interval> > level_sets (SBasis const &f,
+ std::vector<double> const &levels,
+ double a=0.,
+ double b=1.,
+ double vtol = 1e-5,
+ double tol = 1e-5);
+
+/** 'Solve' f(t)\in I=[u,v] for several intervals I at once.
+ \param f sbasis function
+ \param levels vector of 'y' intervals, that should be disjoints and sorted.
+ \param a, b limit search on domain [a,b]
+ \param tol: the bounds of the returned intervals are exact up to that tolerance.
+ \returns a vector of vectors of intervals.
+*/
+std::vector<std::vector<Interval> > level_sets (SBasis const &f,
+ std::vector<Interval> const &levels,
+ double a=0.,
+ double b=1.,
+ double tol = 1e-5);
+
+}
+#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 :
+#endif
diff --git a/include/2geom/solver.h b/include/2geom/solver.h
new file mode 100644
index 0000000..5b082cb
--- /dev/null
+++ b/include/2geom/solver.h
@@ -0,0 +1,88 @@
+/**
+ * \file
+ * \brief Finding roots of Bernstein-Bezier polynomials
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_SOLVER_H
+#define LIB2GEOM_SEEN_SOLVER_H
+
+#include <2geom/point.h>
+#include <2geom/sbasis.h>
+#include <vector>
+
+namespace Geom {
+
+ class Point;
+ class Bezier;
+
+unsigned
+crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */
+ unsigned degree); /* Degree of Bezier curve */
+void
+find_parametric_bezier_roots(
+ Geom::Point const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> & solutions, /* RETURN candidate t-values */
+ unsigned depth); /* The depth of the recursion */
+
+unsigned
+crossing_count(double const *V, /* Control pts of Bezier curve */
+ unsigned degree, /* Degree of Bezier curve */
+ double left_t, double right_t);
+
+
+void
+find_bernstein_roots(
+ double const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> & solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t=0, double right_t=1, bool use_secant=true);
+
+};
+
+void
+find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-values */
+ Geom::Bezier const& bz,
+ double left_t, double right_t);
+
+#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/include/2geom/svg-path-parser.h b/include/2geom/svg-path-parser.h
new file mode 100644
index 0000000..e25316c
--- /dev/null
+++ b/include/2geom/svg-path-parser.h
@@ -0,0 +1,199 @@
+/**
+ * \file
+ * \brief parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 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.
+ *
+ */
+
+#ifndef LIB2GEOM_SEEN_SVG_PATH_PARSER_H
+#define LIB2GEOM_SEEN_SVG_PATH_PARSER_H
+
+#include <iostream>
+#include <iterator>
+#include <stdexcept>
+#include <vector>
+#include <cstdio>
+#include <2geom/exception.h>
+#include <2geom/point.h>
+#include <2geom/path-sink.h>
+#include <2geom/forward.h>
+
+namespace Geom {
+
+/** @brief Read SVG path data and feed it to a PathSink
+ *
+ * This class provides an interface to an SVG path data parser written in Ragel.
+ * It supports parsing the path data either at once or block-by-block.
+ * Use the parse() functions to parse complete data and the feed() and finish()
+ * functions to parse partial data.
+ *
+ * The parser will call the appropriate methods on the PathSink supplied
+ * at construction. To store the path in memory as a PathVector, pass
+ * a PathBuilder. You can also use one of the freestanding helper functions
+ * if you don't need to parse data block-by-block.
+ *
+ * @ingroup Paths
+ */
+class SVGPathParser {
+public:
+ SVGPathParser(PathSink &sink);
+ ~SVGPathParser();
+
+ /** @brief Reset internal state.
+ * Discards the internal state associated with partially parsed data,
+ * letting you start from scratch. Note that any partial data written
+ * to the path sink is not affected - you need to clear it yourself. */
+ void reset();
+
+ /** @brief Parse a C-style string.
+ * The path sink is flushed and the internal state is reset after this call.
+ * Note that the state is not reset before this method, so you can use it to
+ * process the last block of partial data.
+ * @param str String to parse
+ * @param len Length of string or -1 if null-terminated */
+ void parse(char const *str, int len = -1);
+ /** @brief Parse an STL string. */
+ void parse(std::string const &s);
+
+ /** @brief Parse a part of path data stored in a C-style string.
+ * This method does not reset internal state, so it can be called multiple
+ * times to parse successive blocks of a longer SVG path data string.
+ * To finish parsing, call finish() after the final block or call parse()
+ * with the last block of data.
+ * @param str String to parse
+ * @param len Length of string or -1 if null-terminated */
+ void feed(char const *str, int len = -1);
+ /** @brief Parse a part of path data stored in an STL string. */
+ void feed(std::string const &s);
+
+ /** @brief Finalize parsing.
+ * After the last block of data was submitted with feed(), call this method
+ * to finalize parsing, flush the path sink and reset internal state.
+ * You should not call this after parse(). */
+ void finish();
+
+ /** @brief Set the threshold for considering the closing segment degenerate.
+ * When the current point was reached by a relative command, is closer
+ * to the initial point of the path than the specified threshold
+ * and a 'z' is encountered, the last segment will be adjusted instead so that
+ * the closing segment has exactly zero length. This is useful when reading
+ * SVG 1.1 paths that have non-linear final segments written in relative
+ * coordinates, which always suffer from some loss of precision. SVG 2
+ * allows alternate placement of 'z' which does not have this problem. */
+ void setZSnapThreshold(Coord threshold) { _z_snap_threshold = threshold; }
+ Coord zSnapThreshold() const { return _z_snap_threshold; }
+
+private:
+ bool _absolute;
+ bool _moveto_was_absolute;
+ Point _current;
+ Point _initial;
+ Point _cubic_tangent;
+ Point _quad_tangent;
+ std::vector<Coord> _params;
+ PathSink &_sink;
+ Coord _z_snap_threshold;
+ Curve *_curve;
+
+ int cs;
+ std::string _number_part;
+
+ void _push(Coord value);
+ Coord _pop();
+ bool _pop_flag();
+ Coord _pop_coord(Geom::Dim2 axis);
+ Point _pop_point();
+ void _moveTo(Point const &p);
+ void _lineTo(Point const &p);
+ void _curveTo(Point const &c0, Point const &c1, Point const &p);
+ void _quadTo(Point const &c, Point const &p);
+ void _arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point const &p);
+ void _closePath();
+ void _pushCurve(Curve *c);
+
+ void _parse(char const *str, char const *strend, bool finish);
+};
+
+/** @brief Feed SVG path data to the specified sink
+ * @ingroup Paths */
+void parse_svg_path(char const *str, PathSink &sink);
+/** @brief Feed SVG path data to the specified sink
+ * @ingroup Paths */
+inline void parse_svg_path(std::string const &str, PathSink &sink) {
+ parse_svg_path(str.c_str(), sink);
+}
+/** Feed SVG path data from a C stream to the specified sink
+ * @ingroup Paths */
+void parse_svg_path_file(FILE *fi, PathSink &sink);
+
+/** @brief Create path vector from SVG path data stored in a C string
+ * @ingroup Paths */
+inline PathVector parse_svg_path(char const *str) {
+ PathVector ret;
+ SubpathInserter iter(ret);
+ PathIteratorSink<SubpathInserter> generator(iter);
+
+ parse_svg_path(str, generator);
+ return ret;
+}
+
+/** @brief Create path vector from a C stream with SVG path data
+ * @ingroup Paths */
+inline PathVector read_svgd_f(FILE * fi) {
+ PathVector ret;
+ SubpathInserter iter(ret);
+ PathIteratorSink<SubpathInserter> generator(iter);
+
+ parse_svg_path_file(fi, generator);
+ return ret;
+}
+
+/** @brief Create path vector from SVG path data stored in a file
+ * @ingroup Paths */
+inline PathVector read_svgd(char const *filename) {
+ FILE* fi = fopen(filename, "r");
+ if(fi == NULL) throw(std::runtime_error("Error opening file"));
+ PathVector out = read_svgd_f(fi);
+ fclose(fi);
+ return out;
+}
+
+} // end namespace Geom
+
+#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/include/2geom/svg-path-writer.h b/include/2geom/svg-path-writer.h
new file mode 100644
index 0000000..92a80ec
--- /dev/null
+++ b/include/2geom/svg-path-writer.h
@@ -0,0 +1,122 @@
+/** @file
+ * @brief Path sink which writes an SVG-compatible command string
+ *//*
+ * 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.
+ */
+
+#ifndef LIB2GEOM_SEEN_SVG_PATH_WRITER_H
+#define LIB2GEOM_SEEN_SVG_PATH_WRITER_H
+
+#include <2geom/path-sink.h>
+#include <sstream>
+
+namespace Geom {
+
+/** @brief Serialize paths to SVG path data strings.
+ * You can access the generated string by calling the str() method.
+ * @ingroup Paths
+ */
+class SVGPathWriter
+ : public PathSink
+{
+public:
+ SVGPathWriter();
+ ~SVGPathWriter() override {}
+
+ void moveTo(Point const &p) override;
+ void lineTo(Point const &p) override;
+ void quadTo(Point const &c, Point const &p) override;
+ void curveTo(Point const &c0, Point const &c1, Point const &p) override;
+ void arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point const &p) override;
+ void closePath() override;
+ void flush() override;
+
+ /// Clear any path data written so far.
+ void clear();
+
+ /** @brief Set output precision.
+ * When the parameter is negative, the path writer enters a verbatim mode
+ * which preserves all values exactly. */
+ void setPrecision(int prec);
+
+ /** @brief Enable or disable length optimization.
+ *
+ * When set to true, the path writer will optimize the generated path data
+ * for minimum length. However, this will make the data less readable,
+ * because spaces between commands and coordinates will be omitted where
+ * unnecessary for correct parsing.
+ *
+ * When set to false, the string will be a straightforward, partially redundant
+ * representation of the passed commands, optimized for readability.
+ * Commands and coordinates will always be separated by spaces and the command
+ * symbol will not be omitted for multiple consecutive commands of the same type.
+ *
+ * Length optimization is turned off by default. */
+ void setOptimize(bool opt) { _optimize = opt; }
+
+ /** @brief Enable or disable the use of V, H, T and S commands where possible.
+ * Shorthands are turned on by default. */
+ void setUseShorthands(bool use) { _use_shorthands = use; }
+
+ /// Retrieve the generated path data string.
+ std::string str() const { return _s.str(); }
+
+private:
+ void _setCommand(char cmd);
+ std::string _formatCoord(Coord par);
+
+ std::ostringstream _s, _ns;
+ std::vector<Coord> _current_pars;
+ Point _subpath_start;
+ Point _current;
+ Point _quad_tangent;
+ Point _cubic_tangent;
+ Coord _epsilon;
+ int _precision;
+ bool _optimize;
+ bool _use_shorthands;
+ char _command;
+};
+
+std::string write_svg_path(PathVector const &pv, int prec = -1, bool optimize = false, bool shorthands = true);
+
+} // namespace Geom
+
+#endif // LIB2GEOM_SEEN_SVG_PATH_WRITER_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/include/2geom/sweep-bounds.h b/include/2geom/sweep-bounds.h
new file mode 100644
index 0000000..e0ebf29
--- /dev/null
+++ b/include/2geom/sweep-bounds.h
@@ -0,0 +1,62 @@
+/**
+ * \file
+ * \brief Sweepline intersection of groups of rectangles
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ *
+ * Copyright ?-? 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 LIB2GEOM_SEEN_SWEEP_H
+#define LIB2GEOM_SEEN_SWEEP_H
+
+#include <vector>
+#include <2geom/d2.h>
+
+namespace Geom {
+
+std::vector<std::vector<unsigned> >
+sweep_bounds(std::vector<Rect>, Dim2 dim = X);
+
+std::vector<std::vector<unsigned> >
+sweep_bounds(std::vector<Rect>, std::vector<Rect>, Dim2 dim = X);
+
+}
+
+#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/include/2geom/sweeper.h b/include/2geom/sweeper.h
new file mode 100644
index 0000000..233d181
--- /dev/null
+++ b/include/2geom/sweeper.h
@@ -0,0 +1,189 @@
+/** @file
+ * @brief Class for implementing sweepline algorithms
+ *//*
+ * 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.
+ */
+
+#ifndef LIB2GEOM_SEEN_SWEEPER_H
+#define LIB2GEOM_SEEN_SWEEPER_H
+
+#include <2geom/coord.h>
+#include <algorithm>
+#include <vector>
+#include <boost/intrusive/list.hpp>
+
+namespace Geom {
+
+// exposition only
+template <typename Item>
+class SweepVector {
+public:
+ typedef typename std::vector<Item>::const_iterator ItemIterator;
+
+ SweepVector(std::vector<Item> const &v)
+ : _items(v)
+ {}
+
+ std::vector<Item> const &items() { return _items; }
+ Interval itemBounds(ItemIterator /*ii*/) { return Interval(); }
+
+ void addActiveItem(ItemIterator /*ii*/) {}
+ void removeActiveItem(ItemIterator /*ii*/) {}
+
+private:
+ std::vector<Item> const &_items;
+};
+
+/** @brief Generic sweepline algorithm.
+ *
+ * This class encapsulates an algorithm that sorts the objects according
+ * to their bounds, then moves an imaginary line (sweepline) over those
+ * bounds from left to right. Objects are added to the active list when
+ * the line starts intersecting their bounds, and removed when it completely
+ * passes over them.
+ *
+ * To use this, create a class that exposes the following methods:
+ * - Range items() - returns a forward iterable range of items that will be swept.
+ * - Interval itemBounds(iterator i) - given an iterator from the above range,
+ * compute the bounding interval of the referenced item in the direction of sweep.
+ * - void addActiveItem(iterator i) - add an item to the active list.
+ * - void removeActiveItem(iterator i) - remove an item from the active list.
+ *
+ * Create the object, then instantiate this template with the above class
+ * as the template parameter, pass it the constructed object of the class,
+ * and call the process() method.
+ *
+ * A good choice for the active list is a Boost intrusive list, which allows
+ * you to get an iterator from a value in constant time.
+ *
+ * Look in path.cpp for example usage.
+ *
+ * @tparam Item The type of items to sweep
+ * @tparam SweepTraits Traits class that defines the items' bounds,
+ * how to interpret them and how to sort the events
+ * @ingroup Utilities
+ */
+template <typename SweepSet>
+class Sweeper {
+public:
+ typedef typename SweepSet::ItemIterator Iter;
+
+ explicit Sweeper(SweepSet &set)
+ : _set(set)
+ {
+ std::size_t sz = std::distance(set.items().begin(), set.items().end());
+ _entry_events.reserve(sz);
+ _exit_events.reserve(sz);
+ }
+
+ /** @brief Process entry and exit events.
+ * This will iterate over all inserted items, calling the methods
+ * addActiveItem and removeActiveItem on the SweepSet passed at construction
+ * according to the order of the boundaries of each item. */
+ void process() {
+ if (_set.items().empty()) return;
+
+ Iter last = _set.items().end();
+ for (Iter i = _set.items().begin(); i != last; ++i) {
+ Interval b = _set.itemBounds(i);
+ // guard against NANs
+ assert(b.min() == b.min() && b.max() == b.max());
+ _entry_events.push_back(Event(b.max(), i));
+ _exit_events.push_back(Event(b.min(), i));
+ }
+
+ std::make_heap(_entry_events.begin(), _entry_events.end());
+ std::make_heap(_exit_events.begin(), _exit_events.end());
+
+ Event next_entry = _get_next(_entry_events);
+ Event next_exit = _get_next(_exit_events);
+
+ while (next_entry || next_exit) {
+ assert(next_exit);
+
+ if (!next_entry || next_exit > next_entry) {
+ // exit event - remove record from active list
+ _set.removeActiveItem(next_exit.item);
+ next_exit = _get_next(_exit_events);
+ } else {
+ // entry event - add record to active list
+ _set.addActiveItem(next_entry.item);
+ next_entry = _get_next(_entry_events);
+ }
+ }
+ }
+
+private:
+ struct Event
+ : boost::totally_ordered<Event>
+ {
+ Coord coord;
+ Iter item;
+
+ Event(Coord c, Iter const &i)
+ : coord(c), item(i)
+ {}
+ Event()
+ : coord(nan("")), item()
+ {}
+ bool operator<(Event const &other) const { return coord < other.coord; }
+ bool operator==(Event const &other) const { return coord == other.coord; }
+ operator bool() const { return !std::isnan(coord); }
+ };
+
+ static Event _get_next(std::vector<Event> &heap) {
+ if (heap.empty()) {
+ Event e;
+ return e;
+ }
+ std::pop_heap(heap.begin(), heap.end());
+ Event ret = heap.back();
+ heap.pop_back();
+ return ret;
+ }
+
+ SweepSet &_set;
+ std::vector<Event> _entry_events;
+ std::vector<Event> _exit_events;
+};
+
+} // namespace Geom
+
+#endif // !LIB2GEOM_SEEN_SWEEPER_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/include/2geom/symbolic/determinant-minor.h b/include/2geom/symbolic/determinant-minor.h
new file mode 100644
index 0000000..d70c397
--- /dev/null
+++ b/include/2geom/symbolic/determinant-minor.h
@@ -0,0 +1,175 @@
+/*
+ * GiNaC Copyright (C) 1999-2008 Johannes Gutenberg University Mainz, Germany
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _GEOM_SL_DETERMINANT_MINOR_H_
+#define _GEOM_SL_DETERMINANT_MINOR_H_
+
+#include <map>
+
+
+namespace Geom { namespace SL {
+
+/*
+ * determinant_minor
+ * This routine has been taken from the ginac project
+ * and adapted as needed; comments are the original ones.
+ */
+
+/** Recursive determinant for small matrices having at least one symbolic
+ * entry. The basic algorithm, known as Laplace-expansion, is enhanced by
+ * some bookkeeping to avoid calculation of the same submatrices ("minors")
+ * more than once. According to W.M.Gentleman and S.C.Johnson this algorithm
+ * is better than elimination schemes for matrices of sparse multivariate
+ * polynomials and also for matrices of dense univariate polynomials if the
+ * matrix' dimesion is larger than 7.
+ *
+ * @return the determinant as a new expression (in expanded form)
+ * @see matrix::determinant() */
+
+template< typename Coeff >
+Coeff determinant_minor(Matrix<Coeff> const& M)
+{
+ assert(M.rows() == M.columns());
+ // for small matrices the algorithm does not make any sense:
+ const unsigned int n = M.columns();
+ if (n == 1)
+ return M(0,0);
+ if (n == 2)
+ return (M(0,0) * M(1,1) - M(0,1) * M(1,0));
+ if (n == 3)
+ return ( M(0,0)*M(1,1)*M(2,2) + M(0,2)*M(1,0)*M(2,1)
+ + M(0,1)*M(1,2)*M(2,0) - M(0,2)*M(1,1)*M(2,0)
+ - M(0,0)*M(1,2)*M(2,1) - M(0,1)*M(1,0)*M(2,2) );
+
+ // This algorithm can best be understood by looking at a naive
+ // implementation of Laplace-expansion, like this one:
+ // ex det;
+ // matrix minorM(this->rows()-1,this->cols()-1);
+ // for (unsigned r1=0; r1<this->rows(); ++r1) {
+ // // shortcut if element(r1,0) vanishes
+ // if (m[r1*col].is_zero())
+ // continue;
+ // // assemble the minor matrix
+ // for (unsigned r=0; r<minorM.rows(); ++r) {
+ // for (unsigned c=0; c<minorM.cols(); ++c) {
+ // if (r<r1)
+ // minorM(r,c) = m[r*col+c+1];
+ // else
+ // minorM(r,c) = m[(r+1)*col+c+1];
+ // }
+ // }
+ // // recurse down and care for sign:
+ // if (r1%2)
+ // det -= m[r1*col] * minorM.determinant_minor();
+ // else
+ // det += m[r1*col] * minorM.determinant_minor();
+ // }
+ // return det.expand();
+ // What happens is that while proceeding down many of the minors are
+ // computed more than once. In particular, there are binomial(n,k)
+ // kxk minors and each one is computed factorial(n-k) times. Therefore
+ // it is reasonable to store the results of the minors. We proceed from
+ // right to left. At each column c we only need to retrieve the minors
+ // calculated in step c-1. We therefore only have to store at most
+ // 2*binomial(n,n/2) minors.
+
+ // Unique flipper counter for partitioning into minors
+ std::vector<unsigned int> Pkey;
+ Pkey.reserve(n);
+ // key for minor determinant (a subpartition of Pkey)
+ std::vector<unsigned int> Mkey;
+ Mkey.reserve(n-1);
+ // we store our subminors in maps, keys being the rows they arise from
+ typedef typename std::map<std::vector<unsigned>, Coeff> Rmap;
+ typedef typename std::map<std::vector<unsigned>, Coeff>::value_type Rmap_value;
+ Rmap A;
+ Rmap B;
+ Coeff det;
+ // initialize A with last column:
+ for (unsigned int r = 0; r < n; ++r)
+ {
+ Pkey.erase(Pkey.begin(),Pkey.end());
+ Pkey.push_back(r);
+ A.insert(Rmap_value(Pkey,M(r,n-1)));
+ }
+ // proceed from right to left through matrix
+ for (int c = n-2; c >= 0; --c)
+ {
+ Pkey.erase(Pkey.begin(),Pkey.end()); // don't change capacity
+ Mkey.erase(Mkey.begin(),Mkey.end());
+ for (unsigned int i = 0; i < n-c; ++i)
+ Pkey.push_back(i);
+ unsigned int fc = 0; // controls logic for our strange flipper counter
+ do
+ {
+ det = Geom::SL::zero<Coeff>()();
+ for (unsigned int r = 0; r < n-c; ++r)
+ {
+ // maybe there is nothing to do?
+ if (M(Pkey[r], c).is_zero())
+ continue;
+ // create the sorted key for all possible minors
+ Mkey.erase(Mkey.begin(),Mkey.end());
+ for (unsigned int i = 0; i < n-c; ++i)
+ if (i != r)
+ Mkey.push_back(Pkey[i]);
+ // Fetch the minors and compute the new determinant
+ if (r % 2)
+ det -= M(Pkey[r],c)*A[Mkey];
+ else
+ det += M(Pkey[r],c)*A[Mkey];
+ }
+ // store the new determinant at its place in B:
+ if (!det.is_zero())
+ B.insert(Rmap_value(Pkey,det));
+ // increment our strange flipper counter
+ for (fc = n-c; fc > 0; --fc)
+ {
+ ++Pkey[fc-1];
+ if (Pkey[fc-1]<fc+c)
+ break;
+ }
+ if (fc < n-c && fc > 0)
+ for (unsigned int j = fc; j < n-c; ++j)
+ Pkey[j] = Pkey[j-1]+1;
+ } while(fc);
+ // next column, so change the role of A and B:
+ A.swap(B);
+ B.clear();
+ }
+
+ return det;
+}
+
+
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+#endif // _GEOM_SL_DETERMINANT_MINOR_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/include/2geom/symbolic/implicit.h b/include/2geom/symbolic/implicit.h
new file mode 100644
index 0000000..82d77cd
--- /dev/null
+++ b/include/2geom/symbolic/implicit.h
@@ -0,0 +1,353 @@
+/*
+ * Routines to compute the implicit equation of a parametric polynomial curve
+ *
+ * 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_IMPLICIT_H_
+#define _GEOM_SL_IMPLICIT_H_
+
+
+
+#include <2geom/symbolic/multipoly.h>
+#include <2geom/symbolic/matrix.h>
+
+
+#include <2geom/exception.h>
+
+#include <array>
+
+
+namespace Geom { namespace SL {
+
+typedef MultiPoly<1, double> MVPoly1;
+typedef MultiPoly<2, double> MVPoly2;
+typedef MultiPoly<3, double> MVPoly3;
+typedef std::array<MVPoly1, 3> poly_vector_type;
+typedef std::array<poly_vector_type, 2> basis_type;
+typedef std::array<double, 3> coeff_vector_type;
+
+namespace detail {
+
+/*
+ * transform a univariate polynomial f(t) in a 3-variate polynomial
+ * p(t, x, y) = f(t) * x^i * y^j
+ */
+inline
+void poly1_to_poly3(MVPoly3 & p3, MVPoly1 const& p1, size_t i, size_t j)
+{
+ multi_index_type I = make_multi_index(0, i, j);
+ for (; I[0] < p1.get_poly().size(); ++I[0])
+ {
+ p3.coefficient(I, p1[I[0]]);
+ }
+}
+
+/*
+ * evaluates the degree of a poly_vector_type, such a degree is defined as:
+ * deg({p[0](t), p[1](t), p[2](t)}) := {max(deg(p[i](t)), i = 0, 1, 2), k}
+ * here k is the index where the max is achieved,
+ * if deg(p[i](t)) == deg(p[j](t)) and i < j then k = i
+ */
+inline
+std::pair<size_t, size_t> deg(poly_vector_type const& p)
+{
+ std::pair<size_t, size_t> d;
+ d.first = p[0].get_poly().real_degree();
+ d.second = 0;
+ size_t k = p[1].get_poly().real_degree();
+ if (d.first < k)
+ {
+ d.first = k;
+ d.second = 1;
+ }
+ k = p[2].get_poly().real_degree();
+ if (d.first < k)
+ {
+ d.first = k;
+ d.second = 2;
+ }
+ return d;
+}
+
+} // end namespace detail
+
+
+/*
+ * A polynomial parametrization could be seen as 1-variety V in R^3,
+ * intersection of two surfaces x = f(t), y = g(t), this variety V has
+ * attached an ideal I in the ring of polynomials in t, x, y with coefficients
+ * on reals; a basis of generators for I is given by p(t, x, y) = x - f(t),
+ * q(t, x, y) = y - g(t); such a basis has the nice property that could be
+ * written as a couple of vectors of dim 3 with entries in R[t]; the original
+ * polinomials p and q can be obtained by doing a dot product between each
+ * vector and the vector {x, y, 1}
+ * As reference you can read the text book:
+ * Ideals, Varieties and Algorithms by Cox, Little, O'Shea
+ */
+inline
+void make_initial_basis(basis_type& b, MVPoly1 const& p, MVPoly1 const& q)
+{
+ // first basis vector
+ b[0][0] = 1;
+ b[0][1] = 0;
+ b[0][2] = -p;
+
+ // second basis vector
+ b[1][0] = 0;
+ b[1][1] = 1;
+ b[1][2] = -q;
+}
+
+/*
+ * Starting from the initial basis for the ideal I is possible to make up
+ * a new basis, still showing off the nice property that each generator is
+ * a moving line that is a linear combination of x, y, 1 where the coefficients
+ * are polynomials in R[t], and moreover each generator is of minimal degree.
+ * Can be proved that given a polynomial parametrization f(t), g(t)
+ * we are able to make up a "micro" basis of generators p(t, x, y), q(t, x, y)
+ * for the ideal I such that the deg(p, t) = m <= n/2 and deg(q, t) = n - m,
+ * where n = max(deg(f(t)), deg(g(t))); this let us halve the order of
+ * the Bezout matrix.
+ * Reference:
+ * Zheng, Sederberg - A Direct Approach to Computing the micro-basis
+ * of a Planar Rational Curves
+ * Deng, Chen, Shen - Computing micro-Basis of Rational Curves and Surfaces
+ * Using Polynomial Matrix Factorization
+ */
+inline
+void microbasis(basis_type& b, MVPoly1 const& p, MVPoly1 const& q)
+{
+ typedef std::pair<size_t, size_t> degree_pair_t;
+
+ size_t n = std::max(p.get_poly().real_degree(), q.get_poly().real_degree());
+ make_initial_basis(b, p, q);
+ degree_pair_t n0 = detail::deg(b[0]);
+ degree_pair_t n1 = detail::deg(b[1]);
+ size_t d;
+ double r0, r1;
+ //size_t iter = 0;
+ while ((n0.first + n1.first) > n)// && iter < 30)
+ {
+// ++iter;
+// std::cout << "iter = " << iter << std::endl;
+// for (size_t i= 0; i < 2; ++i)
+// for (size_t j= 0; j < 3; ++j)
+// std::cout << b[i][j] << std::endl;
+// std::cout << n0.first << ", " << n0.second << std::endl;
+// std::cout << n1.first << ", " << n1.second << std::endl;
+// std::cout << "-----" << std::endl;
+// if (n0.first < n1.first)
+// {
+// d = n1.first - n0.first;
+// r = b[1][n1.second][n1.first] / b[0][n1.second][n0.first];
+// for (size_t i = 0; i < b[0].size(); ++i)
+// b[1][i] -= ((r * b[0][i]).get_poly() << d);
+// b[1][n1.second][n1.first] = 0;
+// n1 = detail::deg(b[1]);
+// }
+// else
+// {
+// d = n0.first - n1.first;
+// r = b[0][n0.second][n0.first] / b[1][n0.second][n1.first];
+// for (size_t i = 0; i < b[0].size(); ++i)
+// b[0][i] -= ((r * b[1][i]).get_poly() << d);
+// b[0][n0.second][n0.first] = 0;
+// n0 = detail::deg(b[0]);
+// }
+
+ // this version shouldn't suffer of ill-conditioning due to
+ // cancellation issue
+ if (n0.first < n1.first)
+ {
+ d = n1.first - n0.first;
+ r0 = b[0][n1.second][n0.first];
+ r1 = b[1][n1.second][n1.first];
+ for (size_t i = 0; i < b[0].size(); ++i)
+ {
+ b[1][i] *= r0;
+ b[1][i] -= ((r1 * b[0][i]).get_poly() << d);
+ // without the following division the modulus grows
+ // beyond the limit of the double type
+ b[1][i] /= r0;
+ }
+ n1 = detail::deg(b[1]);
+ }
+ else
+ {
+ d = n0.first - n1.first;
+ r0 = b[0][n1.second][n0.first];
+ r1 = b[1][n1.second][n1.first];
+
+ for (size_t i = 0; i < b[0].size(); ++i)
+ {
+ b[0][i] *= r1;
+ b[0][i] -= ((r0 * b[1][i]).get_poly() << d);
+ b[0][i] /= r1;
+ }
+ n0 = detail::deg(b[0]);
+ }
+
+ }
+}
+
+/*
+ * computes the dot product:
+ * p(t, x, y) = {p0(t), p1(t), p2(t)} . {x, y, 1}
+ */
+inline
+void basis_to_poly(MVPoly3 & p0, poly_vector_type const& v)
+{
+ MVPoly3 p1, p2;
+ detail::poly1_to_poly3(p0, v[0], 1,0);
+ detail::poly1_to_poly3(p1, v[1], 0,1);
+ detail::poly1_to_poly3(p2, v[2], 0,0);
+ p0 += p1;
+ p0 += p2;
+}
+
+
+/*
+ * Make up a Bezout matrix with two basis genarators as input.
+ *
+ * A Bezout matrix is the matrix related to the symmetric bilinear form
+ * (f,g) -> B[f,g] where B[f,g](s,t) = (f(t)*g(s) - f(s)*g(t))/(s-t)
+ * where f, g are polynomials, this function is called a bezoutian.
+ * Given a basis of generators {p(t, x, y), q(t, x, y)} for the ideal I
+ * related to our parametrization x = f(t), y = g(t), we are able to prove
+ * that the implicit equation of such polynomial parametrization can be
+ * evaluated computing the determinant of the Bezout matrix made up using
+ * the polinomial p and q as univariate polynomials in t with coefficients
+ * in R[x,y], so the resulting Bezout matrix will be a matrix with bivariate
+ * polynomials as entries. A Bezout matrix is always symmetric.
+ * Reference:
+ * Sederberg, Zheng - Algebraic Methods for Computer Aided Geometric Design
+ */
+Matrix<MVPoly2>
+make_bezout_matrix (MVPoly3 const& p, MVPoly3 const& q)
+{
+ size_t pdeg = p.get_poly().real_degree();
+ size_t qdeg = q.get_poly().real_degree();
+ size_t n = std::max(pdeg, qdeg);
+
+ Matrix<MVPoly2> BM(n, n);
+ //std::cerr << "rows, columns " << BM.rows() << " , " << BM.columns() << std::endl;
+ for (size_t i = n; i >= 1; --i)
+ {
+ for (size_t j = n; j >= i; --j)
+ {
+ size_t m = std::min(i, n + 1 - j);
+ //std::cerr << "m = " << m << std::endl;
+ for (size_t k = 1; k <= m; ++k)
+ {
+ //BM(i-1,j-1) += (p[j-1+k] * q[i-k] - p[i-k] * q[j-1+k]);
+ BM(n-i,n-j) += (p.coefficient(j-1+k) * q.coefficient(i-k)
+ - p.coefficient(i-k) * q.coefficient(j-1+k));
+ }
+ }
+ }
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ for (size_t j = 0; j < i; ++j)
+ BM(j,i) = BM(i,j);
+ }
+ return BM;
+}
+
+/*
+ * Make a matrix that represents a main minor (i.e. with the diagonal
+ * on the diagonal of the matrix to which it owns) of the Bezout matrix
+ * with order n-1 where n is the order of the Bezout matrix.
+ * The minor is obtained by removing the "h"-th row and the "h"-th column,
+ * and as the Bezout matrix is symmetric.
+ */
+Matrix<MVPoly2>
+make_bezout_main_minor (MVPoly3 const& p, MVPoly3 const& q, size_t h)
+{
+ size_t pdeg = p.get_poly().real_degree();
+ size_t qdeg = q.get_poly().real_degree();
+ size_t n = std::max(pdeg, qdeg);
+
+ Matrix<MVPoly2> BM(n-1, n-1);
+ size_t u = 0, v;
+ for (size_t i = 1; i <= n; ++i)
+ {
+ v = 0;
+ if (i == h)
+ {
+ u = 1;
+ continue;
+ }
+ for (size_t j = 1; j <= i; ++j)
+ {
+ if (j == h)
+ {
+ v = 1;
+ continue;
+ }
+ size_t m = std::min(i, n + 1 - j);
+ for (size_t k = 1; k <= m; ++k)
+ {
+ //BM(i-u-1,j-v-1) += (p[j-1+k] * q[i-k] - p[i-k] * q[j-1+k]);
+ BM(i-u-1,j-v-1) += (p.coefficient(j-1+k) * q.coefficient(i-k)
+ - p.coefficient(i-k) * q.coefficient(j-1+k));
+ }
+ }
+ }
+
+ --n;
+ for (size_t i = 0; i < n; ++i)
+ {
+ for (size_t j = 0; j < i; ++j)
+ BM(j,i) = BM(i,j);
+ }
+ return BM;
+}
+
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+
+
+#endif // _GEOM_SL_IMPLICIT_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/include/2geom/symbolic/matrix.h b/include/2geom/symbolic/matrix.h
new file mode 100644
index 0000000..d9dc690
--- /dev/null
+++ b/include/2geom/symbolic/matrix.h
@@ -0,0 +1,265 @@
+/*
+ * Matrix<CoeffT> class template
+ *
+ * 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_MATRIX_H_
+#define _GEOM_SL_MATRIX_H_
+
+
+#include <array>
+#include <vector>
+#include <map>
+
+#include <2geom/point.h>
+#include <2geom/numeric/matrix.h>
+#include <2geom/symbolic/multipoly.h>
+
+
+
+
+namespace Geom { namespace SL {
+
+/*
+ * generic Matrix class template
+ * needed for building up a matrix with polynomial entries
+ */
+template< typename Coeff>
+class Matrix
+{
+ public:
+ typedef Coeff coeff_type;
+ typedef std::vector<coeff_type> container_type;
+
+ Matrix()
+ {}
+
+ Matrix(size_t m, size_t n)
+ : m_data(m*n), m_rows(m), m_columns(n)
+ {
+ }
+
+ void resize(size_t m, size_t n)
+ {
+ m_data.resize(m,n);
+ m_rows = m;
+ m_columns = n;
+ }
+
+ size_t rows() const
+ {
+ return m_rows;
+ }
+
+ size_t columns() const
+ {
+ return m_columns;
+ }
+
+ coeff_type const& operator() (size_t i, size_t j) const
+ {
+ return m_data[i * columns() + j];
+ }
+
+ coeff_type & operator() (size_t i, size_t j)
+ {
+ return m_data[i * columns() + j];
+ }
+
+
+ private:
+ container_type m_data;
+ size_t m_rows;
+ size_t m_columns;
+};
+
+
+template< typename Coeff, typename charT >
+inline
+std::basic_ostream<charT> &
+operator<< ( std::basic_ostream<charT> & os,
+ const Matrix<Coeff> & _matrix )
+{
+ if (_matrix.rows() == 0 || _matrix.columns() == 0) return os;
+
+ os << "{{" << _matrix(0,0);
+ for (size_t j = 1; j < _matrix.columns(); ++j)
+ {
+ os << ", " << _matrix(0,j);
+ }
+ os << "}";
+
+ for (size_t i = 1; i < _matrix.rows(); ++i)
+ {
+ os << ", {" << _matrix(i,0);
+ for (size_t j = 1; j < _matrix.columns(); ++j)
+ {
+ os << ", " << _matrix(i,j);
+ }
+ os << "}";
+ }
+ os << "}";
+ return os;
+}
+
+template <size_t N, typename CoeffT, typename T>
+void polynomial_matrix_evaluate (Matrix<T> & A,
+ Matrix< MultiPoly<N, CoeffT> > const& M,
+ std::array<T, N> const& X)
+{
+ A.resize(M.rows(), M.columns());
+ for (size_t i = 0; i < M.rows(); ++i)
+ {
+ for (size_t j = 0; j < M.columns(); ++j)
+ {
+ A(i,j) = M(i,j)(X);
+ }
+ }
+}
+
+
+inline
+void polynomial_matrix_evaluate (NL::Matrix & A,
+ Matrix< MultiPoly<2, double> > const& M,
+ Point const& P)
+{
+ for (size_t i = 0; i < M.rows(); ++i)
+ {
+ for (size_t j = 0; j < M.columns(); ++j)
+ {
+ A(i,j) = M(i,j)(P[X], P[Y]);
+ }
+ }
+}
+
+
+/*
+template< typename Coeff>
+class SymmetricSquareMatrix
+{
+ public:
+ typedef Coeff coeff_type;
+ typedef std::vector<coeff_type> container_type;
+
+ SymmetricSquareMatrix(size_t n)
+ : m_data((n*n)/2 + n), m_size(n)
+ {
+
+ }
+
+ size_t rows() const
+ {
+ return m_size;
+ }
+
+ size_t columns() const
+ {
+ return m_size;
+ }
+
+ coeff_type const& operator() (size_t i, size_t j) const
+ {
+ return m_data[i * columns() + j];
+ }
+
+ coeff_type & operator() (size_t i, size_t j)
+ {
+ return m_data[i * columns() + j];
+ }
+
+ coeff_type det()
+ {
+
+ }
+
+ private:
+ container_type m_data;
+ size_t m_size;
+};
+*/
+
+/*
+ * This is an adaptation of the LU algorithm used in the numerical case.
+ * This algorithm is based on the article due to Bareiss:
+ * "Sylvester's identity and multistep integer-preserving Gaussian elimination"
+ */
+
+/*
+template< typename CoeffT >
+CoeffT det(Matrix<CoeffT> const& M)
+{
+ assert(M.rows() == M.columns());
+
+ Matrix<CoeffT> A(M);
+ CoeffT n;
+ CoeffT d = one<CoeffT>()();
+ for (size_t k = 1; k < A.rows(); ++k)
+ {
+ for (size_t i = k; i < A.rows(); ++i)
+ {
+ for (size_t j = k; j < A.columns(); ++j)
+ {
+ n = A(i,j) * A(k-1,k-1) - A(k-1,j) * A(i,k-1);
+// std::cout << "k, i, j: "
+// << k << ", " << i << ", " << j << std::endl;
+// std::cout << "n = " << n << std::endl;
+// std::cout << "d = " << d << std::endl;
+ A(i,j) = factor(n, d);
+ }
+ }
+ d = A(k-1,k-1);
+ }
+ return A(A.rows()-1, A.columns()-1);
+}
+*/
+
+
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+#include <2geom/symbolic/determinant-minor.h>
+
+
+#endif // _GEOM_SL_MATRIX_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/include/2geom/symbolic/multi-index.h b/include/2geom/symbolic/multi-index.h
new file mode 100644
index 0000000..311fae8
--- /dev/null
+++ b/include/2geom/symbolic/multi-index.h
@@ -0,0 +1,169 @@
+/*
+ * A multi-index is an ordered sequence of unsigned int
+ *
+ * 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_MULTI_INDEX_H_
+#define _GEOM_SL_MULTI_INDEX_H_
+
+
+#include <2geom/exception.h>
+
+#include <valarray>
+
+#include <boost/preprocessor/cat.hpp>
+#include <boost/preprocessor/repetition/enum_params.hpp>
+#include <boost/preprocessor/repetition/repeat.hpp>
+#include <boost/preprocessor/repetition/repeat_from_to.hpp>
+
+
+
+
+/*
+ * an helper macro for generating function with declaration:
+ * multi_index_type make_multi_index (size_t i0, ..., size_t iN)
+ * that is a facility to make up a multi-index from a list of values
+ */
+
+#define GEOM_SL_MAX_RANK 10
+
+#define GEOM_SL_ASSIGN_INDEX(z, k, unused) I[k] = BOOST_PP_CAT(i, k);
+
+#define GEOM_SL_MAKE_MULTI_INDEX(z, N, unused) \
+inline \
+multi_index_type make_multi_index (BOOST_PP_ENUM_PARAMS(N, size_t i)) \
+{ \
+ multi_index_type I(N); \
+ BOOST_PP_REPEAT(N, GEOM_SL_ASSIGN_INDEX, unused) \
+ return I; \
+}
+// end macro GEOM_SL_MAKE_MULTI_INDEX
+
+
+
+
+namespace Geom { namespace SL {
+
+/*
+ * A multi-index is an ordered sequence of unsigned int;
+ * it's useful for representing exponent, degree and coefficient index
+ * of a multi-variate polynomial;
+ * example: given a monomial x_(0)^i_(0)*x_(1)^i_(1)*...*x_(N-1)^i_(N-1)
+ * we can write it in the simpler form X^I where X=(x_(0), .., x_(N-1))
+ * and I=(i_(0), .., i_(N-1)) is a multi-index
+ * A multi-index is represented as a valarray this let us make simple
+ * arithmetic operations on a multi-index
+ */
+
+typedef std::valarray<size_t> multi_index_type;
+
+
+// make up a multi-index of size N and fill it with zeroes
+inline
+multi_index_type multi_index_zero(size_t N)
+{
+ return multi_index_type(N);
+}
+
+// helper functions for generating a multi-index from a list of values
+// we create an amount of GEOM_SL_MAX_RANK of suzh functions
+BOOST_PP_REPEAT_FROM_TO(0, GEOM_SL_MAX_RANK, GEOM_SL_MAKE_MULTI_INDEX, unused)
+
+
+// helper function for generating a multi-index of size N
+// from a single index v that is placed at position i with i in [0,N[
+template <size_t N>
+inline
+multi_index_type make_multi_index(size_t i, size_t v)
+{
+ if (!(i < N))
+ THROW_RANGEERROR ("make_multi_index<N> from a single index: "
+ "out of range position");
+ multi_index_type I(N);
+ I[i] = v;
+ return I;
+}
+
+// transform a N size multi-index in (N-1)-size multi-index
+// by removing the first index: (i1, i2,...,iN) -> (i2,..,iN)
+inline
+multi_index_type shift(multi_index_type const& I, size_t i = 1)
+{
+ size_t N = I.size() - i;
+ multi_index_type J = I[std::slice(i, N, 1)];
+ return J;
+}
+
+// valarray operator== returns a valarray of bool
+inline
+bool is_equal(multi_index_type const& I, multi_index_type const& J)
+{
+ if (I.size() != J.size()) return false;
+ for (size_t i = 0; i < I.size(); ++i)
+ if (I[i] != J[i]) return false;
+ return true;
+}
+
+// extended operator<< for printing a multi-index
+template <typename charT>
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os,
+ const Geom::SL::multi_index_type & I)
+{
+ if (I.size() == 0 ) return os;
+ os << "[" << I[0];
+ for (unsigned int i = 1; i < I.size(); ++i)
+ {
+ os << ", " << I[i];
+ }
+ os << "]";
+ return os;
+}
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+// argument dependent name lookup doesn't work with typedef
+using Geom::SL::operator<<;
+
+
+#endif // _GEOM_SL_MULTI_INDEX_
+
+
+/*
+ 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/include/2geom/symbolic/multipoly.h b/include/2geom/symbolic/multipoly.h
new file mode 100644
index 0000000..ab3a5f4
--- /dev/null
+++ b/include/2geom/symbolic/multipoly.h
@@ -0,0 +1,684 @@
+/*
+ * MultiPoly<N, CoeffT> class template
+ *
+ * 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_MULTIPOLY_H_
+#define _GEOM_SL_MULTIPOLY_H_
+
+#include <utility>
+
+#include <array>
+#include <functional>
+#include <type_traits>
+
+#include <2geom/symbolic/unity-builder.h>
+#include <2geom/symbolic/mvpoly-tools.h>
+
+
+namespace Geom { namespace SL {
+
+/*
+ * MultiPoly<N, CoeffT> class template
+ *
+ * It represents a multi-variate polynomial with N indeterminates
+ * and coefficients of type CoeffT, but it doesn't support explicit
+ * symbol attaching; the indeterminates should be thought as implicitly
+ * defined in an automatic enumerative style: x_(0),...,x_(N-1) .
+ *
+ */
+
+template <size_t N, typename CoeffT>
+class MultiPoly
+{
+public:
+ typedef typename mvpoly<N, CoeffT>::type poly_type;
+ typedef CoeffT coeff_type;
+ static const size_t rank = N;
+
+public:
+ MultiPoly()
+ {
+ }
+
+ MultiPoly(poly_type p)
+ : m_poly(std::move(p))
+ {
+ }
+
+ // create a mv polynomial of type c*X^I
+ MultiPoly(coeff_type c, multi_index_type const& I = multi_index_zero(N))
+ : m_poly(monomial<N, coeff_type>::make(I, c))
+ {
+ }
+
+ // create a mv polynomial p(x_(N-M),...,x_(N-1))*X'^I
+ // where I.size() == N-M and X'=(x_(0),...,x_(N-M-1))
+ template <size_t M>
+ MultiPoly (MultiPoly<M, CoeffT> const& p,
+ multi_index_type const& I = multi_index_zero(N-M),
+ typename std::enable_if_t<(M > 0) && (M < N)>* = 0)
+ {
+ Geom::SL::coefficient<N-M-1, poly_type>::set_safe(m_poly, I, p.m_poly);
+ }
+
+ /*
+ * assignment operators
+ */
+ MultiPoly& operator=(poly_type const& p)
+ {
+ m_poly = p;
+ return (*this);
+ }
+
+ MultiPoly& operator=(coeff_type const& c)
+ {
+ multi_index_type I = multi_index_zero(N);
+ (*this) = MultiPoly(c);
+ return (*this);
+ }
+
+ // return the degree of the mv polynomial wrt the ordering OrderT
+ template <typename OrderT>
+ multi_index_type degree() const
+ {
+ return Geom::SL::mvdegree<N, CoeffT, OrderT>::value(m_poly);
+ }
+
+ // return the coefficient of the term with the highest degree
+ // wrt the ordering OrderT
+ template <typename OrderT>
+ coeff_type const& leading_coefficient() const
+ {
+ return (*this)(degree<OrderT>());
+ }
+
+ template <typename OrderT>
+ coeff_type & leading_coefficient()
+ {
+ return (*this)(degree<OrderT>());
+ }
+
+ // return the coefficient of the term of degree 0 (wrt all indeterminates)
+ coeff_type const& trailing_coefficient() const
+ {
+ return (*this)(multi_index_zero(N));
+ }
+
+ coeff_type & trailing_coefficient()
+ {
+ return (*this)(multi_index_zero(N));
+ }
+
+ // access coefficient methods with no out-of-range checking
+ coeff_type const& operator() (multi_index_type const& I) const
+ {
+ return Geom::SL::coefficient<N-1, poly_type>::get(m_poly, I);
+ }
+
+ coeff_type & operator() (multi_index_type const& I)
+ {
+ return Geom::SL::coefficient<N-1, poly_type>::get(m_poly, I);
+ }
+
+ // safe coefficient get method
+ coeff_type const& coefficient(multi_index_type const& I) const
+ {
+ return Geom::SL::coefficient<N-1, poly_type>::get_safe(m_poly, I);
+ }
+
+ // safe coefficient set method
+ void coefficient(multi_index_type const& I, coeff_type const& c)
+ {
+ Geom::SL::coefficient<N-1, poly_type>::set_safe(m_poly, I, c);
+ }
+
+ // access the mv poly of rank N-1 with no out-of-range checking
+ typename poly_type::coeff_type const&
+ operator[] (size_t const& i) const
+ {
+ return m_poly[i];
+ }
+
+ typename poly_type::coeff_type &
+ operator[] (size_t const& i)
+ {
+ return m_poly[i];
+ }
+
+ // safe access to the mv poly of rank N-1
+ typename poly_type::coeff_type const&
+ coefficient(size_t const& i) const
+ {
+ return m_poly.coefficient(i);
+ }
+
+ void coefficient (size_t const& i,
+ typename poly_type::coeff_type const& c)
+ {
+ m_poly.coefficient(i, c);
+ }
+
+ /*
+ * polynomail evaluation:
+ * T can be any type that is able to be + and * with the coefficient type
+ */
+ template <typename T>
+ T operator() (std::array<T, N> const& X) const
+ {
+ return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X);
+ }
+
+ template <typename T>
+ typename std::enable_if_t<(N == 1), T>
+ operator() (T const& x0) const
+ {
+ std::array<T, N> X = {{x0}};
+ return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X);
+ }
+
+ template <typename T>
+ typename std::enable_if_t<(N == 2), T>
+ operator() (T const& x0, T const& x1) const
+ {
+ std::array<T, N> X = {{x0, x1}};
+ return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X);
+ }
+
+ template <typename T>
+ typename std::enable_if_t<(N == 3), T>
+ operator() (T const& x0, T const& x1, T const& x2) const
+ {
+ std::array<T, N> X = {{x0, x1, x2}};
+ return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X);
+ }
+
+ /*
+ * trim leading zero coefficients
+ */
+ void normalize()
+ {
+ Geom::SL::mvpoly<N, CoeffT>::normalize(m_poly);
+ }
+
+ /*
+ * select the sub multi-variate polynomial with rank M
+ * which is unambiguously characterized by the multi-index I
+ * requirements:
+ * - M > 0 && M < N;
+ * - multi-index size == N-M
+ */
+ template <size_t M>
+ typename mvpoly<M, CoeffT>::type const&
+ select (multi_index_type const& I= multi_index_zero(N-M),
+ typename std::enable_if_t<(M > 0) && (M < N)>* = 0) const
+ {
+ return Geom::SL::coefficient<N-M-1, poly_type>::get_safe(m_poly, I);
+ }
+
+ poly_type const& get_poly() const
+ {
+ return m_poly;
+ }
+
+ bool is_zero() const
+ {
+ return ((*this) == zero);
+ }
+
+ // return the opposite mv poly
+ MultiPoly operator-() const
+ {
+ MultiPoly r(-m_poly);
+ return r;
+ }
+
+ /*
+ * multipoly-multipoly mutating operators
+ */
+ MultiPoly& operator+=(MultiPoly const& p)
+ {
+ m_poly += p.m_poly;
+ return (*this);
+ }
+
+ MultiPoly& operator-=(MultiPoly const& p)
+ {
+ m_poly -= p.m_poly;
+ return (*this);
+ }
+
+ MultiPoly& operator*=(MultiPoly const& p)
+ {
+ m_poly *= p.m_poly;
+ return (*this);
+ }
+
+ MultiPoly& operator<<=(multi_index_type const& I)
+ {
+ Geom::SL::mvpoly<N, CoeffT>::shift(m_poly, I);
+ return (*this);
+ }
+
+ bool operator==(MultiPoly const& q) const
+ {
+ return (m_poly == q.m_poly);
+ }
+
+ bool operator!=(MultiPoly const& q) const
+ {
+ return !((*this) == q);
+ }
+
+ /*
+ * multipoly-coefficient mutating operators
+ */
+ MultiPoly& operator+=(CoeffT const& c)
+ {
+ trailing_coefficient() += c;
+ return (*this);
+ }
+
+ MultiPoly& operator-=(CoeffT const& c)
+ {
+ trailing_coefficient() -= c;
+ return (*this);
+ }
+
+ MultiPoly& operator*=(CoeffT const& c)
+ {
+ mvpoly<N, CoeffT>::template
+ for_each<0>(m_poly, std::bind(mvpoly<0, CoeffT>::multiply_to, std::placeholders::_1, c));
+ return (*this);
+ }
+
+ MultiPoly& operator/=(CoeffT const& c)
+ {
+ mvpoly<N, CoeffT>::template
+ for_each<0>(m_poly, std::bind(mvpoly<0, CoeffT>::divide_to, std::placeholders::_1, c));
+ return (*this);
+ }
+
+ /*
+ * multipoly-polynomial mutating operators
+ */
+ MultiPoly& operator+=(poly_type const& p)
+ {
+ m_poly += p;
+ return (*this);
+ }
+
+ MultiPoly& operator-=(poly_type const& p)
+ {
+ m_poly -= p;
+ return (*this);
+ }
+
+ MultiPoly& operator*=(poly_type const& p)
+ {
+ m_poly *= p;
+ return (*this);
+ }
+
+ /*
+ * multipoly<N>-multipoly<M> mutating operators
+ * requirements:
+ * - M > 0 && M < N;
+ * - they must have the same coefficient type.
+ */
+
+ template <size_t M>
+ typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> &
+ operator+= (MultiPoly<M, CoeffT> const& p)
+ {
+ multi_index_type I = multi_index_zero(N-M);
+ Geom::SL::coefficient<N-M-1, poly_type>::get(m_poly, I) += p.m_poly;
+ return (*this);
+ }
+
+ template <size_t M>
+ typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> &
+ operator-= (MultiPoly<M, CoeffT> const& p)
+ {
+ multi_index_type I = multi_index_zero(N-M);
+ Geom::SL::coefficient<N-M-1, poly_type>::get(m_poly, I) -= p.m_poly;
+ return (*this);
+ }
+
+ template <size_t M>
+ typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> &
+ operator*= (MultiPoly<M, CoeffT> const& p)
+ {
+ mvpoly<N, CoeffT>::template
+ for_each<M>(m_poly, std::bind(mvpoly<M, CoeffT>::multiply_to, std::placeholders::_1, p.m_poly));
+ return (*this);
+ }
+
+ /*
+ * we need MultiPoly instantiations to be each other friend
+ * in order to be able of implementing operations between
+ * MultiPoly instantiations with a different ranks
+ */
+ template<size_t M, typename C>
+ friend class MultiPoly;
+
+ template< typename charT, size_t M, typename C>
+ friend
+ std::basic_ostream<charT> &
+ operator<< (std::basic_ostream<charT> & os, const MultiPoly<M, C> & p);
+
+ static const MultiPoly zero;
+ static const MultiPoly one;
+ static const coeff_type zero_coeff;
+ static const coeff_type one_coeff;
+
+private:
+ poly_type m_poly;
+
+}; // end class MultiPoly
+
+
+/*
+ * zero and one element spezcialization for MultiPoly
+ */
+template <size_t N, typename CoeffT>
+struct zero<MultiPoly<N, CoeffT>, false>
+{
+ MultiPoly<N, CoeffT> operator() ()
+ {
+ CoeffT _0c = zero<CoeffT>()();
+ MultiPoly<N, CoeffT> _0(_0c);
+ return _0;
+ }
+};
+
+
+template <size_t N, typename CoeffT>
+struct one<MultiPoly<N, CoeffT>, false>
+{
+ MultiPoly<N, CoeffT> operator() ()
+ {
+ CoeffT _1c = one<CoeffT>()();
+ MultiPoly<N, CoeffT> _1(_1c);
+ return _1;
+ }
+};
+
+
+/*
+ * initialization of MultiPoly static data members
+ */
+template <size_t N, typename CoeffT>
+const MultiPoly<N, CoeffT> MultiPoly<N, CoeffT>::one
+ = Geom::SL::one< MultiPoly<N, CoeffT> >()();
+
+template <size_t N, typename CoeffT>
+const MultiPoly<N, CoeffT> MultiPoly<N, CoeffT>::zero
+ = Geom::SL::zero< MultiPoly<N, CoeffT> >()();
+
+template <size_t N, typename CoeffT>
+const typename MultiPoly<N, CoeffT>::coeff_type MultiPoly<N, CoeffT>::zero_coeff
+ = Geom::SL::zero<typename MultiPoly<N, CoeffT>::coeff_type>()();
+
+template <size_t N, typename CoeffT>
+const typename MultiPoly<N, CoeffT>::coeff_type MultiPoly<N, CoeffT>::one_coeff
+ = Geom::SL::one<typename MultiPoly<N, CoeffT>::coeff_type>()();
+
+
+/*
+ * operator<< extended to print out a mv poly type
+ */
+template <typename charT, size_t N, typename CoeffT>
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const MultiPoly<N, CoeffT> & p)
+{
+ return operator<<(os, p.m_poly);
+}
+
+/*
+ * equivalent to multiply by X^I
+ */
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT>
+operator<< (MultiPoly<N, CoeffT> const& p, multi_index_type const& I)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r <<= I;
+ return r;
+}
+
+/*
+ * MultiPoly<M, CoeffT> - MultiPoly<N, CoeffT> binary mathematical operators
+ */
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> >
+operator+ (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<N, CoeffT> r(p);
+ r += q;
+ return r;
+}
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> >
+operator+ (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<M, CoeffT> r(q);
+ r += p;
+ return r;
+}
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> >
+operator- (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<N, CoeffT> r(p);
+ r -= q;
+ return r;
+}
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> >
+operator- (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<M, CoeffT> r(-q);
+ r += p;
+ return r;
+}
+
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> >
+operator* (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<N, CoeffT> r(p);
+ r *= q;
+ return r;
+}
+
+template <size_t M, size_t N, typename CoeffT>
+inline
+typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> >
+operator* (MultiPoly<N, CoeffT> const& p,
+ MultiPoly<M, CoeffT> const& q )
+{
+ MultiPoly<M, CoeffT> r(q);
+ r *= p;
+ return r;
+}
+
+/*
+ * MultiPoly-coefficient and coefficient-MultiPoly binary mathematical operators
+ */
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator+(MultiPoly<N, CoeffT> const& p, CoeffT const& c)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r += c;
+ return r;
+}
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator+(CoeffT const& c, MultiPoly<N, CoeffT> const& p)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r += c;
+ return r;
+}
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator-(MultiPoly<N, CoeffT> const& p, CoeffT const& c)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r -= c;
+ return r;
+}
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator-(CoeffT const& c, MultiPoly<N, CoeffT> const& p)
+{
+ MultiPoly<N, CoeffT> r(-p);
+ r += c;
+ return r;
+}
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator*(MultiPoly<N, CoeffT> const& p, CoeffT const& c)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r *= c;
+ return r;
+}
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator*(CoeffT const& c, MultiPoly<N, CoeffT> const& p)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r *= c;
+ return r;
+}
+
+
+template <size_t N, typename CoeffT>
+inline
+MultiPoly<N, CoeffT> operator/(MultiPoly<N, CoeffT> const& p, CoeffT const& c)
+{
+ MultiPoly<N, CoeffT> r(p);
+ r /= c;
+ return r;
+}
+
+
+
+
+/*
+template< size_t N, typename CoeffT >
+MultiPoly<N, CoeffT>
+factor( MultiPoly<N, CoeffT> const& f,
+ MultiPoly<N, CoeffT> const& g )
+{
+ typedef MultiPoly<N, CoeffT> poly_type;
+
+ if (g == poly_type::one) return f;
+ poly_type h(g), q, r(f);
+ multi_index_type deg_r = r.template degree<ordering::lex>();
+ multi_index_type deg_g = g.template degree<ordering::lex>();
+ multi_index_type deg0 = multi_index_zero(deg_g.size());
+ CoeffT ltg = g(deg_g);
+ if (is_equal(deg_g, deg0)) return (f / ltg);
+ //h(deg_g) = 0;
+// std::cout << "deg_g = " << deg_g << std::endl;
+// std::cout << "ltg = " << ltg << std::endl;
+ CoeffT lt, ltr;
+ multi_index_type deg(1, deg_g.size());
+ size_t iter = 0;
+ while (!is_equal(deg, deg0) && iter < 10000)
+ {
+ ++iter;
+ deg = deg_r - deg_g;
+ ltr = r(deg_r);
+ lt = ltr / ltg;
+ q.coefficient(deg, lt);
+ //r(deg_r) = 0;
+ r -= ((lt * g) << deg);
+ deg_r = r.template degree<ordering::lex>();
+// std::cout << "deg_r = " << deg_r << std::endl;
+// std::cout << "ltr = " << ltr << std::endl;
+// std::cout << "deg = " << deg << std::endl;
+// std::cout << "lt = " << lt << std::endl;
+// std::cout << "q = " << q << std::endl;
+// std::cout << "r = " << r << std::endl;
+
+// break;
+ }
+ //std::cout << "iter = " << iter << std::endl;
+ return q;
+}
+*/
+
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+
+
+#endif /* _MULTIPOLY_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/include/2geom/symbolic/mvpoly-tools.h b/include/2geom/symbolic/mvpoly-tools.h
new file mode 100644
index 0000000..34dece7
--- /dev/null
+++ b/include/2geom/symbolic/mvpoly-tools.h
@@ -0,0 +1,690 @@
+/*
+ * Routines that extend univariate polynomial functions
+ * to multi-variate polynomial exploiting recursion at compile time
+ *
+ * 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_MVPOLY_TOOLS_H_
+#define _GEOM_SL_MVPOLY_TOOLS_H_
+
+
+#include <2geom/exception.h>
+
+#include <2geom/symbolic/multi-index.h>
+#include <2geom/symbolic/unity-builder.h>
+#include <2geom/symbolic/polynomial.h>
+
+#include <array>
+#include <functional>
+#include <iostream>
+#include <type_traits>
+
+
+namespace Geom { namespace SL {
+
+/*
+ * rank<PolyT>::value == total amount of indeterminates
+ * x_(0),x_(1),...,x_(rank-1) that belong to type PolyT
+ */
+
+template <typename T>
+struct rank
+{
+ static const size_t value = 0;
+};
+
+template <typename CoeffT>
+struct rank< Polynomial<CoeffT> >
+{
+ static const size_t value = rank<CoeffT>::value + 1;
+};
+
+
+/*
+ * mvpoly<N, CoeffT> creates a multi-variate polynomial type
+ * by nesting N-1 Polynomial class template and setting
+ * the coefficient type of the most nested Polynomial to CoeffT
+ * example: mvpoly<3, double>::type is the same than
+ * Polynomial< Polynomial< Polynomial<double> > >
+ */
+
+template <size_t N, typename CoeffT>
+struct mvpoly
+{
+ typedef Polynomial<typename mvpoly<N-1, CoeffT>::type> type;
+ typedef CoeffT coeff_type;
+ static const size_t rank = N;
+
+ /*
+ * computes the lexicographic degree of the mv polynomial p
+ */
+ static
+ multi_index_type lex_degree (type const& p)
+ {
+ multi_index_type D(N);
+ lex_degree_impl<0>(p, D);
+ return D;
+ }
+
+ /*
+ * Returns in the out-parameter D an N-sequence where each entry value
+ * represents the max degree of the polynomial related to the passed
+ * index I, if one index value in I is greater than the related max degree
+ * the routine returns false otherwise it returns true.
+ * This routine can be used to test if a given multi-index I is related
+ * to an actual initialized coefficient.
+ */
+ static
+ bool max_degree (type const& p,
+ multi_index_type& D,
+ multi_index_type const& I)
+ {
+ if (I.size() != N)
+ THROW_RANGEERROR ("multi-index with wrong length");
+ D.resize(N);
+ return max_degree_impl<0>(p, D, I);
+ }
+
+ /*
+ * Returns in the out-parameter D an N-sequence where each entry value
+ * represents the real degree of the polynomial related to the passed
+ * index I, if one index value in I is greater than the related real degree
+ * the routine returns false otherwise it returns true.
+ * This routine can be used to test if a given multi-index I is related
+ * to an actual initialized and non-zero coefficient.
+ */
+
+ static
+ bool real_degree (type const& p,
+ multi_index_type& D,
+ multi_index_type const& I)
+ {
+ if (I.size() != N)
+ THROW_RANGEERROR ("multi-index with wrong length");
+ D.resize(N);
+ return real_degree_impl<0>(p, D, I);
+ }
+
+ /*
+ * Multiplies p by X^I
+ */
+ static
+ void shift(type & p, multi_index_type const& I)
+ {
+ if (I.size() != N)
+ THROW_RANGEERROR ("multi-index with wrong length");
+ shift_impl<0>(p, I);
+ }
+
+ /*
+ * mv poly evaluation:
+ * T can be any type that is able to be += with the coefficient type
+ * and that can be *= with the same type T moreover a specialization
+ * of zero struct for the type T is needed
+ */
+ template <typename T>
+ static
+ T evaluate(type const& p, std::array<T, N> const& X)
+ {
+ return evaluate_impl<T, 0>(p, X);
+ }
+
+ /*
+ * trim leading zero coefficients
+ */
+ static
+ void normalize(type & p)
+ {
+ p.normalize();
+ for (size_t k = 0; k < p.size(); ++k)
+ mvpoly<N-1, CoeffT>::normalize(p[k]);
+ }
+
+ /*
+ * Applies the unary operator "op" to each coefficient of p with rank M.
+ * For instance when M = 0 op is applied to each coefficient
+ * of the multi-variate polynomial p.
+ * When M < N the function call recursively the for_each routine
+ * for p.real_degree() times, when M == N the operator "op" is invoked on p;
+ */
+ template <size_t M>
+ static
+ void for_each
+ (type & p,
+ std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op,
+ typename std::enable_if_t<(M < N)>* = 0)
+ {
+ for (size_t k = 0; k <= p.real_degree(); ++k)
+ {
+ mvpoly<N-1, CoeffT>::template for_each<M>(p[k], op);
+ }
+ }
+
+ template <size_t M>
+ static
+ void for_each
+ (type & p,
+ std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op,
+ typename std::enable_if_t<(M == N)>* = 0)
+ {
+ op(p);
+ }
+
+ // this is only an helper function to be passed to the for_each routine
+ static
+ void multiply_to (type& p, type const& q)
+ {
+ p *= q;
+ }
+
+ private:
+ template <size_t i>
+ static
+ void lex_degree_impl (type const& p, multi_index_type& D)
+ {
+ D[i] = p.real_degree();
+ mvpoly<N-1, CoeffT>::template lex_degree_impl<i+1>(p[D[i]], D);
+ }
+
+ template <size_t i>
+ static
+ bool max_degree_impl (type const& p,
+ multi_index_type& D,
+ multi_index_type const& I)
+ {
+ D[i] = p.max_degree();
+ if (I[i] > D[i]) return false;
+ return
+ mvpoly<N-1, CoeffT>::template max_degree_impl<i+1>(p[I[i]], D, I);
+ }
+
+ template <size_t i>
+ static
+ bool real_degree_impl (type const& p,
+ multi_index_type& D,
+ multi_index_type const& I)
+ {
+ D[i] = p.real_degree();
+ if (I[i] > D[i]) return false;
+ return
+ mvpoly<N-1, CoeffT>::template real_degree_impl<i+1>(p[I[i]], D, I);
+ }
+
+ template <size_t i>
+ static
+ void shift_impl(type & p, multi_index_type const& I)
+ {
+ p <<= I[i];
+ for (size_t k = 0; k < p.size(); ++k)
+ {
+ mvpoly<N-1, CoeffT>::template shift_impl<i+1>(p[k], I);
+ }
+ }
+
+ template <typename T, size_t i>
+ static
+ T evaluate_impl(type const& p, std::array<T, N+i> const& X)
+ {
+// T r = zero<T>()();
+// for (size_t k = p.max_degree(); k > 0; --k)
+// {
+// r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[k], X);
+// r *= X[i];
+// }
+// r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[0], X);
+
+ int n = p.max_degree();
+ T r = mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[n], X);
+ for (int k = n - 1; k >= 0; --k)
+ {
+ r *= X[i];
+ r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[k], X);
+ }
+ return r;
+ }
+
+ template <size_t M, typename C>
+ friend struct mvpoly;
+};
+
+/*
+ * rank 0 mv poly, that is a scalar value (usually a numeric value),
+ * the routines implemented here are used only to stop recursion
+ * (but for_each)
+ */
+template< typename CoeffT >
+struct mvpoly<0, CoeffT>
+{
+ typedef CoeffT type;
+ typedef CoeffT coeff_type;
+ static const size_t rank = 0;
+
+ template <size_t M>
+ static
+ void for_each
+ (type & p,
+ std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op,
+ typename std::enable_if_t<(M == 0)>* = 0)
+ {
+ op(p);
+ }
+
+ // multiply_to and divide_to are only helper functions
+ // to be passed to the for_each routine
+ static
+ void multiply_to (type& p, type const& q)
+ {
+ p *= q;
+ }
+
+ static
+ void divide_to (type& p, type const& c)
+ {
+ p /= c;
+ }
+
+ private:
+ template <size_t i>
+ static
+ void lex_degree_impl (type const &/*p*/, multi_index_type&/*D*/)
+ {
+ return;
+ }
+
+ template <size_t i>
+ static
+ bool max_degree_impl (type const &/*p*/,
+ multi_index_type &/*D*/,
+ multi_index_type const &/*I*/)
+ {
+ return true;
+ }
+
+ template <size_t i>
+ static
+ bool real_degree_impl (type const &/*p*/,
+ multi_index_type &/*D*/,
+ multi_index_type const &/*I*/)
+ {
+ return true;
+ }
+
+ template <size_t i>
+ static
+ void shift_impl(type &/*p*/, multi_index_type const &/*I*/)
+ {}
+
+ template <typename T, size_t i>
+ static
+ T evaluate_impl(type const &p, std::array<T, i> const &/*X*/)
+ {
+ return p;
+ }
+
+ static
+ void normalize(type &/*p*/)
+ {}
+
+
+ template <size_t M, typename C>
+ friend struct mvpoly;
+};
+
+
+/*
+ * monomial::make generate a mv-poly made up by a single term:
+ * monomial::make<N>(I,c) == c*X^I, where X=(x_(0), .., x_(N-1))
+ */
+
+template <size_t N, typename CoeffT>
+struct monomial
+{
+ typedef typename mvpoly<N, CoeffT>::type poly_type;
+
+ static inline
+ poly_type make(multi_index_type const& I, CoeffT c)
+ {
+ if (I.size() != N) // an exponent for each indeterminate
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return make_impl<0>(I, c);
+ }
+
+ private:
+ // at i-th level of recursion I need to pick up the i-th exponent in "I"
+ // so I pass i as a template parameter, this trick is needed to avoid
+ // to create a new multi-index at each recursion level:
+ // (J = I[std::slice[1, I.size()-1, 1)]) that will be more expensive
+ template <size_t i>
+ static
+ poly_type make_impl(multi_index_type const& I, CoeffT c)
+ {
+ poly_type p(monomial<N-1,CoeffT>::template make_impl<i+1>(I, c), I[i]);
+ return p;
+ }
+
+ // make_impl private require that monomial classes to be each other friend
+ template <size_t M, typename C>
+ friend struct monomial;
+};
+
+
+// case N = 0 for stopping recursion
+template <typename CoeffT>
+struct monomial<0, CoeffT>
+{
+ private:
+ template <size_t i>
+ static
+ CoeffT make_impl(multi_index_type const &/*I*/, CoeffT c)
+ {
+ return c;
+ }
+
+ template<size_t N, typename C>
+ friend struct monomial;
+};
+
+
+/*
+ * coefficient<N, PolyT>
+ *
+ * N should be in the range [0, rank<PolyT>-1]
+ *
+ * "type" == the type of the coefficient of the polynomial with
+ * rank = rank<PolyT> - N - 1, that is it is the type of the object returned
+ * by applying the operator[] of a Polynomial object N+1 times;
+ *
+ * "zero" represents the zero element (in the group theory meaning)
+ * for the coefficient type "type"; having it as a static class member
+ * allows to return always a (const) reference by the "get_safe" method
+ *
+ * get(p, I) returns the coefficient of the monomial X^I
+ * this method doesn't check if such a coefficient really exists,
+ * so it's up to the user checking that the passed multi-index I is
+ * not out of range
+ *
+ * get_safe(p, I) returns the coefficient of the monomial X^I
+ * in case such a coefficient doesn't really exist "zero" is returned
+ *
+ * set_safe(p, I, c) set the coefficient of the monomial X^I to "c"
+ * in case such a coefficient doesn't really exist this method creates it
+ * and creates all monomials X^J with J < I that don't exist yet, setting
+ * their coefficients to "zero";
+ * (with J < I we mean "<" wrt the lexicographic order)
+ *
+ */
+
+template <size_t N, typename T>
+struct coefficient
+{
+};
+
+
+template <size_t N, typename CoeffT>
+struct coefficient< N, Polynomial<CoeffT> >
+{
+ typedef typename coefficient<N-1, CoeffT>::type type;
+ typedef Polynomial<CoeffT> poly_type;
+
+ static const type zero;
+
+ static
+ type const& get(poly_type const& p, multi_index_type const& I)
+ {
+ if (I.size() != N+1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return get_impl<0>(p, I);
+ }
+
+ static
+ type & get(poly_type & p, multi_index_type const& I)
+ {
+ if (I.size() != N+1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return get_impl<0>(p, I);
+ }
+
+ static
+ type const& get_safe(poly_type const& p, multi_index_type const& I)
+ {
+ if (I.size() != N+1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return get_safe_impl<0>(p, I);
+ }
+
+ static
+ void set_safe(poly_type & p, multi_index_type const& I, type const& c)
+ {
+ if (I.size() != N+1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return set_safe_impl<0>(p, I, c);
+ }
+
+ private:
+ template <size_t i>
+ static
+ type const& get_impl(poly_type const& p, multi_index_type const& I)
+ {
+ return coefficient<N-1, CoeffT>::template get_impl<i+1>(p[I[i]], I);
+ }
+
+ template <size_t i>
+ static
+ type & get_impl(poly_type & p, multi_index_type const& I)
+ {
+ return coefficient<N-1, CoeffT>::template get_impl<i+1>(p[I[i]], I);
+ }
+
+ template <size_t i>
+ static
+ type const& get_safe_impl(poly_type const& p, multi_index_type const& I)
+ {
+ if (I[i] > p.max_degree())
+ {
+ return zero;
+ }
+ else
+ {
+ return
+ coefficient<N-1, CoeffT>::template get_safe_impl<i+1>(p[I[i]], I);
+ }
+ }
+
+ template <size_t i>
+ static
+ void set_safe_impl(poly_type & p, multi_index_type const& I, type const& c)
+ {
+ if (I[i] > p.max_degree())
+ {
+ multi_index_type J = shift(I, i+1);
+ CoeffT m = monomial<N, type>::make(J, c);
+ p.coefficient(I[i], m);
+ }
+ else
+ {
+ coefficient<N-1, CoeffT>::template set_safe_impl<i+1>(p[I[i]], I, c);
+ }
+ }
+
+ template<size_t M, typename T>
+ friend struct coefficient;
+
+};
+
+// initialization of static member zero
+template <size_t N, typename CoeffT>
+const typename coefficient< N, Polynomial<CoeffT> >::type
+coefficient< N, Polynomial<CoeffT> >::zero
+ = Geom::SL::zero<typename coefficient< N, Polynomial<CoeffT> >::type >()();
+
+
+// case N = 0 for stopping recursion
+template <typename CoeffT>
+struct coefficient< 0, Polynomial<CoeffT> >
+{
+ typedef CoeffT type;
+ typedef Polynomial<CoeffT> poly_type;
+
+ static const type zero;
+
+ static
+ type const& get(poly_type const& p, multi_index_type const& I)
+ {
+ if (I.size() != 1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return p[I[0]];
+ }
+
+ static
+ type & get(poly_type & p, multi_index_type const& I)
+ {
+ if (I.size() != 1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return p[I[0]];
+ }
+
+ static
+ type const& get_safe(poly_type const& p, multi_index_type const& I)
+ {
+ if (I.size() != 1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ return p.coefficient(I[0]);
+ }
+
+ static
+ void set_safe(poly_type & p, multi_index_type const& I, type const& c)
+ {
+ if (I.size() != 1)
+ THROW_RANGEERROR ("multi-index with wrong length");
+
+ p.coefficient(I[0], c);
+ }
+
+ private:
+ template <size_t i>
+ static
+ type const& get_impl(poly_type const& p, multi_index_type const& I)
+ {
+ return p[I[i]];
+ }
+
+ template <size_t i>
+ static
+ type & get_impl(poly_type & p, multi_index_type const& I)
+ {
+ return p[I[i]];
+ }
+
+ template <size_t i>
+ static
+ type const& get_safe_impl(poly_type const& p, multi_index_type const& I)
+ {
+ return p.coefficient(I[i]);
+ }
+
+ template <size_t i>
+ static
+ void set_safe_impl(poly_type & p, multi_index_type const& I, type const& c)
+ {
+ p.coefficient(I[i], c);
+ }
+
+ template<size_t M, typename T>
+ friend struct coefficient;
+};
+
+// initialization of static member zero
+template <typename CoeffT>
+const typename coefficient< 0, Polynomial<CoeffT> >::type
+coefficient< 0, Polynomial<CoeffT> >::zero
+ = Geom::SL::zero<typename coefficient< 0, Polynomial<CoeffT> >::type >()();
+
+
+/*
+ * ordering types:
+ * lex : lexicographic ordering
+ * ilex : inverse lexicographic ordering
+ * max_lex : max degree + lexicographic ordering for disambiguation
+ *
+ */
+
+namespace ordering
+{
+ struct lex; // WARNING: at present only lex ordering is supported
+ struct ilex;
+ struct max_lex;
+}
+
+
+/*
+ * degree of a mv poly wrt a given ordering
+ */
+
+template <size_t N, typename CoeffT, typename OrderT = ordering::lex>
+struct mvdegree
+{};
+
+template <size_t N, typename CoeffT>
+struct mvdegree<N, CoeffT, ordering::lex>
+{
+ typedef typename mvpoly<N, CoeffT>::type poly_type;
+ typedef ordering::lex ordering;
+
+ static
+ multi_index_type value(poly_type const& p)
+ {
+ return Geom::SL::mvpoly<N, CoeffT>::lex_degree(p);
+ }
+};
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+#endif // _GEOM_SL_MVPOLY_TOOLS_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/include/2geom/symbolic/polynomial.h b/include/2geom/symbolic/polynomial.h
new file mode 100644
index 0000000..fea7e6c
--- /dev/null
+++ b/include/2geom/symbolic/polynomial.h
@@ -0,0 +1,569 @@
+/*
+ * Polynomial<CoeffT> class template
+ *
+ * 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_POLYNOMIAL_H_
+#define _GEOM_SL_POLYNOMIAL_H_
+
+
+#include <2geom/symbolic/unity-builder.h>
+
+#include <vector>
+#include <string>
+
+#include <2geom/exception.h>
+
+
+
+
+namespace Geom { namespace SL {
+
+/*
+ * Polynomial<CoeffT> class template
+ *
+ * It represents a generic univariate polynomial with coefficients
+ * of type CoeffT. One way to get a multi-variate polynomial is
+ * to utilize a Polynomial instantiation as coefficient type
+ * in a recursive style.
+ *
+ */
+
+template< typename CoeffT >
+class Polynomial
+{
+ public:
+ typedef CoeffT coeff_type;
+ typedef std::vector<coeff_type> coeff_container_t;
+ typedef typename coeff_container_t::iterator iterator;
+ typedef typename coeff_container_t::const_iterator const_iterator;
+
+ /*
+ * a Polynomial should be never empty
+ */
+ Polynomial()
+ {
+ m_coeff.push_back(zero_coeff);
+ }
+
+ explicit
+ Polynomial(CoeffT const& c, size_t i = 0)
+ {
+ m_coeff.resize(i, zero_coeff);
+ m_coeff.push_back(c);
+ }
+
+ /*
+ * forwarding of some std::vector methods
+ */
+
+ size_t size() const
+ {
+ return m_coeff.size();
+ }
+
+ const_iterator begin() const
+ {
+ return m_coeff.begin();
+ }
+
+ const_iterator end() const
+ {
+ return m_coeff.end();
+ }
+
+ iterator begin()
+ {
+ return m_coeff.begin();
+ }
+
+ iterator end()
+ {
+ return m_coeff.end();
+ }
+
+ void reserve(size_t n)
+ {
+ m_coeff.reserve(n);
+ }
+
+ size_t capacity() const
+ {
+ return m_coeff.capacity();
+ }
+
+ /*
+ * degree of the term with the highest degree
+ * and an initialized coefficient (even if zero)
+ */
+ size_t max_degree() const
+ {
+ if (size() == 0)
+ THROW_INVARIANTSVIOLATION (0);
+
+ return (size() - 1);
+ }
+
+ void max_degree(size_t n)
+ {
+ m_coeff.resize(n+1, zero_coeff);
+ }
+
+ /*
+ * degree of the term with the highest degree
+ * and an initialized coefficient that is not null
+ */
+ size_t real_degree() const
+ {
+ if (size() == 0)
+ THROW_INVARIANTSVIOLATION (0);
+
+ const_iterator it = end() - 1;
+ for (; it != begin(); --it)
+ {
+ if (*it != zero_coeff) break;
+ }
+ size_t i = static_cast<size_t>(it - begin());
+ return i;
+ }
+
+ bool is_zero() const
+ {
+ if (size() == 0)
+ THROW_INVARIANTSVIOLATION (0);
+
+ if (real_degree() != 0) return false;
+ if (m_coeff[0] != zero_coeff) return false;
+ return true;
+ }
+
+ /*
+ * trim leading zero coefficients
+ * after calling normalize max_degree == real_degree
+ */
+ void normalize()
+ {
+ size_t rd = real_degree();
+ if (rd != max_degree())
+ {
+ m_coeff.erase(begin() + rd + 1, end());
+ }
+ }
+
+ coeff_type const& operator[] (size_t i) const
+ {
+ return m_coeff[i];
+ }
+
+ coeff_type & operator[] (size_t i)
+ {
+ return m_coeff[i];
+ }
+
+ // safe coefficient getter routine
+ coeff_type const& coefficient(size_t i) const
+ {
+ if (i > max_degree())
+ {
+ return zero_coeff;
+ }
+ else
+ {
+ return m_coeff[i];
+ }
+ }
+
+ // safe coefficient setter routine
+ void coefficient(size_t i, coeff_type const& c)
+ {
+ //std::cerr << "i: " << i << " c: " << c << std::endl;
+ if (i > max_degree())
+ {
+ if (c == zero_coeff) return;
+ reserve(i+1);
+ m_coeff.resize(i, zero_coeff);
+ m_coeff.push_back(c);
+ }
+ else
+ {
+ m_coeff[i] = c;
+ }
+ }
+
+ coeff_type const& leading_coefficient() const
+ {
+ return m_coeff[real_degree()];
+ }
+
+ coeff_type & leading_coefficient()
+ {
+ return m_coeff[real_degree()];
+ }
+
+ /*
+ * polynomail evaluation:
+ * T can be any type that is able to be + and * with the coefficient type
+ */
+ template <typename T>
+ T operator() (T const& x) const
+ {
+ T r = zero<T>()();
+ for(size_t i = max_degree(); i > 0; --i)
+ {
+ r += (*this)[i];
+ r *= x;
+ }
+ r += (*this)[0];
+ return r;
+ }
+
+ // opposite polynomial
+ Polynomial operator-() const
+ {
+ Polynomial r;
+ // we need r.m_coeff to be empty so we can utilize push_back
+ r.m_coeff.pop_back();
+ r.reserve(size());
+ for(size_t i = 0; i < size(); ++i)
+ {
+ r.m_coeff.push_back( -(*this)[i] );
+ }
+ return r;
+ }
+
+ /*
+ * polynomial-polynomial mutating operators
+ */
+
+ Polynomial& operator+=(Polynomial const& p)
+ {
+ size_t sz = std::min(size(), p.size());
+ for (size_t i = 0; i < sz; ++i)
+ {
+ (*this)[i] += p[i];
+ }
+ if (size() < p.size())
+ {
+ m_coeff.insert(end(), p.begin() + size(), p.end());
+ }
+ return (*this);
+ }
+
+ Polynomial& operator-=(Polynomial const& p)
+ {
+ size_t sz = std::min(size(), p.size());
+ for (size_t i = 0; i < sz; ++i)
+ {
+ (*this)[i] -= p[i];
+ }
+ reserve(p.size());
+ for(size_t i = sz; i < p.size(); ++i)
+ {
+ m_coeff.push_back( -p[i] );
+ }
+ return (*this);
+ }
+
+ Polynomial& operator*=(Polynomial const& p)
+ {
+ Polynomial r;
+ r.m_coeff.resize(size() + p.size() - 1, zero_coeff);
+
+ for (size_t i = 0; i < size(); ++i)
+ {
+ for (size_t j = 0; j < p.size(); ++j)
+ {
+ r[i+j] += (*this)[i] * p[j];
+ }
+ }
+ (*this) = r;
+ return (*this);
+ }
+
+ /*
+ * equivalent to multiply by x^n
+ */
+ Polynomial& operator<<=(size_t n)
+ {
+ m_coeff.insert(begin(), n, zero_coeff);
+ return (*this);
+ }
+
+ /*
+ * polynomial-coefficient mutating operators
+ */
+
+ Polynomial& operator=(coeff_type const& c)
+ {
+ m_coeff[0] = c;
+ return (*this);
+ }
+
+ Polynomial& operator+=(coeff_type const& c)
+ {
+ (*this)[0] += c;
+ return (*this);
+ }
+
+ Polynomial& operator-=(coeff_type const& c)
+ {
+ (*this)[0] -= c;
+ return (*this);
+ }
+
+ Polynomial& operator*=(coeff_type const& c)
+ {
+ for (size_t i = 0; i < size(); ++i)
+ {
+ (*this)[i] *= c;
+ }
+ return (*this);
+ }
+
+ // return the poly in a string form
+ std::string str() const;
+
+ private:
+ // with zero_coeff defined as a static data member
+ // coefficient(size_t i) safe get method can always
+ // return a (const) reference
+ static const coeff_type zero_coeff;
+ coeff_container_t m_coeff;
+
+}; // end class Polynomial
+
+
+/*
+ * zero and one element spezcialization for Polynomial
+ */
+
+template< typename CoeffT >
+struct zero<Polynomial<CoeffT>, false>
+{
+ Polynomial<CoeffT> operator() () const
+ {
+ CoeffT zc = zero<CoeffT>()();
+ Polynomial<CoeffT> z(zc);
+ return z;
+ }
+};
+
+template< typename CoeffT >
+struct one<Polynomial<CoeffT>, false>
+{
+ Polynomial<CoeffT> operator() ()
+ {
+ CoeffT _1c = one<CoeffT>()();
+ Polynomial<CoeffT> _1(_1c);
+ return _1;
+ }
+};
+
+
+/*
+ * initialization of Polynomial static data members
+ */
+
+template< typename CoeffT >
+const typename Polynomial<CoeffT>::coeff_type Polynomial<CoeffT>::zero_coeff
+ = zero<typename Polynomial<CoeffT>::coeff_type>()();
+
+/*
+ * Polynomial - Polynomial binary mathematical operators
+ */
+
+template< typename CoeffT >
+inline
+bool operator==(Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q)
+{
+ size_t d = p.real_degree();
+ if (d != q.real_degree()) return false;
+ for (size_t i = 0; i <= d; ++i)
+ {
+ if (p[i] != q[i]) return false;
+ }
+ return true;
+}
+
+template< typename CoeffT >
+inline
+bool operator!=(Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q)
+{
+ return !(p == q);
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator+( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q )
+{
+ Polynomial<CoeffT> r(p);
+ r += q;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator-( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q )
+{
+ Polynomial<CoeffT> r(p);
+ r -= q;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator*( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q )
+{
+ Polynomial<CoeffT> r(p);
+ r *= q;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT> operator<<(Polynomial<CoeffT> const& p, size_t n)
+{
+ Polynomial<CoeffT> r(p);
+ r <<= n;
+ return r;
+}
+
+
+/*
+ * polynomial-coefficient and coefficient-polynomial mathematical operators
+ */
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator+( Polynomial<CoeffT> const& p, CoeffT const& c )
+{
+ Polynomial<CoeffT> r(p);
+ r += c;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator+( CoeffT const& c, Polynomial<CoeffT> const& p)
+{
+ return (p + c);
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator-( Polynomial<CoeffT> const& p, CoeffT const& c )
+{
+ Polynomial<CoeffT> r(p);
+ r -= c;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator-( CoeffT const& c, Polynomial<CoeffT> const& p)
+{
+ return (p - c);
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator*( Polynomial<CoeffT> const& p, CoeffT const& c )
+{
+ Polynomial<CoeffT> r(p);
+ r *= c;
+ return r;
+}
+
+template< typename CoeffT >
+inline
+Polynomial<CoeffT>
+operator*( CoeffT const& c, Polynomial<CoeffT> const& p)
+{
+ return (p * c);
+}
+
+
+/*
+ * operator<< extension for printing Polynomial
+ * and str() method for transforming a Polynomial into a string
+ */
+
+template< typename charT, typename CoeffT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const Polynomial<CoeffT> & p)
+{
+ if (p.size() == 0) return os;
+ os << "{" << p[0];
+ for (size_t i = 1; i < p.size(); ++i)
+ {
+ os << ", " << p[i];
+ }
+ os << "}";
+ return os;
+}
+
+
+template< typename CoeffT >
+inline
+std::string Polynomial<CoeffT>::str() const
+{
+ std::ostringstream oss;
+ oss << (*this);
+ return oss.str();
+}
+
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+
+
+#endif // _GEOM_SL_POLYNOMIAL_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/include/2geom/symbolic/unity-builder.h b/include/2geom/symbolic/unity-builder.h
new file mode 100644
index 0000000..cb8046f
--- /dev/null
+++ b/include/2geom/symbolic/unity-builder.h
@@ -0,0 +1,102 @@
+/*
+ * Routines to make up "zero" and "one" elements of a ring
+ *
+ * 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_UNITY_BUILDER_H_
+#define _GEOM_SL_UNITY_BUILDER_H_
+
+
+#include <type_traits>
+
+
+
+namespace Geom { namespace SL {
+
+
+/*
+ * zero builder function class type
+ *
+ * made up a zero element, in the algebraic ring theory meaning,
+ * for the type T
+ */
+
+template< typename T, bool numeric = std::is_arithmetic<T>::value >
+struct zero
+{};
+
+// specialization for basic numeric type
+template< typename T >
+struct zero<T, true>
+{
+ T operator() () const
+ {
+ return 0;
+ }
+};
+
+
+/*
+ * one builder function class type
+ *
+ * made up a one element, in the algebraic ring theory meaning,
+ * for the type T
+ */
+
+template< typename T, bool numeric = std::is_arithmetic<T>::value >
+struct one
+{};
+
+// specialization for basic numeric type
+template< typename T >
+struct one<T, true>
+{
+ T operator() ()
+ {
+ return 1;
+ }
+};
+
+} /*end namespace Geom*/ } /*end namespace SL*/
+
+
+#endif // _GEOM_SL_UNITY_BUILDER_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/include/2geom/transforms.h b/include/2geom/transforms.h
new file mode 100644
index 0000000..cc55e29
--- /dev/null
+++ b/include/2geom/transforms.h
@@ -0,0 +1,370 @@
+/**
+ * @file
+ * @brief Affine transformation classes
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Johan Engelen
+ *
+ * Copyright ?-2012 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 LIB2GEOM_SEEN_TRANSFORMS_H
+#define LIB2GEOM_SEEN_TRANSFORMS_H
+
+#include <cmath>
+#include <2geom/forward.h>
+#include <2geom/affine.h>
+#include <2geom/angle.h>
+#include <boost/concept/assert.hpp>
+
+namespace Geom {
+
+/** @brief Type requirements for transforms.
+ * @ingroup Concepts */
+template <typename T>
+struct TransformConcept {
+ T t, t2;
+ Affine m;
+ Point p;
+ bool bool_;
+ Coord epsilon;
+ void constraints() {
+ m = t; //implicit conversion
+ m *= t;
+ m = m * t;
+ m = t * m;
+ p *= t;
+ p = p * t;
+ t *= t;
+ t = t * t;
+ t = pow(t, 3);
+ bool_ = (t == t);
+ bool_ = (t != t);
+ t = T::identity();
+ t = t.inverse();
+ bool_ = are_near(t, t2);
+ bool_ = are_near(t, t2, epsilon);
+ }
+};
+
+/** @brief Base template for transforms.
+ * This class is an implementation detail and should not be used directly. */
+template <typename T>
+class TransformOperations
+ : boost::equality_comparable< T
+ , boost::multipliable< T
+ > >
+{
+public:
+ template <typename T2>
+ Affine operator*(T2 const &t) const {
+ Affine ret(*static_cast<T const*>(this)); ret *= t; return ret;
+ }
+};
+
+/** @brief Integer exponentiation for transforms.
+ * Negative exponents will yield the corresponding power of the inverse. This function
+ * can also be applied to matrices.
+ * @param t Affine or transform to exponantiate
+ * @param n Exponent
+ * @return \f$A^n\f$ if @a n is positive, \f$(A^{-1})^n\f$ if negative, identity if zero.
+ * @ingroup Transforms */
+template <typename T>
+T pow(T const &t, int n) {
+ BOOST_CONCEPT_ASSERT((TransformConcept<T>));
+ if (n == 0) return T::identity();
+ T result(T::identity());
+ T x(n < 0 ? t.inverse() : t);
+ if (n < 0) n = -n;
+ while ( n ) { // binary exponentiation - fast
+ if ( n & 1 ) { result *= x; --n; }
+ x *= x; n /= 2;
+ }
+ return result;
+}
+
+/** @brief Translation by a vector.
+ * @ingroup Transforms */
+class Translate
+ : public TransformOperations< Translate >
+{
+ Point vec;
+public:
+ /// Create a translation that doesn't do anything.
+ Translate() : vec(0, 0) {}
+ /// Construct a translation from its vector.
+ Translate(Point const &p) : vec(p) {}
+ /// Construct a translation from its coordinates.
+ Translate(Coord x, Coord y) : vec(x, y) {}
+
+ operator Affine() const { Affine ret(1, 0, 0, 1, vec[X], vec[Y]); return ret; }
+ Coord operator[](Dim2 dim) const { return vec[dim]; }
+ Coord operator[](unsigned dim) const { return vec[dim]; }
+ Translate &operator*=(Translate const &o) { vec += o.vec; return *this; }
+ bool operator==(Translate const &o) const { return vec == o.vec; }
+
+ Point vector() const { return vec; }
+ /// Get the inverse translation.
+ Translate inverse() const { return Translate(-vec); }
+ /// Get a translation that doesn't do anything.
+ static Translate identity() { Translate ret; return ret; }
+
+ friend class Point;
+};
+
+inline bool are_near(Translate const &a, Translate const &b, Coord eps=EPSILON) {
+ return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps);
+}
+
+/** @brief Scaling from the origin.
+ * During scaling, the point (0,0) will not move. To obtain a scale with a different
+ * invariant point, combine with translation to the origin and back.
+ * @ingroup Transforms */
+class Scale
+ : public TransformOperations< Scale >
+{
+ Point vec;
+public:
+ /// Create a scaling that doesn't do anything.
+ Scale() : vec(1, 1) {}
+ /// Create a scaling from two scaling factors given as coordinates of a point.
+ explicit Scale(Point const &p) : vec(p) {}
+ /// Create a scaling from two scaling factors.
+ Scale(Coord x, Coord y) : vec(x, y) {}
+ /// Create an uniform scaling from a single scaling factor.
+ explicit Scale(Coord s) : vec(s, s) {}
+ inline operator Affine() const { Affine ret(vec[X], 0, 0, vec[Y], 0, 0); return ret; }
+
+ Coord operator[](Dim2 d) const { return vec[d]; }
+ Coord operator[](unsigned d) const { return vec[d]; }
+ //TODO: should we keep these mutators? add them to the other transforms?
+ Coord &operator[](Dim2 d) { return vec[d]; }
+ Coord &operator[](unsigned d) { return vec[d]; }
+ Scale &operator*=(Scale const &b) { vec[X] *= b[X]; vec[Y] *= b[Y]; return *this; }
+ bool operator==(Scale const &o) const { return vec == o.vec; }
+
+ Point vector() const { return vec; }
+ Scale inverse() const { return Scale(1./vec[0], 1./vec[1]); }
+ static Scale identity() { Scale ret; return ret; }
+
+ friend class Point;
+};
+
+inline bool are_near(Scale const &a, Scale const &b, Coord eps=EPSILON) {
+ return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps);
+}
+
+/** @brief Rotation around the origin.
+ * Combine with translations to the origin and back to get a rotation around a different point.
+ * @ingroup Transforms */
+class Rotate
+ : public TransformOperations< Rotate >
+{
+ Point vec; ///< @todo Convert to storing the angle, as it's more space-efficient.
+public:
+ /// Construct a zero-degree rotation.
+ Rotate() : vec(1, 0) {}
+ /** @brief Construct a rotation from its angle in radians.
+ * Positive arguments correspond to counter-clockwise rotations (if Y grows upwards). */
+ explicit Rotate(Coord theta) : vec(Point::polar(theta)) {}
+ /// Construct a rotation from its characteristic vector.
+ explicit Rotate(Point const &p) : vec(unit_vector(p)) {}
+ /// Construct a rotation from the coordinates of its characteristic vector.
+ explicit Rotate(Coord x, Coord y) { Rotate(Point(x, y)); }
+ operator Affine() const { Affine ret(vec[X], vec[Y], -vec[Y], vec[X], 0, 0); return ret; }
+
+ /** @brief Get the characteristic vector of the rotation.
+ * @return A vector that would be obtained by applying this transform to the X versor. */
+ Point vector() const { return vec; }
+ Coord angle() const { return atan2(vec); }
+ Coord operator[](Dim2 dim) const { return vec[dim]; }
+ Coord operator[](unsigned dim) const { return vec[dim]; }
+ Rotate &operator*=(Rotate const &o) { vec *= o; return *this; }
+ bool operator==(Rotate const &o) const { return vec == o.vec; }
+ Rotate inverse() const {
+ Rotate r;
+ r.vec = Point(vec[X], -vec[Y]);
+ return r;
+ }
+ /// @brief Get a zero-degree rotation.
+ static Rotate identity() { Rotate ret; return ret; }
+ /** @brief Construct a rotation from its angle in degrees.
+ * Positive arguments correspond to clockwise rotations if Y grows downwards. */
+ static Rotate from_degrees(Coord deg) {
+ Coord rad = (deg / 180.0) * M_PI;
+ return Rotate(rad);
+ }
+ static Affine around(Point const &p, Coord angle);
+
+ friend class Point;
+};
+
+inline bool are_near(Rotate const &a, Rotate const &b, Coord eps=EPSILON) {
+ return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps);
+}
+
+/** @brief Common base for shearing transforms.
+ * This class is an implementation detail and should not be used directly.
+ * @ingroup Transforms */
+template <typename S>
+class ShearBase
+ : public TransformOperations< S >
+{
+protected:
+ Coord f;
+ ShearBase(Coord _f) : f(_f) {}
+public:
+ Coord factor() const { return f; }
+ void setFactor(Coord nf) { f = nf; }
+ S &operator*=(S const &s) { f += s.f; return static_cast<S &>(*this); }
+ bool operator==(S const &s) const { return f == s.f; }
+ S inverse() const { S ret(-f); return ret; }
+ static S identity() { S ret(0); return ret; }
+
+ friend class Point;
+ friend class Affine;
+};
+
+/** @brief Horizontal shearing.
+ * Points on the X axis will not move. Combine with translations to get a shear
+ * with a different invariant line.
+ * @ingroup Transforms */
+class HShear
+ : public ShearBase<HShear>
+{
+public:
+ explicit HShear(Coord h) : ShearBase<HShear>(h) {}
+ operator Affine() const { Affine ret(1, 0, f, 1, 0, 0); return ret; }
+};
+
+inline bool are_near(HShear const &a, HShear const &b, Coord eps=EPSILON) {
+ return are_near(a.factor(), b.factor(), eps);
+}
+
+/** @brief Vertical shearing.
+ * Points on the Y axis will not move. Combine with translations to get a shear
+ * with a different invariant line.
+ * @ingroup Transforms */
+class VShear
+ : public ShearBase<VShear>
+{
+public:
+ explicit VShear(Coord h) : ShearBase<VShear>(h) {}
+ operator Affine() const { Affine ret(1, f, 0, 1, 0, 0); return ret; }
+};
+
+inline bool are_near(VShear const &a, VShear const &b, Coord eps=EPSILON) {
+ return are_near(a.factor(), b.factor(), eps);
+}
+
+/** @brief Combination of a translation and uniform scale.
+ * The translation part is applied first, then the result is scaled from the new origin.
+ * This way when the class is used to accumulate a zoom transform, trans always points
+ * to the new origin in original coordinates.
+ * @ingroup Transforms */
+class Zoom
+ : public TransformOperations< Zoom >
+{
+ Coord _scale;
+ Point _trans;
+ Zoom() : _scale(1), _trans() {}
+public:
+ /// Construct a zoom from a scaling factor.
+ explicit Zoom(Coord s) : _scale(s), _trans() {}
+ /// Construct a zoom from a translation.
+ explicit Zoom(Translate const &t) : _scale(1), _trans(t.vector()) {}
+ /// Construct a zoom from a scaling factor and a translation.
+ Zoom(Coord s, Translate const &t) : _scale(s), _trans(t.vector()) {}
+
+ operator Affine() const {
+ Affine ret(_scale, 0, 0, _scale, _trans[X] * _scale, _trans[Y] * _scale);
+ return ret;
+ }
+ Zoom &operator*=(Zoom const &z) {
+ _trans += z._trans / _scale;
+ _scale *= z._scale;
+ return *this;
+ }
+ bool operator==(Zoom const &z) const { return _scale == z._scale && _trans == z._trans; }
+
+ Coord scale() const { return _scale; }
+ void setScale(Coord s) { _scale = s; }
+ Point translation() const { return _trans; }
+ void setTranslation(Point const &p) { _trans = p; }
+ Zoom inverse() const { Zoom ret(1/_scale, Translate(-_trans*_scale)); return ret; }
+ static Zoom identity() { Zoom ret(1.0); return ret; }
+ static Zoom map_rect(Rect const &old_r, Rect const &new_r);
+
+ friend class Point;
+ friend class Affine;
+};
+
+inline bool are_near(Zoom const &a, Zoom const &b, Coord eps=EPSILON) {
+ return are_near(a.scale(), b.scale(), eps) &&
+ are_near(a.translation(), b.translation(), eps);
+}
+
+/** @brief Specialization of exponentiation for Scale.
+ * @relates Scale */
+template<>
+inline Scale pow(Scale const &s, int n) {
+ Scale ret(::pow(s[X], n), ::pow(s[Y], n));
+ return ret;
+}
+/** @brief Specialization of exponentiation for Translate.
+ * @relates Translate */
+template<>
+inline Translate pow(Translate const &t, int n) {
+ Translate ret(t[X] * n, t[Y] * n);
+ return ret;
+}
+
+
+/** @brief Reflects objects about line.
+ * The line, defined by a vector along the line and a point on it, acts as a mirror.
+ * @ingroup Transforms
+ * @see Line::reflection()
+ */
+Affine reflection(Point const & vector, Point const & origin);
+
+//TODO: decomposition of Affine into some finite combination of the above classes
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_TRANSFORMS_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/include/2geom/utils.h b/include/2geom/utils.h
new file mode 100644
index 0000000..87f49bb
--- /dev/null
+++ b/include/2geom/utils.h
@@ -0,0 +1,114 @@
+/**
+ * \file
+ * \brief Various utility functions.
+ *//*
+ * Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * 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 LIB2GEOM_SEEN_UTILS_H
+#define LIB2GEOM_SEEN_UTILS_H
+
+#include <cstddef>
+#include <vector>
+#include <boost/operators.hpp>
+
+namespace Geom {
+
+// Throw these errors instead of aserting so code can handle them if needed.
+using ErrorCode = int;
+enum Errors : ErrorCode {
+ GEOM_ERR_INTERSECGRAPH,
+};
+
+void binomial_coefficients(std::vector<size_t>& bc, std::size_t n);
+
+struct EmptyClass {};
+
+/**
+ * @brief Noncommutative multiplication helper.
+ * Generates operator*(T, U) from operator*=(T, U). Does not generate operator*(U, T)
+ * like boost::multipliable does. This makes it suitable for noncommutative cases,
+ * such as transforms.
+ */
+template <class T, class U, class B = EmptyClass>
+struct MultipliableNoncommutative : B
+{
+ friend T operator*(T const &lhs, U const &rhs) {
+ T nrv(lhs); nrv *= rhs; return nrv;
+ }
+};
+
+/** @brief Null output iterator
+ * Use this if you want to discard a result returned through an output iterator. */
+struct NullIterator
+ : public boost::output_iterator_helper<NullIterator>
+{
+ NullIterator() {}
+
+ template <typename T>
+ void operator=(T const &) {}
+};
+
+/** @brief Get the next iterator in the container with wrap-around.
+ * If the iterator would become the end iterator after incrementing,
+ * return the begin iterator instead. */
+template <typename Iter, typename Container>
+Iter cyclic_next(Iter i, Container &c) {
+ ++i;
+ if (i == c.end()) {
+ i = c.begin();
+ }
+ return i;
+}
+
+/** @brief Get the previous iterator in the container with wrap-around.
+ * If the passed iterator is the begin iterator, return the iterator
+ * just before the end iterator instead. */
+template <typename Iter, typename Container>
+Iter cyclic_prior(Iter i, Container &c) {
+ if (i == c.begin()) {
+ i = c.end();
+ }
+ --i;
+ return i;
+}
+
+} // end namespace Geom
+
+#endif // LIB2GEOM_SEEN_UTILS_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/include/toys/lpe-framework.h b/include/toys/lpe-framework.h
new file mode 100644
index 0000000..9de5aa0
--- /dev/null
+++ b/include/toys/lpe-framework.h
@@ -0,0 +1,77 @@
+/**
+ * \file lpe-framework.h
+ * \brief A framework for writing an Inkscape Live Path Effect toy.
+ *
+ * Instead of using the standard toy framework, one can use this LPE
+ * framework when creating an LPE for Inkscape. When new 2geom functions
+ * are required for the LPE, adding this functionality directly in Inkscape
+ * greatly increases compile times. Using this framework, one only has to
+ * rebuild 2geom, which speeds up development considerably. (Think about
+ * how much of Inkscape will have to be rebuild if you change/add something
+ * in point.h ...)
+ * An example of how to use this framework is lpe.test.cpp.
+ *//*
+ * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * 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 _2GEOM_LPE_TOY_FRAMEWORK_H_
+#define _2GEOM_LPE_TOY_FRAMEWORK_H_
+
+/**
+ * This should greatly simplify creating toy code for a Live Path Effect (LPE) for Inkscape
+ */
+
+
+#include <toys/toy-framework-2.h>
+#include <2geom/pathvector.h>
+
+class LPEToy: public Toy {
+public:
+ LPEToy();
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override;
+ virtual Geom::PathVector
+ doEffect_path (Geom::PathVector const & path_in);
+ virtual Geom::Piecewise<Geom::D2<Geom::SBasis> >
+ doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in);
+
+ /** this boolean defaults to false, it concatenates the input path to one pwd2,
+ * instead of normally 'splitting' the path into continuous pwd2 paths. */
+ bool concatenate_before_pwd2;
+ PointSetHandle curve_handle;
+};
+
+#endif // _2GEOM_LPE_TOY_FRAMEWORK_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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/include/toys/path-cairo.h b/include/toys/path-cairo.h
new file mode 100644
index 0000000..4c0403f
--- /dev/null
+++ b/include/toys/path-cairo.h
@@ -0,0 +1,57 @@
+#ifndef PATH_CAIRO
+#define PATH_CAIRO
+
+
+#include <cairo.h>
+#include <2geom/line.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+#include <2geom/path.h>
+#include <2geom/convex-hull.h>
+#include <vector>
+#include <string>
+
+typedef struct _cairo cairo_t;
+
+void cairo_curve(cairo_t *cr, Geom::Curve const &c);
+void cairo_rectangle(cairo_t *cr, Geom::Rect const &r);
+void cairo_convex_hull(cairo_t *cr, Geom::ConvexHull const &r);
+void cairo_path(cairo_t *cr, Geom::Path const &p);
+void cairo_path(cairo_t *cr, Geom::PathVector const &p);
+void cairo_path_stitches(cairo_t *cr, Geom::Path const &p);
+void cairo_path_stitches(cairo_t *cr, Geom::PathVector const &p);
+
+void cairo_d2_sb(cairo_t *cr, Geom::D2<Geom::SBasis> const &p);
+void cairo_d2_sb_handles(cairo_t *cr, Geom::D2<Geom::SBasis> const &p);
+void cairo_d2_sb2d(cairo_t* cr, Geom::D2<Geom::SBasis2d> const &sb2, Geom::Point dir, double width);
+void cairo_sb2d(cairo_t* cr, Geom::SBasis2d const &sb2, Geom::Point dir, double width);
+
+void cairo_d2_pw_sb(cairo_t *cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p);
+void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p);
+
+
+void draw_line(cairo_t *cr, const Geom::Line& l, const Geom::Rect& r);
+void draw_line(cairo_t *cr, Geom::Point n, double d, Geom::Rect r);
+void draw_line(cairo_t *cr, Geom::Point a, Geom::Point b, Geom::Rect r);
+
+void draw_line_seg(cairo_t *cr, Geom::Point a, Geom::Point b);
+void draw_line_seg_with_arrow(cairo_t *cr, Geom::Point a, Geom::Point b, double dangle = 15*M_PI/180, double radius = 20);
+void draw_spot(cairo_t *cr, Geom::Point h);
+void draw_handle(cairo_t *cr, Geom::Point h);
+void draw_cross(cairo_t *cr, Geom::Point h);
+void draw_circ(cairo_t *cr, Geom::Point h);
+void draw_ray(cairo_t *cr, Geom::Point h, Geom::Point dir);
+void draw_ray(cairo_t *cr, const Geom::Ray& ray, const Geom::Rect& r);
+void draw_line_segment(cairo_t *cr, const Geom::LineSegment& ls, const Geom::Rect& r);
+
+void cairo_move_to(cairo_t *cr, Geom::Point p1);
+void cairo_line_to(cairo_t *cr, Geom::Point p1);
+void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3);
+
+// H in [0,360)
+// S, V, R, G, B in [0,1]
+void convertHSVtoRGB(const double H, const double S, const double V,
+ double& R, double& G, double& B);
+#endif
diff --git a/include/toys/toy-framework-2.h b/include/toys/toy-framework-2.h
new file mode 100644
index 0000000..5504dfd
--- /dev/null
+++ b/include/toys/toy-framework-2.h
@@ -0,0 +1,451 @@
+
+#ifndef _2GEOM_TOY_FRAMEWORK2_H_
+#define _2GEOM_TOY_FRAMEWORK2_H_
+
+
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <assert.h>
+#include <2geom/exception.h>
+#include <2geom/point.h>
+#include <2geom/geom.h>
+#include <2geom/sbasis.h>
+#include <2geom/d2.h>
+#include <sched.h>
+#include <toys/path-cairo.h>
+
+using std::vector;
+
+//Utility functions
+double uniform();
+
+void draw_text(cairo_t *cr, Geom::Point pos, const char* txt, bool bottom = false, const char* fontdesc = "Sans");
+void draw_text(cairo_t *cr, Geom::Point pos, const std::string& txt, bool bottom = false, const std::string& fontdesc = "Sans");
+void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name=std::string(), bool bottom = true);
+void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name=std::string(), bool bottom = true);
+void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name=std::string(), bool bottom = true);
+
+struct colour{
+ double r,g,b,a;
+ colour(double r, double g, double b, double a) : r(r), g(g), b(b), a(a) {}
+ static colour from_hsv( float H, // hue shift (radians)
+ float S, // saturation shift (scalar)
+ float V, // value multiplier (scalar)
+ float A
+ );
+ static colour from_hsl( float H, // hue shift (radians)
+ float S, // saturation shift (scalar)
+ float L, // value multiplier (scalar)
+ float A
+ );
+};
+void cairo_set_source_rgba(cairo_t* cr, colour c);
+
+class Handle{
+public:
+ std::string name;
+ float rgb[3];
+ Handle() {rgb[0] = rgb[1] = rgb[2] = 0;}
+ virtual ~Handle() {}
+ virtual void draw(cairo_t *cr, bool annotes=false) = 0;
+
+ virtual void* hit(Geom::Point pos) = 0;
+ virtual void move_to(void* hit, Geom::Point om, Geom::Point m) = 0;
+ virtual void load(FILE* f)=0;
+ virtual void save(FILE* f)=0;
+};
+
+class Toggle : public Handle{
+public:
+ Geom::Rect bounds;
+ const char* text;
+ bool on;
+ Toggle() : bounds(Geom::Point(0,0), Geom::Point(0,0)), text(""), on(false) {}
+ Toggle(const char* txt, bool v) : bounds(Geom::Point(0,0), Geom::Point(0,0)), text(txt), on(v) {}
+ Toggle(Geom::Rect bnds, const char* txt, bool v) : bounds(bnds), text(txt), on(v) {}
+ void draw(cairo_t *cr, bool annotes = false) override;
+ void toggle();
+ void set(bool state);
+ void handle_click(GdkEventButton* e);
+ void* hit(Geom::Point pos) override;
+ void move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point /*m*/) override { /* not implemented */ }
+ void load(FILE* /*f*/) override { /* not implemented */ }
+ void save(FILE* /*f*/) override { /* not implemented */ }
+};
+
+
+template< typename T>
+class VectorHandle : public Handle
+{
+ public:
+ VectorHandle()
+ : m_handles()
+ {
+ }
+ void draw(cairo_t *cr, bool annotes=false) override
+ {
+ for (iterator it = m_handles.begin(); it != m_handles.end(); ++it)
+ it->draw(cr, annotes);
+ }
+
+ void* hit(Geom::Point pos) override
+ {
+ void* result = NULL;
+ for (iterator it = m_handles.begin(); it != m_handles.end(); ++it)
+ {
+ result = it->hit(pos);
+ if (result != NULL) break;
+ }
+ return result;
+ }
+
+ void move_to(void* hit, Geom::Point om, Geom::Point m) override
+ {
+ if (hit != NULL)
+ {
+ static_cast<T*>(hit)->move_to(hit, om, m);
+ }
+ }
+
+ void load(FILE* f) override
+ {
+ for (iterator it = m_handles.begin(); it != m_handles.end(); ++it)
+ it->load(f);
+ }
+
+ void save(FILE* f) override
+ {
+ for (iterator it = m_handles.begin(); it != m_handles.end(); ++it)
+ it->save(f);
+ }
+
+ void clear()
+ {
+ m_handles.clear();
+ }
+
+ void reserve(size_t sz)
+ {
+ m_handles.reserve(sz);
+ }
+
+ size_t size() const
+ {
+ return m_handles.size();
+ }
+
+ void push_back (const T& _handle)
+ {
+ m_handles.push_back(_handle);
+ }
+
+ const T& operator[] (size_t i) const
+ {
+ return m_handles.at(i);
+ }
+
+ T& operator[] (size_t i)
+ {
+ return m_handles.at(i);
+ }
+
+ private:
+ typedef typename std::vector<T>::iterator iterator;
+ std::vector<T> m_handles;
+}; // end class VectorHandle
+
+
+class PointHandle : public Handle{
+public:
+ PointHandle(double x, double y) : pos(x,y) {}
+ PointHandle(Geom::Point pt) : pos(pt) {}
+ PointHandle() {}
+ Geom::Point pos;
+ void draw(cairo_t *cr, bool annotes = false) override;
+
+ void* hit(Geom::Point mouse) override;
+ void move_to(void* hit, Geom::Point om, Geom::Point m) override;
+ void load(FILE* f) override;
+ void save(FILE* f) override;
+};
+
+class PointSetHandle : public Handle{
+public:
+ PointSetHandle() {}
+ std::vector<Geom::Point> pts;
+ void draw(cairo_t *cr, bool annotes = false) override;
+
+ void* hit(Geom::Point mouse) override;
+ void move_to(void* hit, Geom::Point om, Geom::Point m) override;
+ void push_back(double x, double y) {pts.emplace_back(x,y);}
+ void push_back(Geom::Point pt) {pts.push_back(pt);}
+ unsigned size() {return pts.size();}
+ Geom::D2<Geom::SBasis> asBezier();
+ void load(FILE* f) override;
+ void save(FILE* f) override;
+};
+
+class RectHandle : public Handle{
+public:
+ RectHandle() {}
+ RectHandle(Geom::Rect pos, bool show_center_handle) : pos(pos), show_center_handle(show_center_handle) {}
+ Geom::Rect pos;
+ bool show_center_handle;
+ void draw(cairo_t *cr, bool annotes = false) override;
+
+ void* hit(Geom::Point mouse) override;
+ void move_to(void* hit, Geom::Point om, Geom::Point m) override;
+ void load(FILE* f) override;
+ void save(FILE* f) override;
+};
+
+
+// used by Slider
+inline std::string default_formatter(double x)
+{
+ std::ostringstream os;
+ os << x;
+ return os.str();
+}
+
+class Slider : public Handle
+{
+ public:
+
+ typedef std::string (*formatter_t) (double );
+ typedef double value_type;
+
+ Slider()
+ : m_handle(), m_pos(Geom::Point(0,0)), m_length(1),
+ m_min(0), m_max(1), m_step(0), m_dir(Geom::X),
+ m_label(""), m_formatter(&default_formatter)
+ {
+ value(0);
+ }
+
+ // pass step = 0 for having a continuos value variation
+ Slider( value_type _min, value_type _max, value_type _step,
+ value_type _value, std::string _label = "" )
+ : m_handle(),m_pos(Geom::Point(0,0)), m_length(1),
+ m_min(_min), m_max(_max), m_step(_step), m_dir(Geom::X),
+ m_label(std::move(_label)), m_formatter(&default_formatter)
+ {
+ value(_value);
+ }
+
+ void set( value_type _min, value_type _max, value_type _step,
+ value_type _value, const std::string& _label = "" )
+ {
+ m_min = _min;
+ m_max = _max;
+ m_step = _step;
+ m_label = _label;
+ value(_value);
+ }
+
+ value_type value() const;
+
+ void value(value_type _value);
+
+ value_type max_value() const
+ {
+ return m_max;
+ }
+
+ void max_value(value_type _value);
+
+ value_type min_value() const
+ {
+ return m_min;
+ }
+
+ void min_value(value_type _value);
+
+ // dir = X horizontal slider dir = Y vertical slider
+ void geometry(Geom::Point _pos, value_type _length, Geom::Dim2 _dir = Geom::X);
+
+ void draw(cairo_t* cr, bool annotate = false) override;
+
+ void formatter( formatter_t _formatter )
+ {
+ m_formatter = _formatter;
+ }
+
+ void* hit(Geom::Point pos) override
+ {
+ if (m_handle.hit(pos) != NULL)
+ return this;
+ return NULL;
+ }
+
+ void move_to(void* hit, Geom::Point om, Geom::Point m) override;
+
+ void load(FILE* f) override
+ {
+ m_handle.load(f);
+ }
+
+ void save(FILE* f) override
+ {
+ m_handle.save(f);
+ }
+
+ private:
+ PointHandle m_handle;
+ Geom::Point m_pos;
+ value_type m_length;
+ value_type m_min, m_max, m_step;
+ int m_dir;
+ std::string m_label;
+ formatter_t m_formatter;
+};
+
+
+
+
+
+class Toy {
+public:
+ vector<Handle*> handles;
+ bool mouse_down = false;
+ Geom::Point old_mouse_point;
+ Handle* selected = nullptr;
+ void* hit_data = nullptr;
+ int canvas_click_button = 0;
+ double notify_offset = 0.0;
+ std::string name;
+ bool show_timings = false;
+ FILE* spool_file = nullptr; // if non-NULL we record all interactions to this file
+
+ Toy() {}
+
+ virtual ~Toy() {}
+
+ virtual void draw(cairo_t *cr, std::ostringstream *notify, int w, int h, bool save, std::ostringstream *timing_stream);
+
+ virtual void mouse_moved(GdkEventMotion* e);
+ virtual void mouse_pressed(GdkEventButton* e);
+ virtual void mouse_released(GdkEventButton* e);
+ virtual void canvas_click(Geom::Point at, int button);
+ virtual void scroll(GdkEventScroll* e);
+
+ virtual void key_hit(GdkEventKey */*e*/) {}
+
+ //Cheapo way of informing the framework what the toy would like drawn for it.
+ virtual bool should_draw_numbers() { return true; }
+ virtual int should_draw_bounds() { return 0; }
+
+ virtual void first_time(int /*argc*/, char** /*argv*/) {}
+
+ virtual void resize_canvas(Geom::Rect const & /*s*/) {}
+ virtual void load(FILE* f);
+ virtual void save(FILE* f);
+};
+
+//Framework Accesors
+void redraw();
+void take_screenshot(const char* file);
+void init(int argc, char **argv, Toy *t, int width=600, int height=600);
+
+void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e);
+void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts);
+Geom::Point read_point(FILE* f);
+
+
+
+
+
+const long long NS_PER_SECOND = 1000000000LL;
+const long long NS_PER_NS = 1;
+
+
+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.
+
+ class Time{
+ public:
+ double value;
+ Time(long long /*s*/, long long l) : value(l) {}
+ Time(double v) : value(v) {}
+ Time operator/(double iters) const {
+ return Time(value / iters);
+ }
+ };
+
+ void start() {
+ nsec(start_time);
+ }
+ void lap(long long &ns) {
+ nsec(ns);
+ ns -= start_time;
+ }
+ Time lap() {
+ long long ns;
+ nsec(ns);
+ return Time(start_time, ns - start_time);
+ }
+ void nsec(long long &ns) {
+#if ! (defined(_WIN32) || defined(__APPLE__))
+ clock_gettime(clock, &ts);
+ ns = ts.tv_sec * NS_PER_SECOND + ts.tv_nsec / NS_PER_NS;
+#else
+ ns = 0;
+#endif
+ }
+ /** Ask the OS nicely for a big time slice */
+ void ask_for_timeslice() {
+#ifndef _WIN32
+ sched_yield();
+#endif
+ }
+private:
+ long long start_time;
+#if ! (defined(_WIN32) || defined(__APPLE__))
+ 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
+#endif
+};
+
+inline std::ostream& operator<<(std::ostream& o, Timer::Time const &t) {
+ double tm = t.value;
+ unsigned prefix = 0;
+ char prefixes[] = "num kMGT";
+ while(prefix < sizeof(prefixes) and tm > 1000) {
+ tm /= 1000.0;
+ prefix += 1;
+ }
+ o << tm << prefixes[prefix] << "s";
+ return o;
+}
+
+
+
+#endif // _2GEOM_TOY_FRAMEWORK2_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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/makefile.in b/makefile.in
new file mode 100644
index 0000000..8c80244
--- /dev/null
+++ b/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) libnr/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) libnr/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/mingwenv.bat b/mingwenv.bat
new file mode 100644
index 0000000..6ef6d2a
--- /dev/null
+++ b/mingwenv.bat
@@ -0,0 +1,18 @@
+@echo Setting environment variables for MinGW build of lib2geom
+IF "%DEVLIBS_PATH%"=="" set DEVLIBS_PATH=c:\devlibs
+IF "%MINGW_PATH%"=="" set MINGW_PATH=C:\mingw
+
+
+set RAGEL_BIN=c:\ragel
+set GS_BIN=C:\latex\gs\gs9.15\bin
+set PYTHON_PATH=C:\Python27
+set GRAPHVIZ_BIN="C:\Program Files (x86)\Graphviz2.38\bin"
+set GS_BIN=C:\latex\gs\gs8.61\bin
+
+set MINGW_BIN=%MINGW_PATH%\bin
+set PKG_CONFIG_PATH=%DEVLIBS_PATH%\lib\pkgconfig
+set CMAKE_PREFIX_PATH=%DEVLIBS_PATH%
+set GTKMM_BASEPATH=%DEVLIBS_PATH%
+set PKG_CONFIG_PATH=%DEVLIBS_PATH%\lib\pkgconfig
+set PATH=%MINGW_BIN%;%RAGEL_BIN%;%PATH%;%GS_BIN%;%GRAPHVIZ_BIN%;%PYTHON_PATH%;%DEVLIBS_PATH%\bin
+set BOOST_DIR=%DEVLIBS_PATH%\include
diff --git a/src/2geom/CMakeLists.txt b/src/2geom/CMakeLists.txt
new file mode 100755
index 0000000..80b9e8e
--- /dev/null
+++ b/src/2geom/CMakeLists.txt
@@ -0,0 +1,206 @@
+# (re-)generate parser file with ragel if it's available
+SET(SVG_PARSER_CPP "svg-path-parser.cpp")
+SET(SVG_PARSER_RL "svg-path-parser.rl")
+find_program(RAGEL_PROGRAM
+ NAMES ragel
+ HINTS /usr/bin
+ /usr/local/bin
+)
+if(RAGEL_PROGRAM)
+ message(STATUS "Found Ragel in ${RAGEL_PROGRAM}. ${SVG_PARSER_CPP} will be recreated from ${SVG_PARSER_RL}.")
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${SVG_PARSER_CPP}"
+ COMMAND ${RAGEL_PROGRAM} -o "${SVG_PARSER_CPP}" "${SVG_PARSER_RL}"
+ DEPENDS "${SVG_PARSER_RL}"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ COMMENT "Generating ${SVG_PARSER_CPP} with ragel")
+else()
+ message(STATUS "Ragel NOT found. Using stale ${SVG_PARSER_CPP}.")
+endif()
+
+
+add_library(2geom ${LIB_TYPE}
+ # sources
+ affine.cpp
+
+ basic-intersection.cpp
+ bezier.cpp
+ bezier-clipping.cpp
+ bezier-curve.cpp
+ bezier-utils.cpp
+
+ cairo-path-sink.cpp
+ circle.cpp
+ concepts.cpp
+ conicsec.cpp
+ conic_section_clipper_impl.cpp
+ convex-hull.cpp
+ coord.cpp
+ crossing.cpp
+ curve.cpp
+
+ d2-sbasis.cpp
+
+ ellipse.cpp
+ elliptical-arc.cpp
+ elliptical-arc-from-sbasis.cpp
+
+ geom.cpp
+
+ intersection-graph.cpp
+
+ line.cpp
+
+ nearest-time.cpp
+
+ numeric/matrix.cpp
+
+ parallelogram.cpp
+ parting-point.cpp
+ path-extrema.cpp
+ path-intersection.cpp
+ path-sink.cpp
+ path.cpp
+ pathvector.cpp
+ piecewise.cpp
+ point.cpp
+ polynomial.cpp
+
+ rect.cpp
+ recursive-bezier-intersection.cpp
+
+ sbasis-2d.cpp
+ sbasis-geometric.cpp
+ sbasis-math.cpp
+ sbasis-poly.cpp
+ sbasis-roots.cpp
+ sbasis-to-bezier.cpp
+ sbasis.cpp
+ self-intersect.cpp
+ solve-bezier.cpp
+ solve-bezier-one-d.cpp
+ solve-bezier-parametric.cpp
+ svg-path-parser.cpp
+ svg-path-writer.cpp
+ sweep-bounds.cpp
+
+ transforms.cpp
+
+ utils.cpp
+
+ # headers (for IDE support only)
+ # private:
+ planar-graph.h
+
+ # public:
+ ${2GEOM_INCLUDE_DIR}/2geom/affine.h
+ ${2GEOM_INCLUDE_DIR}/2geom/angle.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/basic-intersection.h
+ ${2GEOM_INCLUDE_DIR}/2geom/bezier.h
+ ${2GEOM_INCLUDE_DIR}/2geom/bezier-curve.h
+ ${2GEOM_INCLUDE_DIR}/2geom/bezier-to-sbasis.h
+ ${2GEOM_INCLUDE_DIR}/2geom/bezier-utils.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/cairo-path-sink.h
+ ${2GEOM_INCLUDE_DIR}/2geom/choose.h
+ ${2GEOM_INCLUDE_DIR}/2geom/circle.h
+ ${2GEOM_INCLUDE_DIR}/2geom/concepts.h
+ ${2GEOM_INCLUDE_DIR}/2geom/conicsec.h
+ ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper.h
+ ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper_cr.h
+ ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper_impl.h
+ ${2GEOM_INCLUDE_DIR}/2geom/convex-hull.h
+ ${2GEOM_INCLUDE_DIR}/2geom/coord.h
+ ${2GEOM_INCLUDE_DIR}/2geom/crossing.h
+ ${2GEOM_INCLUDE_DIR}/2geom/curve.h
+ ${2GEOM_INCLUDE_DIR}/2geom/curves.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/d2.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/ellipse.h
+ ${2GEOM_INCLUDE_DIR}/2geom/elliptical-arc.h
+ ${2GEOM_INCLUDE_DIR}/2geom/exception.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/forward.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/geom.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/intersection.h
+ ${2GEOM_INCLUDE_DIR}/2geom/intersection-graph.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/line.h
+ ${2GEOM_INCLUDE_DIR}/2geom/linear.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/math-utils.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/nearest-time.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/ord.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/parallelogram.h
+ ${2GEOM_INCLUDE_DIR}/2geom/path-intersection.h
+ ${2GEOM_INCLUDE_DIR}/2geom/path-sink.h
+ ${2GEOM_INCLUDE_DIR}/2geom/path.h
+ ${2GEOM_INCLUDE_DIR}/2geom/pathvector.h
+ ${2GEOM_INCLUDE_DIR}/2geom/piecewise.h
+ ${2GEOM_INCLUDE_DIR}/2geom/point.h
+ ${2GEOM_INCLUDE_DIR}/2geom/polynomial.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/ray.h
+ ${2GEOM_INCLUDE_DIR}/2geom/rect.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-2d.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-curve.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-geometric.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-math.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-poly.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis-to-bezier.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sbasis.h
+ ${2GEOM_INCLUDE_DIR}/2geom/solver.h
+ ${2GEOM_INCLUDE_DIR}/2geom/svg-path-parser.h
+ ${2GEOM_INCLUDE_DIR}/2geom/svg-path-writer.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sweeper.h
+ ${2GEOM_INCLUDE_DIR}/2geom/sweep-bounds.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/transforms.h
+
+ ${2GEOM_INCLUDE_DIR}/2geom/utils.h
+)
+
+# make lib for 2geom
+target_include_directories(2geom
+ PUBLIC
+ ${GLIB_INCLUDE_DIRS}
+ ${GSL_INCLUDE_DIRS}
+ ${CAIRO_INCLUDE_DIRS}
+ ${DoubleConversion_INCLUDE_DIRS}
+ $<BUILD_INTERFACE:${2GEOM_INCLUDE_DIR}>
+ $<BUILD_INTERFACE:${2GEOM_INCLUDE_DIR}/2geom>
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/2geom-${2GEOM_VERSION}>
+ )
+
+target_link_libraries(2geom
+ PUBLIC
+ ${GLIB_LIBRARIES}
+ ${GSL_LIBRARIES}
+ ${CAIRO_LIBRARIES}
+ ${DoubleConversion_LIBRARIES}
+ )
+
+set_target_properties(2geom PROPERTIES SOVERSION "${2GEOM_ABI_VERSION}")
+
+install(TARGETS 2geom
+ EXPORT 2geom_targets
+ RUNTIME
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT "lib2geom${2GEOM_VERSION}"
+ LIBRARY
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT "lib2geom${2GEOM_VERSION}"
+ NAMELINK_COMPONENT "lib2geom_dev"
+ ARCHIVE
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT "lib2geom${2GEOM_VERSION}"
+)
+
+add_library(2Geom::2geom ALIAS 2geom)
diff --git a/src/2geom/affine.cpp b/src/2geom/affine.cpp
new file mode 100644
index 0000000..48179e8
--- /dev/null
+++ b/src/2geom/affine.cpp
@@ -0,0 +1,522 @@
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * This code is in public domain
+ */
+
+#include <2geom/affine.h>
+#include <2geom/point.h>
+#include <2geom/polynomial.h>
+#include <2geom/utils.h>
+
+namespace Geom {
+
+/** Creates a Affine given an axis and origin point.
+ * The axis is represented as two vectors, which represent skew, rotation, and scaling in two dimensions.
+ * from_basis(Point(1, 0), Point(0, 1), Point(0, 0)) would return the identity matrix.
+
+ \param x_basis the vector for the x-axis.
+ \param y_basis the vector for the y-axis.
+ \param offset the translation applied by the matrix.
+ \return The new Affine.
+ */
+//NOTE: Inkscape's version is broken, so when including this version, you'll have to search for code with this func
+Affine from_basis(Point const &x_basis, Point const &y_basis, Point const &offset) {
+ return Affine(x_basis[X], x_basis[Y],
+ y_basis[X], y_basis[Y],
+ offset [X], offset [Y]);
+}
+
+Point Affine::xAxis() const {
+ return Point(_c[0], _c[1]);
+}
+
+Point Affine::yAxis() const {
+ return Point(_c[2], _c[3]);
+}
+
+/// Gets the translation imparted by the Affine.
+Point Affine::translation() const {
+ return Point(_c[4], _c[5]);
+}
+
+void Affine::setXAxis(Point const &vec) {
+ for(int i = 0; i < 2; i++)
+ _c[i] = vec[i];
+}
+
+void Affine::setYAxis(Point const &vec) {
+ for(int i = 0; i < 2; i++)
+ _c[i + 2] = vec[i];
+}
+
+/// Sets the translation imparted by the Affine.
+void Affine::setTranslation(Point const &loc) {
+ for(int i = 0; i < 2; i++)
+ _c[i + 4] = loc[i];
+}
+
+/** Calculates the amount of x-scaling imparted by the Affine. This is the scaling applied to
+ * the original x-axis region. It is \emph{not} the overall x-scaling of the transformation.
+ * Equivalent to L2(m.xAxis()). */
+double Affine::expansionX() const {
+ return sqrt(_c[0] * _c[0] + _c[1] * _c[1]);
+}
+
+/** Calculates the amount of y-scaling imparted by the Affine. This is the scaling applied before
+ * the other transformations. It is \emph{not} the overall y-scaling of the transformation.
+ * Equivalent to L2(m.yAxis()). */
+double Affine::expansionY() const {
+ return sqrt(_c[2] * _c[2] + _c[3] * _c[3]);
+}
+
+void Affine::setExpansionX(double val) {
+ double exp_x = expansionX();
+ if (exp_x != 0.0) { //TODO: best way to deal with it is to skip op?
+ double coef = val / expansionX();
+ for (unsigned i = 0; i < 2; ++i) {
+ _c[i] *= coef;
+ }
+ }
+}
+
+void Affine::setExpansionY(double val) {
+ double exp_y = expansionY();
+ if (exp_y != 0.0) { //TODO: best way to deal with it is to skip op?
+ double coef = val / expansionY();
+ for (unsigned i = 2; i < 4; ++i) {
+ _c[i] *= coef;
+ }
+ }
+}
+
+/** Sets this matrix to be the Identity Affine. */
+void Affine::setIdentity() {
+ _c[0] = 1.0; _c[1] = 0.0;
+ _c[2] = 0.0; _c[3] = 1.0;
+ _c[4] = 0.0; _c[5] = 0.0;
+}
+
+/** @brief Check whether this matrix is an identity matrix.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ 0 & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ */
+bool Affine::isIdentity(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
+ are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents a pure translation.
+ * Will return true for the identity matrix, which represents a zero translation.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ 0 & 1 & 0 \\
+ a & b & 1 \end{array}\right]\f$ */
+bool Affine::isTranslation(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
+ are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps);
+}
+/** @brief Check whether this matrix represents a pure nonzero translation.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ 0 & 1 & 0 \\
+ a & b & 1 \end{array}\right]\f$ and \f$a, b \neq 0\f$ */
+bool Affine::isNonzeroTranslation(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
+ are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
+ (!are_near(_c[4], 0.0, eps) || !are_near(_c[5], 0.0, eps));
+}
+
+/** @brief Check whether this matrix represents pure scaling.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & 0 & 0 \\
+ 0 & b & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$. */
+bool Affine::isScale(Coord eps) const {
+ if (isSingular(eps)) return false;
+ return are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents pure, nonzero scaling.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & 0 & 0 \\
+ 0 & b & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ and \f$a, b \neq 1\f$. */
+bool Affine::isNonzeroScale(Coord eps) const {
+ if (isSingular(eps)) return false;
+ return (!are_near(_c[0], 1.0, eps) || !are_near(_c[3], 1.0, eps)) && //NOTE: these are the diags, and the next line opposite diags
+ are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents pure uniform scaling.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a_1 & 0 & 0 \\
+ 0 & a_2 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$. */
+bool Affine::isUniformScale(Coord eps) const {
+ if (isSingular(eps)) return false;
+ return are_near(fabs(_c[0]), fabs(_c[3]), eps) &&
+ are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents pure, nonzero uniform scaling.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a_1 & 0 & 0 \\
+ 0 & a_2 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$
+ * and \f$a_1, a_2 \neq 1\f$. */
+bool Affine::isNonzeroUniformScale(Coord eps) const {
+ if (isSingular(eps)) return false;
+ // we need to test both c0 and c3 to handle the case of flips,
+ // which should be treated as nonzero uniform scales
+ return !(are_near(_c[0], 1.0, eps) && are_near(_c[3], 1.0, eps)) &&
+ are_near(fabs(_c[0]), fabs(_c[3]), eps) &&
+ are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents a pure rotation.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & b & 0 \\
+ -b & a & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */
+bool Affine::isRotation(Coord eps) const {
+ return are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) &&
+ are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
+}
+
+/** @brief Check whether this matrix represents a pure, nonzero rotation.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & b & 0 \\
+ -b & a & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */
+bool Affine::isNonzeroRotation(Coord eps) const {
+ return !are_near(_c[0], 1.0, eps) &&
+ are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) &&
+ are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
+}
+
+/** @brief Check whether this matrix represents a non-zero rotation about any point.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & b & 0 \\
+ -b & a & 0 \\
+ c & d & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */
+bool Affine::isNonzeroNonpureRotation(Coord eps) const {
+ return !are_near(_c[0], 1.0, eps) &&
+ are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
+ are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
+}
+
+/** @brief For a (possibly non-pure) non-zero-rotation matrix, calculate the rotation center.
+ * @pre The matrix must be a non-zero-rotation matrix to prevent division by zero, see isNonzeroNonpureRotation().
+ * @return The rotation center x, the solution to the equation
+ * \f$A x = x\f$. */
+Point Affine::rotationCenter() const {
+ Coord x = (_c[2]*_c[5]+_c[4]-_c[4]*_c[3]) / (1-_c[3]-_c[0]+_c[0]*_c[3]-_c[2]*_c[1]);
+ Coord y = (_c[1]*x + _c[5]) / (1 - _c[3]);
+ return Point(x,y);
+};
+
+/** @brief Check whether this matrix represents pure horizontal shearing.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ k & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$. */
+bool Affine::isHShear(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
+ are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) &&
+ are_near(_c[5], 0.0, eps);
+}
+/** @brief Check whether this matrix represents pure, nonzero horizontal shearing.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & 0 & 0 \\
+ k & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */
+bool Affine::isNonzeroHShear(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
+ !are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents pure vertical shearing.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & k & 0 \\
+ 0 & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$. */
+bool Affine::isVShear(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && are_near(_c[2], 0.0, eps) &&
+ are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) &&
+ are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents pure, nonzero vertical shearing.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ 1 & k & 0 \\
+ 0 & 1 & 0 \\
+ 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */
+bool Affine::isNonzeroVShear(Coord eps) const {
+ return are_near(_c[0], 1.0, eps) && !are_near(_c[1], 0.0, eps) &&
+ are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
+ are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
+}
+
+/** @brief Check whether this matrix represents zooming.
+ * Zooming is any combination of translation and uniform non-flipping scaling.
+ * It preserves angles, ratios of distances between arbitrary points
+ * and unit vectors of line segments.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is invertible and of the form
+ * \f$\left[\begin{array}{ccc}
+ a & 0 & 0 \\
+ 0 & a & 0 \\
+ b & c & 1 \end{array}\right]\f$. */
+bool Affine::isZoom(Coord eps) const {
+ if (isSingular(eps)) return false;
+ return are_near(_c[0], _c[3], eps) && are_near(_c[1], 0, eps) && are_near(_c[2], 0, eps);
+}
+
+/** @brief Check whether the transformation preserves areas of polygons.
+ * This means that the transformation can be any combination of translation, rotation,
+ * shearing and squeezing (non-uniform scaling such that the absolute value of the product
+ * of Y-scale and X-scale is 1).
+ * @param eps Numerical tolerance
+ * @return True iff \f$|\det A| = 1\f$. */
+bool Affine::preservesArea(Coord eps) const
+{
+ return are_near(descrim2(), 1.0, eps);
+}
+
+/** @brief Check whether the transformation preserves angles between lines.
+ * This means that the transformation can be any combination of translation, uniform scaling,
+ * rotation and flipping.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & b & 0 \\
+ -b & a & 0 \\
+ c & d & 1 \end{array}\right]\f$ or
+ \f$\left[\begin{array}{ccc}
+ -a & b & 0 \\
+ b & a & 0 \\
+ c & d & 1 \end{array}\right]\f$. */
+bool Affine::preservesAngles(Coord eps) const
+{
+ if (isSingular(eps)) return false;
+ return (are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) ||
+ (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps));
+}
+
+/** @brief Check whether the transformation preserves distances between points.
+ * This means that the transformation can be any combination of translation,
+ * rotation and flipping.
+ * @param eps Numerical tolerance
+ * @return True iff the matrix is of the form
+ * \f$\left[\begin{array}{ccc}
+ a & b & 0 \\
+ -b & a & 0 \\
+ c & d & 1 \end{array}\right]\f$ or
+ \f$\left[\begin{array}{ccc}
+ -a & b & 0 \\
+ b & a & 0 \\
+ c & d & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */
+bool Affine::preservesDistances(Coord eps) const
+{
+ return ((are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) ||
+ (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps))) &&
+ are_near(_c[0] * _c[0] + _c[1] * _c[1], 1.0, eps);
+}
+
+/** @brief Check whether this transformation flips objects.
+ * A transformation flips objects if it has a negative scaling component. */
+bool Affine::flips() const {
+ return det() < 0;
+}
+
+/** @brief Check whether this matrix is singular.
+ * Singular matrices have no inverse, which means that applying them to a set of points
+ * results in a loss of information.
+ * @param eps Numerical tolerance
+ * @return True iff the determinant is near zero. */
+bool Affine::isSingular(Coord eps) const {
+ return are_near(det(), 0.0, eps);
+}
+
+/** @brief Compute the inverse matrix.
+ * Inverse is a matrix (denoted \f$A^{-1}\f$) such that \f$AA^{-1} = A^{-1}A = I\f$.
+ * Singular matrices have no inverse (for example a matrix that has two of its columns equal).
+ * For such matrices, the identity matrix will be returned instead.
+ * @param eps Numerical tolerance
+ * @return Inverse of the matrix, or the identity matrix if the inverse is undefined.
+ * @post (m * m.inverse()).isIdentity() == true */
+Affine Affine::inverse() const {
+ Affine d;
+
+ double mx = std::max(fabs(_c[0]) + fabs(_c[1]),
+ fabs(_c[2]) + fabs(_c[3])); // a random matrix norm (either l1 or linfty
+ if(mx > 0) {
+ Geom::Coord const determ = det();
+ if (!rel_error_bound(std::sqrt(fabs(determ)), mx)) {
+ Geom::Coord const ideterm = 1.0 / (determ);
+
+ d._c[0] = _c[3] * ideterm;
+ d._c[1] = -_c[1] * ideterm;
+ d._c[2] = -_c[2] * ideterm;
+ d._c[3] = _c[0] * ideterm;
+ d._c[4] = (-_c[4] * d._c[0] - _c[5] * d._c[2]);
+ d._c[5] = (-_c[4] * d._c[1] - _c[5] * d._c[3]);
+ } else {
+ d.setIdentity();
+ }
+ } else {
+ d.setIdentity();
+ }
+
+ return d;
+}
+
+/** @brief Calculate the determinant.
+ * @return \f$\det A\f$. */
+Coord Affine::det() const {
+ // TODO this can overflow
+ return _c[0] * _c[3] - _c[1] * _c[2];
+}
+
+/** @brief Calculate the square of the descriminant.
+ * This is simply the absolute value of the determinant.
+ * @return \f$|\det A|\f$. */
+Coord Affine::descrim2() const {
+ return fabs(det());
+}
+
+/** @brief Calculate the descriminant.
+ * If the matrix doesn't contain a shearing or non-uniform scaling component, this value says
+ * how will the length of any line segment change after applying this transformation
+ * to arbitrary objects on a plane. The new length will be
+ * @code line_seg.length() * m.descrim()) @endcode
+ * @return \f$\sqrt{|\det A|}\f$. */
+Coord Affine::descrim() const {
+ return sqrt(descrim2());
+}
+
+/** @brief Combine this transformation with another one.
+ * After this operation, the matrix will correspond to the transformation
+ * obtained by first applying the original version of this matrix, and then
+ * applying @a m. */
+Affine &Affine::operator*=(Affine const &o) {
+ Coord nc[6];
+ for(int a = 0; a < 5; a += 2) {
+ for(int b = 0; b < 2; b++) {
+ nc[a + b] = _c[a] * o._c[b] + _c[a + 1] * o._c[b + 2];
+ }
+ }
+ for(int a = 0; a < 6; ++a) {
+ _c[a] = nc[a];
+ }
+ _c[4] += o._c[4];
+ _c[5] += o._c[5];
+ return *this;
+}
+
+//TODO: What's this!?!
+/** Given a matrix m such that unit_circle = m*x, this returns the
+ * quadratic form x*A*x = 1.
+ * @relates Affine */
+Affine elliptic_quadratic_form(Affine const &m) {
+ double od = m[0] * m[1] + m[2] * m[3];
+ Affine ret (m[0]*m[0] + m[1]*m[1], od,
+ od, m[2]*m[2] + m[3]*m[3],
+ 0, 0);
+ return ret; // allow NRVO
+}
+
+Eigen::Eigen(Affine const &m) {
+ double const B = -m[0] - m[3];
+ double const C = m[0]*m[3] - m[1]*m[2];
+
+ std::vector<double> v = solve_quadratic(1, B, C);
+
+ for (unsigned i = 0; i < v.size(); ++i) {
+ values[i] = v[i];
+ vectors[i] = unit_vector(rot90(Point(m[0] - values[i], m[1])));
+ }
+ for (unsigned i = v.size(); i < 2; ++i) {
+ values[i] = 0;
+ vectors[i] = Point(0,0);
+ }
+}
+
+Eigen::Eigen(double m[2][2]) {
+ double const B = -m[0][0] - m[1][1];
+ double const C = m[0][0]*m[1][1] - m[1][0]*m[0][1];
+
+ std::vector<double> v = solve_quadratic(1, B, C);
+
+ for (unsigned i = 0; i < v.size(); ++i) {
+ values[i] = v[i];
+ vectors[i] = unit_vector(rot90(Point(m[0][0] - values[i], m[0][1])));
+ }
+ for (unsigned i = v.size(); i < 2; ++i) {
+ values[i] = 0;
+ vectors[i] = Point(0,0);
+ }
+}
+
+/** @brief Nearness predicate for affine transforms.
+ * @returns True if all entries of matrices are within eps of each other.
+ * @relates Affine */
+bool are_near(Affine const &a, Affine const &b, Coord eps)
+{
+ return are_near(a[0], b[0], eps) && are_near(a[1], b[1], eps) &&
+ are_near(a[2], b[2], eps) && are_near(a[3], b[3], eps) &&
+ are_near(a[4], b[4], eps) && are_near(a[5], b[5], eps);
+}
+
+} //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/src/2geom/basic-intersection.cpp b/src/2geom/basic-intersection.cpp
new file mode 100644
index 0000000..61d7a6d
--- /dev/null
+++ b/src/2geom/basic-intersection.cpp
@@ -0,0 +1,493 @@
+/** @file
+ * @brief Basic intersection routines
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Jean-François Barraud <jf.barraud@gmail.com>
+ *
+ * Copyright 2008-2009 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/basic-intersection.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/exception.h>
+
+#ifdef HAVE_GSL
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_multiroots.h>
+#endif
+
+using std::vector;
+namespace Geom {
+
+//#ifdef USE_RECURSIVE_INTERSECTOR
+
+// void find_intersections(std::vector<std::pair<double, double> > &xs,
+// D2<SBasis> const & A,
+// D2<SBasis> const & B) {
+// vector<Point> BezA, BezB;
+// sbasis_to_bezier(BezA, A);
+// sbasis_to_bezier(BezB, B);
+
+// xs.clear();
+
+// find_intersections_bezier_recursive(xs, BezA, BezB);
+// }
+// void find_intersections(std::vector< std::pair<double, double> > & xs,
+// std::vector<Point> const& A,
+// std::vector<Point> const& B,
+// double precision){
+// find_intersections_bezier_recursive(xs, A, B, precision);
+// }
+
+//#else
+
+namespace detail{ namespace bezier_clipping {
+void portion(std::vector<Point> &B, Interval const &I);
+void derivative(std::vector<Point> &D, std::vector<Point> const &B);
+}; };
+
+void find_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<Bezier> const & A,
+ D2<Bezier> const & B,
+ double precision)
+{
+ find_intersections_bezier_clipping(xs, bezier_points(A), bezier_points(B), precision);
+}
+
+void find_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const & A,
+ D2<SBasis> const & B,
+ double precision)
+{
+ vector<Point> BezA, BezB;
+ sbasis_to_bezier(BezA, A);
+ sbasis_to_bezier(BezB, B);
+
+ find_intersections_bezier_clipping(xs, BezA, BezB, precision);
+}
+
+void find_intersections(std::vector< std::pair<double, double> > & xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ find_intersections_bezier_clipping(xs, A, B, precision);
+}
+
+//#endif
+
+/*
+ * split the curve at the midpoint, returning an array with the two parts
+ * Temporary storage is minimized by using part of the storage for the result
+ * to hold an intermediate value until it is no longer needed.
+ */
+// TODO replace with Bezier method
+void split(vector<Point> const &p, double t,
+ vector<Point> &left, vector<Point> &right) {
+ const unsigned sz = p.size();
+ //Geom::Point Vtemp[sz][sz];
+ vector<vector<Point> > Vtemp(sz);
+ for ( size_t i = 0; i < sz; ++i )
+ Vtemp[i].reserve(sz);
+
+ /* Copy control points */
+ std::copy(p.begin(), p.end(), Vtemp[0].begin());
+
+ /* Triangle computation */
+ for (unsigned i = 1; i < sz; i++) {
+ for (unsigned j = 0; j < sz - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+
+ left.resize(sz);
+ right.resize(sz);
+ for (unsigned j = 0; j < sz; j++)
+ left[j] = Vtemp[j][0];
+ for (unsigned j = 0; j < sz; j++)
+ right[j] = Vtemp[sz-1-j][j];
+}
+
+
+
+void find_self_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<Bezier> const &A,
+ double precision)
+{
+ std::vector<double> dr = derivative(A[X]).roots();
+ {
+ std::vector<double> dyr = derivative(A[Y]).roots();
+ dr.insert(dr.begin(), dyr.begin(), dyr.end());
+ }
+ dr.push_back(0);
+ dr.push_back(1);
+ // We want to be sure that we have no empty segments
+ std::sort(dr.begin(), dr.end());
+ std::vector<double>::iterator new_end = std::unique(dr.begin(), dr.end());
+ dr.resize( new_end - dr.begin() );
+
+ std::vector< D2<Bezier> > pieces;
+ for (unsigned i = 0; i < dr.size() - 1; ++i) {
+ pieces.push_back(portion(A, dr[i], dr[i+1]));
+ }
+ /*{
+ vector<Point> l, r, in = A;
+ for(unsigned i = 0; i < dr.size()-1; i++) {
+ split(in, (dr[i+1]-dr[i]) / (1 - dr[i]), l, r);
+ pieces.push_back(l);
+ in = r;
+ }
+ }*/
+
+ for(unsigned i = 0; i < dr.size()-1; i++) {
+ for(unsigned j = i+1; j < dr.size()-1; j++) {
+ std::vector<std::pair<double, double> > section;
+
+ find_intersections(section, pieces[i], pieces[j], precision);
+ for(auto & k : section) {
+ double l = k.first;
+ double r = k.second;
+// XXX: This condition will prune out false positives, but it might create some false negatives. Todo: Confirm it is correct.
+ if(j == i+1)
+ //if((l == 1) && (r == 0))
+ if( ( l > precision ) && (r < precision) )//FIXME: what precision should be used here???
+ continue;
+ xs.emplace_back((1-l)*dr[i] + l*dr[i+1],
+ (1-r)*dr[j] + r*dr[j+1]);
+ }
+ }
+ }
+
+ // Because i is in order, xs should be roughly already in order?
+ //sort(xs.begin(), xs.end());
+ //unique(xs.begin(), xs.end());
+}
+
+void find_self_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const &A,
+ double precision)
+{
+ D2<Bezier> in;
+ sbasis_to_bezier(in, A);
+ find_self_intersections(xs, in, precision);
+}
+
+
+void subdivide(D2<Bezier> const &a,
+ D2<Bezier> const &b,
+ std::vector< std::pair<double, double> > const &xs,
+ std::vector< D2<Bezier> > &av,
+ std::vector< D2<Bezier> > &bv)
+{
+ if (xs.empty()) {
+ av.push_back(a);
+ bv.push_back(b);
+ return;
+ }
+
+ std::pair<double, double> prev = std::make_pair(0., 0.);
+ for (const auto & x : xs) {
+ av.push_back(portion(a, prev.first, x.first));
+ bv.push_back(portion(b, prev.second, x.second));
+ av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0());
+ av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1());
+ av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0());
+ av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1());
+ prev = x;
+ }
+ av.push_back(portion(a, prev.first, 1));
+ bv.push_back(portion(b, prev.second, 1));
+ av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0());
+ av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1());
+ av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0());
+ av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1());
+}
+
+#ifdef HAVE_GSL
+#include <gsl/gsl_multiroots.h>
+
+struct rparams
+{
+ D2<SBasis> const &A;
+ D2<SBasis> const &B;
+};
+
+static int
+intersect_polish_f (const gsl_vector * x, void *params,
+ gsl_vector * f)
+{
+ const double x0 = gsl_vector_get (x, 0);
+ const double x1 = gsl_vector_get (x, 1);
+
+ Geom::Point dx = ((struct rparams *) params)->A(x0) -
+ ((struct rparams *) params)->B(x1);
+
+ gsl_vector_set (f, 0, dx[0]);
+ gsl_vector_set (f, 1, dx[1]);
+
+ return GSL_SUCCESS;
+}
+#endif
+
+union dbl_64{
+ long long i64;
+ double d64;
+};
+
+static double EpsilonBy(double value, int eps)
+{
+ dbl_64 s;
+ s.d64 = value;
+ s.i64 += eps;
+ return s.d64;
+}
+
+
+static void intersect_polish_root (D2<SBasis> const &A, double &s,
+ D2<SBasis> const &B, double &t) {
+#ifdef HAVE_GSL
+ const gsl_multiroot_fsolver_type *T;
+ gsl_multiroot_fsolver *sol;
+
+ int status;
+ size_t iter = 0;
+#endif
+ std::vector<Point> as, bs;
+ as = A.valueAndDerivatives(s, 2);
+ bs = B.valueAndDerivatives(t, 2);
+ Point F = as[0] - bs[0];
+ double best = dot(F, F);
+
+ for(int i = 0; i < 4; i++) {
+
+ /**
+ we want to solve
+ J*(x1 - x0) = f(x0)
+
+ |dA(s)[0] -dB(t)[0]| (X1 - X0) = A(s) - B(t)
+ |dA(s)[1] -dB(t)[1]|
+ **/
+
+ // We're using the standard transformation matricies, which is numerically rather poor. Much better to solve the equation using elimination.
+
+ Affine jack(as[1][0], as[1][1],
+ -bs[1][0], -bs[1][1],
+ 0, 0);
+ Point soln = (F)*jack.inverse();
+ double ns = s - soln[0];
+ double nt = t - soln[1];
+
+ as = A.valueAndDerivatives(ns, 2);
+ bs = B.valueAndDerivatives(nt, 2);
+ F = as[0] - bs[0];
+ double trial = dot(F, F);
+ if (trial > best*0.1) {// we have standards, you know
+ // At this point we could do a line search
+ break;
+ }
+ best = trial;
+ s = ns;
+ t = nt;
+ }
+
+#ifdef HAVE_GSL
+ const size_t n = 2;
+ struct rparams p = {A, B};
+ gsl_multiroot_function f = {&intersect_polish_f, n, &p};
+
+ double x_init[2] = {s, t};
+ gsl_vector *x = gsl_vector_alloc (n);
+
+ gsl_vector_set (x, 0, x_init[0]);
+ gsl_vector_set (x, 1, x_init[1]);
+
+ T = gsl_multiroot_fsolver_hybrids;
+ sol = gsl_multiroot_fsolver_alloc (T, 2);
+ gsl_multiroot_fsolver_set (sol, &f, x);
+
+ do
+ {
+ iter++;
+ status = gsl_multiroot_fsolver_iterate (sol);
+
+ if (status) /* check if solver is stuck */
+ break;
+
+ status =
+ gsl_multiroot_test_residual (sol->f, 1e-12);
+ }
+ while (status == GSL_CONTINUE && iter < 1000);
+
+ s = gsl_vector_get (sol->x, 0);
+ t = gsl_vector_get (sol->x, 1);
+
+ gsl_multiroot_fsolver_free (sol);
+ gsl_vector_free (x);
+#endif
+
+ {
+ // This code does a neighbourhood search for minor improvements.
+ double best_v = L1(A(s) - B(t));
+ //std::cout << "------\n" << best_v << std::endl;
+ Point best(s,t);
+ while (true) {
+ Point trial = best;
+ double trial_v = best_v;
+ for(int nsi = -1; nsi < 2; nsi++) {
+ for(int nti = -1; nti < 2; nti++) {
+ Point n(EpsilonBy(best[0], nsi),
+ EpsilonBy(best[1], nti));
+ double c = L1(A(n[0]) - B(n[1]));
+ //std::cout << c << "; ";
+ if (c < trial_v) {
+ trial = n;
+ trial_v = c;
+ }
+ }
+ }
+ if(trial == best) {
+ //std::cout << "\n" << s << " -> " << s - best[0] << std::endl;
+ //std::cout << t << " -> " << t - best[1] << std::endl;
+ //std::cout << best_v << std::endl;
+ s = best[0];
+ t = best[1];
+ return;
+ } else {
+ best = trial;
+ best_v = trial_v;
+ }
+ }
+ }
+}
+
+
+void polish_intersections(std::vector<std::pair<double, double> > &xs,
+ D2<SBasis> const &A, D2<SBasis> const &B)
+{
+ for(auto & x : xs)
+ intersect_polish_root(A, x.first,
+ B, x.second);
+}
+
+/**
+ * Compute the Hausdorf distance from A to B only.
+ */
+double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B,
+ double m_precision,
+ double *a_t, double* b_t) {
+ std::vector< std::pair<double, double> > xs;
+ std::vector<Point> Az, Bz;
+ sbasis_to_bezier (Az, A);
+ sbasis_to_bezier (Bz, B);
+ find_collinear_normal(xs, Az, Bz, m_precision);
+ double h_dist = 0, h_a_t = 0, h_b_t = 0;
+ double dist = 0;
+ Point Ax = A.at0();
+ double t = Geom::nearest_time(Ax, B);
+ dist = Geom::distance(Ax, B(t));
+ if (dist > h_dist) {
+ h_a_t = 0;
+ h_b_t = t;
+ h_dist = dist;
+ }
+ Ax = A.at1();
+ t = Geom::nearest_time(Ax, B);
+ dist = Geom::distance(Ax, B(t));
+ if (dist > h_dist) {
+ h_a_t = 1;
+ h_b_t = t;
+ h_dist = dist;
+ }
+ for (auto & x : xs)
+ {
+ Point At = A(x.first);
+ Point Bu = B(x.second);
+ double distAtBu = Geom::distance(At, Bu);
+ t = Geom::nearest_time(At, B);
+ dist = Geom::distance(At, B(t));
+ //FIXME: we might miss it due to floating point precision...
+ if (dist >= distAtBu-.1 && distAtBu > h_dist) {
+ h_a_t = x.first;
+ h_b_t = x.second;
+ h_dist = distAtBu;
+ }
+
+ }
+ if(a_t) *a_t = h_a_t;
+ if(b_t) *b_t = h_b_t;
+
+ return h_dist;
+}
+
+/**
+ * Compute the symmetric Hausdorf distance.
+ */
+double hausdorf(D2<SBasis>& A, D2<SBasis> const& B,
+ double m_precision,
+ double *a_t, double* b_t) {
+ double h_dist = hausdorfl(A, B, m_precision, a_t, b_t);
+
+ double dist = 0;
+ Point Bx = B.at0();
+ double t = Geom::nearest_time(Bx, A);
+ dist = Geom::distance(Bx, A(t));
+ if (dist > h_dist) {
+ if(a_t) *a_t = t;
+ if(b_t) *b_t = 0;
+ h_dist = dist;
+ }
+ Bx = B.at1();
+ t = Geom::nearest_time(Bx, A);
+ dist = Geom::distance(Bx, A(t));
+ if (dist > h_dist) {
+ if(a_t) *a_t = t;
+ if(b_t) *b_t = 1;
+ h_dist = dist;
+ }
+
+ return h_dist;
+}
+
+bool non_collinear_segments_intersect(const Point &A, const Point &B, const Point &C, const Point &D)
+{
+ return cross(D - C, A - C) * cross(D - C, B - C) < 0 && //
+ cross(B - A, C - A) * cross(B - A, D - A) < 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/src/2geom/bezier-clipping.cpp b/src/2geom/bezier-clipping.cpp
new file mode 100644
index 0000000..27da3d2
--- /dev/null
+++ b/src/2geom/bezier-clipping.cpp
@@ -0,0 +1,1174 @@
+/*
+ * Implement the Bezier clipping algorithm for finding
+ * Bezier curve intersection points and collinear normals
+ *
+ * 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/basic-intersection.h>
+#include <2geom/choose.h>
+#include <2geom/point.h>
+#include <2geom/interval.h>
+#include <2geom/bezier.h>
+#include <2geom/numeric/matrix.h>
+#include <2geom/convex-hull.h>
+#include <2geom/line.h>
+
+#include <cassert>
+#include <vector>
+#include <algorithm>
+#include <utility>
+//#include <iomanip>
+
+using std::swap;
+
+
+#define VERBOSE 0
+#define CHECK 0
+
+namespace Geom {
+
+namespace detail { namespace bezier_clipping {
+
+////////////////////////////////////////////////////////////////////////////////
+// for debugging
+//
+
+void print(std::vector<Point> const& cp, const char* msg = "")
+{
+ std::cerr << msg << std::endl;
+ for (size_t i = 0; i < cp.size(); ++i)
+ std::cerr << i << " : " << cp[i] << std::endl;
+}
+
+template< class charT >
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const Interval & I)
+{
+ os << "[" << I.min() << ", " << I.max() << "]";
+ return os;
+}
+
+double angle (std::vector<Point> const& A)
+{
+ size_t n = A.size() -1;
+ double a = std::atan2(A[n][Y] - A[0][Y], A[n][X] - A[0][X]);
+ return (180 * a / M_PI);
+}
+
+size_t get_precision(Interval const& I)
+{
+ double d = I.extent();
+ double e = 0.1, p = 10;
+ int n = 0;
+ while (n < 16 && d < e)
+ {
+ p *= 10;
+ e = 1/p;
+ ++n;
+ }
+ return n;
+}
+
+void range_assertion(int k, int m, int n, const char* msg)
+{
+ if ( k < m || k > n)
+ {
+ std::cerr << "range assertion failed: \n"
+ << msg << std::endl
+ << "value: " << k
+ << " range: " << m << ", " << n << std::endl;
+ assert (k >= m && k <= n);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// numerical routines
+
+/*
+ * Compute the determinant of the 2x2 matrix with column the point P1, P2
+ */
+double det(Point const& P1, Point const& P2)
+{
+ return P1[X]*P2[Y] - P1[Y]*P2[X];
+}
+
+/*
+ * Solve the linear system [P1,P2] * P = Q
+ * in case there isn't exactly one solution the routine returns false
+ */
+bool solve(Point & P, Point const& P1, Point const& P2, Point const& Q)
+{
+ double d = det(P1, P2);
+ if (d == 0) return false;
+ d = 1 / d;
+ P[X] = det(Q, P2) * d;
+ P[Y] = det(P1, Q) * d;
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// interval routines
+
+/*
+ * Map the sub-interval I in [0,1] into the interval J and assign it to J
+ */
+void map_to(Interval & J, Interval const& I)
+{
+ J.setEnds(J.valueAt(I.min()), J.valueAt(I.max()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// bezier curve routines
+
+/*
+ * Return true if all the Bezier curve control points are near,
+ * false otherwise
+ */
+// Bezier.isConstant(precision)
+bool is_constant(std::vector<Point> const& A, double precision)
+{
+ for (unsigned int i = 1; i < A.size(); ++i)
+ {
+ if(!are_near(A[i], A[0], precision))
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Compute the hodograph of the bezier curve B and return it in D
+ */
+// derivative(Bezier)
+void derivative(std::vector<Point> & D, std::vector<Point> const& B)
+{
+ D.clear();
+ size_t sz = B.size();
+ if (sz == 0) return;
+ if (sz == 1)
+ {
+ D.resize(1, Point(0,0));
+ return;
+ }
+ size_t n = sz-1;
+ D.reserve(n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ D.push_back(n*(B[i+1] - B[i]));
+ }
+}
+
+/*
+ * Compute the hodograph of the Bezier curve B rotated of 90 degree
+ * and return it in D; we have N(t) orthogonal to B(t) for any t
+ */
+// rot90(derivative(Bezier))
+void normal(std::vector<Point> & N, std::vector<Point> const& B)
+{
+ derivative(N,B);
+ for (auto & i : N)
+ {
+ i = rot90(i);
+ }
+}
+
+/*
+ * Compute the portion of the Bezier curve "B" wrt the interval [0,t]
+ */
+// portion(Bezier, 0, t)
+void left_portion(Coord t, std::vector<Point> & B)
+{
+ size_t n = B.size();
+ for (size_t i = 1; i < n; ++i)
+ {
+ for (size_t j = n-1; j > i-1 ; --j)
+ {
+ B[j] = lerp(t, B[j-1], B[j]);
+ }
+ }
+}
+
+/*
+ * Compute the portion of the Bezier curve "B" wrt the interval [t,1]
+ */
+// portion(Bezier, t, 1)
+void right_portion(Coord t, std::vector<Point> & B)
+{
+ size_t n = B.size();
+ for (size_t i = 1; i < n; ++i)
+ {
+ for (size_t j = 0; j < n-i; ++j)
+ {
+ B[j] = lerp(t, B[j], B[j+1]);
+ }
+ }
+}
+
+/*
+ * Compute the portion of the Bezier curve "B" wrt the interval "I"
+ */
+// portion(Bezier, I)
+void portion (std::vector<Point> & B , Interval const& I)
+{
+ if (I.min() == 0)
+ {
+ if (I.max() == 1) return;
+ left_portion(I.max(), B);
+ return;
+ }
+ right_portion(I.min(), B);
+ if (I.max() == 1) return;
+ double t = I.extent() / (1 - I.min());
+ left_portion(t, B);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// tags
+
+struct intersection_point_tag;
+struct collinear_normal_tag;
+template <typename Tag>
+OptInterval clip(std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision);
+template <typename Tag>
+void iterate(std::vector<Interval>& domsA,
+ std::vector<Interval>& domsB,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ Interval const& domA,
+ Interval const& domB,
+ double precision );
+
+
+////////////////////////////////////////////////////////////////////////////////
+// intersection
+
+/*
+ * Make up an orientation line using the control points c[i] and c[j]
+ * the line is returned in the output parameter "l" in the form of a 3 element
+ * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized.
+ */
+// Line(c[i], c[j])
+void orientation_line (std::vector<double> & l,
+ std::vector<Point> const& c,
+ size_t i, size_t j)
+{
+ l[0] = c[j][Y] - c[i][Y];
+ l[1] = c[i][X] - c[j][X];
+ l[2] = cross(c[j], c[i]);
+ double length = std::sqrt(l[0] * l[0] + l[1] * l[1]);
+ assert (length != 0);
+ l[0] /= length;
+ l[1] /= length;
+ l[2] /= length;
+}
+
+/*
+ * Pick up an orientation line for the Bezier curve "c" and return it in
+ * the output parameter "l"
+ */
+Line pick_orientation_line (std::vector<Point> const &c, double precision)
+{
+ size_t i = c.size();
+ while (--i > 0 && are_near(c[0], c[i], precision))
+ {}
+
+ // this should never happen because when a new curve portion is created
+ // we check that it is not constant;
+ // however this requires that the precision used in the is_constant
+ // routine has to be the same used here in the are_near test
+ assert(i != 0);
+
+ Line line(c[0], c[i]);
+ return line;
+ //std::cerr << "i = " << i << std::endl;
+}
+
+/*
+ * Make up an orientation line for constant bezier curve;
+ * the orientation line is made up orthogonal to the other curve base line;
+ * the line is returned in the output parameter "l" in the form of a 3 element
+ * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized.
+ */
+Line orthogonal_orientation_line (std::vector<Point> const &c,
+ Point const &p,
+ double precision)
+{
+ // this should never happen
+ assert(!is_constant(c, precision));
+
+ Line line(p, (c.back() - c.front()).cw() + p);
+ return line;
+}
+
+/*
+ * Compute the signed distance of the point "P" from the normalized line l
+ */
+double signed_distance(Point const &p, Line const &l)
+{
+ Coord a, b, c;
+ l.coefficients(a, b, c);
+ return a * p[X] + b * p[Y] + c;
+}
+
+/*
+ * Compute the min and max distance of the control points of the Bezier
+ * curve "c" from the normalized orientation line "l".
+ * This bounds are returned through the output Interval parameter"bound".
+ */
+Interval fat_line_bounds (std::vector<Point> const &c,
+ Line const &l)
+{
+ Interval bound(0, 0);
+ for (auto i : c) {
+ bound.expandTo(signed_distance(i, l));
+ }
+ return bound;
+}
+
+/*
+ * return the x component of the intersection point between the line
+ * passing through points p1, p2 and the line Y = "y"
+ */
+double intersect (Point const& p1, Point const& p2, double y)
+{
+ // we are sure that p2[Y] != p1[Y] because this routine is called
+ // only when the lower or the upper bound is crossed
+ double dy = (p2[Y] - p1[Y]);
+ double s = (y - p1[Y]) / dy;
+ return (p2[X]-p1[X])*s + p1[X];
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the fat line defined by the orientation
+ * line "l" and the interval range "bound", the new parameter interval for
+ * the clipped curve is returned through the output parameter "dom"
+ */
+OptInterval clip_interval (std::vector<Point> const& B,
+ Line const &l,
+ Interval const &bound)
+{
+ double n = B.size() - 1; // number of sub-intervals
+ std::vector<Point> D; // distance curve control points
+ D.reserve (B.size());
+ for (size_t i = 0; i < B.size(); ++i)
+ {
+ const double d = signed_distance(B[i], l);
+ D.emplace_back(i/n, d);
+ }
+ //print(D);
+
+ ConvexHull p;
+ p.swap(D);
+ //print(p);
+
+ bool plower, phigher;
+ bool clower, chigher;
+ double t, tmin = 1, tmax = 0;
+// std::cerr << "bound : " << bound << std::endl;
+
+ plower = (p[0][Y] < bound.min());
+ phigher = (p[0][Y] > bound.max());
+ if (!(plower || phigher)) // inside the fat line
+ {
+ if (tmin > p[0][X]) tmin = p[0][X];
+ if (tmax < p[0][X]) tmax = p[0][X];
+// std::cerr << "0 : inside " << p[0]
+// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+
+ for (size_t i = 1; i < p.size(); ++i)
+ {
+ clower = (p[i][Y] < bound.min());
+ chigher = (p[i][Y] > bound.max());
+ if (!(clower || chigher)) // inside the fat line
+ {
+ if (tmin > p[i][X]) tmin = p[i][X];
+ if (tmax < p[i][X]) tmax = p[i][X];
+// std::cerr << i << " : inside " << p[i]
+// << " : tmin = " << tmin << ", tmax = " << tmax
+// << std::endl;
+ }
+ if (clower != plower) // cross the lower bound
+ {
+ t = intersect(p[i-1], p[i], bound.min());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ plower = clower;
+// std::cerr << i << " : lower " << p[i]
+// << " : tmin = " << tmin << ", tmax = " << tmax
+// << std::endl;
+ }
+ if (chigher != phigher) // cross the upper bound
+ {
+ t = intersect(p[i-1], p[i], bound.max());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ phigher = chigher;
+// std::cerr << i << " : higher " << p[i]
+// << " : tmin = " << tmin << ", tmax = " << tmax
+// << std::endl;
+ }
+ }
+
+ // we have to test the closing segment for intersection
+ size_t last = p.size() - 1;
+ clower = (p[0][Y] < bound.min());
+ chigher = (p[0][Y] > bound.max());
+ if (clower != plower) // cross the lower bound
+ {
+ t = intersect(p[last], p[0], bound.min());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+// std::cerr << "0 : lower " << p[0]
+// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+ if (chigher != phigher) // cross the upper bound
+ {
+ t = intersect(p[last], p[0], bound.max());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+// std::cerr << "0 : higher " << p[0]
+// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+
+ if (tmin == 1 && tmax == 0) {
+ return OptInterval();
+ } else {
+ return Interval(tmin, tmax);
+ }
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating
+ * intersection points the new parameter interval for the clipped curve
+ * is returned through the output parameter "dom"
+ */
+template <>
+OptInterval clip<intersection_point_tag> (std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ Line bl;
+ if (is_constant(A, precision)) {
+ Point M = middle_point(A.front(), A.back());
+ bl = orthogonal_orientation_line(B, M, precision);
+ } else {
+ bl = pick_orientation_line(A, precision);
+ }
+ bl.normalize();
+ Interval bound = fat_line_bounds(A, bl);
+ return clip_interval(B, bl, bound);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// collinear normal
+
+/*
+ * Compute a closed focus for the Bezier curve B and return it in F
+ * A focus is any curve through which all lines perpendicular to B(t) pass.
+ */
+void make_focus (std::vector<Point> & F, std::vector<Point> const& B)
+{
+ assert (B.size() > 2);
+ size_t n = B.size() - 1;
+ normal(F, B);
+ Point c(1, 1);
+#if VERBOSE
+ if (!solve(c, F[0], -F[n-1], B[n]-B[0]))
+ {
+ std::cerr << "make_focus: unable to make up a closed focus" << std::endl;
+ }
+#else
+ solve(c, F[0], -F[n-1], B[n]-B[0]);
+#endif
+// std::cerr << "c = " << c << std::endl;
+
+
+ // B(t) + c(t) * N(t)
+ double n_inv = 1 / (double)(n);
+ Point c0ni;
+ F.push_back(c[1] * F[n-1]);
+ F[n] += B[n];
+ for (size_t i = n-1; i > 0; --i)
+ {
+ F[i] *= -c[0];
+ c0ni = F[i];
+ F[i] += (c[1] * F[i-1]);
+ F[i] *= (i * n_inv);
+ F[i] -= c0ni;
+ F[i] += B[i];
+ }
+ F[0] *= c[0];
+ F[0] += B[0];
+}
+
+/*
+ * Compute the projection on the plane (t, d) of the control points
+ * (t, u, D(t,u)) where D(t,u) = <(B(t) - F(u)), B'(t)> with 0 <= t, u <= 1
+ * B is a Bezier curve and F is a focus of another Bezier curve.
+ * See Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping.
+ */
+void distance_control_points (std::vector<Point> & D,
+ std::vector<Point> const& B,
+ std::vector<Point> const& F)
+{
+ assert (B.size() > 1);
+ assert (!F.empty());
+ const size_t n = B.size() - 1;
+ const size_t m = F.size() - 1;
+ const size_t r = 2 * n - 1;
+ const double r_inv = 1 / (double)(r);
+ D.clear();
+ D.reserve (B.size() * F.size());
+
+ std::vector<Point> dB;
+ dB.reserve(n);
+ for (size_t k = 0; k < n; ++k)
+ {
+ dB.push_back (B[k+1] - B[k]);
+ }
+ NL::Matrix dBB(n,B.size());
+ for (size_t i = 0; i < n; ++i)
+ for (size_t j = 0; j < B.size(); ++j)
+ dBB(i,j) = dot (dB[i], B[j]);
+ NL::Matrix dBF(n, F.size());
+ for (size_t i = 0; i < n; ++i)
+ for (size_t j = 0; j < F.size(); ++j)
+ dBF(i,j) = dot (dB[i], F[j]);
+
+ size_t l;
+ double bc;
+ Point dij;
+ std::vector<double> d(F.size());
+ int rci = 1;
+ int b1 = 1;
+ for (size_t i = 0; i <= r; ++i)
+ {
+ for (size_t j = 0; j <= m; ++j)
+ {
+ d[j] = 0;
+ }
+ const size_t k0 = std::max(i, n) - n;
+ const size_t kn = std::min(i, n-1);
+ const double bri = (double)n / rci;
+
+ // assert(rci == binomial(r, i));
+ binomial_increment_k(rci, r, i);
+
+ int b2 = b1;
+ for (size_t k = k0; k <= kn; ++k)
+ {
+ //if (k > i || (i-k) > n) continue;
+ l = i - k;
+#if CHECK
+ assert (l <= n);
+#endif
+ bc = bri * b2;
+
+ // assert(b2 == binomial(n, l) * binomial(n - 1, k));
+ binomial_decrement_k(b2, n, l);
+ binomial_increment_k(b2, n - 1, k);
+
+ for (size_t j = 0; j <= m; ++j)
+ {
+ //d[j] += bc * dot(dB[k], B[l] - F[j]);
+ d[j] += bc * (dBB(k,l) - dBF(k,j));
+ }
+ }
+
+ // assert(b1 == binomial(n, i - k0) * binomial(n - 1, k0));
+ if (i < n) {
+ binomial_increment_k(b1, n, i);
+ } else {
+ binomial_increment_k(b1, n - 1, k0);
+ }
+
+ double dmin, dmax;
+ dmin = dmax = d[m];
+ for (size_t j = 0; j < m; ++j)
+ {
+ if (dmin > d[j]) dmin = d[j];
+ if (dmax < d[j]) dmax = d[j];
+ }
+ dij[0] = i * r_inv;
+ dij[1] = dmin;
+ D.push_back (dij);
+ dij[1] = dmax;
+ D.push_back (dij);
+ }
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the focus "F"; the new parameter interval for
+ * the clipped curve is returned through the output parameter "dom"
+ */
+OptInterval clip_interval (std::vector<Point> const& B,
+ std::vector<Point> const& F)
+{
+ std::vector<Point> D; // distance curve control points
+ distance_control_points(D, B, F);
+ //print(D, "D");
+// ConvexHull chD(D);
+// std::vector<Point>& p = chD.boundary; // convex hull vertices
+
+ ConvexHull p;
+ p.swap(D);
+ //print(p, "CH(D)");
+
+ bool plower, clower;
+ double t, tmin = 1, tmax = 0;
+
+ plower = (p[0][Y] < 0);
+ if (p[0][Y] == 0) // on the x axis
+ {
+ if (tmin > p[0][X]) tmin = p[0][X];
+ if (tmax < p[0][X]) tmax = p[0][X];
+// std::cerr << "0 : on x axis " << p[0]
+// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+
+ for (size_t i = 1; i < p.size(); ++i)
+ {
+ clower = (p[i][Y] < 0);
+ if (p[i][Y] == 0) // on x axis
+ {
+ if (tmin > p[i][X]) tmin = p[i][X];
+ if (tmax < p[i][X]) tmax = p[i][X];
+// std::cerr << i << " : on x axis " << p[i]
+// << " : tmin = " << tmin << ", tmax = " << tmax
+// << std::endl;
+ }
+ else if (clower != plower) // cross the x axis
+ {
+ t = intersect(p[i-1], p[i], 0);
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ plower = clower;
+// std::cerr << i << " : lower " << p[i]
+// << " : tmin = " << tmin << ", tmax = " << tmax
+// << std::endl;
+ }
+ }
+
+ // we have to test the closing segment for intersection
+ size_t last = p.size() - 1;
+ clower = (p[0][Y] < 0);
+ if (clower != plower) // cross the x axis
+ {
+ t = intersect(p[last], p[0], 0);
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+// std::cerr << "0 : lower " << p[0]
+// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+ if (tmin == 1 && tmax == 0) {
+ return OptInterval();
+ } else {
+ return Interval(tmin, tmax);
+ }
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating
+ * points which have collinear normals; the new parameter interval
+ * for the clipped curve is returned through the output parameter "dom"
+ */
+template <>
+OptInterval clip<collinear_normal_tag> (std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double /*precision*/)
+{
+ std::vector<Point> F;
+ make_focus(F, A);
+ return clip_interval(B, F);
+}
+
+
+
+const double MAX_PRECISION = 1e-8;
+const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8;
+const Interval UNIT_INTERVAL(0,1);
+const OptInterval EMPTY_INTERVAL;
+const Interval H1_INTERVAL(0, 0.5);
+const Interval H2_INTERVAL(nextafter(0.5, 1.0), 1.0);
+
+/*
+ * iterate
+ *
+ * input:
+ * A, B: control point sets of two bezier curves
+ * domA, domB: real parameter intervals of the two curves
+ * precision: required computational precision of the returned parameter ranges
+ * output:
+ * domsA, domsB: sets of parameter intervals
+ *
+ * The parameter intervals are computed by using a Bezier clipping algorithm,
+ * in case the clipping doesn't shrink the initial interval more than 20%,
+ * a subdivision step is performed.
+ * If during the computation both curves collapse to a single point
+ * the routine exits independently by the precision reached in the computation
+ * of the curve intervals.
+ */
+template <>
+void iterate<intersection_point_tag> (std::vector<Interval>& domsA,
+ std::vector<Interval>& domsB,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ Interval const& domA,
+ Interval const& domB,
+ double precision )
+{
+ // in order to limit recursion
+ static size_t counter = 0;
+ if (domA.extent() == 1 && domB.extent() == 1) counter = 0;
+ if (++counter > 100) return;
+#if VERBOSE
+ std::cerr << std::fixed << std::setprecision(16);
+ std::cerr << ">> curve subdision performed <<" << std::endl;
+ std::cerr << "dom(A) : " << domA << std::endl;
+ std::cerr << "dom(B) : " << domB << std::endl;
+// std::cerr << "angle(A) : " << angle(A) << std::endl;
+// std::cerr << "angle(B) : " << angle(B) << std::endl;
+#endif
+
+ if (precision < MAX_PRECISION)
+ precision = MAX_PRECISION;
+
+ std::vector<Point> pA = A;
+ std::vector<Point> pB = B;
+ std::vector<Point>* C1 = &pA;
+ std::vector<Point>* C2 = &pB;
+
+ Interval dompA = domA;
+ Interval dompB = domB;
+ Interval* dom1 = &dompA;
+ Interval* dom2 = &dompB;
+
+ OptInterval dom;
+
+ if ( is_constant(A, precision) && is_constant(B, precision) ){
+ Point M1 = middle_point(C1->front(), C1->back());
+ Point M2 = middle_point(C2->front(), C2->back());
+ if (are_near(M1,M2)){
+ domsA.push_back(domA);
+ domsB.push_back(domB);
+ }
+ return;
+ }
+
+ size_t iter = 0;
+ while (++iter < 100
+ && (dompA.extent() >= precision || dompB.extent() >= precision))
+ {
+#if VERBOSE
+ std::cerr << "iter: " << iter << std::endl;
+#endif
+ dom = clip<intersection_point_tag>(*C1, *C2, precision);
+
+ if (dom.empty())
+ {
+#if VERBOSE
+ std::cerr << "dom: empty" << std::endl;
+#endif
+ return;
+ }
+#if VERBOSE
+ std::cerr << "dom : " << dom << std::endl;
+#endif
+ // all other cases where dom[0] > dom[1] are invalid
+ assert(dom->min() <= dom->max());
+
+ map_to(*dom2, *dom);
+
+ portion(*C2, *dom);
+ if (is_constant(*C2, precision) && is_constant(*C1, precision))
+ {
+ Point M1 = middle_point(C1->front(), C1->back());
+ Point M2 = middle_point(C2->front(), C2->back());
+#if VERBOSE
+ std::cerr << "both curves are constant: \n"
+ << "M1: " << M1 << "\n"
+ << "M2: " << M2 << std::endl;
+ print(*C2, "C2");
+ print(*C1, "C1");
+#endif
+ if (are_near(M1,M2))
+ break; // append the new interval
+ else
+ return; // exit without appending any new interval
+ }
+
+
+ // if we have clipped less than 20% than we need to subdive the curve
+ // with the largest domain into two sub-curves
+ if (dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD)
+ {
+#if VERBOSE
+ std::cerr << "clipped less than 20% : " << dom->extent() << std::endl;
+ std::cerr << "angle(pA) : " << angle(pA) << std::endl;
+ std::cerr << "angle(pB) : " << angle(pB) << std::endl;
+#endif
+ std::vector<Point> pC1, pC2;
+ Interval dompC1, dompC2;
+ if (dompA.extent() > dompB.extent())
+ {
+ pC1 = pC2 = pA;
+ portion(pC1, H1_INTERVAL);
+ portion(pC2, H2_INTERVAL);
+ dompC1 = dompC2 = dompA;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ iterate<intersection_point_tag>(domsA, domsB, pC1, pB,
+ dompC1, dompB, precision);
+ iterate<intersection_point_tag>(domsA, domsB, pC2, pB,
+ dompC2, dompB, precision);
+ }
+ else
+ {
+ pC1 = pC2 = pB;
+ portion(pC1, H1_INTERVAL);
+ portion(pC2, H2_INTERVAL);
+ dompC1 = dompC2 = dompB;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ iterate<intersection_point_tag>(domsB, domsA, pC1, pA,
+ dompC1, dompA, precision);
+ iterate<intersection_point_tag>(domsB, domsA, pC2, pA,
+ dompC2, dompA, precision);
+ }
+ return;
+ }
+
+ swap(C1, C2);
+ swap(dom1, dom2);
+#if VERBOSE
+ std::cerr << "dom(pA) : " << dompA << std::endl;
+ std::cerr << "dom(pB) : " << dompB << std::endl;
+#endif
+ }
+ domsA.push_back(dompA);
+ domsB.push_back(dompB);
+}
+
+
+/*
+ * iterate
+ *
+ * input:
+ * A, B: control point sets of two bezier curves
+ * domA, domB: real parameter intervals of the two curves
+ * precision: required computational precision of the returned parameter ranges
+ * output:
+ * domsA, domsB: sets of parameter intervals
+ *
+ * The parameter intervals are computed by using a Bezier clipping algorithm,
+ * in case the clipping doesn't shrink the initial interval more than 20%,
+ * a subdivision step is performed.
+ * If during the computation one of the two curve interval length becomes less
+ * than MAX_PRECISION the routine exits independently by the precision reached
+ * in the computation of the other curve interval.
+ */
+template <>
+void iterate<collinear_normal_tag> (std::vector<Interval>& domsA,
+ std::vector<Interval>& domsB,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ Interval const& domA,
+ Interval const& domB,
+ double precision)
+{
+ // in order to limit recursion
+ static size_t counter = 0;
+ if (domA.extent() == 1 && domB.extent() == 1) counter = 0;
+ if (++counter > 100) return;
+#if VERBOSE
+ std::cerr << std::fixed << std::setprecision(16);
+ std::cerr << ">> curve subdision performed <<" << std::endl;
+ std::cerr << "dom(A) : " << domA << std::endl;
+ std::cerr << "dom(B) : " << domB << std::endl;
+// std::cerr << "angle(A) : " << angle(A) << std::endl;
+// std::cerr << "angle(B) : " << angle(B) << std::endl;
+#endif
+
+ if (precision < MAX_PRECISION)
+ precision = MAX_PRECISION;
+
+ std::vector<Point> pA = A;
+ std::vector<Point> pB = B;
+ std::vector<Point>* C1 = &pA;
+ std::vector<Point>* C2 = &pB;
+
+ Interval dompA = domA;
+ Interval dompB = domB;
+ Interval* dom1 = &dompA;
+ Interval* dom2 = &dompB;
+
+ OptInterval dom;
+
+ size_t iter = 0;
+ while (++iter < 100
+ && (dompA.extent() >= precision || dompB.extent() >= precision))
+ {
+#if VERBOSE
+ std::cerr << "iter: " << iter << std::endl;
+#endif
+ dom = clip<collinear_normal_tag>(*C1, *C2, precision);
+
+ if (dom.empty()) {
+#if VERBOSE
+ std::cerr << "dom: empty" << std::endl;
+#endif
+ return;
+ }
+#if VERBOSE
+ std::cerr << "dom : " << dom << std::endl;
+#endif
+ assert(dom->min() <= dom->max());
+
+ map_to(*dom2, *dom);
+
+ // it's better to stop before losing computational precision
+ if (iter > 1 && (dom2->extent() <= MAX_PRECISION))
+ {
+#if VERBOSE
+ std::cerr << "beyond max precision limit" << std::endl;
+#endif
+ break;
+ }
+
+ portion(*C2, *dom);
+ if (iter > 1 && is_constant(*C2, precision))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC1 is constant" << std::endl;
+#endif
+ break;
+ }
+
+
+ // if we have clipped less than 20% than we need to subdive the curve
+ // with the largest domain into two sub-curves
+ if ( dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD)
+ {
+#if VERBOSE
+ std::cerr << "clipped less than 20% : " << dom->extent() << std::endl;
+ std::cerr << "angle(pA) : " << angle(pA) << std::endl;
+ std::cerr << "angle(pB) : " << angle(pB) << std::endl;
+#endif
+ std::vector<Point> pC1, pC2;
+ Interval dompC1, dompC2;
+ if (dompA.extent() > dompB.extent())
+ {
+ if ((dompA.extent() / 2) < MAX_PRECISION)
+ {
+ break;
+ }
+ pC1 = pC2 = pA;
+ portion(pC1, H1_INTERVAL);
+ if (false && is_constant(pC1, precision))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC1 is constant" << std::endl;
+#endif
+ break;
+ }
+ portion(pC2, H2_INTERVAL);
+ if (is_constant(pC2, precision))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC2 is constant" << std::endl;
+#endif
+ break;
+ }
+ dompC1 = dompC2 = dompA;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ iterate<collinear_normal_tag>(domsA, domsB, pC1, pB,
+ dompC1, dompB, precision);
+ iterate<collinear_normal_tag>(domsA, domsB, pC2, pB,
+ dompC2, dompB, precision);
+ }
+ else
+ {
+ if ((dompB.extent() / 2) < MAX_PRECISION)
+ {
+ break;
+ }
+ pC1 = pC2 = pB;
+ portion(pC1, H1_INTERVAL);
+ if (is_constant(pC1, precision))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC1 is constant" << std::endl;
+#endif
+ break;
+ }
+ portion(pC2, H2_INTERVAL);
+ if (is_constant(pC2, precision))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC2 is constant" << std::endl;
+#endif
+ break;
+ }
+ dompC1 = dompC2 = dompB;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ iterate<collinear_normal_tag>(domsB, domsA, pC1, pA,
+ dompC1, dompA, precision);
+ iterate<collinear_normal_tag>(domsB, domsA, pC2, pA,
+ dompC2, dompA, precision);
+ }
+ return;
+ }
+
+ swap(C1, C2);
+ swap(dom1, dom2);
+#if VERBOSE
+ std::cerr << "dom(pA) : " << dompA << std::endl;
+ std::cerr << "dom(pB) : " << dompB << std::endl;
+#endif
+ }
+ domsA.push_back(dompA);
+ domsB.push_back(dompB);
+}
+
+
+/*
+ * get_solutions
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * input: clip - the routine used for clipping
+ * output: xs - set of pairs of parameter values
+ * at which the clipping algorithm converges
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg - Computer Aided Geometric Design
+ */
+template <typename Tag>
+void get_solutions (std::vector< std::pair<double, double> >& xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ std::pair<double, double> ci;
+ std::vector<Interval> domsA, domsB;
+ iterate<Tag> (domsA, domsB, A, B, UNIT_INTERVAL, UNIT_INTERVAL, precision);
+ if (domsA.size() != domsB.size())
+ {
+ assert (domsA.size() == domsB.size());
+ }
+ xs.clear();
+ xs.reserve(domsA.size());
+ for (size_t i = 0; i < domsA.size(); ++i)
+ {
+#if VERBOSE
+ std::cerr << i << " : domA : " << domsA[i] << std::endl;
+ std::cerr << "extent A: " << domsA[i].extent() << " ";
+ std::cerr << "precision A: " << get_precision(domsA[i]) << std::endl;
+ std::cerr << i << " : domB : " << domsB[i] << std::endl;
+ std::cerr << "extent B: " << domsB[i].extent() << " ";
+ std::cerr << "precision B: " << get_precision(domsB[i]) << std::endl;
+#endif
+ ci.first = domsA[i].middle();
+ ci.second = domsB[i].middle();
+ xs.push_back(ci);
+ }
+}
+
+} /* end namespace bezier_clipping */ } /* end namespace detail */
+
+
+/*
+ * find_collinear_normal
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * output: xs - set of pairs of parameter values
+ * at which there are collinear normals
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping
+ */
+void find_collinear_normal (std::vector< std::pair<double, double> >& xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ using detail::bezier_clipping::get_solutions;
+ using detail::bezier_clipping::collinear_normal_tag;
+ get_solutions<collinear_normal_tag>(xs, A, B, precision);
+}
+
+
+/*
+ * find_intersections_bezier_clipping
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * output: xs - set of pairs of parameter values
+ * at which crossing happens
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping
+ */
+void find_intersections_bezier_clipping (std::vector< std::pair<double, double> >& xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ using detail::bezier_clipping::get_solutions;
+ using detail::bezier_clipping::intersection_point_tag;
+ get_solutions<intersection_point_tag>(xs, A, B, precision);
+}
+
+} // 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/src/2geom/bezier-curve.cpp b/src/2geom/bezier-curve.cpp
new file mode 100644
index 0000000..ca0f787
--- /dev/null
+++ b/src/2geom/bezier-curve.cpp
@@ -0,0 +1,695 @@
+/* Bezier curve implementation
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-2009 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/bezier-curve.h>
+#include <2geom/path-sink.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/nearest-time.h>
+#include <2geom/polynomial.h>
+
+namespace Geom
+{
+
+/**
+ * @class BezierCurve
+ * @brief Two-dimensional Bezier curve of arbitrary order.
+ *
+ * Bezier curves are an expansion of the concept of linear interpolation to n points.
+ * Linear segments in 2Geom are in fact Bezier curves of order 1.
+ *
+ * Let \f$\mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_n}\f$ denote a Bezier curve
+ * of order \f$n\f$ defined by the points \f$\mathbf{p}_0, \mathbf{p}_1, \ldots, \mathbf{p}_n\f$.
+ * Bezier curve of order 1 is a linear interpolation curve between two points, defined as
+ * \f[ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1}(t) = (1-t)\mathbf{p}_0 + t\mathbf{p}_1 \f]
+ * If we now substitute points \f$\mathbf{p_0}\f$ and \f$\mathbf{p_1}\f$ in this definition
+ * by linear interpolations, we get the definition of a Bezier curve of order 2, also called
+ * a quadratic Bezier curve.
+ * \f{align*}{ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t)
+ &= (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1}(t) + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2}(t) \\
+ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t)
+ &= (1-t)^2\mathbf{p}_0 + 2(1-t)t\mathbf{p}_1 + t^2\mathbf{p}_2 \f}
+ * By substituting points for quadratic Bezier curves in the original definition,
+ * we get a Bezier curve of order 3, called a cubic Bezier curve.
+ * \f{align*}{ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t)
+ &= (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t)
+ + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t) \\
+ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t)
+ &= (1-t)^3\mathbf{p}_0+3(1-t)^2t\mathbf{p}_1+3(1-t)t^2\mathbf{p}_2+t^3\mathbf{p}_3 \f}
+ * In general, a Bezier curve or order \f$n\f$ can be recursively defined as
+ * \f[ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_n}(t)
+ = (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_{n-1}}(t)
+ + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2\ldots\mathbf{p}_n}(t) \f]
+ *
+ * This substitution can be repeated an arbitrary number of times. To picture this, imagine
+ * the evaluation of a point on the curve as follows: first, all control points are joined with
+ * straight lines, and a point corresponding to the selected time value is marked on them.
+ * Then, the marked points are joined with straight lines and the point corresponding to
+ * the time value is marked. This is repeated until only one marked point remains, which is the
+ * point at the selected time value.
+ *
+ * @image html bezier-curve-evaluation.png "Evaluation of the Bezier curve"
+ *
+ * An important property of the Bezier curves is that their parameters (control points)
+ * have an intuitive geometric interpretation. Because of this, they are frequently used
+ * in vector graphics editors.
+ *
+ * Every Bezier curve is contained in its control polygon (the convex polygon composed
+ * of its control points). This fact is useful for sweepline algorithms and intersection.
+ *
+ * @par Implementation notes
+ * The order of a Bezier curve is immuable once it has been created. Normally, you should
+ * know the order at compile time and use the BezierCurveN template. If you need to determine
+ * the order at runtime, use the BezierCurve::create() function. It will create a BezierCurveN
+ * for orders 1, 2 and 3 (up to cubic Beziers), so you can later <tt>dynamic_cast</tt>
+ * to those types, and for higher orders it will create an instance of BezierCurve.
+ *
+ * @relates BezierCurveN
+ * @ingroup Curves
+ */
+
+/**
+ * @class BezierCurveN
+ * @brief Bezier curve with compile-time specified order.
+ *
+ * @tparam degree unsigned value indicating the order of the Bezier curve
+ *
+ * @relates BezierCurve
+ * @ingroup Curves
+ */
+
+
+BezierCurve::BezierCurve(std::vector<Point> const &pts)
+ : inner(pts)
+{
+ if (pts.size() < 2) {
+ THROW_RANGEERROR("Bezier curve must have at least 2 control points");
+ }
+}
+
+bool BezierCurve::isDegenerate() const
+{
+ for (unsigned d = 0; d < 2; ++d) {
+ Coord ic = inner[d][0];
+ for (unsigned i = 1; i < size(); ++i) {
+ if (inner[d][i] != ic) return false;
+ }
+ }
+ return true;
+}
+
+/** Return false if there are at least 3 distinct control points, true otherwise. */
+bool BezierCurve::isLineSegment() const
+{
+ auto const last_idx = size() - 1;
+ if (last_idx == 1) {
+ return true;
+ }
+ auto const start = controlPoint(0);
+ auto const end = controlPoint(last_idx);
+ for (unsigned i = 1; i < last_idx; ++i) {
+ auto const pi = controlPoint(i);
+ if (pi != start && pi != end) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void BezierCurve::expandToTransformed(Rect &bbox, Affine const &transform) const
+{
+ bbox |= bounds_exact(inner * transform);
+}
+
+Coord BezierCurve::length(Coord tolerance) const
+{
+ switch (order())
+ {
+ case 0:
+ return 0.0;
+ case 1:
+ return distance(initialPoint(), finalPoint());
+ case 2:
+ {
+ std::vector<Point> pts = controlPoints();
+ return bezier_length(pts[0], pts[1], pts[2], tolerance);
+ }
+ case 3:
+ {
+ std::vector<Point> pts = controlPoints();
+ return bezier_length(pts[0], pts[1], pts[2], pts[3], tolerance);
+ }
+ default:
+ return bezier_length(controlPoints(), tolerance);
+ }
+}
+
+std::vector<CurveIntersection>
+BezierCurve::intersect(Curve const &other, Coord eps) const
+{
+ std::vector<CurveIntersection> result;
+
+ // in case we encounter an order-1 curve created from a vector
+ // or a degenerate elliptical arc
+ if (isLineSegment()) {
+ LineSegment ls(initialPoint(), finalPoint());
+ result = ls.intersect(other);
+ return result;
+ }
+
+ // here we are sure that this curve is at least a quadratic Bezier
+ BezierCurve const *bez = dynamic_cast<BezierCurve const *>(&other);
+ if (bez) {
+ std::vector<std::pair<double, double> > xs;
+ find_intersections(xs, inner, bez->inner, eps);
+ for (auto & i : xs) {
+ CurveIntersection x(*this, other, i.first, i.second);
+ result.push_back(x);
+ }
+ return result;
+ }
+
+ // pass other intersection types to the other curve
+ result = other.intersect(*this, eps);
+ transpose_in_place(result);
+ return result;
+}
+
+bool BezierCurve::isNear(Curve const &c, Coord precision) const
+{
+ if (this == &c) return true;
+
+ BezierCurve const *other = dynamic_cast<BezierCurve const *>(&c);
+ if (!other) return false;
+
+ if (!are_near(inner.at0(), other->inner.at0(), precision)) return false;
+ if (!are_near(inner.at1(), other->inner.at1(), precision)) return false;
+
+ if (size() == other->size()) {
+ for (unsigned i = 1; i < order(); ++i) {
+ if (!are_near(inner.point(i), other->inner.point(i), precision)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ // Must equalize the degrees before comparing
+ BezierCurve elevated_this, elevated_other;
+ for (size_t dim = 0; dim < 2; dim++) {
+ unsigned const our_degree = inner[dim].degree();
+ unsigned const other_degree = other->inner[dim].degree();
+
+ if (our_degree < other_degree) {
+ // Elevate our degree
+ elevated_this.inner[dim] = inner[dim].elevate_to_degree(other_degree);
+ elevated_other.inner[dim] = other->inner[dim];
+ } else if (our_degree > other_degree) {
+ // Elevate the other's degree
+ elevated_this.inner[dim] = inner[dim];
+ elevated_other.inner[dim] = other->inner[dim].elevate_to_degree(our_degree);
+ } else {
+ // Equal degrees: just copy
+ elevated_this.inner[dim] = inner[dim];
+ elevated_other.inner[dim] = other->inner[dim];
+ }
+ }
+ assert(elevated_other.size() == elevated_this.size());
+ return elevated_this.isNear(elevated_other, precision);
+ }
+}
+
+Curve *BezierCurve::portion(Coord f, Coord t) const
+{
+ if (f == 0.0 && t == 1.0) {
+ return duplicate();
+ }
+ if (f == 1.0 && t == 0.0) {
+ return reverse();
+ }
+ return new BezierCurve(Geom::portion(inner, f, t));
+}
+
+bool BezierCurve::operator==(Curve const &c) const
+{
+ if (this == &c) return true;
+
+ BezierCurve const *other = dynamic_cast<BezierCurve const *>(&c);
+ if (!other) return false;
+ if (size() != other->size()) return false;
+
+ for (unsigned i = 0; i < size(); ++i) {
+ if (controlPoint(i) != other->controlPoint(i)) return false;
+ }
+ return true;
+}
+
+Coord BezierCurve::nearestTime(Point const &p, Coord from, Coord to) const
+{
+ return nearest_time(p, inner, from, to);
+}
+
+void BezierCurve::feed(PathSink &sink, bool moveto_initial) const
+{
+ if (size() > 4) {
+ Curve::feed(sink, moveto_initial);
+ return;
+ }
+
+ Point ip = controlPoint(0);
+ if (moveto_initial) {
+ sink.moveTo(ip);
+ }
+ switch (size()) {
+ case 2:
+ sink.lineTo(controlPoint(1));
+ break;
+ case 3:
+ sink.quadTo(controlPoint(1), controlPoint(2));
+ break;
+ case 4:
+ sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3));
+ break;
+ default:
+ // TODO: add a path sink method that accepts a vector of control points
+ // and converts to cubic spline by default
+ assert(false);
+ break;
+ }
+}
+
+BezierCurve *BezierCurve::create(std::vector<Point> const &pts)
+{
+ switch (pts.size()) {
+ case 0:
+ case 1:
+ THROW_LOGICALERROR("BezierCurve::create: too few points in vector");
+ return NULL;
+ case 2:
+ return new LineSegment(pts[0], pts[1]);
+ case 3:
+ return new QuadraticBezier(pts[0], pts[1], pts[2]);
+ case 4:
+ return new CubicBezier(pts[0], pts[1], pts[2], pts[3]);
+ default:
+ return new BezierCurve(pts);
+ }
+}
+
+// optimized specializations for LineSegment
+
+template <>
+Curve *BezierCurveN<1>::derivative() const {
+ double dx = inner[X][1] - inner[X][0], dy = inner[Y][1] - inner[Y][0];
+ return new BezierCurveN<1>(Point(dx,dy),Point(dx,dy));
+}
+
+template<>
+Coord BezierCurveN<1>::nearestTime(Point const& p, Coord from, Coord to) const
+{
+ using std::swap;
+
+ if ( from > to ) swap(from, to);
+ Point ip = pointAt(from);
+ Point fp = pointAt(to);
+ Point v = fp - ip;
+ Coord l2v = L2sq(v);
+ if (l2v == 0) return 0;
+ Coord t = dot( p - ip, v ) / l2v;
+ if ( t <= 0 ) return from;
+ else if ( t >= 1 ) return to;
+ else return from + t*(to-from);
+}
+
+/* Specialized intersection routine for line segments. */
+template <>
+std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &other, Coord eps) const
+{
+ std::vector<CurveIntersection> result;
+
+ // only handle intersections with other LineSegments here
+ if (other.isLineSegment()) {
+ Line this_line(initialPoint(), finalPoint());
+ Line other_line(other.initialPoint(), other.finalPoint());
+ result = this_line.intersect(other_line);
+ filter_line_segment_intersections(result, true, true);
+ return result;
+ }
+
+ // pass all other types to the other curve
+ result = other.intersect(*this, eps);
+ transpose_in_place(result);
+ return result;
+}
+
+/** @brief Find intersections of a low-degree Bézier curve with a line segment.
+ *
+ * Uses algebraic solutions to low-degree polynomial equations which may be faster
+ * and more precise than iterative methods.
+ *
+ * @tparam degree The degree of the Bézier curve; must be 2 or 3.
+ * @param curve A Bézier curve of the given degree.
+ * @param line A line (but really a segment).
+ * @return Intersections between the passed curve and the fundamental segment of the line
+ * (the segment where the time parameter lies in the unit interval).
+ */
+template <unsigned degree>
+static std::vector<CurveIntersection> bezier_line_intersections(BezierCurveN<degree> const &curve, Line const &line)
+{
+ static_assert(degree == 2 || degree == 3, "bezier_line_intersections<degree>() error: degree must be 2 or 3.");
+
+ auto const length = distance(line.initialPoint(), line.finalPoint());
+ if (length == 0) {
+ return {};
+ }
+ std::vector<CurveIntersection> result;
+
+ // Find the isometry mapping the line to the x-axis, taking the initial point to the origin
+ // and the final point to (length, 0). Apply this transformation to the Bézier curve and
+ // extract the y-coordinate polynomial.
+ auto const transform = line.rotationToZero(Y);
+ Bezier const y = (curve.fragment() * transform)[Y];
+ std::vector<double> roots;
+
+ // Find roots of the polynomial y.
+ {
+ double const c2 = y[0] + y[2] - 2.0 * y[1];
+ double const c1 = y[1] - y[0];
+ double const c0 = y[0];
+
+ if constexpr (degree == 2) {
+ roots = solve_quadratic(c2, 2.0 * c1, c0);
+ } else if constexpr (degree == 3) {
+ double const c3 = y[3] - y[0] + 3.0 * (y[1] - y[2]);
+ roots = solve_cubic(c3, 3.0 * c2, 3.0 * c1 , c0);
+ }
+ }
+
+ // Filter the roots and assemble intersections.
+ for (double root : roots) {
+ if (root < 0.0 || root > 1.0) {
+ continue;
+ }
+ Coord x = (curve.pointAt(root) * transform)[X];
+ if (x < 0.0 || x > length) {
+ continue;
+ }
+ result.emplace_back(curve, line, root, x / length);
+ }
+ return result;
+}
+
+/* Specialized intersection routine for quadratic Bézier curves. */
+template <>
+std::vector<CurveIntersection> BezierCurveN<2>::intersect(Curve const &other, Coord eps) const
+{
+ if (auto other_bezier = dynamic_cast<BezierCurve const *>(&other)) {
+ auto const other_degree = other_bezier->order();
+ if (other_degree == 1) {
+ // Use the exact method to intersect a quadratic Bézier with a line segment.
+ auto line = Line(other_bezier->initialPoint(), other_bezier->finalPoint());
+ return bezier_line_intersections<2>(*this, line);
+ }
+ // TODO: implement exact intersection of two quadratic Béziers using the method of resultants.
+ }
+ return BezierCurve::intersect(other, eps);
+}
+
+/* Specialized intersection routine for cubic Bézier curves. */
+template <>
+std::vector<CurveIntersection> BezierCurveN<3>::intersect(Curve const &other, Coord eps) const
+{
+ if (auto other_bezier = dynamic_cast<BezierCurve const *>(&other)) {
+ if (other_bezier->order() == 1) {
+ // Use the exact method to intersect a cubic Bézier with a line segment.
+ auto line = Line(other_bezier->initialPoint(), other_bezier->finalPoint());
+ return bezier_line_intersections<3>(*this, line);
+ }
+ }
+ return BezierCurve::intersect(other, eps);
+}
+
+template <>
+int BezierCurveN<1>::winding(Point const &p) const
+{
+ Point ip = inner.at0(), fp = inner.at1();
+ if (p[Y] == std::max(ip[Y], fp[Y])) return 0;
+
+ Point v = fp - ip;
+ assert(v[Y] != 0);
+ Coord t = (p[Y] - ip[Y]) / v[Y];
+ assert(t >= 0 && t <= 1);
+ Coord xcross = lerp(t, ip[X], fp[X]);
+ if (xcross > p[X]) {
+ return v[Y] > 0 ? 1 : -1;
+ }
+ return 0;
+}
+
+template <>
+void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const
+{
+ if (moveto_initial) {
+ sink.moveTo(controlPoint(0));
+ }
+ sink.lineTo(controlPoint(1));
+}
+
+template <>
+void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const
+{
+ if (moveto_initial) {
+ sink.moveTo(controlPoint(0));
+ }
+ sink.quadTo(controlPoint(1), controlPoint(2));
+}
+
+template <>
+void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const
+{
+ if (moveto_initial) {
+ sink.moveTo(controlPoint(0));
+ }
+ sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3));
+}
+
+static void bezier_expand_to_image(Rect &range, Point const &x0, Point const &x1, Point const &x2)
+{
+ for (auto i : { X, Y }) {
+ bezier_expand_to_image(range[i], x0[i], x1[i], x2[i]);
+ }
+}
+
+static void bezier_expand_to_image(Rect &range, Point const &x0, Point const &x1, Point const &x2, Point const &x3)
+{
+ for (auto i : { X, Y }) {
+ bezier_expand_to_image(range[i], x0[i], x1[i], x2[i], x3[i]);
+ }
+}
+
+template <>
+void BezierCurveN<1>::expandToTransformed(Rect &bbox, Affine const &transform) const
+{
+ bbox.expandTo(finalPoint() * transform);
+}
+
+template <>
+void BezierCurveN<2>::expandToTransformed(Rect &bbox, Affine const &transform) const
+{
+ bezier_expand_to_image(bbox, controlPoint(0) * transform,
+ controlPoint(1) * transform,
+ controlPoint(2) * transform);
+}
+
+template <>
+void BezierCurveN<3>::expandToTransformed(Rect &bbox, Affine const &transform) const
+{
+ bezier_expand_to_image(bbox, controlPoint(0) * transform,
+ controlPoint(1) * transform,
+ controlPoint(2) * transform,
+ controlPoint(3) * transform);
+}
+
+static Coord bezier_length_internal(std::vector<Point> &v1, Coord tolerance, int level)
+{
+ /* The Bezier length algorithm used in 2Geom utilizes a simple fact:
+ * the Bezier curve is longer than the distance between its endpoints
+ * but shorter than the length of the polyline formed by its control
+ * points. When the difference between the two values is smaller than the
+ * error tolerance, we can be sure that the true value is no further than
+ * 0.5 * tolerance from their arithmetic mean. When it's larger, we recursively
+ * subdivide the Bezier curve into two parts and add their lengths.
+ *
+ * We cap the maximum number of subdivisions at 256, which corresponds to 8 levels.
+ */
+ Coord lower = distance(v1.front(), v1.back());
+ Coord upper = 0.0;
+ for (size_t i = 0; i < v1.size() - 1; ++i) {
+ upper += distance(v1[i], v1[i+1]);
+ }
+ if (upper - lower <= 2*tolerance || level >= 8) {
+ return (lower + upper) / 2;
+ }
+
+
+ std::vector<Point> v2 = v1;
+
+ /* Compute the right subdivision directly in v1 and the left one in v2.
+ * Explanation of the algorithm used:
+ * We have to compute the left and right edges of this triangle in which
+ * the top row are the control points of the Bezier curve, and each cell
+ * is equal to the arithmetic mean of the cells directly above it
+ * to the right and left. This corresponds to subdividing the Bezier curve
+ * at time value 0.5: the left edge has the control points of the first
+ * portion of the Bezier curve and the right edge - the second one.
+ * In the example we subdivide a curve with 5 control points (order 4).
+ *
+ * Start:
+ * 0 1 2 3 4
+ * ? ? ? ?
+ * ? ? ?
+ * ? ?
+ * ?
+ * # means we have overwritten the value, ? means we don't know
+ * the value yet. Numbers mean the value is at i-th position in the vector.
+ *
+ * After loop with i==1
+ * # 1 2 3 4
+ * 0 ? ? ? -> write 0 to v2[1]
+ * ? ? ?
+ * ? ?
+ * ?
+ *
+ * After loop with i==2
+ * # # 2 3 4
+ * # 1 ? ?
+ * 0 ? ? -> write 0 to v2[2]
+ * ? ?
+ * ?
+ *
+ * After loop with i==3
+ * # # # 3 4
+ * # # 2 ?
+ * # 1 ?
+ * 0 ? -> write 0 to v2[3]
+ * ?
+ *
+ * After loop with i==4, we have the right edge of the triangle in v1,
+ * and we write the last value needed for the left edge in v2[4].
+ */
+
+ for (size_t i = 1; i < v1.size(); ++i) {
+ for (size_t j = i; j > 0; --j) {
+ v1[j-1] = 0.5 * (v1[j-1] + v1[j]);
+ }
+ v2[i] = v1[0];
+ }
+
+ return bezier_length_internal(v1, 0.5 * tolerance, level + 1) +
+ bezier_length_internal(v2, 0.5 * tolerance, level + 1);
+}
+
+/** @brief Compute the length of a bezier curve given by a vector of its control points
+ * @relatesalso BezierCurve */
+Coord bezier_length(std::vector<Point> const &points, Coord tolerance)
+{
+ if (points.size() < 2) return 0.0;
+ std::vector<Point> v1 = points;
+ return bezier_length_internal(v1, tolerance, 0);
+}
+
+static Coord bezier_length_internal(Point a0, Point a1, Point a2, Coord tolerance, int level)
+{
+ Coord lower = distance(a0, a2);
+ Coord upper = distance(a0, a1) + distance(a1, a2);
+
+ if (upper - lower <= 2*tolerance || level >= 8) {
+ return (lower + upper) / 2;
+ }
+
+ Point // Casteljau subdivision
+ // b0 = a0,
+ // c0 = a2,
+ b1 = 0.5*(a0 + a1),
+ c1 = 0.5*(a1 + a2),
+ b2 = 0.5*(b1 + c1); // == c2
+ return bezier_length_internal(a0, b1, b2, 0.5 * tolerance, level + 1) +
+ bezier_length_internal(b2, c1, a2, 0.5 * tolerance, level + 1);
+}
+
+/** @brief Compute the length of a quadratic bezier curve given by its control points
+ * @relatesalso QuadraticBezier */
+Coord bezier_length(Point a0, Point a1, Point a2, Coord tolerance)
+{
+ return bezier_length_internal(a0, a1, a2, tolerance, 0);
+}
+
+static Coord bezier_length_internal(Point a0, Point a1, Point a2, Point a3, Coord tolerance, int level)
+{
+ Coord lower = distance(a0, a3);
+ Coord upper = distance(a0, a1) + distance(a1, a2) + distance(a2, a3);
+
+ if (upper - lower <= 2*tolerance || level >= 8) {
+ return (lower + upper) / 2;
+ }
+
+ Point // Casteljau subdivision
+ // b0 = a0,
+ // c0 = a3,
+ b1 = 0.5*(a0 + a1),
+ t0 = 0.5*(a1 + a2),
+ c1 = 0.5*(a2 + a3),
+ b2 = 0.5*(b1 + t0),
+ c2 = 0.5*(t0 + c1),
+ b3 = 0.5*(b2 + c2); // == c3
+ return bezier_length_internal(a0, b1, b2, b3, 0.5 * tolerance, level + 1) +
+ bezier_length_internal(b3, c2, c1, a3, 0.5 * tolerance, level + 1);
+}
+
+/** @brief Compute the length of a cubic bezier curve given by its control points
+ * @relatesalso CubicBezier */
+Coord bezier_length(Point a0, Point a1, Point a2, Point a3, Coord tolerance)
+{
+ return bezier_length_internal(a0, a1, a2, a3, tolerance, 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/src/2geom/bezier-utils.cpp b/src/2geom/bezier-utils.cpp
new file mode 100644
index 0000000..181b5b3
--- /dev/null
+++ b/src/2geom/bezier-utils.cpp
@@ -0,0 +1,997 @@
+/* Bezier interpolation for inkscape drawing code.
+ *
+ * Original code published in:
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2003,2004 Monash University
+ *
+ * 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.
+ *
+ */
+
+#define SP_HUGE 1e5
+#define noBEZIER_DEBUG
+
+#ifdef HAVE_IEEEFP_H
+# include <ieeefp.h>
+#endif
+
+#include <2geom/bezier-utils.h>
+#include <2geom/math-utils.h>
+#include <assert.h>
+
+namespace Geom {
+
+/* Forward declarations */
+static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2, double tolerance_sq);
+static void estimate_lengths(Point bezier[],
+ Point const data[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2);
+static void estimate_bi(Point b[4], unsigned ei,
+ Point const data[], double const u[], unsigned len);
+static void reparameterize(Point const d[], unsigned len, double u[], Point const bezCurve[]);
+static double NewtonRaphsonRootFind(Point const Q[], Point const &P, double u);
+static Point darray_center_tangent(Point const d[], unsigned center, unsigned length);
+static Point darray_right_tangent(Point const d[], unsigned const len);
+static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]);
+static void chord_length_parameterize(Point const d[], double u[], unsigned len);
+static double compute_max_error_ratio(Point const d[], double const u[], unsigned len,
+ Point const bezCurve[], double tolerance,
+ unsigned *splitPoint);
+static double compute_hook(Point const &a, Point const &b, double const u, Point const bezCurve[],
+ double const tolerance);
+
+
+static Point const unconstrained_tangent(0, 0);
+
+
+/*
+ * B0, B1, B2, B3 : Bezier multipliers
+ */
+
+#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B2(u) ( 3 * u * u * ( 1.0 - u ) )
+#define B3(u) ( u * u * u )
+
+#ifdef BEZIER_DEBUG
+# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) )
+# define BEZIER_ASSERT(b) do { \
+ DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \
+ DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \
+ DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \
+ DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \
+ } while(0)
+#else
+# define DOUBLE_ASSERT(x) do { } while(0)
+# define BEZIER_ASSERT(b) do { } while(0)
+#endif
+
+
+/**
+ * Fit a single-segment Bezier curve to a set of digitized points.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic(Point *bezier, Point const *data, int len, double error)
+{
+ return bezier_fit_cubic_r(bezier, data, len, error, 1);
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, with
+ * possible weedout of identical points and NaNs.
+ *
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers)
+{
+ if(bezier == NULL ||
+ data == NULL ||
+ len <= 0 ||
+ max_beziers >= (1ul << (31 - 2 - 1 - 3)))
+ return -1;
+
+ Point *uniqued_data = new Point[len];
+ unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data);
+
+ if ( uniqued_len < 2 ) {
+ delete[] uniqued_data;
+ return 0;
+ }
+
+ /* Call fit-cubic function with recursion. */
+ int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len,
+ unconstrained_tangent, unconstrained_tangent,
+ error, max_beziers);
+ delete[] uniqued_data;
+ return ret;
+}
+
+/**
+ * Copy points from src to dest, filter out points containing NaN and
+ * adjacent points with equal x and y.
+ * \return length of dest
+ */
+static unsigned
+copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[])
+{
+ unsigned si = 0;
+ for (;;) {
+ if ( si == src_len ) {
+ return 0;
+ }
+ if (!std::isnan(src[si][X]) &&
+ !std::isnan(src[si][Y])) {
+ dest[0] = Point(src[si]);
+ ++si;
+ break;
+ }
+ si++;
+ }
+ unsigned di = 0;
+ for (; si < src_len; ++si) {
+ Point const src_pt = Point(src[si]);
+ if ( src_pt != dest[di]
+ && !std::isnan(src_pt[X])
+ && !std::isnan(src_pt[Y])) {
+ dest[++di] = src_pt;
+ }
+ }
+ unsigned dest_len = di + 1;
+ assert( dest_len <= src_len );
+ return dest_len;
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, without
+ * possible weedout of identical points and NaNs.
+ *
+ * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1].
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ */
+int
+bezier_fit_cubic_full(Point bezier[], int split_points[],
+ Point const data[], int const len,
+ Point const &tHat1, Point const &tHat2,
+ double const error, unsigned const max_beziers)
+{
+ if(!(bezier != NULL) ||
+ !(data != NULL) ||
+ !(len > 0) ||
+ !(max_beziers >= 1) ||
+ !(error >= 0.0))
+ return -1;
+
+ if ( len < 2 ) return 0;
+
+ if ( len == 2 ) {
+ /* We have 2 points, which can be fitted trivially. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+ double const dist = distance(bezier[0], bezier[3]) / 3.0;
+ if (std::isnan(dist)) {
+ /* Numerical problem, fall back to straight line segment. */
+ bezier[1] = bezier[0];
+ bezier[2] = bezier[3];
+ } else {
+ bezier[1] = ( is_zero(tHat1)
+ ? ( 2 * bezier[0] + bezier[3] ) / 3.
+ : bezier[0] + dist * tHat1 );
+ bezier[2] = ( is_zero(tHat2)
+ ? ( bezier[0] + 2 * bezier[3] ) / 3.
+ : bezier[3] + dist * tHat2 );
+ }
+ BEZIER_ASSERT(bezier);
+ return 1;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ unsigned splitPoint; /* Point to split point set at. */
+ bool is_corner;
+ {
+ double *u = new double[len];
+ chord_length_parameterize(data, u, len);
+ if ( u[len - 1] == 0.0 ) {
+ /* Zero-length path: every point in data[] is the same.
+ *
+ * (Clients aren't allowed to pass such data; handling the case is defensive
+ * programming.)
+ */
+ delete[] u;
+ return 0;
+ }
+
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+
+ /* Find max deviation of points to fitted curve. */
+ double const tolerance = sqrt(error + 1e-9);
+ double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+
+ /* If error not too large, then try some reparameterization and iteration. */
+ if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) {
+ int const maxIterations = 4; /* std::max times to try iterating */
+ for (int i = 0; i < maxIterations; i++) {
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+ maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+ }
+ }
+ delete[] u;
+ is_corner = (maxErrorRatio < 0);
+ }
+
+ if (is_corner) {
+ assert(splitPoint < unsigned(len));
+ if (splitPoint == 0) {
+ if (is_zero(tHat1)) {
+ /* Got spike even with unconstrained initial tangent. */
+ ++splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2,
+ error, max_beziers);
+ }
+ } else if (splitPoint == unsigned(len - 1)) {
+ if (is_zero(tHat2)) {
+ /* Got spike even with unconstrained final tangent. */
+ --splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent,
+ error, max_beziers);
+ }
+ }
+ }
+
+ if ( 1 < max_beziers ) {
+ /*
+ * Fitting failed -- split at max error point and fit recursively
+ */
+ unsigned const rec_max_beziers1 = max_beziers - 1;
+
+ Point recTHat2, recTHat1;
+ if (is_corner) {
+ if(!(0 < splitPoint && splitPoint < unsigned(len - 1)))
+ return -1;
+ recTHat1 = recTHat2 = unconstrained_tangent;
+ } else {
+ /* Unit tangent vector at splitPoint. */
+ recTHat2 = darray_center_tangent(data, splitPoint, len);
+ recTHat1 = -recTHat2;
+ }
+ int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1,
+ tHat1, recTHat2, error, rec_max_beziers1);
+ if ( nsegs1 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[1]: recursive call failed\n");
+#endif
+ return -1;
+ }
+ assert( nsegs1 != 0 );
+ if (split_points != NULL) {
+ split_points[nsegs1 - 1] = splitPoint;
+ }
+ unsigned const rec_max_beziers2 = max_beziers - nsegs1;
+ int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4,
+ ( split_points == NULL
+ ? NULL
+ : split_points + nsegs1 ),
+ data + splitPoint, len - splitPoint,
+ recTHat1, tHat2, error, rec_max_beziers2);
+ if ( nsegs2 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[2]: recursive call failed\n");
+#endif
+ return -1;
+ }
+
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n",
+ nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers);
+#endif
+ return nsegs1 + nsegs2;
+ } else {
+ return -1;
+ }
+}
+
+
+/**
+ * Fill in \a bezier[] based on the given data and tangent requirements, using
+ * a least-squares fit.
+ *
+ * Each of tHat1 and tHat2 should be either a zero vector or a unit vector.
+ * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise,
+ * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3].
+ *
+ * \param tolerance_sq Used only for an initial guess as to tangent directions
+ * when \a tHat1 or \a tHat2 is zero.
+ */
+static void
+generate_bezier(Point bezier[],
+ Point const data[], double const u[], unsigned const len,
+ Point const &tHat1, Point const &tHat2,
+ double const tolerance_sq)
+{
+ bool const est1 = is_zero(tHat1);
+ bool const est2 = is_zero(tHat2);
+ Point est_tHat1( est1
+ ? darray_left_tangent(data, len, tolerance_sq)
+ : tHat1 );
+ Point est_tHat2( est2
+ ? darray_right_tangent(data, len, tolerance_sq)
+ : tHat2 );
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ /* We find that darray_right_tangent tends to produce better results
+ for our current freehand tool than full estimation. */
+ if (est1) {
+ estimate_bi(bezier, 1, data, u, len);
+ if (bezier[1] != bezier[0]) {
+ est_tHat1 = unit_vector(bezier[1] - bezier[0]);
+ }
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ }
+}
+
+
+static void
+estimate_lengths(Point bezier[],
+ Point const data[], double const uPrime[], unsigned const len,
+ Point const &tHat1, Point const &tHat2)
+{
+ double C[2][2]; /* Matrix C. */
+ double X[2]; /* Matrix X. */
+
+ /* Create the C and X matrices. */
+ C[0][0] = 0.0;
+ C[0][1] = 0.0;
+ C[1][0] = 0.0;
+ C[1][1] = 0.0;
+ X[0] = 0.0;
+ X[1] = 0.0;
+
+ /* First and last control points of the Bezier curve are positioned exactly at the first and
+ last data points. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+
+ for (unsigned i = 0; i < len; i++) {
+ /* Bezier control point coefficients. */
+ double const b0 = B0(uPrime[i]);
+ double const b1 = B1(uPrime[i]);
+ double const b2 = B2(uPrime[i]);
+ double const b3 = B3(uPrime[i]);
+
+ /* rhs for eqn */
+ Point const a1 = b1 * tHat1;
+ Point const a2 = b2 * tHat2;
+
+ C[0][0] += dot(a1, a1);
+ C[0][1] += dot(a1, a2);
+ C[1][0] = C[0][1];
+ C[1][1] += dot(a2, a2);
+
+ /* Additional offset to the data point from the predicted point if we were to set bezier[1]
+ to bezier[0] and bezier[2] to bezier[3]. */
+ Point const shortfall
+ = ( data[i]
+ - ( ( b0 + b1 ) * bezier[0] )
+ - ( ( b2 + b3 ) * bezier[3] ) );
+ X[0] += dot(a1, shortfall);
+ X[1] += dot(a2, shortfall);
+ }
+
+ /* We've constructed a pair of equations in the form of a matrix product C * alpha = X.
+ Now solve for alpha. */
+ double alpha_l, alpha_r;
+
+ /* Compute the determinants of C and X. */
+ double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+ if ( det_C0_C1 != 0 ) {
+ /* Apparently Kramer's rule. */
+ double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+ double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+ alpha_l = det_X_C1 / det_C0_C1;
+ alpha_r = det_C0_X / det_C0_C1;
+ } else {
+ /* The matrix is under-determined. Try requiring alpha_l == alpha_r.
+ *
+ * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same
+ * variable in the equations. We can do this by adding the columns of C to form a single
+ * column, to be multiplied by alpha to give the column vector X.
+ *
+ * We try each row in turn.
+ */
+ double const c0 = C[0][0] + C[0][1];
+ if (c0 != 0) {
+ alpha_l = alpha_r = X[0] / c0;
+ } else {
+ double const c1 = C[1][0] + C[1][1];
+ if (c1 != 0) {
+ alpha_l = alpha_r = X[1] / c1;
+ } else {
+ /* Let the below code handle this. */
+ alpha_l = alpha_r = 0.;
+ }
+ }
+ }
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get
+ coincident control points that lead to divide by zero in any subsequent
+ NewtonRaphsonRootFind() call.) */
+ /// \todo Check whether this special-casing is necessary now that
+ /// NewtonRaphsonRootFind handles non-positive denominator.
+ if ( alpha_l < 1.0e-6 ||
+ alpha_r < 1.0e-6 )
+ {
+ alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0;
+ }
+
+ /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and
+ right, respectively. */
+ bezier[1] = alpha_l * tHat1 + bezier[0];
+ bezier[2] = alpha_r * tHat2 + bezier[3];
+
+ return;
+}
+
+static double lensq(Point const p) {
+ return dot(p, p);
+}
+
+static void
+estimate_bi(Point bezier[4], unsigned const ei,
+ Point const data[], double const u[], unsigned const len)
+{
+ if(!(1 <= ei && ei <= 2))
+ return;
+ unsigned const oi = 3 - ei;
+ double num[2] = {0., 0.};
+ double den = 0.;
+ for (unsigned i = 0; i < len; ++i) {
+ double const ui = u[i];
+ double const b[4] = {
+ B0(ui),
+ B1(ui),
+ B2(ui),
+ B3(ui)
+ };
+
+ for (unsigned d = 0; d < 2; ++d) {
+ num[d] += b[ei] * (b[0] * bezier[0][d] +
+ b[oi] * bezier[oi][d] +
+ b[3] * bezier[3][d] +
+ - data[i][d]);
+ }
+ den -= b[ei] * b[ei];
+ }
+
+ if (den != 0.) {
+ for (unsigned d = 0; d < 2; ++d) {
+ bezier[ei][d] = num[d] / den;
+ }
+ } else {
+ bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.;
+ }
+}
+
+/**
+ * Given set of points and their parameterization, try to find a better assignment of parameter
+ * values for the points.
+ *
+ * \param d Array of digitized points.
+ * \param u Current parameter values.
+ * \param bezCurve Current fitted curve.
+ * \param len Number of values in both d and u arrays.
+ * Also the size of the array that is allocated for return.
+ */
+static void
+reparameterize(Point const d[],
+ unsigned const len,
+ double u[],
+ Point const bezCurve[])
+{
+ assert( 2 <= len );
+
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* Otherwise, consider including 0 and last in the below loop. */
+
+ for (unsigned i = 1; i < last; i++) {
+ u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]);
+ }
+}
+
+/**
+ * Use Newton-Raphson iteration to find better root.
+ *
+ * \param Q Current fitted curve
+ * \param P Digitized point
+ * \param u Parameter value for "P"
+ *
+ * \return Improved u
+ */
+static double
+NewtonRaphsonRootFind(Point const Q[], Point const &P, double const u)
+{
+ assert( 0.0 <= u );
+ assert( u <= 1.0 );
+
+ /* Generate control vertices for Q'. */
+ Point Q1[3];
+ for (unsigned i = 0; i < 3; i++) {
+ Q1[i] = 3.0 * ( Q[i+1] - Q[i] );
+ }
+
+ /* Generate control vertices for Q''. */
+ Point Q2[2];
+ for (unsigned i = 0; i < 2; i++) {
+ Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] );
+ }
+
+ /* Compute Q(u), Q'(u) and Q''(u). */
+ Point const Q_u = bezier_pt(3, Q, u);
+ Point const Q1_u = bezier_pt(2, Q1, u);
+ Point const Q2_u = bezier_pt(1, Q2, u);
+
+ /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the
+ distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the
+ distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum
+ distance from P to Q(u)). */
+ Point const diff = Q_u - P;
+ double numerator = dot(diff, Q1_u);
+ double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u);
+
+ double improved_u;
+ if ( denominator > 0. ) {
+ /* One iteration of Newton-Raphson:
+ improved_u = u - f(u)/f'(u) */
+ improved_u = u - ( numerator / denominator );
+ } else {
+ /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather
+ than local minimum), so we move an arbitrary amount in the right direction. */
+ if ( numerator > 0. ) {
+ improved_u = u * .98 - .01;
+ } else if ( numerator < 0. ) {
+ /* Deliberately asymmetrical, to reduce the chance of cycling. */
+ improved_u = .031 + u * .98;
+ } else {
+ improved_u = u;
+ }
+ }
+
+ if (!std::isfinite(improved_u)) {
+ improved_u = u;
+ } else if ( improved_u < 0.0 ) {
+ improved_u = 0.0;
+ } else if ( improved_u > 1.0 ) {
+ improved_u = 1.0;
+ }
+
+ /* Ensure that improved_u isn't actually worse. */
+ {
+ double const diff_lensq = lensq(diff);
+ for (double proportion = .125; ; proportion += .125) {
+ if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) {
+ if ( proportion > 1.0 ) {
+ //g_warning("found proportion %g", proportion);
+ improved_u = u;
+ break;
+ }
+ improved_u = ( ( 1 - proportion ) * improved_u +
+ proportion * u );
+ } else {
+ break;
+ }
+ }
+ }
+
+ DOUBLE_ASSERT(improved_u);
+ return improved_u;
+}
+
+/**
+ * Evaluate a Bezier curve at parameter value \a t.
+ *
+ * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. Must be less
+ * than 4.
+ * \param V The control points for the Bezier curve. Must have (\a degree+1)
+ * elements.
+ * \param t The "parameter" value, specifying whereabouts along the curve to
+ * evaluate. Typically in the range [0.0, 1.0].
+ *
+ * Let s = 1 - t.
+ * BezierII(1, V) gives (s, t) * V, i.e. t of the way
+ * from V[0] to V[1].
+ * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V.
+ * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V.
+ *
+ * The derivative of BezierII(i, V) with respect to t
+ * is i * BezierII(i-1, V'), where for all j, V'[j] =
+ * V[j + 1] - V[j].
+ */
+Point
+bezier_pt(unsigned const degree, Point const V[], double const t)
+{
+ /** Pascal's triangle. */
+ static int const pascal[4][4] = {{1, 0, 0, 0},
+ {1, 1, 0, 0},
+ {1, 2, 1, 0},
+ {1, 3, 3, 1}};
+ assert( degree < 4);
+ double const s = 1.0 - t;
+
+ /* Calculate powers of t and s. */
+ double spow[4];
+ double tpow[4];
+ spow[0] = 1.0; spow[1] = s;
+ tpow[0] = 1.0; tpow[1] = t;
+ for (unsigned i = 1; i < degree; ++i) {
+ spow[i + 1] = spow[i] * s;
+ tpow[i + 1] = tpow[i] * t;
+ }
+
+ Point ret = spow[degree] * V[0];
+ for (unsigned i = 1; i <= degree; ++i) {
+ ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i];
+ }
+ return ret;
+}
+
+/*
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ * Approximate unit tangents at endpoints and "center" of digitized curve
+ */
+
+/**
+ * Estimate the (forward) tangent at point d[first + 0.5].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ * \pre (2 \<= len) and (d[0] != d[1]).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len)
+{
+ assert( len >= 2 );
+ assert( d[0] != d[1] );
+ return unit_vector( d[1] - d[0] );
+}
+
+/**
+ * Estimates the (backward) tangent at d[last - 0.5].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+static Point
+darray_right_tangent(Point const d[], unsigned const len)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ unsigned const prev = last - 1;
+ assert( d[last] != d[prev] );
+ return unit_vector( d[prev] - d[last] );
+}
+
+/**
+ * Estimate the (forward) tangent at point d[0].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ *
+ * \pre 2 \<= len.
+ * \pre d[0] != d[1].
+ * \pre all[p in d] in_svg_plane(p).
+ * \post is_unit_vector(ret).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ for (unsigned i = 1;;) {
+ Point const pi(d[i]);
+ Point const t(pi - d[0]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ ++i;
+ if (i == len) {
+ return ( distsq == 0
+ ? darray_left_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[last].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+Point
+darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ unsigned const last = len - 1;
+ for (unsigned i = last - 1;; i--) {
+ Point const pi(d[i]);
+ Point const t(pi - d[last]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ if (i == 0) {
+ return ( distsq == 0
+ ? darray_right_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[center], by averaging the two
+ * segments connected to d[center] (and then normalizing the result).
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre (0 \< center \< len - 1) and d is uniqued (at least in
+ * the immediate vicinity of \a center).
+ */
+static Point
+darray_center_tangent(Point const d[],
+ unsigned const center,
+ unsigned const len)
+{
+ assert( center != 0 );
+ assert( center < len - 1 );
+
+ Point ret;
+ if ( d[center + 1] == d[center - 1] ) {
+ /* Rotate 90 degrees in an arbitrary direction. */
+ Point const diff = d[center] - d[center - 1];
+ ret = rot90(diff);
+ } else {
+ ret = d[center - 1] - d[center + 1];
+ }
+ ret.normalize();
+ return ret;
+}
+
+
+/**
+ * Assign parameter values to digitized points using relative distances between points.
+ *
+ * \pre Parameter array u must have space for \a len items.
+ */
+static void
+chord_length_parameterize(Point const d[], double u[], unsigned const len)
+{
+ if(!( 2 <= len ))
+ return;
+
+ /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */
+ u[0] = 0.0;
+ for (unsigned i = 1; i < len; i++) {
+ double const dist = distance(d[i], d[i-1]);
+ u[i] = u[i-1] + dist;
+ }
+
+ /* Then scale to [0.0 .. 1.0]. */
+ double tot_len = u[len - 1];
+ if(!( tot_len != 0 ))
+ return;
+ if (std::isfinite(tot_len)) {
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] /= tot_len;
+ }
+ } else {
+ /* We could do better, but this probably never happens anyway. */
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] = i / (double) ( len - 1 );
+ }
+ }
+
+ /** \todo
+ * It's been reported that u[len - 1] can differ from 1.0 on some
+ * systems (amd64), despite it having been calculated as x / x where x
+ * is isFinite and non-zero.
+ */
+ if (u[len - 1] != 1) {
+ double const diff = u[len - 1] - 1;
+ if (fabs(diff) > 1e-13) {
+ assert(0); // No warnings in 2geom
+ //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1",
+ // u[len - 1], diff);
+ }
+ u[len - 1] = 1;
+ }
+
+#ifdef BEZIER_DEBUG
+ assert( u[0] == 0.0 && u[len - 1] == 1.0 );
+ for (unsigned i = 1; i < len; i++) {
+ assert( u[i] >= u[i-1] );
+ }
+#endif
+}
+
+
+
+
+/**
+ * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum
+ * error is non-zero) set \a *splitPoint to the corresponding index.
+ *
+ * \pre 2 \<= len.
+ * \pre u[0] == 0.
+ * \pre u[len - 1] == 1.0.
+ * \post ((ret == 0.0)
+ * || ((*splitPoint \< len - 1)
+ * \&\& (*splitPoint != 0 || ret \< 0.0))).
+ */
+static double
+compute_max_error_ratio(Point const d[], double const u[], unsigned const len,
+ Point const bezCurve[], double const tolerance,
+ unsigned *const splitPoint)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* I.e. assert that the error for the first & last points is zero.
+ * Otherwise we should include those points in the below loop.
+ * The assertion is also necessary to ensure 0 < splitPoint < last.
+ */
+
+ double maxDistsq = 0.0; /* Maximum error */
+ double max_hook_ratio = 0.0;
+ unsigned snap_end = 0;
+ Point prev = bezCurve[0];
+ for (unsigned i = 1; i <= last; i++) {
+ Point const curr = bezier_pt(3, bezCurve, u[i]);
+ double const distsq = lensq( curr - d[i] );
+ if ( distsq > maxDistsq ) {
+ maxDistsq = distsq;
+ *splitPoint = i;
+ }
+ double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance);
+ if (max_hook_ratio < hook_ratio) {
+ max_hook_ratio = hook_ratio;
+ snap_end = i;
+ }
+ prev = curr;
+ }
+
+ double const dist_ratio = sqrt(maxDistsq) / tolerance;
+ double ret;
+ if (max_hook_ratio <= dist_ratio) {
+ ret = dist_ratio;
+ } else {
+ assert(0 < snap_end);
+ ret = -max_hook_ratio;
+ *splitPoint = snap_end - 1;
+ }
+ assert( ret == 0.0
+ || ( ( *splitPoint < last )
+ && ( *splitPoint != 0 || ret < 0. ) ) );
+ return ret;
+}
+
+/**
+ * Whereas compute_max_error_ratio() checks for itself that each data point
+ * is near some point on the curve, this function checks that each point on
+ * the curve is near some data point (or near some point on the polyline
+ * defined by the data points, or something like that: we allow for a
+ * "reasonable curviness" from such a polyline). "Reasonable curviness"
+ * means we draw a circle centred at the midpoint of a..b, of radius
+ * proportional to the length |a - b|, and require that each point on the
+ * segment of bezCurve between the parameters of a and b be within that circle.
+ * If any point P on the bezCurve segment is outside of that allowable
+ * region (circle), then we return some metric that increases with the
+ * distance from P to the circle.
+ *
+ * Given that this is a fairly arbitrary criterion for finding appropriate
+ * places for sharp corners, we test only one point on bezCurve, namely
+ * the point on bezCurve with parameter halfway between our estimated
+ * parameters for a and b. (Alternatives are taking the farthest of a
+ * few parameters between those of a and b, or even using a variant of
+ * NewtonRaphsonFindRoot() for finding the maximum rather than minimum
+ * distance.)
+ */
+static double
+compute_hook(Point const &a, Point const &b, double const u, Point const bezCurve[],
+ double const tolerance)
+{
+ Point const P = bezier_pt(3, bezCurve, u);
+ double const dist = distance((a+b)*.5, P);
+ if (dist < tolerance) {
+ return 0;
+ }
+ double const allowed = distance(a, b) + tolerance;
+ return dist / allowed;
+ /** \todo
+ * effic: Hooks are very rare. We could start by comparing
+ * distsq, only resorting to the more expensive L2 in cases of
+ * uncertainty.
+ */
+}
+
+}
+
+/*
+ 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/src/2geom/bezier.cpp b/src/2geom/bezier.cpp
new file mode 100644
index 0000000..264b3c2
--- /dev/null
+++ b/src/2geom/bezier.cpp
@@ -0,0 +1,415 @@
+/**
+ * @file
+ * @brief Bernstein-Bezier polynomial
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Michael Sloan <mgsloan@gmail.com>
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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/bezier.h>
+#include <2geom/solver.h>
+#include <2geom/concepts.h>
+#include <2geom/choose.h>
+
+namespace Geom {
+
+std::vector<Coord> Bezier::valueAndDerivatives(Coord t, unsigned n_derivs) const {
+ /* This is inelegant, as it uses several extra stores. I think there might be a way to
+ * evaluate roughly in situ. */
+
+ // initialize return vector with zeroes, such that we only need to replace the non-zero derivs
+ std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0));
+
+ // initialize temp storage variables
+ std::valarray<Coord> d_(order()+1);
+ for(unsigned i = 0; i < size(); i++) {
+ d_[i] = c_[i];
+ }
+
+ unsigned nn = n_derivs + 1;
+ if(n_derivs > order()) {
+ nn = order()+1; // only calculate the non zero derivs
+ }
+ for(unsigned di = 0; di < nn; di++) {
+ //val_n_der[di] = (casteljau_subdivision(t, &d_[0], NULL, NULL, order() - di));
+ val_n_der[di] = bernstein_value_at(t, &d_[0], order() - di);
+ for(unsigned i = 0; i < order() - di; i++) {
+ d_[i] = (order()-di)*(d_[i+1] - d_[i]);
+ }
+ }
+
+ return val_n_der;
+}
+
+void Bezier::subdivide(Coord t, Bezier *left, Bezier *right) const
+{
+ if (left) {
+ left->c_.resize(size());
+ if (right) {
+ right->c_.resize(size());
+ casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0],
+ &left->c_[0], &right->c_[0], order());
+ } else {
+ casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0],
+ &left->c_[0], NULL, order());
+ }
+ } else if (right) {
+ right->c_.resize(size());
+ casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0],
+ NULL, &right->c_[0], order());
+ }
+}
+
+std::pair<Bezier, Bezier> Bezier::subdivide(Coord t) const
+{
+ std::pair<Bezier, Bezier> ret;
+ subdivide(t, &ret.first, &ret.second);
+ return ret;
+}
+
+std::vector<Coord> Bezier::roots() const
+{
+ std::vector<Coord> solutions;
+ find_bezier_roots(solutions, 0, 1);
+ std::sort(solutions.begin(), solutions.end());
+ return solutions;
+}
+
+std::vector<Coord> Bezier::roots(Interval const &ivl) const
+{
+ std::vector<Coord> solutions;
+ find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, ivl.min(), ivl.max());
+ std::sort(solutions.begin(), solutions.end());
+ return solutions;
+}
+
+Bezier Bezier::forward_difference(unsigned k) const
+{
+ Bezier fd(Order(order() - k));
+ int n = fd.size();
+
+ for (int i = 0; i < n; i++) {
+ fd[i] = 0;
+ int b = (i & 1) ? -1 : 1; // b = (-1)^j binomial(n, j - i)
+ for (int j = i; j < n; j++) {
+ fd[i] += c_[j] * b;
+ binomial_increment_k(b, n, j - i);
+ b = -b;
+ }
+ }
+ return fd;
+}
+
+Bezier Bezier::elevate_degree() const
+{
+ Bezier ed(Order(order()+1));
+ unsigned n = size();
+ ed[0] = c_[0];
+ ed[n] = c_[n-1];
+ for(unsigned i = 1; i < n; i++) {
+ ed[i] = (i*c_[i-1] + (n - i)*c_[i])/(n);
+ }
+ return ed;
+}
+
+Bezier Bezier::reduce_degree() const
+{
+ if(order() == 0) return *this;
+ Bezier ed(Order(order()-1));
+ unsigned n = size();
+ ed[0] = c_[0];
+ ed[n-1] = c_[n]; // ensure exact endpoints
+ unsigned middle = n/2;
+ for(unsigned i = 1; i < middle; i++) {
+ ed[i] = (n*c_[i] - i*ed[i-1])/(n-i);
+ }
+ for(unsigned i = n-1; i >= middle; i--) {
+ ed[i] = (n*c_[i] - i*ed[n-i])/(i);
+ }
+ return ed;
+}
+
+Bezier Bezier::elevate_to_degree(unsigned newDegree) const
+{
+ Bezier ed = *this;
+ for(unsigned i = degree(); i < newDegree; i++) {
+ ed = ed.elevate_degree();
+ }
+ return ed;
+}
+
+Bezier Bezier::deflate() const
+{
+ if(order() == 0) return *this;
+ unsigned n = order();
+ Bezier b(Order(n-1));
+ for(unsigned i = 0; i < n; i++) {
+ b[i] = (n*c_[i+1])/(i+1);
+ }
+ return b;
+}
+
+SBasis Bezier::toSBasis() const
+{
+ SBasis sb;
+ bezier_to_sbasis(sb, (*this));
+ return sb;
+ //return bezier_to_sbasis(&c_[0], order());
+}
+
+Bezier &Bezier::operator+=(Bezier const &other)
+{
+ if (c_.size() > other.size()) {
+ c_ += other.elevate_to_degree(degree()).c_;
+ } else if (c_.size() < other.size()) {
+ *this = elevate_to_degree(other.degree());
+ c_ += other.c_;
+ } else {
+ c_ += other.c_;
+ }
+ return *this;
+}
+
+Bezier &Bezier::operator-=(Bezier const &other)
+{
+ if (c_.size() > other.size()) {
+ c_ -= other.elevate_to_degree(degree()).c_;
+ } else if (c_.size() < other.size()) {
+ *this = elevate_to_degree(other.degree());
+ c_ -= other.c_;
+ } else {
+ c_ -= other.c_;
+ }
+ return *this;
+}
+
+Bezier operator*(Bezier const &f, Bezier const &g)
+{
+ int m = f.order();
+ int n = g.order();
+ Bezier h(Bezier::Order(m+n));
+ // h_k = sum_(i+j=k) (m i)f_i (n j)g_j / (m+n k)
+
+ int mci = 1;
+ for (int i = 0; i <= m; i++) {
+ double const fi = mci * f[i];
+
+ int ncj = 1;
+ for (int j = 0; j <= n; j++) {
+ h[i + j] += fi * ncj * g[j];
+ binomial_increment_k(ncj, n, j);
+ }
+
+ binomial_increment_k(mci, m, i);
+ }
+
+ int mnck = 1;
+ for (int k = 0; k <= m + n; k++) {
+ h[k] /= mnck;
+ binomial_increment_k(mnck, m + n, k);
+ }
+
+ return h;
+}
+
+Bezier portion(Bezier const &a, double from, double to)
+{
+ Bezier ret(a);
+
+ bool reverse_result = false;
+ if (from > to) {
+ std::swap(from, to);
+ reverse_result = true;
+ }
+
+ do {
+ if (from == 0) {
+ if (to == 1) {
+ break;
+ }
+ casteljau_subdivision<double>(to, &ret.c_[0], &ret.c_[0], NULL, ret.order());
+ break;
+ }
+ casteljau_subdivision<double>(from, &ret.c_[0], NULL, &ret.c_[0], ret.order());
+ if (to == 1) break;
+ casteljau_subdivision<double>((to - from) / (1 - from), &ret.c_[0], &ret.c_[0], NULL, ret.order());
+ // to protect against numerical inaccuracy in the above expression, we manually set
+ // the last coefficient to a value evaluated directly from the original polynomial
+ ret.c_[ret.order()] = a.valueAt(to);
+ } while(0);
+
+ if (reverse_result) {
+ std::reverse(&ret.c_[0], &ret.c_[0] + ret.c_.size());
+ }
+ return ret;
+}
+
+Bezier derivative(Bezier const &a)
+{
+ //if(a.order() == 1) return Bezier(0.0);
+ if(a.order() == 1) return Bezier(a.c_[1]-a.c_[0]);
+ Bezier der(Bezier::Order(a.order()-1));
+
+ for(unsigned i = 0; i < a.order(); i++) {
+ der.c_[i] = a.order()*(a.c_[i+1] - a.c_[i]);
+ }
+ return der;
+}
+
+Bezier integral(Bezier const &a)
+{
+ Bezier inte(Bezier::Order(a.order()+1));
+
+ inte[0] = 0;
+ for(unsigned i = 0; i < inte.order(); i++) {
+ inte[i+1] = inte[i] + a[i]/(inte.order());
+ }
+ return inte;
+}
+
+OptInterval bounds_fast(Bezier const &b)
+{
+ OptInterval ret = Interval::from_array(&const_cast<Bezier&>(b).c_[0], b.size());
+ return ret;
+}
+
+OptInterval bounds_exact(Bezier const &b)
+{
+ OptInterval ret(b.at0(), b.at1());
+ std::vector<Coord> r = derivative(b).roots();
+ for (double i : r) {
+ ret->expandTo(b.valueAt(i));
+ }
+ return ret;
+}
+
+OptInterval bounds_local(Bezier const &b, OptInterval const &i)
+{
+ //return bounds_local(b.toSBasis(), i);
+ if (i) {
+ return bounds_fast(portion(b, i->min(), i->max()));
+ } else {
+ return OptInterval();
+ }
+}
+
+/*
+ * The general bézier of degree n is
+ *
+ * p(t) = sum_{i = 0...n} binomial(n, i) t^i (1 - t)^(n - i) x[i]
+ *
+ * It can be written explicitly as a polynomial in t as
+ *
+ * p(t) = sum_{i = 0...n} binomial(n, i) t^i [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ]
+ *
+ * Its derivative is
+ *
+ * p'(t) = n sum_{i = 1...n} binomial(n - 1, i - 1) t^(i - 1) [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ]
+ *
+ * This is used by the various specialisations below as an optimisation for low degree n <= 3.
+ * In the remaining cases, the generic implementation is used which resorts to iteration.
+ */
+
+void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2)
+{
+ range.expandTo(x2);
+
+ if (range.contains(x1)) {
+ // The interval contains all control points, and therefore the entire curve.
+ return;
+ }
+
+ // p'(t) / 2 = at + b
+ auto a = (x2 - x1) - (x1 - x0);
+ auto b = x1 - x0;
+
+ // t = -b / a
+ if (std::abs(a) > EPSILON) {
+ auto t = -b / a;
+ if (t > 0.0 && t < 1.0) {
+ auto s = 1.0 - t;
+ auto x = s * s * x0 + 2 * s * t * x1 + t * t * x2;
+ range.expandTo(x);
+ }
+ }
+}
+
+void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2, Coord x3)
+{
+ range.expandTo(x3);
+
+ if (range.contains(x1) && range.contains(x2)) {
+ // The interval contains all control points, and therefore the entire curve.
+ return;
+ }
+
+ // p'(t) / 3 = at^2 + 2bt + c
+ auto a = (x3 - x0) - 3 * (x2 - x1);
+ auto b = (x2 - x1) - (x1 - x0);
+ auto c = x1 - x0;
+
+ auto expand = [&] (Coord t) {
+ if (t > 0.0 && t < 1.0) {
+ auto s = 1.0 - t;
+ auto x = s * s * s * x0 + 3 * s * s * t * x1 + 3 * t * t * s * x2 + t * t * t * x3;
+ range.expandTo(x);
+ }
+ };
+
+ // t = (-b ± sqrt(b^2 - ac)) / a
+ if (std::abs(a) < EPSILON) {
+ if (std::abs(b) > EPSILON) {
+ expand(-c / (2 * b));
+ }
+ } else {
+ auto d2 = b * b - a * c;
+ if (d2 >= 0.0) {
+ auto bsign = b >= 0.0 ? 1 : -1;
+ auto tmp = -(b + bsign * std::sqrt(d2));
+ expand(tmp / a);
+ expand(c / tmp); // Using Vieta's formula: product of roots == c/a
+ }
+ }
+}
+
+} // 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/src/2geom/cairo-path-sink.cpp b/src/2geom/cairo-path-sink.cpp
new file mode 100644
index 0000000..a04f715
--- /dev/null
+++ b/src/2geom/cairo-path-sink.cpp
@@ -0,0 +1,127 @@
+/**
+ * @file
+ * @brief Path sink for Cairo contexts
+ *//*
+ * Copyright 2014 Krzysztof Kosiński
+ *
+ * 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, output 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.
+ *
+ */
+
+#ifdef HAVE_CAIRO
+
+#include <cairo.h>
+#include <2geom/cairo-path-sink.h>
+#include <2geom/elliptical-arc.h>
+
+namespace Geom {
+
+CairoPathSink::CairoPathSink(cairo_t *cr)
+ : _cr(cr)
+{}
+
+void CairoPathSink::moveTo(Point const &p)
+{
+ cairo_move_to(_cr, p[X], p[Y]);
+ _current_point = p;
+}
+
+void CairoPathSink::lineTo(Point const &p)
+{
+ cairo_line_to(_cr, p[X], p[Y]);
+ _current_point = p;
+}
+
+void CairoPathSink::curveTo(Point const &p1, Point const &p2, Point const &p3)
+{
+ cairo_curve_to(_cr, p1[X], p1[Y], p2[X], p2[Y], p3[X], p3[Y]);
+ _current_point = p3;
+}
+
+void CairoPathSink::quadTo(Point const &p1, Point const &p2)
+{
+ // degree-elevate to cubic Bezier, since Cairo doesn't do quad Beziers
+ // google "Bezier degree elevation" for more info
+ Point q1 = (1./3.) * _current_point + (2./3.) * p1;
+ Point q2 = (2./3.) * p1 + (1./3.) * p2;
+ // q3 = p2
+ cairo_curve_to(_cr, q1[X], q1[Y], q2[X], q2[Y], p2[X], p2[Y]);
+ _current_point = p2;
+}
+
+void CairoPathSink::arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point const &p)
+{
+ EllipticalArc arc(_current_point, rx, ry, angle, large_arc, sweep, p);
+ // Cairo only does circular arcs.
+ // To do elliptical arcs, we must use a temporary transform.
+ Affine uct = arc.unitCircleTransform();
+
+ // TODO move Cairo-2Geom matrix conversion into a common location
+ cairo_matrix_t cm;
+ cm.xx = uct[0];
+ cm.xy = uct[2];
+ cm.x0 = uct[4];
+ cm.yx = uct[1];
+ cm.yy = uct[3];
+ cm.y0 = uct[5];
+
+ cairo_save(_cr);
+ cairo_transform(_cr, &cm);
+ if (sweep) {
+ cairo_arc(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle());
+ } else {
+ cairo_arc_negative(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle());
+ }
+ _current_point = p;
+ cairo_restore(_cr);
+
+ /* Note that an extra linear segment will be inserted before the arc
+ * if Cairo considers the current point distinct from the initial point
+ * of the arc; we could partially alleviate this by not emitting
+ * linear segments that are followed by arc segments, but this would require
+ * buffering the input curves. */
+}
+
+void CairoPathSink::closePath()
+{
+ cairo_close_path(_cr);
+}
+
+void CairoPathSink::flush() {}
+
+} // namespace Geom
+
+#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/src/2geom/circle.cpp b/src/2geom/circle.cpp
new file mode 100644
index 0000000..d97487a
--- /dev/null
+++ b/src/2geom/circle.cpp
@@ -0,0 +1,337 @@
+/** @file
+ * @brief Circle shape
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2008-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 <2geom/circle.h>
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+namespace Geom {
+
+Rect Circle::boundsFast() const
+{
+ Point rr(_radius, _radius);
+ Rect bbox(_center - rr, _center + rr);
+ return bbox;
+}
+
+void Circle::setCoefficients(Coord A, Coord B, Coord C, Coord D)
+{
+ if (A == 0) {
+ THROW_RANGEERROR("square term coefficient == 0");
+ }
+
+ //std::cerr << "B = " << B << " C = " << C << " D = " << D << std::endl;
+
+ Coord b = B / A;
+ Coord c = C / A;
+ Coord d = D / A;
+
+ _center[X] = -b/2;
+ _center[Y] = -c/2;
+ Coord r2 = _center[X] * _center[X] + _center[Y] * _center[Y] - d;
+
+ if (r2 < 0) {
+ THROW_RANGEERROR("ray^2 < 0");
+ }
+
+ _radius = std::sqrt(r2);
+}
+
+void Circle::coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const
+{
+ A = 1;
+ B = -2 * _center[X];
+ C = -2 * _center[Y];
+ D = _center[X] * _center[X] + _center[Y] * _center[Y] - _radius * _radius;
+}
+
+std::vector<Coord> Circle::coefficients() const
+{
+ std::vector<Coord> c(4);
+ coefficients(c[0], c[1], c[2], c[3]);
+ return c;
+}
+
+
+Zoom Circle::unitCircleTransform() const
+{
+ Zoom ret(_radius, _center / _radius);
+ return ret;
+}
+
+Zoom Circle::inverseUnitCircleTransform() const
+{
+ if (_radius == 0) {
+ THROW_RANGEERROR("degenerate circle does not have an inverse unit circle transform");
+ }
+
+ Zoom ret(1/_radius, Translate(-_center));
+ return ret;
+}
+
+Point Circle::initialPoint() const
+{
+ Point p(_center);
+ p[X] += _radius;
+ return p;
+}
+
+Point Circle::pointAt(Coord t) const {
+ return _center + Point::polar(t) * _radius;
+}
+
+Coord Circle::valueAt(Coord t, Dim2 d) const {
+ Coord delta = (d == X ? std::cos(t) : std::sin(t));
+ return _center[d] + delta * _radius;
+}
+
+Coord Circle::timeAt(Point const &p) const {
+ if (_center == p) return 0;
+ return atan2(p - _center);
+}
+
+Coord Circle::nearestTime(Point const &p) const {
+ return timeAt(p);
+}
+
+bool Circle::contains(Rect const &r) const
+{
+ for (unsigned i = 0; i < 4; ++i) {
+ if (!contains(r.corner(i))) return false;
+ }
+ return true;
+}
+
+bool Circle::contains(Circle const &other) const
+{
+ Coord cdist = distance(_center, other._center);
+ Coord rdist = fabs(_radius - other._radius);
+ return cdist <= rdist;
+}
+
+bool Circle::intersects(Line const &l) const
+{
+ // http://mathworld.wolfram.com/Circle-LineIntersection.html
+ Coord dr = l.vector().length();
+ Coord r = _radius;
+ Coord D = cross(l.initialPoint(), l.finalPoint());
+ Coord delta = r*r * dr*dr - D*D;
+ if (delta >= 0) return true;
+ return false;
+}
+
+bool Circle::intersects(Circle const &other) const
+{
+ Coord cdist = distance(_center, other._center);
+ Coord rsum = _radius + other._radius;
+ return cdist <= rsum;
+}
+
+
+std::vector<ShapeIntersection> Circle::intersect(Line const &l) const
+{
+ // http://mathworld.wolfram.com/Circle-LineIntersection.html
+ Coord dr = l.vector().length();
+ Coord dx = l.vector().x();
+ Coord dy = l.vector().y();
+ Coord D = cross(l.initialPoint() - _center, l.finalPoint() - _center);
+ Coord delta = _radius*_radius * dr*dr - D*D;
+
+ std::vector<ShapeIntersection> result;
+ if (delta < 0) return result;
+ if (delta == 0) {
+ Coord ix = (D*dy) / (dr*dr);
+ Coord iy = (-D*dx) / (dr*dr);
+ Point ip(ix, iy); ip += _center;
+ result.emplace_back(timeAt(ip), l.timeAt(ip), ip);
+ return result;
+ }
+
+ Coord sqrt_delta = std::sqrt(delta);
+ Coord signmod = dy < 0 ? -1 : 1;
+
+ Coord i1x = (D*dy + signmod * dx * sqrt_delta) / (dr*dr);
+ Coord i1y = (-D*dx + fabs(dy) * sqrt_delta) / (dr*dr);
+ Point i1p(i1x, i1y); i1p += _center;
+
+ Coord i2x = (D*dy - signmod * dx * sqrt_delta) / (dr*dr);
+ Coord i2y = (-D*dx - fabs(dy) * sqrt_delta) / (dr*dr);
+ Point i2p(i2x, i2y); i2p += _center;
+
+ result.emplace_back(timeAt(i1p), l.timeAt(i1p), i1p);
+ result.emplace_back(timeAt(i2p), l.timeAt(i2p), i2p);
+ return result;
+}
+
+std::vector<ShapeIntersection> Circle::intersect(LineSegment const &l) const
+{
+ std::vector<ShapeIntersection> result = intersect(Line(l));
+ filter_line_segment_intersections(result);
+ return result;
+}
+
+std::vector<ShapeIntersection> Circle::intersect(Circle const &other) const
+{
+ std::vector<ShapeIntersection> result;
+
+ if (*this == other) {
+ THROW_INFINITESOLUTIONS();
+ }
+ if (contains(other)) return result;
+ if (!intersects(other)) return result;
+
+ // See e.g. http://mathworld.wolfram.com/Circle-CircleIntersection.html
+ // Basically, we figure out where is the third point of a triangle
+ // with two points in the centers and with edge lengths equal to radii
+ Point cv = other._center - _center;
+ Coord d = cv.length();
+ Coord R = radius(), r = other.radius();
+
+ if (d == R + r) {
+ Point px = lerp(R / d, _center, other._center);
+ Coord T = timeAt(px), t = other.timeAt(px);
+ result.emplace_back(T, t, px);
+ return result;
+ }
+
+ // q is the distance along the line between centers to the perpendicular line
+ // that goes through both intersections.
+ Coord q = (d*d - r*r + R*R) / (2*d);
+ Point qp = lerp(q/d, _center, other._center);
+
+ // The triangle given by the points:
+ // _center, qp, intersection
+ // is a right triangle. Determine the distance between qp and intersection
+ // using the Pythagorean theorem.
+ Coord h = std::sqrt(R*R - q*q);
+ Point qd = (h/d) * cv.cw();
+
+ // now compute the intersection points
+ Point x1 = qp + qd;
+ Point x2 = qp - qd;
+
+ result.emplace_back(timeAt(x1), other.timeAt(x1), x1);
+ result.emplace_back(timeAt(x2), other.timeAt(x2), x2);
+ return result;
+}
+
+/**
+ @param inner a point whose angle with the circle center is inside the angle that the arc spans
+ */
+EllipticalArc *
+Circle::arc(Point const& initial, Point const& inner, Point const& final) const
+{
+ // TODO native implementation!
+ Ellipse e(_center[X], _center[Y], _radius, _radius, 0);
+ return e.arc(initial, inner, final);
+}
+
+bool Circle::operator==(Circle const &other) const
+{
+ if (_center != other._center) return false;
+ if (_radius != other._radius) return false;
+ return true;
+}
+
+D2<SBasis> Circle::toSBasis() const
+{
+ D2<SBasis> B;
+ Linear bo = Linear(0, 2 * M_PI);
+
+ B[0] = cos(bo,4);
+ B[1] = sin(bo,4);
+
+ B = B * _radius + _center;
+
+ return B;
+}
+
+
+void Circle::fit(std::vector<Point> const& points)
+{
+ size_t sz = points.size();
+ if (sz < 2) {
+ THROW_RANGEERROR("fitting error: too few points passed");
+ }
+ if (sz == 2) {
+ _center = points[0] * 0.5 + points[1] * 0.5;
+ _radius = distance(points[0], points[1]) / 2;
+ return;
+ }
+
+ NL::LFMCircle model;
+ NL::least_squeares_fitter<NL::LFMCircle> fitter(model, sz);
+
+ for (size_t i = 0; i < sz; ++i) {
+ fitter.append(points[i]);
+ }
+ fitter.update();
+
+ NL::Vector z(sz, 0.0);
+ model.instance(*this, fitter.result(z));
+}
+
+
+bool are_near(Circle const &a, Circle const &b, Coord eps)
+{
+ // to check whether no point on a is further than eps from b,
+ // we check two things:
+ // 1. if radii differ by more than eps, there is definitely a point that fails
+ // 2. if they differ by less, we check the centers. They have to be closer
+ // together if the radius differs, since the maximum distance will be
+ // equal to sum of radius difference and distance between centers.
+ if (!are_near(a.radius(), b.radius(), eps)) return false;
+ Coord adjusted_eps = eps - fabs(a.radius() - b.radius());
+ return are_near(a.center(), b.center(), adjusted_eps);
+}
+
+std::ostream &operator<<(std::ostream &out, Circle const &c)
+{
+ out << "Circle(" << c.center() << ", " << format_coord_nice(c.radius()) << ")";
+ return out;
+}
+
+} // 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/src/2geom/concepts.cpp b/src/2geom/concepts.cpp
new file mode 100644
index 0000000..e8c8e5c
--- /dev/null
+++ b/src/2geom/concepts.cpp
@@ -0,0 +1,69 @@
+/**
+ * \file
+ * \brief Concept checking
+ *//*
+ * Copyright 2015 Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * 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, output 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 <boost/concept/assert.hpp>
+#include <2geom/concepts.h>
+
+#include <2geom/line.h>
+#include <2geom/circle.h>
+#include <2geom/ellipse.h>
+#include <2geom/curves.h>
+#include <2geom/convex-hull.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+
+#include <2geom/bezier.h>
+#include <2geom/sbasis.h>
+#include <2geom/linear.h>
+#include <2geom/d2.h>
+
+namespace Geom {
+
+void concept_checks()
+{
+ BOOST_CONCEPT_ASSERT((ShapeConcept<Line>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<Circle>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<Ellipse>));
+ BOOST_CONCEPT_ASSERT((ShapeConcept<BezierCurve>));
+ BOOST_CONCEPT_ASSERT((ShapeConcept<EllipticalArc>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<SBasisCurve>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<ConvexHull>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<Path>));
+ //BOOST_CONCEPT_ASSERT((ShapeConcept<PathVector>));
+
+ BOOST_CONCEPT_ASSERT((NearConcept<Coord>));
+ BOOST_CONCEPT_ASSERT((NearConcept<Point>));
+
+ BOOST_CONCEPT_ASSERT((FragmentConcept<Bezier>));
+ BOOST_CONCEPT_ASSERT((FragmentConcept<Linear>));
+ BOOST_CONCEPT_ASSERT((FragmentConcept<SBasis>));
+}
+
+} // end namespace Geom
diff --git a/src/2geom/conic_section_clipper_impl.cpp b/src/2geom/conic_section_clipper_impl.cpp
new file mode 100644
index 0000000..8b0445e
--- /dev/null
+++ b/src/2geom/conic_section_clipper_impl.cpp
@@ -0,0 +1,574 @@
+/* Conic section clipping with respect to a rectangle
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail>
+ *
+ * Copyright 2009 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 <optional>
+
+#ifndef CLIP_WITH_CAIRO_SUPPORT
+ #include <2geom/conic_section_clipper.h>
+#endif
+
+namespace Geom
+{
+
+/*
+ * Find rectangle-conic crossing points. They are returned in the
+ * "crossing_points" parameter.
+ * The method returns true if the conic section intersects at least one
+ * of the four lines passing through rectangle edges, else it returns false.
+ */
+bool CLIPPER_CLASS::intersect (std::vector<Point> & crossing_points) const
+{
+ crossing_points.clear();
+
+ std::vector<double> rts;
+ std::vector<Point> cpts;
+ // rectangle corners
+ enum {TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT};
+
+ bool no_crossing = true;
+
+ // right edge
+ cs.roots (rts, R.right(), X);
+ if (!rts.empty())
+ {
+ no_crossing = false;
+ DBGPRINT ("CLIP: right: rts[0] = ", rts[0])
+ DBGPRINTIF ((rts.size() == 2), "CLIP: right: rts[1] = ", rts[1])
+
+ Point corner1 = R.corner(TOP_RIGHT);
+ Point corner2 = R.corner(BOTTOM_RIGHT);
+
+ for (double rt : rts)
+ {
+ if (rt < R.top() || rt > R.bottom()) continue;
+ Point P (R.right(), rt);
+ if (are_near (P, corner1))
+ P = corner1;
+ else if (are_near (P, corner2))
+ P = corner2;
+
+ cpts.push_back (P);
+ }
+ if (cpts.size() == 2 && are_near (cpts[0], cpts[1]))
+ {
+ cpts[0] = middle_point (cpts[0], cpts[1]);
+ cpts.pop_back();
+ }
+ }
+
+ // top edge
+ cs.roots (rts, R.top(), Y);
+ if (!rts.empty())
+ {
+ no_crossing = false;
+ DBGPRINT ("CLIP: top: rts[0] = ", rts[0])
+ DBGPRINTIF ((rts.size() == 2), "CLIP: top: rts[1] = ", rts[1])
+
+ Point corner1 = R.corner(TOP_RIGHT);
+ Point corner2 = R.corner(TOP_LEFT);
+
+ for (double rt : rts)
+ {
+ if (rt < R.left() || rt > R.right()) continue;
+ Point P (rt, R.top());
+ if (are_near (P, corner1))
+ P = corner1;
+ else if (are_near (P, corner2))
+ P = corner2;
+
+ cpts.push_back (P);
+ }
+ if (cpts.size() == 2 && are_near (cpts[0], cpts[1]))
+ {
+ cpts[0] = middle_point (cpts[0], cpts[1]);
+ cpts.pop_back();
+ }
+ }
+
+ // left edge
+ cs.roots (rts, R.left(), X);
+ if (!rts.empty())
+ {
+ no_crossing = false;
+ DBGPRINT ("CLIP: left: rts[0] = ", rts[0])
+ DBGPRINTIF ((rts.size() == 2), "CLIP: left: rts[1] = ", rts[1])
+
+ Point corner1 = R.corner(TOP_LEFT);
+ Point corner2 = R.corner(BOTTOM_LEFT);
+
+ for (double rt : rts)
+ {
+ if (rt < R.top() || rt > R.bottom()) continue;
+ Point P (R.left(), rt);
+ if (are_near (P, corner1))
+ P = corner1;
+ else if (are_near (P, corner2))
+ P = corner2;
+
+ cpts.push_back (P);
+ }
+ if (cpts.size() == 2 && are_near (cpts[0], cpts[1]))
+ {
+ cpts[0] = middle_point (cpts[0], cpts[1]);
+ cpts.pop_back();
+ }
+ }
+
+ // bottom edge
+ cs.roots (rts, R.bottom(), Y);
+ if (!rts.empty())
+ {
+ no_crossing = false;
+ DBGPRINT ("CLIP: bottom: rts[0] = ", rts[0])
+ DBGPRINTIF ((rts.size() == 2), "CLIP: bottom: rts[1] = ", rts[1])
+
+ Point corner1 = R.corner(BOTTOM_RIGHT);
+ Point corner2 = R.corner(BOTTOM_LEFT);
+
+ for (double rt : rts)
+ {
+ if (rt < R.left() || rt > R.right()) continue;
+ Point P (rt, R.bottom());
+ if (are_near (P, corner1))
+ P = corner1;
+ else if (are_near (P, corner2))
+ P = corner2;
+
+ cpts.push_back (P);
+ }
+ if (cpts.size() == 2 && are_near (cpts[0], cpts[1]))
+ {
+ cpts[0] = middle_point (cpts[0], cpts[1]);
+ cpts.pop_back();
+ }
+ }
+
+ DBGPRINT ("CLIP: intersect: crossing_points.size (with duplicates) = ",
+ cpts.size())
+
+ // remove duplicates
+ std::sort (cpts.begin(), cpts.end(), Point::LexLess<X>());
+ cpts.erase (std::unique (cpts.begin(), cpts.end()), cpts.end());
+
+
+ // Order crossing points on the rectangle edge clockwise, so two consecutive
+ // crossing points would be the end points of a conic arc all inside or all
+ // outside the rectangle.
+ std::map<double, size_t> cp_angles;
+ for (size_t i = 0; i < cpts.size(); ++i)
+ {
+ cp_angles.insert (std::make_pair (cs.angle_at (cpts[i]), i));
+ }
+
+ std::map<double, size_t>::const_iterator pos;
+ for (pos = cp_angles.begin(); pos != cp_angles.end(); ++pos)
+ {
+ crossing_points.push_back (cpts[pos->second]);
+ }
+
+ DBGPRINT ("CLIP: intersect: crossing_points.size = ", crossing_points.size())
+ DBGPRINTCOLL ("CLIP: intersect: crossing_points:", crossing_points)
+
+ return no_crossing;
+} // end function intersect
+
+
+
+inline
+double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3)
+{
+ return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2));
+}
+
+
+/*
+ * Test if two crossing points are the end points of a conic arc inner to the
+ * rectangle. In such a case the method returns true, else it returns false.
+ * Moreover by the parameter "M" it returns a point inner to the conic arc
+ * with the given end-points.
+ *
+ */
+bool CLIPPER_CLASS::are_paired (Point& M, const Point & P1, const Point & P2) const
+{
+ using std::swap;
+
+ /*
+ * we looks for the points on the conic whose tangent is parallel to the
+ * arc chord P1P2, they will be extrema of the conic arc P1P2 wrt the
+ * direction orthogonal to the chord
+ */
+ Point dir = P2 - P1;
+ DBGPRINT ("CLIP: are_paired: first point: ", P1)
+ DBGPRINT ("CLIP: are_paired: second point: ", P2)
+
+ double grad0 = 2 * cs.coeff(0) * dir[0] + cs.coeff(1) * dir[1];
+ double grad1 = cs.coeff(1) * dir[0] + 2 * cs.coeff(2) * dir[1];
+ double grad2 = cs.coeff(3) * dir[0] + cs.coeff(4) * dir[1];
+
+
+ /*
+ * such points are found intersecating the conic section with the line
+ * orthogonal to "grad": the derivative wrt the "dir" direction
+ */
+ Line gl (grad0, grad1, grad2);
+ std::vector<double> rts;
+ rts = cs.roots (gl);
+ DBGPRINT ("CLIP: are_paired: extrema: rts.size() = ", rts.size())
+
+
+
+ std::vector<Point> extrema;
+ for (double rt : rts)
+ {
+ extrema.push_back (gl.pointAt (rt));
+ }
+
+ if (extrema.size() == 2)
+ {
+ // in case we are dealing with an hyperbola we could have two extrema
+ // on the same side wrt the line passing through P1 and P2, but
+ // only the nearer extremum is on the arc P1P2
+ double side0 = signed_triangle_area (P1, extrema[0], P2);
+ double side1 = signed_triangle_area (P1, extrema[1], P2);
+
+ if (sgn(side0) == sgn(side1))
+ {
+ if (std::fabs(side0) > std::fabs(side1)) {
+ swap(extrema[0], extrema[1]);
+ }
+ extrema.pop_back();
+ }
+ }
+
+ std::vector<Point> inner_points;
+ for (auto & i : extrema)
+ {
+ if (!R.contains (i)) continue;
+ // in case we are dealing with an ellipse tangent to two orthogonal
+ // rectangle edges we could have two extrema on opposite sides wrt the
+ // line passing through P1P2 and both inner the rectangle; anyway, since
+ // we order the crossing points clockwise we have only one extremum
+ // that follows such an ordering wrt P1 and P2;
+ // remark: the other arc will be selected when we test for the arc P2P1.
+ double P1angle = cs.angle_at (P1);
+ double P2angle = cs.angle_at (P2);
+ double Qangle = cs.angle_at (i);
+ if (P1angle < P2angle && !(P1angle <= Qangle && Qangle <= P2angle))
+ continue;
+ if (P1angle > P2angle && !(P1angle <= Qangle || Qangle <= P2angle))
+ continue;
+
+ inner_points.push_back (i);
+ }
+
+ if (inner_points.size() > 1)
+ {
+ THROW_LOGICALERROR ("conic section clipper: "
+ "more than one extremum found");
+ }
+ else if (inner_points.size() == 1)
+ {
+ M = inner_points.front();
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+ * Pair the points contained in the "crossing_points" vector; the paired points
+ * are put in the paired_points vector so that given a point with an even index
+ * and the next one they are the end points of a conic arc that is inner to the
+ * rectangle. In the "inner_points" are returned points that are inner to the
+ * arc, where the inner point with index k is related to the arc with end
+ * points with indexes 2k, 2k+1. In case there are unpaired points the are put
+ * in to the "single_points" vector.
+ */
+void CLIPPER_CLASS::pairing (std::vector<Point> & paired_points,
+ std::vector<Point> & inner_points,
+ const std::vector<Point> & crossing_points)
+{
+ paired_points.clear();
+ paired_points.reserve (crossing_points.size());
+
+ inner_points.clear();
+ inner_points.reserve (crossing_points.size() / 2);
+
+ single_points.clear();
+
+ // to keep trace of which crossing points have been paired
+ std::vector<bool> paired (crossing_points.size(), false);
+
+ Point M;
+
+ // by the way we have ordered crossing points we need to test one point wrt
+ // the next point only, for pairing; moreover the last point need to be
+ // tested wrt the first point; pay attention: one point can be paired both
+ // with the previous and the next one: this is not an error, think of
+ // crossing points that are tangent to the rectangle edge (and inner);
+ for (size_t i = 0; i < crossing_points.size(); ++i)
+ {
+ // we need to test the last point wrt the first one
+ size_t j = (i == 0) ? (crossing_points.size() - 1) : (i-1);
+ if (are_paired (M, crossing_points[j], crossing_points[i]))
+ {
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.8, 1.0);
+ draw_line_seg (cr, crossing_points[j], crossing_points[i]);
+ draw_handle (cr, crossing_points[j]);
+ draw_handle (cr, crossing_points[i]);
+ draw_handle (cr, M);
+ cairo_stroke (cr);
+#endif
+ paired[j] = paired[i] = true;
+ paired_points.push_back (crossing_points[j]);
+ paired_points.push_back (crossing_points[i]);
+ inner_points.push_back (M);
+ }
+ }
+
+ // some point are not paired with any point, e.g. a crossing point tangent
+ // to a rectangle edge but with the conic arc outside the rectangle
+ for (size_t i = 0; i < paired.size(); ++i)
+ {
+ if (!paired[i])
+ single_points.push_back (crossing_points[i]);
+ }
+ DBGPRINTCOLL ("single_points", single_points)
+
+}
+
+
+/*
+ * This method clip the section conic wrt the rectangle and returns the inner
+ * conic arcs as a vector of RatQuad objects by the "arcs" parameter.
+ */
+bool CLIPPER_CLASS::clip (std::vector<RatQuad> & arcs)
+{
+ using std::swap;
+
+ arcs.clear();
+ std::vector<Point> crossing_points;
+ std::vector<Point> paired_points;
+ std::vector<Point> inner_points;
+
+ Line l1, l2;
+ if (cs.decompose (l1, l2))
+ {
+ bool inner_empty = true;
+
+ DBGINFO ("CLIP: degenerate section conic")
+
+ std::optional<LineSegment> ls1 = Geom::clip (l1, R);
+ if (ls1)
+ {
+ if (ls1->isDegenerate())
+ {
+ single_points.push_back (ls1->initialPoint());
+ }
+ else
+ {
+ Point M = middle_point (*ls1);
+ arcs.emplace_back(ls1->initialPoint(), M, ls1->finalPoint(), 1);
+ inner_empty = false;
+ }
+ }
+
+ std::optional<LineSegment> ls2 = Geom::clip (l2, R);
+ if (ls2)
+ {
+ if (ls2->isDegenerate())
+ {
+ single_points.push_back (ls2->initialPoint());
+ }
+ else
+ {
+ Point M = middle_point (*ls2);
+ arcs.emplace_back(ls2->initialPoint(), M, ls2->finalPoint(), 1);
+ inner_empty = false;
+ }
+ }
+
+ return !inner_empty;
+ }
+
+
+ bool no_crossing = intersect (crossing_points);
+
+ // if the only crossing point is a rectangle corner than the section conic
+ // is all outside the rectangle
+ if (crossing_points.size() == 1)
+ {
+ for (size_t i = 0; i < 4; ++i)
+ {
+ if (crossing_points[0] == R.corner(i))
+ {
+ single_points.push_back (R.corner(i));
+ return false;
+ }
+ }
+ }
+
+ // if the conic does not cross any line passing through a rectangle edge or
+ // it is tangent to only one edge then it is an ellipse
+ if (no_crossing
+ || (crossing_points.size() == 1 && single_points.empty()))
+ {
+ // if the ellipse centre is inside the rectangle
+ // then so it is the ellipse
+ std::optional<Point> c = cs.centre();
+ if (c && R.contains (*c))
+ {
+ DBGPRINT ("CLIP: ellipse with centre", *c)
+ // we set paired and inner points by finding the ellipse
+ // intersection with its axes; this choice let us having a more
+ // accurate RatQuad parametric arc
+ paired_points.resize(4);
+ std::vector<double> rts;
+ double angle = cs.axis_angle();
+ Line axis1 (*c, angle);
+ rts = cs.roots (axis1);
+ if (rts[0] > rts[1]) swap (rts[0], rts[1]);
+ paired_points[0] = axis1.pointAt (rts[0]);
+ paired_points[1] = axis1.pointAt (rts[1]);
+ paired_points[2] = paired_points[1];
+ paired_points[3] = paired_points[0];
+ Line axis2 (*c, angle + M_PI/2);
+ rts = cs.roots (axis2);
+ if (rts[0] > rts[1]) swap (rts[0], rts[1]);
+ inner_points.push_back (axis2.pointAt (rts[0]));
+ inner_points.push_back (axis2.pointAt (rts[1]));
+ }
+ else if (crossing_points.size() == 1)
+ {
+ // so we have a tangent crossing point but the ellipse is outside
+ // the rectangle
+ single_points.push_back (crossing_points[0]);
+ }
+ }
+ else
+ {
+ // in case the conic section intersects any of the four lines passing
+ // through the rectangle edges but it does not cross any rectangle edge
+ // then the conic is all outer of the rectangle
+ if (crossing_points.empty()) return false;
+ // else we need to pair crossing points, and to find an arc inner point
+ // in order to generate a RatQuad object
+ pairing (paired_points, inner_points, crossing_points);
+ }
+
+
+ // we split arcs until the end-point distance is less than a given value,
+ // in this way the RatQuad parametrization is enough accurate
+ std::list<Point> points;
+ std::list<Point>::iterator sp, ip, fp;
+ for (size_t i = 0, j = 0; i < paired_points.size(); i += 2, ++j)
+ {
+ //DBGPRINT ("CLIP: clip: P = ", paired_points[i])
+ //DBGPRINT ("CLIP: clip: M = ", inner_points[j])
+ //DBGPRINT ("CLIP: clip: Q = ", paired_points[i+1])
+
+ // in case inner point and end points are near is better not split
+ // the conic arc further or we could get a degenerate RatQuad object
+ if (are_near (paired_points[i], inner_points[j], 1e-4)
+ && are_near (paired_points[i+1], inner_points[j], 1e-4))
+ {
+ arcs.push_back (cs.toRatQuad (paired_points[i],
+ inner_points[j],
+ paired_points[i+1]));
+ continue;
+ }
+
+ // populate the list
+ points.push_back(paired_points[i]);
+ points.push_back(inner_points[j]);
+ points.push_back(paired_points[i+1]);
+
+ // an initial unconditioned splitting
+ sp = points.begin();
+ ip = sp; ++ip;
+ fp = ip; ++fp;
+ rsplit (points, sp, ip, size_t(1u));
+ rsplit (points, ip, fp, size_t(1u));
+
+ // length conditioned split
+ sp = points.begin();
+ fp = sp; ++fp;
+ while (fp != points.end())
+ {
+ rsplit (points, sp, fp, 100.0);
+ sp = fp;
+ ++fp;
+ }
+
+ sp = points.begin();
+ ip = sp; ++ip;
+ fp = ip; ++fp;
+ //DBGPRINT ("CLIP: points ", j)
+ //DBGPRINT ("CLIP: points.size = ", points.size())
+ while (ip != points.end())
+ {
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.8, 1.0);
+ draw_handle (cr, *sp);
+ draw_handle (cr, *ip);
+ cairo_stroke (cr);
+#endif
+ //std::cerr << "CLIP: arc: [" << *sp << ", " << *ip << ", "
+ // << *fp << "]" << std::endl;
+ arcs.push_back (cs.toRatQuad (*sp, *ip, *fp));
+ sp = fp;
+ ip = sp; ++ip;
+ fp = ip; ++fp;
+ }
+ points.clear();
+ }
+ DBGPRINT ("CLIP: arcs.size() = ", arcs.size())
+ return (arcs.size() != 0);
+} // end method clip
+
+
+} // 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/src/2geom/conicsec.cpp b/src/2geom/conicsec.cpp
new file mode 100644
index 0000000..0865c0e
--- /dev/null
+++ b/src/2geom/conicsec.cpp
@@ -0,0 +1,1640 @@
+/*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com
+ *
+ * Copyright 2009 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/conicsec.h>
+#include <2geom/conic_section_clipper.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+
+// File: convert.h
+#include <utility>
+#include <sstream>
+#include <stdexcept>
+#include <optional>
+
+namespace Geom
+{
+
+LineSegment intersection(Line l, Rect r) {
+ std::optional<LineSegment> seg = l.clip(r);
+ if (seg) {
+ return *seg;
+ } else {
+ return LineSegment(Point(0,0), Point(0,0));
+ }
+}
+
+static double det(Point a, Point b) {
+ return a[0]*b[1] - a[1]*b[0];
+}
+
+template <typename T>
+static T det(T a, T b, T c, T d) {
+ return a*d - b*c;
+}
+
+template <typename T>
+static T det(T M[2][2]) {
+ return M[0][0]*M[1][1] - M[1][0]*M[0][1];
+}
+
+template <typename T>
+static T det3(T M[3][3]) {
+ return ( M[0][0] * det(M[1][1], M[1][2],
+ M[2][1], M[2][2])
+ -M[1][0] * det(M[0][1], M[0][2],
+ M[2][1], M[2][2])
+ +M[2][0] * det(M[0][1], M[0][2],
+ M[1][1], M[1][2]));
+}
+
+static double boxprod(Point a, Point b, Point c) {
+ return det(a,b) - det(a,c) + det(b,c);
+}
+
+class BadConversion : public std::runtime_error {
+public:
+ BadConversion(const std::string& s)
+ : std::runtime_error(s)
+ { }
+};
+
+template <typename T>
+inline std::string stringify(T x)
+{
+ std::ostringstream o;
+ if (!(o << x))
+ throw BadConversion("stringify(T)");
+ return o.str();
+}
+
+ /* A G4 continuous cubic parametric approximation for rational quadratics.
+ See
+ An analysis of cubic approximation schemes for conic sections
+ Michael Floater
+ SINTEF
+
+ This is less accurate overall than some of his other schemes, but
+ produces very smooth joins and is still optimally h^-6
+ convergent.
+ */
+
+double RatQuad::lambda() const {
+ return 2*(6*w*w +1 -std::sqrt(3*w*w+1))/(12*w*w+3);
+}
+
+RatQuad RatQuad::fromPointsTangents(Point P0, Point dP0,
+ Point P,
+ Point P2, Point dP2) {
+ Line Line0 = Line::from_origin_and_vector(P0, dP0);
+ Line Line2 = Line::from_origin_and_vector(P2, dP2);
+ try {
+ OptCrossing oc = intersection(Line0, Line2);
+ if(!oc) // what to do?
+ return RatQuad(Point(), Point(), Point(), 0); // need opt really
+ //assert(0);
+ Point P1 = Line0.pointAt((*oc).ta);
+ double triarea = boxprod(P0, P1, P2);
+// std::cout << "RatQuad::fromPointsTangents: triarea = " << triarea << std::endl;
+ if (triarea == 0)
+ {
+ return RatQuad(P0, 0.5*(P0+P2), P2, 1);
+ }
+ double tau0 = boxprod(P, P1, P2)/triarea;
+ double tau1 = boxprod(P0, P, P2)/triarea;
+ double tau2 = boxprod(P0, P1, P)/triarea;
+ if (tau0 == 0 || tau1 == 0 || tau2 == 0)
+ {
+ return RatQuad(P0, 0.5*(P0+P2), P2, 1);
+ }
+ double w = tau1/(2*std::sqrt(tau0*tau2));
+// std::cout << "RatQuad::fromPointsTangents: tau0 = " << tau0 << std::endl;
+// std::cout << "RatQuad::fromPointsTangents: tau1 = " << tau1 << std::endl;
+// std::cout << "RatQuad::fromPointsTangents: tau2 = " << tau2 << std::endl;
+// std::cout << "RatQuad::fromPointsTangents: w = " << w << std::endl;
+ return RatQuad(P0, P1, P2, w);
+ } catch(Geom::InfiniteSolutions const&) {
+ return RatQuad(P0, 0.5*(P0+P2), P2, 1);
+ }
+ return RatQuad(Point(), Point(), Point(), 0); // need opt really
+}
+
+RatQuad RatQuad::circularArc(Point P0, Point P1, Point P2) {
+ return RatQuad(P0, P1, P2, dot(unit_vector(P0 - P1), unit_vector(P0 - P2)));
+}
+
+
+CubicBezier RatQuad::toCubic() const {
+ return toCubic(lambda());
+}
+
+CubicBezier RatQuad::toCubic(double lamb) const {
+ return CubicBezier(P[0],
+ (1-lamb)*P[0] + lamb*P[1],
+ (1-lamb)*P[2] + lamb*P[1],
+ P[2]);
+}
+
+Point RatQuad::pointAt(double t) const {
+ Bezier xt(P[0][0], P[1][0]*w, P[2][0]);
+ Bezier yt(P[0][1], P[1][1]*w, P[2][1]);
+ double wt = Bezier(1, w, 1).valueAt(t);
+ return Point(xt.valueAt(t)/wt,
+ yt.valueAt(t)/wt);
+}
+
+void RatQuad::split(RatQuad &a, RatQuad &b) const {
+ a.P[0] = P[0];
+ b.P[2] = P[2];
+ a.P[1] = (P[0]+w*P[1])/(1+w);
+ b.P[1] = (w*P[1]+P[2])/(1+w);
+ a.w = b.w = std::sqrt((1+w)/2);
+ a.P[2] = b.P[0] = (0.5*a.P[1]+0.5*b.P[1]);
+}
+
+
+D2<SBasis> RatQuad::hermite() const {
+ SBasis t = Linear(0, 1);
+ SBasis omt = Linear(1, 0);
+
+ D2<SBasis> out(omt*omt*P[0][0]+2*omt*t*P[1][0]*w+t*t*P[2][0],
+ omt*omt*P[0][1]+2*omt*t*P[1][1]*w+t*t*P[2][1]);
+ for(int dim = 0; dim < 2; dim++) {
+ out[dim] = divide(out[dim], (omt*omt+2*omt*t*w+t*t), 2);
+ }
+ return out;
+}
+
+ std::vector<SBasis> RatQuad::homogeneous() const {
+ std::vector<SBasis> res(3, SBasis());
+ Bezier xt(P[0][0], P[1][0]*w, P[2][0]);
+ bezier_to_sbasis(res[0],xt);
+ Bezier yt(P[0][1], P[1][1]*w, P[2][1]);
+ bezier_to_sbasis(res[1],yt);
+ Bezier wt(1, w, 1);
+ bezier_to_sbasis(res[2],wt);
+ return res;
+}
+
+#if 0
+ std::string xAx::categorise() const {
+ double M[3][3] = {{c[0], c[1], c[3]},
+ {c[1], c[2], c[4]},
+ {c[3], c[4], c[5]}};
+ double D = det3(M);
+ if (c[0] == 0 && c[1] == 0 && c[2] == 0)
+ return "line";
+ std::string res = stringify(D);
+ double descr = c[1]*c[1] - c[0]*c[2];
+ if (descr < 0) {
+ if (c[0] == c[2] && c[1] == 0)
+ return res + "circle";
+ return res + "ellipse";
+ } else if (descr == 0) {
+ return res + "parabola";
+ } else if (descr > 0) {
+ if (c[0] + c[2] == 0) {
+ if (D == 0)
+ return res + "two lines";
+ return res + "rectangular hyperbola";
+ }
+ return res + "hyperbola";
+
+ }
+ return "no idea!";
+}
+#endif
+
+
+std::vector<Point> decompose_degenerate(xAx const & C1, xAx const & C2, xAx const & xC0) {
+ std::vector<Point> res;
+ double A[2][2] = {{2*xC0.c[0], xC0.c[1]},
+ {xC0.c[1], 2*xC0.c[2]}};
+//Point B0 = xC0.bottom();
+ double const determ = det(A);
+ //std::cout << determ << "\n";
+ if (fabs(determ) >= 1e-20) { // hopeful, I know
+ Geom::Coord const ideterm = 1.0 / determ;
+
+ double b[2] = {-xC0.c[3], -xC0.c[4]};
+ Point B0((A[1][1]*b[0] -A[0][1]*b[1]),
+ (-A[1][0]*b[0] + A[0][0]*b[1]));
+ B0 *= ideterm;
+ Point n0, n1;
+ // Are these just the eigenvectors of A11?
+ if(xC0.c[0] == xC0.c[2]) {
+ double b = 0.5*xC0.c[1]/xC0.c[0];
+ double c = xC0.c[2]/xC0.c[0];
+ //assert(fabs(b*b-c) > 1e-10);
+ double d = std::sqrt(b*b-c);
+ //assert(fabs(b-d) > 1e-10);
+ n0 = Point(1, b+d);
+ n1 = Point(1, b-d);
+ } else if(fabs(xC0.c[0]) > fabs(xC0.c[2])) {
+ double b = 0.5*xC0.c[1]/xC0.c[0];
+ double c = xC0.c[2]/xC0.c[0];
+ //assert(fabs(b*b-c) > 1e-10);
+ double d = std::sqrt(b*b-c);
+ //assert(fabs(b-d) > 1e-10);
+ n0 = Point(1, b+d);
+ n1 = Point(1, b-d);
+ } else {
+ double b = 0.5*xC0.c[1]/xC0.c[2];
+ double c = xC0.c[0]/xC0.c[2];
+ //assert(fabs(b*b-c) > 1e-10);
+ double d = std::sqrt(b*b-c);
+ //assert(fabs(b-d) > 1e-10);
+ n0 = Point(b+d, 1);
+ n1 = Point(b-d, 1);
+ }
+
+ Line L0 = Line::from_origin_and_vector(B0, rot90(n0));
+ Line L1 = Line::from_origin_and_vector(B0, rot90(n1));
+
+ std::vector<double> rts = C1.roots(L0);
+ for(double rt : rts) {
+ Point P = L0.pointAt(rt);
+ res.push_back(P);
+ }
+ rts = C1.roots(L1);
+ for(double rt : rts) {
+ Point P = L1.pointAt(rt);
+ res.push_back(P);
+ }
+ } else {
+ // single or double line
+ // check for completely zero case (what to do?)
+ assert(xC0.c[0] || xC0.c[1] ||
+ xC0.c[2] || xC0.c[3] ||
+ xC0.c[4] || xC0.c[5]);
+ Point trial_pt(0,0);
+ Point g = xC0.gradient(trial_pt);
+ if(L2sq(g) == 0) {
+ trial_pt[0] += 1;
+ g = xC0.gradient(trial_pt);
+ if(L2sq(g) == 0) {
+ trial_pt[1] += 1;
+ g = xC0.gradient(trial_pt);
+ if(L2sq(g) == 0) {
+ trial_pt[0] += 1;
+ g = xC0.gradient(trial_pt);
+ if(L2sq(g) == 0) {
+ trial_pt = Point(1.5,0.5);
+ g = xC0.gradient(trial_pt);
+ }
+ }
+ }
+ }
+ //std::cout << trial_pt << ", " << g << "\n";
+ /**
+ * At this point we have tried up to 4 points: 0,0, 1,0, 1,1, 2,1, 1.5,1.5
+ *
+ * No degenerate conic can pass through these points, so we can assume
+ * that we've found a perpendicular to the double line.
+ * Proof:
+ * any degenerate must consist of at most 2 lines. 1.5,0.5 is not on any pair of lines
+ * passing through the previous 4 trials.
+ *
+ * alternatively, there may be a way to determine this directly from xC0
+ */
+ assert(L2sq(g) != 0);
+
+ Line Lx = Line::from_origin_and_vector(trial_pt, g); // a line along the gradient
+ std::vector<double> rts = xC0.roots(Lx);
+ for(double rt : rts) {
+ Point P0 = Lx.pointAt(rt);
+ //std::cout << P0 << "\n";
+ Line L = Line::from_origin_and_vector(P0, rot90(g));
+ std::vector<double> cnrts;
+ // It's very likely that at least one of the conics is degenerate, this will hopefully pick the more generate of the two.
+ if(fabs(C1.hessian().det()) > fabs(C2.hessian().det()))
+ cnrts = C1.roots(L);
+ else
+ cnrts = C2.roots(L);
+ for(double cnrt : cnrts) {
+ Point P = L.pointAt(cnrt);
+ res.push_back(P);
+ }
+ }
+ }
+ return res;
+}
+
+double xAx_descr(xAx const & C) {
+ double mC[3][3] = {{C.c[0], (C.c[1])/2, (C.c[3])/2},
+ {(C.c[1])/2, C.c[2], (C.c[4])/2},
+ {(C.c[3])/2, (C.c[4])/2, C.c[5]}};
+
+ return det3(mC);
+}
+
+
+std::vector<Point> intersect(xAx const & C1, xAx const & C2) {
+ // You know, if either of the inputs are degenerate we should use them first!
+ if(xAx_descr(C1) == 0) {
+ return decompose_degenerate(C1, C2, C1);
+ }
+ if(xAx_descr(C2) == 0) {
+ return decompose_degenerate(C1, C2, C2);
+ }
+ std::vector<Point> res;
+ SBasis T(Linear(-1,1));
+ SBasis S(Linear(1,1));
+ SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
+ {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
+ {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
+
+ SBasis D = det3(C);
+ std::vector<double> rts = Geom::roots(D);
+ if(rts.empty()) {
+ T = Linear(1,1);
+ S = Linear(-1,1);
+ SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
+ {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
+ {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
+
+ D = det3(C);
+ rts = Geom::roots(D);
+ }
+ // at this point we have a T and S and perhaps some roots that represent our degenerate conic
+ // Let's just pick one randomly (can we do better?)
+ //for(unsigned i = 0; i < rts.size(); i++) {
+ if(!rts.empty()) {
+ unsigned i = 0;
+ double t = T.valueAt(rts[i]);
+ double s = S.valueAt(rts[i]);
+ xAx xC0 = C1*t + C2*s;
+ //::draw(cr, xC0, screen_rect); // degen
+
+ return decompose_degenerate(C1, C2, xC0);
+
+
+ } else {
+ std::cout << "What?" << std::endl;
+ ;//std::cout << D << "\n";
+ }
+ return res;
+}
+
+
+xAx xAx::fromPoint(Point p) {
+ return xAx(1., 0, 1., -2*p[0], -2*p[1], dot(p,p));
+}
+
+xAx xAx::fromDistPoint(Point /*p*/, double /*d*/) {
+ return xAx();//1., 0, 1., -2*(1+d)*p[0], -2*(1+d)*p[1], dot(p,p)+d*d);
+}
+
+xAx xAx::fromLine(Point n, double d) {
+ return xAx(n[0]*n[0], 2*n[0]*n[1], n[1]*n[1], 2*d*n[0], 2*d*n[1], d*d);
+}
+
+xAx xAx::fromLine(Line l) {
+ double dist;
+ Point norm = l.normalAndDist(dist);
+
+ return fromLine(norm, dist);
+}
+
+xAx xAx::fromPoints(std::vector<Geom::Point> const &pt) {
+ Geom::NL::Vector V(pt.size(), -1.0);
+ Geom::NL::Matrix M(pt.size(), 5);
+ for(unsigned i = 0; i < pt.size(); i++) {
+ Geom::Point P = pt[i];
+ Geom::NL::VectorView vv = M.row_view(i);
+ vv[0] = P[0]*P[0];
+ vv[1] = P[0]*P[1];
+ vv[2] = P[1]*P[1];
+ vv[3] = P[0];
+ vv[4] = P[1];
+ }
+
+ Geom::NL::LinearSystem ls(M, V);
+
+ Geom::NL::Vector x = ls.SV_solve();
+ return Geom::xAx(x[0], x[1], x[2], x[3], x[4], 1);
+
+}
+
+
+
+double xAx::valueAt(Point P) const {
+ return evaluate_at(P[0], P[1]);
+}
+
+xAx xAx::scale(double sx, double sy) const {
+ return xAx(c[0]*sx*sx, c[1]*sx*sy, c[2]*sy*sy,
+ c[3]*sx, c[4]*sy, c[5]);
+}
+
+Point xAx::gradient(Point p) const{
+ double x = p[0];
+ double y = p[1];
+ return Point(2*c[0]*x + c[1]*y + c[3],
+ c[1]*x + 2*c[2]*y + c[4]);
+}
+
+xAx xAx::operator-(xAx const &b) const {
+ xAx res;
+ for(int i = 0; i < 6; i++) {
+ res.c[i] = c[i] - b.c[i];
+ }
+ return res;
+}
+xAx xAx::operator+(xAx const &b) const {
+ xAx res;
+ for(int i = 0; i < 6; i++) {
+ res.c[i] = c[i] + b.c[i];
+ }
+ return res;
+}
+xAx xAx::operator+(double const &b) const {
+ xAx res;
+ for(int i = 0; i < 5; i++) {
+ res.c[i] = c[i];
+ }
+ res.c[5] = c[5] + b;
+ return res;
+}
+
+xAx xAx::operator*(double const &b) const {
+ xAx res;
+ for(int i = 0; i < 6; i++) {
+ res.c[i] = c[i] * b;
+ }
+ return res;
+}
+
+ std::vector<Point> xAx::crossings(Rect r) const {
+ std::vector<Point> res;
+ for(int ei = 0; ei < 4; ei++) {
+ Geom::LineSegment ls(r.corner(ei), r.corner(ei+1));
+ D2<SBasis> lssb = ls.toSBasis();
+ SBasis edge_curve = evaluate_at(lssb[0], lssb[1]);
+ std::vector<double> rts = Geom::roots(edge_curve);
+ for(double rt : rts) {
+ res.push_back(lssb.valueAt(rt));
+ }
+ }
+ return res;
+}
+
+ std::optional<RatQuad> xAx::toCurve(Rect const & bnd) const {
+ std::vector<Point> crs = crossings(bnd);
+ if(crs.size() == 1) {
+ Point A = crs[0];
+ Point dA = rot90(gradient(A));
+ if(L2sq(dA) <= 1e-10) { // perhaps a single point?
+ return std::optional<RatQuad> ();
+ }
+ LineSegment ls = intersection(Line::from_origin_and_vector(A, dA), bnd);
+ return RatQuad::fromPointsTangents(A, dA, ls.pointAt(0.5), ls[1], dA);
+ }
+ else if(crs.size() >= 2 && crs.size() < 4) {
+ Point A = crs[0];
+ Point C = crs[1];
+ if(crs.size() == 3) {
+ if(distance(A, crs[2]) > distance(A, C))
+ C = crs[2];
+ else if(distance(C, crs[2]) > distance(A, C))
+ A = crs[2];
+ }
+ Line bisector = make_bisector_line(LineSegment(A, C));
+ std::vector<double> bisect_rts = this->roots(bisector);
+ if(!bisect_rts.empty()) {
+ int besti = -1;
+ for(unsigned i =0; i < bisect_rts.size(); i++) {
+ Point p = bisector.pointAt(bisect_rts[i]);
+ if(bnd.contains(p)) {
+ besti = i;
+ }
+ }
+ if(besti >= 0) {
+ Point B = bisector.pointAt(bisect_rts[besti]);
+
+ Point dA = gradient(A);
+ Point dC = gradient(C);
+ if(L2sq(dA) <= 1e-10 || L2sq(dC) <= 1e-10) {
+ return RatQuad::fromPointsTangents(A, C-A, B, C, A-C);
+ }
+
+ RatQuad rq = RatQuad::fromPointsTangents(A, rot90(dA),
+ B, C, rot90(dC));
+ return rq;
+ //std::vector<SBasis> hrq = rq.homogeneous();
+ /*SBasis vertex_poly = evaluate_at(hrq[0], hrq[1], hrq[2]);
+ std::vector<double> rts = roots(vertex_poly);
+ for(unsigned i = 0; i < rts.size(); i++) {
+ //draw_circ(cr, Point(rq.pointAt(rts[i])));
+ }*/
+ }
+ }
+ }
+ return std::optional<RatQuad>();
+}
+
+ std::vector<double> xAx::roots(Point d, Point o) const {
+ // Find the roots on line l
+ // form the quadratic Q(t) = 0 by composing l with xAx
+ double q2 = c[0]*d[0]*d[0] + c[1]*d[0]*d[1] + c[2]*d[1]*d[1];
+ double q1 = (2*c[0]*d[0]*o[0] +
+ c[1]*(d[0]*o[1]+d[1]*o[0]) +
+ 2*c[2]*d[1]*o[1] +
+ c[3]*d[0] + c[4]*d[1]);
+ double q0 = c[0]*o[0]*o[0] + c[1]*o[0]*o[1] + c[2]*o[1]*o[1] + c[3]*o[0] + c[4]*o[1] + c[5];
+ std::vector<double> r;
+ if(q2 == 0) {
+ if(q1 == 0) {
+ return r;
+ }
+ r.push_back(-q0/q1);
+ } else {
+ double desc = q1*q1 - 4*q2*q0;
+ /*std::cout << q2 << ", "
+ << q1 << ", "
+ << q0 << "; "
+ << desc << "\n";*/
+ if (desc < 0)
+ return r;
+ else if (desc == 0)
+ r.push_back(-q1/(2*q2));
+ else {
+ desc = std::sqrt(desc);
+ double t;
+ if (q1 == 0)
+ {
+ t = -0.5 * desc;
+ }
+ else
+ {
+ t = -0.5 * (q1 + sgn(q1) * desc);
+ }
+ r.push_back(t/q2);
+ r.push_back(q0/t);
+ }
+ }
+ return r;
+}
+
+std::vector<double> xAx::roots(Line const &l) const {
+ return roots(l.versor(), l.origin());
+}
+
+Interval xAx::quad_ex(double a, double b, double c, Interval ivl) {
+ double cx = -b*0.5/a;
+ Interval bnds((a*ivl.min()+b)*ivl.min()+c, (a*ivl.max()+b)*ivl.max()+c);
+ if(ivl.contains(cx))
+ bnds.expandTo((a*cx+b)*cx+c);
+ return bnds;
+}
+
+Geom::Affine xAx::hessian() const {
+ Geom::Affine m(2*c[0], c[1],
+ c[1], 2*c[2],
+ 0, 0);
+ return m;
+}
+
+
+std::optional<Point> solve(double A[2][2], double b[2]) {
+ double const determ = det(A);
+ if (determ != 0.0) { // hopeful, I know
+ Geom::Coord const ideterm = 1.0 / determ;
+
+ return Point ((A[1][1]*b[0] -A[0][1]*b[1]),
+ (-A[1][0]*b[0] + A[0][0]*b[1]))* ideterm;
+ } else {
+ return std::optional<Point>();
+ }
+}
+
+std::optional<Point> xAx::bottom() const {
+ double A[2][2] = {{2*c[0], c[1]},
+ {c[1], 2*c[2]}};
+ double b[2] = {-c[3], -c[4]};
+ return solve(A, b);
+ //return Point(-c[3], -c[4])*hessian().inverse();
+}
+
+Interval xAx::extrema(Rect r) const {
+ if (c[0] == 0 && c[1] == 0 && c[2] == 0) {
+ Interval ext(valueAt(r.corner(0)));
+ for(int i = 1; i < 4; i++)
+ ext |= Interval(valueAt(r.corner(i)));
+ return ext;
+ }
+ double k = r[X].min();
+ Interval ext = quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]);
+ k = r[X].max();
+ ext |= quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]);
+ k = r[Y].min();
+ ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]);
+ k = r[Y].max();
+ ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]);
+ std::optional<Point> B0 = bottom();
+ if (B0 && r.contains(*B0))
+ ext.expandTo(0);
+ return ext;
+}
+
+
+
+
+
+
+
+
+
+/*
+ * helper functions
+ */
+
+bool at_infinity (Point const& p)
+{
+ if (p[X] == infinity() || p[X] == -infinity()
+ || p[Y] == infinity() || p[Y] == -infinity())
+ {
+ return true;
+ }
+ return false;
+}
+
+inline
+double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3)
+{
+ return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2));
+}
+
+
+
+/*
+ * Define a conic section by computing the one that fits better with
+ * N points.
+ *
+ * points: points to fit
+ *
+ * precondition: there must be at least 5 non-overlapping points
+ */
+void xAx::set(std::vector<Point> const& points)
+{
+ size_t sz = points.size();
+ if (sz < 5)
+ {
+ THROW_RANGEERROR("fitting error: too few points passed");
+ }
+ NL::LFMConicSection model;
+ NL::least_squeares_fitter<NL::LFMConicSection> fitter(model, sz);
+
+ for (size_t i = 0; i < sz; ++i)
+ {
+ fitter.append(points[i]);
+ }
+ fitter.update();
+
+ NL::Vector z(sz, 0.0);
+ model.instance(*this, fitter.result(z));
+}
+
+/*
+ * Define a section conic by providing the coordinates of one of its vertex,
+ * the major axis inclination angle and the coordinates of its foci
+ * with respect to the unidimensional system defined by the major axis with
+ * origin set at the provided vertex.
+ *
+ * _vertex : section conic vertex V
+ * _angle : section conic major axis angle
+ * _dist1: +/-distance btw V and nearest focus
+ * _dist2: +/-distance btw V and farest focus
+ *
+ * prerequisite: _dist1 <= _dist2
+ */
+void xAx::set (const Point& _vertex, double _angle, double _dist1, double _dist2)
+{
+ using std::swap;
+
+ if (_dist2 == infinity() || _dist2 == -infinity()) // parabola
+ {
+ if (_dist1 == infinity()) // degenerate to a line
+ {
+ Line l(_vertex, _angle);
+ std::vector<double> lcoeff = l.coefficients();
+ coeff(3) = lcoeff[0];
+ coeff(4) = lcoeff[1];
+ coeff(5) = lcoeff[2];
+ return;
+ }
+
+ // y^2 - 4px == 0
+ double cD = -4 * _dist1;
+
+ double cosa = std::cos (_angle);
+ double sina = std::sin (_angle);
+ double cca = cosa * cosa;
+ double ssa = sina * sina;
+ double csa = cosa * sina;
+
+ coeff(0) = ssa;
+ coeff(1) = -2 * csa;
+ coeff(2) = cca;
+ coeff(3) = cD * cosa;
+ coeff(4) = cD * sina;
+
+ double VxVx = _vertex[X] * _vertex[X];
+ double VxVy = _vertex[X] * _vertex[Y];
+ double VyVy = _vertex[Y] * _vertex[Y];
+
+ coeff(5) = coeff(0) * VxVx + coeff(1) * VxVy + coeff(2) * VyVy
+ - coeff(3) * _vertex[X] - coeff(4) * _vertex[Y];
+ coeff(3) -= (2 * coeff(0) * _vertex[X] + coeff(1) * _vertex[Y]);
+ coeff(4) -= (2 * coeff(2) * _vertex[Y] + coeff(1) * _vertex[X]);
+
+ return;
+ }
+
+ if (std::fabs(_dist1) > std::fabs(_dist2))
+ {
+ swap (_dist1, _dist2);
+ }
+ if (_dist1 < 0)
+ {
+ _angle -= M_PI;
+ _dist1 = -_dist1;
+ _dist2 = -_dist2;
+ }
+
+ // ellipse and hyperbola
+ double lin_ecc = (_dist2 - _dist1) / 2;
+ double rx = (_dist2 + _dist1) / 2;
+
+ double cA = rx * rx - lin_ecc * lin_ecc;
+ double cC = rx * rx;
+ double cF = - cA * cC;
+// std::cout << "cA: " << cA << std::endl;
+// std::cout << "cC: " << cC << std::endl;
+// std::cout << "cF: " << cF << std::endl;
+
+ double cosa = std::cos (_angle);
+ double sina = std::sin (_angle);
+ double cca = cosa * cosa;
+ double ssa = sina * sina;
+ double csa = cosa * sina;
+
+ coeff(0) = cca * cA + ssa * cC;
+ coeff(2) = ssa * cA + cca * cC;
+ coeff(1) = 2 * csa * (cA - cC);
+
+ Point C (rx * cosa + _vertex[X], rx * sina + _vertex[Y]);
+ double CxCx = C[X] * C[X];
+ double CxCy = C[X] * C[Y];
+ double CyCy = C[Y] * C[Y];
+
+ coeff(3) = -2 * coeff(0) * C[X] - coeff(1) * C[Y];
+ coeff(4) = -2 * coeff(2) * C[Y] - coeff(1) * C[X];
+ coeff(5) = cF + coeff(0) * CxCx + coeff(1) * CxCy + coeff(2) * CyCy;
+}
+
+/*
+ * Define a conic section by providing one of its vertex and its foci.
+ *
+ * _vertex: section conic vertex
+ * _focus1: section conic focus
+ * _focus2: section conic focus
+ */
+void xAx::set (const Point& _vertex, const Point& _focus1, const Point& _focus2)
+{
+ if (at_infinity(_vertex))
+ {
+ THROW_RANGEERROR("case not handled: vertex at infinity");
+ }
+ if (at_infinity(_focus2))
+ {
+ if (at_infinity(_focus1))
+ {
+ THROW_RANGEERROR("case not handled: both focus at infinity");
+ }
+ Point VF = _focus1 - _vertex;
+ double dist1 = L2(VF);
+ double angle = atan2(VF);
+ set(_vertex, angle, dist1, infinity());
+ return;
+ }
+ else if (at_infinity(_focus1))
+ {
+ Point VF = _focus2 - _vertex;
+ double dist1 = L2(VF);
+ double angle = atan2(VF);
+ set(_vertex, angle, dist1, infinity());
+ return;
+ }
+ assert (are_collinear (_vertex, _focus1, _focus2));
+ if (!are_near(_vertex, _focus1))
+ {
+ Point VF = _focus1 - _vertex;
+ Line axis(_vertex, _focus1);
+ double angle = atan2(VF);
+ double dist1 = L2(VF);
+ double dist2 = distance (_vertex, _focus2);
+ double t = axis.timeAt(_focus2);
+ if (t < 0) dist2 = -dist2;
+// std::cout << "t = " << t << std::endl;
+// std::cout << "dist2 = " << dist2 << std::endl;
+ set (_vertex, angle, dist1, dist2);
+ }
+ else if (!are_near(_vertex, _focus2))
+ {
+ Point VF = _focus2 - _vertex;
+ double angle = atan2(VF);
+ double dist1 = 0;
+ double dist2 = L2(VF);
+ set (_vertex, angle, dist1, dist2);
+ }
+ else
+ {
+ coeff(0) = coeff(2) = 1;
+ coeff(1) = coeff(3) = coeff(4) = coeff(5) = 0;
+ }
+}
+
+/*
+ * Define a conic section by passing a focus, the related directrix,
+ * and the eccentricity (e)
+ * (e < 1 -> ellipse; e = 1 -> parabola; e > 1 -> hyperbola)
+ *
+ * _focus: a focus of the conic section
+ * _directrix: the directrix related to the given focus
+ * _eccentricity: the eccentricity parameter of the conic section
+ */
+void xAx::set (const Point & _focus, const Line & _directrix, double _eccentricity)
+{
+ Point O = _directrix.pointAt (_directrix.timeAtProjection (_focus));
+ //std::cout << "O = " << O << std::endl;
+ Point OF = _focus - O;
+ double p = L2(OF);
+
+ coeff(0) = 1 - _eccentricity * _eccentricity;
+ coeff(1) = 0;
+ coeff(2) = 1;
+ coeff(3) = -2 * p;
+ coeff(4) = 0;
+ coeff(5) = p * p;
+
+ double angle = atan2 (OF);
+
+ (*this) = rotate (angle);
+ //std::cout << "O = " << O << std::endl;
+ (*this) = translate (O);
+}
+
+/*
+ * Made up a degenerate conic section as a pair of lines
+ *
+ * l1, l2: lines that made up the conic section
+ */
+void xAx::set (const Line& l1, const Line& l2)
+{
+ std::vector<double> cl1 = l1.coefficients();
+ std::vector<double> cl2 = l2.coefficients();
+
+ coeff(0) = cl1[0] * cl2[0];
+ coeff(2) = cl1[1] * cl2[1];
+ coeff(5) = cl1[2] * cl2[2];
+ coeff(1) = cl1[0] * cl2[1] + cl1[1] * cl2[0];
+ coeff(3) = cl1[0] * cl2[2] + cl1[2] * cl2[0];
+ coeff(4) = cl1[1] * cl2[2] + cl1[2] * cl2[1];
+}
+
+
+
+/*
+ * Return the section conic kind
+ */
+xAx::kind_t xAx::kind () const
+{
+
+ xAx conic(*this);
+ NL::SymmetricMatrix<3> C = conic.get_matrix();
+ NL::ConstSymmetricMatrixView<2> A = C.main_minor_const_view();
+
+ double t1 = trace(A);
+ double t2 = det(A);
+ //double T3 = det(C);
+ int st1 = trace_sgn(A);
+ int st2 = det_sgn(A);
+ int sT3 = det_sgn(C);
+
+ //std::cout << "T3 = " << T3 << std::endl;
+ //std::cout << "sT3 = " << sT3 << std::endl;
+ //std::cout << "t2 = " << t2 << std::endl;
+ //std::cout << "t1 = " << t1 << std::endl;
+ //std::cout << "st2 = " << st2 << std::endl;
+
+ if (sT3 != 0)
+ {
+ if (st2 == 0)
+ {
+ return PARABOLA;
+ }
+ else if (st2 == 1)
+ {
+
+ if (sT3 * st1 < 0)
+ {
+ NL::SymmetricMatrix<2> discr;
+ discr(0,0) = 4; discr(1,1) = t2; discr(1,0) = t1;
+ int discr_sgn = - det_sgn (discr);
+ //std::cout << "t1 * t1 - 4 * t2 = "
+ // << (t1 * t1 - 4 * t2) << std::endl;
+ //std::cout << "discr_sgn = " << discr_sgn << std::endl;
+ if (discr_sgn == 0)
+ {
+ return CIRCLE;
+ }
+ else
+ {
+ return REAL_ELLIPSE;
+ }
+ }
+ else // sT3 * st1 > 0
+ {
+ return IMAGINARY_ELLIPSE;
+ }
+ }
+ else // t2 < 0
+ {
+ if (st1 == 0)
+ {
+ return RECTANGULAR_HYPERBOLA;
+ }
+ else
+ {
+ return HYPERBOLA;
+ }
+ }
+ }
+ else // T3 == 0
+ {
+ if (st2 == 0)
+ {
+ //double T2 = NL::trace<2>(C);
+ int sT2 = NL::trace_sgn<2>(C);
+ //std::cout << "T2 = " << T2 << std::endl;
+ //std::cout << "sT2 = " << sT2 << std::endl;
+
+ if (sT2 == 0)
+ {
+ return DOUBLE_LINE;
+ }
+ if (sT2 == -1)
+ {
+ return TWO_REAL_PARALLEL_LINES;
+ }
+ else // T2 > 0
+ {
+ return TWO_IMAGINARY_PARALLEL_LINES;
+ }
+ }
+ else if (st2 == -1)
+ {
+ return TWO_REAL_CROSSING_LINES;
+ }
+ else // t2 > 0
+ {
+ return TWO_IMAGINARY_CROSSING_LINES;
+ }
+ }
+ return UNKNOWN;
+}
+
+/*
+ * Return a string representing the conic section kind
+ */
+std::string xAx::categorise() const
+{
+ kind_t KIND = kind();
+
+ switch (KIND)
+ {
+ case PARABOLA :
+ return "parabola";
+ case CIRCLE :
+ return "circle";
+ case REAL_ELLIPSE :
+ return "real ellispe";
+ case IMAGINARY_ELLIPSE :
+ return "imaginary ellispe";
+ case RECTANGULAR_HYPERBOLA :
+ return "rectangular hyperbola";
+ case HYPERBOLA :
+ return "hyperbola";
+ case DOUBLE_LINE :
+ return "double line";
+ case TWO_REAL_PARALLEL_LINES :
+ return "two real parallel lines";
+ case TWO_IMAGINARY_PARALLEL_LINES :
+ return "two imaginary parallel lines";
+ case TWO_REAL_CROSSING_LINES :
+ return "two real crossing lines";
+ case TWO_IMAGINARY_CROSSING_LINES :
+ return "two imaginary crossing lines";
+ default :
+ return "unknown";
+ }
+}
+
+/*
+ * Compute the solutions of the conic section algebraic equation with respect to
+ * one coordinate after substituting to the other coordinate the passed value
+ *
+ * sol: the computed solutions
+ * v: the provided value
+ * d: the index of the coordinate the passed value have to be substituted to
+ */
+void xAx::roots (std::vector<double>& sol, Coord v, Dim2 d) const
+{
+ sol.clear();
+ if (d < 0 || d > Y)
+ {
+ THROW_RANGEERROR("dimension parameter out of range");
+ }
+
+ // p*t^2 + q*t + r = 0;
+ double p, q, r;
+
+ if (d == X)
+ {
+ p = coeff(2);
+ q = coeff(4) + coeff(1) * v;
+ r = coeff(5) + (coeff(0) * v + coeff(3)) * v;
+ }
+ else
+ {
+ p = coeff(0);
+ q = coeff(3) + coeff(1) * v;
+ r = coeff(5) + (coeff(2) * v + coeff(4)) * v;
+ }
+
+ if (p == 0)
+ {
+ if (q == 0) return;
+ double t = -r/q;
+ sol.push_back(t);
+ return;
+ }
+
+ if (q == 0)
+ {
+ if ((p > 0 && r > 0) || (p < 0 && r < 0)) return;
+ double t = -r / p;
+ t = std::sqrt (t);
+ sol.push_back(-t);
+ sol.push_back(t);
+ return;
+ }
+
+ if (r == 0)
+ {
+ double t = -q/p;
+ sol.push_back(0);
+ sol.push_back(t);
+ return;
+ }
+
+
+ //std::cout << "p = " << p << ", q = " << q << ", r = " << r << std::endl;
+ double delta = q * q - 4 * p * r;
+ if (delta < 0) return;
+ if (delta == 0)
+ {
+ double t = -q / (2 * p);
+ sol.push_back(t);
+ return;
+ }
+ // else
+ double srd = std::sqrt(delta);
+ double t = - (q + sgn(q) * srd) / 2;
+ sol.push_back (t/p);
+ sol.push_back (r/t);
+
+}
+
+/*
+ * Return the inclination angle of the major axis of the conic section
+ */
+double xAx::axis_angle() const
+{
+ if (coeff(0) == 0 && coeff(1) == 0 && coeff(2) == 0)
+ {
+ Line l (coeff(3), coeff(4), coeff(5));
+ return l.angle();
+ }
+ if (coeff(1) == 0 && (coeff(0) == coeff(2))) return 0;
+
+ double angle;
+
+ int sgn_discr = det_sgn (get_matrix().main_minor_const_view());
+ if (sgn_discr == 0)
+ {
+ //std::cout << "rotation_angle: sgn_discr = "
+ // << sgn_discr << std::endl;
+ angle = std::atan2 (-coeff(1), 2 * coeff(2));
+ if (angle < 0) angle += 2*M_PI;
+ if (angle >= M_PI) angle -= M_PI;
+
+ }
+ else
+ {
+ angle = std::atan2 (coeff(1), coeff(0) - coeff(2));
+ if (angle < 0) angle += 2*M_PI;
+ angle -= M_PI;
+ if (angle < 0) angle += 2*M_PI;
+ angle /= 2;
+ if (angle >= M_PI) angle -= M_PI;
+ }
+ //std::cout << "rotation_angle : angle = " << angle << std::endl;
+ return angle;
+}
+
+/*
+ * Translate the conic section by the given vector offset
+ *
+ * _offset: represent the vector offset
+ */
+xAx xAx::translate (const Point & _offset) const
+{
+ double B = coeff(1) / 2;
+ double D = coeff(3) / 2;
+ double E = coeff(4) / 2;
+
+ Point T = - _offset;
+
+ xAx cs;
+ cs.coeff(0) = coeff(0);
+ cs.coeff(1) = coeff(1);
+ cs.coeff(2) = coeff(2);
+
+ Point DE;
+ DE[0] = coeff(0) * T[0] + B * T[1];
+ DE[1] = B * T[0] + coeff(2) * T[1];
+
+ cs.coeff(3) = (DE[0] + D) * 2;
+ cs.coeff(4) = (DE[1] + E) * 2;
+
+ cs.coeff(5) = dot (T, DE) + 2 * (T[0] * D + T[1] * E) + coeff(5);
+
+ return cs;
+}
+
+
+/*
+ * Rotate the conic section by the given angle wrt the point (0,0)
+ *
+ * angle: represent the rotation angle
+ */
+xAx xAx::rotate (double angle) const
+{
+ double c = std::cos(-angle);
+ double s = std::sin(-angle);
+ double cc = c * c;
+ double ss = s * s;
+ double cs = c * s;
+
+ xAx result;
+ result.coeff(5) = coeff(5);
+
+ // quadratic terms
+ double Bcs = coeff(1) * cs;
+
+ result.coeff(0) = coeff(0) * cc + Bcs + coeff(2) * ss;
+ result.coeff(2) = coeff(0) * ss - Bcs + coeff(2) * cc;
+ result.coeff(1) = coeff(1) * (cc - ss) + 2 * (coeff(2) - coeff(0)) * cs;
+
+ // linear terms
+ result.coeff(3) = coeff(3) * c + coeff(4) * s;
+ result.coeff(4) = coeff(4) * c - coeff(3) * s;
+
+ return result;
+}
+
+
+/*
+ * Decompose a degenerate conic in two lines the conic section is made by.
+ * Return true if the decomposition is successful, else if it fails.
+ *
+ * l1, l2: out parameters where the decomposed conic section is returned
+ */
+bool xAx::decompose (Line& l1, Line& l2) const
+{
+ NL::SymmetricMatrix<3> C = get_matrix();
+ if (!is_quadratic() || !isDegenerate())
+ {
+ return false;
+ }
+ NL::Matrix M(C);
+ NL::SymmetricMatrix<3> D = -adj(C);
+
+ if (!D.is_zero()) // D == 0 <=> rank(C) < 2
+ {
+
+ //if (D.get<0,0>() < 0 || D.get<1,1>() < 0 || D.get<2,2>() < 0)
+ //{
+ //std::cout << "C: \n" << C << std::endl;
+ //std::cout << "D: \n" << D << std::endl;
+
+ /*
+ * This case should be impossible because any diagonal element
+ * of D is a square, but due to non exact aritmethic computation
+ * it can actually happen; however the algorithm seems to work
+ * correctly even if some diagonal term is negative, the only
+ * difference is that we should compute the absolute value of
+ * diagonal elements. So until we elaborate a better degenerate
+ * test it's better not rising exception when we have a negative
+ * diagonal element.
+ */
+ //}
+
+ NL::Vector d(3);
+ d[0] = std::fabs (D.get<0,0>());
+ d[1] = std::fabs (D.get<1,1>());
+ d[2] = std::fabs (D.get<2,2>());
+
+ size_t idx = d.max_index();
+ if (d[idx] == 0)
+ {
+ THROW_LOGICALERROR ("xAx::decompose: "
+ "rank 2 but adjoint with null diagonal");
+ }
+ d[0] = D(idx,0); d[1] = D(idx,1); d[2] = D(idx,2);
+ d.scale (1 / std::sqrt (std::fabs (D(idx,idx))));
+ M(1,2) += d[0]; M(2,1) -= d[0];
+ M(0,2) -= d[1]; M(2,0) += d[1];
+ M(0,1) += d[2]; M(1,0) -= d[2];
+
+ //std::cout << "C: \n" << C << std::endl;
+ //std::cout << "D: \n" << D << std::endl;
+ //std::cout << "d = " << d << std::endl;
+ //std::cout << "M = " << M << std::endl;
+ }
+
+ std::pair<size_t, size_t> max_ij = M.max_index();
+ std::pair<size_t, size_t> min_ij = M.min_index();
+ double abs_max = std::fabs (M(max_ij.first, max_ij.second));
+ double abs_min = std::fabs (M(min_ij.first, min_ij.second));
+ size_t i_max, j_max;
+ if (abs_max > abs_min)
+ {
+ i_max = max_ij.first;
+ j_max = max_ij.second;
+ }
+ else
+ {
+ i_max = min_ij.first;
+ j_max = min_ij.second;
+ }
+ l1.setCoefficients (M(i_max,0), M(i_max,1), M(i_max,2));
+ l2.setCoefficients (M(0, j_max), M(1,j_max), M(2,j_max));
+
+ return true;
+}
+
+std::array<Line, 2> xAx::decompose_df(Coord epsilon) const
+{
+ // For the classification of degenerate conics, see https://mathworld.wolfram.com/QuadraticCurve.html
+ using std::sqrt, std::abs;
+
+ // Create 2 degenerate lines
+ auto const origin = Point(0, 0);
+ std::array<Line, 2> result = {Line(origin, origin), Line(origin, origin)};
+
+ double A = c[0];
+ double B = c[1];
+ double C = c[2];
+ double D = c[3];
+ double E = c[4];
+ double F = c[5];
+ Coord discriminant = sqr(B) - 4 * A * C;
+ if (discriminant < -epsilon) {
+ return result;
+ }
+
+ bool single_line = false; // In the generic case, there will be 2 lines.
+ bool parallel_lines = false;
+ if (discriminant < epsilon) {
+ discriminant = 0;
+ parallel_lines = true;
+ // Check the secondary discriminant
+ Coord const secondary = sqr(D) + sqr(E) - 4 * F * (A + C);
+ if (secondary < -epsilon) {
+ return result;
+ }
+ single_line = (secondary < epsilon);
+ }
+
+ if (abs(A) > epsilon || abs(C) > epsilon) {
+ // This is the typical case: either x² or y² come with a nonzero coefficient.
+ // To guard against numerical errors, we check which of the coefficients A, C has larger absolute value.
+
+ bool const swap_xy = abs(C) > abs(A);
+ if (swap_xy) {
+ std::swap(A, C);
+ std::swap(D, E);
+ }
+
+ // From now on, we may assume that A is "reasonably large".
+ if (parallel_lines) {
+ if (single_line) {
+ // Special case: a single line.
+ std::array<double, 3> coeffs = {sqrt(abs(A)), sqrt(abs(C)), sqrt(abs(F))};
+ if (swap_xy) {
+ std::swap(coeffs[0], coeffs[1]);
+ }
+ rescale_homogenous(coeffs);
+ result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
+ return result;
+ }
+
+ // Two parallel lines.
+ Coord const quotient_discriminant = sqr(D) - 4 * A * F;
+ if (quotient_discriminant < 0) {
+ return result;
+ }
+ Coord const sqrt_disc = sqrt(quotient_discriminant);
+ double const c1 = 0.5 * (D - sqrt_disc);
+ double const c2 = c1 + sqrt_disc;
+ std::array<double, 3> coeffs = {A, 0.5 * B, c1};
+ if (swap_xy) {
+ std::swap(coeffs[0], coeffs[1]);
+ }
+ rescale_homogenous(coeffs);
+ result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
+
+ coeffs = {A, 0.5 * B, c2};
+ if (swap_xy) {
+ std::swap(coeffs[0], coeffs[1]);
+ }
+ rescale_homogenous(coeffs);
+ result[1].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
+ return result;
+ }
+
+ // Now for the typical case of 2 non-parallel lines.
+
+ // We know that A is further away from 0 than C is.
+ // The mathematical derivation of the solution is as follows:
+ // let Δ = B² - 4AC (the discriminant); we know Δ > 0.
+ // Write δ = sqrt(Δ); we know that this is also positive.
+ // Then the product AΔ is nonzero, so the equation
+ // Ax² + Bxy + Cy² + Dx + Ey + F = 0
+ // is equivalent to
+ // AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) = 0.
+ // Consider the two factors
+ // L_1 = Aδx + 0.5 (Bδ-Δ)y + EA - 0.5 D(B-δ)
+ // L_2 = Aδx + 0.5 (Bδ+Δ)y - EA + 0.5 D(B+δ)
+ // With a bit of algebra, you can show that L_1 * L_2 expands
+ // to AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) (in order to get the
+ // correct value of F, you have to use the fact that the conic
+ // is degenerate). Therefore, the factors L_1 and L_2 are in
+ // fact equations of the two lines to be found.
+ Coord const delta = sqrt(discriminant);
+ std::array<double, 3> coeffs1 = {A * delta, 0.5 * (B * delta - discriminant), E * A - 0.5 * D * (B - delta)};
+ std::array<double, 3> coeffs2 = {coeffs1[0], coeffs1[1] + discriminant, D * delta - coeffs1[2]};
+ if (swap_xy) { // We must unswap the coefficients of x and y
+ std::swap(coeffs1[0], coeffs1[1]);
+ std::swap(coeffs2[0], coeffs2[1]);
+ }
+
+ unsigned index = 0;
+ if (coeffs1[0] != 0 || coeffs1[1] != 0) {
+ rescale_homogenous(coeffs1);
+ result[index++].setCoefficients(coeffs1[0], coeffs1[1], coeffs1[2]);
+ }
+ if (coeffs2[0] != 0 || coeffs2[1] != 0) {
+ rescale_homogenous(coeffs2);
+ result[index].setCoefficients(coeffs2[0], coeffs2[1], coeffs2[2]);
+ }
+ return result;
+ }
+
+ // If we're here, then A==0 and C==0.
+ if (abs(B) < epsilon) { // A == B == C == 0, so the conic reduces to Dx + Ey + F.
+ if (D == 0 && E == 0) {
+ return result;
+ }
+ std::array<double, 3> coeffs = {D, E, F};
+ rescale_homogenous(coeffs);
+ result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
+ return result;
+ }
+
+ // OK, so A == C == 0 but B != 0. In other words, the conic has the form
+ // Bxy + Dx + Ey + F. Since B != 0, the zero set stays the same if we multiply the
+ // equation by B, which gives us this equation:
+ // B²xy + BDx + BEy + BF = 0.
+ // The above factors as (Bx + E)(By + D) = 0.
+ std::array<double, 2> nonzero_coeffs = {B, E};
+ rescale_homogenous(nonzero_coeffs);
+ result[0].setCoefficients(nonzero_coeffs[0], 0, nonzero_coeffs[1]);
+
+ nonzero_coeffs = {B, D};
+ rescale_homogenous(nonzero_coeffs);
+ result[1].setCoefficients(0, nonzero_coeffs[0], nonzero_coeffs[1]);
+ return result;
+}
+
+/*
+ * Return the rectangle that bound the conic section arc characterized by
+ * the passed points.
+ *
+ * P1: the initial point of the arc
+ * Q: the inner point of the arc
+ * P2: the final point of the arc
+ *
+ * prerequisite: the passed points must lie on the conic
+ */
+Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const
+{
+ using std::swap;
+ //std::cout << "BOUND: P1 = " << P1 << std::endl;
+ //std::cout << "BOUND: Q = " << Q << std::endl;
+ //std::cout << "BOUND: P2 = " << P2 << std::endl;
+
+ Rect B(P1, P2);
+ double Qside = signed_triangle_area (P1, Q, P2);
+ //std::cout << "BOUND: Qside = " << Qside << std::endl;
+
+ Line gl[2];
+ bool empty[2] = {false, false};
+
+ try // if the passed coefficients lead to an equation 0x + 0y + c == 0,
+ { // with c != 0 the setCoefficients rise an exception
+ gl[0].setCoefficients (coeff(1), 2 * coeff(2), coeff(4));
+ }
+ catch(Geom::LogicalError const &e)
+ {
+ empty[0] = true;
+ }
+
+ try
+ {
+ gl[1].setCoefficients (2 * coeff(0), coeff(1), coeff(3));
+ }
+ catch(Geom::LogicalError const &e)
+ {
+ empty[1] = true;
+ }
+
+ std::vector<double> rts;
+ std::vector<Point> M;
+ for (size_t dim = 0; dim < 2; ++dim)
+ {
+ if (empty[dim]) continue;
+ rts = roots (gl[dim]);
+ M.clear();
+ for (double rt : rts)
+ M.push_back (gl[dim].pointAt (rt));
+ if (M.size() == 1)
+ {
+ double Mside = signed_triangle_area (P1, M[0], P2);
+ if (sgn(Mside) == sgn(Qside))
+ {
+ //std::cout << "BOUND: M.size() == 1" << std::endl;
+ B[dim].expandTo(M[0][dim]);
+ }
+ }
+ else if (M.size() == 2)
+ {
+ //std::cout << "BOUND: M.size() == 2" << std::endl;
+ if (M[0][dim] > M[1][dim])
+ swap (M[0], M[1]);
+
+ if (M[0][dim] > B[dim].max())
+ {
+ double Mside = signed_triangle_area (P1, M[0], P2);
+ if (sgn(Mside) == sgn(Qside))
+ B[dim].setMax(M[0][dim]);
+ }
+ else if (M[1][dim] < B[dim].min())
+ {
+ double Mside = signed_triangle_area (P1, M[1], P2);
+ if (sgn(Mside) == sgn(Qside))
+ B[dim].setMin(M[1][dim]);
+ }
+ else
+ {
+ double Mside = signed_triangle_area (P1, M[0], P2);
+ if (sgn(Mside) == sgn(Qside))
+ B[dim].setMin(M[0][dim]);
+ Mside = signed_triangle_area (P1, M[1], P2);
+ if (sgn(Mside) == sgn(Qside))
+ B[dim].setMax(M[1][dim]);
+ }
+ }
+ }
+
+ return B;
+}
+
+/*
+ * Return all points on the conic section nearest to the passed point "P".
+ *
+ * P: the point to compute the nearest one
+ */
+std::vector<Point> xAx::allNearestTimes (const Point &P) const
+{
+ // TODO: manage the circle - centre case
+ std::vector<Point> points;
+
+ // named C the conic we look for points (x,y) on C such that
+ // dot (grad (C(x,y)), rot90 (P -(x,y))) == 0; the set of points satisfying
+ // this equation is still a conic G, so the wanted points can be found by
+ // intersecting C with G
+ xAx G (-coeff(1),
+ 2 * (coeff(0) - coeff(2)),
+ coeff(1),
+ -coeff(4) + coeff(1) * P[X] - 2 * coeff(0) * P[Y],
+ coeff(3) - coeff(1) * P[Y] + 2 * coeff(2) * P[X],
+ -coeff(3) * P[Y] + coeff(4) * P[X]);
+
+ std::vector<Point> crs = intersect (*this, G);
+
+ //std::cout << "NEAREST POINT: crs.size = " << crs.size() << std::endl;
+ if (crs.empty()) return points;
+
+ size_t idx = 0;
+ double mindist = distanceSq (crs[0], P);
+ std::vector<double> dist;
+ dist.push_back (mindist);
+
+ for (size_t i = 1; i < crs.size(); ++i)
+ {
+ dist.push_back (distanceSq (crs[i], P));
+ if (mindist > dist.back())
+ {
+ idx = i;
+ mindist = dist.back();
+ }
+ }
+
+ points.push_back (crs[idx]);
+ for (size_t i = 0; i < crs.size(); ++i)
+ {
+ if (i == idx) continue;
+ if (dist[i] == mindist)
+ points.push_back (crs[i]);
+ }
+
+ return points;
+}
+
+
+
+bool clip (std::vector<RatQuad> & rq, const xAx & cs, const Rect & R)
+{
+ clipper aclipper (cs, R);
+ return aclipper.clip (rq);
+}
+
+
+} // 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/src/2geom/convex-hull.cpp b/src/2geom/convex-hull.cpp
new file mode 100644
index 0000000..f801fcc
--- /dev/null
+++ b/src/2geom/convex-hull.cpp
@@ -0,0 +1,746 @@
+/** @file
+ * @brief Convex hull of a set of points
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael G. Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2006-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/convex-hull.h>
+#include <2geom/exception.h>
+#include <algorithm>
+#include <map>
+#include <iostream>
+#include <cassert>
+#include <boost/array.hpp>
+
+/** Todo:
+ + modify graham scan to work top to bottom, rather than around angles
+ + intersection
+ + minimum distance between convex hulls
+ + maximum distance between convex hulls
+ + hausdorf metric?
+ + check all degenerate cases carefully
+ + check all algorithms meet all invariants
+ + generalise rotating caliper algorithm (iterator/circulator?)
+*/
+
+using std::vector;
+using std::map;
+using std::pair;
+using std::make_pair;
+using std::swap;
+
+namespace Geom {
+
+ConvexHull::ConvexHull(Point const &a, Point const &b)
+ : _boundary(2)
+ , _lower(0)
+{
+ _boundary[0] = a;
+ _boundary[1] = b;
+ std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>());
+ _construct();
+}
+
+ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c)
+ : _boundary(3)
+ , _lower(0)
+{
+ _boundary[0] = a;
+ _boundary[1] = b;
+ _boundary[2] = c;
+ std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>());
+ _construct();
+}
+
+ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d)
+ : _boundary(4)
+ , _lower(0)
+{
+ _boundary[0] = a;
+ _boundary[1] = b;
+ _boundary[2] = c;
+ _boundary[3] = d;
+ std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>());
+ _construct();
+}
+
+ConvexHull::ConvexHull(std::vector<Point> const &pts)
+ : _lower(0)
+{
+ //if (pts.size() > 16) { // arbitrary threshold
+ // _prune(pts.begin(), pts.end(), _boundary);
+ //} else {
+ _boundary = pts;
+ std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>());
+ //}
+ _construct();
+}
+
+bool ConvexHull::_is_clockwise_turn(Point const &a, Point const &b, Point const &c)
+{
+ if (b == c) return false;
+ return cross(b-a, c-a) > 0;
+}
+
+void ConvexHull::_construct()
+{
+ // _boundary must already be sorted in LexLess<X> order
+ if (_boundary.empty()) {
+ _lower = 0;
+ return;
+ }
+ if (_boundary.size() == 1 || (_boundary.size() == 2 && _boundary[0] == _boundary[1])) {
+ _boundary.resize(1);
+ _lower = 1;
+ return;
+ }
+ if (_boundary.size() == 2) {
+ _lower = 2;
+ return;
+ }
+
+ std::size_t k = 2;
+ for (std::size_t i = 2; i < _boundary.size(); ++i) {
+ while (k >= 2 && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) {
+ --k;
+ }
+ std::swap(_boundary[k++], _boundary[i]);
+ }
+
+ _lower = k;
+ std::sort(_boundary.begin() + k, _boundary.end(), Point::LexGreater<X>());
+ _boundary.push_back(_boundary.front());
+ for (std::size_t i = _lower; i < _boundary.size(); ++i) {
+ while (k > _lower && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) {
+ --k;
+ }
+ std::swap(_boundary[k++], _boundary[i]);
+ }
+
+ _boundary.resize(k-1);
+}
+
+double ConvexHull::area() const
+{
+ if (size() <= 2) return 0;
+
+ double a = 0;
+ for (std::size_t i = 0; i < size()-1; ++i) {
+ a += cross(_boundary[i], _boundary[i+1]);
+ }
+ a += cross(_boundary.back(), _boundary.front());
+ return fabs(a * 0.5);
+}
+
+OptRect ConvexHull::bounds() const
+{
+ OptRect ret;
+ if (empty()) return ret;
+ ret = Rect(left(), top(), right(), bottom());
+ return ret;
+}
+
+Point ConvexHull::topPoint() const
+{
+ Point ret;
+ ret[Y] = std::numeric_limits<Coord>::infinity();
+
+ for (auto i : upperHull()) {
+ if (ret[Y] >= i.y()) {
+ ret = i;
+ } else {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+Point ConvexHull::bottomPoint() const
+{
+ Point ret;
+ ret[Y] = -std::numeric_limits<Coord>::infinity();
+
+ for (auto j : lowerHull()) {
+ if (ret[Y] <= j.y()) {
+ ret = j;
+ } else {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+template <typename Iter, typename Lex>
+bool below_x_monotonic_polyline(Point const &p, Iter first, Iter last, Lex lex)
+{
+ typename Lex::Secondary above;
+ Iter f = std::lower_bound(first, last, p, lex);
+ if (f == last) return false;
+ if (f == first) {
+ if (p == *f) return true;
+ return false;
+ }
+
+ Point a = *(f-1), b = *f;
+ if (a[X] == b[X]) {
+ if (above(p[Y], a[Y]) || above(b[Y], p[Y])) return false;
+ } else {
+ // TODO: maybe there is a more numerically stable method
+ Coord y = lerp((p[X] - a[X]) / (b[X] - a[X]), a[Y], b[Y]);
+ if (above(p[Y], y)) return false;
+ }
+ return true;
+}
+
+bool ConvexHull::contains(Point const &p) const
+{
+ if (_boundary.empty()) return false;
+ if (_boundary.size() == 1) {
+ if (_boundary[0] == p) return true;
+ return false;
+ }
+
+ // 1. verify that the point is in the relevant X range
+ if (p[X] < _boundary[0][X] || p[X] > _boundary[_lower-1][X]) return false;
+
+ // 2. check whether it is below the upper hull
+ UpperIterator ub = upperHull().begin(), ue = upperHull().end();
+ if (!below_x_monotonic_polyline(p, ub, ue, Point::LexLess<X>())) return false;
+
+ // 3. check whether it is above the lower hull
+ LowerIterator lb = lowerHull().begin(), le = lowerHull().end();
+ if (!below_x_monotonic_polyline(p, lb, le, Point::LexGreater<X>())) return false;
+
+ return true;
+}
+
+bool ConvexHull::contains(Rect const &r) const
+{
+ for (unsigned i = 0; i < 4; ++i) {
+ if (!contains(r.corner(i))) return false;
+ }
+ return true;
+}
+
+bool ConvexHull::contains(ConvexHull const &ch) const
+{
+ // TODO: requires interiorContains.
+ // We have to check all points of ch, and each point takes logarithmic time.
+ // If there are more points in ch that here, it is faster to make the check
+ // the other way around.
+ /*if (ch.size() > size()) {
+ for (iterator i = begin(); i != end(); ++i) {
+ if (ch.interiorContains(*i)) return false;
+ }
+ return true;
+ }*/
+
+ for (auto i : ch) {
+ if (!contains(i)) return false;
+ }
+ return true;
+}
+
+void ConvexHull::swap(ConvexHull &other)
+{
+ _boundary.swap(other._boundary);
+ std::swap(_lower, other._lower);
+}
+
+void ConvexHull::swap(std::vector<Point> &pts)
+{
+ _boundary.swap(pts);
+ _lower = 0;
+ std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>());
+ _construct();
+}
+
+#if 0
+/*** SignedTriangleArea
+ * returns the area of the triangle defined by p0, p1, p2. A clockwise triangle has positive area.
+ */
+double
+SignedTriangleArea(Point p0, Point p1, Point p2) {
+ return cross((p1 - p0), (p2 - p0));
+}
+
+class angle_cmp{
+public:
+ Point o;
+ angle_cmp(Point o) : o(o) {}
+
+#if 0
+ bool
+ operator()(Point a, Point b) {
+ // not remove this check or std::sort could crash
+ if (a == b) return false;
+ Point da = a - o;
+ Point db = b - o;
+ if (da == -db) return false;
+
+#if 1
+ double aa = da[0];
+ double ab = db[0];
+ if((da[1] == 0) && (db[1] == 0))
+ return da[0] < db[0];
+ if(da[1] == 0)
+ return true; // infinite tangent
+ if(db[1] == 0)
+ return false; // infinite tangent
+ aa = da[0] / da[1];
+ ab = db[0] / db[1];
+ if(aa > ab)
+ return true;
+#else
+ //assert((ata > atb) == (aa < ab));
+ double aa = atan2(da);
+ double ab = atan2(db);
+ if(aa < ab)
+ return true;
+#endif
+ if(aa == ab)
+ return L2sq(da) < L2sq(db);
+ return false;
+ }
+#else
+ bool operator() (Point const& a, Point const& b)
+ {
+ // not remove this check or std::sort could generate
+ // a segmentation fault because it needs a strict '<'
+ // but due to round errors a == b doesn't mean dxy == dyx
+ if (a == b) return false;
+ Point da = a - o;
+ Point db = b - o;
+ if (da == -db) return false;
+ double dxy = da[X] * db[Y];
+ double dyx = da[Y] * db[X];
+ if (dxy > dyx) return true;
+ else if (dxy < dyx) return false;
+ return L2sq(da) < L2sq(db);
+ }
+#endif
+};
+
+//Mathematically incorrect mod, but more useful.
+int mod(int i, int l) {
+ return i >= 0 ?
+ i % l : (i % l) + l;
+}
+//OPT: usages can often be replaced by conditions
+
+/*** ConvexHull::add_point
+ * to add a point we need to find whether the new point extends the boundary, and if so, what it
+ * obscures. Tarjan? Jarvis?*/
+void
+ConvexHull::merge(Point p) {
+ std::vector<Point> out;
+
+ int len = boundary.size();
+
+ if(len < 2) {
+ if(boundary.empty() || boundary[0] != p)
+ boundary.push_back(p);
+ return;
+ }
+
+ bool pushed = false;
+
+ bool pre = is_left(p, -1);
+ for(int i = 0; i < len; i++) {
+ bool cur = is_left(p, i);
+ if(pre) {
+ if(cur) {
+ if(!pushed) {
+ out.push_back(p);
+ pushed = true;
+ }
+ continue;
+ }
+ else if(!pushed) {
+ out.push_back(p);
+ pushed = true;
+ }
+ }
+ out.push_back(boundary[i]);
+ pre = cur;
+ }
+
+ boundary = out;
+}
+//OPT: quickly find an obscured point and find the bounds by extending from there. then push all points not within the bounds in order.
+ //OPT: use binary searches to find the actual starts/ends, use known rights as boundaries. may require cooperation of find_left algo.
+
+/*** ConvexHull::is_clockwise
+ * We require that successive pairs of edges always turn right.
+ * We return false on collinear points
+ * proposed algorithm: walk successive edges and require triangle area is positive.
+ */
+bool
+ConvexHull::is_clockwise() const {
+ if(is_degenerate())
+ return true;
+ Point first = boundary[0];
+ Point second = boundary[1];
+ for(std::vector<Point>::const_iterator it(boundary.begin()+2), e(boundary.end());
+ it != e;) {
+ if(SignedTriangleArea(first, second, *it) > 0)
+ return false;
+ first = second;
+ second = *it;
+ ++it;
+ }
+ return true;
+}
+
+/*** ConvexHull::top_point_first
+ * We require that the first point in the convex hull has the least y coord, and that off all such points on the hull, it has the least x coord.
+ * proposed algorithm: track lexicographic minimum while walking the list.
+ */
+bool
+ConvexHull::top_point_first() const {
+ if(size() <= 1) return true;
+ std::vector<Point>::const_iterator pivot = boundary.begin();
+ for(std::vector<Point>::const_iterator it(boundary.begin()+1),
+ e(boundary.end());
+ it != e; it++) {
+ if((*it)[1] < (*pivot)[1])
+ pivot = it;
+ else if(((*it)[1] == (*pivot)[1]) &&
+ ((*it)[0] < (*pivot)[0]))
+ pivot = it;
+ }
+ return pivot == boundary.begin();
+}
+//OPT: since the Y values are orderly there should be something like a binary search to do this.
+
+bool
+ConvexHull::meets_invariants() const {
+ return is_clockwise() && top_point_first();
+}
+
+/*** ConvexHull::is_degenerate
+ * We allow three degenerate cases: empty, 1 point and 2 points. In many cases these should be handled explicitly.
+ */
+bool
+ConvexHull::is_degenerate() const {
+ return boundary.size() < 3;
+}
+
+
+int sgn(double x) {
+ if(x == 0) return 0;
+ return (x<0)?-1:1;
+}
+
+bool same_side(Point L[2], Point xs[4]) {
+ int side = 0;
+ for(int i = 0; i < 4; i++) {
+ int sn = sgn(SignedTriangleArea(L[0], L[1], xs[i]));
+ if(sn && !side)
+ side = sn;
+ else if(sn != side) return false;
+ }
+ return true;
+}
+
+/** find bridging pairs between two convex hulls.
+ * this code is based on Hormoz Pirzadeh's masters thesis. There is room for optimisation:
+ * 1. reduce recomputation
+ * 2. use more efficient angle code
+ * 3. write as iterator
+ */
+std::vector<pair<int, int> > bridges(ConvexHull a, ConvexHull b) {
+ vector<pair<int, int> > ret;
+
+ // 1. find maximal points on a and b
+ int ai = 0, bi = 0;
+ // 2. find first copodal pair
+ double ap_angle = atan2(a[ai+1] - a[ai]);
+ double bp_angle = atan2(b[bi+1] - b[bi]);
+ Point L[2] = {a[ai], b[bi]};
+ while(ai < int(a.size()) || bi < int(b.size())) {
+ if(ap_angle == bp_angle) {
+ // In the case of parallel support lines, we must consider all four pairs of copodal points
+ {
+ assert(0); // untested
+ Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]};
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ xs[2] = b[bi];
+ xs[3] = b[bi+2];
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ xs[0] = a[ai];
+ xs[1] = a[ai+2];
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ xs[2] = b[bi-1];
+ xs[3] = b[bi+1];
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ }
+ ai++;
+ ap_angle += angle_between(a[ai] - a[ai-1], a[ai+1] - a[ai]);
+ L[0] = a[ai];
+ bi++;
+ bp_angle += angle_between(b[bi] - b[bi-1], b[bi+1] - b[bi]);
+ L[1] = b[bi];
+ std::cout << "parallel\n";
+ } else if(ap_angle < bp_angle) {
+ ai++;
+ ap_angle += angle_between(a[ai] - a[ai-1], a[ai+1] - a[ai]);
+ L[0] = a[ai];
+ Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]};
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ } else {
+ bi++;
+ bp_angle += angle_between(b[bi] - b[bi-1], b[bi+1] - b[bi]);
+ L[1] = b[bi];
+ Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]};
+ if(same_side(L, xs)) ret.push_back(make_pair(ai, bi));
+ }
+ }
+ return ret;
+}
+
+unsigned find_bottom_right(ConvexHull const &a) {
+ unsigned it = 1;
+ while(it < a.boundary.size() &&
+ a.boundary[it][Y] > a.boundary[it-1][Y])
+ it++;
+ return it-1;
+}
+
+/*** ConvexHull sweepline_intersection(ConvexHull a, ConvexHull b);
+ * find the intersection between two convex hulls. The intersection is also a convex hull.
+ * (Proof: take any two points both in a and in b. Any point between them is in a by convexity,
+ * and in b by convexity, thus in both. Need to prove still finite bounds.)
+ * This algorithm works by sweeping a line down both convex hulls in parallel, working out the left and right edges of the new hull.
+ */
+ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b) {
+ ConvexHull ret;
+
+ unsigned al = 0;
+ unsigned bl = 0;
+
+ while(al+1 < a.boundary.size() &&
+ (a.boundary[al+1][Y] > b.boundary[bl][Y])) {
+ al++;
+ }
+ while(bl+1 < b.boundary.size() &&
+ (b.boundary[bl+1][Y] > a.boundary[al][Y])) {
+ bl++;
+ }
+ // al and bl now point to the top of the first pair of edges that overlap in y value
+ //double sweep_y = std::min(a.boundary[al][Y],
+ // b.boundary[bl][Y]);
+ return ret;
+}
+
+/*** ConvexHull intersection(ConvexHull a, ConvexHull b);
+ * find the intersection between two convex hulls. The intersection is also a convex hull.
+ * (Proof: take any two points both in a and in b. Any point between them is in a by convexity,
+ * and in b by convexity, thus in both. Need to prove still finite bounds.)
+ */
+ConvexHull intersection(ConvexHull /*a*/, ConvexHull /*b*/) {
+ ConvexHull ret;
+ /*
+ int ai = 0, bi = 0;
+ int aj = a.boundary.size() - 1;
+ int bj = b.boundary.size() - 1;
+ */
+ /*while (true) {
+ if(a[ai]
+ }*/
+ return ret;
+}
+
+template <typename T>
+T idx_to_pair(pair<T, T> p, int idx) {
+ return idx?p.second:p.first;
+}
+
+/*** ConvexHull merge(ConvexHull a, ConvexHull b);
+ * find the smallest convex hull that surrounds a and b.
+ */
+ConvexHull merge(ConvexHull a, ConvexHull b) {
+ ConvexHull ret;
+
+ std::cout << "---\n";
+ std::vector<pair<int, int> > bpair = bridges(a, b);
+
+ // Given our list of bridges {(pb1, qb1), ..., (pbk, qbk)}
+ // we start with the highest point in p0, q0, say it is p0.
+ // then the merged hull is p0, ..., pb1, qb1, ..., qb2, pb2, ...
+ // In other words, either of the two polygons vertices are added in order until the vertex coincides with a bridge point, at which point we swap.
+
+ unsigned state = (a[0][Y] < b[0][Y])?0:1;
+ ret.boundary.reserve(a.size() + b.size());
+ ConvexHull chs[2] = {a, b};
+ unsigned idx = 0;
+
+ for(unsigned k = 0; k < bpair.size(); k++) {
+ unsigned limit = idx_to_pair(bpair[k], state);
+ std::cout << bpair[k].first << " , " << bpair[k].second << "; "
+ << idx << ", " << limit << ", s: "
+ << state
+ << " \n";
+ while(idx <= limit) {
+ ret.boundary.push_back(chs[state][idx++]);
+ }
+ state = 1-state;
+ idx = idx_to_pair(bpair[k], state);
+ }
+ while(idx < chs[state].size()) {
+ ret.boundary.push_back(chs[state][idx++]);
+ }
+ return ret;
+}
+
+ConvexHull graham_merge(ConvexHull a, ConvexHull b) {
+ ConvexHull result;
+
+ // we can avoid the find pivot step because of top_point_first
+ if(b.boundary[0] <= a.boundary[0])
+ swap(a, b);
+
+ result.boundary = a.boundary;
+ result.boundary.insert(result.boundary.end(),
+ b.boundary.begin(), b.boundary.end());
+
+/** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the
+ angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan
+ online, avoiding a bunch of memory copies. That would probably be linear. -- njh*/
+ result.angle_sort();
+ result.graham_scan();
+
+ return result;
+}
+
+ConvexHull andrew_merge(ConvexHull a, ConvexHull b) {
+ ConvexHull result;
+
+ // we can avoid the find pivot step because of top_point_first
+ if(b.boundary[0] <= a.boundary[0])
+ swap(a, b);
+
+ result.boundary = a.boundary;
+ result.boundary.insert(result.boundary.end(),
+ b.boundary.begin(), b.boundary.end());
+
+/** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the
+ angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan
+ online, avoiding a bunch of memory copies. That would probably be linear. -- njh*/
+ result.andrew_scan();
+
+ return result;
+}
+
+//TODO: reinstate
+/*ConvexCover::ConvexCover(Path const &sp) : path(&sp) {
+ cc.reserve(sp.size());
+ for(Geom::Path::const_iterator it(sp.begin()), end(sp.end()); it != end; ++it) {
+ cc.push_back(ConvexHull((*it).begin(), (*it).end()));
+ }
+}*/
+
+double ConvexHull::centroid_and_area(Geom::Point& centroid) const {
+ const unsigned n = boundary.size();
+ if (n < 2)
+ return 0;
+ if(n < 3) {
+ centroid = (boundary[0] + boundary[1])/2;
+ return 0;
+ }
+ Geom::Point centroid_tmp(0,0);
+ double atmp = 0;
+ for (unsigned i = n-1, j = 0; j < n; i = j, j++) {
+ const double ai = cross(boundary[j], boundary[i]);
+ atmp += ai;
+ centroid_tmp += (boundary[j] + boundary[i])*ai; // first moment.
+ }
+ if (atmp != 0) {
+ centroid = centroid_tmp / (3 * atmp);
+ }
+ return atmp / 2;
+}
+
+// TODO: This can be made lg(n) using golden section/fibonacci search three starting points, say 0,
+// n/2, n-1 construct a new point, say (n/2 + n)/2 throw away the furthest boundary point iterate
+// until interval is a single value
+Point const * ConvexHull::furthest(Point direction) const {
+ Point const * p = &boundary[0];
+ double d = dot(*p, direction);
+ for(unsigned i = 1; i < boundary.size(); i++) {
+ double dd = dot(boundary[i], direction);
+ if(d < dd) {
+ p = &boundary[i];
+ d = dd;
+ }
+ }
+ return p;
+}
+
+
+// returns (a, (b,c)), three points which define the narrowest diameter of the hull as the pair of
+// lines going through b,c, and through a, parallel to b,c TODO: This can be made linear time by
+// moving point tc incrementally from the previous value (it can only move in one direction). It
+// is currently n*O(furthest)
+double ConvexHull::narrowest_diameter(Point &a, Point &b, Point &c) {
+ Point tb = boundary.back();
+ double d = std::numeric_limits<double>::max();
+ for(unsigned i = 0; i < boundary.size(); i++) {
+ Point tc = boundary[i];
+ Point n = -rot90(tb-tc);
+ Point ta = *furthest(n);
+ double td = dot(n, ta-tb)/dot(n,n);
+ if(td < d) {
+ a = ta;
+ b = tb;
+ c = tc;
+ d = td;
+ }
+ tb = tc;
+ }
+ return d;
+}
+#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/src/2geom/coord.cpp b/src/2geom/coord.cpp
new file mode 100644
index 0000000..205a82f
--- /dev/null
+++ b/src/2geom/coord.cpp
@@ -0,0 +1,123 @@
+/** @file
+ * @brief Conversion between Coord and strings
+ *//*
+ * 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.
+ */
+
+// Most of the code in this file is derived from:
+// https://code.google.com/p/double-conversion/
+// The copyright notice for that code is attached below.
+//
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * 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.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may 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.
+
+#include <2geom/coord.h>
+#include <cstdint>
+#include <cstdlib>
+#include <cassert>
+#include <cstring>
+#include <climits>
+#include <cstdarg>
+#include <cmath>
+
+#include <double-conversion/double-conversion.h>
+
+namespace Geom {
+
+std::string format_coord_shortest(Coord x)
+{
+ static const double_conversion::DoubleToStringConverter conv(
+ double_conversion::DoubleToStringConverter::UNIQUE_ZERO,
+ "inf", "NaN", 'e', -3, 6, 0, 0);
+ std::string ret(' ', 32);
+ double_conversion::StringBuilder builder(&ret[0], 32);
+ conv.ToShortest(x, &builder);
+ ret.resize(builder.position());
+ return ret;
+}
+
+std::string format_coord_nice(Coord x)
+{
+ static const double_conversion::DoubleToStringConverter conv(
+ double_conversion::DoubleToStringConverter::UNIQUE_ZERO,
+ "inf", "NaN", 'e', -6, 21, 0, 0);
+ std::string ret(' ', 32);
+ double_conversion::StringBuilder builder(&ret[0], 32);
+ conv.ToShortest(x, &builder);
+ ret.resize(builder.position());
+ return ret;
+}
+
+Coord parse_coord(std::string const &s)
+{
+ static const double_conversion::StringToDoubleConverter conv(
+ double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |
+ double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES |
+ double_conversion::StringToDoubleConverter::ALLOW_SPACES_AFTER_SIGN,
+ 0.0, nan(""), "inf", "NaN");
+ int dummy;
+ return conv.StringToDouble(s.c_str(), s.length(), &dummy);
+}
+
+} // 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/src/2geom/crossing.cpp b/src/2geom/crossing.cpp
new file mode 100644
index 0000000..1159fb0
--- /dev/null
+++ b/src/2geom/crossing.cpp
@@ -0,0 +1,233 @@
+#include <2geom/crossing.h>
+#include <2geom/path.h>
+
+namespace Geom {
+
+//bool edge_involved_in(Edge const &e, Crossing const &c) {
+// if(e.path == c.a) {
+// if(e.time == c.ta) return true;
+// } else if(e.path == c.b) {
+// if(e.time == c.tb) return true;
+// }
+// return false;
+//}
+
+double wrap_dist(double from, double to, double size, bool rev) {
+ if(rev) {
+ if(to > from) {
+ return from + (size - to);
+ } else {
+ return from - to;
+ }
+ } else {
+ if(to < from) {
+ return to + (size - from);
+ } else {
+ return to - from;
+ }
+ }
+}
+/*
+CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs) {
+ std::vector<Point> locs;
+ CrossingGraph ret;
+ for(unsigned i = 0; i < crs.size(); i++) {
+ Point pnt = p[crs[i].a].pointAt(crs[i].ta);
+ unsigned j = 0;
+ for(; j < locs.size(); j++) {
+ if(are_near(pnt, locs[j])) break;
+ }
+ if(j == locs.size()) {
+ ret.push_back(CrossingNode());
+ locs.push_back(pnt);
+ }
+ ret[j].add_edge(Edge(crs[i].a, crs[i].ta, false));
+ ret[j].add_edge(Edge(crs[i].a, crs[i].ta, true));
+ ret[j].add_edge(Edge(crs[i].b, crs[i].tb, false));
+ ret[j].add_edge(Edge(crs[i].b, crs[i].tb, true));
+ }
+
+ for(unsigned i = 0; i < ret.size(); i++) {
+ for(unsigned j = 0; j < ret[i].edges.size(); j++) {
+ unsigned pth = ret[i].edges[j].path;
+ double t = ret[i].edges[j].time;
+ bool rev = ret[i].edges[j].reverse;
+ double size = p[pth].size()+1;
+ double best = size;
+ unsigned bix = ret.size();
+ for(unsigned k = 0; k < ret.size(); k++) {
+ for(unsigned l = 0; l < ret[k].edges.size(); l++) {
+ if(ret[i].edges[j].path == ret[k].edges[l].path && (k != i || l != j)) {
+ double d = wrap_dist(t, ret[i].edges[j].time, size, rev);
+ if(d < best) {
+ best = d;
+ bix = k;
+ }
+ }
+ }
+ }
+ if(bix == ret.size()) {
+ std::cout << "couldn't find an adequate next-crossing node";
+ bix = i;
+ }
+ ret[i].edges[j].node = bix;
+ }
+ }
+
+ return ret;
+ */
+ /* Various incoherent code bits
+ // list of sets of edges, each set corresponding to those emanating from the path
+ CrossingGraph ret;
+ std::vector<Edge> edges(crs.size());
+
+ std::vector<std::vector<bool> > used;
+ unsigned i, j;
+ do {
+ first_false(used, i, j);
+ CrossingNode cn;
+ do {
+ unsigned di = i, dj = j;
+ crossing_dual(di, dj);
+ if(!used[di,dj]) {
+
+ }
+ }
+
+ } while(!used[i,j])
+
+
+ for(unsigned j = 0; j < crs[i].size(); j++) {
+
+ edges.push_back(Edge(i, crs[i][j].getOtherTime(i), false));
+ edges.push_back(Edge(i, crs[i][j].getOtherTime(i), true));
+ }
+ std::sort(edges.begin(), edges.end(), TimeOrder());
+ for(unsigned j = 0; j < edges.size(); ) {
+ CrossingNode cn;
+ double t = edges[j].time;
+ while(j < edges.size() && are_near(edges[j].time, t)) {
+ cn.edges.push_back(edges[j]);
+ }
+ }
+*/
+//}
+
+// provide specific method for Paths because paths can be closed or open. Path::size() is named somewhat wrong...
+std::vector<Rect> bounds(Path const &a) {
+ std::vector<Rect> rs;
+ for (unsigned i = 0; i < a.size_default(); i++) {
+ OptRect bb = a[i].boundsFast();
+ if (bb) {
+ rs.push_back(*bb);
+ }
+ }
+ return rs;
+}
+
+void merge_crossings(Crossings &a, Crossings &b, unsigned i) {
+ Crossings n;
+ sort_crossings(b, i);
+ n.resize(a.size() + b.size());
+ std::merge(a.begin(), a.end(), b.begin(), b.end(), n.begin(), CrossingOrder(i));
+ a = n;
+}
+
+void offset_crossings(Crossings &cr, double a, double b) {
+ for(auto & i : cr) {
+ i.ta += a;
+ i.tb += b;
+ }
+}
+
+Crossings reverse_ta(Crossings const &cr, std::vector<double> max) {
+ Crossings ret;
+ for(const auto & i : cr) {
+ double mx = max[i.a];
+ ret.push_back(Crossing(i.ta > mx+0.01 ? (1 - (i.ta - mx) + mx) : mx - i.ta,
+ i.tb, !i.dir));
+ }
+ return ret;
+}
+
+Crossings reverse_tb(Crossings const &cr, unsigned split, std::vector<double> max) {
+ Crossings ret;
+ for(const auto & i : cr) {
+ double mx = max[i.b - split];
+ ret.push_back(Crossing(i.ta, i.tb > mx+0.01 ? (1 - (i.tb - mx) + mx) : mx - i.tb,
+ !i.dir));
+ }
+ return ret;
+}
+
+CrossingSet reverse_ta(CrossingSet const &cr, unsigned split, std::vector<double> max) {
+ CrossingSet ret;
+ for(unsigned i = 0; i < cr.size(); i++) {
+ Crossings res = reverse_ta(cr[i], max);
+ if(i < split) std::reverse(res.begin(), res.end());
+ ret.push_back(res);
+ }
+ return ret;
+}
+
+CrossingSet reverse_tb(CrossingSet const &cr, unsigned split, std::vector<double> max) {
+ CrossingSet ret;
+ for(unsigned i = 0; i < cr.size(); i++) {
+ Crossings res = reverse_tb(cr[i], split, max);
+ if(i >= split) std::reverse(res.begin(), res.end());
+ ret.push_back(res);
+ }
+ return ret;
+}
+
+// Delete any duplicates in a vector of crossings
+// A crossing is considered to be a duplicate when it has both t_a and t_b near to another crossing's t_a and t_b
+// For example, duplicates will be found when calculating the intersections of a linesegment with a polygon, if the
+// endpoint of that line coincides with a cusp node of the polygon. In that case, an intersection will be found of
+// the linesegment with each of the polygon's linesegments extending from the cusp node (i.e. two intersections)
+void delete_duplicates(Crossings &crs) {
+ Crossings::reverse_iterator rit = crs.rbegin();
+
+ for (rit = crs.rbegin(); rit!= crs.rend(); ++rit) {
+ Crossings::reverse_iterator rit2 = rit;
+ while (++rit2 != crs.rend()) {
+ if (Geom::are_near((*rit).ta, (*rit2).ta) && Geom::are_near((*rit).tb, (*rit2).tb)) {
+ crs.erase((rit + 1).base()); // This +1 and .base() construction is needed to convert to a regular iterator
+ break; // out of while loop, and continue with next iteration of for loop
+ }
+ }
+ }
+}
+
+void clean(Crossings &/*cr_a*/, Crossings &/*cr_b*/) {
+/* if(cr_a.empty()) return;
+
+ //Remove anything with dupes
+
+ for(Eraser<Crossings> i(&cr_a); !i.ended(); i++) {
+ const Crossing cur = *i;
+ Eraser<Crossings> next(i);
+ next++;
+ if(are_near(cur, *next)) {
+ cr_b.erase(std::find(cr_b.begin(), cr_b.end(), cur));
+ for(i = next; near(*i, cur); i++) {
+ cr_b.erase(std::find(cr_b.begin(), cr_b.end(), *i));
+ }
+ continue;
+ }
+ }
+*/
+}
+
+}
+
+/*
+ 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/src/2geom/curve.cpp b/src/2geom/curve.cpp
new file mode 100644
index 0000000..f79edb3
--- /dev/null
+++ b/src/2geom/curve.cpp
@@ -0,0 +1,235 @@
+/* Abstract curve type - implementation of default methods
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Rafał Siejakowski <rs@rs-math.net>
+ *
+ * Copyright 2007-2009 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/curve.h>
+#include <2geom/exception.h>
+#include <2geom/nearest-time.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/ord.h>
+#include <2geom/path-sink.h>
+
+namespace Geom
+{
+
+Coord Curve::nearestTime(Point const& p, Coord a, Coord b) const
+{
+ return nearest_time(p, toSBasis(), a, b);
+}
+
+std::vector<Coord> Curve::allNearestTimes(Point const& p, Coord from, Coord to) const
+{
+ return all_nearest_times(p, toSBasis(), from, to);
+}
+
+Coord Curve::length(Coord tolerance) const
+{
+ return ::Geom::length(toSBasis(), tolerance);
+}
+
+int Curve::winding(Point const &p) const
+{
+ try {
+ std::vector<Coord> ts = roots(p[Y], Y);
+ if(ts.empty()) return 0;
+ std::sort(ts.begin(), ts.end());
+
+ // skip endpoint roots when they are local maxima on the Y axis
+ // this follows the convention used in other winding routines,
+ // i.e. that the bottommost coordinate is not part of the shape
+ bool ignore_0 = unitTangentAt(0)[Y] <= 0;
+ bool ignore_1 = unitTangentAt(1)[Y] >= 0;
+
+ int wind = 0;
+ for (double t : ts) {
+ //std::cout << t << std::endl;
+ if ((t == 0 && ignore_0) || (t == 1 && ignore_1)) continue;
+ if (valueAt(t, X) > p[X]) { // root is ray intersection
+ Point tangent = unitTangentAt(t);
+ if (tangent[Y] > 0) {
+ // at the point of intersection, curve goes in +Y direction,
+ // so it winds in the direction of positive angles
+ ++wind;
+ } else if (tangent[Y] < 0) {
+ --wind;
+ }
+ }
+ }
+ return wind;
+ } catch (InfiniteSolutions const &e) {
+ // this means we encountered a line segment exactly coincident with the point
+ // skip, since this will be taken care of by endpoint roots in other segments
+ return 0;
+ }
+}
+
+std::vector<CurveIntersection> Curve::intersect(Curve const &/*other*/, Coord /*eps*/) const
+{
+ // TODO: approximate as Bezier
+ THROW_NOTIMPLEMENTED();
+}
+
+std::vector<CurveIntersection> Curve::intersectSelf(Coord eps) const
+{
+ /// Represents a sub-arc of the curve.
+ struct Subcurve
+ {
+ std::unique_ptr<Curve> curve;
+ Interval parameter_range;
+
+ Subcurve(Curve *piece, Coord from, Coord to)
+ : curve{piece}
+ , parameter_range{from, to}
+ {}
+ };
+
+ /// A closure to split the curve into portions at the prescribed split points.
+ auto const split_into_subcurves = [=](std::vector<Coord> const &splits) {
+ std::vector<Subcurve> result;
+ result.reserve(splits.size() + 1);
+ Coord previous = 0;
+ for (Coord split : splits) {
+ // Use global EPSILON since we're operating on normalized curve times.
+ if (split < EPSILON || split > 1.0 - EPSILON) {
+ continue;
+ }
+ result.emplace_back(portion(previous, split), previous, split);
+ previous = split;
+ }
+ result.emplace_back(portion(previous, 1.0), previous, 1.0);
+ return result;
+ };
+
+ /// A closure to find pairwise intersections between the passed subcurves.
+ auto const pairwise_intersect = [=](std::vector<Subcurve> const &subcurves) {
+ std::vector<CurveIntersection> result;
+ for (unsigned i = 0; i < subcurves.size(); i++) {
+ for (unsigned j = i + 1; j < subcurves.size(); j++) {
+ auto const xings = subcurves[i].curve->intersect(*subcurves[j].curve, eps);
+ for (auto const &xing : xings) {
+ // To avoid duplicate intersections, skip values at exactly 1.
+ if (xing.first == 1. || xing.second == 1.) {
+ continue;
+ }
+ Coord const ti = subcurves[i].parameter_range.valueAt(xing.first);
+ Coord const tj = subcurves[j].parameter_range.valueAt(xing.second);
+ result.emplace_back(ti, tj, xing.point());
+ }
+ }
+ }
+ std::sort(result.begin(), result.end());
+ return result;
+ };
+
+ // Monotonic segments cannot have self-intersections. Thus, we can split
+ // the curve at critical points of the X or Y coordinate and intersect
+ // the portions. However, there's the risk that a juncture between two
+ // adjacent portions is mistaken for an intersection due to numerical errors.
+ // Hence, we run the algorithm for both the X and Y coordinates and only
+ // keep the intersections that show up in both intersection lists.
+
+ // Find the critical points of both coordinates.
+ std::unique_ptr<Curve> deriv{derivative()};
+ auto const crits_x = deriv->roots(0, X);
+ auto const crits_y = deriv->roots(0, Y);
+ if (crits_x.empty() || crits_y.empty()) {
+ return {};
+ }
+
+ // Split into pieces in two ways and find self-intersections.
+ auto const pieces_x = split_into_subcurves(crits_x);
+ auto const pieces_y = split_into_subcurves(crits_y);
+ auto const crossings_from_x = pairwise_intersect(pieces_x);
+ auto const crossings_from_y = pairwise_intersect(pieces_y);
+ if (crossings_from_x.empty() || crossings_from_y.empty()) {
+ return {};
+ }
+
+ // Filter the results, only keeping self-intersections found by both approaches.
+ std::vector<CurveIntersection> result;
+ unsigned index_y = 0;
+ for (auto &&candidate_x : crossings_from_x) {
+ // Find a crossing corresponding to this one in the y-method collection.
+ while (index_y != crossings_from_y.size()) {
+ auto const gap = crossings_from_y[index_y].first - candidate_x.first;
+ if (std::abs(gap) < EPSILON) {
+ // We found the matching intersection!
+ result.emplace_back(candidate_x);
+ index_y++;
+ break;
+ } else if (gap < 0.0) {
+ index_y++;
+ } else {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+Point Curve::unitTangentAt(Coord t, unsigned n) const
+{
+ std::vector<Point> derivs = pointAndDerivatives(t, n);
+ for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) {
+ Coord length = derivs[deriv_n].length();
+ if ( ! are_near(length, 0) ) {
+ // length of derivative is non-zero, so return unit vector
+ return derivs[deriv_n] / length;
+ }
+ }
+ return Point (0,0);
+};
+
+void Curve::feed(PathSink &sink, bool moveto_initial) const
+{
+ std::vector<Point> pts;
+ sbasis_to_bezier(pts, toSBasis(), 2); //TODO: use something better!
+ if (moveto_initial) {
+ sink.moveTo(initialPoint());
+ }
+ sink.curveTo(pts[0], pts[1], pts[2]);
+}
+
+} // 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/src/2geom/d2-sbasis.cpp b/src/2geom/d2-sbasis.cpp
new file mode 100644
index 0000000..4e95f6f
--- /dev/null
+++ b/src/2geom/d2-sbasis.cpp
@@ -0,0 +1,364 @@
+/**
+ * \file
+ * \brief Some two-dimensional SBasis operations
+ *//*
+ * Authors:
+ * MenTaLguy <mental@rydia.net>
+ * Jean-François Barraud <jf.barraud@gmail.com>
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright 2007-2012 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, output 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/d2.h>
+#include <2geom/piecewise.h>
+
+namespace Geom {
+
+SBasis L2(D2<SBasis> const & a, unsigned k) { return sqrt(dot(a, a), k); }
+
+D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b) {
+ return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y]));
+}
+
+D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b) {
+ return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y]));
+}
+
+D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms) {
+ return D2<SBasis>(truncate(a[X], terms), truncate(a[Y], terms));
+}
+
+unsigned sbasis_size(D2<SBasis> const & a) {
+ return std::max((unsigned) a[0].size(), (unsigned) a[1].size());
+}
+
+//TODO: Is this sensical? shouldn't it be like pythagorean or something?
+double tail_error(D2<SBasis> const & a, unsigned tail) {
+ return std::max(a[0].tailError(tail), a[1].tailError(tail));
+}
+
+Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a) {
+ Piecewise<SBasis> x = partition(a[0], a[1].cuts), y = partition(a[1], a[0].cuts);
+ assert(x.size() == y.size());
+ Piecewise<D2<SBasis> > ret;
+ for(unsigned i = 0; i < x.size(); i++)
+ ret.push_seg(D2<SBasis>(x[i], y[i]));
+ ret.cuts.insert(ret.cuts.end(), x.cuts.begin(), x.cuts.end());
+ return ret;
+}
+
+D2<Piecewise<SBasis> > make_cuts_independent(Piecewise<D2<SBasis> > const &a) {
+ D2<Piecewise<SBasis> > ret;
+ for(unsigned d = 0; d < 2; d++) {
+ for(unsigned i = 0; i < a.size(); i++)
+ ret[d].push_seg(a[i][d]);
+ ret[d].cuts.insert(ret[d].cuts.end(), a.cuts.begin(), a.cuts.end());
+ }
+ return ret;
+}
+
+Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &M){
+ Piecewise<D2<SBasis> > result;
+ if (M.empty()) return M;
+ result.push_cut(M.cuts[0]);
+ for (unsigned i=0; i<M.size(); i++){
+ result.push(rot90(M[i]),M.cuts[i+1]);
+ }
+ return result;
+}
+
+/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b
+ * @return \f[
+ * f(t) \rightarrow \left\{
+ * \begin{array}{c}
+ * a_1 \bullet b_1 \\
+ * a_2 \bullet b_2 \\
+ * \ldots \\
+ * a_n \bullet b_n \\
+ * \end{array}\right.
+ * \f]
+ * @relates Piecewise */
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b)
+{
+ Piecewise<SBasis > result;
+ if (a.empty() || b.empty()) return result;
+ Piecewise<D2<SBasis> > aa = partition(a,b.cuts);
+ Piecewise<D2<SBasis> > bb = partition(b,a.cuts);
+
+ result.push_cut(aa.cuts.front());
+ for (unsigned i=0; i<aa.size(); i++){
+ result.push(dot(aa.segs[i],bb.segs[i]),aa.cuts[i+1]);
+ }
+ return result;
+}
+
+/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b
+ * @return \f[
+ * f(t) \rightarrow \left\{
+ * \begin{array}{c}
+ * a_1 \bullet b \\
+ * a_2 \bullet b \\
+ * \ldots \\
+ * a_n \bullet b \\
+ * \end{array}\right.
+ * \f]
+ * @relates Piecewise */
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b)
+{
+ Piecewise<SBasis > result;
+ if (a.empty()) return result;
+
+ result.push_cut(a.cuts.front());
+ for (unsigned i = 0; i < a.size(); ++i){
+ result.push(dot(a.segs[i],b), a.cuts[i+1]);
+ }
+ return result;
+}
+
+
+Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a,
+ Piecewise<D2<SBasis> > const &b){
+ Piecewise<SBasis > result;
+ if (a.empty() || b.empty()) return result;
+ Piecewise<D2<SBasis> > aa = partition(a,b.cuts);
+ Piecewise<D2<SBasis> > bb = partition(b,a.cuts);
+
+ result.push_cut(aa.cuts.front());
+ for (unsigned i=0; i<a.size(); i++){
+ result.push(cross(aa.segs[i],bb.segs[i]),aa.cuts[i+1]);
+ }
+ return result;
+}
+
+Piecewise<D2<SBasis> > operator*(Piecewise<D2<SBasis> > const &a, Affine const &m) {
+ Piecewise<D2<SBasis> > result;
+ if(a.empty()) return result;
+ result.push_cut(a.cuts[0]);
+ for (unsigned i = 0; i < a.size(); i++) {
+ result.push(a[i] * m, a.cuts[i+1]);
+ }
+ return result;
+}
+
+//if tol>0, only force continuity where the jump is smaller than tol.
+Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f, double tol, bool closed)
+{
+ if (f.size()==0) return f;
+ Piecewise<D2<SBasis> > result=f;
+ unsigned cur = (closed)? 0:1;
+ unsigned prev = (closed)? f.size()-1:0;
+ while(cur<f.size()){
+ Point pt0 = f.segs[prev].at1();
+ Point pt1 = f.segs[cur ].at0();
+ if (tol<=0 || L2sq(pt0-pt1)<tol*tol){
+ pt0 = (pt0+pt1)/2;
+ for (unsigned dim=0; dim<2; dim++){
+ SBasis &prev_sb=result.segs[prev][dim];
+ SBasis &cur_sb =result.segs[cur][dim];
+ Coord const c=pt0[dim];
+ if (prev_sb.isZero(0)) {
+ prev_sb = SBasis(Linear(0.0, c));
+ } else {
+ prev_sb[0][1] = c;
+ }
+ if (cur_sb.isZero(0)) {
+ cur_sb = SBasis(Linear(c, 0.0));
+ } else {
+ cur_sb[0][0] = c;
+ }
+ }
+ }
+ prev = cur++;
+ }
+ return result;
+}
+
+std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > >
+split_at_discontinuities (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwsbin, double tol)
+{
+ using namespace Geom;
+ std::vector<Piecewise<D2<SBasis> > > ret;
+ unsigned piece_start = 0;
+ for (unsigned i=0; i<pwsbin.segs.size(); i++){
+ if (i==(pwsbin.segs.size()-1) || L2(pwsbin.segs[i].at1()- pwsbin.segs[i+1].at0()) > tol){
+ Piecewise<D2<SBasis> > piece;
+ piece.cuts.push_back(pwsbin.cuts[piece_start]);
+ for (unsigned j = piece_start; j<i+1; j++){
+ piece.segs.push_back(pwsbin.segs[j]);
+ piece.cuts.push_back(pwsbin.cuts[j+1]);
+ }
+ ret.push_back(piece);
+ piece_start = i+1;
+ }
+ }
+ return ret;
+}
+
+Point unitTangentAt(D2<SBasis> const & a, Coord t, unsigned n)
+{
+ std::vector<Point> derivs = a.valueAndDerivatives(t, n);
+ for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) {
+ Coord length = derivs[deriv_n].length();
+ if ( ! are_near(length, 0) ) {
+ // length of derivative is non-zero, so return unit vector
+ return derivs[deriv_n] / length;
+ }
+ }
+ return Point (0,0);
+}
+
+static void set_first_point(Piecewise<D2<SBasis> > &f, Point const &a){
+ if ( f.empty() ){
+ f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y])))));
+ return;
+ }
+ for (unsigned dim=0; dim<2; dim++){
+ f.segs.front()[dim][0][0] = a[dim];
+ }
+}
+static void set_last_point(Piecewise<D2<SBasis> > &f, Point const &a){
+ if ( f.empty() ){
+ f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y])))));
+ return;
+ }
+ for (unsigned dim=0; dim<2; dim++){
+ f.segs.back()[dim][0][1] = a[dim];
+ }
+}
+
+std::vector<Piecewise<D2<SBasis> > > fuse_nearby_ends(std::vector<Piecewise<D2<SBasis> > > const &f, double tol){
+
+ if ( f.empty()) return f;
+ std::vector<Piecewise<D2<SBasis> > > result;
+ std::vector<std::vector<unsigned> > pre_result;
+ for (unsigned i=0; i<f.size(); i++){
+ bool inserted = false;
+ Point a = f[i].firstValue();
+ Point b = f[i].lastValue();
+ for (auto & j : pre_result){
+ Point aj = f.at(j.back()).lastValue();
+ Point bj = f.at(j.front()).firstValue();
+ if ( L2(a-aj) < tol ) {
+ j.push_back(i);
+ inserted = true;
+ break;
+ }
+ if ( L2(b-bj) < tol ) {
+ j.insert(j.begin(),i);
+ inserted = true;
+ break;
+ }
+ }
+ if (!inserted) {
+ pre_result.emplace_back();
+ pre_result.back().push_back(i);
+ }
+ }
+ for (auto & i : pre_result){
+ Piecewise<D2<SBasis> > comp;
+ for (unsigned j=0; j<i.size(); j++){
+ Piecewise<D2<SBasis> > new_comp = f.at(i[j]);
+ if ( j>0 ){
+ set_first_point( new_comp, comp.segs.back().at1() );
+ }
+ comp.concat(new_comp);
+ }
+ if ( L2(comp.firstValue()-comp.lastValue()) < tol ){
+ //TODO: check sizes!!!
+ set_last_point( comp, comp.segs.front().at0() );
+ }
+ result.push_back(comp);
+ }
+ return result;
+}
+
+/*
+ * Computes the intersection of two sets given as (ordered) union of intervals.
+ */
+static std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){
+ std::vector<Interval> result;
+ //TODO: use order!
+ for (auto i : a){
+ for (auto j : b){
+ OptInterval c( i );
+ c &= j;
+ if ( c ) {
+ result.push_back( *c );
+ }
+ }
+ }
+ return result;
+}
+
+std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){
+ std::vector<Rect> regions( 1, region );
+ return level_sets( f, regions ).front();
+}
+std::vector<Interval> level_set( D2<SBasis> const &f, Point p, double tol){
+ Rect region(p, p);
+ region.expandBy( tol );
+ return level_set( f, region );
+}
+std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Rect> regions){
+ std::vector<Interval> regsX (regions.size(), Interval() );
+ std::vector<Interval> regsY (regions.size(), Interval() );
+ for ( unsigned i=0; i < regions.size(); i++ ){
+ regsX[i] = regions[i][X];
+ regsY[i] = regions[i][Y];
+ }
+ std::vector<std::vector<Interval> > x_in_regs = level_sets( f[X], regsX );
+ std::vector<std::vector<Interval> > y_in_regs = level_sets( f[Y], regsY );
+ std::vector<std::vector<Interval> >result(regions.size(), std::vector<Interval>() );
+ for (unsigned i=0; i<regions.size(); i++){
+ result[i] = intersect ( x_in_regs[i], y_in_regs[i] );
+ }
+ return result;
+}
+std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Point> pts, double tol){
+ std::vector<Rect> regions( pts.size(), Rect() );
+ for (unsigned i=0; i<pts.size(); i++){
+ regions[i] = Rect( pts[i], pts[i] );
+ regions[i].expandBy( tol );
+ }
+ return level_sets( f, regions );
+}
+
+
+} // 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/src/2geom/doxygen.cpp b/src/2geom/doxygen.cpp
new file mode 100644
index 0000000..3c64eec
--- /dev/null
+++ b/src/2geom/doxygen.cpp
@@ -0,0 +1,301 @@
+/*
+ * Doxygen documentation for the lib2geom library
+ *
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2009-2011 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.
+ */
+
+// Main page of the documentation - contains logo and introductory text
+/**
+ * @mainpage
+ *
+ * @image html 2geom-logo.png
+ *
+ * @section Introduction
+ *
+ * 2Geom is a computational geometry library intended for use with 2D vector graphics.
+ * It concentrates on high-level algorithms, such as computing the length of a curve
+ * or Boolean operations on paths. It evolved from the geometry code used
+ * in Inkscape, a free software, cross-platform vector graphics editor.
+ *
+ * @section UserGuide User guide
+ *
+ * - @subpage Overview "Overview of 2Geom"
+ * - @ref Primitives "Primitives" - points, angles, lines, axis-aligned rectangles...
+ * - @ref Transforms "Transformations" - mathematical representation for operations
+ * like translation, scaling and rotation.
+ * - @ref Fragments "Fragments" - one-dimensional functions and related utilities.
+ * - @ref Curves "Curves" - functions mapping the unit interval to points on a plane.
+ * - @ref Shapes "Shapes" - circles, ellipses, polygons and the like.
+ * - @ref Paths "Paths" - sequences of contiguous curves, aka splines, and their processing.
+ * - @ref ShapeOps "Shape operations" - boolean algebra, offsets and other advanced operations.
+ * - @ref Containers "Geometric containers" - efficient ways to store and retrieve
+ * geometric information.
+ * - @ref Utilities "Utilities" - other useful code that does not fit under the above categories.
+ * - @subpage ReleaseNotes "Release notes" - what's new in 2Geom
+ *
+ * @section DeveloperInfo Developer information
+ *
+ * - @subpage CodingStandards "Coding standards used in 2Geom"
+ */
+
+// Overview subpage
+/**
+ * @page Overview Overview of 2Geom
+ *
+ * 2Geom has two APIs: a high level one, which uses virtual functions to allow handling
+ * objects of in a generic way without knowing their actual type at compile time,
+ * and a lower-level one based on templates, which is designed with performance in mind.
+ * For performance-critical tasks it may be necessary to use the lower level API.
+ *
+ * @section CoordSys Standard coordinate system
+ *
+ * 2Geom's standard coordinate system is common for computer graphics: the X axis grows
+ * to the right and the Y axis grows downwards. Angles start from the +X axis
+ * and grow towards the +Y axis (clockwise).
+ *
+ * @image html coords.png Standard coordinate system in 2Geom
+ *
+ * Most functions can be used without taking the coordinate system into account,
+ * as their interpretation is the same regardless of the coordinate system. However,
+ * a few of them depend on this definition, for example Rect's top() and bottom() methods.
+ *
+ * @section OpNote Operator note
+ *
+ * Most operators are provided by Boost operator helpers. This means that not all operators
+ * are defined in the class. For example, Rect only implements the operators
+ * +=, -= for points and *= for affines. The corresponding +, - and * operators
+ * are generated automatically by Boost.
+ */
+
+// RELEASE NOTES
+// Update this to describe the most important API changes.
+/**
+ * @page ReleaseNotes 2Geom release notes
+ *
+ * @section Ver04 Version 0.4
+ * - API additions:
+ * - Integer versions of Point, Interval and OptInterval, called
+ * IntPoint, IntInterval and OptIntInterval.
+ * - New geometric primitives: Angle and AngleInterval.
+ * - Major changes:
+ * - Matrix has been renamed to Affine.
+ * - Classification methods of Affine, for example Affine::isRotation(), will now
+ * return true for transforms that are close to identity. This is to reflect the
+ * fact that an identity transform can be interpreted as a rotation by zero
+ * degrees. To get the old behavior of returning false for identity, use
+ * methods prefixed with "Nonzero", e.g. Affine::isNonzeroRotation().
+ * - EllipticalArc and SVGEllipticalArc have been merged. Now there is only the former.
+ * All arcs are SVG-compliant.
+ * - Minor changes:
+ * - Affine::without_translation() is now called Affine::withoutTranslation().
+ * - Interval::strict_contains() is now called Interval::interiorContains().
+ * The same change has been made for Rect.
+ * - Some unclear and unused operators of D2 were removed, for instance D2 * Point.
+ * - Interval is now a derived class of a GenericInterval template.
+ * - Rect is no longer a D2 specialization.
+ * - isnan.h merged with math-utils.h.
+ * @section Ver03 Version 0.3
+ * - release notes were started after this version.
+ */
+
+/**
+ * @page CodingStandards Coding standards and conventions used in 2Geom
+ *
+ * @section Filenames
+ *
+ * Files and directories should be all lowercase. Words should be separated with hyphens (-).
+ * Underscores, capital letters and non-ASCII characters should not be used.
+ *
+ * @section Indenting
+ *
+ * All files should use 4 spaces as indentation.
+ *
+ * @section Namespaces
+ *
+ * All classes intended for direct use by the end users should be in the Geom namespace.
+ * Contents of namespaces should not be indented. Closing brace of a namespace
+ * should have a comment indicating which namespace it is closing.
+ * @code
+ namespace Geom {
+ namespace FooInternal {
+
+ unsigned some_function()
+ {
+ // ...code...
+ }
+
+ } // namespace FooInternal
+ } // namespace Geom
+ @endcode
+ *
+ * @section Classes
+ *
+ * @code
+ // superclass list should use Boost notation,
+ // especially if there is more than one.
+ class Foo
+ : public Bar
+ , public Baz
+ {
+ // constructors should use Boost notation if the class has superclasses.
+ Foo(int a)
+ : Bar(a)
+ , Baz(b)
+ {
+ // constructor body
+ }
+ Foo(int a) {
+ // constructor with default initialization of superclasses
+ }
+
+ // methods use camelCaseNames.
+ // one-line methods can be collapsed.
+ bool isActive() { return _blurp; }
+ // multi-line methods have the opening brace on the same line.
+ void invert() {
+ // ... code ...
+ }
+
+ // static functions use lowercase_with_underscores.
+ // static factory functions should be called from_something.
+ static Foo from_point(Point const &p) {
+ // ...
+ }
+ }; // end of class Foo
+
+ // Closing brace of a class should have the above comment, unless it's very short.
+ @endcode
+ *
+ * @section FreeFuns Free functions
+ *
+ * Functions should use lowercase_with_underscores names. The opening brace of
+ * the definition should be on a separate line.
+ *
+ * @section InlineInClasses When to use inline
+ *
+ * The "inline" keyword is not required when the body of the function is given
+ * in the definition of the class. Do not mark such functions inline, because
+ * they are automatically marked as inline by the compiler. It is only
+ * necessary to use the inline keyword when the body of the function is given
+ * after the class definition.
+ */
+
+// Documentation for groups
+/**
+ * @defgroup Transforms Affine transformations
+ * @brief Transformations of the plane such as rotation and scaling
+ *
+ * Each transformation class represent a set of affine transforms that is closed
+ * under multiplication. Those are translation, scaling, rotation, horizontal shearing
+ * and vertical shearing. Any affine transform can be obtained by combining those
+ * basic operations.
+ *
+ * Each of the transforms can be applied to points and matrices (using multiplication).
+ * Each can also be converted into a matrix (which can represent any composition
+ * of transforms generically). All (except translation) use the origin (0,0) as the invariant
+ * point (e.g. one that stays in the same place after applying the transform to the plane).
+ * To obtain transforms with different invariant points, combine them with translation to
+ * and back from the origin. For example, to get a 60 degree rotation around the point @a p:
+ * @code Affine rot_around_p = Translate(-p) * Rotate::from_degrees(60) * Translate(p); @endcode
+ *
+ * Multiplication of transforms is associative: the result of an expression involving
+ * points and matrices is the same regardless of the order of evaluating multiplications.
+ *
+ * If you need to transform a complicated object
+ * by A, then B, and then C, you should first compute the total transform and apply it to the
+ * object in one go. This way instead of performing 3 expensive operations, you will only do
+ * two very fast matrix multiplications and one complex transformation. Here is an example:
+ * @code
+ transformed_path = long_path * A * B * C; // wrong! long_path will be transformed 3 times.
+ transformed_path = long_path * (A * B * C); // good! long_path will be transformed only once.
+ Affine total = A * B * C; // you can store the transform to apply it to several objects.
+ transformed_path = long_path * total; // good!
+ @endcode
+ * Ordering note: if you compose transformations via multiplication, they are applied
+ * from left to right. If you write <code> ptrans = p * A * B * C * D;</code>, then it means
+ * that @a ptrans is obtained from @a p by first transforming it by A, then by B, then by C,
+ * and finally by D. This is a consequence of interpreting points as row vectors, instead
+ * of the more common column vector interpretation; 2Geom's choice leads to more intuitive
+ * notation.
+ */
+
+/**
+ * @defgroup Primitives Primitives
+ * @brief Basic mathematical objects such as intervals and points
+ *
+ * 2Geom has several basic geometrical objects: points, lines, intervals, angles,
+ * and others. Most of those objects can be treated as sets of points or numbers
+ * satisfying some equation or as functions.
+ */
+
+/**
+ * @defgroup Fragments Fragments and related classes
+ * @brief 1D functions on the unit interval
+ *
+ * Each type of fragments represents one of the various ways in which a function from
+ * the unit interval to the real line may be given. These are the most important mathematical
+ * primitives in 2Geom.
+ */
+
+/**
+ * @defgroup Curves Curves
+ * @brief Functions mapping the unit interval to a plane
+ *
+ * Curves are functions \f$\mathbf{C}: [0, 1] \to \mathbb{R}^2\f$. For details, see
+ * the documentation for the Curve class. All curves can be included in paths and path sequences.
+ */
+
+/**
+ * @defgroup Shapes Basic shapes
+ * @brief Circles, ellipes, polygons...
+ *
+ * Among the shapes supported by 2Geom are circles, ellipses and polygons.
+ * Polygons can also be represented by paths containing only linear segments.
+ */
+
+/**
+ * @defgroup Paths Paths and path sequences
+ * @brief Sequences of contiguous curves, aka splines, and their processing
+ */
+
+/**
+ * @defgroup Utilities Miscellaneous utilities
+ * @brief Useful code that does not fit under other categories.
+ */
+
+/*
+ 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/src/2geom/ellipse.cpp b/src/2geom/ellipse.cpp
new file mode 100644
index 0000000..42cb36d
--- /dev/null
+++ b/src/2geom/ellipse.cpp
@@ -0,0 +1,790 @@
+/** @file
+ * @brief Ellipse shape
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2008-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 <2geom/conicsec.h>
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+namespace Geom {
+
+Ellipse::Ellipse(Geom::Circle const &c)
+ : _center(c.center())
+ , _rays(c.radius(), c.radius())
+ , _angle(0)
+{}
+
+void Ellipse::setCoefficients(double A, double B, double C, double D, double E, double F)
+{
+ double den = 4*A*C - B*B;
+ if (den == 0) {
+ THROW_RANGEERROR("den == 0, while computing ellipse centre");
+ }
+ _center[X] = (B*E - 2*C*D) / den;
+ _center[Y] = (B*D - 2*A*E) / den;
+
+ // evaluate the a coefficient of the ellipse equation in normal form
+ // E(x,y) = a*(x-cx)^2 + b*(x-cx)*(y-cy) + c*(y-cy)^2 = 1
+ // where b = a*B , c = a*C, (cx,cy) == centre
+ double num = A * sqr(_center[X])
+ + B * _center[X] * _center[Y]
+ + C * sqr(_center[Y])
+ - F;
+
+
+ //evaluate ellipse rotation angle
+ _angle = std::atan2( -B, -(A - C) )/2;
+
+ // evaluate the length of the ellipse rays
+ double sinrot, cosrot;
+ sincos(_angle, sinrot, cosrot);
+ double cos2 = cosrot * cosrot;
+ double sin2 = sinrot * sinrot;
+ double cossin = cosrot * sinrot;
+
+ den = A * cos2 + B * cossin + C * sin2;
+ if (den == 0) {
+ THROW_RANGEERROR("den == 0, while computing 'rx' coefficient");
+ }
+ double rx2 = num / den;
+ if (rx2 < 0) {
+ THROW_RANGEERROR("rx2 < 0, while computing 'rx' coefficient");
+ }
+ _rays[X] = std::sqrt(rx2);
+
+ den = C * cos2 - B * cossin + A * sin2;
+ if (den == 0) {
+ THROW_RANGEERROR("den == 0, while computing 'ry' coefficient");
+ }
+ double ry2 = num / den;
+ if (ry2 < 0) {
+ THROW_RANGEERROR("ry2 < 0, while computing 'rx' coefficient");
+ }
+ _rays[Y] = std::sqrt(ry2);
+
+ // the solution is not unique so we choose always the ellipse
+ // with a rotation angle between 0 and PI/2
+ makeCanonical();
+}
+
+Point Ellipse::initialPoint() const
+{
+ Coord sinrot, cosrot;
+ sincos(_angle, sinrot, cosrot);
+ Point p(ray(X) * cosrot + center(X), ray(X) * sinrot + center(Y));
+ return p;
+}
+
+
+Affine Ellipse::unitCircleTransform() const
+{
+ Affine ret = Scale(ray(X), ray(Y)) * Rotate(_angle);
+ ret.setTranslation(center());
+ return ret;
+}
+
+Affine Ellipse::inverseUnitCircleTransform() const
+{
+ if (ray(X) == 0 || ray(Y) == 0) {
+ THROW_RANGEERROR("a degenerate ellipse doesn't have an inverse unit circle transform");
+ }
+ Affine ret = Translate(-center()) * Rotate(-_angle) * Scale(1/ray(X), 1/ray(Y));
+ return ret;
+}
+
+
+LineSegment Ellipse::axis(Dim2 d) const
+{
+ Point a(0, 0), b(0, 0);
+ a[d] = -1;
+ b[d] = 1;
+ LineSegment ls(a, b);
+ ls.transform(unitCircleTransform());
+ return ls;
+}
+
+LineSegment Ellipse::semiaxis(Dim2 d, int sign) const
+{
+ Point a(0, 0), b(0, 0);
+ b[d] = sgn(sign);
+ LineSegment ls(a, b);
+ ls.transform(unitCircleTransform());
+ return ls;
+}
+
+Rect Ellipse::boundsExact() const
+{
+ auto const trans = unitCircleTransform();
+
+ auto proj_bounds = [&] (Dim2 d) {
+ // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4]
+ // in the coordinate system where the ellipse is a unit circle. We compute its range of
+ // values on the unit circle.
+ auto const r = std::hypot(trans[d], trans[d + 2]);
+ auto const mid = trans[d + 4];
+ return Interval(mid - r, mid + r);
+ };
+
+ return { proj_bounds(X), proj_bounds(Y) };
+}
+
+Rect Ellipse::boundsFast() const
+{
+ // Every ellipse is contained in the circle with the same center and radius
+ // equal to the larger of the two rays. We return the bounding square
+ // of this circle (this is really fast but only exact for circles).
+ auto const larger_ray = (ray(X) > ray(Y) ? ray(X) : ray(Y));
+ assert(larger_ray >= 0.0);
+ auto const rr = Point(larger_ray, larger_ray);
+ return Rect(_center - rr, _center + rr);
+}
+
+std::vector<double> Ellipse::coefficients() const
+{
+ std::vector<double> c(6);
+ coefficients(c[0], c[1], c[2], c[3], c[4], c[5]);
+ return c;
+}
+
+void Ellipse::coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const
+{
+ if (ray(X) == 0 || ray(Y) == 0) {
+ THROW_RANGEERROR("a degenerate ellipse doesn't have an implicit form");
+ }
+
+ double cosrot, sinrot;
+ sincos(_angle, sinrot, cosrot);
+ double cos2 = cosrot * cosrot;
+ double sin2 = sinrot * sinrot;
+ double cossin = cosrot * sinrot;
+ double invrx2 = 1 / (ray(X) * ray(X));
+ double invry2 = 1 / (ray(Y) * ray(Y));
+
+ A = invrx2 * cos2 + invry2 * sin2;
+ B = 2 * (invrx2 - invry2) * cossin;
+ C = invrx2 * sin2 + invry2 * cos2;
+ D = -2 * A * center(X) - B * center(Y);
+ E = -2 * C * center(Y) - B * center(X);
+ F = A * center(X) * center(X)
+ + B * center(X) * center(Y)
+ + C * center(Y) * center(Y)
+ - 1;
+}
+
+
+void Ellipse::fit(std::vector<Point> const &points)
+{
+ size_t sz = points.size();
+ if (sz < 5) {
+ THROW_RANGEERROR("fitting error: too few points passed");
+ }
+ NL::LFMEllipse model;
+ NL::least_squeares_fitter<NL::LFMEllipse> fitter(model, sz);
+
+ for (size_t i = 0; i < sz; ++i) {
+ fitter.append(points[i]);
+ }
+ fitter.update();
+
+ NL::Vector z(sz, 0.0);
+ model.instance(*this, fitter.result(z));
+}
+
+
+EllipticalArc *
+Ellipse::arc(Point const &ip, Point const &inner, Point const &fp)
+{
+ // This is resistant to degenerate ellipses:
+ // both flags evaluate to false in that case.
+
+ bool large_arc_flag = false;
+ bool sweep_flag = false;
+
+ // Determination of large arc flag:
+ // large_arc is false when the inner point is on the same side
+ // of the center---initial point line as the final point, AND
+ // is on the same side of the center---final point line as the
+ // initial point.
+ // Additionally, large_arc is always false when we have exactly
+ // 1/2 of an arc, i.e. the cross product of the center -> initial point
+ // and center -> final point vectors is zero.
+ // Negating the above leads to the condition for large_arc being true.
+ Point fv = fp - _center;
+ Point iv = ip - _center;
+ Point innerv = inner - _center;
+ double ifcp = cross(fv, iv);
+
+ if (ifcp != 0 && (sgn(cross(fv, innerv)) != sgn(ifcp) ||
+ sgn(cross(iv, innerv)) != sgn(-ifcp)))
+ {
+ large_arc_flag = true;
+ }
+
+ // Determination of sweep flag:
+ // For clarity, let's assume that Y grows up. Then the cross product
+ // is positive for points on the left side of a vector and negative
+ // on the right side of a vector.
+ //
+ // cross(?, v) > 0
+ // o------------------->
+ // cross(?, v) < 0
+ //
+ // If the arc is small (large_arc_flag is false) and the final point
+ // is on the right side of the vector initial point -> center,
+ // we have to go in the direction of increasing angles
+ // (counter-clockwise) and the sweep flag is true.
+ // If the arc is large, the opposite is true, since we have to reach
+ // the final point going the long way - in the other direction.
+ // We can express this observation as:
+ // cross(_center - ip, fp - _center) < 0 xor large_arc flag
+ // This is equal to:
+ // cross(-iv, fv) < 0 xor large_arc flag
+ // But cross(-iv, fv) is equal to cross(fv, iv) due to antisymmetry
+ // of the cross product, so we end up with the condition below.
+ if ((ifcp < 0) ^ large_arc_flag) {
+ sweep_flag = true;
+ }
+
+ EllipticalArc *ret_arc = new EllipticalArc(ip, ray(X), ray(Y), rotationAngle(),
+ large_arc_flag, sweep_flag, fp);
+ return ret_arc;
+}
+
+Ellipse &Ellipse::operator*=(Rotate const &r)
+{
+ _angle += r.angle();
+ _center *= r;
+ return *this;
+}
+
+Ellipse &Ellipse::operator*=(Affine const& m)
+{
+ Affine a = Scale(ray(X), ray(Y)) * Rotate(_angle);
+ Affine mwot = m.withoutTranslation();
+ Affine am = a * mwot;
+ Point new_center = _center * m;
+
+ if (are_near(am.descrim(), 0)) {
+ double angle;
+ if (am[0] != 0) {
+ angle = std::atan2(am[2], am[0]);
+ } else if (am[1] != 0) {
+ angle = std::atan2(am[3], am[1]);
+ } else {
+ angle = M_PI/2;
+ }
+ Point v = Point::polar(angle) * am;
+ _center = new_center;
+ _rays[X] = L2(v);
+ _rays[Y] = 0;
+ _angle = atan2(v);
+ return *this;
+ } else if (mwot.isScale(0) && _angle.radians() == 0) {
+ _rays[X] *= std::abs(mwot[0]);
+ _rays[Y] *= std::abs(mwot[3]);
+ _center = new_center;
+ return *this;
+ }
+
+ std::vector<double> coeff = coefficients();
+ Affine q( coeff[0], coeff[1]/2,
+ coeff[1]/2, coeff[2],
+ 0, 0 );
+
+ Affine invm = mwot.inverse();
+ q = invm * q ;
+ std::swap(invm[1], invm[2]);
+ q *= invm;
+ setCoefficients(q[0], 2*q[1], q[3], 0, 0, -1);
+ _center = new_center;
+
+ return *this;
+}
+
+Ellipse Ellipse::canonicalForm() const
+{
+ Ellipse result(*this);
+ result.makeCanonical();
+ return result;
+}
+
+void Ellipse::makeCanonical()
+{
+ if (_rays[X] == _rays[Y]) {
+ _angle = 0;
+ return;
+ }
+
+ if (_angle < 0) {
+ _angle += M_PI;
+ }
+ if (_angle >= M_PI/2) {
+ std::swap(_rays[X], _rays[Y]);
+ _angle -= M_PI/2;
+ }
+}
+
+Point Ellipse::pointAt(Coord t) const
+{
+ Point p = Point::polar(t);
+ p *= unitCircleTransform();
+ return p;
+}
+
+Coord Ellipse::valueAt(Coord t, Dim2 d) const
+{
+ Coord sinrot, cosrot, cost, sint;
+ sincos(rotationAngle(), sinrot, cosrot);
+ sincos(t, sint, cost);
+
+ if ( d == X ) {
+ return ray(X) * cosrot * cost
+ - ray(Y) * sinrot * sint
+ + center(X);
+ } else {
+ return ray(X) * sinrot * cost
+ + ray(Y) * cosrot * sint
+ + center(Y);
+ }
+}
+
+Coord Ellipse::timeAt(Point const &p) const
+{
+ // degenerate ellipse is basically a reparametrized line segment
+ if (ray(X) == 0 || ray(Y) == 0) {
+ if (ray(X) != 0) {
+ return asin(Line(axis(X)).timeAt(p));
+ } else if (ray(Y) != 0) {
+ return acos(Line(axis(Y)).timeAt(p));
+ } else {
+ return 0;
+ }
+ }
+ Affine iuct = inverseUnitCircleTransform();
+ return Angle(atan2(p * iuct)).radians0(); // return a value in [0, 2pi)
+}
+
+Point Ellipse::unitTangentAt(Coord t) const
+{
+ Point p = Point::polar(t + M_PI/2);
+ p *= unitCircleTransform().withoutTranslation();
+ p.normalize();
+ return p;
+}
+
+bool Ellipse::contains(Point const &p) const
+{
+ Point tp = p * inverseUnitCircleTransform();
+ return tp.length() <= 1;
+}
+
+/** @brief Convert curve time on the major axis to the corresponding angle
+ * parameters on a degenerate ellipse collapsed onto that axis.
+ * @param t The curve time on the major axis of an ellipse.
+ * @param vertical If true, the major axis goes from angle -π/2 to +π/2;
+ * otherwise, the major axis connects angles π and 0.
+ * @return The two angles at which the collapsed ellipse passes through the
+ * major axis point corresponding to the given time \f$t \in [0, 1]\f$.
+ */
+static std::array<Coord, 2> axis_time_to_angles(Coord t, bool vertical)
+{
+ Coord const to_unit = std::clamp(2.0 * t - 1.0, -1.0, 1.0);
+ if (vertical) {
+ double const arcsin = std::asin(to_unit);
+ return {arcsin, M_PI - arcsin};
+ } else {
+ double const arccos = std::acos(to_unit);
+ return {arccos, -arccos};
+ }
+}
+
+/** @brief For each intersection of some shape with the major axis of an ellipse, produce one or two
+ * intersections of a degenerate ellipse (collapsed onto that axis) with the same shape.
+ *
+ * @param axis_intersections The intersections of some shape with the major axis.
+ * @param vertical Whether this is the vertical major axis (in the ellipse's natural coordinates).
+ * @return A vector with doubled intersections (corresponding to the two passages of the squashed
+ * ellipse through that point) and swapped order of the intersected shapes.
+*/
+static std::vector<ShapeIntersection> double_axis_intersections(std::vector<ShapeIntersection> &&axis_intersections,
+ bool vertical)
+{
+ if (axis_intersections.empty()) {
+ return {};
+ }
+ std::vector<ShapeIntersection> result;
+ result.reserve(2 * axis_intersections.size());
+
+ for (auto const &x : axis_intersections) {
+ for (auto a : axis_time_to_angles(x.second, vertical)) {
+ result.emplace_back(a, x.first, x.point()); // Swap first <-> converted second.
+ if (x.second == 0.0 || x.second == 1.0) {
+ break; // Do not double up endpoint intersections.
+ }
+ }
+ }
+ return result;
+}
+
+std::vector<ShapeIntersection> Ellipse::intersect(Line const &line) const
+{
+ std::vector<ShapeIntersection> result;
+
+ if (line.isDegenerate()) {
+ return result;
+ }
+ if (ray(X) == 0 || ray(Y) == 0) {
+ return double_axis_intersections(line.intersect(majorAxis()), ray(X) == 0);
+ }
+
+ // Ax^2 + Bxy + Cy^2 + Dx + Ey + F
+ std::array<Coord, 6> coeffs;
+ coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]);
+ rescale_homogenous(coeffs);
+ auto [A, B, C, D, E, F] = coeffs;
+ Affine iuct = inverseUnitCircleTransform();
+
+ // generic case
+ std::array<Coord, 3> line_coeffs;
+ line.coefficients(line_coeffs[0], line_coeffs[1], line_coeffs[2]);
+ rescale_homogenous(line_coeffs);
+ auto [a, b, c] = line_coeffs;
+ Point lv = line.vector();
+
+ if (fabs(lv[X]) > fabs(lv[Y])) {
+ // y = -a/b x - c/b
+ Coord q = -a/b;
+ Coord r = -c/b;
+
+ // substitute that into the ellipse equation, making it quadratic in x
+ Coord I = A + B*q + C*q*q; // x^2 terms
+ Coord J = B*r + C*2*q*r + D + E*q; // x^1 terms
+ Coord K = C*r*r + E*r + F; // x^0 terms
+ std::vector<Coord> xs = solve_quadratic(I, J, K);
+
+ for (double x : xs) {
+ Point p(x, q*x + r);
+ result.emplace_back(atan2(p * iuct), line.timeAt(p), p);
+ }
+ } else {
+ Coord q = -b/a;
+ Coord r = -c/a;
+
+ Coord I = A*q*q + B*q + C;
+ Coord J = A*2*q*r + B*r + D*q + E;
+ Coord K = A*r*r + D*r + F;
+ std::vector<Coord> xs = solve_quadratic(I, J, K);
+
+ for (double x : xs) {
+ Point p(q*x + r, x);
+ result.emplace_back(atan2(p * iuct), line.timeAt(p), p);
+ }
+ }
+ return result;
+}
+
+std::vector<ShapeIntersection> Ellipse::intersect(LineSegment const &seg) const
+{
+ if (!boundsFast().intersects(seg.boundsFast())) {
+ return {};
+ }
+
+ // We simply reuse the procedure for lines and filter out
+ // results where the line time value is outside of the unit interval,
+ // but we apply a small tolerance to account for numerical errors.
+ double const param_prec = EPSILON / seg.length(0.0);
+ // TODO: accept a precision setting instead of always using EPSILON
+ // (requires an ABI break).
+
+ auto xings = intersect(Line(seg));
+ if (xings.empty()) {
+ return xings;
+ }
+ decltype(xings) result;
+ result.reserve(xings.size());
+
+ for (auto const &x : xings) {
+ if (x.second < -param_prec || x.second > 1.0 + param_prec) {
+ continue;
+ }
+ result.emplace_back(x.first, std::clamp(x.second, 0.0, 1.0), x.point());
+ }
+ return result;
+}
+
+std::vector<ShapeIntersection> Ellipse::intersect(Ellipse const &other) const
+{
+ // Handle degenerate cases first.
+ if (ray(X) == 0 || ray(Y) == 0) { // Degenerate ellipse, collapsed to the major axis.
+ return double_axis_intersections(other.intersect(majorAxis()), ray(X) == 0);
+ }
+ if (*this == other) { // Two identical ellipses.
+ THROW_INFINITELY_MANY_SOLUTIONS("The two ellipses are identical.");
+ }
+ if (!boundsFast().intersects(other.boundsFast())) {
+ return {};
+ }
+
+ // Find coefficients of the implicit equations of the two ellipses and rescale
+ // them (losslessly) for better numerical conditioning.
+ std::array<double, 6> coeffs;
+ coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]);
+ rescale_homogenous(coeffs);
+ auto [A, B, C, D, E, F] = coeffs;
+
+ std::array<double, 6> otheffs;
+ other.coefficients(otheffs[0], otheffs[1], otheffs[2], otheffs[3], otheffs[4], otheffs[5]);
+ rescale_homogenous(otheffs);
+ auto [a, b, c, d, e, f] = otheffs;
+
+ // Assume that Q(x, y) = 0 is the ellipse equation given by uppercase letters
+ // and R(x, y) = 0 is the equation given by lowercase ones.
+ // In other words, Q is the quadratic function describing this ellipse and
+ // R is the quadratic function for the other ellipse.
+ //
+ // A point (x, y) is common to both ellipses if and only if it solves the system
+ // { Q(x, y) = 0,
+ // { R(x, y) = 0.
+ //
+ // If µ is any real number, we can multiply the first equation by µ and add that
+ // to the first equation, obtaining the new system of equations:
+ // { Q(x, y) = 0,
+ // { µQ(x, y) + R(x, y) = 0.
+ //
+ // The first equation still says that (x, y) is a point on this ellipse, but the
+ // second equation uses the new expression (µQ + R) instead of the original R.
+ //
+ // Why do we do this? The reason is that the set of functions {µQ + R : µ real}
+ // is a "real system of conics" and there's a theorem which guarantees that such a system
+ // always contains a "degenerate conic" [proof below].
+ // In practice, the degenerate conic will describe a line or a pair of lines, and intersecting
+ // a line with an ellipse is much easier than intersecting two ellipses directly.
+ //
+ // But in order to be able to do this, we must find a value of µ for which µQ + R is degenerate.
+ // We can write the expression (µQ + R)(x, y) in the following way:
+ //
+ // | aa bb/2 dd/2 | |x|
+ // (µQ + R)(x, y) = [x y 1] | bb/2 cc ee/2 | |y|
+ // | dd/2 ee/2 ff | |1|
+ //
+ // where aa = µA + a and so on. The determinant can be explicitly written out,
+ // giving an equation which is cubic in µ and can be solved analytically.
+ // The conic µQ + R is degenerate if and only if this determinant is 0.
+ //
+ // Proof that there's always a degenerate conic: a cubic real polynomial always has a root,
+ // and if the polynomial in µ isn't cubic (coefficient of µ^3 is zero), then the starting
+ // conic is already degenerate.
+
+ Coord I, J, K, L; // Coefficients of µ in the expression for the determinant.
+ I = (-B*B*F + 4*A*C*F + D*E*B - A*E*E - C*D*D) / 4;
+ J = -((B*B - 4*A*C) * f + (2*B*F - D*E) * b + (2*A*E - D*B) * e +
+ (2*C*D - E*B) * d + (D*D - 4*A*F) * c + (E*E - 4*C*F) * a) / 4;
+ K = -((b*b - 4*a*c) * F + (2*b*f - d*e) * B + (2*a*e - d*b) * E +
+ (2*c*d - e*b) * D + (d*d - 4*a*f) * C + (e*e - 4*c*f) * A) / 4;
+ L = (-b*b*f + 4*a*c*f + d*e*b - a*e*e - c*d*d) / 4;
+
+ std::vector<Coord> mus = solve_cubic(I, J, K, L);
+ Coord mu = infinity();
+
+ // Now that we have solved for µ, we need to check whether the conic
+ // determined by µQ + R is reducible to a product of two lines. If it's not,
+ // it means that there are no intersections. If it is, the intersections of these
+ // lines with the original ellipses (if there are any) give the coordinates
+ // of intersections.
+
+ // Prefer middle root if there are three.
+ // Out of three possible pairs of lines that go through four points of intersection
+ // of two ellipses, this corresponds to cross-lines. These intersect the ellipses
+ // at less shallow angles than the other two options.
+ if (mus.size() == 3) {
+ std::swap(mus[1], mus[0]);
+ }
+ /// Discriminant within this radius of 0 will be considered zero.
+ static Coord const discriminant_precision = 1e-10;
+
+ for (Coord candidate_mu : mus) {
+ Coord const aa = std::fma(candidate_mu, A, a);
+ Coord const bb = std::fma(candidate_mu, B, b);
+ Coord const cc = std::fma(candidate_mu, C, c);
+ Coord const delta = sqr(bb) - 4*aa*cc;
+ if (delta < -discriminant_precision) {
+ continue;
+ }
+ mu = candidate_mu;
+ break;
+ }
+
+ // if no suitable mu was found, there are no intersections
+ if (mu == infinity()) {
+ return {};
+ }
+
+ // Create the degenerate conic and decompose it into lines.
+ std::array<double, 6> degen = {std::fma(mu, A, a), std::fma(mu, B, b), std::fma(mu, C, c),
+ std::fma(mu, D, d), std::fma(mu, E, e), std::fma(mu, F, f)};
+ rescale_homogenous(degen);
+ auto const lines = xAx(degen[0], degen[1], degen[2],
+ degen[3], degen[4], degen[5]).decompose_df(discriminant_precision);
+
+ // intersect with the obtained lines and report intersections
+ std::vector<ShapeIntersection> result;
+ for (auto const &line : lines) {
+ if (line.isDegenerate()) {
+ continue;
+ }
+ auto as = intersect(line);
+ // NOTE: If we only cared about the intersection points, we could simply
+ // intersect this ellipse with the lines and ignore the other ellipse.
+ // But we need the time coordinates on the other ellipse as well.
+ auto bs = other.intersect(line);
+ if (as.empty() || bs.empty()) {
+ continue;
+ }
+ // Due to numerical errors, a tangency may sometimes be found as 1 intersection
+ // on one ellipse and 2 intersections on the other. If this happens, we average
+ // the points of the two intersections.
+ auto const intersection_average = [](ShapeIntersection const &i,
+ ShapeIntersection const &j) -> ShapeIntersection
+ {
+ return ShapeIntersection(i.first, j.first, middle_point(i.point(), j.point()));
+ };
+ auto const synthesize_intersection = [&](ShapeIntersection const &i,
+ ShapeIntersection const &j) -> void
+ {
+ result.emplace_back(i.first, j.first, middle_point(i.point(), j.point()));
+ };
+ if (as.size() == 2) {
+ if (bs.size() == 2) {
+ synthesize_intersection(as[0], bs[0]);
+ synthesize_intersection(as[1], bs[1]);
+ } else if (bs.size() == 1) {
+ synthesize_intersection(intersection_average(as[0], as[1]), bs[0]);
+ }
+ } else if (as.size() == 1) {
+ if (bs.size() == 2) {
+ synthesize_intersection(as[0], intersection_average(bs[0], bs[1]));
+ } else if (bs.size() == 1) {
+ synthesize_intersection(as[0], bs[0]);
+ }
+ }
+ }
+ return result;
+}
+
+std::vector<ShapeIntersection> Ellipse::intersect(D2<Bezier> const &b) const
+{
+ Coord A, B, C, D, E, F;
+ coefficients(A, B, C, D, E, F);
+
+ // We plug the X and Y curves into the implicit equation and solve for t.
+ Bezier x = A*b[X]*b[X] + B*b[X]*b[Y] + C*b[Y]*b[Y] + D*b[X] + E*b[Y] + F;
+ std::vector<Coord> r = x.roots();
+
+ std::vector<ShapeIntersection> result;
+ for (double & i : r) {
+ Point p = b.valueAt(i);
+ result.emplace_back(timeAt(p), i, p);
+ }
+ return result;
+}
+
+bool Ellipse::operator==(Ellipse const &other) const
+{
+ if (_center != other._center) return false;
+
+ Ellipse a = this->canonicalForm();
+ Ellipse b = other.canonicalForm();
+
+ if (a._rays != b._rays) return false;
+ if (a._angle != b._angle) return false;
+
+ return true;
+}
+
+
+bool are_near(Ellipse const &a, Ellipse const &b, Coord precision)
+{
+ // We want to know whether no point on ellipse a is further than precision
+ // from the corresponding point on ellipse b. To check this, we compute
+ // the four extreme points at the end of each ray for each ellipse
+ // and check whether they are sufficiently close.
+
+ // First, we need to correct the angles on the ellipses, so that they are
+ // no further than M_PI/4 apart. This can always be done by rotating
+ // and exchanging axes.
+ Ellipse ac = a, bc = b;
+ if (distance(ac.rotationAngle(), bc.rotationAngle()).radians0() >= M_PI/2) {
+ ac.setRotationAngle(ac.rotationAngle() + M_PI);
+ }
+ if (distance(ac.rotationAngle(), bc.rotationAngle()) >= M_PI/4) {
+ Angle d1 = distance(ac.rotationAngle() + M_PI/2, bc.rotationAngle());
+ Angle d2 = distance(ac.rotationAngle() - M_PI/2, bc.rotationAngle());
+ Coord adj = d1.radians0() < d2.radians0() ? M_PI/2 : -M_PI/2;
+ ac.setRotationAngle(ac.rotationAngle() + adj);
+ ac.setRays(ac.ray(Y), ac.ray(X));
+ }
+
+ // Do the actual comparison by computing four points on each ellipse.
+ Point tps[] = {Point(1,0), Point(0,1), Point(-1,0), Point(0,-1)};
+ for (auto & tp : tps) {
+ if (!are_near(tp * ac.unitCircleTransform(),
+ tp * bc.unitCircleTransform(),
+ precision))
+ return false;
+ }
+ return true;
+}
+
+std::ostream &operator<<(std::ostream &out, Ellipse const &e)
+{
+ out << "Ellipse(" << e.center() << ", " << e.rays()
+ << ", " << format_coord_nice(e.rotationAngle()) << ")";
+ return out;
+}
+
+} // 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/src/2geom/elliptical-arc-from-sbasis.cpp b/src/2geom/elliptical-arc-from-sbasis.cpp
new file mode 100644
index 0000000..c536d89
--- /dev/null
+++ b/src/2geom/elliptical-arc-from-sbasis.cpp
@@ -0,0 +1,341 @@
+/** @file
+ * @brief Fitting elliptical arc to SBasis
+ *
+ * This file contains the implementation of the function arc_from_sbasis.
+ *//*
+ * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * 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/curve.h>
+#include <2geom/angle.h>
+#include <2geom/utils.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/sbasis-curve.h> // for non-native methods
+#include <2geom/numeric/vector.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+#include <algorithm>
+
+namespace Geom {
+
+// forward declaration
+namespace detail
+{
+ struct ellipse_equation;
+}
+
+/*
+ * make_elliptical_arc
+ *
+ * convert a parametric polynomial curve given in symmetric power basis form
+ * into an EllipticalArc type; in order to be successful the input curve
+ * has to look like an actual elliptical arc even if a certain tolerance
+ * is allowed through an ad-hoc parameter.
+ * The conversion is performed through an interpolation on a certain amount of
+ * sample points computed on the input curve;
+ * the interpolation computes the coefficients of the general implicit equation
+ * of an ellipse (A*X^2 + B*XY + C*Y^2 + D*X + E*Y + F = 0), then from the
+ * implicit equation we compute the parametric form.
+ *
+ */
+class make_elliptical_arc
+{
+ public:
+ typedef D2<SBasis> curve_type;
+
+ /*
+ * constructor
+ *
+ * it doesn't execute the conversion but set the input and output parameters
+ *
+ * _ea: the output EllipticalArc that will be generated;
+ * _curve: the input curve to be converted;
+ * _total_samples: the amount of sample points to be taken
+ * on the input curve for performing the conversion
+ * _tolerance: how much likelihood is required between the input curve
+ * and the generated elliptical arc; the smaller it is the
+ * the tolerance the higher it is the likelihood.
+ */
+ make_elliptical_arc( EllipticalArc& _ea,
+ curve_type const& _curve,
+ unsigned int _total_samples,
+ double _tolerance );
+
+ private:
+ bool bound_exceeded( unsigned int k, detail::ellipse_equation const & ee,
+ double e1x, double e1y, double e2 );
+
+ bool check_bound(double A, double B, double C, double D, double E, double F);
+
+ void fit();
+
+ bool make_elliptiarc();
+
+ void print_bound_error(unsigned int k)
+ {
+ std::cerr
+ << "tolerance error" << std::endl
+ << "at point: " << k << std::endl
+ << "error value: "<< dist_err << std::endl
+ << "bound: " << dist_bound << std::endl
+ << "angle error: " << angle_err
+ << " (" << angle_tol << ")" << std::endl;
+ }
+
+ public:
+ /*
+ * perform the actual conversion
+ * return true if the conversion is successful, false on the contrary
+ */
+ bool operator()()
+ {
+ // initialize the reference
+ const NL::Vector & coeff = fitter.result();
+ fit();
+ if ( !check_bound(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]) )
+ return false;
+ if ( !(make_elliptiarc()) ) return false;
+ return true;
+ }
+
+ private:
+ EllipticalArc& ea; // output elliptical arc
+ const curve_type & curve; // input curve
+ Piecewise<D2<SBasis> > dcurve; // derivative of the input curve
+ NL::LFMEllipse model; // model used for fitting
+ // perform the actual fitting task
+ NL::least_squeares_fitter<NL::LFMEllipse> fitter;
+ // tolerance: the user-defined tolerance parameter;
+ // tol_at_extr: the tolerance at end-points automatically computed
+ // on the value of "tolerance", and usually more strict;
+ // tol_at_center: tolerance at the center of the ellipse
+ // angle_tol: tolerance for the angle btw the input curve tangent
+ // versor and the ellipse normal versor at the sample points
+ double tolerance, tol_at_extr, tol_at_center, angle_tol;
+ Point initial_point, final_point; // initial and final end-points
+ unsigned int N; // total samples
+ unsigned int last; // N-1
+ double partitions; // N-1
+ std::vector<Point> p; // sample points
+ double dist_err, dist_bound, angle_err;
+};
+
+namespace detail
+{
+/*
+ * ellipse_equation
+ *
+ * this is an helper struct, it provides two routines:
+ * the first one evaluates the implicit form of an ellipse on a given point
+ * the second one computes the normal versor at a given point of an ellipse
+ * in implicit form
+ */
+struct ellipse_equation
+{
+ ellipse_equation(double a, double b, double c, double d, double e, double f)
+ : A(a), B(b), C(c), D(d), E(e), F(f)
+ {
+ }
+
+ double operator()(double x, double y) const
+ {
+ // A * x * x + B * x * y + C * y * y + D * x + E * y + F;
+ return (A * x + B * y + D) * x + (C * y + E) * y + F;
+ }
+
+ double operator()(Point const& p) const
+ {
+ return (*this)(p[X], p[Y]);
+ }
+
+ Point normal(double x, double y) const
+ {
+ Point n( 2 * A * x + B * y + D, 2 * C * y + B * x + E );
+ return unit_vector(n);
+ }
+
+ Point normal(Point const& p) const
+ {
+ return normal(p[X], p[Y]);
+ }
+
+ double A, B, C, D, E, F;
+};
+
+} // end namespace detail
+
+make_elliptical_arc::
+make_elliptical_arc( EllipticalArc& _ea,
+ curve_type const& _curve,
+ unsigned int _total_samples,
+ double _tolerance )
+ : ea(_ea), curve(_curve),
+ dcurve( unitVector(derivative(curve)) ),
+ model(), fitter(model, _total_samples),
+ tolerance(_tolerance), tol_at_extr(tolerance/2),
+ tol_at_center(0.1), angle_tol(0.1),
+ initial_point(curve.at0()), final_point(curve.at1()),
+ N(_total_samples), last(N-1), partitions(N-1), p(N)
+{
+}
+
+/*
+ * check that the coefficients computed by the fit method satisfy
+ * the tolerance parameters at the k-th sample point
+ */
+bool
+make_elliptical_arc::
+bound_exceeded( unsigned int k, detail::ellipse_equation const & ee,
+ double e1x, double e1y, double e2 )
+{
+ dist_err = std::fabs( ee(p[k]) );
+ dist_bound = std::fabs( e1x * p[k][X] + e1y * p[k][Y] + e2 );
+ // check that the angle btw the tangent versor to the input curve
+ // and the normal versor of the elliptical arc, both evaluate
+ // at the k-th sample point, are really othogonal
+ angle_err = std::fabs( dot( dcurve(k/partitions), ee.normal(p[k]) ) );
+ //angle_err *= angle_err;
+ return ( dist_err > dist_bound || angle_err > angle_tol );
+}
+
+/*
+ * check that the coefficients computed by the fit method satisfy
+ * the tolerance parameters at each sample point
+ */
+bool
+make_elliptical_arc::
+check_bound(double A, double B, double C, double D, double E, double F)
+{
+ detail::ellipse_equation ee(A, B, C, D, E, F);
+
+ // check error magnitude at the end-points
+ double e1x = (2*A + B) * tol_at_extr;
+ double e1y = (B + 2*C) * tol_at_extr;
+ double e2 = ((D + E) + (A + B + C) * tol_at_extr) * tol_at_extr;
+ if (bound_exceeded(0, ee, e1x, e1y, e2))
+ {
+ print_bound_error(0);
+ return false;
+ }
+ if (bound_exceeded(0, ee, e1x, e1y, e2))
+ {
+ print_bound_error(last);
+ return false;
+ }
+
+ // e1x = derivative((ee(x,y), x) | x->tolerance, y->tolerance
+ e1x = (2*A + B) * tolerance;
+ // e1y = derivative((ee(x,y), y) | x->tolerance, y->tolerance
+ e1y = (B + 2*C) * tolerance;
+ // e2 = ee(tolerance, tolerance) - F;
+ e2 = ((D + E) + (A + B + C) * tolerance) * tolerance;
+// std::cerr << "e1x = " << e1x << std::endl;
+// std::cerr << "e1y = " << e1y << std::endl;
+// std::cerr << "e2 = " << e2 << std::endl;
+
+ // check error magnitude at sample points
+ for ( unsigned int k = 1; k < last; ++k )
+ {
+ if ( bound_exceeded(k, ee, e1x, e1y, e2) )
+ {
+ print_bound_error(k);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * fit
+ *
+ * supply the samples to the fitter and compute
+ * the ellipse implicit equation coefficients
+ */
+void make_elliptical_arc::fit()
+{
+ for (unsigned int k = 0; k < N; ++k)
+ {
+ p[k] = curve( k / partitions );
+ fitter.append(p[k]);
+ }
+ fitter.update();
+
+ NL::Vector z(N, 0.0);
+ fitter.result(z);
+}
+
+bool make_elliptical_arc::make_elliptiarc()
+{
+ const NL::Vector & coeff = fitter.result();
+ Ellipse e;
+ try
+ {
+ e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]);
+ }
+ catch(LogicalError const &exc)
+ {
+ return false;
+ }
+
+ Point inner_point = curve(0.5);
+
+ std::unique_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) );
+ ea = *arc;
+
+ if ( !are_near( e.center(),
+ ea.center(),
+ tol_at_center * std::min(e.ray(X),e.ray(Y))
+ )
+ )
+ {
+ return false;
+ }
+ return true;
+}
+
+
+
+bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in,
+ double tolerance, unsigned num_samples)
+{
+ make_elliptical_arc convert(ea, in, num_samples, tolerance);
+ return convert();
+}
+
+} // 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/src/2geom/elliptical-arc.cpp b/src/2geom/elliptical-arc.cpp
new file mode 100644
index 0000000..63e534c
--- /dev/null
+++ b/src/2geom/elliptical-arc.cpp
@@ -0,0 +1,1045 @@
+/*
+ * SVG Elliptical Arc Class
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2008-2009 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 <cfloat>
+#include <limits>
+#include <memory>
+
+#include <2geom/bezier-curve.h>
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/path-sink.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/transforms.h>
+#include <2geom/utils.h>
+
+#include <2geom/numeric/vector.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+namespace Geom
+{
+
+/**
+ * @class EllipticalArc
+ * @brief Elliptical arc curve
+ *
+ * Elliptical arc is a curve taking the shape of a section of an ellipse.
+ *
+ * The arc function has two forms: the regular one, mapping the unit interval to points
+ * on 2D plane (the linear domain), and a second form that maps some interval
+ * \f$A \subseteq [0,2\pi)\f$ to the same points (the angular domain). The interval \f$A\f$
+ * determines which part of the ellipse forms the arc. The arc is said to contain an angle
+ * if its angular domain includes that angle (and therefore it is defined for that angle).
+ *
+ * The angular domain considers each ellipse to be
+ * a rotated, scaled and translated unit circle: 0 corresponds to \f$(1,0)\f$ on the unit circle,
+ * \f$\pi/2\f$ corresponds to \f$(0,1)\f$, \f$\pi\f$ to \f$(-1,0)\f$ and \f$3\pi/2\f$
+ * to \f$(0,-1)\f$. After the angle is mapped to a point from a unit circle, the point is
+ * transformed using a matrix of this form
+ * \f[ M = \left[ \begin{array}{ccc}
+ r_X \cos(\theta) & -r_Y \sin(\theta) & 0 \\
+ r_X \sin(\theta) & r_Y \cos(\theta) & 0 \\
+ c_X & c_Y & 1 \end{array} \right] \f]
+ * where \f$r_X, r_Y\f$ are the X and Y rays of the ellipse, \f$\theta\f$ is its angle of rotation,
+ * and \f$c_X, c_Y\f$ the coordinates of the ellipse's center - thus mapping the angle
+ * to some point on the ellipse. Note that for example the point at angluar coordinate 0,
+ * the center and the point at angular coordinate \f$\pi/4\f$ do not necessarily
+ * create an angle of \f$\pi/4\f$ radians; it is only the case if both axes of the ellipse
+ * are of the same length (i.e. it is a circle).
+ *
+ * @image html ellipse-angular-coordinates.png "An illustration of the angular domain"
+ *
+ * Each arc is defined by five variables: The initial and final point, the ellipse's rays,
+ * and the ellipse's rotation. Each set of those parameters corresponds to four different arcs,
+ * with two of them larger than half an ellipse and two of them turning clockwise while traveling
+ * from initial to final point. The two flags disambiguate between them: "large arc flag" selects
+ * the bigger arc, while the "sweep flag" selects the arc going in the direction of positive
+ * angles. Angles always increase when going from the +X axis in the direction of the +Y axis,
+ * so if Y grows downwards, this means clockwise.
+ *
+ * @image html elliptical-arc-flags.png "Meaning of arc flags (Y grows downwards)"
+ *
+ * @ingroup Curves
+ */
+
+
+/** @brief Compute bounds of an elliptical arc.
+ * The bounds computation works as follows. The extreme X and Y points
+ * are either the endpoints or local minima / maxima of the ellipse.
+ * We already have endpoints, and we compute the local extremes.
+ * The local extremes correspond to two angles separated by \f$\pi\f$.
+ * Once we compute these angles, we check whether they belong to the arc,
+ * and if they do, we evaluate the ellipse at these angles.
+ * The bounding box of the arc is equal to the bounding box of the endpoints
+ * and the local extrema that belong to the arc.
+ */
+Rect EllipticalArc::boundsExact() const
+{
+ if (isChord()) {
+ return { _initial_point, _final_point };
+ }
+
+ if (_angles.isFull()) {
+ return _ellipse.boundsExact();
+ }
+
+ auto const trans = unitCircleTransform();
+
+ auto proj_bounds = [&] (Dim2 d) {
+ // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4]
+ // in the coordinate system where the ellipse is a unit circle. We compute its range of
+ // values on the unit circle arc.
+ auto result = Interval(_initial_point[d], _final_point[d]);
+
+ auto const v = Point(trans[d], trans[d + 2]);
+ auto const r = v.length();
+ auto const mid = trans[d + 4];
+ auto const angle = Angle(v);
+
+ if (_angles.contains(angle)) {
+ result.expandTo(mid + r);
+ }
+ if (_angles.contains(angle + M_PI)) {
+ result.expandTo(mid - r);
+ }
+
+ return result;
+ };
+
+ return { proj_bounds(X), proj_bounds(Y) };
+}
+
+void EllipticalArc::expandToTransformed(Rect &bbox, Affine const &transform) const
+{
+ bbox.expandTo(_final_point * transform);
+
+ if (isChord() || bbox.contains(_ellipse.boundsFast())) {
+ return;
+ }
+
+ auto const trans = unitCircleTransform() * transform;
+
+ for (auto d : { X, Y }) {
+ // See boundsExact() for explanation.
+ auto const v = Point(trans[d], trans[d + 2]);
+ auto const r = v.length();
+ auto const mid = trans[d + 4];
+
+ if (_angles.isFull()) {
+ bbox[d].unionWith(Interval(mid - r, mid + r));
+ } else {
+ auto const angle = Angle(v);
+ if (_angles.contains(angle)) {
+ bbox[d].expandTo(mid + r);
+ }
+ if (_angles.contains(angle + M_PI)) {
+ bbox[d].expandTo(mid - r);
+ }
+ }
+ }
+}
+
+Point EllipticalArc::pointAtAngle(Coord t) const
+{
+ Point ret = _ellipse.pointAt(t);
+ return ret;
+}
+
+Coord EllipticalArc::valueAtAngle(Coord t, Dim2 d) const
+{
+ return _ellipse.valueAt(t, d);
+}
+
+std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const
+{
+ std::vector<Coord> sol;
+
+ if (isChord()) {
+ sol = chord().roots(v, d);
+ return sol;
+ }
+
+ Interval unit_interval(0, 1);
+
+ double rotx, roty;
+ if (d == X) {
+ sincos(rotationAngle(), roty, rotx);
+ roty = -roty;
+ } else {
+ sincos(rotationAngle(), rotx, roty);
+ }
+
+ double rxrotx = ray(X) * rotx;
+ double c_v = center(d) - v;
+
+ double a = -rxrotx + c_v;
+ double b = ray(Y) * roty;
+ double c = rxrotx + c_v;
+ //std::cerr << "a = " << a << std::endl;
+ //std::cerr << "b = " << b << std::endl;
+ //std::cerr << "c = " << c << std::endl;
+
+ if (a == 0)
+ {
+ sol.push_back(M_PI);
+ if (b != 0)
+ {
+ double s = 2 * std::atan(-c/(2*b));
+ if ( s < 0 ) s += 2*M_PI;
+ sol.push_back(s);
+ }
+ }
+ else
+ {
+ double delta = b * b - a * c;
+ //std::cerr << "delta = " << delta << std::endl;
+ if (delta == 0) {
+ double s = 2 * std::atan(-b/a);
+ if ( s < 0 ) s += 2*M_PI;
+ sol.push_back(s);
+ }
+ else if ( delta > 0 )
+ {
+ double sq = std::sqrt(delta);
+ double s = 2 * std::atan( (-b - sq) / a );
+ if ( s < 0 ) s += 2*M_PI;
+ sol.push_back(s);
+ s = 2 * std::atan( (-b + sq) / a );
+ if ( s < 0 ) s += 2*M_PI;
+ sol.push_back(s);
+ }
+ }
+
+ std::vector<double> arc_sol;
+ for (double & i : sol) {
+ //std::cerr << "s = " << deg_from_rad(sol[i]);
+ i = timeAtAngle(i);
+ //std::cerr << " -> t: " << sol[i] << std::endl;
+ if (unit_interval.contains(i)) {
+ arc_sol.push_back(i);
+ }
+ }
+ return arc_sol;
+}
+
+
+// D(E(t,C),t) = E(t+PI/2,O), where C is the ellipse center
+// the derivative doesn't rotate the ellipse but there is a translation
+// of the parameter t by an angle of PI/2 so the ellipse points are shifted
+// of such an angle in the cw direction
+Curve *EllipticalArc::derivative() const
+{
+ if (isChord()) {
+ return chord().derivative();
+ }
+
+ EllipticalArc *result = static_cast<EllipticalArc*>(duplicate());
+ result->_ellipse.setCenter(0, 0);
+ result->_angles.setInitial(result->_angles.initialAngle() + M_PI/2);
+ result->_angles.setFinal(result->_angles.finalAngle() + M_PI/2);
+ result->_initial_point = result->pointAtAngle( result->initialAngle() );
+ result->_final_point = result->pointAtAngle( result->finalAngle() );
+ return result;
+}
+
+
+std::vector<Point>
+EllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const
+{
+ if (isChord()) {
+ return chord().pointAndDerivatives(t, n);
+ }
+
+ unsigned int nn = n+1; // nn represents the size of the result vector.
+ std::vector<Point> result;
+ result.reserve(nn);
+ double angle = angleAt(t);
+ std::unique_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) );
+ ea->_ellipse.setCenter(0, 0);
+ unsigned int m = std::min(nn, 4u);
+ for ( unsigned int i = 0; i < m; ++i )
+ {
+ result.push_back( ea->pointAtAngle(angle) );
+ angle += (sweep() ? M_PI/2 : -M_PI/2);
+ if ( !(angle < 2*M_PI) ) angle -= 2*M_PI;
+ }
+ m = nn / 4;
+ for ( unsigned int i = 1; i < m; ++i )
+ {
+ for ( unsigned int j = 0; j < 4; ++j )
+ result.push_back( result[j] );
+ }
+ m = nn - 4 * m;
+ for ( unsigned int i = 0; i < m; ++i )
+ {
+ result.push_back( result[i] );
+ }
+ if ( !result.empty() ) // nn != 0
+ result[0] = pointAtAngle(angle);
+ return result;
+}
+
+Point EllipticalArc::pointAt(Coord t) const
+{
+ if (t == 0.0) {
+ return initialPoint();
+ }
+ if (t == 1.0) {
+ return finalPoint();
+ }
+ if (isChord()) {
+ return chord().pointAt(t);
+ }
+ return _ellipse.pointAt(angleAt(t));
+}
+
+Coord EllipticalArc::valueAt(Coord t, Dim2 d) const
+{
+ if (isChord()) return chord().valueAt(t, d);
+ return valueAtAngle(angleAt(t), d);
+}
+
+Curve* EllipticalArc::portion(double f, double t) const
+{
+ // fix input arguments
+ f = std::clamp(f, 0.0, 1.0);
+ t = std::clamp(t, 0.0, 1.0);
+
+ if (f == t) {
+ EllipticalArc *arc = new EllipticalArc();
+ arc->_initial_point = arc->_final_point = pointAt(f);
+ return arc;
+ }
+ if (f == 0.0 && t == 1.0) {
+ return duplicate();
+ }
+ if (f == 1.0 && t == 0.0) {
+ return reverse();
+ }
+
+ EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate());
+ arc->_initial_point = pointAt(f);
+ arc->_final_point = pointAt(t);
+ arc->_angles.setAngles(angleAt(f), angleAt(t));
+ if (f > t) arc->_angles.setSweep(!sweep());
+ if ( _large_arc && fabs(angularExtent() * (t-f)) <= M_PI) {
+ arc->_large_arc = false;
+ }
+ return arc;
+}
+
+// the arc is the same but traversed in the opposite direction
+Curve *EllipticalArc::reverse() const
+{
+ using std::swap;
+ EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate());
+ rarc->_angles.reverse();
+ swap(rarc->_initial_point, rarc->_final_point);
+ return rarc;
+}
+
+#ifdef HAVE_GSL // GSL is required for function "solve_reals"
+std::vector<double> EllipticalArc::allNearestTimes( Point const& p, double from, double to ) const
+{
+ std::vector<double> result;
+
+ if ( from > to ) std::swap(from, to);
+ if ( from < 0 || to > 1 )
+ {
+ THROW_RANGEERROR("[from,to] interval out of range");
+ }
+
+ if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) || are_near(from, to) )
+ {
+ result.push_back(from);
+ return result;
+ }
+ else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) )
+ {
+ LineSegment seg(pointAt(from), pointAt(to));
+ Point np = seg.pointAt( seg.nearestTime(p) );
+ if ( are_near(ray(Y), 0) )
+ {
+ if ( are_near(rotationAngle(), M_PI/2)
+ || are_near(rotationAngle(), 3*M_PI/2) )
+ {
+ result = roots(np[Y], Y);
+ }
+ else
+ {
+ result = roots(np[X], X);
+ }
+ }
+ else
+ {
+ if ( are_near(rotationAngle(), M_PI/2)
+ || are_near(rotationAngle(), 3*M_PI/2) )
+ {
+ result = roots(np[X], X);
+ }
+ else
+ {
+ result = roots(np[Y], Y);
+ }
+ }
+ return result;
+ }
+ else if ( are_near(ray(X), ray(Y)) )
+ {
+ Point r = p - center();
+ if ( are_near(r, Point(0,0)) )
+ {
+ THROW_INFINITESOLUTIONS(0);
+ }
+ // TODO: implement case r != 0
+// Point np = ray(X) * unit_vector(r);
+// std::vector<double> solX = roots(np[X],X);
+// std::vector<double> solY = roots(np[Y],Y);
+// double t;
+// if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1]))
+// {
+// t = solX[0];
+// }
+// else
+// {
+// t = solX[1];
+// }
+// if ( !(t < from || t > to) )
+// {
+// result.push_back(t);
+// }
+// else
+// {
+//
+// }
+ }
+
+ // solve the equation <D(E(t),t)|E(t)-p> == 0
+ // that provides min and max distance points
+ // on the ellipse E wrt the point p
+ // after the substitutions:
+ // cos(t) = (1 - s^2) / (1 + s^2)
+ // sin(t) = 2t / (1 + s^2)
+ // where s = tan(t/2)
+ // we get a 4th degree equation in s
+ /*
+ * ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) +
+ * ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) +
+ * 2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) +
+ * 2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi])
+ */
+
+ Point p_c = p - center();
+ double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y));
+ double sinrot, cosrot;
+ sincos(rotationAngle(), sinrot, cosrot);
+ double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot);
+ Poly coeff;
+ coeff.resize(5);
+ coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot );
+ coeff[3] = 2 * ( rx2_ry2 + expr1 );
+ coeff[2] = 0;
+ coeff[1] = 2 * ( -rx2_ry2 + expr1 );
+ coeff[0] = -coeff[4];
+
+// for ( unsigned int i = 0; i < 5; ++i )
+// std::cerr << "c[" << i << "] = " << coeff[i] << std::endl;
+
+ std::vector<double> real_sol;
+ // gsl_poly_complex_solve raises an error
+ // if the leading coefficient is zero
+ if ( are_near(coeff[4], 0) )
+ {
+ real_sol.push_back(0);
+ if ( !are_near(coeff[3], 0) )
+ {
+ double sq = -coeff[1] / coeff[3];
+ if ( sq > 0 )
+ {
+ double s = std::sqrt(sq);
+ real_sol.push_back(s);
+ real_sol.push_back(-s);
+ }
+ }
+ }
+ else
+ {
+ real_sol = solve_reals(coeff);
+ }
+
+ for (double & i : real_sol)
+ {
+ i = 2 * std::atan(i);
+ if ( i < 0 ) i += 2*M_PI;
+ }
+ // when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0
+ // so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity
+ if ( (real_sol.size() % 2) != 0 )
+ {
+ real_sol.push_back(M_PI);
+ }
+
+ double mindistsq1 = std::numeric_limits<double>::max();
+ double mindistsq2 = std::numeric_limits<double>::max();
+ double dsq = 0;
+ unsigned int mi1 = 0, mi2 = 0;
+ for ( unsigned int i = 0; i < real_sol.size(); ++i )
+ {
+ dsq = distanceSq(p, pointAtAngle(real_sol[i]));
+ if ( mindistsq1 > dsq )
+ {
+ mindistsq2 = mindistsq1;
+ mi2 = mi1;
+ mindistsq1 = dsq;
+ mi1 = i;
+ }
+ else if ( mindistsq2 > dsq )
+ {
+ mindistsq2 = dsq;
+ mi2 = i;
+ }
+ }
+
+ double t = timeAtAngle(real_sol[mi1]);
+ if ( !(t < from || t > to) )
+ {
+ result.push_back(t);
+ }
+
+ bool second_sol = false;
+ t = timeAtAngle(real_sol[mi2]);
+ if ( real_sol.size() == 4 && !(t < from || t > to) )
+ {
+ if ( result.empty() || are_near(mindistsq1, mindistsq2) )
+ {
+ result.push_back(t);
+ second_sol = true;
+ }
+ }
+
+ // we need to test extreme points too
+ double dsq1 = distanceSq(p, pointAt(from));
+ double dsq2 = distanceSq(p, pointAt(to));
+ if ( second_sol )
+ {
+ if ( mindistsq2 > dsq1 )
+ {
+ result.clear();
+ result.push_back(from);
+ mindistsq2 = dsq1;
+ }
+ else if ( are_near(mindistsq2, dsq) )
+ {
+ result.push_back(from);
+ }
+ if ( mindistsq2 > dsq2 )
+ {
+ result.clear();
+ result.push_back(to);
+ }
+ else if ( are_near(mindistsq2, dsq2) )
+ {
+ result.push_back(to);
+ }
+
+ }
+ else
+ {
+ if ( result.empty() )
+ {
+ if ( are_near(dsq1, dsq2) )
+ {
+ result.push_back(from);
+ result.push_back(to);
+ }
+ else if ( dsq2 > dsq1 )
+ {
+ result.push_back(from);
+ }
+ else
+ {
+ result.push_back(to);
+ }
+ }
+ }
+
+ return result;
+}
+#endif
+
+/** @brief Convert the passed intersections to curve time parametrization
+ * and filter out any invalid intersections.
+ */
+std::vector<ShapeIntersection> EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &&xs,
+ bool is_first) const
+{
+ std::vector<ShapeIntersection> result;
+ result.reserve(xs.size());
+ for (auto &xing : xs) {
+ if (_validateIntersection(xing, is_first)) {
+ result.emplace_back(std::move(xing));
+ }
+ }
+ return result;
+}
+
+/** @brief Convert the passed intersection to curve time and check whether the intersection
+ * is numerically sane.
+ *
+ * @param xing The intersection to convert to curve time and to validate.
+ * @param is_first If true, this arc is the first of the intersected curves; if false, it's second.
+ * @return Whether the intersection is valid.
+ *
+ * Note that the intersection is guaranteed to be converted only if the return value is true.
+ */
+bool EllipticalArc::_validateIntersection(ShapeIntersection &xing, bool is_first) const
+{
+ static auto const UNIT_INTERVAL = Interval(0, 1);
+ constexpr auto EPS = 1e-4;
+
+ Coord &t = is_first ? xing.first : xing.second;
+ if (!are_near_rel(_ellipse.pointAt(t), xing.point(), EPS)) {
+ return false;
+ }
+
+ t = timeAtAngle(t);
+ if (!UNIT_INTERVAL.contains(t)) {
+ return false;
+ }
+ if (!are_near_rel(pointAt(t), xing.point(), EPS)) {
+ return false;
+ }
+ return true;
+}
+
+std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const
+{
+ if (isLineSegment()) {
+ LineSegment ls(_initial_point, _final_point);
+ return ls.intersect(other, eps);
+ }
+
+ if (other.isLineSegment()) {
+ LineSegment ls(other.initialPoint(), other.finalPoint());
+ return _filterIntersections(_ellipse.intersect(ls), true);
+ }
+
+ if (auto bez = dynamic_cast<BezierCurve const *>(&other)) {
+ return _filterIntersections(_ellipse.intersect(bez->fragment()), true);
+ }
+
+ if (auto arc = dynamic_cast<EllipticalArc const *>(&other)) {
+ std::vector<CurveIntersection> crossings;
+ try {
+ crossings = _ellipse.intersect(arc->_ellipse);
+ } catch (InfinitelyManySolutions &) {
+ // This could happen if the two arcs come from the same ellipse.
+ return _intersectSameEllipse(arc);
+ }
+ return arc->_filterIntersections(_filterIntersections(std::move(crossings), true), false);
+ }
+
+ // in case someone wants to make a custom curve type
+ auto result = other.intersect(*this, eps);
+ transpose_in_place(result);
+ return result;
+}
+
+/** @brief Check if two arcs on the same ellipse intersect/overlap.
+ *
+ * @param other Another elliptical arc on the same ellipse as this one.
+ * @return If the arcs overlap, the returned vector contains synthesized intersections
+ * at the start and end of the overlap.
+ * If the arcs do not overlap, an empty vector is returned.
+ */
+std::vector<ShapeIntersection> EllipticalArc::_intersectSameEllipse(EllipticalArc const *other) const
+{
+ assert(_ellipse == other->_ellipse);
+ auto const &other_angles = other->angularInterval();
+ std::vector<ShapeIntersection> result;
+
+ /// A closure to create an "intersection" at the prescribed angle.
+ auto const synthesize_intersection = [&](Angle angle) {
+ auto const time = timeAtAngle(angle);
+ if (result.end() == std::find_if(result.begin(), result.end(),
+ [=](ShapeIntersection const &xing) -> bool {
+ return xing.first == time;
+ }))
+ {
+ result.emplace_back(time, other->timeAtAngle(angle), _ellipse.pointAt(angle));
+ }
+ };
+
+ for (auto a : {_angles.initialAngle(), _angles.finalAngle()}) {
+ if (other_angles.contains(a)) {
+ synthesize_intersection(a);
+ }
+ }
+ for (auto a : {other_angles.initialAngle(), other_angles.finalAngle()}) {
+ if (_angles.contains(a)) {
+ synthesize_intersection(a);
+ }
+ }
+ return result;
+}
+
+void EllipticalArc::_updateCenterAndAngles()
+{
+ // See: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+ Point d = initialPoint() - finalPoint();
+ Point mid = middle_point(initialPoint(), finalPoint());
+
+ auto degenerate_ellipse = [&] {
+ _ellipse = Ellipse();
+ _ellipse.setCenter(initialPoint());
+ _angles = AngleInterval();
+ _large_arc = false;
+ };
+
+ // if ip = fp, the arc contains no other points
+ if (initialPoint() == finalPoint()) {
+ degenerate_ellipse();
+ return;
+ }
+
+ // rays should be positive
+ _ellipse.setRays(std::fabs(ray(X)), std::fabs(ray(Y)));
+
+ if (isChord()) {
+ _ellipse.setRays(L2(d) / 2, 0);
+ _ellipse.setRotationAngle(atan2(d));
+ _ellipse.setCenter(mid);
+ _angles.setAngles(0, M_PI);
+ _angles.setSweep(false);
+ _large_arc = false;
+ return;
+ }
+
+ Rotate rot(rotationAngle()); // the matrix in F.6.5.3
+ Rotate invrot = rot.inverse(); // the matrix in F.6.5.1
+
+ Point r = rays();
+ Point p = d / 2 * invrot; // x', y' in F.6.5.1
+ Point c(0,0); // cx', cy' in F.6.5.2
+
+ // Correct out-of-range radii
+ Coord lambda = hypot(p[X]/r[X], p[Y]/r[Y]);
+ if (lambda > 1) {
+ r *= lambda;
+ _ellipse.setRays(r);
+ _ellipse.setCenter(mid);
+ } else {
+ // evaluate F.6.5.2
+ Coord rxry = r[X]*r[X] * r[Y]*r[Y];
+ Coord pxry = p[X]*p[X] * r[Y]*r[Y];
+ Coord rxpy = r[X]*r[X] * p[Y]*p[Y];
+ Coord const denominator = rxpy + pxry;
+ if (denominator == 0.0) {
+ degenerate_ellipse();
+ return;
+ }
+ Coord rad = (rxry - pxry - rxpy) / denominator;
+ // normally rad should never be negative, but numerical inaccuracy may cause this
+ if (rad > 0) {
+ rad = std::sqrt(rad);
+ if (sweep() == _large_arc) {
+ rad = -rad;
+ }
+ c = rad * Point(r[X]*p[Y]/r[Y], -r[Y]*p[X]/r[X]);
+ _ellipse.setCenter(c * rot + mid);
+ } else {
+ _ellipse.setCenter(mid);
+ }
+ }
+
+ // Compute start and end angles.
+ // If the ellipse was enlarged, c will be zero - this is correct.
+ Point sp((p[X] - c[X]) / r[X], (p[Y] - c[Y]) / r[Y]);
+ Point ep((-p[X] - c[X]) / r[X], (-p[Y] - c[Y]) / r[Y]);
+ Point v(1, 0);
+
+ _angles.setInitial(angle_between(v, sp));
+ _angles.setFinal(angle_between(v, ep));
+
+ /*double sweep_angle = angle_between(sp, ep);
+ if (!sweep() && sweep_angle > 0) sweep_angle -= 2*M_PI;
+ if (sweep() && sweep_angle < 0) sweep_angle += 2*M_PI;*/
+}
+
+D2<SBasis> EllipticalArc::toSBasis() const
+{
+ if (isChord()) {
+ return chord().toSBasis();
+ }
+
+ D2<SBasis> arc;
+ // the interval of parametrization has to be [0,1]
+ Coord et = initialAngle().radians() + sweepAngle();
+ Linear param(initialAngle().radians(), et);
+ Coord cosrot, sinrot;
+ sincos(rotationAngle(), sinrot, cosrot);
+
+ // order = 4 seems to be enough to get a perfect looking elliptical arc
+ SBasis arc_x = ray(X) * cos(param,4);
+ SBasis arc_y = ray(Y) * sin(param,4);
+ arc[0] = arc_x * cosrot - arc_y * sinrot + Linear(center(X), center(X));
+ arc[1] = arc_x * sinrot + arc_y * cosrot + Linear(center(Y), center(Y));
+
+ // ensure that endpoints remain exact
+ for ( int d = 0 ; d < 2 ; d++ ) {
+ arc[d][0][0] = initialPoint()[d];
+ arc[d][0][1] = finalPoint()[d];
+ }
+
+ return arc;
+}
+
+// All operations that do not contain skew can be evaluated
+// without passing through the implicit form of the ellipse,
+// which preserves precision.
+
+void EllipticalArc::operator*=(Translate const &tr)
+{
+ _initial_point *= tr;
+ _final_point *= tr;
+ _ellipse *= tr;
+}
+
+void EllipticalArc::operator*=(Scale const &s)
+{
+ _initial_point *= s;
+ _final_point *= s;
+ _ellipse *= s;
+}
+
+void EllipticalArc::operator*=(Rotate const &r)
+{
+ _initial_point *= r;
+ _final_point *= r;
+ _ellipse *= r;
+}
+
+void EllipticalArc::operator*=(Zoom const &z)
+{
+ _initial_point *= z;
+ _final_point *= z;
+ _ellipse *= z;
+}
+
+void EllipticalArc::operator*=(Affine const& m)
+{
+ if (isChord()) {
+ _initial_point *= m;
+ _final_point *= m;
+ _ellipse.setCenter(middle_point(_initial_point, _final_point));
+ _ellipse.setRays(0, 0);
+ _ellipse.setRotationAngle(0);
+ return;
+ }
+
+ _initial_point *= m;
+ _final_point *= m;
+ _ellipse *= m;
+ if (m.det() < 0) {
+ _angles.setSweep(!sweep());
+ }
+
+ // ellipse transformation does not preserve its functional form,
+ // i.e. e.pointAt(0.5)*m and (e*m).pointAt(0.5) can be different.
+ // We need to recompute start / end angles.
+ _angles.setInitial(_ellipse.timeAt(_initial_point));
+ _angles.setFinal(_ellipse.timeAt(_final_point));
+}
+
+bool EllipticalArc::operator==(Curve const &c) const
+{
+ EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c);
+ if (!other) return false;
+ if (_initial_point != other->_initial_point) return false;
+ if (_final_point != other->_final_point) return false;
+ // TODO: all arcs with ellipse rays which are too small
+ // and fall back to a line should probably be equal
+ if (rays() != other->rays()) return false;
+ if (rotationAngle() != other->rotationAngle()) return false;
+ if (_large_arc != other->_large_arc) return false;
+ if (sweep() != other->sweep()) return false;
+ return true;
+}
+
+bool EllipticalArc::isNear(Curve const &c, Coord precision) const
+{
+ EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c);
+ if (!other) {
+ if (isChord()) {
+ return c.isNear(chord(), precision);
+ }
+ return false;
+ }
+
+ if (!are_near(_initial_point, other->_initial_point, precision)) return false;
+ if (!are_near(_final_point, other->_final_point, precision)) return false;
+ if (isChord() && other->isChord()) return true;
+
+ if (sweep() != other->sweep()) return false;
+ if (!are_near(_ellipse, other->_ellipse, precision)) return false;
+ return true;
+}
+
+void EllipticalArc::feed(PathSink &sink, bool moveto_initial) const
+{
+ if (moveto_initial) {
+ sink.moveTo(_initial_point);
+ }
+ sink.arcTo(ray(X), ray(Y), rotationAngle(), _large_arc, sweep(), _final_point);
+}
+
+int EllipticalArc::winding(Point const &p) const
+{
+ using std::swap;
+
+ double sinrot, cosrot;
+ sincos(rotationAngle(), sinrot, cosrot);
+
+ Angle ymin_a = std::atan2( ray(Y) * cosrot, ray(X) * sinrot );
+ Angle ymax_a = ymin_a + M_PI;
+
+ Point ymin = pointAtAngle(ymin_a);
+ Point ymax = pointAtAngle(ymax_a);
+ if (ymin[Y] > ymax[Y]) {
+ swap(ymin, ymax);
+ swap(ymin_a, ymax_a);
+ }
+
+ if (!Interval(ymin[Y], ymax[Y]).lowerContains(p[Y])) {
+ return 0;
+ }
+
+ bool const left = cross(ymax - ymin, p - ymin) > 0;
+ bool const inside = _ellipse.contains(p);
+ if (_angles.isFull()) {
+ if (inside) {
+ return sweep() ? 1 : -1;
+ }
+ return 0;
+ }
+ bool const includes_ymin = _angles.contains(ymin_a);
+ bool const includes_ymax = _angles.contains(ymax_a);
+
+ AngleInterval rarc(ymin_a, ymax_a, true),
+ larc(ymax_a, ymin_a, true);
+
+ // we'll compute the result for an arc in the direction of increasing angles
+ // and then negate if necessary
+ Angle ia = initialAngle(), fa = finalAngle();
+ Point ip = _initial_point, fp = _final_point;
+ if (!sweep()) {
+ swap(ia, fa);
+ swap(ip, fp);
+ }
+
+ bool const initial_left = larc.contains(ia);
+ bool const final_left = larc.contains(fa);
+
+ bool intersects_left = false, intersects_right = false;
+ if (inside || left) {
+ // The point is inside the ellipse or to the left of it, so the rightwards horizontal ray
+ // may intersect the part of the arc contained in the right half of the ellipse.
+ // There are four ways in which this can happen.
+
+ intersects_right =
+ // Possiblity 1: the arc extends into the right half through the min-Y point
+ // and the ray intersects this extension:
+ (includes_ymin && !final_left && Interval(ymin[Y], fp[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 2: the arc starts and ends within the right half (hence, it cannot be the
+ // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc:
+ (!initial_left && !final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 3: the arc starts in the right half and continues through the max-Y
+ // point into the left half:
+ (!initial_left && includes_ymax && Interval(ip[Y], ymax[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 4: the entire right half of the ellipse is contained in the arc.
+ (initial_left && final_left && includes_ymin && includes_ymax);
+ }
+ if (left && !inside) {
+ // The point is to the left of the ellipse, so the rightwards horizontal ray
+ // may intersect the part of the arc contained in the left half of the ellipse.
+ // There are four ways in which this can happen.
+
+ intersects_left =
+ // Possibility 1: the arc starts in the left half and continues through the min-Y
+ // point into the right half:
+ (includes_ymin && initial_left && Interval(ymin[Y], ip[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 2: the arc starts and ends within the left half (hence, it cannot be the
+ // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc:
+ (initial_left && final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 3: the arc extends into the left half through the max-Y point
+ // and the ray intersects this extension:
+ (final_left && includes_ymax && Interval(fp[Y], ymax[Y]).lowerContains(p[Y]))
+ ||
+ // Possibility 4: the entire left half of the ellipse is contained in the arc.
+ (!initial_left && !final_left && includes_ymin && includes_ymax);
+
+ }
+ int const winding_assuming_increasing_angles = (int)intersects_right - (int)intersects_left;
+ return sweep() ? winding_assuming_increasing_angles : -winding_assuming_increasing_angles;
+}
+
+std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea)
+{
+ out << "EllipticalArc("
+ << ea.initialPoint() << ", "
+ << format_coord_nice(ea.ray(X)) << ", " << format_coord_nice(ea.ray(Y)) << ", "
+ << format_coord_nice(ea.rotationAngle()) << ", "
+ << "large_arc=" << (ea.largeArc() ? "true" : "false") << ", "
+ << "sweep=" << (ea.sweep() ? "true" : "false") << ", "
+ << ea.finalPoint() << ")";
+ return out;
+}
+
+} // 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/src/2geom/geom.cpp b/src/2geom/geom.cpp
new file mode 100644
index 0000000..791e3a6
--- /dev/null
+++ b/src/2geom/geom.cpp
@@ -0,0 +1,396 @@
+/**
+ * \brief Various geometrical calculations.
+ */
+
+#include <2geom/geom.h>
+#include <2geom/point.h>
+#include <algorithm>
+#include <optional>
+#include <2geom/rect.h>
+
+using std::swap;
+
+namespace Geom {
+
+enum IntersectorKind {
+ intersects = 0,
+ parallel,
+ coincident,
+ no_intersection
+};
+
+/**
+ * Finds the intersection of the two (infinite) lines
+ * defined by the points p such that dot(n0, p) == d0 and dot(n1, p) == d1.
+ *
+ * If the two lines intersect, then \a result becomes their point of
+ * intersection; otherwise, \a result remains unchanged.
+ *
+ * This function finds the intersection of the two lines (infinite)
+ * defined by n0.X = d0 and x1.X = d1. The algorithm is as follows:
+ * To compute the intersection point use kramer's rule:
+ * \verbatim
+ * convert lines to form
+ * ax + by = c
+ * dx + ey = f
+ *
+ * (
+ * e.g. a = (x2 - x1), b = (y2 - y1), c = (x2 - x1)*x1 + (y2 - y1)*y1
+ * )
+ *
+ * In our case we use:
+ * a = n0.x d = n1.x
+ * b = n0.y e = n1.y
+ * c = d0 f = d1
+ *
+ * so:
+ *
+ * adx + bdy = cd
+ * adx + aey = af
+ *
+ * bdy - aey = cd - af
+ * (bd - ae)y = cd - af
+ *
+ * y = (cd - af)/(bd - ae)
+ *
+ * repeat for x and you get:
+ *
+ * x = (fb - ce)/(bd - ae) \endverbatim
+ *
+ * If the denominator (bd-ae) is 0 then the lines are parallel, if the
+ * numerators are 0 then the lines coincide.
+ *
+ * \todo Why not use existing but outcommented code below
+ * (HAVE_NEW_INTERSECTOR_CODE)?
+ */
+IntersectorKind
+line_intersection(Geom::Point const &n0, double const d0,
+ Geom::Point const &n1, double const d1,
+ Geom::Point &result)
+{
+ double denominator = dot(Geom::rot90(n0), n1);
+ double X = n1[Geom::Y] * d0 -
+ n0[Geom::Y] * d1;
+ /* X = (-d1, d0) dot (n0[Y], n1[Y]) */
+
+ if (denominator == 0) {
+ if ( X == 0 ) {
+ return coincident;
+ } else {
+ return parallel;
+ }
+ }
+
+ double Y = n0[Geom::X] * d1 -
+ n1[Geom::X] * d0;
+
+ result = Geom::Point(X, Y) / denominator;
+
+ return intersects;
+}
+
+
+
+/* ccw exists as a building block */
+int
+intersector_ccw(const Geom::Point& p0, const Geom::Point& p1,
+ const Geom::Point& p2)
+/* Determine which way a set of three points winds. */
+{
+ Geom::Point d1 = p1 - p0;
+ Geom::Point d2 = p2 - p0;
+ /* compare slopes but avoid division operation */
+ double c = dot(Geom::rot90(d1), d2);
+ if(c > 0)
+ return +1; // ccw - do these match def'n in header?
+ if(c < 0)
+ return -1; // cw
+
+ /* Colinear [or NaN]. Decide the order. */
+ if ( ( d1[0] * d2[0] < 0 ) ||
+ ( d1[1] * d2[1] < 0 ) ) {
+ return -1; // p2 < p0 < p1
+ } else if ( dot(d1,d1) < dot(d2,d2) ) {
+ return +1; // p0 <= p1 < p2
+ } else {
+ return 0; // p0 <= p2 <= p1
+ }
+}
+
+/** Determine whether the line segment from p00 to p01 intersects the
+ infinite line passing through p10 and p11. This doesn't find the
+ point of intersection, use the line_intersect function above,
+ or the segment_intersection interface below.
+
+ \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+*/
+bool
+line_segment_intersectp(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11)
+{
+ if(p00 == p01) return false;
+ if(p10 == p11) return false;
+
+ return ((intersector_ccw(p00, p01, p10) * intersector_ccw(p00, p01, p11)) <= 0 );
+}
+
+
+/** Determine whether two line segments intersect. This doesn't find
+ the point of intersection, use the line_intersect function above,
+ or the segment_intersection interface below.
+
+ \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+*/
+bool
+segment_intersectp(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11)
+{
+ if(p00 == p01) return false;
+ if(p10 == p11) return false;
+
+ /* true iff ( (the p1 segment straddles the p0 infinite line)
+ * and (the p0 segment straddles the p1 infinite line) ). */
+ return (line_segment_intersectp(p00, p01, p10, p11) &&
+ line_segment_intersectp(p10, p11, p00, p01));
+}
+
+/** Determine whether \& where a line segments intersects an (infinite) line.
+
+If there is no intersection, then \a result remains unchanged.
+
+\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+**/
+IntersectorKind
+line_segment_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result)
+{
+ if(line_segment_intersectp(p00, p01, p10, p11)) {
+ Geom::Point n0 = (p01 - p00).ccw();
+ double d0 = dot(n0,p00);
+
+ Geom::Point n1 = (p11 - p10).ccw();
+ double d1 = dot(n1,p10);
+ return line_intersection(n0, d0, n1, d1, result);
+ } else {
+ return no_intersection;
+ }
+}
+
+
+/** Determine whether \& where two line segments intersect.
+
+If the two segments don't intersect, then \a result remains unchanged.
+
+\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+**/
+IntersectorKind
+segment_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result)
+{
+ if(segment_intersectp(p00, p01, p10, p11)) {
+ Geom::Point n0 = (p01 - p00).ccw();
+ double d0 = dot(n0,p00);
+
+ Geom::Point n1 = (p11 - p10).ccw();
+ double d1 = dot(n1,p10);
+ return line_intersection(n0, d0, n1, d1, result);
+ } else {
+ return no_intersection;
+ }
+}
+
+/** Determine whether \& where two line segments intersect.
+
+If the two segments don't intersect, then \a result remains unchanged.
+
+\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+**/
+IntersectorKind
+line_twopoint_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result)
+{
+ Geom::Point n0 = (p01 - p00).ccw();
+ double d0 = dot(n0,p00);
+
+ Geom::Point n1 = (p11 - p10).ccw();
+ double d1 = dot(n1,p10);
+ return line_intersection(n0, d0, n1, d1, result);
+}
+
+// this is used to compare points for std::sort below
+static bool
+is_less(Point const &A, Point const &B)
+{
+ if (A[X] < B[X]) {
+ return true;
+ } else if (A[X] == B[X] && A[Y] < B[Y]) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// TODO: this can doubtlessly be improved
+static void
+eliminate_duplicates_p(std::vector<Point> &pts)
+{
+ unsigned int size = pts.size();
+
+ if (size < 2)
+ return;
+
+ if (size == 2) {
+ if (pts[0] == pts[1]) {
+ pts.pop_back();
+ }
+ } else {
+ std::sort(pts.begin(), pts.end(), &is_less);
+ if (size == 3) {
+ if (pts[0] == pts[1]) {
+ pts.erase(pts.begin());
+ } else if (pts[1] == pts[2]) {
+ pts.pop_back();
+ }
+ } else {
+ // we have size == 4
+ if (pts[2] == pts[3]) {
+ pts.pop_back();
+ }
+ if (pts[0] == pts[1]) {
+ pts.erase(pts.begin());
+ }
+ }
+ }
+}
+
+/** Determine whether \& where an (infinite) line intersects a rectangle.
+ *
+ * \a c0, \a c1 are diagonal corners of the rectangle and
+ * \a p1, \a p1 are distinct points on the line
+ *
+ * \return A list (possibly empty) of points of intersection. If two such points (say \a r0 and \a
+ * r1) then it is guaranteed that the order of \a r0, \a r1 along the line is the same as the that
+ * of \a c0, \a c1 (i.e., the vectors \a r1 - \a r0 and \a p1 - \a p0 point into the same
+ * direction).
+ */
+std::vector<Geom::Point>
+rect_line_intersect(Geom::Point const &c0, Geom::Point const &c1,
+ Geom::Point const &p0, Geom::Point const &p1)
+{
+ using namespace Geom;
+
+ std::vector<Point> results;
+
+ Point A(c0);
+ Point C(c1);
+
+ Point B(A[X], C[Y]);
+ Point D(C[X], A[Y]);
+
+ Point res;
+
+ if (line_segment_intersect(p0, p1, A, B, res) == intersects) {
+ results.push_back(res);
+ }
+ if (line_segment_intersect(p0, p1, B, C, res) == intersects) {
+ results.push_back(res);
+ }
+ if (line_segment_intersect(p0, p1, C, D, res) == intersects) {
+ results.push_back(res);
+ }
+ if (line_segment_intersect(p0, p1, D, A, res) == intersects) {
+ results.push_back(res);
+ }
+
+ eliminate_duplicates_p(results);
+
+ if (results.size() == 2) {
+ // sort the results so that the order is the same as that of p0 and p1
+ Point dir1 (results[1] - results[0]);
+ Point dir2 (p1 - p0);
+ if (dot(dir1, dir2) < 0) {
+ swap(results[0], results[1]);
+ }
+ }
+
+ return results;
+}
+
+/** Determine whether \& where an (infinite) line intersects a rectangle.
+ *
+ * \a c0, \a c1 are diagonal corners of the rectangle and
+ * \a p1, \a p1 are distinct points on the line
+ *
+ * \return A list (possibly empty) of points of intersection. If two such points (say \a r0 and \a
+ * r1) then it is guaranteed that the order of \a r0, \a r1 along the line is the same as the that
+ * of \a c0, \a c1 (i.e., the vectors \a r1 - \a r0 and \a p1 - \a p0 point into the same
+ * direction).
+ */
+std::optional<LineSegment>
+rect_line_intersect(Geom::Rect &r,
+ Geom::LineSegment ls)
+{
+ std::vector<Point> results;
+
+ results = rect_line_intersect(r.min(), r.max(), ls[0], ls[1]);
+ if(results.size() == 2) {
+ return LineSegment(results[0], results[1]);
+ }
+ return std::optional<LineSegment>();
+}
+
+std::optional<LineSegment>
+rect_line_intersect(Geom::Rect &r,
+ Geom::Line l)
+{
+ return rect_line_intersect(r, l.segment(0, 1));
+}
+
+/**
+ * polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon, given its
+ * vertices (x[0], y[0]) ... (x[n-1], y[n-1]). It is assumed that the contour is closed, i.e., that
+ * the vertex following (x[n-1], y[n-1]) is (x[0], y[0]). The algebraic sign of the area is
+ * positive for counterclockwise ordering of vertices in x-y plane; otherwise negative.
+
+ * Returned values:
+ 0 for normal execution;
+ 1 if the polygon is degenerate (number of vertices < 3);
+ 2 if area = 0 (and the centroid is undefined).
+
+ * for now we require the path to be a polyline and assume it is closed.
+**/
+
+int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &area) {
+ const unsigned n = p.size();
+ if (n < 3)
+ return 1;
+ Geom::Point centroid_tmp(0,0);
+ double atmp = 0;
+ for (unsigned i = n-1, j = 0; j < n; i = j, j++) {
+ const double ai = cross(p[j], p[i]);
+ atmp += ai;
+ centroid_tmp += (p[j] + p[i])*ai; // first moment.
+ }
+ area = atmp / 2;
+ if (atmp != 0) {
+ centroid = centroid_tmp / (3 * atmp);
+ return 0;
+ }
+ return 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/src/2geom/intersection-graph.cpp b/src/2geom/intersection-graph.cpp
new file mode 100644
index 0000000..524267e
--- /dev/null
+++ b/src/2geom/intersection-graph.cpp
@@ -0,0 +1,535 @@
+/**
+ * \file
+ * \brief Intersection graph for Boolean operations
+ *//*
+ * 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/intersection-graph.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/utils.h>
+#include <iostream>
+#include <iterator>
+
+namespace Geom {
+
+/// Function object for comparing intersection vertices based on the intersection time.
+struct PathIntersectionGraph::IntersectionVertexLess {
+ bool operator()(IntersectionVertex const &a, IntersectionVertex const &b) const {
+ return a.pos < b.pos;
+ }
+};
+
+PathIntersectionGraph::PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision)
+ : _graph_valid(true)
+{
+ _pv[0] = a;
+ _pv[1] = b;
+
+ if (a.empty() || b.empty()) return;
+
+ _prepareArguments();
+ bool has_intersections = _prepareIntersectionLists(precision);
+ if (!has_intersections) return;
+
+ _assignEdgeWindingParities(precision);
+
+ // If a path has only degenerate intersections, assign its status now.
+ // This protects against later accidentally picking a point for winding
+ // determination that is exactly at a removed intersection.
+ _assignComponentStatusFromDegenerateIntersections();
+ _removeDegenerateIntersections();
+ if (_graph_valid) {
+ _verify();
+ }
+}
+
+/** Prepare the operands stored in PathIntersectionGraph::_pv by closing all of their constituent
+ * paths and removing degenerate segments from them.
+ */
+void PathIntersectionGraph::_prepareArguments()
+{
+ // all paths must be closed, otherwise we will miss some intersections
+ for (auto & w : _pv) {
+ for (auto & i : w) {
+ i.close();
+ }
+ }
+ // remove degenerate segments
+ for (auto & w : _pv) {
+ for (std::size_t i = w.size(); i > 0; --i) {
+ if (w[i-1].empty()) {
+ w.erase(w.begin() + (i-1));
+ continue;
+ }
+ for (std::size_t j = w[i-1].size(); j > 0; --j) {
+ if (w[i-1][j-1].isDegenerate()) {
+ w[i-1].erase(w[i-1].begin() + (j-1));
+ }
+ }
+ }
+ }
+}
+
+/** @brief Compute the lists of intersections between the constituent paths of both operands.
+ * @param precision – the precision setting for the sweepline algorithm.
+ * @return Whether any intersections were found.
+ */
+bool PathIntersectionGraph::_prepareIntersectionLists(Coord precision)
+{
+ std::vector<PVIntersection> pxs = _pv[0].intersect(_pv[1], precision);
+ // NOTE: this early return means that the path data structures will not be created
+ // if there are no intersections at all!
+ if (pxs.empty()) return false;
+
+ // prepare intersection lists for each path component
+ for (unsigned w = 0; w < 2; ++w) {
+ for (std::size_t i = 0; i < _pv[w].size(); ++i) {
+ _components[w].push_back(new PathData(w, i));
+ }
+ }
+
+ // create intersection vertices
+ for (auto & px : pxs) {
+ IntersectionVertex *xa, *xb;
+ xa = new IntersectionVertex();
+ xb = new IntersectionVertex();
+ //xa->processed = xb->processed = false;
+ xa->which = 0; xb->which = 1;
+ xa->pos = px.first;
+ xb->pos = px.second;
+ xa->p = xb->p = px.point();
+ xa->neighbor = xb;
+ xb->neighbor = xa;
+ xa->next_edge = xb->next_edge = OUTSIDE;
+ xa->defective = xb->defective = false;
+ _xs.push_back(xa);
+ _xs.push_back(xb);
+ _components[0][xa->pos.path_index].xlist.push_back(*xa);
+ _components[1][xb->pos.path_index].xlist.push_back(*xb);
+ }
+
+ // sort intersections in each component according to time value
+ for (auto & _component : _components) {
+ for (std::size_t i = 0; i < _component.size(); ++i) {
+ _component[i].xlist.sort(IntersectionVertexLess());
+ }
+ }
+
+ return true;
+}
+
+/** Determine whether path portions between consecutive intersections lie inside or outside
+ * of the other path-vector.
+ */
+void PathIntersectionGraph::_assignEdgeWindingParities(Coord precision)
+{
+ for (unsigned w = 0; w < 2; ++w) {
+ unsigned ow = (w+1) % 2; ///< The index of the other operand
+
+ for (unsigned li = 0; li < _components[w].size(); ++li) { // Traverse all paths in the component
+ IntersectionList &xl = _components[w][li].xlist;
+ for (ILIter i = xl.begin(); i != xl.end(); ++i) { // Traverse all intersections in the path
+ ILIter n = cyclic_next(i, xl);
+ std::size_t pi = i->pos.path_index;
+
+ /// Path time interval from the current crossing to the next one
+ PathInterval ival = forward_interval(i->pos, n->pos, _pv[w][pi].size());
+ PathTime mid = ival.inside(precision);
+
+ Point wpoint = _pv[w][pi].pointAt(mid);
+ _winding_points.push_back(wpoint);
+ int wdg = _pv[ow].winding(wpoint);
+ if (wdg % 2) {
+ i->next_edge = INSIDE;
+ } else {
+ i->next_edge = OUTSIDE;
+ }
+ }
+ }
+ }
+}
+
+/** Detect the situation where a path is either entirely inside or entirely outside of the other
+ * path-vector and set the status flag accordingly.
+ */
+void PathIntersectionGraph::_assignComponentStatusFromDegenerateIntersections()
+{
+ for (auto & _component : _components) {
+ for (unsigned li = 0; li < _component.size(); ++li) {
+ IntersectionList &xl = _component[li].xlist;
+ bool has_in = false;
+ bool has_out = false;
+ for (auto & i : xl) {
+ has_in |= (i.next_edge == INSIDE);
+ has_out |= (i.next_edge == OUTSIDE);
+ }
+ if (has_in && !has_out) {
+ _component[li].status = INSIDE;
+ }
+ if (!has_in && has_out) {
+ _component[li].status = OUTSIDE;
+ }
+ }
+ }
+}
+
+/** Remove intersections that don't change between in/out.
+ *
+ * In general, a degenerate intersection can happen at a point where
+ * two shapes "kiss" (are tangent) but do not cross into each other.
+ */
+void PathIntersectionGraph::_removeDegenerateIntersections()
+{
+ for (auto & _component : _components) {
+ for (unsigned li = 0; li < _component.size(); ++li) {
+ IntersectionList &xl = _component[li].xlist;
+ for (ILIter i = xl.begin(); i != xl.end();) {
+ ILIter n = cyclic_next(i, xl);
+ if (i->next_edge == n->next_edge) { // Both edges inside or both outside
+ bool last_node = (i == n); ///< Whether this is the last remaining crossing.
+ ILIter nn = _getNeighbor(n);
+ IntersectionList &oxl = _getPathData(nn).xlist;
+
+ // When exactly 3 out of 4 edges adjacent to an intersection
+ // have the same winding, we have a defective intersection,
+ // which is neither degenerate nor normal. Those can occur in paths
+ // that contain overlapping segments.
+ if (cyclic_prior(nn, oxl)->next_edge != nn->next_edge) {
+ // Not a backtrack - set the defective flag.
+ _graph_valid = false;
+ n->defective = true;
+ nn->defective = true;
+ ++i;
+ continue;
+ }
+ // Erase the degenerate or defective crossings
+ oxl.erase(nn);
+ xl.erase(n);
+ if (last_node) break;
+ } else {
+ ++i;
+ }
+ }
+ }
+ }
+}
+
+/** Verify that all paths contain an even number of intersections and that
+ * the intersection graph does not contain leaves (degree one vertices).
+ */
+void PathIntersectionGraph::_verify()
+{
+#ifndef NDEBUG
+ for (auto & _component : _components) {
+ for (unsigned li = 0; li < _component.size(); ++li) {
+ IntersectionList &xl = _component[li].xlist;
+ assert(xl.size() % 2 == 0);
+ for (ILIter i = xl.begin(); i != xl.end(); ++i) {
+ ILIter j = cyclic_next(i, xl);
+ assert(i->next_edge != j->next_edge);
+ }
+ }
+ }
+#endif
+}
+
+PathVector PathIntersectionGraph::getUnion()
+{
+ PathVector result = _getResult(false, false);
+ _handleNonintersectingPaths(result, 0, false);
+ _handleNonintersectingPaths(result, 1, false);
+ return result;
+}
+
+PathVector PathIntersectionGraph::getIntersection()
+{
+ PathVector result = _getResult(true, true);
+ _handleNonintersectingPaths(result, 0, true);
+ _handleNonintersectingPaths(result, 1, true);
+ return result;
+}
+
+PathVector PathIntersectionGraph::getAminusB()
+{
+ PathVector result = _getResult(false, true);
+ _handleNonintersectingPaths(result, 0, false);
+ _handleNonintersectingPaths(result, 1, true);
+ return result;
+}
+
+PathVector PathIntersectionGraph::getBminusA()
+{
+ PathVector result = _getResult(true, false);
+ _handleNonintersectingPaths(result, 1, false);
+ _handleNonintersectingPaths(result, 0, true);
+ return result;
+}
+
+PathVector PathIntersectionGraph::getXOR()
+{
+ PathVector r1, r2;
+ r1 = getAminusB();
+ r2 = getBminusA();
+ std::copy(r2.begin(), r2.end(), std::back_inserter(r1));
+ return r1;
+}
+
+std::size_t PathIntersectionGraph::size() const
+{
+ std::size_t result = 0;
+ for (std::size_t i = 0; i < _components[0].size(); ++i) {
+ result += _components[0][i].xlist.size();
+ }
+ return result;
+}
+
+std::vector<Point> PathIntersectionGraph::intersectionPoints(bool defective) const
+{
+ std::vector<Point> result;
+
+ for (std::size_t i = 0; i < _components[0].size(); ++i) {
+ for (const auto & j : _components[0][i].xlist) {
+ if (j.defective == defective) {
+ result.push_back(j.p);
+ }
+ }
+ }
+ return result;
+}
+
+void PathIntersectionGraph::fragments(PathVector &in, PathVector &out) const
+{
+ typedef boost::ptr_vector<PathData>::const_iterator PIter;
+ for (unsigned w = 0; w < 2; ++w) {
+ for (PIter li = _components[w].begin(); li != _components[w].end(); ++li) {
+ for (CILIter k = li->xlist.begin(); k != li->xlist.end(); ++k) {
+ CILIter n = cyclic_next(k, li->xlist);
+ // TODO: investigate why non-contiguous paths are sometimes generated here
+ Path frag(k->p);
+ frag.setStitching(true);
+ PathInterval ival = forward_interval(k->pos, n->pos, _pv[w][k->pos.path_index].size());
+ _pv[w][k->pos.path_index].appendPortionTo(frag, ival, k->p, n->p);
+ if (k->next_edge == INSIDE) {
+ in.push_back(frag);
+ } else {
+ out.push_back(frag);
+ }
+ }
+ }
+ }
+}
+
+/** @brief Compute the partial result of a boolean operation by looking at components containing
+ * intersections and stitching the correct path portions between them, depending on the truth
+ * table of the operation.
+ *
+ * @param enter_a – whether the path portions contained inside operand A should be part of the boundary
+ * of the boolean operation's result.
+ * @param enter_b – whether the path portions contained inside operand B should be part of the boundary
+ * of the boolean operation's result.
+ *
+ * These two flags completely determine how to resolve the crossings when building the result
+ * and therefore encode which boolean operation we are performing. For example, the boolean intersection
+ * corresponds to enter_a == true and enter_b == true, as can be seen by looking at a Venn diagram.
+ */
+PathVector PathIntersectionGraph::_getResult(bool enter_a, bool enter_b)
+{
+ PathVector result;
+ if (_xs.empty()) return result;
+
+ // Create the list of intersections to process
+ _ulist.clear();
+ for (auto & _component : _components) {
+ for (auto & li : _component) {
+ for (auto & k : li.xlist) {
+ _ulist.push_back(k);
+ }
+ }
+ }
+
+ unsigned n_processed = 0;
+
+ while (true) {
+ // get unprocessed intersection
+ if (_ulist.empty()) break;
+ IntersectionVertex &iv = _ulist.front();
+ unsigned w = iv.which;
+ ILIter i = _components[w][iv.pos.path_index].xlist.iterator_to(iv);
+
+ result.push_back(Path(i->p));
+ result.back().setStitching(true);
+ bool reverse = false; ///< Whether to traverse the current component in the backwards direction.
+ while (i->_proc_hook.is_linked()) {
+ ILIter prev = i;
+ std::size_t pi = i->pos.path_index; ///< Index of the path in its PathVector
+ // determine which direction to go
+ // union: always go outside
+ // intersection: always go inside
+ // a minus b: go inside in b, outside in a
+ // b minus a: go inside in a, outside in b
+ if (w == 0) { // The path we're on is a part of A
+ reverse = (i->next_edge == INSIDE) ^ enter_a;
+ } else { // The path we're on is a part of B
+ reverse = (i->next_edge == INSIDE) ^ enter_b;
+ }
+
+ // get next intersection
+ if (reverse) {
+ i = cyclic_prior(i, _components[w][pi].xlist);
+ } else {
+ i = cyclic_next(i, _components[w][pi].xlist);
+ }
+
+ // append portion of path to the result
+ PathInterval ival = PathInterval::from_direction(
+ prev->pos.asPathTime(), i->pos.asPathTime(),
+ reverse, _pv[i->which][pi].size());
+
+ _pv[i->which][pi].appendPortionTo(result.back(), ival, prev->p, i->p);
+
+ // count both vertices as processed
+ n_processed += 2;
+ if (prev->_proc_hook.is_linked()) {
+ _ulist.erase(_ulist.iterator_to(*prev));
+ }
+ if (i->_proc_hook.is_linked()) {
+ _ulist.erase(_ulist.iterator_to(*i));
+ }
+
+ // switch to the other path
+ i = _getNeighbor(i);
+ w = i->which;
+ }
+ result.back().close(true);
+ if (reverse){
+ result.back() = result.back().reversed();
+ }
+ if (result.back().empty()) {
+ // std::cerr << "Path is empty" << std::endl;
+ throw GEOM_ERR_INTERSECGRAPH;
+ }
+ }
+
+ if (n_processed != size() * 2) {
+ // std::cerr << "Processed " << n_processed << " intersections, expected " << (size() * 2) << std::endl;
+ throw GEOM_ERR_INTERSECGRAPH;
+ }
+
+ return result;
+}
+
+/** @brief Select intersection-free path components ahead of a boolean operation based on whether
+ * they should be a part of that operation's result.
+ *
+ * Every component that has intersections will be processed by _getResult().
+ * Here we take care of paths that don't have any intersections. They are either
+ * completely inside or completely outside the other path-vector.
+ *
+ * @param result – output parameter to store the selected components.
+ * @param which – which of the two operands to search for intersection-free paths.
+ * @param inside – If set to true, add paths entirely contained inside the other path-vector to
+ * the result. If set to false, add paths entirely outside of the other path-vector instead.
+ */
+void PathIntersectionGraph::_handleNonintersectingPaths(PathVector &result, unsigned which, bool inside)
+{
+ unsigned w = which;
+ unsigned ow = (w+1) % 2;
+
+ for (std::size_t i = 0; i < _pv[w].size(); ++i) {
+ // the path data vector might have been left empty if there were no intersections at all
+ bool has_path_data = !_components[w].empty();
+ // Skip if the path has intersections
+ if (has_path_data && !_components[w][i].xlist.empty()) continue;
+ bool path_inside = false;
+
+ // Use the status flag set in the constructor if available.
+ if (has_path_data && _components[w][i].status == INSIDE) {
+ path_inside = true;
+ } else if (has_path_data && _components[w][i].status == OUTSIDE) {
+ path_inside = false;
+ } else {
+ // The status flag is ambiguous: we evaluate the winding number of the initial point.
+ int wdg = _pv[ow].winding(_pv[w][i].initialPoint());
+ path_inside = wdg % 2 != 0;
+ }
+
+ if (path_inside == inside) {
+ result.push_back(_pv[w][i]);
+ }
+ }
+}
+
+/** @brief Get an iterator to the corresponding crossing on the other path-vector.
+ *
+ * @param ILIter – an iterator to a list of intersections in one of the path-vectors.
+ * @return An iterator to the corresponding intersection in the other path-vector.
+ */
+PathIntersectionGraph::ILIter PathIntersectionGraph::_getNeighbor(ILIter iter)
+{
+ unsigned ow = (iter->which + 1) % 2;
+ return _components[ow][iter->neighbor->pos.path_index].xlist.iterator_to(*iter->neighbor);
+}
+
+/** Get the path data for the path containing the intersection given an iterator to the intersection */
+PathIntersectionGraph::PathData &
+PathIntersectionGraph::_getPathData(ILIter iter)
+{
+ return _components[iter->which][iter->pos.path_index];
+}
+
+/** Format the PathIntersectionGraph for output. */
+std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig)
+{
+ os << "Intersection graph:\n"
+ << pig._xs.size()/2 << " total intersections\n"
+ << pig.size() << " considered intersections\n";
+ for (std::size_t i = 0; i < pig._components[0].size(); ++i) {
+ PathIntersectionGraph::IntersectionList const &xl = pig._components[0][i].xlist;
+ for (const auto & j : xl) {
+ os << j.pos << " - " << j.neighbor->pos << " @ " << j.p << "\n";
+ }
+ }
+ return os;
+}
+
+} // 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/src/2geom/intervaltree/interval_tree.cc b/src/2geom/intervaltree/interval_tree.cc
new file mode 100644
index 0000000..01a222a
--- /dev/null
+++ b/src/2geom/intervaltree/interval_tree.cc
@@ -0,0 +1,799 @@
+#include "interval_tree.h"
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+
+// From Emin Martinian, licenced LGPL and MPL with permission
+
+
+using namespace std;
+
+// If the symbol CHECK_INTERVAL_TREE_ASSUMPTIONS is defined then the
+// code does a lot of extra checking to make sure certain assumptions
+// are satisfied. This only needs to be done if you suspect bugs are
+// present or if you make significant changes and want to make sure
+// your changes didn't mess anything up.
+//#define CHECK_INTERVAL_TREE_ASSUMPTIONS 1
+
+
+const int MIN_INT=-MAX_INT;
+
+IntervalTreeNode::IntervalTreeNode(){}
+
+IntervalTreeNode::IntervalTreeNode(Interval * newInterval)
+ : storedInterval (newInterval) ,
+ key(newInterval->GetLowPoint()),
+ high(newInterval->GetHighPoint()) ,
+ maxHigh(high) {
+}
+IntervalTreeNode::~IntervalTreeNode(){}
+Interval::Interval(){}
+Interval::~Interval(){}
+void Interval::Print() const {
+ cout << "No Print Method defined for instance of Interval" << endl;
+}
+
+IntervalTree::IntervalTree()
+{
+ nil = new IntervalTreeNode;
+ nil->left = nil->right = nil->parent = nil;
+ nil->red = 0;
+ nil->key = nil->high = nil->maxHigh = MIN_INT;
+ nil->storedInterval = NULL;
+
+ root = new IntervalTreeNode;
+ root->parent = root->left = root->right = nil;
+ root->key = root->high = root->maxHigh = MAX_INT;
+ root->red=0;
+ root->storedInterval = NULL;
+
+ /* the following are used for the Enumerate function */
+ recursionNodeStackSize = 8; // the tree depth is approximately lg(n), this is a 256 element tree. The code will adapt to deeper trees, but this saves considerable space for small trees.
+ recursionNodeStack = new it_recursion_node[recursionNodeStackSize];
+ recursionNodeStackTop = 1;
+ recursionNodeStack[0].start_node = NULL;
+
+}
+
+/***********************************************************************/
+/* FUNCTION: LeftRotate */
+/**/
+/* INPUTS: the node to rotate on */
+/**/
+/* OUTPUT: None */
+/**/
+/* Modifies Input: this, x */
+/**/
+/* EFFECTS: Rotates as described in _Introduction_To_Algorithms by */
+/* Cormen, Leiserson, Rivest (Chapter 14). Basically this */
+/* makes the parent of x be to the left of x, x the parent of */
+/* its parent before the rotation and fixes other pointers */
+/* accordingly. Also updates the maxHigh fields of x and y */
+/* after rotation. */
+/***********************************************************************/
+
+void IntervalTree::LeftRotate(IntervalTreeNode* x) {
+ IntervalTreeNode* y;
+
+ /* I originally wrote this function to use the sentinel for */
+ /* nil to avoid checking for nil. However this introduces a */
+ /* very subtle bug because sometimes this function modifies */
+ /* the parent pointer of nil. This can be a problem if a */
+ /* function which calls LeftRotate also uses the nil sentinel */
+ /* and expects the nil sentinel's parent pointer to be unchanged */
+ /* after calling this function. For example, when DeleteFixUP */
+ /* calls LeftRotate it expects the parent pointer of nil to be */
+ /* unchanged. */
+
+ y=x->right;
+ x->right=y->left;
+
+ if (y->left != nil) y->left->parent=x; /* used to use sentinel here */
+ /* and do an unconditional assignment instead of testing for nil */
+
+ y->parent=x->parent;
+
+ /* instead of checking if x->parent is the root as in the book, we */
+ /* count on the root sentinel to implicitly take care of this case */
+ if( x == x->parent->left) {
+ x->parent->left=y;
+ } else {
+ x->parent->right=y;
+ }
+ y->left=x;
+ x->parent=y;
+
+ x->maxHigh=std::max(x->left->maxHigh,std::max(x->right->maxHigh,x->high));
+ y->maxHigh=std::max(x->maxHigh,std::max(y->right->maxHigh,y->high));
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not red in ITLeftRotate");
+ assert((nil->maxHigh=MIN_INT),
+ "nil->maxHigh != MIN_INT in ITLeftRotate");
+#endif
+}
+
+
+/***********************************************************************/
+/* FUNCTION: RighttRotate */
+/**/
+/* INPUTS: node to rotate on */
+/**/
+/* OUTPUT: None */
+/**/
+/* Modifies Input?: this, y */
+/**/
+/* EFFECTS: Rotates as described in _Introduction_To_Algorithms by */
+/* Cormen, Leiserson, Rivest (Chapter 14). Basically this */
+/* makes the parent of x be to the left of x, x the parent of */
+/* its parent before the rotation and fixes other pointers */
+/* accordingly. Also updates the maxHigh fields of x and y */
+/* after rotation. */
+/***********************************************************************/
+
+
+void IntervalTree::RightRotate(IntervalTreeNode* y) {
+ IntervalTreeNode* x;
+
+ /* I originally wrote this function to use the sentinel for */
+ /* nil to avoid checking for nil. However this introduces a */
+ /* very subtle bug because sometimes this function modifies */
+ /* the parent pointer of nil. This can be a problem if a */
+ /* function which calls LeftRotate also uses the nil sentinel */
+ /* and expects the nil sentinel's parent pointer to be unchanged */
+ /* after calling this function. For example, when DeleteFixUP */
+ /* calls LeftRotate it expects the parent pointer of nil to be */
+ /* unchanged. */
+
+ x=y->left;
+ y->left=x->right;
+
+ if (nil != x->right) x->right->parent=y; /*used to use sentinel here */
+ /* and do an unconditional assignment instead of testing for nil */
+
+ /* instead of checking if x->parent is the root as in the book, we */
+ /* count on the root sentinel to implicitly take care of this case */
+ x->parent=y->parent;
+ if( y == y->parent->left) {
+ y->parent->left=x;
+ } else {
+ y->parent->right=x;
+ }
+ x->right=y;
+ y->parent=x;
+
+ y->maxHigh=std::max(y->left->maxHigh,std::max(y->right->maxHigh,y->high));
+ x->maxHigh=std::max(x->left->maxHigh,std::max(y->maxHigh,x->high));
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not red in ITRightRotate");
+ assert((nil->maxHigh=MIN_INT),
+ "nil->maxHigh != MIN_INT in ITRightRotate");
+#endif
+}
+
+/***********************************************************************/
+/* FUNCTION: TreeInsertHelp */
+/**/
+/* INPUTS: z is the node to insert */
+/**/
+/* OUTPUT: none */
+/**/
+/* Modifies Input: this, z */
+/**/
+/* EFFECTS: Inserts z into the tree as if it were a regular binary tree */
+/* using the algorithm described in _Introduction_To_Algorithms_ */
+/* by Cormen et al. This function is only intended to be called */
+/* by the InsertTree function and not by the user */
+/***********************************************************************/
+
+void IntervalTree::TreeInsertHelp(IntervalTreeNode* z) {
+ /* This function should only be called by InsertITTree (see above) */
+ IntervalTreeNode* x;
+ IntervalTreeNode* y;
+
+ z->left=z->right=nil;
+ y=root;
+ x=root->left;
+ while( x != nil) {
+ y=x;
+ if ( x->key > z->key) {
+ x=x->left;
+ } else { /* x->key <= z->key */
+ x=x->right;
+ }
+ }
+ z->parent=y;
+ if ( (y == root) ||
+ (y->key > z->key) ) {
+ y->left=z;
+ } else {
+ y->right=z;
+ }
+
+
+#if defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not red in ITTreeInsertHelp");
+ assert((nil->maxHigh=MIN_INT),
+ "nil->maxHigh != MIN_INT in ITTreeInsertHelp");
+#endif
+}
+
+
+/***********************************************************************/
+/* FUNCTION: FixUpMaxHigh */
+/**/
+/* INPUTS: x is the node to start from*/
+/**/
+/* OUTPUT: none */
+/**/
+/* Modifies Input: this */
+/**/
+/* EFFECTS: Travels up to the root fixing the maxHigh fields after */
+/* an insertion or deletion */
+/***********************************************************************/
+
+void IntervalTree::FixUpMaxHigh(IntervalTreeNode * x) {
+ while(x != root) {
+ x->maxHigh=std::max(x->high,std::max(x->left->maxHigh,x->right->maxHigh));
+ x=x->parent;
+ }
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#endif
+}
+
+/* Before calling InsertNode the node x should have its key set */
+
+/***********************************************************************/
+/* FUNCTION: InsertNode */
+/**/
+/* INPUTS: newInterval is the interval to insert*/
+/**/
+/* OUTPUT: This function returns a pointer to the newly inserted node */
+/* which is guaranteed to be valid until this node is deleted. */
+/* What this means is if another data structure stores this */
+/* pointer then the tree does not need to be searched when this */
+/* is to be deleted. */
+/**/
+/* Modifies Input: tree */
+/**/
+/* EFFECTS: Creates a node node which contains the appropriate key and */
+/* info pointers and inserts it into the tree. */
+/***********************************************************************/
+
+IntervalTreeNode * IntervalTree::Insert(Interval * newInterval)
+{
+ IntervalTreeNode * y;
+ IntervalTreeNode * x;
+ IntervalTreeNode * newNode;
+
+ x = new IntervalTreeNode(newInterval);
+ TreeInsertHelp(x);
+ FixUpMaxHigh(x->parent);
+ newNode = x;
+ x->red=1;
+ while(x->parent->red) { /* use sentinel instead of checking for root */
+ if (x->parent == x->parent->parent->left) {
+ y=x->parent->parent->right;
+ if (y->red) {
+ x->parent->red=0;
+ y->red=0;
+ x->parent->parent->red=1;
+ x=x->parent->parent;
+ } else {
+ if (x == x->parent->right) {
+ x=x->parent;
+ LeftRotate(x);
+ }
+ x->parent->red=0;
+ x->parent->parent->red=1;
+ RightRotate(x->parent->parent);
+ }
+ } else { /* case for x->parent == x->parent->parent->right */
+ /* this part is just like the section above with */
+ /* left and right interchanged */
+ y=x->parent->parent->left;
+ if (y->red) {
+ x->parent->red=0;
+ y->red=0;
+ x->parent->parent->red=1;
+ x=x->parent->parent;
+ } else {
+ if (x == x->parent->left) {
+ x=x->parent;
+ RightRotate(x);
+ }
+ x->parent->red=0;
+ x->parent->parent->red=1;
+ LeftRotate(x->parent->parent);
+ }
+ }
+ }
+ root->left->red=0;
+ return(newNode);
+
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not red in ITTreeInsert");
+ assert(!root->red,"root not red in ITTreeInsert");
+ assert((nil->maxHigh=MIN_INT),
+ "nil->maxHigh != MIN_INT in ITTreeInsert");
+#endif
+}
+
+/***********************************************************************/
+/* FUNCTION: GetSuccessorOf */
+/**/
+/* INPUTS: x is the node we want the successor of */
+/**/
+/* OUTPUT: This function returns the successor of x or NULL if no */
+/* successor exists. */
+/**/
+/* Modifies Input: none */
+/**/
+/* Note: uses the algorithm in _Introduction_To_Algorithms_ */
+/***********************************************************************/
+
+IntervalTreeNode * IntervalTree::GetSuccessorOf(IntervalTreeNode * x) const
+{
+ IntervalTreeNode* y;
+
+ if (nil != (y = x->right)) { /* assignment to y is intentional */
+ while(y->left != nil) { /* returns the minimum of the right subtree of x */
+ y=y->left;
+ }
+ return(y);
+ } else {
+ y=x->parent;
+ while(x == y->right) { /* sentinel used instead of checking for nil */
+ x=y;
+ y=y->parent;
+ }
+ if (y == root) return(nil);
+ return(y);
+ }
+}
+
+/***********************************************************************/
+/* FUNCTION: GetPredecessorOf */
+/**/
+/* INPUTS: x is the node to get predecessor of */
+/**/
+/* OUTPUT: This function returns the predecessor of x or NULL if no */
+/* predecessor exists. */
+/**/
+/* Modifies Input: none */
+/**/
+/* Note: uses the algorithm in _Introduction_To_Algorithms_ */
+/***********************************************************************/
+
+IntervalTreeNode * IntervalTree::GetPredecessorOf(IntervalTreeNode * x) const {
+ IntervalTreeNode* y;
+
+ if (nil != (y = x->left)) { /* assignment to y is intentional */
+ while(y->right != nil) { /* returns the maximum of the left subtree of x */
+ y=y->right;
+ }
+ return(y);
+ } else {
+ y=x->parent;
+ while(x == y->left) {
+ if (y == root) return(nil);
+ x=y;
+ y=y->parent;
+ }
+ return(y);
+ }
+}
+
+/***********************************************************************/
+/* FUNCTION: Print */
+/**/
+/* INPUTS: none */
+/**/
+/* OUTPUT: none */
+/**/
+/* EFFECTS: This function recursively prints the nodes of the tree */
+/* inorder. */
+/**/
+/* Modifies Input: none */
+/**/
+/* Note: This function should only be called from ITTreePrint */
+/***********************************************************************/
+
+void IntervalTreeNode::Print(IntervalTreeNode * nil,
+ IntervalTreeNode * root) const {
+ storedInterval->Print();
+ printf(", k=%i, h=%i, mH=%i",key,high,maxHigh);
+ printf(" l->key=");
+ if( left == nil) printf("NULL"); else printf("%i",left->key);
+ printf(" r->key=");
+ if( right == nil) printf("NULL"); else printf("%i",right->key);
+ printf(" p->key=");
+ if( parent == root) printf("NULL"); else printf("%i",parent->key);
+ printf(" red=%i\n",red);
+}
+
+void IntervalTree::TreePrintHelper( IntervalTreeNode* x) const {
+
+ if (x != nil) {
+ TreePrintHelper(x->left);
+ x->Print(nil,root);
+ TreePrintHelper(x->right);
+ }
+}
+
+IntervalTree::~IntervalTree() {
+ IntervalTreeNode * x = root->left;
+ vector<IntervalTreeNode *> stuffToFree;
+
+ if (x != nil) {
+ if (x->left != nil) {
+ stuffToFree.push_back(x->left);
+ }
+ if (x->right != nil) {
+ stuffToFree.push_back(x->right);
+ }
+ // delete x->storedInterval;
+ delete x;
+ while(! stuffToFree.empty() ) {
+ x = stuffToFree.back();
+ stuffToFree.pop_back();
+ if (x->left != nil) {
+ stuffToFree.push_back(x->left);
+ }
+ if (x->right != nil) {
+ stuffToFree.push_back(x->right);
+ }
+ // delete x->storedInterval;
+ delete x;
+ }
+ }
+ delete nil;
+ delete root;
+ delete[] recursionNodeStack;
+}
+
+
+/***********************************************************************/
+/* FUNCTION: Print */
+/**/
+/* INPUTS: none */
+/**/
+/* OUTPUT: none */
+/**/
+/* EFFECT: This function recursively prints the nodes of the tree */
+/* inorder. */
+/**/
+/* Modifies Input: none */
+/**/
+/***********************************************************************/
+
+void IntervalTree::Print() const {
+ TreePrintHelper(root->left);
+}
+
+/***********************************************************************/
+/* FUNCTION: DeleteFixUp */
+/**/
+/* INPUTS: x is the child of the spliced */
+/* out node in DeleteNode. */
+/**/
+/* OUTPUT: none */
+/**/
+/* EFFECT: Performs rotations and changes colors to restore red-black */
+/* properties after a node is deleted */
+/**/
+/* Modifies Input: this, x */
+/**/
+/* The algorithm from this function is from _Introduction_To_Algorithms_ */
+/***********************************************************************/
+
+void IntervalTree::DeleteFixUp(IntervalTreeNode* x) {
+ IntervalTreeNode * w;
+ IntervalTreeNode * rootLeft = root->left;
+
+ while( (!x->red) && (rootLeft != x)) {
+ if (x == x->parent->left) {
+ w=x->parent->right;
+ if (w->red) {
+ w->red=0;
+ x->parent->red=1;
+ LeftRotate(x->parent);
+ w=x->parent->right;
+ }
+ if ( (!w->right->red) && (!w->left->red) ) {
+ w->red=1;
+ x=x->parent;
+ } else {
+ if (!w->right->red) {
+ w->left->red=0;
+ w->red=1;
+ RightRotate(w);
+ w=x->parent->right;
+ }
+ w->red=x->parent->red;
+ x->parent->red=0;
+ w->right->red=0;
+ LeftRotate(x->parent);
+ x=rootLeft; /* this is to exit while loop */
+ }
+ } else { /* the code below is has left and right switched from above */
+ w=x->parent->left;
+ if (w->red) {
+ w->red=0;
+ x->parent->red=1;
+ RightRotate(x->parent);
+ w=x->parent->left;
+ }
+ if ( (!w->right->red) && (!w->left->red) ) {
+ w->red=1;
+ x=x->parent;
+ } else {
+ if (!w->left->red) {
+ w->right->red=0;
+ w->red=1;
+ LeftRotate(w);
+ w=x->parent->left;
+ }
+ w->red=x->parent->red;
+ x->parent->red=0;
+ w->left->red=0;
+ RightRotate(x->parent);
+ x=rootLeft; /* this is to exit while loop */
+ }
+ }
+ }
+ x->red=0;
+
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not black in ITDeleteFixUp");
+ assert((nil->maxHigh=MIN_INT),
+ "nil->maxHigh != MIN_INT in ITDeleteFixUp");
+#endif
+}
+
+
+/***********************************************************************/
+/* FUNCTION: DeleteNode */
+/**/
+/* INPUTS: tree is the tree to delete node z from */
+/**/
+/* OUTPUT: returns the Interval stored at deleted node */
+/**/
+/* EFFECT: Deletes z from tree and but don't call destructor */
+/* Then calls FixUpMaxHigh to fix maxHigh fields then calls */
+/* ITDeleteFixUp to restore red-black properties */
+/**/
+/* Modifies Input: z */
+/**/
+/* The algorithm from this function is from _Introduction_To_Algorithms_ */
+/***********************************************************************/
+
+Interval * IntervalTree::DeleteNode(IntervalTreeNode * z){
+ IntervalTreeNode* y;
+ IntervalTreeNode* x;
+ Interval * returnValue = z->storedInterval;
+
+ y= ((z->left == nil) || (z->right == nil)) ? z : GetSuccessorOf(z);
+ x= (y->left == nil) ? y->right : y->left;
+ if (root == (x->parent = y->parent)) { /* assignment of y->p to x->p is intentional */
+ root->left=x;
+ } else {
+ if (y == y->parent->left) {
+ y->parent->left=x;
+ } else {
+ y->parent->right=x;
+ }
+ }
+ if (y != z) { /* y should not be nil in this case */
+
+#ifdef DEBUG_ASSERT
+ assert( (y!=nil),"y is nil in DeleteNode \n");
+#endif
+ /* y is the node to splice out and x is its child */
+
+ y->maxHigh = MIN_INT;
+ y->left=z->left;
+ y->right=z->right;
+ y->parent=z->parent;
+ z->left->parent=z->right->parent=y;
+ if (z == z->parent->left) {
+ z->parent->left=y;
+ } else {
+ z->parent->right=y;
+ }
+ FixUpMaxHigh(x->parent);
+ if (!(y->red)) {
+ y->red = z->red;
+ DeleteFixUp(x);
+ } else
+ y->red = z->red;
+ delete z;
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not black in ITDelete");
+ assert((nil->maxHigh=MIN_INT),"nil->maxHigh != MIN_INT in ITDelete");
+#endif
+ } else {
+ FixUpMaxHigh(x->parent);
+ if (!(y->red)) DeleteFixUp(x);
+ delete y;
+#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS
+ CheckAssumptions();
+#elif defined(DEBUG_ASSERT)
+ assert(!nil->red,"nil not black in ITDelete");
+ assert((nil->maxHigh=MIN_INT),"nil->maxHigh != MIN_INT in ITDelete");
+#endif
+ }
+ return returnValue;
+}
+
+
+/***********************************************************************/
+/* FUNCTION: Overlap */
+/**/
+/* INPUTS: [a1,a2] and [b1,b2] are the low and high endpoints of two */
+/* closed intervals. */
+/**/
+/* OUTPUT: stack containing pointers to the nodes between [low,high] */
+/**/
+/* Modifies Input: none */
+/**/
+/* EFFECT: returns 1 if the intervals overlap, and 0 otherwise */
+/***********************************************************************/
+
+int Overlap(int a1, int a2, int b1, int b2) {
+ if (a1 <= b1) {
+ return( (b1 <= a2) );
+ } else {
+ return( (a1 <= b2) );
+ }
+}
+
+
+/***********************************************************************/
+/* FUNCTION: Enumerate */
+/**/
+/* INPUTS: tree is the tree to look for intervals overlapping the */
+/* closed interval [low,high] */
+/**/
+/* OUTPUT: stack containing pointers to the nodes overlapping */
+/* [low,high] */
+/**/
+/* Modifies Input: none */
+/**/
+/* EFFECT: Returns a stack containing pointers to nodes containing */
+/* intervals which overlap [low,high] in O(max(N,k*log(N))) */
+/* where N is the number of intervals in the tree and k is */
+/* the number of overlapping intervals */
+/**/
+/* Note: This basic idea for this function comes from the */
+/* _Introduction_To_Algorithms_ book by Cormen et al, but */
+/* modifications were made to return all overlapping intervals */
+/* instead of just the first overlapping interval as in the */
+/* book. The natural way to do this would require recursive */
+/* calls of a basic search function. I translated the */
+/* recursive version into an interative version with a stack */
+/* as described below. */
+/***********************************************************************/
+
+
+
+/* The basic idea for the function below is to take the IntervalSearch */
+/* function from the book and modify to find all overlapping intervals */
+/* instead of just one. This means that any time we take the left */
+/* branch down the tree we must also check the right branch if and only if */
+/* we find an overlapping interval in that left branch. Note this is a */
+/* recursive condition because if we go left at the root then go left */
+/* again at the first left child and find an overlap in the left subtree */
+/* of the left child of root we must recursively check the right subtree */
+/* of the left child of root as well as the right child of root. */
+
+vector<void *> IntervalTree::Enumerate(int low, int high) {
+ vector<void *> enumResultStack;
+ IntervalTreeNode* x=root->left;
+ int stuffToDo = (x != nil);
+
+ // Possible speed up: add min field to prune right searches //
+
+#ifdef DEBUG_ASSERT
+ assert((recursionNodeStackTop == 1),
+ "recursionStack not empty when entering IntervalTree::Enumerate");
+#endif
+ currentParent = 0;
+
+ while(stuffToDo) {
+ if (Overlap(low,high,x->key,x->high) ) {
+ enumResultStack.push_back(x->storedInterval);
+ recursionNodeStack[currentParent].tryRightBranch=true;
+ }
+ if(x->left->maxHigh >= low) { // implies x != nil
+ if ( recursionNodeStackTop == recursionNodeStackSize ) {
+ recursionNodeStackSize *= 2;
+ recursionNodeStack = (it_recursion_node *)
+ realloc(recursionNodeStack,
+ recursionNodeStackSize * sizeof(it_recursion_node));
+ if (recursionNodeStack == NULL)
+ assert("realloc failed in IntervalTree::Enumerate\n");
+ }
+ recursionNodeStack[recursionNodeStackTop].start_node = x;
+ recursionNodeStack[recursionNodeStackTop].tryRightBranch = 0;
+ recursionNodeStack[recursionNodeStackTop].parentIndex = currentParent;
+ currentParent = recursionNodeStackTop++;
+ x = x->left;
+ } else {
+ x = x->right;
+ }
+ stuffToDo = (x != nil);
+ while( (!stuffToDo) && (recursionNodeStackTop > 1) ) {
+ if(recursionNodeStack[--recursionNodeStackTop].tryRightBranch) {
+ x=recursionNodeStack[recursionNodeStackTop].start_node->right;
+ currentParent=recursionNodeStack[recursionNodeStackTop].parentIndex;
+ recursionNodeStack[currentParent].tryRightBranch=true;
+ stuffToDo = ( x != nil);
+ }
+ }
+ }
+#ifdef DEBUG_ASSERT
+ assert((recursionNodeStackTop == 1),
+ "recursionStack not empty when exiting IntervalTree::Enumerate");
+#endif
+ return enumResultStack;
+}
+
+
+
+int IntervalTree::CheckMaxHighFieldsHelper(IntervalTreeNode * y,
+ const int currentHigh,
+ int match) const
+{
+ if (y != nil) {
+ match = CheckMaxHighFieldsHelper(y->left,currentHigh,match) ?
+ 1 : match;
+ assert(y->high <= currentHigh);
+ if (y->high == currentHigh)
+ match = 1;
+ match = CheckMaxHighFieldsHelper(y->right,currentHigh,match) ?
+ 1 : match;
+ }
+ return match;
+}
+
+
+
+/* Make sure the maxHigh fields for everything makes sense. *
+ * If something is wrong, print a warning and exit */
+void IntervalTree::CheckMaxHighFields(IntervalTreeNode * x) const {
+ if (x != nil) {
+ CheckMaxHighFields(x->left);
+ if(!(CheckMaxHighFieldsHelper(x,x->maxHigh,0) > 0)) {
+ assert("error found in CheckMaxHighFields.\n");
+ }
+ CheckMaxHighFields(x->right);
+ }
+}
+
+void IntervalTree::CheckAssumptions() const {
+ assert(nil->key == MIN_INT);
+ assert(nil->high == MIN_INT);
+ assert(nil->maxHigh == MIN_INT);
+ assert(root->key == MAX_INT);
+ assert(root->high == MAX_INT);
+ assert(root->maxHigh == MAX_INT);
+ assert(nil->storedInterval == NULL);
+ assert(root->storedInterval == NULL);
+ assert(nil->red == 0);
+ assert(root->red == 0);
+ CheckMaxHighFields(root->left);
+}
+
+
+
diff --git a/src/2geom/intervaltree/test2.cc b/src/2geom/intervaltree/test2.cc
new file mode 100644
index 0000000..bce448e
--- /dev/null
+++ b/src/2geom/intervaltree/test2.cc
@@ -0,0 +1,74 @@
+#include <iostream>
+
+// Nathan Hurst and Emin Martinian, licenced LGPL and MPL with permission
+
+
+#include "interval_tree.h"
+
+class SimpleInterval : public Interval {
+public:
+ SimpleInterval() :
+ _low(0),
+ _high(0),
+ _node(NULL)
+ {}
+ SimpleInterval(const int low,const int high)
+ :_low(low),
+ _high(high),
+ _node(NULL)
+ { }
+
+ int GetLowPoint() const { return _low;}
+ int GetHighPoint() const { return _high;}
+ IntervalTreeNode * GetNode() { return _node;}
+ void SetNode(IntervalTreeNode * node) {_node = node;}
+ virtual void Print() const {
+ printf("(%d, %d)", _low, _high);
+ }
+protected:
+ int _low;
+ int _high;
+ IntervalTreeNode * _node;
+
+};
+
+using namespace std;
+
+#include <stdlib.h>
+#include <time.h>
+
+int main() {
+ const int N = 1L<<24;
+ SimpleInterval *x = new SimpleInterval[N];
+ for(int i = 0; i < N; i++) {
+ x[i] = SimpleInterval(random(), random());
+ }
+
+ cout << "sizeof(SimpleInterval)" << sizeof(SimpleInterval) << endl;
+ cout << "sizeof(IntervalTreeNode)" << sizeof(IntervalTreeNode) << endl;
+ cout << "sizeof(it_recursion_node)" << sizeof(it_recursion_node) << endl;
+ cout << "sizeof(IntervalTree)" << sizeof(IntervalTree) << endl;
+
+ IntervalTree itree;
+ int onn = 0;
+ for(int nn = 1; nn < N; nn*=2) {
+ for(int i = onn; i < nn; i++) {
+ itree.Insert(&x[i]);
+ }
+ onn = nn;
+ clock_t s = clock();
+
+ int iters = 0;
+ int outputs = 0;
+ while(clock() - s < CLOCKS_PER_SEC/4) {
+ vector<void *> n = itree.Enumerate(random(), random()) ;
+ outputs += n.size();
+ //cout << n.size() << endl;
+ iters++;
+ }
+ clock_t e = clock();
+ double total = double(e - s)/(CLOCKS_PER_SEC);
+ cout << total << " " << outputs << " " << total/outputs << " " << nn << endl;
+ }
+ //itree.Print();
+}
diff --git a/src/2geom/line.cpp b/src/2geom/line.cpp
new file mode 100644
index 0000000..3db3039
--- /dev/null
+++ b/src/2geom/line.cpp
@@ -0,0 +1,610 @@
+/*
+ * Infinite Straight Line
+ *
+ * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>
+ * Nathan Hurst
+ *
+ * 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 <algorithm>
+#include <optional>
+#include <2geom/line.h>
+#include <2geom/math-utils.h>
+
+namespace Geom
+{
+
+/**
+ * @class Line
+ * @brief Infinite line on a plane.
+ *
+ * A line is specified as two points through which it passes. Lines can be interpreted as functions
+ * \f$ f: (-\infty, \infty) \to \mathbb{R}^2\f$. Zero corresponds to the first (origin) point,
+ * one corresponds to the second (final) point. All other points are computed as a linear
+ * interpolation between those two: \f$p = (1-t) a + t b\f$. Many such functions have the same
+ * image and therefore represent the same lines; for example, adding \f$b-a\f$ to both points
+ * yields the same line.
+ *
+ * 2Geom can represent the same line in many ways by design: using a different representation
+ * would lead to precision loss. For example, a line from (1e30, 1e30) to (10,0) would actually
+ * evaluate to (0,0) at time 1 if it was stored as origin and normalized versor,
+ * or origin and angle.
+ *
+ * @ingroup Primitives
+ */
+
+/** @brief Set the line by solving the line equation.
+ * A line is a set of points that satisfies the line equation
+ * \f$Ax + By + C = 0\f$. This function changes the line so that its points
+ * satisfy the line equation with the given coefficients. */
+void Line::setCoefficients (Coord a, Coord b, Coord c)
+{
+ // degenerate case
+ if (a == 0 && b == 0) {
+ if (c != 0) {
+ THROW_LOGICALERROR("the passed coefficients give the empty set");
+ }
+ _initial = Point(0,0);
+ _final = Point(0,0);
+ return;
+ }
+
+ // The way final / initial points are set based on coefficients is somewhat unusual.
+ // This is done to make sure that calling coefficients() will give back
+ // (almost) the same values.
+
+ // vertical case
+ if (a == 0) {
+ // b must be nonzero
+ _initial = Point(-b/2, -c / b);
+ _final = _initial;
+ _final[X] = b/2;
+ return;
+ }
+
+ // horizontal case
+ if (b == 0) {
+ _initial = Point(-c / a, a/2);
+ _final = _initial;
+ _final[Y] = -a/2;
+ return;
+ }
+
+ // This gives reasonable results regardless of the magnitudes of a, b and c.
+ _initial = Point(-b/2,a/2);
+ _final = Point(b/2,-a/2);
+
+ Point offset(-c/(2*a), -c/(2*b));
+
+ _initial += offset;
+ _final += offset;
+}
+
+void Line::coefficients(Coord &a, Coord &b, Coord &c) const
+{
+ Point v = vector().cw();
+ a = v[X];
+ b = v[Y];
+ c = cross(_initial, _final);
+}
+
+/** @brief Get the implicit line equation coefficients.
+ * Note that conversion to implicit form always causes loss of
+ * precision when dealing with lines that start far from the origin
+ * and end very close to it. It is recommended to normalize the line
+ * before converting it to implicit form.
+ * @return Vector with three values corresponding to the A, B and C
+ * coefficients of the line equation for this line. */
+std::vector<Coord> Line::coefficients() const
+{
+ std::vector<Coord> c(3);
+ coefficients(c[0], c[1], c[2]);
+ return c;
+}
+
+/** @brief Find intersection with an axis-aligned line.
+ * @param v Coordinate of the axis-aligned line
+ * @param d Which axis the coordinate is on. X means a vertical line, Y means a horizontal line.
+ * @return Time values at which this line intersects the query line. */
+std::vector<Coord> Line::roots(Coord v, Dim2 d) const {
+ std::vector<Coord> result;
+ Coord r = root(v, d);
+ if (std::isfinite(r)) {
+ result.push_back(r);
+ }
+ return result;
+}
+
+Coord Line::root(Coord v, Dim2 d) const
+{
+ assert(d == X || d == Y);
+ Point vs = vector();
+ if (vs[d] != 0) {
+ return (v - _initial[d]) / vs[d];
+ } else {
+ return nan("");
+ }
+}
+
+std::optional<LineSegment> Line::clip(Rect const &r) const
+{
+ Point v = vector();
+ // handle horizontal and vertical lines first,
+ // since the root-based code below will break for them
+ for (unsigned i = 0; i < 2; ++i) {
+ Dim2 d = (Dim2) i;
+ Dim2 o = other_dimension(d);
+ if (v[d] != 0) continue;
+ if (r[d].contains(_initial[d])) {
+ Point a, b;
+ a[o] = r[o].min();
+ b[o] = r[o].max();
+ a[d] = b[d] = _initial[d];
+ if (v[o] > 0) {
+ return LineSegment(a, b);
+ } else {
+ return LineSegment(b, a);
+ }
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ Interval xpart(root(r[X].min(), X), root(r[X].max(), X));
+ Interval ypart(root(r[Y].min(), Y), root(r[Y].max(), Y));
+ if (!xpart.isFinite() || !ypart.isFinite()) {
+ return std::nullopt;
+ }
+
+ OptInterval common = xpart & ypart;
+ if (common) {
+ Point p1 = pointAt(common->min()), p2 = pointAt(common->max());
+ LineSegment result(r.clamp(p1), r.clamp(p2));
+ return result;
+ } else {
+ return std::nullopt;
+ }
+
+ /* old implementation using coefficients:
+
+ if (fabs(b) > fabs(a)) {
+ p0 = Point(r[X].min(), (-c - a*r[X].min())/b);
+ if (p0[Y] < r[Y].min())
+ p0 = Point((-c - b*r[Y].min())/a, r[Y].min());
+ if (p0[Y] > r[Y].max())
+ p0 = Point((-c - b*r[Y].max())/a, r[Y].max());
+ p1 = Point(r[X].max(), (-c - a*r[X].max())/b);
+ if (p1[Y] < r[Y].min())
+ p1 = Point((-c - b*r[Y].min())/a, r[Y].min());
+ if (p1[Y] > r[Y].max())
+ p1 = Point((-c - b*r[Y].max())/a, r[Y].max());
+ } else {
+ p0 = Point((-c - b*r[Y].min())/a, r[Y].min());
+ if (p0[X] < r[X].min())
+ p0 = Point(r[X].min(), (-c - a*r[X].min())/b);
+ if (p0[X] > r[X].max())
+ p0 = Point(r[X].max(), (-c - a*r[X].max())/b);
+ p1 = Point((-c - b*r[Y].max())/a, r[Y].max());
+ if (p1[X] < r[X].min())
+ p1 = Point(r[X].min(), (-c - a*r[X].min())/b);
+ if (p1[X] > r[X].max())
+ p1 = Point(r[X].max(), (-c - a*r[X].max())/b);
+ }
+ return LineSegment(p0, p1); */
+}
+
+/** @brief Get a time value corresponding to a point.
+ * @param p Point on the line. If the point is not on the line,
+ * the returned value will be meaningless.
+ * @return Time value t such that \f$f(t) = p\f$.
+ * @see timeAtProjection */
+Coord Line::timeAt(Point const &p) const
+{
+ Point v = vector();
+ // degenerate case
+ if (v[X] == 0 && v[Y] == 0) {
+ return 0;
+ }
+
+ // use the coordinate that will give better precision
+ if (fabs(v[X]) > fabs(v[Y])) {
+ return (p[X] - _initial[X]) / v[X];
+ } else {
+ return (p[Y] - _initial[Y]) / v[Y];
+ }
+}
+
+/** @brief Create a transformation that maps one line to another.
+ * This will return a transformation \f$A\f$ such that
+ * \f$L_1(t) \cdot A = L_2(t)\f$, where \f$L_1\f$ is this line
+ * and \f$L_2\f$ is the line passed as the parameter. The returned
+ * transformation will preserve angles. */
+Affine Line::transformTo(Line const &other) const
+{
+ Affine result = Translate(-_initial);
+ result *= Rotate(angle_between(vector(), other.vector()));
+ result *= Scale(other.vector().length() / vector().length());
+ result *= Translate(other._initial);
+ return result;
+}
+
+std::vector<ShapeIntersection> Line::intersect(Line const &other) const
+{
+ std::vector<ShapeIntersection> result;
+
+ Point v1 = vector();
+ Point v2 = other.vector();
+ Coord cp = cross(v1, v2);
+ if (cp == 0) return result;
+
+ Point odiff = other.initialPoint() - initialPoint();
+ Coord t1 = cross(odiff, v2) / cp;
+ Coord t2 = cross(odiff, v1) / cp;
+ result.emplace_back(*this, other, t1, t2);
+ return result;
+}
+
+std::vector<ShapeIntersection> Line::intersect(Ray const &r) const
+{
+ Line other(r);
+ std::vector<ShapeIntersection> result = intersect(other);
+ filter_ray_intersections(result, false, true);
+ return result;
+}
+
+std::vector<ShapeIntersection> Line::intersect(LineSegment const &ls) const
+{
+ Line other(ls);
+ std::vector<ShapeIntersection> result = intersect(other);
+ filter_line_segment_intersections(result, false, true);
+ return result;
+}
+
+
+
+void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b)
+{
+ Interval unit(0, 1);
+ std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend();
+ while (i != last) {
+ if ((a && !unit.contains(i->first)) || (b && !unit.contains(i->second))) {
+ xs.erase((++i).base());
+ } else {
+ ++i;
+ }
+ }
+}
+
+void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b)
+{
+ Interval unit(0, 1);
+ std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend();
+ while (i != last) {
+ if ((a && i->first < 0) || (b && i->second < 0)) {
+ xs.erase((++i).base());
+ } else {
+ ++i;
+ }
+ }
+}
+
+namespace detail
+{
+
+inline
+OptCrossing intersection_impl(Point const &v1, Point const &o1,
+ Point const &v2, Point const &o2)
+{
+ Coord cp = cross(v1, v2);
+ if (cp == 0) return OptCrossing();
+
+ Point odiff = o2 - o1;
+
+ Crossing c;
+ c.ta = cross(odiff, v2) / cp;
+ c.tb = cross(odiff, v1) / cp;
+ return c;
+}
+
+
+OptCrossing intersection_impl(Ray const& r1, Line const& l2, unsigned int i)
+{
+ using std::swap;
+
+ OptCrossing crossing =
+ intersection_impl(r1.vector(), r1.origin(),
+ l2.vector(), l2.origin() );
+
+ if (crossing) {
+ if (crossing->ta < 0) {
+ return OptCrossing();
+ } else {
+ if (i != 0) {
+ swap(crossing->ta, crossing->tb);
+ }
+ return crossing;
+ }
+ }
+ if (are_near(r1.origin(), l2)) {
+ THROW_INFINITESOLUTIONS();
+ } else {
+ return OptCrossing();
+ }
+}
+
+
+OptCrossing intersection_impl( LineSegment const& ls1,
+ Line const& l2,
+ unsigned int i )
+{
+ using std::swap;
+
+ OptCrossing crossing =
+ intersection_impl(ls1.finalPoint() - ls1.initialPoint(),
+ ls1.initialPoint(),
+ l2.vector(),
+ l2.origin() );
+
+ if (crossing) {
+ if ( crossing->getTime(0) < 0
+ || crossing->getTime(0) > 1 )
+ {
+ return OptCrossing();
+ } else {
+ if (i != 0) {
+ swap((*crossing).ta, (*crossing).tb);
+ }
+ return crossing;
+ }
+ }
+ if (are_near(ls1.initialPoint(), l2)) {
+ THROW_INFINITESOLUTIONS();
+ } else {
+ return OptCrossing();
+ }
+}
+
+
+OptCrossing intersection_impl( LineSegment const& ls1,
+ Ray const& r2,
+ unsigned int i )
+{
+ using std::swap;
+
+ Point direction = ls1.finalPoint() - ls1.initialPoint();
+ OptCrossing crossing =
+ intersection_impl( direction,
+ ls1.initialPoint(),
+ r2.vector(),
+ r2.origin() );
+
+ if (crossing) {
+ if ( (crossing->getTime(0) < 0)
+ || (crossing->getTime(0) > 1)
+ || (crossing->getTime(1) < 0) )
+ {
+ return OptCrossing();
+ } else {
+ if (i != 0) {
+ swap(crossing->ta, crossing->tb);
+ }
+ return crossing;
+ }
+ }
+
+ if ( are_near(r2.origin(), ls1) ) {
+ bool eqvs = (dot(direction, r2.vector()) > 0);
+ if ( are_near(ls1.initialPoint(), r2.origin()) && !eqvs) {
+ crossing->ta = crossing->tb = 0;
+ return crossing;
+ } else if ( are_near(ls1.finalPoint(), r2.origin()) && eqvs) {
+ if (i == 0) {
+ crossing->ta = 1;
+ crossing->tb = 0;
+ } else {
+ crossing->ta = 0;
+ crossing->tb = 1;
+ }
+ return crossing;
+ } else {
+ THROW_INFINITESOLUTIONS();
+ }
+ } else if ( are_near(ls1.initialPoint(), r2) ) {
+ THROW_INFINITESOLUTIONS();
+ } else {
+ OptCrossing no_crossing;
+ return no_crossing;
+ }
+}
+
+} // end namespace detail
+
+
+
+OptCrossing intersection(Line const& l1, Line const& l2)
+{
+ OptCrossing c = detail::intersection_impl(
+ l1.vector(), l1.origin(),
+ l2.vector(), l2.origin());
+
+ if (!c && distance(l1.origin(), l2) == 0) {
+ THROW_INFINITESOLUTIONS();
+ }
+ return c;
+}
+
+OptCrossing intersection(Ray const& r1, Ray const& r2)
+{
+ OptCrossing crossing =
+ detail::intersection_impl( r1.vector(), r1.origin(),
+ r2.vector(), r2.origin() );
+
+ if (crossing)
+ {
+ if ( crossing->ta < 0
+ || crossing->tb < 0 )
+ {
+ OptCrossing no_crossing;
+ return no_crossing;
+ }
+ else
+ {
+ return crossing;
+ }
+ }
+
+ if ( are_near(r1.origin(), r2) || are_near(r2.origin(), r1) )
+ {
+ if ( are_near(r1.origin(), r2.origin())
+ && !are_near(r1.vector(), r2.vector()) )
+ {
+ crossing->ta = crossing->tb = 0;
+ return crossing;
+ }
+ else
+ {
+ THROW_INFINITESOLUTIONS();
+ }
+ }
+ else
+ {
+ OptCrossing no_crossing;
+ return no_crossing;
+ }
+}
+
+
+OptCrossing intersection( LineSegment const& ls1, LineSegment const& ls2 )
+{
+ Point direction1 = ls1.finalPoint() - ls1.initialPoint();
+ Point direction2 = ls2.finalPoint() - ls2.initialPoint();
+ OptCrossing crossing =
+ detail::intersection_impl( direction1,
+ ls1.initialPoint(),
+ direction2,
+ ls2.initialPoint() );
+
+ if (crossing)
+ {
+ if ( crossing->getTime(0) < 0
+ || crossing->getTime(0) > 1
+ || crossing->getTime(1) < 0
+ || crossing->getTime(1) > 1 )
+ {
+ OptCrossing no_crossing;
+ return no_crossing;
+ }
+ else
+ {
+ return crossing;
+ }
+ }
+
+ bool eqvs = (dot(direction1, direction2) > 0);
+ if ( are_near(ls2.initialPoint(), ls1) )
+ {
+ if ( are_near(ls1.initialPoint(), ls2.initialPoint()) && !eqvs )
+ {
+ crossing->ta = crossing->tb = 0;
+ return crossing;
+ }
+ else if ( are_near(ls1.finalPoint(), ls2.initialPoint()) && eqvs )
+ {
+ crossing->ta = 1;
+ crossing->tb = 0;
+ return crossing;
+ }
+ else
+ {
+ THROW_INFINITESOLUTIONS();
+ }
+ }
+ else if ( are_near(ls2.finalPoint(), ls1) )
+ {
+ if ( are_near(ls1.finalPoint(), ls2.finalPoint()) && !eqvs )
+ {
+ crossing->ta = crossing->tb = 1;
+ return crossing;
+ }
+ else if ( are_near(ls1.initialPoint(), ls2.finalPoint()) && eqvs )
+ {
+ crossing->ta = 0;
+ crossing->tb = 1;
+ return crossing;
+ }
+ else
+ {
+ THROW_INFINITESOLUTIONS();
+ }
+ }
+ else
+ {
+ OptCrossing no_crossing;
+ return no_crossing;
+ }
+}
+
+Line make_angle_bisector_line(Line const& l1, Line const& l2)
+{
+ OptCrossing crossing;
+ try
+ {
+ crossing = intersection(l1, l2);
+ }
+ catch(InfiniteSolutions const &e)
+ {
+ return l1;
+ }
+ if (!crossing)
+ {
+ THROW_RANGEERROR("passed lines are parallel");
+ }
+ Point O = l1.pointAt(crossing->ta);
+ Point A = l1.pointAt(crossing->ta + 1);
+ double angle = angle_between(l1.vector(), l2.vector());
+ Point B = (angle > 0) ? l2.pointAt(crossing->tb + 1)
+ : l2.pointAt(crossing->tb - 1);
+
+ return make_angle_bisector_line(A, O, B);
+}
+
+
+
+
+} // end namespace Geom
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
diff --git a/src/2geom/nearest-time.cpp b/src/2geom/nearest-time.cpp
new file mode 100644
index 0000000..e52251c
--- /dev/null
+++ b/src/2geom/nearest-time.cpp
@@ -0,0 +1,322 @@
+/** @file
+ * @brief Nearest time routines for D2<SBasis> and Piecewise<D2<SBasis>>
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2007-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/nearest-time.h>
+#include <algorithm>
+
+namespace Geom
+{
+
+Coord nearest_time(Point const &p, D2<Bezier> const &input, Coord from, Coord to)
+{
+ Interval domain(from, to);
+ bool partial = false;
+
+ if (domain.min() < 0 || domain.max() > 1) {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+
+ if (input.isConstant(0)) return from;
+
+ D2<Bezier> bez;
+ if (domain.min() != 0 || domain.max() != 1) {
+ bez = portion(input, domain) - p;
+ partial = true;
+ } else {
+ bez = input - p;
+ }
+
+ // find extrema of the function x(t)^2 + y(t)^2
+ // use the fact that (f^2)' = 2 f f'
+ // this reduces the order of the distance function by 1
+ D2<Bezier> deriv = derivative(bez);
+ std::vector<Coord> ts = (multiply(bez[X], deriv[X]) + multiply(bez[Y], deriv[Y])).roots();
+
+ Coord t = -1, mind = infinity();
+ for (double i : ts) {
+ Coord droot = L2sq(bez.valueAt(i));
+ if (droot < mind) {
+ mind = droot;
+ t = i;
+ }
+ }
+
+ // also check endpoints
+ Coord dinitial = L2sq(bez.at0());
+ Coord dfinal = L2sq(bez.at1());
+
+ if (dinitial < mind) {
+ mind = dinitial;
+ t = 0;
+ }
+ if (dfinal < mind) {
+ //mind = dfinal;
+ t = 1;
+ }
+
+ if (partial) {
+ t = domain.valueAt(t);
+ }
+ return t;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// D2<SBasis> versions
+
+/*
+ * Return the parameter t of the nearest time value on the portion of the curve "c",
+ * related to the interval [from, to], to the point "p".
+ * The needed curve derivative "dc" is passed as parameter.
+ * The function return the first nearest time value to "p" that is found.
+ */
+
+double nearest_time(Point const& p,
+ D2<SBasis> const& c,
+ D2<SBasis> const& dc,
+ double from, double to )
+{
+ if ( from > to ) std::swap(from, to);
+ if ( from < 0 || to > 1 )
+ {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+ if (c.isConstant()) return from;
+ SBasis dd = dot(c - p, dc);
+ //std::cout << dd << std::endl;
+ std::vector<double> zeros = Geom::roots(dd);
+
+ double closest = from;
+ double min_dist_sq = L2sq(c(from) - p);
+ for (double zero : zeros)
+ {
+ double distsq = L2sq(c(zero) - p);
+ if ( min_dist_sq > L2sq(c(zero) - p) )
+ {
+ closest = zero;
+ min_dist_sq = distsq;
+ }
+ }
+ if ( min_dist_sq > L2sq( c(to) - p ) )
+ closest = to;
+ return closest;
+
+}
+
+/*
+ * Return the parameters t of all the nearest points on the portion of
+ * the curve "c", related to the interval [from, to], to the point "p".
+ * The needed curve derivative "dc" is passed as parameter.
+ */
+
+std::vector<double>
+all_nearest_times(Point const &p,
+ D2<SBasis> const &c,
+ D2<SBasis> const &dc,
+ double from, double to)
+{
+ if (from > to) {
+ std::swap(from, to);
+ }
+ if (from < 0 || to > 1) {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+
+ std::vector<double> result;
+ if (c.isConstant()) {
+ result.push_back(from);
+ return result;
+ }
+ SBasis dd = dot(c - p, dc);
+
+ std::vector<double> zeros = Geom::roots(dd);
+ std::vector<double> candidates;
+ candidates.push_back(from);
+ candidates.insert(candidates.end(), zeros.begin(), zeros.end());
+ candidates.push_back(to);
+ std::vector<double> distsq;
+ distsq.reserve(candidates.size());
+ for (double candidate : candidates) {
+ distsq.push_back(L2sq(c(candidate) - p));
+ }
+ unsigned closest = 0;
+ double dsq = distsq[0];
+ for (unsigned i = 1; i < candidates.size(); ++i) {
+ if (dsq > distsq[i]) {
+ closest = i;
+ dsq = distsq[i];
+ }
+ }
+ for (unsigned i = 0; i < candidates.size(); ++i) {
+ if (distsq[closest] == distsq[i]) {
+ result.push_back(candidates[i]);
+ }
+ }
+ return result;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Piecewise< D2<SBasis> > versions
+
+
+double nearest_time(Point const &p,
+ Piecewise< D2<SBasis> > const &c,
+ double from, double to)
+{
+ if (from > to) std::swap(from, to);
+ if (from < c.cuts[0] || to > c.cuts[c.size()]) {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+
+ unsigned si = c.segN(from);
+ unsigned ei = c.segN(to);
+ if (si == ei) {
+ double nearest =
+ nearest_time(p, c[si], c.segT(from, si), c.segT(to, si));
+ return c.mapToDomain(nearest, si);
+ }
+
+ double t;
+ double nearest = nearest_time(p, c[si], c.segT(from, si));
+ unsigned int ni = si;
+ double dsq;
+ double mindistsq = distanceSq(p, c[si](nearest));
+ Rect bb;
+ for (unsigned i = si + 1; i < ei; ++i) {
+ bb = *bounds_fast(c[i]);
+ dsq = distanceSq(p, bb);
+ if ( mindistsq <= dsq ) continue;
+
+ t = nearest_time(p, c[i]);
+ dsq = distanceSq(p, c[i](t));
+ if (mindistsq > dsq) {
+ nearest = t;
+ ni = i;
+ mindistsq = dsq;
+ }
+ }
+ bb = *bounds_fast(c[ei]);
+ dsq = distanceSq(p, bb);
+ if (mindistsq > dsq) {
+ t = nearest_time(p, c[ei], 0, c.segT(to, ei));
+ dsq = distanceSq(p, c[ei](t));
+ if (mindistsq > dsq) {
+ nearest = t;
+ ni = ei;
+ }
+ }
+ return c.mapToDomain(nearest, ni);
+}
+
+std::vector<double>
+all_nearest_times(Point const &p,
+ Piecewise< D2<SBasis> > const &c,
+ double from, double to)
+{
+ if (from > to) {
+ std::swap(from, to);
+ }
+ if (from < c.cuts[0] || to > c.cuts[c.size()]) {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+
+ unsigned si = c.segN(from);
+ unsigned ei = c.segN(to);
+ if ( si == ei )
+ {
+ std::vector<double> all_nearest =
+ all_nearest_times(p, c[si], c.segT(from, si), c.segT(to, si));
+ for (double & i : all_nearest)
+ {
+ i = c.mapToDomain(i, si);
+ }
+ return all_nearest;
+ }
+ std::vector<double> all_t;
+ std::vector< std::vector<double> > all_np;
+ all_np.push_back( all_nearest_times(p, c[si], c.segT(from, si)) );
+ std::vector<unsigned> ni;
+ ni.push_back(si);
+ double dsq;
+ double mindistsq = distanceSq( p, c[si](all_np.front().front()) );
+ Rect bb;
+
+ for (unsigned i = si + 1; i < ei; ++i) {
+ bb = *bounds_fast(c[i]);
+ dsq = distanceSq(p, bb);
+ if ( mindistsq < dsq ) continue;
+ all_t = all_nearest_times(p, c[i]);
+ dsq = distanceSq( p, c[i](all_t.front()) );
+ if ( mindistsq > dsq )
+ {
+ all_np.clear();
+ all_np.push_back(all_t);
+ ni.clear();
+ ni.push_back(i);
+ mindistsq = dsq;
+ }
+ else if ( mindistsq == dsq )
+ {
+ all_np.push_back(all_t);
+ ni.push_back(i);
+ }
+ }
+ bb = *bounds_fast(c[ei]);
+ dsq = distanceSq(p, bb);
+ if (mindistsq >= dsq) {
+ all_t = all_nearest_times(p, c[ei], 0, c.segT(to, ei));
+ dsq = distanceSq( p, c[ei](all_t.front()) );
+ if (mindistsq > dsq) {
+ for (double & i : all_t) {
+ i = c.mapToDomain(i, ei);
+ }
+ return all_t;
+ } else if (mindistsq == dsq) {
+ all_np.push_back(all_t);
+ ni.push_back(ei);
+ }
+ }
+ std::vector<double> all_nearest;
+ for (unsigned i = 0; i < all_np.size(); ++i) {
+ for (unsigned int j = 0; j < all_np[i].size(); ++j) {
+ all_nearest.push_back( c.mapToDomain(all_np[i][j], ni[i]) );
+ }
+ }
+ all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()),
+ all_nearest.end());
+ return all_nearest;
+}
+
+} // end namespace Geom
+
+
diff --git a/src/2geom/numeric/matrix.cpp b/src/2geom/numeric/matrix.cpp
new file mode 100644
index 0000000..98ff3b6
--- /dev/null
+++ b/src/2geom/numeric/matrix.cpp
@@ -0,0 +1,154 @@
+/*
+ * Matrix, MatrixView, ConstMatrixView classes wrap the gsl matrix routines;
+ * "views" mimic the semantic of C++ references: any operation performed
+ * on a "view" is actually performed on the "viewed object"
+ *
+ * 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/numeric/matrix.h>
+#include <2geom/numeric/vector.h>
+
+
+namespace Geom { namespace NL {
+
+Vector operator*( detail::BaseMatrixImpl const& A,
+ detail::BaseVectorImpl const& v )
+{
+ assert(A.columns() == v.size());
+
+ Vector result(A.rows(), 0.0);
+ for (size_t i = 0; i < A.rows(); ++i)
+ for (size_t j = 0; j < A.columns(); ++j)
+ result[i] += A(i,j) * v[j];
+
+ return result;
+}
+
+Matrix operator*( detail::BaseMatrixImpl const& A,
+ detail::BaseMatrixImpl const& B )
+{
+ assert(A.columns() == B.rows());
+
+ Matrix C(A.rows(), B.columns(), 0.0);
+ for (size_t i = 0; i < C.rows(); ++i)
+ for (size_t j = 0; j < C.columns(); ++j)
+ for (size_t k = 0; k < A.columns(); ++k)
+ C(i,j) += A(i,k) * B(k, j);
+
+ return C;
+}
+
+Matrix pseudo_inverse(detail::BaseMatrixImpl const& A)
+{
+
+ Matrix U(A);
+ Matrix V(A.columns(), A.columns());
+ Vector s(A.columns());
+ gsl_vector* work = gsl_vector_alloc(A.columns());
+
+ gsl_linalg_SV_decomp( U.get_gsl_matrix(),
+ V.get_gsl_matrix(),
+ s.get_gsl_vector(),
+ work );
+
+ Matrix P(A.columns(), A.rows(), 0.0);
+
+ int sz = s.size();
+ while ( sz-- > 0 && s[sz] == 0 ) {}
+ ++sz;
+ if (sz == 0) return P;
+ VectorView sv(s, sz);
+
+ for (size_t i = 0; i < sv.size(); ++i)
+ {
+ VectorView v = V.column_view(i);
+ v.scale(1/sv[i]);
+ for (size_t h = 0; h < P.rows(); ++h)
+ for (size_t k = 0; k < P.columns(); ++k)
+ P(h,k) += V(h,i) * U(k,i);
+ }
+
+ return P;
+}
+
+
+double trace (detail::BaseMatrixImpl const& A)
+{
+ if (A.rows() != A.columns())
+ {
+ THROW_RANGEERROR ("NL::Matrix: computing trace: "
+ "rows() != columns()");
+ }
+ double t = 0;
+ for (size_t i = 0; i < A.rows(); ++i)
+ {
+ t += A(i,i);
+ }
+ return t;
+}
+
+
+double det (detail::BaseMatrixImpl const& A)
+{
+ if (A.rows() != A.columns())
+ {
+ THROW_RANGEERROR ("NL::Matrix: computing determinant: "
+ "rows() != columns()");
+ }
+
+ Matrix LU(A);
+ int s;
+ gsl_permutation * p = gsl_permutation_alloc(LU.rows());
+ gsl_linalg_LU_decomp (LU.get_gsl_matrix(), p, &s);
+
+ double t = 1;
+ for (size_t i = 0; i < LU.rows(); ++i)
+ {
+ t *= LU(i,i);
+ }
+
+ gsl_permutation_free(p);
+ return t;
+}
+
+
+} } // end namespaces
+
+/*
+ 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/src/2geom/orphan-code/arc-length.cpp b/src/2geom/orphan-code/arc-length.cpp
new file mode 100644
index 0000000..3f72862
--- /dev/null
+++ b/src/2geom/orphan-code/arc-length.cpp
@@ -0,0 +1,292 @@
+/*
+ * arc-length.cpp
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * 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/arc-length.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/polynomial.h>
+using namespace Geom;
+
+/** Calculates the length of a cubic element through subdivision.
+ * The 'tol' parameter is the maximum error allowed. This is used to subdivide the curve where necessary.
+ */
+double cubic_length_subdividing(Path::Elem const & e, double tol) {
+ Point v[3];
+ for(int i = 0; i < 3; i++)
+ v[i] = e[i+1] - e[0];
+ Point orth = v[2]; // unit normal to path line
+ rot90(orth);
+ orth.normalize();
+ double err = fabs(dot(orth, v[1])) + fabs(dot(orth, v[0]));
+ if(err < tol) {
+ return distance(e.first(), e.last()); // approximately a line
+ } else {
+ Point mid[3];
+ double result;
+ for(int i = 0; i < 3; i++)
+ mid[i] = lerp(0.5, e[i], e[i+1]);
+ Point midmid[2];
+ for(int i = 0; i < 2; i++)
+ midmid[i] = lerp(0.5, mid[i], mid[i+1]);
+ Point midmidmid = lerp(0.5, midmid[0], midmid[1]);
+ {
+ Point curve[4] = {e[0], mid[0], midmid[0], midmidmid};
+ Path::Elem e0(cubicto, std::vector<Point>::const_iterator(curve), std::vector<Point>::const_iterator(curve) + 4);
+ result = cubic_length_subdividing(e0, tol);
+ } {
+ Point curve[4] = {midmidmid, midmid[1], mid[2], e[3]};
+ Path::Elem e1(cubicto, std::vector<Point>::const_iterator(curve), std::vector<Point>::const_iterator(curve) + 4);
+ return result + cubic_length_subdividing(e1, tol);
+ }
+ }
+}
+
+/** Calculates the length of a path through iteration and subsequent subdivision.
+ * Currently handles cubic curves and lines.
+ * The 'tol' parameter is the maximum error allowed. This is used to subdivide the curve where necessary.
+ */
+double arc_length_subdividing(Path const & p, double tol) {
+ double result = 0;
+
+ for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) {
+ if(dynamic_cast<LineTo *>(iter.cmd()))
+ result += distance((*iter).first(), (*iter).last());
+ else if(dynamic_cast<CubicTo *>(iter.cmd()))
+ result += cubic_length_subdividing(*iter, tol);
+ else
+ ;
+ }
+
+ return result;
+}
+
+
+#ifdef HAVE_GSL
+#include <gsl/gsl_integration.h>
+static double poly_length_integrating(double t, void* param) {
+ Poly* pc = (Poly*)param;
+ return hypot(pc[0].eval(t), pc[1].eval(t));
+}
+
+/** Calculates the length of a path Element through gsl integration.
+ \param pe the Element.
+ \param t the parametric input 0 to 1 which specifies the amount of the curve to use.
+ \param tol the maximum error allowed.
+ \param result variable to be incremented with the length of the path
+ \param abs_error variable to be incremented with the estimated error
+*/
+void arc_length_integrating(Path::Elem pe, double t, double tol, double &result, double &abs_error) {
+ if(dynamic_cast<LineTo *>(iter.cmd()))
+ result += distance(pe.first(), pe.last()) * t;
+ else if(dynamic_cast<QuadTo *>(iter.cmd()) ||
+ dynamic_cast<CubicTo *>(iter.cmd())) {
+ Poly B[2] = {get_parametric_poly(pe, X), get_parametric_poly(pe, Y)};
+ for(int i = 0; i < 2; i++)
+ B[i] = derivative(B[i]);
+
+ gsl_function F;
+ gsl_integration_workspace * w
+ = gsl_integration_workspace_alloc (20);
+ F.function = &poly_length_integrating;
+ F.params = (void*)B;
+ double quad_result, err;
+ /* We could probably use the non adaptive code here if we removed any cusps first. */
+ int returncode =
+ gsl_integration_qag (&F, 0, t, 0, tol, 20,
+ GSL_INTEG_GAUSS21, w, &quad_result, &err);
+
+ abs_error += err;
+ result += quad_result;
+ } else
+ return;
+}
+
+/** Calculates the length of a Path through gsl integration. The parameter 'tol' is the maximum error allowed. */
+double arc_length_integrating(Path const & p, double tol) {
+ double result = 0, abserr = 0;
+
+ for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) {
+ arc_length_integrating(*iter, 1.0, tol, result, abserr);
+ }
+ //printf("got %g with err %g\n", result, abserr);
+
+ return result;
+}
+
+/** Calculates the arc length to a specific location on the path. The parameter 'tol' is the maximum error allowed. */
+double arc_length_integrating(Path const & p, Path::Location const & pl, double tol) {
+ double result = 0, abserr = 0;
+ ptrdiff_t offset = pl.it - p.begin();
+
+ assert(offset >= 0);
+ assert(offset < p.size());
+
+ for(Path::const_iterator iter(p.begin()), end(p.end());
+ (iter != pl.it); ++iter) {
+ arc_length_integrating(*iter, 1.0, tol, result, abserr);
+ }
+ arc_length_integrating(*pl.it, pl.t, tol, result, abserr);
+
+ return result;
+}
+
+/* We use a somewhat surprising result for this that s'(t) = |p'(t)|
+ Thus, we can use a derivative based root finder.
+*/
+
+#include <stdio.h>
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_roots.h>
+
+struct arc_length_params
+{
+ Path::Elem pe;
+ double s,tol, result, abs_error;
+ double left, right;
+};
+
+double
+arc_length (double t, void *params)
+{
+ struct arc_length_params *p
+ = (struct arc_length_params *) params;
+
+ double result = 0, abs_error = 0;
+ if(t < 0) t = 0;
+ if(t > 1) t = 1;
+ if(!((t >= 0) && (t <= 1))) {
+ printf("t = %g\n", t);
+ }
+ assert((t >= 0) && (t <= 1));
+ arc_length_integrating(p->pe, t, p->tol, result, abs_error);
+ return result - p->s ;
+}
+
+double
+arc_length_deriv (double t, void *params)
+{
+ struct arc_length_params *p
+ = (struct arc_length_params *) params;
+
+ Point pos, tgt, acc;
+ p->pe.point_tangent_acc_at(t, pos, tgt, acc);
+ return L2(tgt);
+}
+
+void
+arc_length_fdf (double t, void *params,
+ double *y, double *dy)
+{
+ *y = arc_length(t, params);
+ *dy = arc_length_deriv(t, params);
+}
+
+double polish_brent(double t, arc_length_params &alp) {
+ int status;
+ int iter = 0, max_iter = 10;
+ const gsl_root_fsolver_type *T;
+ gsl_root_fsolver *solver;
+ double x_lo = 0.0, x_hi = 1.0;
+ gsl_function F;
+
+ F.function = &arc_length;
+ F.params = &alp;
+
+ T = gsl_root_fsolver_brent;
+ solver = gsl_root_fsolver_alloc (T);
+ gsl_root_fsolver_set (solver, &F, x_lo, x_hi);
+
+ do
+ {
+ iter++;
+ status = gsl_root_fsolver_iterate (solver);
+ t = gsl_root_fsolver_root (solver);
+ x_lo = gsl_root_fsolver_x_lower (solver);
+ x_hi = gsl_root_fsolver_x_upper (solver);
+ status = gsl_root_test_interval (x_lo, x_hi,
+ 0, alp.tol);
+
+ //if (status == GSL_SUCCESS)
+ // printf ("Converged:\n");
+
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+ return t;
+}
+
+double polish (double t, arc_length_params &alp) {
+ int status;
+ int iter = 0, max_iter = 5;
+ const gsl_root_fdfsolver_type *T;
+ gsl_root_fdfsolver *solver;
+ double t0;
+ gsl_function_fdf FDF;
+
+ FDF.f = &arc_length;
+ FDF.df = &arc_length_deriv;
+ FDF.fdf = &arc_length_fdf;
+ FDF.params = &alp;
+
+ T = gsl_root_fdfsolver_newton;
+ solver = gsl_root_fdfsolver_alloc (T);
+ gsl_root_fdfsolver_set (solver, &FDF, t);
+
+ do
+ {
+ iter++;
+ status = gsl_root_fdfsolver_iterate (solver);
+ t0 = t;
+ t = gsl_root_fdfsolver_root (solver);
+ status = gsl_root_test_delta (t, t0, 0, alp.tol);
+
+ if (status == GSL_SUCCESS)
+ ;//printf ("Converged:\n");
+
+ printf ("%5d %10.7f %+10.7f\n",
+ iter, t, t - t0);
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+ return t;
+}
+
+
+#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/src/2geom/orphan-code/chebyshev.cpp b/src/2geom/orphan-code/chebyshev.cpp
new file mode 100644
index 0000000..c886daf
--- /dev/null
+++ b/src/2geom/orphan-code/chebyshev.cpp
@@ -0,0 +1,126 @@
+#include <2geom/chebyshev.h>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-poly.h>
+
+#include <vector>
+using std::vector;
+
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_chebyshev.h>
+
+namespace Geom{
+
+SBasis cheb(unsigned n) {
+ static std::vector<SBasis> basis;
+ if(basis.empty()) {
+ basis.push_back(Linear(1,1));
+ basis.push_back(Linear(0,1));
+ }
+ for(unsigned i = basis.size(); i <= n; i++) {
+ basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);
+ }
+
+ return basis[n];
+}
+
+SBasis cheb_series(unsigned n, double* cheb_coeff) {
+ SBasis r;
+ for(unsigned i = 0; i < n; i++) {
+ double cof = cheb_coeff[i];
+ //if(i == 0)
+ //cof /= 2;
+ r += cheb(i)*cof;
+ }
+
+ return r;
+}
+
+SBasis clenshaw_series(unsigned m, double* cheb_coeff) {
+ /** b_n = a_n
+ b_n-1 = 2*x*b_n + a_n-1
+ b_n-k = 2*x*b_{n-k+1} + a_{n-k} - b_{n - k + 2}
+ b_0 = x*b_1 + a_0 - b_2
+ */
+
+ double a = -1, b = 1;
+ SBasis d, dd;
+ SBasis y = (Linear(0, 2) - (a+b)) / (b-a);
+ SBasis y2 = 2*y;
+ for(int j = m - 1; j >= 1; j--) {
+ SBasis sv = d;
+ d = y2*d - dd + cheb_coeff[j];
+ dd = sv;
+ }
+
+ return y*d - dd + 0.5*cheb_coeff[0];
+}
+
+SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p) {
+ gsl_cheb_series *cs = gsl_cheb_alloc (order+2);
+
+ gsl_function F;
+
+ F.function = f;
+ F.params = p;
+
+ gsl_cheb_init (cs, &F, in[0], in[1]);
+
+ SBasis r = compose(clenshaw_series(order, cs->c), Linear(-1,1));
+
+ gsl_cheb_free (cs);
+ return r;
+}
+
+struct wrap {
+ double (*f)(double,void*);
+ void* pp;
+ double fa, fb;
+ Interval in;
+};
+
+double f_interp(double x, void* p) {
+ struct wrap *wr = (struct wrap *)p;
+ double z = (x - wr->in[0]) / (wr->in[1] - wr->in[0]);
+ return (wr->f)(x, wr->pp) - ((1 - z)*wr->fa + z*wr->fb);
+}
+
+SBasis chebyshev_approximant_interpolating (double (*f)(double,void*),
+ int order, Interval in, void* p) {
+ double fa = f(in[0], p);
+ double fb = f(in[1], p);
+ struct wrap wr;
+ wr.fa = fa;
+ wr.fb = fb;
+ wr.in = in;
+ printf("%f %f\n", fa, fb);
+ wr.f = f;
+ wr.pp = p;
+ return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb);
+}
+
+SBasis chebyshev(unsigned n) {
+ static std::vector<SBasis> basis;
+ if(basis.empty()) {
+ basis.push_back(Linear(1,1));
+ basis.push_back(Linear(0,1));
+ }
+ for(unsigned i = basis.size(); i <= n; i++) {
+ basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);
+ }
+
+ return basis[n];
+}
+
+};
+
+/*
+ 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/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp b/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp
new file mode 100644
index 0000000..c55f623
--- /dev/null
+++ b/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp
@@ -0,0 +1,560 @@
+
+/*
+ * Find intersecions between two Bezier curves.
+ * The intersection points are found by using Bezier clipping.
+ *
+ * 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/basic-intersection.h>
+#include <2geom/bezier.h>
+#include <2geom/interval.h>
+#include <2geom/convex-hull.h>
+
+
+#include <vector>
+#include <utility>
+#include <iomanip>
+
+
+namespace Geom {
+
+namespace detail { namespace bezier_clipping {
+
+
+////////////////////////////////////////////////////////////////////////////////
+// for debugging
+//
+
+inline
+void print(std::vector<Point> const& cp)
+{
+ for (size_t i = 0; i < cp.size(); ++i)
+ std::cerr << i << " : " << cp[i] << std::endl;
+}
+
+template< class charT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const Interval & I)
+{
+ os << "[" << I.min() << ", " << I.max() << "]";
+ return os;
+}
+
+inline
+double angle (std::vector<Point> const& A)
+{
+ size_t n = A.size() -1;
+ double a = std::atan2(A[n][Y] - A[0][Y], A[n][X] - A[0][X]);
+ return (180 * a / M_PI);
+}
+
+inline
+size_t get_precision(Interval const& I)
+{
+ double d = I.extent();
+ double e = 1, p = 1;
+ size_t n = 0;
+ while (n < 16 && (are_near(d, 0, e)))
+ {
+ p *= 10;
+ e = 1 /p;
+ ++n;
+ }
+ return n;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+/*
+ * return true if all the Bezier curve control points are near,
+ * false otherwise
+ */
+inline
+bool is_constant(std::vector<Point> const& A, double precision = EPSILON)
+{
+ for (unsigned int i = 1; i < A.size(); ++i)
+ {
+ if(!are_near(A[i], A[0], precision))
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Make up an orientation line using the control points c[i] and c[j]
+ * the line is returned in the output parameter "l" in the form of a 3 element
+ * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized.
+ */
+inline
+void orientation_line (std::vector<double> & l,
+ std::vector<Point> const& c,
+ size_t i, size_t j)
+{
+ l[0] = c[j][Y] - c[i][Y];
+ l[1] = c[i][X] - c[j][X];
+ l[2] = cross(c[i], c[j]);
+ double length = std::sqrt(l[0] * l[0] + l[1] * l[1]);
+ assert (length != 0);
+ l[0] /= length;
+ l[1] /= length;
+ l[2] /= length;
+}
+
+/*
+ * Pick up an orientation line for the Bezier curve "c" and return it in
+ * the output parameter "l"
+ */
+inline
+void pick_orientation_line (std::vector<double> & l,
+ std::vector<Point> const& c)
+{
+ size_t i = c.size();
+ while (--i > 0 && are_near(c[0], c[i]))
+ {}
+ if (i == 0)
+ {
+ // this should never happen because when a new curve portion is created
+ // we check that it is not constant;
+ // however this requires that the precision used in the is_constant
+ // routine has to be the same used here in the are_near test
+ assert(i != 0);
+ }
+ orientation_line(l, c, 0, i);
+ //std::cerr << "i = " << i << std::endl;
+}
+
+/*
+ * Compute the signed distance of the point "P" from the normalized line l
+ */
+inline
+double distance (Point const& P, std::vector<double> const& l)
+{
+ return l[X] * P[X] + l[Y] * P[Y] + l[2];
+}
+
+/*
+ * Compute the min and max distance of the control points of the Bezier
+ * curve "c" from the normalized orientation line "l".
+ * This bounds are returned through the output Interval parameter"bound".
+ */
+inline
+void fat_line_bounds (Interval& bound,
+ std::vector<Point> const& c,
+ std::vector<double> const& l)
+{
+ bound[0] = 0;
+ bound[1] = 0;
+ double d;
+ for (size_t i = 0; i < c.size(); ++i)
+ {
+ d = distance(c[i], l);
+ if (bound[0] > d) bound[0] = d;
+ if (bound[1] < d) bound[1] = d;
+ }
+}
+
+/*
+ * return the x component of the intersection point between the line
+ * passing through points p1, p2 and the line Y = "y"
+ */
+inline
+double intersect (Point const& p1, Point const& p2, double y)
+{
+ // we are sure that p2[Y] != p1[Y] because this routine is called
+ // only when the lower or the upper bound is crossed
+ double dy = (p2[Y] - p1[Y]);
+ double s = (y - p1[Y]) / dy;
+ return (p2[X]-p1[X])*s + p1[X];
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the fat line defined by the orientation
+ * line "l" and the interval range "bound", the new parameter interval for
+ * the clipped curve is returned through the output parameter "dom"
+ */
+void clip (Interval& dom,
+ std::vector<Point> const& B,
+ std::vector<double> const& l,
+ Interval const& bound)
+{
+ double n = B.size() - 1; // number of sub-intervals
+ std::vector<Point> D; // distance curve control points
+ D.reserve (B.size());
+ double d;
+ for (size_t i = 0; i < B.size(); ++i)
+ {
+ d = distance (B[i], l);
+ D.push_back (Point(i/n, d));
+ }
+ //print(D);
+ ConvexHull chD(D);
+ std::vector<Point> & p = chD.boundary; // convex hull vertices
+
+ //print(p);
+
+ bool plower, phigher;
+ bool clower, chigher;
+ double t, tmin = 1, tmax = 0;
+ //std::cerr << "bound : " << bound << std::endl;
+
+ plower = (p[0][Y] < bound.min());
+ phigher = (p[0][Y] > bound.max());
+ if (!(plower || phigher)) // inside the fat line
+ {
+ if (tmin > p[0][X]) tmin = p[0][X];
+ if (tmax < p[0][X]) tmax = p[0][X];
+ //std::cerr << "0 : inside " << p[0]
+ // << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+
+ for (size_t i = 1; i < p.size(); ++i)
+ {
+ clower = (p[i][Y] < bound.min());
+ chigher = (p[i][Y] > bound.max());
+ if (!(clower || chigher)) // inside the fat line
+ {
+ if (tmin > p[i][X]) tmin = p[i][X];
+ if (tmax < p[i][X]) tmax = p[i][X];
+ //std::cerr << i << " : inside " << p[i]
+ // << " : tmin = " << tmin << ", tmax = " << tmax
+ // << std::endl;
+ }
+ if (clower != plower) // cross the lower bound
+ {
+ t = intersect(p[i-1], p[i], bound.min());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ plower = clower;
+ //std::cerr << i << " : lower " << p[i]
+ // << " : tmin = " << tmin << ", tmax = " << tmax
+ // << std::endl;
+ }
+ if (chigher != phigher) // cross the upper bound
+ {
+ t = intersect(p[i-1], p[i], bound.max());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ phigher = chigher;
+ //std::cerr << i << " : higher " << p[i]
+ // << " : tmin = " << tmin << ", tmax = " << tmax
+ // << std::endl;
+ }
+ }
+
+ // we have to test the closing segment for intersection
+ size_t last = p.size() - 1;
+ clower = (p[0][Y] < bound.min());
+ chigher = (p[0][Y] > bound.max());
+ if (clower != plower) // cross the lower bound
+ {
+ t = intersect(p[last], p[0], bound.min());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ //std::cerr << "0 : lower " << p[0]
+ // << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+ if (chigher != phigher) // cross the upper bound
+ {
+ t = intersect(p[last], p[0], bound.max());
+ if (tmin > t) tmin = t;
+ if (tmax < t) tmax = t;
+ //std::cerr << "0 : higher " << p[0]
+ // << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;
+ }
+
+ dom[0] = tmin;
+ dom[1] = tmax;
+}
+
+/*
+ * Compute the portion of the Bezier curve "B" wrt the interval "I"
+ */
+void portion (std::vector<Point> & B, Interval const& I)
+{
+ Bezier::Order bo(B.size()-1);
+ Bezier Bx(bo), By(bo);
+ for (size_t i = 0; i < B.size(); ++i)
+ {
+ Bx[i] = B[i][X];
+ By[i] = B[i][Y];
+ }
+ Bx = portion(Bx, I.min(), I.max());
+ By = portion(By, I.min(), I.max());
+ assert (Bx.size() == By.size());
+ B.resize(Bx.size());
+ for (size_t i = 0; i < Bx.size(); ++i)
+ {
+ B[i][X] = Bx[i];
+ B[i][Y] = By[i];
+ }
+}
+
+/*
+ * Map the sub-interval I in [0,1] into the interval J and assign it to J
+ */
+inline
+void map_to(Interval & J, Interval const& I)
+{
+ double length = J.extent();
+ J[1] = I.max() * length + J[0];
+ J[0] = I.min() * length + J[0];
+}
+
+/*
+ * The interval [1,0] is used to represent the empty interval, this routine
+ * is just an helper function for creating such an interval
+ */
+inline
+Interval make_empty_interval()
+{
+ Interval I(0);
+ I[0] = 1;
+ return I;
+}
+
+
+
+
+const double MAX_PRECISION = 1e-8;
+const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8;
+const Interval UNIT_INTERVAL(0,1);
+const Interval EMPTY_INTERVAL = make_empty_interval();
+const Interval H1_INTERVAL(0, 0.5);
+const Interval H2_INTERVAL(0.5 + MAX_PRECISION, 1.0);
+
+/*
+ * intersection
+ *
+ * input:
+ * A, B: control point sets of two bezier curves
+ * domA, domB: real parameter intervals of the two curves
+ * precision: required computational precision of the returned parameter ranges
+ * output:
+ * domsA, domsB: sets of parameter intervals describing an intersection point
+ *
+ * The parameter intervals are computed by using a Bezier clipping algorithm,
+ * in case the clipping doesn't shrink the initial interval more than 20%,
+ * a subdivision step is performed.
+ * If during the computation one of the two curve interval length becomes less
+ * than MAX_PRECISION the routine exits independently by the precision reached
+ * in the computation of the other curve interval.
+ */
+void intersection (std::vector<Interval>& domsA,
+ std::vector<Interval>& domsB,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ Interval const& domA,
+ Interval const& domB,
+ double precision)
+{
+// std::cerr << ">> curve subdision performed <<" << std::endl;
+// std::cerr << "dom(A) : " << domA << std::endl;
+// std::cerr << "dom(B) : " << domB << std::endl;
+// std::cerr << "angle(A) : " << angle(A) << std::endl;
+// std::cerr << "angle(B) : " << angle(B) << std::endl;
+
+
+ if (precision < MAX_PRECISION)
+ precision = MAX_PRECISION;
+
+ std::vector<Point> pA = A;
+ std::vector<Point> pB = B;
+ std::vector<Point>* C1 = &pA;
+ std::vector<Point>* C2 = &pB;
+
+ Interval dompA = domA;
+ Interval dompB = domB;
+ Interval* dom1 = &dompA;
+ Interval* dom2 = &dompB;
+
+ std::vector<double> bl(3);
+ Interval bound, dom;
+
+
+ size_t iter = 0;
+ while (++iter < 100
+ && (dompA.extent() >= precision || dompB.extent() >= precision))
+ {
+// std::cerr << "iter: " << iter << std::endl;
+
+ pick_orientation_line(bl, *C1);
+ fat_line_bounds(bound, *C1, bl);
+ clip(dom, *C2, bl, bound);
+
+ // [1,0] is utilized to represent an empty interval
+ if (dom == EMPTY_INTERVAL)
+ {
+// std::cerr << "dom: empty" << std::endl;
+ return;
+ }
+// std::cerr << "dom : " << dom << std::endl;
+
+ // all other cases where dom[0] > dom[1] are invalid
+ if (dom.min() > dom.max())
+ {
+ assert(dom.min() < dom.max());
+ }
+
+ map_to(*dom2, dom);
+
+ // it's better to stop before losing computational precision
+ if (dom2->extent() <= MAX_PRECISION)
+ {
+// std::cerr << "beyond max precision limit" << std::endl;
+ break;
+ }
+
+ portion(*C2, dom);
+ if (is_constant(*C2))
+ {
+// std::cerr << "new curve portion is constant" << std::endl;
+ break;
+ }
+ // if we have clipped less than 20% than we need to subdive the curve
+ // with the largest domain into two sub-curves
+ if (dom.extent() > MIN_CLIPPED_SIZE_THRESHOLD)
+ {
+// std::cerr << "clipped less than 20% : " << dom.extent() << std::endl;
+// std::cerr << "angle(pA) : " << angle(pA) << std::endl;
+// std::cerr << "angle(pB) : " << angle(pB) << std::endl;
+
+ std::vector<Point> pC1, pC2;
+ Interval dompC1, dompC2;
+ if (dompA.extent() > dompB.extent())
+ {
+ if ((dompA.extent() / 2) < MAX_PRECISION)
+ {
+ break;
+ }
+ pC1 = pC2 = pA;
+ portion(pC1, H1_INTERVAL);
+ portion(pC2, H2_INTERVAL);
+ dompC1 = dompC2 = dompA;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ intersection(domsA, domsB, pC1, pB, dompC1, dompB, precision);
+ intersection(domsA, domsB, pC2, pB, dompC2, dompB, precision);
+ }
+ else
+ {
+ if ((dompB.extent() / 2) < MAX_PRECISION)
+ {
+ break;
+ }
+ pC1 = pC2 = pB;
+ portion(pC1, H1_INTERVAL);
+ portion(pC2, H2_INTERVAL);
+ dompC1 = dompC2 = dompB;
+ map_to(dompC1, H1_INTERVAL);
+ map_to(dompC2, H2_INTERVAL);
+ intersection(domsB, domsA, pC1, pA, dompC1, dompA, precision);
+ intersection(domsB, domsA, pC2, pA, dompC2, dompA, precision);
+ }
+ return;
+ }
+
+ using std::swap;
+ swap(C1, C2);
+ swap(dom1, dom2);
+// std::cerr << "dom(pA) : " << dompA << std::endl;
+// std::cerr << "dom(pB) : " << dompB << std::endl;
+ }
+ domsA.push_back(dompA);
+ domsB.push_back(dompB);
+}
+
+} /* end namespace bezier_clipping */ } /* end namespace detail */
+
+
+/*
+ * find_intersection
+ *
+ * input: A, B - set of control points of two Bezier curve
+ * input: precision - required precision of computation
+ * output: xs - set of pairs of parameter values
+ * at which crossing happens
+ *
+ * This routine is based on the Bezier Clipping Algorithm,
+ * see: Sederberg - Computer Aided Geometric Design
+ */
+void find_intersections (std::vector< std::pair<double, double> > & xs,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B,
+ double precision)
+{
+ std::cout << "find_intersections: intersection-by-clipping.cpp version\n";
+// std::cerr << std::fixed << std::setprecision(16);
+
+ using detail::bezier_clipping::get_precision;
+ using detail::bezier_clipping::operator<<;
+ using detail::bezier_clipping::intersection;
+ using detail::bezier_clipping::UNIT_INTERVAL;
+
+ std::pair<double, double> ci;
+ std::vector<Interval> domsA, domsB;
+ intersection (domsA, domsB, A, B, UNIT_INTERVAL, UNIT_INTERVAL, precision);
+ if (domsA.size() != domsB.size())
+ {
+ assert (domsA.size() == domsB.size());
+ }
+ xs.clear();
+ xs.reserve(domsA.size());
+ for (size_t i = 0; i < domsA.size(); ++i)
+ {
+// std::cerr << i << " : domA : " << domsA[i] << std::endl;
+// std::cerr << "precision A: " << get_precision(domsA[i]) << std::endl;
+// std::cerr << i << " : domB : " << domsB[i] << std::endl;
+// std::cerr << "precision B: " << get_precision(domsB[i]) << std::endl;
+
+ ci.first = domsA[i].middle();
+ ci.second = domsB[i].middle();
+ xs.push_back(ci);
+ }
+}
+
+} // 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/src/2geom/orphan-code/intersection-by-smashing.cpp b/src/2geom/orphan-code/intersection-by-smashing.cpp
new file mode 100644
index 0000000..02e44b1
--- /dev/null
+++ b/src/2geom/orphan-code/intersection-by-smashing.cpp
@@ -0,0 +1,349 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/orphan-code/intersection-by-smashing.h>
+
+#include <cstdlib>
+#include <cstdio>
+#include <vector>
+#include <algorithm>
+
+namespace Geom {
+using namespace Geom;
+
+/*
+ * Computes the top and bottom boundaries of the L_\infty neighborhood
+ * of a curve. The curve is supposed to be a graph over the x-axis.
+ */
+static
+void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){
+ double signx = ( f[X].at0() > f[X].at1() )? -1 : 1;
+ double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1;
+
+ Piecewise<D2<SBasis> > top, bot;
+ top = Piecewise<D2<SBasis> > (f);
+ top.cuts.insert( top.cuts.end(), 2);
+ top.segs.insert( top.segs.end(), D2<SBasis>(SBasis(Linear( f[X].at1(), f[X].at1()+2*tol*signx)),
+ SBasis(Linear( f[Y].at1() )) ));
+ bot = Piecewise<D2<SBasis> >(f);
+ bot.cuts.insert( bot.cuts.begin(), - 1 );
+ bot.segs.insert( bot.segs.begin(), D2<SBasis>(SBasis(Linear( f[X].at0()-2*tol*signx, f[X].at0())),
+ SBasis(Linear( f[Y].at0() )) ));
+ top += Point(-tol*signx, tol);
+ bot += Point( tol*signx, -tol);
+
+ if ( signy < 0 ){
+ std::swap( top, bot );
+ top += Point( 0, 2*tol);
+ bot += Point( 0, -2*tol);
+ }
+ topside = make_cuts_independent(top);
+ botside = make_cuts_independent(bot);
+}
+
+
+/*
+ * Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f.
+ * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol].
+ * if not, it is [f(t+tol)-tol, f(t-tol)+tol].
+ */
+static
+void computeLinfinityNeighborhood( Piecewise<SBasis> const &f, double tol, Piecewise<SBasis> &top, Piecewise<SBasis> &bot){
+ top = f + tol;
+ top.offsetDomain( - tol );
+ top.cuts.insert( top.cuts.end(), f.domain().max() + tol);
+ top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) );
+
+ bot = f - tol;
+ bot.offsetDomain( tol );
+ bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol);
+ bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) );
+
+ if (f.firstValue() > f.lastValue()) {
+ std::swap(top, bot);
+ top += 2 * tol;
+ bot -= 2 * tol;
+ }
+}
+
+/*
+ * Returns the intervals over which the curve keeps its slope
+ * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x.
+ */
+std::vector<Interval> monotonicSplit(D2<SBasis> const &p){
+ std::vector<Interval> result;
+
+ D2<SBasis> v = derivative(p);
+
+ std::vector<double> someroots;
+ std::vector<double> cuts (2,0.);
+ cuts[1] = 1.;
+
+ someroots = roots(v[X]);
+ cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
+
+ someroots = roots(v[Y]);
+ cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
+
+ //we could split in the middle to avoid computing roots again...
+ someroots = roots(v[X]-v[Y]);
+ cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
+
+ someroots = roots(v[X]+v[Y]);
+ cuts.insert( cuts.end(), someroots.begin(), someroots.end() );
+
+ sort(cuts.begin(),cuts.end());
+ unique(cuts.begin(), cuts.end() );
+
+ for (unsigned i=1; i<cuts.size(); i++){
+ result.push_back( Interval( cuts[i-1], cuts[i] ) );
+ }
+ return result;
+}
+
+//std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){
+// std::vector<Interval> x_in_reg = level_set( f[X], region[X] );
+// std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] );
+// std::vector<Interval> result = intersect ( x_in_reg, y_in_reg );
+// return result;
+//}
+
+/*TODO: remove this!!!
+ * the minimum would be to move it to piecewise.h but this would be stupid.
+ * The best would be to let 'compose' be aware of extension modes (constant, linear, polynomial..)
+ * (I think the extension modes (at start and end) should be properties of the pwsb).
+ */
+static
+void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){
+ if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb?
+ f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width );
+ f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) );
+ f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width );
+ f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) );
+}
+
+static
+bool compareIntersectionsTimesX( SmashIntersection const &inter1, SmashIntersection const &inter2 ){
+ return inter1.times[X].min() < inter2.times[Y].min();
+}
+/*Fuse contiguous intersection domains
+ *
+ */
+static
+void cleanup_and_fuse( std::vector<SmashIntersection> &inters ){
+ std::sort( inters.begin(), inters.end(), compareIntersectionsTimesX);
+ for (unsigned i=0; i < inters.size(); i++ ){
+ for (unsigned j=i+1; j < inters.size() && inters[i].times[X].intersects( inters[j].times[X]) ; j++ ){
+ if (inters[i].times[Y].intersects( inters[j].times[Y] ) ){
+ inters[i].times.unionWith(inters[j].times);
+ inters[i].bbox.unionWith(inters[j].bbox);
+ inters.erase( inters.begin() + j );
+ }
+ }
+ }
+}
+
+/* Computes the intersection of two sets given as (ordered) union intervals.
+ */
+static
+std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){
+ std::vector<Interval> result;
+ //TODO: use order to optimize this!
+ for (auto i : a){
+ for (auto j : b){
+ OptInterval c( i );
+ c &= j;
+ if ( c ) {
+ result.push_back( *c );
+ }
+ }
+ }
+ return result;
+}
+
+/* Returns the intervals over which the curves are in the
+ * tol-neighborhood one of the other for the L_\infty metric.
+ * WARNING: each curve is supposed to be a graph over x or y axis
+ * (but not necessarily the same axis for both) and the smaller
+ * the slope the better (typically <=45°).
+ */
+std::vector<SmashIntersection> monotonic_smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){
+ using std::swap;
+
+ // a and b or X and Y may have to be exchanged, so make local copies.
+ D2<SBasis> aa = a;
+ D2<SBasis> bb = b;
+ bool swapresult = false;
+ bool swapcoord = false;//debug only!
+
+ //if the (enlarged) bounding boxes don't intersect, stop.
+ OptRect abounds = bounds_fast( a );
+ OptRect bbounds = bounds_fast( b );
+ if ( !abounds || !bbounds ) return std::vector<SmashIntersection>();
+ abounds->expandBy(tol);
+ if ( !(abounds->intersects(*bbounds))){
+ return std::vector<SmashIntersection>();
+ }
+
+ //Choose the best curve to be re-parametrized by x or y values.
+ OptRect dabounds = bounds_exact(derivative(a));
+ OptRect dbbounds = bounds_exact(derivative(b));
+ if ( dbbounds->min().length() > dabounds->min().length() ){
+ aa=b;
+ bb=a;
+ swap( dabounds, dbbounds );
+ swapresult = true;
+ }
+
+ //Choose the best coordinate to use as new parameter
+ double dxmin = std::min( std::abs((*dabounds)[X].max()), std::abs((*dabounds)[X].min()) );
+ double dymin = std::min( std::abs((*dabounds)[Y].max()), std::abs((*dabounds)[Y].min()) );
+ if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0;
+ if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0;
+ assert (dxmin>=0 && dymin>=0);
+
+ if (dxmin < dymin) {
+ aa = D2<SBasis>( aa[Y], aa[X] );
+ bb = D2<SBasis>( bb[Y], bb[X] );
+ swapcoord = true;
+ }
+
+ //re-parametrize aa by the value of x.
+ Interval x_range_strict( aa[X].at0(), aa[X].at1() );
+ Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5);
+
+ //Compute top and bottom boundaries of the L^infty nbhd of aa.
+ Piecewise<SBasis> top_ay, bot_ay;
+ computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay);
+
+ Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol.
+ std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range );
+
+ // find times when bb is in the neighborhood of aa.
+ std::vector<Interval> tbs;
+ for (auto & i : bx_in_ax_range){
+ D2<Piecewise<SBasis> > bb_in;
+ bb_in[X] = Piecewise<SBasis> ( portion( bb[X], i ) );
+ bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], i) );
+ bb_in[X].setDomain( i );
+ bb_in[Y].setDomain( i );
+
+ Piecewise<SBasis> h;
+ Interval level;
+ h = bb_in[Y] - compose( top_ay, bb_in[X] );
+ level = Interval( -infinity(), 0 );
+ std::vector<Interval> rts_lo = level_set( h, level);
+ h = bb_in[Y] - compose( bot_ay, bb_in[X] );
+ level = Interval( 0, infinity());
+ std::vector<Interval> rts_hi = level_set( h, level);
+
+ std::vector<Interval> rts = intersect( rts_lo, rts_hi );
+ tbs.insert(tbs.end(), rts.begin(), rts.end() );
+ }
+
+ std::vector<SmashIntersection> result(tbs.size(), SmashIntersection());
+
+ /* for each solution I, find times when aa is in the neighborhood of bb(I).
+ * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this:
+ * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1).
+ * + faster computation.
+ * - implies little jumps depending on the subdivision of the input curve into monotonic pieces
+ * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-(
+ */
+ for (unsigned j=0; j<tbs.size(); j++){
+ result[j].times[Y] = tbs[j];
+ std::vector<Interval> tas;
+ //TODO: replace this by some option in the "compose(pw,pw)" method!
+ Piecewise<SBasis> fat_y_of_x = y_of_x;
+ prolongateByConstants( fat_y_of_x, 100*(1+tol) );
+
+ D2<Piecewise<SBasis> > top_b, bot_b;
+ D2<SBasis> bbj = portion( bb, tbs[j] );
+ computeLinfinityNeighborhood( bbj, tol, top_b, bot_b );
+
+ Piecewise<SBasis> h;
+ Interval level;
+ h = top_b[Y] - compose( fat_y_of_x, top_b[X] );
+ level = Interval( +infinity(), 0 );
+ std::vector<Interval> rts_top = level_set( h, level);
+ for (auto & idx : rts_top){
+ idx = Interval( top_b[X].valueAt( idx.min() ),
+ top_b[X].valueAt( idx.max() ) );
+ }
+ assert( rts_top.size() == 1 );
+
+ h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] );
+ level = Interval( 0, -infinity());
+ std::vector<Interval> rts_bot = level_set( h, level);
+ for (auto & idx : rts_bot){
+ idx = Interval( bot_b[X].valueAt( idx.min() ),
+ bot_b[X].valueAt( idx.max() ) );
+ }
+ assert( rts_bot.size() == 1 );
+ rts_top = intersect( rts_top, rts_bot );
+ assert (rts_top.size() == 1);
+ Interval x_dom = rts_top[0];
+
+ if ( x_dom.max() <= x_range_strict.min() ){
+ tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) );
+ }else if ( x_dom.min() >= x_range_strict.max() ){
+ tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) );
+ }else{
+ tas = level_set(aa[X], x_dom );
+ }
+ assert( tas.size()==1 );
+ result[j].times[X] = tas.front();
+
+ result[j].bbox = Rect( bbj.at0(), bbj.at1() );
+ Interval y_dom( aa[Y](result[j].times[X].min()), aa[Y](result[j].times[X].max()) );
+ result[j].bbox.unionWith( Rect( x_dom, y_dom ) );
+ }
+
+ if (swapresult) {
+ for (auto & i : result){
+ swap( i.times[X], i.times[Y]);
+ }
+ }
+ if (swapcoord) {
+ for (auto & i : result){
+ swap( i.bbox[X], i.bbox[Y] );
+ }
+ }
+
+ //TODO: cleanup result? fuse contiguous intersections?
+ return result;
+}
+
+std::vector<SmashIntersection> smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){
+ std::vector<SmashIntersection> result;
+
+ std::vector<Interval> acuts = monotonicSplit(a);
+ std::vector<Interval> bcuts = monotonicSplit(b);
+ for (auto & acut : acuts){
+ D2<SBasis> ai = portion( a, acut);
+ for (auto & bcut : bcuts){
+ D2<SBasis> bj = portion( b, bcut);
+ std::vector<SmashIntersection> ai_cap_bj = monotonic_smash_intersect( ai, bj, tol );
+ for (auto & k : ai_cap_bj){
+ k.times[X] = k.times[X] * acut.extent() + acut.min();
+ k.times[Y] = k.times[Y] * bcut.extent() + bcut.min();
+ }
+ result.insert( result.end(), ai_cap_bj.begin(), ai_cap_bj.end() );
+ }
+ }
+ cleanup_and_fuse( result );
+ return result;
+}
+
+}
+
+/*
+ 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/src/2geom/orphan-code/nearestpoint.cpp b/src/2geom/orphan-code/nearestpoint.cpp
new file mode 100644
index 0000000..870ed09
--- /dev/null
+++ b/src/2geom/orphan-code/nearestpoint.cpp
@@ -0,0 +1,405 @@
+/*
+** vim: ts=4 sw=4 et tw=0 wm=0
+**
+** RCS Information:
+** $Author: mjw $
+** $Revision: 1 $
+** $Date: 2006-03-28 15:59:38 +1100 (Tue, 28 Mar 2006) $
+**
+** Solving the Nearest Point-on-Curve Problem and
+** A Bezier Curve-Based Root-Finder
+** by Philip J. Schneider
+** from "Graphics Gems", Academic Press, 1990
+** modified by mwybrow, njh
+*/
+
+/* point_on_curve.c */
+
+static double SquaredLength(const Geom::Point a)
+{
+ return dot(a, a);
+}
+
+
+/*
+ * Forward declarations
+ */
+static int FindRoots(Geom::Point *w, int degree, double *t, int depth);
+static Geom::Point *ConvertToBezierForm( Geom::Point P, Geom::Point *V);
+static double ComputeXIntercept( Geom::Point *V, int degree);
+static int ControlPolygonFlatEnough( Geom::Point *V, int degree);
+static int CrossingCount(Geom::Point *V, int degree);
+static Geom::Point Bez(Geom::Point *V, int degree, double t, Geom::Point *Left,
+ Geom::Point *Right);
+
+int MAXDEPTH = 64; /* Maximum depth for recursion */
+
+#define EPSILON (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */
+#define DEGREE 3 /* Cubic Bezier curve */
+#define W_DEGREE 5 /* Degree of eqn to find roots of */
+
+
+/*
+ * NearestPointOnCurve :
+ * Compute the parameter value of the point on a Bezier
+ * curve segment closest to some arbtitrary, user-input point.
+ * Return the point on the curve at that parameter value.
+ *
+ Geom::Point P; The user-supplied point
+ Geom::Point *V; Control points of cubic Bezier
+*/
+double NearestPointOnCurve(Geom::Point P, Geom::Point *V)
+{
+ double t_candidate[W_DEGREE]; /* Possible roots */
+
+ /* Convert problem to 5th-degree Bezier form */
+ Geom::Point *w = ConvertToBezierForm(P, V);
+
+ /* Find all possible roots of 5th-degree equation */
+ int n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0);
+ std::free((char *)w);
+
+ /* Check distance to end of the curve, where t = 1 */
+ double dist = SquaredLength(P - V[DEGREE]);
+ double t = 1.0;
+
+ /* Find distances for candidate points */
+ for (int i = 0; i < n_solutions; i++) {
+ Geom::Point p = Bez(V, DEGREE, t_candidate[i], NULL, NULL);
+ double new_dist = SquaredLength(P - p);
+ if (new_dist < dist) {
+ dist = new_dist;
+ t = t_candidate[i];
+ }
+ }
+
+ /* Return the parameter value t */
+ return t;
+}
+
+
+/*
+ * ConvertToBezierForm :
+ * Given a point and a Bezier curve, generate a 5th-degree
+ * Bezier-format equation whose solution finds the point on the
+ * curve nearest the user-defined point.
+ */
+static Geom::Point *ConvertToBezierForm(
+ Geom::Point P, /* The point to find t for */
+ Geom::Point *V) /* The control points */
+{
+ Geom::Point c[DEGREE+1]; /* V(i)'s - P */
+ Geom::Point d[DEGREE]; /* V(i+1) - V(i) */
+ Geom::Point *w; /* Ctl pts of 5th-degree curve */
+ double cdTable[3][4]; /* Dot product of c, d */
+ static double z[3][4] = { /* Precomputed "z" for cubics */
+ {1.0, 0.6, 0.3, 0.1},
+ {0.4, 0.6, 0.6, 0.4},
+ {0.1, 0.3, 0.6, 1.0},
+ };
+
+
+ /*Determine the c's -- these are vectors created by subtracting*/
+ /* point P from each of the control points */
+ for (int i = 0; i <= DEGREE; i++) {
+ c[i] = V[i] - P;
+ }
+ /* Determine the d's -- these are vectors created by subtracting*/
+ /* each control point from the next */
+ for (int i = 0; i <= DEGREE - 1; i++) {
+ d[i] = 3.0*(V[i+1] - V[i]);
+ }
+
+ /* Create the c,d table -- this is a table of dot products of the */
+ /* c's and d's */
+ for (int row = 0; row <= DEGREE - 1; row++) {
+ for (int column = 0; column <= DEGREE; column++) {
+ cdTable[row][column] = dot(d[row], c[column]);
+ }
+ }
+
+ /* Now, apply the z's to the dot products, on the skew diagonal*/
+ /* Also, set up the x-values, making these "points" */
+ w = (Geom::Point *)malloc((unsigned)(W_DEGREE+1) * sizeof(Geom::Point));
+ for (int i = 0; i <= W_DEGREE; i++) {
+ w[i][Geom::Y] = 0.0;
+ w[i][Geom::X] = (double)(i) / W_DEGREE;
+ }
+
+ const int n = DEGREE;
+ const int m = DEGREE-1;
+ for (int k = 0; k <= n + m; k++) {
+ const int lb = std::max(0, k - m);
+ const int ub = std::min(k, n);
+ for (int i = lb; i <= ub; i++) {
+ int j = k - i;
+ w[i+j][Geom::Y] += cdTable[j][i] * z[j][i];
+ }
+ }
+
+ return w;
+}
+
+
+/*
+ * FindRoots :
+ * Given a 5th-degree equation in Bernstein-Bezier form, find
+ * all of the roots in the interval [0, 1]. Return the number
+ * of roots found.
+ */
+static int FindRoots(
+ Geom::Point *w, /* The control points */
+ int degree, /* The degree of the polynomial */
+ double *t, /* RETURN candidate t-values */
+ int depth) /* The depth of the recursion */
+{
+ int i;
+ Geom::Point Left[W_DEGREE+1], /* New left and right */
+ Right[W_DEGREE+1]; /* control polygons */
+ int left_count, /* Solution count from */
+ right_count; /* children */
+ double left_t[W_DEGREE+1], /* Solutions from kids */
+ right_t[W_DEGREE+1];
+
+ switch (CrossingCount(w, degree)) {
+ case 0 : { /* No solutions here */
+ return 0;
+ break;
+ }
+ case 1 : { /* Unique solution */
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth >= MAXDEPTH) {
+ t[0] = (w[0][Geom::X] + w[W_DEGREE][Geom::X]) / 2.0;
+ return 1;
+ }
+ if (ControlPolygonFlatEnough(w, degree)) {
+ t[0] = ComputeXIntercept(w, degree);
+ return 1;
+ }
+ break;
+ }
+ }
+
+ /* Otherwise, solve recursively after */
+ /* subdividing control polygon */
+ Bez(w, degree, 0.5, Left, Right);
+ left_count = FindRoots(Left, degree, left_t, depth+1);
+ right_count = FindRoots(Right, degree, right_t, depth+1);
+
+
+ /* Gather solutions together */
+ for (i = 0; i < left_count; i++) {
+ t[i] = left_t[i];
+ }
+ for (i = 0; i < right_count; i++) {
+ t[i+left_count] = right_t[i];
+ }
+
+ /* Send back total number of solutions */
+ return (left_count+right_count);
+}
+
+
+/*
+ * CrossingCount :
+ * Count the number of times a Bezier control polygon
+ * crosses the 0-axis. This number is >= the number of roots.
+ *
+ */
+static int CrossingCount(
+ Geom::Point *V, /* Control pts of Bezier curve */
+ int degree) /* Degree of Bezier curve */
+{
+ int n_crossings = 0; /* Number of zero-crossings */
+ int old_sign; /* Sign of coefficients */
+
+ old_sign = Geom::sgn(V[0][Geom::Y]);
+ for (int i = 1; i <= degree; i++) {
+ int sign = Geom::sgn(V[i][Geom::Y]);
+ if (sign != old_sign)
+ n_crossings++;
+ old_sign = sign;
+ }
+ return n_crossings;
+}
+
+
+
+/*
+ * ControlPolygonFlatEnough :
+ * Check if the control polygon of a Bezier curve is flat enough
+ * for recursive subdivision to bottom out.
+ *
+ */
+static int ControlPolygonFlatEnough(
+ Geom::Point *V, /* Control points */
+ int degree) /* Degree of polynomial */
+{
+ int i; /* Index variable */
+ double *distance; /* Distances from pts to line */
+ double max_distance_above; /* maximum of these */
+ double max_distance_below;
+ double error; /* Precision of root */
+ //Geom::Point t; /* Vector from V[0] to V[degree]*/
+ double intercept_1,
+ intercept_2,
+ left_intercept,
+ right_intercept;
+ double a, b, c; /* Coefficients of implicit */
+ /* eqn for line from V[0]-V[deg]*/
+
+ /* Find the perpendicular distance */
+ /* from each interior control point to */
+ /* line connecting V[0] and V[degree] */
+ distance = (double *)malloc((unsigned)(degree + 1) * sizeof(double));
+ {
+ double abSquared;
+
+ /* Derive the implicit equation for line connecting first */
+ /* and last control points */
+ a = V[0][Geom::Y] - V[degree][Geom::Y];
+ b = V[degree][Geom::X] - V[0][Geom::X];
+ c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y];
+
+ abSquared = (a * a) + (b * b);
+
+ for (i = 1; i < degree; i++) {
+ /* Compute distance from each of the points to that line */
+ distance[i] = a * V[i][Geom::X] + b * V[i][Geom::Y] + c;
+ if (distance[i] > 0.0) {
+ distance[i] = (distance[i] * distance[i]) / abSquared;
+ }
+ if (distance[i] < 0.0) {
+ distance[i] = -((distance[i] * distance[i]) / abSquared);
+ }
+ }
+ }
+
+
+ /* Find the largest distance */
+ max_distance_above = 0.0;
+ max_distance_below = 0.0;
+ for (i = 1; i < degree; i++) {
+ if (distance[i] < 0.0) {
+ max_distance_below = std::min(max_distance_below, distance[i]);
+ };
+ if (distance[i] > 0.0) {
+ max_distance_above = std::max(max_distance_above, distance[i]);
+ }
+ }
+ free((char *)distance);
+
+ {
+ double det;
+ double a1, b1, c1, a2, b2, c2;
+
+ /* Implicit equation for zero line */
+ a1 = 0.0;
+ b1 = 1.0;
+ c1 = 0.0;
+
+ /* Implicit equation for "above" line */
+ a2 = a;
+ b2 = b;
+ c2 = c + max_distance_above;
+
+ det = a1 * b2 - a2 * b1;
+
+ intercept_1 = (b1 * c2 - b2 * c1) / det;
+
+ /* Implicit equation for "below" line */
+ a2 = a;
+ b2 = b;
+ c2 = c + max_distance_below;
+
+ det = a1 * b2 - a2 * b1;
+
+ intercept_2 = (b1 * c2 - b2 * c1) / det;
+ }
+
+ /* Compute intercepts of bounding box */
+ left_intercept = std::min(intercept_1, intercept_2);
+ right_intercept = std::max(intercept_1, intercept_2);
+
+ error = 0.5 * (right_intercept-left_intercept);
+ if (error < EPSILON) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+
+/*
+ * ComputeXIntercept :
+ * Compute intersection of chord from first control point to last
+ * with 0-axis.
+ *
+ */
+static double ComputeXIntercept(
+ Geom::Point *V, /* Control points */
+ int degree) /* Degree of curve */
+{
+ const Geom::Point A = V[degree] - V[0];
+
+ return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y];
+}
+
+
+/*
+ * Bez :
+ * Evaluate a Bezier curve at a particular parameter value
+ * Fill in control points for resulting sub-curves if "Left" and
+ * "Right" are non-null.
+ *
+ */
+static Geom::Point Bez(
+ Geom::Point *V, /* Control pts */
+ int degree, /* Degree of bezier curve */
+ double t, /* Parameter value */
+ Geom::Point *Left, /* RETURN left half ctl pts */
+ Geom::Point *Right) /* RETURN right half ctl pts */
+{
+ Geom::Point Vtemp[W_DEGREE+1][W_DEGREE+1];
+
+
+ /* Copy control points */
+ for (int j =0; j <= degree; j++) {
+ Vtemp[0][j] = V[j];
+ }
+
+ /* Triangle computation */
+ for (int i = 1; i <= degree; i++) {
+ for (int j =0 ; j <= degree - i; j++) {
+ Vtemp[i][j] =
+ (1.0 - t) * Vtemp[i-1][j] + t * Vtemp[i-1][j+1];
+ }
+ }
+
+ if (Left != NULL) {
+ for (int j = 0; j <= degree; j++) {
+ Left[j] = Vtemp[j][0];
+ }
+ }
+ if (Right != NULL) {
+ for (int j = 0; j <= degree; j++) {
+ Right[j] = Vtemp[degree-j][j];
+ }
+ }
+
+ return (Vtemp[degree][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/src/2geom/orphan-code/redblack-toy.cpp b/src/2geom/orphan-code/redblack-toy.cpp
new file mode 100644
index 0000000..01ffa7d
--- /dev/null
+++ b/src/2geom/orphan-code/redblack-toy.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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/toys/path-cairo.h>
+#include <2geom/toys/toy-framework-2.h>
+
+#include <2geom/orphan-code/redblacktree.h>
+#include <2geom/orphan-code/redblacktree.cpp>
+
+#include <time.h>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+
+class RedBlackToy: public Toy
+{
+ PointSetHandle handle_set;
+ Geom::Point starting_point; // during click and drag: start point of click
+ Geom::Point ending_point; // during click and drag: end point of click (release)
+ Geom::Point highlight_point; // not used
+
+ Geom::RedBlackTree rbtree_x;
+ RedBlack* search_result;
+ RedBlack temp_deleted_node;
+
+ // colors we are going to use for different purposes
+ colour color_rect, color_rect_guide; // black(a=0.6), black
+ colour color_select_area, color_select_area_guide; // red(a=0.6), red
+
+ int alter_existing_rect;
+ int add_new_rect;
+
+ Rect rect_chosen; // the rectangle of the search area
+ Rect dummy_draw; // the "helper" rectangle that is shown during the click and drag (before the mouse release)
+ int mode; // insert/alter, search, delete modes
+
+ // printing of the tree
+ int help_counter; // the "x" of the label of each node
+ static const int label_size = 15 ; // size the label of each node
+
+ // used for the keys that switch between modes
+ enum menu_item_t
+ {
+ INSERT = 0,
+ DELETE,
+ SEARCH,
+ TOTAL_ITEMS // this one must be the last item
+ };
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ cairo_set_line_width( cr, 1 );
+
+ // draw the rects that we have in the handles
+ for( unsigned i=0; i<handle_set.pts.size(); i=i+2 ){
+ Rect r1( handle_set.pts[i], handle_set.pts[i+1] );
+ cairo_rectangle( cr, r1 );
+ }
+ cairo_set_source_rgba( cr, color_rect);
+ cairo_stroke( cr );
+
+ // draw a rect if we click & drag (so that we know what we are going to create)
+ if(add_new_rect){
+ dummy_draw = Rect( starting_point, ending_point );
+ cairo_rectangle( cr, dummy_draw );
+ if( mode == 0){
+ cairo_set_source_rgba( cr, color_rect_guide);
+ }
+ else if( mode == 1){
+ cairo_set_source_rgba( cr, color_select_area_guide );
+ }
+ cairo_stroke( cr );
+ }
+
+ // draw a rect for the search area
+ cairo_rectangle( cr, rect_chosen );
+ cairo_set_source_rgba( cr, color_select_area);
+ cairo_stroke( cr );
+
+ Toy::draw( cr, notify, width, height, save,timer_stream );
+ draw_tree_in_toy( cr ,rbtree_x.root, 0);
+ help_counter=0;
+ }
+
+ void mouse_moved(GdkEventMotion* e){
+ if( !( alter_existing_rect && mode == 1 ) ){
+ Toy::mouse_moved(e);
+ }
+
+ if(add_new_rect){
+ ending_point = Point(e->x, e->y);
+ }
+ }
+
+ void mouse_pressed(GdkEventButton* e) {
+ Toy::mouse_pressed(e);
+ if(e->button == 1){ // left mouse button
+ if( mode == 0 ){ // mode: insert / alter
+ if(!selected) {
+ starting_point = Point(e->x, e->y);
+ ending_point = starting_point;
+ add_new_rect = 1;
+ }
+ else
+ {
+ // TODO find the selected rect
+ // ideas : from Handle *selected ???
+ //std::cout <<find_selected_rect(selected) << std::endl ;
+ alter_existing_rect = 1;
+ }
+ }
+ else if( mode == 1 ){ // mode: search
+ if(!selected) {
+ starting_point = Point(e->x, e->y);
+ ending_point = starting_point;
+ add_new_rect = 1;
+ }
+ else{
+ alter_existing_rect = 1;
+ }
+ }
+ else if( mode == 2) { // mode: delete
+ }
+ }
+ else if(e->button == 2){ //middle button
+ }
+ else if(e->button == 3){ //right button
+ }
+ }
+
+ virtual void mouse_released(GdkEventButton* e) {
+ Toy::mouse_released(e);
+ if( e->button == 1 ) { //left mouse button
+ if( mode == 0) { // mode: insert / alter
+ if( add_new_rect ){
+ ending_point = Point(e->x, e->y);
+ handle_set.push_back(starting_point);
+ handle_set.push_back(ending_point);
+ insert_in_tree_the_last_rect();
+ add_new_rect = 0;
+ }
+ else if( alter_existing_rect ){
+ //TODO update rect (and tree)
+ // delete selected rect
+ // insert altered
+ alter_existing_rect = 0;
+ }
+ }
+ else if( mode == 1 ){ // mode: search
+ if( add_new_rect ){
+ ending_point = Point(e->x, e->y);
+ rect_chosen = Rect(starting_point, ending_point);
+
+ // search in the X axis
+ Coord a = rect_chosen[0].min();
+ Coord b = rect_chosen[0].max();
+ search_result = rbtree_x.search( Interval( a, b ) );
+ if(search_result){
+ std::cout << "Found: (" << search_result->data << ": " << search_result->key()
+ << ", " << search_result->high() << " : " << search_result->subtree_max << ") "
+ << std::endl;
+ }
+ else{
+ std::cout << "Nothing found..."<< std::endl;
+ }
+ add_new_rect = 0;
+ }
+ else if(alter_existing_rect){
+ // do nothing
+ alter_existing_rect = 0;
+ }
+ }
+ else if( mode == 2) { // mode: delete
+
+ }
+ }
+ else if(e->button == 2){ //middle button
+ }
+ else if(e->button == 3){ //right button
+
+ }
+ }
+
+
+ void key_hit(GdkEventKey *e)
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ mode = 0;
+ break;
+ case 'B':
+ mode = 1;
+ break;
+ case 'C':
+ mode = 2;
+ break;
+ }
+ //redraw();
+ }
+
+ void insert_in_tree_the_last_rect(){
+ unsigned i = handle_set.pts.size() - 2;
+ Rect r1(handle_set.pts[i], handle_set.pts[i+1]);
+ // insert in X axis tree
+ rbtree_x.insert(r1, i, 0);
+ rbtree_x.print_tree();
+ };
+
+ void draw_tree_in_toy(cairo_t* cr, Geom::RedBlack* n, int depth = 0) {
+ if(n){
+ if(n->left){
+ draw_tree_in_toy(cr, n->left, depth+1);
+ }
+ help_counter += 1;
+ //drawthisnode(cr, x*10, depth*10);
+ if(n->isRed){
+ cairo_set_source_rgba (cr, color_select_area_guide);
+ }
+ else{
+ cairo_set_source_rgba (cr, color_rect_guide);
+ }
+
+ cairo_stroke(cr);
+
+ Geom::Point text_point = Point( help_counter*15, depth*15 );
+ char label[4];
+ sprintf( label,"%d",n->data ); // instead of std::itoa(depth, label, 10);
+
+ draw_text(cr, text_point, label);
+ ////////////////////////////////////////////////////////////////
+ if(n->right){
+ draw_tree_in_toy(cr, n->right, depth+1);
+ }
+ }
+ };
+
+/*
+ int find_selected_rect(PointHandle * selected){
+
+ for( unsigned i=0; i<handle_set.pts.size(); i=i+2 ){
+ if( handle_set.pts[i] == selected || handle_set.pts[i+1] == selected ){
+ return i;
+ }
+ }
+
+ return -1;
+ };
+*/
+
+
+public:
+ RedBlackToy(): color_rect(0, 0, 0, 0.6), color_rect_guide(0, 0, 0, 1),
+ color_select_area(1, 0, 0, 0.6 ), color_select_area_guide(1, 0, 0, 1 ),
+ alter_existing_rect(0), add_new_rect(0), mode(0), help_counter(0)
+ {
+ if(handles.empty()) {
+ handles.push_back(&handle_set);
+ }
+ Rect rect_chosen();
+ Rect dummy_draw();
+ }
+
+
+};
+
+
+
+int main(int argc, char **argv) {
+ std::cout << "---------------------------------------------------------"<< std::endl;
+ std::cout << "Let's play with the Red Black Tree! ONLY Insert works now!!!"<< std::endl;
+ std::cout << " Key A: insert/alter mode "<< std::endl;
+ std::cout << " * Left click and drag on white area: create a rectangle"<< std::endl;
+ std::cout << " *NOT READY: Left click and drag on handler: alter a rectangle"<< std::endl;
+ std::cout << " Key B: search mode "<< std::endl;
+ std::cout << " * Left click and drag on white area: \"search\" for nodes that intersect red area"<< std::endl;
+ std::cout << " NOT READY: Key C: delete mode "<< std::endl;
+ std::cout << " * Left click on handler: delete for a rectangle"<< std::endl;
+ std::cout << "---------------------------------------------------------"<< std::endl;
+ init(argc, argv, new RedBlackToy);
+ return 0;
+}
+
+const char* RedBlackToy::menu_items[] =
+{
+ "Insert / Alter Rectangle",
+ "Search Rectangle",
+ "Delete Reactangle"
+};
+
+const char RedBlackToy::keys[] =
+{
+ 'A', 'B', 'C'
+};
diff --git a/src/2geom/orphan-code/redblacktree.cpp b/src/2geom/orphan-code/redblacktree.cpp
new file mode 100644
index 0000000..bf9a728
--- /dev/null
+++ b/src/2geom/orphan-code/redblacktree.cpp
@@ -0,0 +1,575 @@
+#include <2geom/orphan-code/redblacktree.h>
+//#include <algorithm>
+
+
+#define _REDBLACK_PRINT(x) std::cout << x << std::endl;
+//comment the following if you want output during RedBlack Tree operations
+//#define _REDBLACK_PRINT(x) ;
+
+
+namespace Geom{
+
+
+
+RedBlack* RedBlackTree::search(Rect const &r, int dimension){
+ return search( Interval( r[dimension].min(), r[dimension].max() ) );
+ // TODO get rid of dimension
+ // TODO put 2 trees (X, Y axis in one lump)
+}
+
+/*
+INTERVAL-SEARCH(T, i)
+1 x <- root[T]
+2 while x != nil[T] and i does not overlap int[x]
+3 do if left[x] != nil[T] and max[left[x]] >= low[i]
+4 then x <- left[x]
+5 else x <- right[x]
+6 return x
+
+Two intervals i,x overlap in the 4 following cases:
+ 1) |--------| i
+ |---| x
+
+ 2) |-----| i
+ |----------| x
+
+ 3) |------| i
+ |------| x
+
+ 4) |----| i
+ |----| x
+
+And do not overlap when (the one os left or right of the other)
+ 1) |--------| i
+ |---| x
+
+ 2) |-----| i
+ |----------| x
+
+
+*/
+RedBlack* RedBlackTree::search(Interval i){
+ _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: search(Interval i) : (" << i.min() << ", " << i.max() << ")" )
+ RedBlack *x;
+ x = root;
+
+ while( x!=0 &&
+ ( i.max() < x->interval.min() ||
+ i.min() > x->interval.max() )
+ ){
+ _REDBLACK_PRINT( "(" << x->data << ": " << x->key() << ", " << x->high() << " : " << x->subtree_max << ") "
+ << " i do not overlap with x")
+
+ if(x->left != 0 && (x->left)->subtree_max >= i.min() ){
+ x = x->left;
+ }
+ else{
+ x = x->right;
+ }
+ }
+ _REDBLACK_PRINT( "RETURN: search" << std::endl )
+ return x;
+}
+
+
+
+void RedBlackTree::insert(Rect const &r, int shape, int dimension) {
+ _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: insert(Rect, int, dimension): " << " dimension:" << dimension << " shape:" << shape )
+ insert(r[dimension].min(), r[dimension].max(), shape);
+ _REDBLACK_PRINT( "RETURN: insert(Rect, int, dimension)")
+}
+
+// source: book pp 251
+void RedBlackTree::insert(Coord dimension_min, Coord dimension_max, int shape) {
+ _REDBLACK_PRINT( std::endl << "ENTER: insert(Coord, Coord, int): " << dimension_min << ", " << dimension_max << " , shape: " << shape )
+ // x is the new node we insert
+ RedBlack *x = new RedBlack();
+ x->interval = Interval( dimension_min, dimension_max );
+ x->data = shape;
+ x->isRed = true;
+
+ _REDBLACK_PRINT( " x: " << x << " KEY: " << x->key() << " high: " << x->high() )
+
+ tree_insert(x);
+
+ print_tree();
+
+ _REDBLACK_PRINT( " Begin coloring" )
+ // we now do the coloring of the tree.
+ _REDBLACK_PRINT( " while( x!= root && (x->parent)->isRed )" )
+ while( x!= root && (x->parent)->isRed ){
+ _REDBLACK_PRINT( " ((x->parent)->parent)->left:" << ((x->parent)->parent)->left << " ((x->parent)->parent)->right:" << ((x->parent)->parent)->right )
+
+ if( x->parent == ((x->parent)->parent)->left ){
+ _REDBLACK_PRINT( " Left:" )
+ RedBlack *y = new RedBlack;
+ y = ((x->parent)->parent)->right;
+ if( y == 0 ){
+ /*
+ This 1st brach is not in the book, but is needed. We must check y->isRed but it is
+ undefined, so we get segfault. But 0 (undefined) means that y is a leaf, so it is
+ black by definition. So, do the same as in the else part.
+ */
+ _REDBLACK_PRINT( " y==0" )
+ if( x == (x->parent)->right ){
+ x = x->parent;
+ left_rotate(x);
+ }
+ (x->parent)->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ right_rotate((x->parent)->parent);
+ }
+ else if( y->isRed ){
+ _REDBLACK_PRINT( " y->isRed" )
+ (x->parent)->isRed = false;
+ y->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ x = (x->parent)->parent;
+ }
+ else{
+ _REDBLACK_PRINT( " !( y->isRed)" )
+ if( x == (x->parent)->right ){
+ x = x->parent;
+ left_rotate(x);
+ }
+ (x->parent)->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ right_rotate((x->parent)->parent);
+ }
+ }
+ else{ // this branch is the same with the above if clause with "right", "left" exchanged
+ _REDBLACK_PRINT( " Right:" )
+ RedBlack *y = new RedBlack;
+ y = ((x->parent)->parent)->left;
+ if( y == 0 ){
+ /*
+ This 1st brach is not in the book, but is needed. We must check y->isRed but it is
+ undefined, so we get segfault. But 0 (undefined) means that y is a leaf, so it is
+ black by definition. So, do the same as in the else part.
+ */
+ _REDBLACK_PRINT( " y==0" )
+ if( x == (x->parent)->left ){
+ x = x->parent;
+ right_rotate(x);
+ }
+ (x->parent)->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ left_rotate((x->parent)->parent);
+ }
+ else if( y->isRed ){
+ _REDBLACK_PRINT( " y->isRed" )
+ (x->parent)->isRed = false;
+ y->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ x = (x->parent)->parent;
+ }
+ else{
+ _REDBLACK_PRINT( " !( y->isRed)" )
+ if( x == (x->parent)->left ){
+ x = x->parent;
+ right_rotate(x);
+ }
+ (x->parent)->isRed = false;
+ ((x->parent)->parent)->isRed = true;
+ left_rotate((x->parent)->parent);
+ }
+ }
+ }
+ root->isRed = false;
+
+ // update the max value with a slow/stupid yet certain way, walking all the tree :P
+ // TODO find better way
+ _REDBLACK_PRINT( " Update max" )
+
+ update_max(root);
+
+ _REDBLACK_PRINT( "RETURN: insert(Coord, Coord, int)" << std::endl)
+}
+
+// from book p. 266)
+void RedBlackTree::left_rotate(RedBlack* x){
+ // x->right != 0 (assumption book page 266)
+ // ??? hm problem ???
+ _REDBLACK_PRINT( "ENTER: left_rotate" )
+ RedBlack* y = new RedBlack;
+ y = x->right;
+ x->right = y->left;
+
+ if( y->left != 0){
+ (y->left)->parent = x;
+ }
+
+ y->parent = x->parent;
+
+ if( x->parent == 0){
+ root = y;
+ }
+ else{
+ if( x == (x->parent)->left ){
+ (x->parent)->left = y;
+ }
+ else{
+ (x->parent)->right = y;
+ }
+ }
+ y->left = x;
+ x->parent = y;
+ _REDBLACK_PRINT( "RETURN: left_rotate" << std::endl )
+}
+
+// from book p. 266: right_rotate is inverse of left_rotate
+// same to left_rotate with "right", "left" exchanged
+void RedBlackTree::right_rotate(RedBlack* x){
+ // x->right != 0 (assumption book page 266)
+ // ??? hm problem ??
+ _REDBLACK_PRINT( "ENTER: right_rotate" )
+ RedBlack* y = new RedBlack;
+
+ _REDBLACK_PRINT( "x->left: " << x->left )
+ y = x->left;
+ x->left = y->right;
+
+ if( y->right != 0){
+ (y->right)->parent = x;
+ }
+
+ y->parent = x->parent;
+
+ if( x->parent == 0){
+ root = y;
+ }
+ else{
+ if( x == (x->parent)->left ){
+ (x->parent)->left = y;
+ }
+ else{
+ (x->parent)->right = y;
+ }
+ }
+ y->right = x;
+ x->parent = y;
+ _REDBLACK_PRINT( "RETURN: right_rotate" << std::endl )
+}
+
+// insertion in binary search tree: book page 251
+// then the redblack insert performs the coloring
+void RedBlackTree::tree_insert(RedBlack* z){
+ _REDBLACK_PRINT( "ENTER: tree_insert(RedBlack* z)" )
+ RedBlack* y = 0; // y <- nil
+
+ RedBlack* x = root;
+
+ _REDBLACK_PRINT( " while x!=0 " )
+ while( x != 0 ){
+ y = x;
+// _REDBLACK_PRINT( " x:" << x << " y:" << y << " z:" << z )
+ _REDBLACK_PRINT( " z->key: " << z->key() << " y->key: " << y->key() << " compare")
+ if( z->key() < x->key() ){
+ _REDBLACK_PRINT( " z smaller: go left" )
+ x = x->left;
+ }
+ else{
+ _REDBLACK_PRINT( " z bigger: go right" )
+ x = x->right;
+ }
+ }
+
+ _REDBLACK_PRINT( " z->parent = y" )
+ z->parent = y;
+
+ if( y == 0 ){
+ _REDBLACK_PRINT( " set z root (empty tree)" )
+ root = z;
+ }
+ else{
+ _REDBLACK_PRINT( " z->key: " << z->key() << " y->key: " << y->key() << " compare")
+ if( z->key() < y->key() ){
+ _REDBLACK_PRINT( " z->key() smaller: y->left = z; " )
+ y->left = z;
+ }
+ else{
+ _REDBLACK_PRINT( " z->key() bigger: y->right = z " )
+ y->right = z;
+ }
+ }
+ _REDBLACK_PRINT( "RETURN: tree_insert(RedBlack* z)" << std::endl )
+}
+
+
+/*
+RB-DELETE(T, z)
+ 1 if left[z] = nil[T] or right[z] = nil[T]
+ 2 then y <- z
+ 3 else y <- TREE-SUCCESSOR(z)
+ 4 if left[y] != nil[T]
+ 5 then x <- left[y]
+ 6 else x <- right[y]
+ 7 p[x] <- p[y]
+ 8 if p[y] = nil[T]
+ 9 then root[T] <- x
+10 else if y = left[p[y]]
+11 then left[p[y]] <- x
+12 else right[p[y]] <- x
+13 if y != z
+14 then key[z] <- key[y]
+15 copy y's satellite data into z
+16 if color[y] = BLACK
+17 then RB-DELETE-FIXUP(T, x)
+18 return y
+*/
+RedBlack* RedBlackTree::erase(RedBlack* z){
+ _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: earse(z)" )
+ RedBlack* x = new RedBlack();
+ RedBlack* y = new RedBlack();
+ if( z->left == 0 || z->right == 0 ){
+ y = z;
+ }
+ else{
+ y = tree_successor(z);
+ }
+
+ if( y->left != 0 ){
+ x = y->left;
+ }
+ else{
+ x = y->right;
+ }
+
+ x->parent = y->parent;
+
+ if( y->parent == 0){
+ root = x;
+ }
+ else {
+ if( y == (y->parent)->left ){
+ (y->parent)->left = x;
+ }
+ else{
+ (y->parent)->right = x;
+ }
+ }
+
+ if( y != z){
+ z->interval = y->interval ; // key[z] <- key[y] TODO check this
+ //copy y's satellite data into z
+ z->data = y->data;
+ z->isRed = y->isRed;
+
+ z->left = y->left;
+ z->right = y->right;
+ z->parent = y->parent;
+ }
+
+ if( y->isRed == false){
+ erase_fixup(x);
+ }
+
+ _REDBLACK_PRINT( "Update max" )
+ update_max(root);
+
+ _REDBLACK_PRINT( "RETURN: erase" )
+ return y;
+}
+
+/*
+RB-DELETE-FIXUP(T, x)
+ 1 while x != root[T] and color[x] = BLACK
+ 2 do if x = left[p[x]]
+ 3 then w <- right[p[x]]
+ 4 if color[w] = RED
+ 5 then color[w] <- BLACK Case 1
+ 6 color[p[x]] <- RED Case 1
+ 7 LEFT-ROTATE(T, p[x]) Case 1
+ 8 w <- right[p[x]]
+ 9 if color[left[w]] = BLACK and color[right[w]] = BLACK
+10 then color[w] <- RED Case 2
+11 x p[x] Case 2
+12 else if color[right[w]] = BLACK
+13 then color[left[w]] <- BLACK Case 3
+14 color[w] <- RED Case 3
+15 RIGHT-ROTATE(T, w) Case 3
+16 w <- right[p[x]] Case 3
+17 color[w] <- color[p[x]] Case 4
+18 color[p[x]] <- BLACK Case 4
+19 color[right[w]] <- BLACK Case 4
+20 LEFT-ROTATE(T, p[x]) Case 4
+21 x <- root[T] Case 4
+22 else (same as then clause with "right" and "left" exchanged)
+23 color[x] <- BLACK
+*/
+void RedBlackTree::erase_fixup(RedBlack* x){
+ RedBlack* w = 0;
+ while( x != root && x->isRed == false ){
+ if( x == (x->parent)->left ){
+ w = (x->parent)->right;
+ if(w->isRed == true){
+ w->isRed = false;
+ (w->parent)->isRed = true;
+ left_rotate(x->parent);
+ w = (x->parent)->right;
+ }
+ if( (w->left)->isRed == false && (w->right)->isRed == false ){
+ w->isRed = true;
+ x = x->parent; // TODO understand why this happens ???
+ }
+ else{
+ if( (w->right)->isRed == false ){
+ (w->left)->isRed = false;
+ right_rotate(w);
+ w = (x->parent)->right;
+ }
+ else{ // TODO ??? is this correct ???
+ w->isRed = (x->parent)->isRed;
+ (x->parent)->isRed = false;
+ (w->right)->isRed = false;
+ left_rotate(x->parent);
+ x = root; // TODO ??? is this correct ???
+ }
+ }
+ }
+ else{ // same as then clause with "right" and "left" exchanged
+ w = (x->parent)->left;
+ if(w->isRed == true){
+ w->isRed = false;
+ (w->parent)->isRed = true;
+ right_rotate(x->parent);
+ w = (x->parent)->left;
+ }
+ if( (w->right)->isRed == false && (w->left)->isRed == false ){
+ w->isRed = true;
+ x = x->parent; // ??? is this correct ???
+ }
+ else{
+ if( (w->left)->isRed == false ){
+ (w->right)->isRed = false;
+ left_rotate(w);
+ w = (x->parent)->left;
+ }
+ else{ // TODO ??? is this correct ???
+ w->isRed = (x->parent)->isRed;
+ (x->parent)->isRed = false;
+ (w->left)->isRed = false;
+ right_rotate(x->parent);
+ x = root; // TODO ??? is this correct ???
+ }
+ }
+ }
+ }
+ x->isRed = false;
+}
+
+
+void RedBlackTree::print_tree(){
+ std::cout << "Print RedBlackTree status:" << std::endl;
+ inorder_tree_walk(root);
+}
+
+
+void RedBlackTree::inorder_tree_walk(RedBlack* x){
+ int oops =0;
+ if( x != 0 ){
+ inorder_tree_walk(x->left);
+ std::cout<< "(" << x->data << ": " << x->key() << ", " << x->high() << " : " << x->subtree_max << ") " ;
+
+ if( x->left != 0 ){
+ std::cout<< "L:(" << (x->left)->data << ", " << (x->left)->key() << ") " ;
+ if( x->key() < (x->left)->key()){
+ std::cout<<" !!! ";
+ oops = 1;
+ }
+ }
+ else{
+ std::cout<< "L:0 " ;
+ }
+
+ if( x->right != 0 ){
+ std::cout<< "R:(" << (x->right)->data << ", "<< (x->right)->key() << ") " ;
+ if( x->key() > (x->right)->key() ){
+ std::cout<<" !!! ";
+ oops = 1;
+ }
+ }
+ else{
+ std::cout<< "R:0 " ;
+ }
+
+ if(oops){
+ std::cout<<" ....... !!! Problem " << oops ;
+ }
+ std::cout << std::endl;
+ inorder_tree_walk(x->right);
+ }
+}
+
+// not an norder walk of the tree
+void RedBlackTree::update_max(RedBlack* x){
+ Coord max_left, max_right;
+ if( x != 0 ){
+ update_max(x->left);
+ update_max(x->right);
+
+ // check for child
+ // if child is Nil then max = DBL_MIN
+ // could there be problems when comparing for max between two DBL_MIN ???
+ if( x->left == 0 ){
+ max_left = DBL_MIN ;
+ }
+ else{
+ max_left = (x->left)->subtree_max;
+ }
+
+ if( x->right == 0 ){
+ max_right = DBL_MIN ;
+ }
+ else{
+ max_right = (x->right)->subtree_max;
+ }
+
+ //find max of: x->high(), max_left, max_right
+ Coord temp_max;
+ temp_max = std::max( x->high(), max_left );
+ temp_max = std::max( temp_max, max_right );
+ x->subtree_max = temp_max;
+
+ }
+}
+
+
+RedBlack* RedBlackTree::tree_minimum(RedBlack* x){
+ _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: tree_minimum" )
+ while( x->left <- 0 ) {
+ x->left = x;
+ }
+ _REDBLACK_PRINT( "RETURN: tree_minimum" << std::endl )
+ return x;
+}
+
+RedBlack* RedBlackTree::tree_successor(RedBlack* x){
+ _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: tree_successor" )
+ if( x->right <- 0 ){
+ _REDBLACK_PRINT( "RETURN: tree_successor" << std::endl )
+ return tree_minimum(x);
+ }
+ RedBlack* y = x->parent;
+ _REDBLACK_PRINT( "y->parent: y->parent" )
+ while( y <- 0 && x == y->right ){
+ x = y;
+ y = y->parent;
+ }
+ _REDBLACK_PRINT( "RETURN: tree_successor" << std::endl )
+ return y;
+}
+
+
+};
+
+/*
+ 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/src/2geom/orphan-code/rtree.cpp b/src/2geom/orphan-code/rtree.cpp
new file mode 100644
index 0000000..4264292
--- /dev/null
+++ b/src/2geom/orphan-code/rtree.cpp
@@ -0,0 +1,1350 @@
+#include <2geom/orphan-code/rtree.h>
+#include <limits>
+
+/*
+Based on source (BibTex):
+@inproceedings{DBLP:conf/sigmod/Guttman84,
+ author = {Antonin Guttman},
+ title = {R-Trees: A Dynamic Index Structure for Spatial Searching},
+ booktitle = {SIGMOD Conference},
+ year = {1984},
+ pages = {47-57},
+ ee = {http://doi.acm.org/10.1145/602259.602266, db/conf/sigmod/Guttman84.html},
+}
+*/
+
+/*
+#define _RTREE_PRINT(x) std::cout << x << std::endl;
+#define _RTREE_PRINT_TREE( x, y ) print_tree( x, y );
+#define _RTREE_PRINT_TREE_INS( x, y, z ) print_tree( x, y, z );
+*/
+//comment the following if you want output during RTree operations
+
+
+#define _RTREE_PRINT(x) ;
+#define _RTREE_PRINT_TREE( x, y ) ;
+#define _RTREE_PRINT_TREE_INS( x, y, z ) ;
+
+
+
+/*
+TODO 1
+some if(non_leaf)
+ else // leaf
+could be eliminated when function starts from a leaf
+do leaf action
+then repeat function for non-leafs only
+candidates:
+- adjust_tree
+- condense_tree
+
+TODO 2
+generalize in a different way the splitting techniques
+
+*/
+
+
+namespace Geom{
+
+/*=============================================================================
+ insert
+===============================================================================
+insert a new index entry E into the R-tree:
+
+I1) find position of new record:
+ choose_node will find a leaf node L (position) in which to place r
+I2) add record to leaf node:
+ if L has room for another entry install E
+ else split_node will obtain L and LL containing E and all the old entries of L
+ from the available splitting strategies we chose quadtratic-cost algorithm (just to begin
+ with sth)
+ // TODO implement more of them
+I3) propagate changes upward:
+ Invoke adjust_tree on L, also passing LL if a split was performed.
+I4) grow tree taller:
+ if a node spilt propagation, cuased the root to split
+ create new root whose children are the 2 resulting nodes
+*/
+
+void RTree::insert( Rect const &r, unsigned shape ){
+ _RTREE_PRINT("\n=====================================");
+ _RTREE_PRINT("insert");
+ RTreeRecord_Leaf* leaf_record= new RTreeRecord_Leaf( r, shape );
+ insert( *leaf_record );
+}
+
+
+
+void RTree::insert( const RTreeRecord_Leaf &leaf_record,
+ const bool &insert_high /* false */,
+ const unsigned &stop_height /* 0 */,
+ const RTreeRecord_NonLeaf &nonleaf_record /* 0 */
+ )
+{
+ _RTREE_PRINT("\n--------------");
+ _RTREE_PRINT("insert private. element:" << leaf_record.data << " insert high:" << insert_high << " stop height:" << stop_height );
+ RTreeNode *position = 0;
+
+ // if tree is unused create the root Node, not described in source, stupid me :P
+ if(root == 0){
+ root = new RTreeNode();
+ }
+
+ _RTREE_PRINT("I1"); // I1
+ if( insert_high == false ){ // choose leaf node
+ position = choose_node( leaf_record.bounding_box );
+ }
+ else { // choose nonleaf node
+ position = choose_node( nonleaf_record.bounding_box, insert_high, stop_height );
+ }
+ _RTREE_PRINT("leaf node chosen: " );
+ _RTREE_PRINT_TREE( position , 0 );
+ std::pair< RTreeNode*, RTreeNode* > node_division;
+
+ bool split_performed = false;
+
+ if( position->children_nodes.size() > 0 ){ // non-leaf node: position
+ // we must reach here only to insert high non leaf node, not insert leaf node
+ assert( insert_high == true );
+
+ // put new element in node temporarily. Later on, if we need to, we will split the node.
+ position->children_nodes.push_back( nonleaf_record );
+ if( position->children_nodes.size() <= max_records ){
+ _RTREE_PRINT("I2 nonleaf: no split: " << position->children_nodes.size() ); // I2
+ }
+ else{
+ _RTREE_PRINT("I2 nonleaf: split: " << position->children_nodes.size() ); // I2
+ node_division = split_node( position );
+ split_performed = true;
+ }
+
+ }
+ else { // leaf node: position:
+ // we must reach here only to insert leaf node, not insert high non leaf node
+ assert( insert_high == false );
+
+
+ // put new element in node temporarily. Later on, if we need to, we will split the node.
+ position->children_leaves.push_back( leaf_record );
+ if( position->children_leaves.size() <= max_records ){
+ _RTREE_PRINT("I2 leaf: no split: " << position->children_leaves.size() ); // I2
+ }
+ else{
+ _RTREE_PRINT("I2 leaf: split: " << position->children_leaves.size() << " max_records:" << max_records); // I2
+ node_division = split_node( position );
+ split_performed = true;
+
+ _RTREE_PRINT(" group A");
+ _RTREE_PRINT_TREE( node_division.first , 3 );
+ _RTREE_PRINT(" group B");
+ _RTREE_PRINT_TREE( node_division.second , 3 );
+
+ }
+
+ }
+
+ _RTREE_PRINT("I3"); // I3
+ bool root_split_performed = adjust_tree( position, node_division, split_performed );
+ _RTREE_PRINT("root split: " << root_split_performed);
+
+
+// _RTREE_PRINT("TREE:");
+// print_tree( root , 2 );
+
+ _RTREE_PRINT("I4"); // I4
+ if( root_split_performed ){
+ std::pair<RTreeNode*, RTreeNode*> root_division;
+ root_division = quadratic_split( root ); // AT5
+
+ Rect first_record_bounding_box;
+ Rect second_record_bounding_box;
+
+ RTreeRecord_NonLeaf first_new_record = create_nonleaf_record_from_rtreenode( first_record_bounding_box, root_division.first );
+ RTreeRecord_NonLeaf second_new_record = create_nonleaf_record_from_rtreenode( second_record_bounding_box, root_division.second );
+ _RTREE_PRINT(" 1st:");
+ _RTREE_PRINT_TREE( first_new_record.data, 5 );
+ _RTREE_PRINT(" 2nd:");
+ _RTREE_PRINT_TREE( second_new_record.data, 5 );
+
+ // *new* root is by definition non-leaf. Install the new records there
+ RTreeNode* new_root = new RTreeNode();
+ new_root->children_nodes.push_back( first_new_record );
+ new_root->children_nodes.push_back( second_new_record );
+
+ delete root;
+
+ root = new_root;
+ tree_height++; // increse tree height
+
+ _RTREE_PRINT_TREE( root, 5 );
+ sanity_check( root, 0 );
+ }
+ _RTREE_PRINT("done");
+
+ /*
+ the node_division.second is saved on the tree
+ the node_division.first was copied in existing tree in node
+ so we don't need this anymore
+ */
+ delete node_division.first;
+}
+
+/* I1 =========================================================================
+
+original: choose_node will find a leaf node L in which to place r
+changed to choose_node will find a node L in which to place r
+the node L is:
+non-leaf: if flag is set. the height of the node is insert_at_height
+leaf: if flag is NOT set
+
+1) Initialize: set N to be the root node
+2) Leaf Check:
+ insert_height = false
+ if N is leaf return N
+ insert_height = true
+3) Choose subtree: If N not leaf OR not we are not in the proper height then
+ let F be an entry in N whose rect Fi needs least enlargement to include r
+ ties resolved with rect of smallest area
+4) descend until a leaf is reached OR proper height is reached: set N to the child node pointed to by F and goto 2.
+*/
+
+// TODO keep stack with visited nodes
+
+RTreeNode* RTree::choose_node( const Rect &r, const bool &insert_high /* false */, const unsigned &stop_height /* 0 */) const {
+
+ _RTREE_PRINT(" CL1");// CL1
+ RTreeNode *pos = root;
+
+ double min_enlargement;
+ double current_enlargement;
+ int node_min_enlargement;
+ unsigned current_height = 0; // zero is the root
+
+ _RTREE_PRINT(" CL2 current_height:" << current_height << " stop_height:" << stop_height << " insert_high:" << insert_high);
+ // CL2 Leaf check && Height check
+ while( ( insert_high ? true : pos->children_nodes.size() != 0 )
+ && ( insert_high ? current_height < stop_height : true ) )
+ /* Leaf check, during insert leaf */
+ /* node height check, during insert non-leaf */
+ {
+ _RTREE_PRINT(" CL3 current_height:" << current_height << " stop_height:" << stop_height ); // CL3
+ min_enlargement = std::numeric_limits<double>::max();
+ current_enlargement = 0;
+ node_min_enlargement = 0;
+
+ for(unsigned i=0; i< pos->children_nodes.size(); i++){
+ current_enlargement = find_enlargement( pos->children_nodes[i].bounding_box, r );
+
+ // TODO tie not solved!
+ if( current_enlargement < min_enlargement ){
+ node_min_enlargement = i;
+ min_enlargement = current_enlargement;
+ }
+ }
+ _RTREE_PRINT(" CL4"); // CL4
+ // descend to the node with the min_enlargement
+ pos = pos->children_nodes[node_min_enlargement].data;
+ current_height++; // increase current visiting height
+ }
+
+ return pos;
+}
+
+
+/*
+find_enlargement:
+
+enlargement that "a" needs in order to include "b"
+b is the new rect we want to insert.
+a is the rect of the node we try to see if b should go in.
+*/
+double RTree::find_enlargement( const Rect &a, const Rect &b ) const{
+
+
+ Rect union_rect(a);
+ union_rect.unionWith(b);
+
+ OptRect a_intersection_b = intersect( a, b );
+
+ // a, b do not intersect
+ if( a_intersection_b.empty() ){
+ _RTREE_PRINT(" find_enlargement (no intersect): " << union_rect.area() - a.area() - b.area() );
+ return union_rect.area() - a.area() - b.area();
+ }
+
+ // a, b intersect
+
+ // a contains b
+ if( a.contains( b ) ){
+ _RTREE_PRINT(" find_enlargement (intersect: a cont b): " << a.area() - b.area() );
+ //return a.area() - b.area();
+ return b.area() - a.area(); // enlargement is negative in this case.
+ }
+
+ // b contains a
+ if( b.contains( a ) ){
+ _RTREE_PRINT(" find_enlargement (intersect: b cont a): " << a.area() - b.area() );
+ return b.area() - a.area();
+ }
+
+ // a partially cover b
+ _RTREE_PRINT(" find_enlargement (intersect: a partial cover b): " << union_rect.area() - a.area() - b.area() - a_intersection_b->area() );
+ return union_rect.area()
+ - ( a.area() - a_intersection_b->area() )
+ - ( b.area() - a_intersection_b->area() );
+}
+
+
+/* I2 =========================================================================
+use one split strategy
+*/
+
+std::pair<RTreeNode*, RTreeNode*> RTree::split_node( RTreeNode *s ){
+/*
+ if( split_strategy == LINEAR_COST ){
+ linear_cost_split( ............. );
+ }
+*/
+ return quadratic_split( s ); // else QUADRATIC_SPIT
+}
+
+
+/*-----------------------------------------------------------------------------
+ Quadratic Split
+
+QS1) Pick first entry for each group:
+ Appy pick_seeds to choose 2 entries to be the first elements of the groups. Assign each one of
+ them to one group
+QS2) check if done:
+ a) if all entries have been assinged stop
+ b) if one group has so few entries that all the rest must be assignmed to it, in order for it to
+ have the min number , assign them and stop
+QS3) select entry and assign:
+ Inkvoke pick_next() to choose the next entry to assign.
+ *[in pick_next] Add it to the group whose covering rectangle will have to be enlrarged least to
+ accommodate it. Resolve ties by adding the entry to the group with the smaller are, then to the
+ one with fewer entries, then to either of the two.
+ goto 2.
+*/
+std::pair<RTreeNode*, RTreeNode*> RTree::quadratic_split( RTreeNode *s ) {
+
+ // s is the original leaf node or non-leaf node
+ RTreeNode* group_a = new RTreeNode(); // a is the 1st group
+ RTreeNode* group_b = new RTreeNode(); // b is the 2nd group
+
+
+ _RTREE_PRINT(" QS1"); // QS1
+ std::pair<unsigned, unsigned> initial_seeds;
+ initial_seeds = pick_seeds(s);
+
+ // if non-leaf node: s
+ if( s->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" non-leaf node");
+ // each element is true if the node has been assinged to either "a" or "b"
+ std::vector<bool> assigned_v( s->children_nodes.size() );
+ std::fill( assigned_v.begin(), assigned_v.end(), false );
+
+ group_a->children_nodes.push_back( s->children_nodes[initial_seeds.first] );
+ assert(initial_seeds.first < assigned_v.size());
+ assigned_v[ initial_seeds.first ] = true;
+
+ group_b->children_nodes.push_back( s->children_nodes[initial_seeds.second] );
+ assert(initial_seeds.second < assigned_v.size());
+ assigned_v[ initial_seeds.second ] = true;
+
+ _RTREE_PRINT(" QS2"); // QS2
+ unsigned num_of_not_assigned = s->children_nodes.size() - 2;
+ // so far we have assinged 2 out of all
+
+ while( num_of_not_assigned ){// QS2 a
+ _RTREE_PRINT(" QS2 b, num_of_not_assigned:" << num_of_not_assigned); // QS2 b
+ /*
+ we are on NON leaf node so children of split groups must be nodes
+
+ Check each group to see if one group has so few entries that all the rest must
+ be assignmed to it, in order for it to have the min number.
+ */
+ if( group_a->children_nodes.size() + num_of_not_assigned <= min_records ){
+ // add the non-assigned to group_a
+ for(unsigned i = 0; i < assigned_v.size(); i++){
+ if(assigned_v[i] == false){
+ group_a->children_nodes.push_back( s->children_nodes[i] );
+ assigned_v[i] = true;
+ }
+ }
+ break;
+ }
+
+ if( group_b->children_nodes.size() + num_of_not_assigned <= min_records ){
+ // add the non-assigned to group_b
+ for( unsigned i = 0; i < assigned_v.size(); i++ ){
+ if( assigned_v[i] == false ){
+ group_b->children_nodes.push_back( s->children_nodes[i] );
+ assigned_v[i] = true;
+ }
+ }
+ break;
+ }
+
+ _RTREE_PRINT(" QS3"); // QS3
+ std::pair<unsigned, enum_add_to_group> next_element;
+ next_element = pick_next( group_a, group_b, s, assigned_v );
+ if( next_element.second == ADD_TO_GROUP_A ){
+ group_a->children_nodes.push_back( s->children_nodes[ next_element.first ] );
+ }
+ else{
+ group_b->children_nodes.push_back( s->children_nodes[ next_element.first ] );
+ }
+
+ num_of_not_assigned--;
+ }
+ }
+ // else leaf node: s
+ else{
+ _RTREE_PRINT(" leaf node");
+ // each element is true if the node has been assinged to either "a" or "b"
+ std::vector<bool> assigned_v( s->children_leaves.size() );
+ std::fill( assigned_v.begin(), assigned_v.end(), false );
+
+ // assign 1st seed to group a
+ group_a->children_leaves.push_back( s->children_leaves[initial_seeds.first] );
+ assert(initial_seeds.first < assigned_v.size());
+ assigned_v[ initial_seeds.first ] = true;
+
+ // assign 2nd seed to group b
+ group_b->children_leaves.push_back( s->children_leaves[initial_seeds.second] );
+ assert(initial_seeds.second < assigned_v.size());
+ assigned_v[ initial_seeds.second ] = true;
+
+ _RTREE_PRINT(" QS2"); // QS2
+ unsigned num_of_not_assigned = s->children_leaves.size() - 2;
+ // so far we have assinged 2 out of all
+
+ while( num_of_not_assigned ){// QS2 a
+ _RTREE_PRINT(" QS2 b, num_of_not_assigned:" << num_of_not_assigned); // QS2 b
+ /*
+ we are on leaf node so children of split groups must be leaves
+
+ Check each group to see if one group has so few entries that all the rest must
+ be assignmed to it, in order for it to have the min number.
+ */
+ if( group_a->children_leaves.size() + num_of_not_assigned <= min_records ){
+ _RTREE_PRINT(" add the non-assigned to group_a");
+ // add the non-assigned to group_a
+ for( unsigned i = 0; i < assigned_v.size(); i++ ){
+ if( assigned_v[i] == false ){
+ group_a->children_leaves.push_back( s->children_leaves[i] );
+ assigned_v[i] = true;
+ }
+ }
+ break;
+ }
+
+ if( group_b->children_leaves.size() + num_of_not_assigned <= min_records ){
+ _RTREE_PRINT(" add the non-assigned to group_b");
+ // add the non-assigned to group_b
+ for( unsigned i = 0; i < assigned_v.size(); i++ ){
+ if( assigned_v[i] == false ){
+ group_b->children_leaves.push_back( s->children_leaves[i] );
+ assigned_v[i] = true;
+ }
+ }
+ break;
+ }
+
+ _RTREE_PRINT(" QS3"); // QS3
+ std::pair<unsigned, enum_add_to_group> next_element;
+ next_element = pick_next(group_a, group_b, s, assigned_v);
+ if( next_element.second == ADD_TO_GROUP_A ){
+ group_a->children_leaves.push_back( s->children_leaves[ next_element.first ] );
+ }
+ else{
+ group_b->children_leaves.push_back( s->children_leaves[ next_element.first ] );
+ }
+
+ num_of_not_assigned--;
+ }
+ }
+ assert( initial_seeds.first != initial_seeds.second );
+ return std::make_pair( group_a, group_b );
+}
+
+/*
+PS1) caclulate ineffeciency of grouping entries together:
+ Foreach pair of entries E1 (i), E2 (j) compose rectangle J (i_union_j) including E1, E2.
+ Calculate d = area(i_union_j) - area(i) - area(j)
+PS2) choose the most wastefull pair:
+ Choose pair with largest d
+*/
+
+std::pair<unsigned, unsigned> RTree::pick_seeds( RTreeNode *s ) const{
+ double current_d = 0;
+ double max_d = std::numeric_limits<double>::min();
+ unsigned seed_a = 0;
+ unsigned seed_b = 1;
+ _RTREE_PRINT(" pick_seeds");
+
+ // if non leaf node: s
+ if( s->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" non leaf");
+ _RTREE_PRINT(" PS1"); // PS1
+ for( unsigned a = 0; a < s->children_nodes.size(); a++ ){
+ // with j=i we check only the upper (diagonal) half
+ // with j=i+1 we also avoid checking for b==a (we don't need it)
+ for( unsigned b = a+1; b < s->children_nodes.size(); b++ ){
+ _RTREE_PRINT(" PS2 " << a << " - " << b ); // PS2
+ current_d = find_waste_area( s->children_nodes[a].bounding_box, s->children_nodes[b].bounding_box );
+
+ if( current_d > max_d ){
+ max_d = current_d;
+ seed_a = a;
+ seed_b = b;
+ }
+ }
+ }
+ }
+ // else leaf node: s
+ else{
+ _RTREE_PRINT(" leaf node");
+ _RTREE_PRINT(" PS1"); // PS1
+ for( unsigned a = 0; a < s->children_leaves.size(); a++ ){
+ // with j=i we check only the upper (diagonal) half
+ // with j=i+1 we also avoid checking for j==i (we don't need this one)
+ for( unsigned b = a+1; b < s->children_leaves.size(); b++ ){
+ _RTREE_PRINT(" PS2 " << s->children_leaves[a].data << ":" << s->children_leaves[a].bounding_box.area()
+ << " - " << s->children_leaves[b].data << ":" << s->children_leaves[b].bounding_box.area() ); // PS2
+ current_d = find_waste_area( s->children_leaves[a].bounding_box, s->children_leaves[b].bounding_box );
+
+ if( current_d > max_d ){
+ max_d = current_d;
+ seed_a = a;
+ seed_b = b;
+ }
+ }
+ }
+ }
+ _RTREE_PRINT(" seed_a: " << seed_a << " seed_b: " << seed_b );
+ return std::make_pair( seed_a, seed_b );
+}
+
+/*
+find_waste_area (used in pick_seeds step 1)
+
+for a pair A, B compose a rect union_rect that includes a and b
+calculate area of union_rect - area of a - area b
+*/
+double RTree::find_waste_area( const Rect &a, const Rect &b ) const{
+ Rect union_rect(a);
+ union_rect.unionWith(b);
+
+ return union_rect.area() - a.area() - b.area();
+}
+
+/*
+pick_next:
+select one remaining entry for classification in a group
+
+PN1) Determine cost of putting each entry in each group:
+ Foreach entry E not yet in a group, calculate
+ d1= area increase required in the cover rect of Group 1 to include E
+ d2= area increase required in the cover rect of Group 2 to include E
+PN2) Find entry with greatest preference for each group:
+ Choose any entry with the maximum difference between d1 and d2
+
+*/
+
+std::pair<unsigned, enum_add_to_group> RTree::pick_next( RTreeNode* group_a,
+ RTreeNode* group_b,
+ RTreeNode* s,
+ std::vector<bool> &assigned_v )
+{
+ double max_increase_difference = std::numeric_limits<double>::min();
+ unsigned max_increase_difference_node = 0;
+ double current_increase_difference = 0;
+
+ enum_add_to_group group_to_add = ADD_TO_GROUP_A;
+
+ /*
+ bounding boxes of the 2 new groups. This info isn't available, since they
+ have no parent nodes (so that the parent node knows the bounding box).
+ */
+ Rect bounding_box_a;
+ Rect bounding_box_b;
+
+ double increase_area_a = 0;
+ double increase_area_b = 0;
+
+ _RTREE_PRINT(" pick_next, assigned_v.size:" << assigned_v.size() );
+
+ // if non leaf node: one of the 2 groups (both groups are the same, either leaf/nonleaf)
+ if( group_a->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" non leaf");
+
+ // calculate the bounding boxes of the 2 new groups.
+ bounding_box_a = Rect( group_a->children_nodes[0].bounding_box );
+ for( unsigned i = 1; i < group_a->children_nodes.size(); i++ ){
+ bounding_box_a.unionWith( group_a->children_nodes[i].bounding_box );
+ }
+
+ bounding_box_b = Rect( group_b->children_nodes[0].bounding_box );
+ for( unsigned i = 1; i < group_b->children_nodes.size(); i++ ){
+ bounding_box_b.unionWith( group_b->children_nodes[i].bounding_box );
+ }
+
+
+ _RTREE_PRINT(" PN1"); // PN1
+ for( unsigned i = 0; i < assigned_v.size(); i++ ){
+ _RTREE_PRINT(" i:" << i << " assigned:" << assigned_v[i]);
+ if( assigned_v[i] == false ){
+
+ increase_area_a = find_enlargement( bounding_box_a, s->children_nodes[i].bounding_box );
+ increase_area_b = find_enlargement( bounding_box_b, s->children_nodes[i].bounding_box );
+
+ current_increase_difference = std::abs( increase_area_a - increase_area_b );
+ _RTREE_PRINT(" PN2 " << i << ": " << current_increase_difference ); // PN2
+ if( current_increase_difference > max_increase_difference ){
+ max_increase_difference = current_increase_difference;
+ max_increase_difference_node = i;
+
+ // TODO tie not solved!
+ if( increase_area_a < increase_area_b ){
+ group_to_add = ADD_TO_GROUP_A;
+ }
+ else{
+ group_to_add = ADD_TO_GROUP_B;
+ }
+ }
+ }
+ }
+ //assert(max_increase_difference_node >= 0);
+ assert(max_increase_difference_node < assigned_v.size());
+ assigned_v[max_increase_difference_node] = true;
+ _RTREE_PRINT(" ... i:" << max_increase_difference_node << " assigned:" << assigned_v[max_increase_difference_node] );
+ }
+ else{ // else leaf node
+ _RTREE_PRINT(" leaf");
+
+ // calculate the bounding boxes of the 2 new groups
+ bounding_box_a = Rect( group_a->children_leaves[0].bounding_box );
+ for( unsigned i = 1; i < group_a->children_leaves.size(); i++ ){
+ std::cout<< " lala";
+ bounding_box_a.unionWith( group_a->children_leaves[i].bounding_box );
+ }
+
+ bounding_box_b = Rect( group_b->children_leaves[0].bounding_box );
+ for( unsigned i = 1; i < group_b->children_leaves.size(); i++ ){
+ std::cout<< " lala";
+ bounding_box_b.unionWith( group_b->children_leaves[i].bounding_box );
+ }
+ std::cout<< "" << std::endl;
+
+ _RTREE_PRINT(" PN1"); // PN1
+ for( unsigned i = 0; i < assigned_v.size(); i++ ){
+ _RTREE_PRINT(" i:" << i << " assigned:" << assigned_v[i]);
+ if( assigned_v[i] == false ){
+ increase_area_a = find_enlargement( bounding_box_a, s->children_leaves[i].bounding_box );
+ increase_area_b = find_enlargement( bounding_box_b, s->children_leaves[i].bounding_box );
+
+ current_increase_difference = std::abs( increase_area_a - increase_area_b );
+ _RTREE_PRINT(" PN2 " << i << ": " << current_increase_difference ); // PN2
+
+ if( current_increase_difference > max_increase_difference ){
+ max_increase_difference = current_increase_difference;
+ max_increase_difference_node = i;
+
+ // TODO tie not solved!
+ if( increase_area_a < increase_area_b ){
+ group_to_add = ADD_TO_GROUP_A;
+ }
+ else{
+ group_to_add = ADD_TO_GROUP_B;
+ }
+ }
+ }
+ }
+ assert(max_increase_difference_node < assigned_v.size());
+ assigned_v[max_increase_difference_node] = true;
+ _RTREE_PRINT(" ... i:" << max_increase_difference_node << " assigned:" << assigned_v[max_increase_difference_node] );
+ }
+
+ _RTREE_PRINT(" node:" << max_increase_difference_node << " added:" << group_to_add );
+ return std::make_pair( max_increase_difference_node, group_to_add );
+}
+
+/* I3 =========================================================================
+
+adjust_tree:
+Ascend from a leaf node L to root, adjusting covering rectangles and propagating node splits as
+necessary
+
+We modified this one from the source in the step AT1 and AT5
+
+AT1) Initialize:
+ Set N=L
+ IF L was spilt previously, set NN to be the resulting second node AND
+ (not mentioned in the original source but that's what it should mean)
+ Assign all entries of first node to L
+AT2) check if done:
+ IF N is root stop
+AT3) adjust covering rectangle in parent entry
+ 1) Let P be the parent of N
+ 2) Let EN be the N's entry in P
+ 3) Adjust EN bounding box so that it tightly enclosses all entry rectangles in N
+AT4) Propagate node split upward
+ IF N has a partner NN resulting from an earlier split
+ create a new entry ENN with ENN "p" pointing to NN and ENN bounding box enclosing all
+ rectangles in NN
+
+ IF there is room in P add ENN
+ ELSE invoke split_node to produce P an PP containing ENN and all P's old entries.
+AT5) Move up to next level
+ Set N=P,
+ IF a split occurred, set NN=PP
+ goto AT1 (was goto AT2)
+*/
+
+bool RTree::adjust_tree( RTreeNode* position,
+ // modified: it holds the last split group
+ std::pair<RTreeNode*, RTreeNode*> &node_division,
+ bool initial_split_performed)
+{
+ RTreeNode* parent;
+ unsigned child_in_parent; // the element in parent node that points to current posistion
+ std::pair< RTreeNode*, bool > find_result;
+ bool split_performed = initial_split_performed;
+ bool root_split_performed = false;
+
+ _RTREE_PRINT(" adjust_tree");
+ _RTREE_PRINT(" AT1");
+
+ while( true ){
+ _RTREE_PRINT(" ------- current tree status:");
+ _RTREE_PRINT_TREE_INS(root, 2, true);
+
+ // check for loop BREAK
+ if( position == root ){
+ _RTREE_PRINT(" AT2: found root");
+ if( split_performed ){
+ root_split_performed = true;
+ }
+ break;
+ }
+
+ if( split_performed ){
+ copy_group_a_to_existing_node( position, node_division.first );
+ }
+
+ /*
+ pick randomly, let's say the 1st entry of the current node.
+ Search for this spatial area in the tree, and stop to the parent node.
+ Then find position of current node pointer, in the parent node.
+ */
+ _RTREE_PRINT(" AT3.1"); // AT3.1 Let P be the parent of N
+ if( position->children_nodes.size() > 0 ){
+ find_result = find_parent( root, position->children_nodes[0].bounding_box, position);
+ }
+ else{
+ find_result = find_parent( root, position->children_leaves[0].bounding_box, position);
+ }
+ parent = find_result.first;
+
+ // parent is a non-leaf, by definition
+ _RTREE_PRINT(" AT3.2"); // AT3.2 Let EN be the N's entry in P
+ for( child_in_parent = 0; child_in_parent < parent->children_nodes.size(); child_in_parent++ ){
+ if( parent->children_nodes[ child_in_parent ].data == position){
+ _RTREE_PRINT(" child_in_parent: " << child_in_parent);
+ break;
+ }
+ }
+
+ _RTREE_PRINT(" AT3.3");
+ // AT3.2 Adjust EN bounding box so that it tightly enclosses all entry rectangles in N
+ recalculate_bounding_box( parent, position, child_in_parent );
+
+
+ _RTREE_PRINT(" AT4"); // AT4
+ if( split_performed ){
+ // create new record (from group_b)
+ //RTreeNode* new_node = new RTreeNode();
+ Rect new_record_bounding;
+
+ RTreeRecord_NonLeaf new_record = create_nonleaf_record_from_rtreenode( new_record_bounding, node_division.second );
+
+ // install new entry (group_b)
+ if( parent->children_nodes.size() < max_records ){
+ parent->children_nodes.push_back( new_record );
+ split_performed = false;
+ }
+ else{
+ parent->children_nodes.push_back( new_record );
+ node_division = quadratic_split( parent ); // AT5
+ split_performed = true;
+ }
+
+ }
+ _RTREE_PRINT(" AT5"); // AT5
+ position = parent;
+ }
+
+ return root_split_performed;
+}
+
+/*
+find_parent:
+The source only mentions that we should "find" the parent. But it doesn't seay how. So we made a
+modification of search.
+
+Initially we take the root, a rect of the node, of which the parent we look for and the node we seek
+
+We do a spatial search for this rect. If we find get an intersecttion with the rect we check if the
+child is the one we look for.
+If not we call find_parent again recursively
+*/
+
+std::pair< RTreeNode*, bool > RTree::find_parent( RTreeNode* subtree_root,
+ Rect search_area,
+ RTreeNode* wanted) const
+{
+ _RTREE_PRINT("find_parent");
+
+ std::pair< RTreeNode*, bool > result;
+ if( subtree_root->children_nodes.size() > 0 ){
+
+ for( unsigned i=0; i < subtree_root->children_nodes.size(); i++ ){
+ if( subtree_root->children_nodes[i].data == wanted){
+ _RTREE_PRINT("FOUND!!"); // non leaf node
+ return std::make_pair( subtree_root, true );
+ }
+
+ if( subtree_root->children_nodes[i].bounding_box.intersects( search_area ) ){
+ result = find_parent( subtree_root->children_nodes[i].data, search_area, wanted);
+ if ( result.second ){
+ break;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+
+void RTree::copy_group_a_to_existing_node( RTreeNode *position, RTreeNode* group_a ){
+ // clear position (the one that was split) and put there all the nodes of group_a
+ if( position->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" copy_group...(): install group A to existing non-leaf node");
+ // non leaf-node: position
+ position->children_nodes.clear();
+ for(auto & children_node : group_a->children_nodes){
+ position->children_nodes.push_back( children_node );
+ }
+ }
+ else{
+ _RTREE_PRINT(" copy_group...(): install group A to existing leaf node");
+ // leaf-node: positions
+ position->children_leaves.clear();
+ for(auto & children_leave : group_a->children_leaves){
+ position->children_leaves.push_back( children_leave );
+ }
+ }
+}
+
+
+
+RTreeRecord_NonLeaf RTree::create_nonleaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode* rtreenode ){
+
+ if( rtreenode->children_nodes.size() > 0 ){
+ // found bounding box of new entry
+ new_entry_bounding = Rect( rtreenode->children_nodes[0].bounding_box );
+ for(unsigned i = 1; i < rtreenode->children_nodes.size(); i++ ){
+ new_entry_bounding.unionWith( rtreenode->children_nodes[ i ].bounding_box );
+ }
+ }
+ else{ // non leaf: rtreenode
+ // found bounding box of new entry
+ new_entry_bounding = Rect( rtreenode->children_leaves[0].bounding_box );
+ for(unsigned i = 1; i < rtreenode->children_leaves.size(); i++ ){
+ new_entry_bounding.unionWith( rtreenode->children_leaves[ i ].bounding_box );
+ }
+ }
+ return RTreeRecord_NonLeaf( new_entry_bounding, rtreenode );
+}
+
+
+
+/*
+ print the elements of the tree
+ based on ordered tree walking
+*/
+void RTree::print_tree(RTreeNode* subtree_root, int depth ) const{
+
+ if( subtree_root->children_nodes.size() > 0 ){
+
+ // descend in each one of the elements and call print_tree
+ for( unsigned i=0; i < subtree_root->children_nodes.size(); i++ ){
+ //print spaces for indentation
+ for(int j=0; j < depth; j++){
+ std::cout << " " ;
+ }
+
+ std::cout << subtree_root->children_nodes[i].bounding_box << ", " << subtree_root->children_nodes.size() << std::endl ;
+ _RTREE_PRINT_TREE_INS( subtree_root->children_nodes[i].data, depth+1, used_during_insert);
+ }
+
+ }
+ else{
+ for(int j=0; j < depth; j++){
+ std::cout << " " ;
+ }
+ std::cout << subtree_root->children_leaves.size() << ": " ;
+
+ // print all the elements of the leaf node
+ for(auto & children_leave : subtree_root->children_leaves){
+ std::cout << children_leave.data << ", " ;
+ }
+ std::cout << std::endl ;
+
+ }
+}
+
+
+void RTree::sanity_check(RTreeNode* subtree_root, int depth, bool used_during_insert ) const{
+
+ if( subtree_root->children_nodes.size() > 0 ){
+ // descend in each one of the elements and call sanity_check
+ for(auto & children_node : subtree_root->children_nodes){
+ sanity_check( children_node.data, depth+1, used_during_insert);
+ }
+
+
+ // sanity check
+ if( subtree_root != root ){
+ assert( subtree_root->children_nodes.size() >= min_records);
+ }
+/*
+ else{
+ assert( subtree_root->children_nodes.size() >= 1);
+ }
+*/
+
+ if( used_during_insert ){
+ // allow one more during for insert
+ assert( subtree_root->children_nodes.size() <= max_records + 1 );
+ }
+ else{
+ assert( subtree_root->children_nodes.size() <= max_records );
+ }
+
+ }
+ else{
+ // sanity check
+ if( subtree_root != root ){
+ assert( subtree_root->children_leaves.size() >= min_records);
+ }
+/*
+ else{
+ assert( subtree_root->children_leaves.size() >= 1);
+ }
+*/
+
+ if( used_during_insert ){
+ // allow one more during for insert
+ assert( subtree_root->children_leaves.size() <= max_records + 1 );
+ }
+ else{
+ assert( subtree_root->children_nodes.size() <= max_records );
+ }
+ }
+}
+
+
+
+/*=============================================================================
+ search
+===============================================================================
+Given an RTree whose root node is T find all index records whose rects overlap search rect S
+S1) Search subtrees:
+ IF T isn't a leaf, check every entry E to determine whether E I overlaps S
+ FOR all overlapping entries invoke Search on the tree whose root node is pointed by E P
+S2) ELSE T is leaf
+ check all entries E to determine whether E I overlaps S
+ IF so E is a qualifying record
+*/
+
+
+void RTree::search( const Rect &search_area, std::vector< int >* result, const RTreeNode* subtree ) const {
+ // S1
+ if( subtree->children_nodes.size() > 0 ){ // non-leaf: subtree
+ for(const auto & children_node : subtree->children_nodes){
+ if( children_node.bounding_box.intersects( search_area ) ){
+ search( search_area, result, children_node.data );
+ }
+ }
+ }
+ // S2
+ else{ // leaf: subtree
+ for(const auto & children_leave : subtree->children_leaves){
+ if( children_leave.bounding_box.intersects( search_area ) ){
+ result->push_back( children_leave.data );
+ }
+ }
+ }
+}
+
+
+/*=============================================================================
+ erase
+===============================================================================
+we changed steps D2)
+D1) Find node containing record
+ Invoke find_leaf() to locate the leaf node L containing E
+ IF record is found stop
+D2) Delete record
+ Remove E from L (it happened in find_leaf step FL2)
+D3) Propagate changes
+ Invoke condense_tree, passing L
+D4) Shorten tree
+ If root node has only one child, after the tree was adjusted, make the child new root
+
+return
+0 on success
+1 in case no entry was found
+
+*/
+//int RTree::erase( const RTreeRecord_Leaf & record_to_erase ){
+int RTree::erase( const Rect &search_area, const int shape_to_delete ){
+ _RTREE_PRINT("\n=====================================");
+ _RTREE_PRINT("erase element: " << shape_to_delete);
+ // D1 + D2: entry is deleted in find_leaf
+ _RTREE_PRINT("D1 & D2 : find and delete the leaf");
+ RTreeNode* contains_record = find_leaf( root, search_area, shape_to_delete );
+ if( !contains_record ){ // no entry returned from find_leaf
+ return 1; // no entry found
+ }
+
+ // D3
+ //bool root_elimination_performed = condense_tree( contains_record );
+
+ // D4
+
+ //if( root_elimination_performed ){
+ if( root->children_nodes.size() > 0 ){ // non leaf: root
+ // D4
+ if( root->children_nodes.size() == 1 ){
+ _RTREE_PRINT("D4 : non leaf: ");
+ tree_height--;
+ RTreeNode* t = root;
+ root = root->children_nodes[0].data;
+ delete t;
+ }
+
+ }
+ else { // leaf: root
+ // D4
+ // do nothing
+ }
+ sanity_check( root, 0 );
+ return 0; // success
+}
+
+
+/*
+ find_leaf()
+Given an RTree whose root node is T find the leaf node containing index entry E
+
+FL1) Search subtrees
+ IF T is non leaf, check each entry F in T to determine if F I overlaps E I
+ foreach such entry invoke find_leaf on the tree whose root is pointed to by F P until E is
+ found or all entries have been checked
+FL2) search leaf node for record
+ IF T is leaf, check each entry to see if it matches E
+ IF E is found return T
+ AND delete element E (step D2)
+*/
+
+RTreeNode* RTree::find_leaf( RTreeNode* subtree, const Rect &search_area, const int shape_to_delete ) const {
+ // FL1
+ if( subtree->children_nodes.size() > 0 ){ // non-leaf: subtree
+ for(auto & children_node : subtree->children_nodes){
+ if( children_node.bounding_box.intersects( search_area ) ){
+ RTreeNode* t = find_leaf( children_node.data, search_area, shape_to_delete );
+ if( t ){ // if search was successful terminate
+ return t;
+ }
+ }
+ }
+ }
+ // FL2
+ else{ // leaf: subtree
+ for( std::vector< RTreeRecord_Leaf >::iterator it = subtree->children_leaves.begin(); it!=subtree->children_leaves.end(); ++it ){
+ if( it->data == shape_to_delete ){
+ // delete element: implement step D2)
+ subtree->children_leaves.erase( it );
+ return subtree;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ condense_tree()
+Given a Leaf node L from which an entry has been deleted eliminate the node if it has too few entries and reallocate its entries
+Propagate node elimination upwards as necessary
+Adjust all covering recsts n the path to the root making them smaller if possible
+
+CT1) Initialize
+ Set N=L
+ Set Q the set of eliminated nodes to be empty
+CT2) // Find parent entry (was there but doesn't make sense)
+ IF N is the root
+ goto CT6
+ ELSE
+ 1) Find parent entry
+ 2) let P be the parent of N
+ 3) and let EN be N's entry in P
+CT3) IF N has fewer than m entries
+ Eliminate underfull node
+ 1) delete EN from P
+ 2) and add N to set Q
+CT4) ELSE
+ adjust EN I to tightly contain all entries in N
+CT5) move up one level in tree
+ set N=P and repeat from CT2
+
+CT6) Re insert orphaned entries
+ Re-inser all entreis of nodes in set Q
+ Entries from eliminated leaf nodes are re-inserted in tree leaves (like in insert)
+ BUT non-leaf nodes must be placed higher in the tree so that leaves of their dependent subtrees
+ will be on the same level as leaves of the main tree. (on the same height they originally were)
+ (not mentioned in the source description: the criteria for placing the node should be
+ again TODO ??? least enlargement)
+
+*/
+// TODO this can be merged with adjust_tree or refactor to reutilize some parts. less readable
+bool RTree::condense_tree( RTreeNode* position )
+{
+ RTreeNode* parent;
+ unsigned child_in_parent = 0; // the element in parent node that points to current posistion
+
+ std::pair< RTreeNode*, bool > find_result;
+ bool elimination_performed = false;
+ bool root_elimination_performed = false;
+ unsigned current_height = tree_height+1;
+ Rect special_case_bounding_box;
+ _RTREE_PRINT(" condense_tree");
+ _RTREE_PRINT(" CT1");
+ // leaf records that were eliminated due to under-full node
+ std::vector< RTreeRecord_Leaf > Q_leaf_records( 0 );
+
+ // < non-leaf records, their height > that were eliminated due to under-full node
+ std::vector< std::pair< RTreeRecord_NonLeaf, unsigned > > Q_nonleaf_records( 0 );
+
+
+ while( true ){
+
+ // check for loop BREAK
+ if( position == root ){
+ _RTREE_PRINT(" CT2 position is root");
+ if( elimination_performed ){
+ root_elimination_performed = true;
+ }
+ break;
+ }
+
+ /*
+ pick randomly, let's say the 1st entry of the current node.
+ Search for this spatial area in the tree, and stop to the parent node.
+ Then find position of current node pointer, in the parent node.
+ */
+ /*
+ special case. if elimination due to children being underfull was performed AND
+ AND parent had only 1 record ,then this one record was removed.
+ */
+ if( position->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" CT2.1 - 2 non leaf: find parent, P is parent");
+ // CT2.1 find parent. By definition it's nonleaf
+ find_result = find_parent( root, position->children_nodes[0].bounding_box, position);
+ }
+ else if( position->children_nodes.size() == 0
+ && position->children_leaves.size() == 0
+ && elimination_performed )
+ { // special case
+ _RTREE_PRINT(" CT2.1 - 2 special case: find parent, P is parent");
+ // CT2.1 find parent. By definition it's nonleaf
+ find_result = find_parent( root, special_case_bounding_box, position);
+ }
+ else{
+ _RTREE_PRINT(" CT2.1 - 2 leaf: find parent, P is parent");
+ // CT2.1 find parent. By definition it's nonleaf
+ find_result = find_parent( root, position->children_leaves[0].bounding_box, position);
+ }
+ // CT2.2 Let P be the parent of N
+ parent = find_result.first;
+
+
+ // parent is a non-leaf, by definition. Calculate "child_in_parent"
+ _RTREE_PRINT(" CT2.3 find in parent, position's record EN");
+ // CT2.3 Let EN be the N's entry in P
+ for( child_in_parent = 0; child_in_parent < parent->children_nodes.size(); child_in_parent++ ){
+ if( parent->children_nodes[ child_in_parent ].data == position){
+ _RTREE_PRINT(" child_in_parent: " << child_in_parent << " out of " << parent->children_nodes.size() << " (size)" );
+ break;
+ }
+ }
+
+ if( position->children_nodes.size() > 0 ){ // non leaf node: position
+ _RTREE_PRINT(" CT3 nonleaf: eliminate underfull node");
+ // CT3 Eliminate underfull node
+ if( position->children_nodes.size() < min_records ){
+ _RTREE_PRINT(" CT3.2 add N to Q");
+ // CT3.2 add N to set Q ( EN the record that points to N )
+ for(auto & children_node : position->children_nodes){
+ _RTREE_PRINT(" i " << i );
+ std::pair< RTreeRecord_NonLeaf, unsigned > t = std::make_pair( children_node, current_height-1);
+ Q_nonleaf_records.push_back( t );
+
+ }
+ special_case_bounding_box = parent->children_nodes[ child_in_parent ].bounding_box;
+
+ _RTREE_PRINT(" CT3.1 delete in parent, position's record EN");
+ // CT3.1 delete EN from P ( parent is by definition nonleaf )
+ if( remove_record_from_parent( parent, position ) ){ // TODO does erase, delete to the pointer ???
+ _RTREE_PRINT(" remove_record_from_parent error ");
+ }
+ elimination_performed = true;
+ }
+ else{
+ _RTREE_PRINT(" CT4 "); /// CT4) if not underfull
+ recalculate_bounding_box( parent, position, child_in_parent );
+ elimination_performed = false;
+ }
+
+ }
+ else{ // leaf node: position
+ _RTREE_PRINT(" CT3 leaf: eliminate underfull node");
+ // CT3 Eliminate underfull node
+ if( position->children_leaves.size() < min_records ){
+ _RTREE_PRINT(" CT3.2 add N to Q " << position->children_leaves.size() );
+ // CT3.2 add N to set Q
+ for(auto & children_leave : position->children_leaves){
+ _RTREE_PRINT(" i " << i );
+ Q_leaf_records.push_back( children_leave ); // TODO problem here
+ special_case_bounding_box = children_leave.bounding_box;
+ }
+
+ _RTREE_PRINT(" CT3.1 delete in parent, position's record EN");
+ // CT3.1 delete EN from P ( parent is by definition nonleaf )
+ if( remove_record_from_parent( parent, position ) ){
+ _RTREE_PRINT(" remove_record_from_parent error ");
+ }
+ elimination_performed = true;
+ }
+ else{
+ _RTREE_PRINT(" CT4 "); /// CT4) if not underfull
+ recalculate_bounding_box( parent, position, child_in_parent );
+ elimination_performed = false;
+ }
+ }
+ _RTREE_PRINT(" CT5 ");// CT5) move up one level in tree
+ position = parent;
+
+ current_height--;
+ }
+ // CT6: reinsert
+ _RTREE_PRINT(" ------ Q_leaf");
+ for( std::vector< RTreeRecord_Leaf >::iterator it = Q_leaf_records.begin(); it != Q_leaf_records.end(); ++it ){
+ _RTREE_PRINT(" leaf:" << (*it).data);
+ }
+ _RTREE_PRINT(" ------ Q_nonleaf");
+ for( std::vector< std::pair< RTreeRecord_NonLeaf, unsigned > >::iterator it = Q_nonleaf_records.begin(); it != Q_nonleaf_records.end(); ++it ){
+ _RTREE_PRINT(" ------- " << it->second );
+ _RTREE_PRINT_TREE( it->first.data, 0);
+ }
+
+ _RTREE_PRINT(" CT6 ");
+ for(auto & Q_leaf_record : Q_leaf_records){
+ insert( Q_leaf_record );
+ _RTREE_PRINT(" inserted leaf:" << (*it).data << " ------------");
+ _RTREE_PRINT_TREE( root, 0);
+ }
+
+
+ for(auto & Q_nonleaf_record : Q_nonleaf_records){
+ insert( RTreeRecord_Leaf() , true, Q_nonleaf_record.second, Q_nonleaf_record.first );
+ _RTREE_PRINT(" inserted nonleaf------------");
+ _RTREE_PRINT_TREE( root, 0);
+ // TODO this fake RTreeRecord_Leaf() looks stupid. find better way to do this ???
+ }
+
+ return root_elimination_performed;
+}
+
+
+/*
+given:
+- a parent
+- a child node
+- and the position of the child node in the parent
+recalculate the parent record's bounding box of the child, in order to ightly contain all entries of child
+
+NOTE! child must be indeed child of the parent, otherwise it screws up things. So find parent and child
+before calling this function
+*/
+void RTree::recalculate_bounding_box( RTreeNode* parent, RTreeNode* child, unsigned &child_in_parent ) {
+ if( child->children_nodes.size() > 0 ){
+ _RTREE_PRINT(" non-leaf: recalculate bounding box of parent "); // non leaf-node: child
+ parent->children_nodes[ child_in_parent ].bounding_box = Rect( child->children_nodes[0].bounding_box );
+ for( unsigned i=1; i < child->children_nodes.size(); i++ ){
+ parent->children_nodes[ child_in_parent ].bounding_box.unionWith( child->children_nodes[i].bounding_box );
+ }
+ }
+ else{
+ _RTREE_PRINT(" leaf: recalculate bounding box of parent "); // leaf-node: child
+ parent->children_nodes[ child_in_parent ].bounding_box = Rect( child->children_leaves[0].bounding_box );
+
+ for( unsigned i=1; i < child->children_leaves.size(); i++ ){
+ parent->children_nodes[ child_in_parent ].bounding_box.unionWith( child->children_leaves[i].bounding_box );
+ }
+ }
+}
+
+/*
+given:
+- a parent
+- a child node
+it removes the child record from the parent
+
+NOTE! child must be indeed child of the parent, otherwise it screws up things.
+So find parent and child before calling this function
+*/
+int RTree::remove_record_from_parent( RTreeNode* parent, RTreeNode* child ) {
+ _RTREE_PRINT( "remove_record_from_parent)" );
+ for( std::vector< RTreeRecord_NonLeaf >::iterator it = parent->children_nodes.begin(); it!=parent->children_nodes.end(); ++it ){
+ if( it->data == child ){
+ // delete element: implement step D2)
+ parent->children_nodes.erase( it );
+ return 0; // success
+ }
+ }
+ return 1; // failure
+}
+
+/*=============================================================================
+TODO update
+===============================================================================
+*/
+
+
+};
+
+/*
+ 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/src/2geom/parallelogram.cpp b/src/2geom/parallelogram.cpp
new file mode 100644
index 0000000..b477a01
--- /dev/null
+++ b/src/2geom/parallelogram.cpp
@@ -0,0 +1,136 @@
+/*
+ * Authors:
+ * Thomas Holder
+ * Sergei Izmailov
+ *
+ * Copyright 2020 Authors
+ *
+ * SPDX-License-Identifier: LGPL-2.1 or MPL-1.1
+ */
+
+#include <2geom/basic-intersection.h>
+#include <2geom/parallelogram.h>
+
+#include <cassert>
+
+namespace Geom {
+
+namespace {
+/// Return true if `p` is inside a unit rectangle
+inline bool unit_rect_contains(Point const &p)
+{
+ return 0 <= p.x() && p.x() <= 1 && //
+ 0 <= p.y() && p.y() <= 1;
+}
+
+/// N'th corner of a unit rectangle
+inline Point unit_rect_corner(unsigned i)
+{
+ assert(i < 4);
+ unsigned const y = i >> 1;
+ unsigned const x = (i & 1) ^ y;
+ return Point(x, y);
+}
+} // namespace
+
+Point Parallelogram::corner(unsigned i) const
+{
+ assert(i < 4);
+ return unit_rect_corner(i) * m_affine;
+}
+
+Rect Parallelogram::bounds() const
+{
+ Rect rect(corner(0), corner(2));
+ rect.expandTo(corner(1));
+ rect.expandTo(corner(3));
+ return rect;
+}
+
+bool Parallelogram::intersects(Parallelogram const &other) const
+{
+ if (m_affine.isSingular() || other.m_affine.isSingular()) {
+ return false;
+ }
+
+ auto const affine1 = other.m_affine * m_affine.inverse();
+ auto const affine2 = affine1.inverse();
+
+ // case 1: any corner inside the other rectangle
+ for (unsigned i = 0; i != 4; ++i) {
+ auto const p = unit_rect_corner(i);
+ if (unit_rect_contains(p * affine1) || //
+ unit_rect_contains(p * affine2)) {
+ return true;
+ }
+ }
+
+ // case 2: any sides intersect (check diagonals)
+ for (unsigned i = 0; i != 2; ++i) {
+ auto const A = corner(i);
+ auto const B = corner((i + 2) % 4);
+ for (unsigned j = 0; j != 2; ++j) {
+ auto const C = other.corner(j);
+ auto const D = other.corner((j + 2) % 4);
+ if (non_collinear_segments_intersect(A, B, C, D)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Parallelogram::contains(Point const &p) const
+{
+ return !m_affine.isSingular() && //
+ unit_rect_contains(p * m_affine.inverse());
+}
+
+bool Parallelogram::contains(Parallelogram const &other) const
+{
+ if (m_affine.isSingular()) {
+ return false;
+ }
+
+ auto const inv = m_affine.inverse();
+
+ for (unsigned i = 0; i != 4; ++i) {
+ if (!unit_rect_contains(other.corner(i) * inv)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Coord Parallelogram::minExtent() const
+{
+ return std::min(m_affine.expansionX(), //
+ m_affine.expansionY());
+}
+
+Coord Parallelogram::maxExtent() const
+{
+ return std::max(m_affine.expansionX(), //
+ m_affine.expansionY());
+}
+
+bool Parallelogram::isSheared(Coord eps) const
+{
+ return !are_near(dot(m_affine.xAxis(), m_affine.yAxis()), //
+ 0.0, eps);
+}
+
+} // 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/src/2geom/parting-point.cpp b/src/2geom/parting-point.cpp
new file mode 100644
index 0000000..3e3e803
--- /dev/null
+++ b/src/2geom/parting-point.cpp
@@ -0,0 +1,280 @@
+/** @file Implementation of parting_point(Path const&, Path const&, Coord)
+ */
+/* An algorithm to find the first parting point of two paths.
+ *
+ * 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 <2geom/path.h>
+#include <2geom/point.h>
+
+namespace Geom
+{
+
+PathIntersection parting_point(Path const &first, Path const &second, Coord precision)
+{
+ Path const *paths[2] = { &first, &second };
+ Point const starts[2] = { first.initialPoint(), second.initialPoint() };
+
+ if (!are_near(starts[0], starts[1], precision)) {
+ auto const invalid = PathTime(0, -1.0);
+ return PathIntersection(invalid, invalid, middle_point(starts[0], starts[1]));
+ }
+
+ if (first.empty() || second.empty()) {
+ auto const start_time = PathTime(0, 0.0);
+ return PathIntersection(start_time, start_time, middle_point(starts[0], starts[1]));
+ }
+
+ size_t const curve_count[2] = { first.size(), second.size() };
+ Coord const max_time[2] = { first.timeRange().max(), second.timeRange().max() };
+
+ /// Curve indices up until which the paths are known to overlap
+ unsigned pos[2] = { 0, 0 };
+ /// Curve times on the curves with indices pos[] up until which the
+ /// curves are known to overlap ahead of the nodes.
+ Coord curve_times[2] = { 0.0, 0.0 };
+
+ bool leg = 0; ///< Flag indicating which leg is stepping on the ladder
+ bool just_changed_legs = false;
+
+ /* The ladder algorithm takes steps along the two paths, as if they the stiles of
+ * an imaginary ladder. Note that the nodes (X) on boths paths may not coincide:
+ *
+ * paths[0] START--------X-----------X-----------------------X---------X----> ...
+ * paths[1] START--------------X-----------------X-----------X--------------> ...
+ *
+ * The variables pos[0], pos[1] are the indices of the nodes we've cleared so far;
+ * i.e., we know that the portions before pos[] overlap.
+ *
+ * In each iteration of the loop, we move to the next node along one of the paths;
+ * the variable `leg` tells us which path. We find the point nearest to that node
+ * on the first unprocessed curve of the other path and check the curve time.
+ *
+ * Suppose the current node positions are denoted by P; one possible location of
+ * the nearest point (N) to the next node is:
+ *
+ * ----P------------------N--X---- paths[!leg]
+ * --------P--------------X------- paths[leg] (we've stepped forward from P to X)
+ *
+ * We detect this situation when we find that the curve time of N is < 1.0.
+ * We then create a trimmed version of the top curve so that it corresponds to
+ * the current bottom curve:
+ *
+ * ----P----------------------N--X---------- paths[!leg]
+ * [------------------] trimmed curve
+ * --------P------------------X------------- paths[leg]
+ *
+ * Using isNear(), we can compare the trimmed curve to the front curve (P--X) on
+ * paths[leg]; if they are indeed near, then pos[leg] can be incremented.
+ *
+ * Another possibility is that we overstep the end of the other curve:
+ *
+ * ----P-----------------X------------------ paths[!leg]
+ * N
+ * --------P------------------X------------- paths[leg]
+ *
+ * so the nearest point N now coincides with a node on the top path. We detect
+ * this situation by observing that the curve time of N is close to 1. In case
+ * of such overstep, we change legs by flipping the `leg` variable:
+ *
+ * ----P-----------------X------------------ paths[leg]
+ * --------P------------------X------------- paths[!leg]
+ *
+ * We can now continue the stepping procedure, but the next step will be taken on
+ * the path `paths[leg]`, so it should be a shorter step (if it isn't, the paths
+ * must have diverged and we're done):
+ *
+ * ----P-----------------X------------------ paths[leg]
+ * --------P-------------N----X------------- paths[!leg]
+ *
+ * Another piece of data we hold on to are the curve times on the current curves
+ * up until which the paths have been found to coincide. In other words, at every
+ * step of the algorithm we know that the curves agree up to the path-times
+ * PathTime(pos[i], curve_times[i]).
+ *
+ * In the situation mentioned just above, the times (T) will be as follows:
+ *
+ * ----P---T-------------X------------------ paths[leg]
+ *
+ * --------P-------------N----X------------- paths[!leg]
+ * T
+ *
+ * In this example, the time on top path is > 0.0, since the T mark is further
+ * ahead than P on that path. This value of the curve time is needed to correctly
+ * crop the top curve for the purpose of the isNear() comparison:
+ *
+ * ----P---T-------------X---------- paths[leg]
+ * [-------------] comparison curve (cropped from paths[leg])
+ * [-------------] comparison curve (cropped from paths[!leg])
+ * --------P-------------N----X----- paths[!leg]
+ * T
+ *
+ * In fact, the lower end of the curve time range for cropping is always
+ * given by curve_times[i].
+ *
+ * The iteration ends when we find that the two paths have diverged or when we
+ * reach the end. When that happens, the positions and curve times will be
+ * the PathTime components of the actual point of divergence on both paths.
+ */
+
+ /// A closure to crop and compare the curve pieces ([----] in the diagrams above).
+ auto const pieces_agree = [&](Coord time_on_other) -> bool {
+ Curve *pieces[2];
+ // The leg-side curve is always cropped to the end:
+ pieces[ leg] = paths[ leg]->at(pos[ leg]).portion(curve_times[ leg], 1.0);
+ // The other one is cropped to a variable curve time:
+ pieces[!leg] = paths[!leg]->at(pos[!leg]).portion(curve_times[!leg], time_on_other);
+ bool ret = pieces[0]->isNear(*pieces[1], precision);
+ delete pieces[0];
+ delete pieces[1];
+ return ret;
+ };
+
+ /// A closure to skip degenerate curves; returns true if we reached the end.
+ auto const skip_degenerates = [&](size_t which) -> bool {
+ while (paths[which]->at(pos[which]).isDegenerate()) {
+ ++pos[which];
+ curve_times[which] = 0.0;
+ if (pos[which] == curve_count[which]) {
+ return true; // We've reached the end
+ }
+ }
+ return false;
+ };
+
+ // Main loop of the ladder algorithm.
+ while (pos[0] < curve_count[0] && pos[1] < curve_count[1]) {
+ // Skip degenerate curves if any.
+ if (skip_degenerates(0)) {
+ break;
+ }
+ if (skip_degenerates(1)) {
+ break;
+ }
+
+ // Try to step to the next node with the current leg and see what happens.
+ Coord forward_coord = (Coord)(pos[leg] + 1);
+ if (forward_coord > max_time[leg]) {
+ forward_coord = max_time[leg];
+ }
+ auto const step_point = paths[leg]->pointAt(forward_coord);
+ auto const time_on_other = paths[!leg]->at(pos[!leg]).nearestTime(step_point);
+
+ if (are_near(time_on_other, 1.0, precision) &&
+ are_near(step_point, paths[!leg]->pointAt(pos[!leg] + 1), precision))
+ { // The step took us very near to the first uncertified node on the other path.
+ just_changed_legs = false;
+ //
+ // -------PT-----------------X---------- paths[!leg]
+ // --P-----T-----------------X---------- paths[leg]
+ // ^
+ // endpoints (almost) coincide
+ //
+ // We should compare the curves cropped to the end:
+ //
+ // --------T-----------------X---------- paths[!leg]
+ // [-----------------]
+ // [-----------------]
+ // --------T-----------------X---------- paths[leg]
+ if (pieces_agree(1.0)) {
+ // The curves are nearly identical, so we advance both positions
+ // and zero out the forward curve times.
+ for (size_t i = 0; i < 2; i++) {
+ pos[i]++;
+ curve_times[i] = 0.0;
+ }
+ } else { // We've diverged.
+ break;
+ }
+ } else if (time_on_other < 1.0 - precision) {
+ just_changed_legs = false;
+
+ // The other curve is longer than our step! We trim the other curve to the point
+ // nearest to the step point and compare the resulting pieces.
+ //
+ // --------T-----------------N--------X---- paths[!leg]
+ // [-----------------]
+ // [-----------------]
+ // --------T-----------------X------------- paths[leg]
+ //
+ if (pieces_agree(time_on_other)) { // The curve pieces are near to one another!
+ // We can advance our position and zero out the curve time:
+ pos[leg]++;
+ curve_times[leg] = 0.0;
+ // But on the other path, we can only advance the time, not the curve index:
+ curve_times[!leg] = time_on_other;
+ } else { // We've diverged.
+ break;
+ }
+ } else {
+ // The other curve is shorter than ours, which means that we've overstepped.
+ // We change legs and try to take a shorter step in the next iteration.
+ if (just_changed_legs) {
+ // We already changed legs before and it didn't help, i.e., we've diverged.
+ break;
+ } else {
+ leg = !leg;
+ just_changed_legs = true;
+ }
+ }
+ }
+
+ // Compute the parting time on both paths
+ PathTime path_times[2];
+ for (size_t i = 0; i < 2; i++) {
+ path_times[i] = (pos[i] == curve_count[i]) ? PathTime(curve_count[i] - 1, 1.0)
+ : PathTime(pos[i], curve_times[i]);
+ }
+
+ // Get the parting point from the numerically nicest source
+ Point parting_pt;
+ if (curve_times[0] == 0.0) {
+ parting_pt = paths[0]->pointAt(path_times[0]);
+ } else if (curve_times[1] == 0.0) {
+ parting_pt = paths[1]->pointAt(path_times[1]);
+ } else {
+ parting_pt = middle_point(first.pointAt(path_times[0]), second.pointAt(path_times[1]));
+ }
+
+ return PathIntersection(path_times[0], path_times[1], std::move(parting_pt));
+}
+
+} // 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/src/2geom/path-extrema.cpp b/src/2geom/path-extrema.cpp
new file mode 100644
index 0000000..319ccec
--- /dev/null
+++ b/src/2geom/path-extrema.cpp
@@ -0,0 +1,156 @@
+/** @file Implementation of Path::extrema()
+ */
+/* An algorithm to find the points on a path where a given coordinate
+ * attains its minimum and maximum values.
+ *
+ * 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 <2geom/curve.h>
+#include <2geom/path.h>
+#include <2geom/point.h>
+
+namespace Geom {
+
+/** Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise. */
+inline static float sign(double number)
+{
+ if (number > 0.0) {
+ return 1.0;
+ } else if (number < 0.0) {
+ return -1.0;
+ }
+ return 0.0;
+}
+
+/** @brief Determine whether the d-coordinate increases or decreases at the given path time.
+ *
+ * @param path A path.
+ * @param time A forward-normalized time on the given path.
+ * @param d The coordinate about which we want to know whether it increases.
+ * @return +1.0 if the coordinate increases, -1.0 if it decreases, 0.0 if it remains constant.
+*/
+static float find_direction_of_travel(Path const &path, PathTime const &time, Dim2 d)
+{
+ if (time.t == 0.0) { // We're at a node point
+ if (time.curve_index == 0) { // Starting point of the path.
+ if (path.closed()) {
+ return sign(path.initialUnitTangent()[d] + path.finalUnitTangent()[d]);
+ } else {
+ return sign(path.initialUnitTangent()[d]);
+ }
+ } else if (time.curve_index == path.size()) { // End point of the path.
+ if (path.closed()) {
+ return sign(path.initialUnitTangent()[d] + path.finalUnitTangent()[d]);
+ } else {
+ return sign(path.finalUnitTangent()[d]);
+ }
+ }
+
+ // Otherwise, check the average of the two unit tangent vectors.
+ auto const outgoing_tangent = path.curveAt(time.curve_index).unitTangentAt(0.0);
+ auto const incoming_tangent = path.curveAt(time.curve_index - 1).unitTangentAt(1.0);
+ return sign(outgoing_tangent[d] + incoming_tangent[d]);
+ }
+ // We're in the middle of a curve
+ return sign(path.curveAt(time.curve_index).unitTangentAt(time.t)[d]);
+}
+
+/* Find information about the points on the path where the specified
+ * coordinate attains its minimum and maximum values.
+ */
+PathExtrema Path::extrema(Dim2 d) const
+{
+ auto const ZERO_TIME = PathTime(0, 0);
+
+ // Handle the trivial case of an empty path.
+ if (empty()) {
+ auto const origin = initialPoint();
+ return PathExtrema{
+ .min_point = origin,
+ .max_point = origin,
+ .glance_direction_at_min = 0.0,
+ .glance_direction_at_max = 0.0,
+ .min_time = ZERO_TIME,
+ .max_time = ZERO_TIME
+ };
+ }
+
+ // Set up the simultaneous min-max search
+ Point min_point = initialPoint(), max_point = min_point;
+ auto min_time = ZERO_TIME, max_time = ZERO_TIME;
+ unsigned curve_counter = 0;
+
+ /// A closure to update the current minimum and maximum values.
+ auto const update_minmax = [&](Point const &new_point, Coord t) {
+ if (new_point[d] < min_point[d]) {
+ min_point = new_point;
+ min_time = PathTime(curve_counter, t);
+ } else if (new_point[d] > max_point[d]) {
+ max_point = new_point;
+ max_time = PathTime(curve_counter, t);
+ }
+ };
+
+ // Iterate through the curves, searching for min and max.
+ for (auto const &curve : _data->curves) {
+ // Check the starting node first
+ update_minmax(curve.initialPoint(), 0.0);
+
+ // Check the critical points (zeroes of the derivative).
+ std::unique_ptr<Curve> const derivative{curve.derivative()};
+ for (auto root : derivative->roots(0.0, d)) {
+ update_minmax(curve.pointAt(root), root);
+ }
+ curve_counter++;
+ }
+
+ auto const other = other_dimension(d);
+ return PathExtrema{
+ .min_point = min_point,
+ .max_point = max_point,
+ .glance_direction_at_min = find_direction_of_travel(*this, min_time, other),
+ .glance_direction_at_max = find_direction_of_travel(*this, max_time, other),
+ .min_time = min_time,
+ .max_time = max_time
+ };
+}
+
+} // 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/src/2geom/path-intersection.cpp b/src/2geom/path-intersection.cpp
new file mode 100644
index 0000000..280d7ba
--- /dev/null
+++ b/src/2geom/path-intersection.cpp
@@ -0,0 +1,728 @@
+#include <2geom/path-intersection.h>
+
+#include <2geom/ord.h>
+
+//for path_direction:
+#include <2geom/sbasis-geometric.h>
+#include <2geom/line.h>
+#ifdef HAVE_GSL
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_multiroots.h>
+#endif
+
+namespace Geom {
+
+/// Compute winding number of the path at the specified point
+int winding(Path const &path, Point const &p) {
+ return path.winding(p);
+}
+
+/**
+ * This function should only be applied to simple paths (regions), as otherwise
+ * a boolean winding direction is undefined. It returns true for fill, false for
+ * hole. Defaults to using the sign of area when it reaches funny cases.
+ */
+bool path_direction(Path const &p) {
+ if(p.empty()) return false;
+
+ /*goto doh;
+ //could probably be more efficient, but this is a quick job
+ double y = p.initialPoint()[Y];
+ double x = p.initialPoint()[X];
+ Cmp res = cmp(p[0].finalPoint()[Y], y);
+ for(unsigned i = 1; i < p.size(); i++) {
+ Cmp final_to_ray = cmp(p[i].finalPoint()[Y], y);
+ Cmp initial_to_ray = cmp(p[i].initialPoint()[Y], y);
+ // if y is included, these will have opposite values, giving order.
+ Cmp c = cmp(final_to_ray, initial_to_ray);
+ if(c != EQUAL_TO) {
+ std::vector<double> rs = p[i].roots(y, Y);
+ for(unsigned j = 0; j < rs.size(); j++) {
+ double nx = p[i].valueAt(rs[j], X);
+ if(nx > x) {
+ x = nx;
+ res = c;
+ }
+ }
+ } else if(final_to_ray == EQUAL_TO) goto doh;
+ }
+ return res < 0;
+
+ doh:*/
+ //Otherwise fallback on area
+
+ Piecewise<D2<SBasis> > pw = p.toPwSb();
+ double area;
+ Point centre;
+ Geom::centroid(pw, centre, area);
+ return area > 0;
+}
+
+//pair intersect code based on njh's pair-intersect
+
+/** A little sugar for appending a list to another */
+template<typename T>
+void append(T &a, T const &b) {
+ a.insert(a.end(), b.begin(), b.end());
+}
+
+/**
+ * Finds the intersection between the lines defined by A0 & A1, and B0 & B1.
+ * Returns through the last 3 parameters, returning the t-values on the lines
+ * and the cross-product of the deltas (a useful byproduct). The return value
+ * indicates if the time values are within their proper range on the line segments.
+ */
+bool
+linear_intersect(Point const &A0, Point const &A1, Point const &B0, Point const &B1,
+ double &tA, double &tB, double &det) {
+ bool both_lines_non_zero = (!are_near(A0, A1)) && (!are_near(B0, B1));
+
+ // Cramer's rule as cross products
+ Point Ad = A1 - A0,
+ Bd = B1 - B0,
+ d = B0 - A0;
+ det = cross(Ad, Bd);
+
+ double det_rel = det; // Calculate the determinant of the normalized vectors
+ if (both_lines_non_zero) {
+ det_rel /= Ad.length();
+ det_rel /= Bd.length();
+ }
+
+ if( fabs(det_rel) < 1e-12 ) { // If the cross product is NEARLY zero,
+ // Then one of the linesegments might have length zero
+ if (both_lines_non_zero) {
+ // If that's not the case, then we must have either:
+ // - parallel lines, with no intersections, or
+ // - coincident lines, with an infinite number of intersections
+ // Either is quite useless, so we'll just bail out
+ return false;
+ } // Else, one of the linesegments is zero, and we might still be able to calculate a single intersection point
+ } // Else we haven't bailed out, and we'll try to calculate the intersections
+
+ double detinv = 1.0 / det;
+ tA = cross(d, Bd) * detinv;
+ tB = cross(d, Ad) * detinv;
+ return (tA >= 0.) && (tA <= 1.) && (tB >= 0.) && (tB <= 1.);
+}
+
+
+#if 0
+typedef union dbl_64{
+ long long i64;
+ double d64;
+};
+
+static double EpsilonOf(double value)
+{
+ dbl_64 s;
+ s.d64 = value;
+ if(s.i64 == 0)
+ {
+ s.i64++;
+ return s.d64 - value;
+ }
+ else if(s.i64-- < 0)
+ return s.d64 - value;
+ else
+ return value - s.d64;
+}
+#endif
+
+#ifdef HAVE_GSL
+struct rparams {
+ Curve const &A;
+ Curve const &B;
+};
+
+static int
+intersect_polish_f (const gsl_vector * x, void *params,
+ gsl_vector * f)
+{
+ const double x0 = gsl_vector_get (x, 0);
+ const double x1 = gsl_vector_get (x, 1);
+
+ Geom::Point dx = ((struct rparams *) params)->A(x0) -
+ ((struct rparams *) params)->B(x1);
+
+ gsl_vector_set (f, 0, dx[0]);
+ gsl_vector_set (f, 1, dx[1]);
+
+ return GSL_SUCCESS;
+}
+#endif
+
+static void
+intersect_polish_root (Curve const &A, double &s, Curve const &B, double &t)
+{
+ std::vector<Point> as, bs;
+ as = A.pointAndDerivatives(s, 2);
+ bs = B.pointAndDerivatives(t, 2);
+ Point F = as[0] - bs[0];
+ double best = dot(F, F);
+
+ for(int i = 0; i < 4; i++) {
+
+ /**
+ we want to solve
+ J*(x1 - x0) = f(x0)
+
+ |dA(s)[0] -dB(t)[0]| (X1 - X0) = A(s) - B(t)
+ |dA(s)[1] -dB(t)[1]|
+ **/
+
+ // We're using the standard transformation matricies, which is numerically rather poor. Much better to solve the equation using elimination.
+
+ Affine jack(as[1][0], as[1][1],
+ -bs[1][0], -bs[1][1],
+ 0, 0);
+ Point soln = (F)*jack.inverse();
+ double ns = s - soln[0];
+ double nt = t - soln[1];
+
+ if (ns<0) ns=0;
+ else if (ns>1) ns=1;
+ if (nt<0) nt=0;
+ else if (nt>1) nt=1;
+
+ as = A.pointAndDerivatives(ns, 2);
+ bs = B.pointAndDerivatives(nt, 2);
+ F = as[0] - bs[0];
+ double trial = dot(F, F);
+ if (trial > best*0.1) // we have standards, you know
+ // At this point we could do a line search
+ break;
+ best = trial;
+ s = ns;
+ t = nt;
+ }
+
+#ifdef HAVE_GSL
+ if(0) { // the GSL version is more accurate, but taints this with GPL
+ int status;
+ size_t iter = 0;
+ const size_t n = 2;
+ struct rparams p = {A, B};
+ gsl_multiroot_function f = {&intersect_polish_f, n, &p};
+
+ double x_init[2] = {s, t};
+ gsl_vector *x = gsl_vector_alloc (n);
+
+ gsl_vector_set (x, 0, x_init[0]);
+ gsl_vector_set (x, 1, x_init[1]);
+
+ const gsl_multiroot_fsolver_type *T = gsl_multiroot_fsolver_hybrids;
+ gsl_multiroot_fsolver *sol = gsl_multiroot_fsolver_alloc (T, 2);
+ gsl_multiroot_fsolver_set (sol, &f, x);
+
+ do
+ {
+ iter++;
+ status = gsl_multiroot_fsolver_iterate (sol);
+
+ if (status) /* check if solver is stuck */
+ break;
+
+ status =
+ gsl_multiroot_test_residual (sol->f, 1e-12);
+ }
+ while (status == GSL_CONTINUE && iter < 1000);
+
+ s = gsl_vector_get (sol->x, 0);
+ t = gsl_vector_get (sol->x, 1);
+
+ gsl_multiroot_fsolver_free (sol);
+ gsl_vector_free (x);
+ }
+#endif
+}
+
+/**
+ * This uses the local bounds functions of curves to generically intersect two.
+ * It passes in the curves, time intervals, and keeps track of depth, while
+ * returning the results through the Crossings parameter.
+ */
+void pair_intersect(Curve const & A, double Al, double Ah,
+ Curve const & B, double Bl, double Bh,
+ Crossings &ret, unsigned depth = 0) {
+ // std::cout << depth << "(" << Al << ", " << Ah << ")\n";
+ OptRect Ar = A.boundsLocal(Interval(Al, Ah));
+ if (!Ar) return;
+
+ OptRect Br = B.boundsLocal(Interval(Bl, Bh));
+ if (!Br) return;
+
+ if(! Ar->intersects(*Br)) return;
+
+ //Checks the general linearity of the function
+ if((depth > 12)) { // || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1
+ //&& B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) {
+ double tA, tB, c;
+ if(linear_intersect(A.pointAt(Al), A.pointAt(Ah),
+ B.pointAt(Bl), B.pointAt(Bh),
+ tA, tB, c)) {
+ tA = tA * (Ah - Al) + Al;
+ tB = tB * (Bh - Bl) + Bl;
+ intersect_polish_root(A, tA,
+ B, tB);
+ if(depth % 2)
+ ret.push_back(Crossing(tB, tA, c < 0));
+ else
+ ret.push_back(Crossing(tA, tB, c > 0));
+ return;
+ }
+ }
+ if(depth > 12) return;
+ double mid = (Bl + Bh)/2;
+ pair_intersect(B, Bl, mid,
+ A, Al, Ah,
+ ret, depth+1);
+ pair_intersect(B, mid, Bh,
+ A, Al, Ah,
+ ret, depth+1);
+}
+
+Crossings pair_intersect(Curve const & A, Interval const &Ad,
+ Curve const & B, Interval const &Bd) {
+ Crossings ret;
+ pair_intersect(A, Ad.min(), Ad.max(), B, Bd.min(), Bd.max(), ret);
+ return ret;
+}
+
+/** A simple wrapper around pair_intersect */
+Crossings SimpleCrosser::crossings(Curve const &a, Curve const &b) {
+ Crossings ret;
+ pair_intersect(a, 0, 1, b, 0, 1, ret);
+ return ret;
+}
+
+
+//same as below but curves not paths
+void mono_intersect(Curve const &A, double Al, double Ah,
+ Curve const &B, double Bl, double Bh,
+ Crossings &ret, double tol = 0.1, unsigned depth = 0) {
+ if( Al >= Ah || Bl >= Bh) return;
+ //std::cout << " " << depth << "[" << Al << ", " << Ah << "]" << "[" << Bl << ", " << Bh << "]";
+
+ Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah),
+ B0 = B.pointAt(Bl), B1 = B.pointAt(Bh);
+ //inline code that this implies? (without rect/interval construction)
+ Rect Ar = Rect(A0, A1), Br = Rect(B0, B1);
+ if(!Ar.intersects(Br) || A0 == A1 || B0 == B1) return;
+
+ if(depth > 12 || (Ar.maxExtent() < tol && Ar.maxExtent() < tol)) {
+ double tA, tB, c;
+ if(linear_intersect(A.pointAt(Al), A.pointAt(Ah),
+ B.pointAt(Bl), B.pointAt(Bh),
+ tA, tB, c)) {
+ tA = tA * (Ah - Al) + Al;
+ tB = tB * (Bh - Bl) + Bl;
+ intersect_polish_root(A, tA,
+ B, tB);
+ if(depth % 2)
+ ret.push_back(Crossing(tB, tA, c < 0));
+ else
+ ret.push_back(Crossing(tA, tB, c > 0));
+ return;
+ }
+ }
+ if(depth > 12) return;
+ double mid = (Bl + Bh)/2;
+ mono_intersect(B, Bl, mid,
+ A, Al, Ah,
+ ret, tol, depth+1);
+ mono_intersect(B, mid, Bh,
+ A, Al, Ah,
+ ret, tol, depth+1);
+}
+
+Crossings mono_intersect(Curve const & A, Interval const &Ad,
+ Curve const & B, Interval const &Bd) {
+ Crossings ret;
+ mono_intersect(A, Ad.min(), Ad.max(), B, Bd.min(), Bd.max(), ret);
+ return ret;
+}
+
+/**
+ * Takes two paths and time ranges on them, with the invariant that the
+ * paths are monotonic on the range. Splits A when the linear intersection
+ * doesn't exist or is inaccurate. Uses the fact that it is monotonic to
+ * do very fast local bounds.
+ */
+void mono_pair(Path const &A, double Al, double Ah,
+ Path const &B, double Bl, double Bh,
+ Crossings &ret, double /*tol*/, unsigned depth = 0) {
+ if( Al >= Ah || Bl >= Bh) return;
+ std::cout << " " << depth << "[" << Al << ", " << Ah << "]" << "[" << Bl << ", " << Bh << "]";
+
+ Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah),
+ B0 = B.pointAt(Bl), B1 = B.pointAt(Bh);
+ //inline code that this implies? (without rect/interval construction)
+ Rect Ar = Rect(A0, A1), Br = Rect(B0, B1);
+ if(!Ar.intersects(Br) || A0 == A1 || B0 == B1) return;
+
+ if(depth > 12 || (Ar.maxExtent() < 0.1 && Ar.maxExtent() < 0.1)) {
+ double tA, tB, c;
+ if(linear_intersect(A0, A1, B0, B1,
+ tA, tB, c)) {
+ tA = tA * (Ah - Al) + Al;
+ tB = tB * (Bh - Bl) + Bl;
+ if(depth % 2)
+ ret.push_back(Crossing(tB, tA, c < 0));
+ else
+ ret.push_back(Crossing(tA, tB, c > 0));
+ return;
+ }
+ }
+ if(depth > 12) return;
+ double mid = (Bl + Bh)/2;
+ mono_pair(B, Bl, mid,
+ A, Al, Ah,
+ ret, depth+1);
+ mono_pair(B, mid, Bh,
+ A, Al, Ah,
+ ret, depth+1);
+}
+
+/** This returns the times when the x or y derivative is 0 in the curve. */
+std::vector<double> curve_mono_splits(Curve const &d) {
+ Curve* deriv = d.derivative();
+ std::vector<double> rs = deriv->roots(0, X);
+ append(rs, deriv->roots(0, Y));
+ delete deriv;
+ std::sort(rs.begin(), rs.end());
+ return rs;
+}
+
+/** Convenience function to add a value to each entry in a vector of doubles. */
+std::vector<double> offset_doubles(std::vector<double> const &x, double offs) {
+ std::vector<double> ret;
+ for(double i : x) {
+ ret.push_back(i + offs);
+ }
+ return ret;
+}
+
+/**
+ * Finds all the monotonic splits for a path. Only includes the split between
+ * curves if they switch derivative directions at that point.
+ */
+std::vector<double> path_mono_splits(Path const &p) {
+ std::vector<double> ret;
+ if(p.empty()) return ret;
+
+ int pdx = 2, pdy = 2; // Previous derivative direction
+ for(unsigned i = 0; i < p.size(); i++) {
+ std::vector<double> spl = offset_doubles(curve_mono_splits(p[i]), i);
+ int dx = p[i].initialPoint()[X] > (spl.empty() ? p[i].finalPoint()[X] : p.valueAt(spl.front(), X)) ? 1 : 0;
+ int dy = p[i].initialPoint()[Y] > (spl.empty() ? p[i].finalPoint()[Y] : p.valueAt(spl.front(), Y)) ? 1 : 0;
+ //The direction changed, include the split time
+ if(dx != pdx || dy != pdy) {
+ ret.push_back(i);
+ pdx = dx; pdy = dy;
+ }
+ append(ret, spl);
+ }
+ return ret;
+}
+
+/**
+ * Applies path_mono_splits to multiple paths, and returns the results such that
+ * time-set i corresponds to Path i.
+ */
+std::vector<std::vector<double> > paths_mono_splits(PathVector const &ps) {
+ std::vector<std::vector<double> > ret;
+ for(const auto & p : ps)
+ ret.push_back(path_mono_splits(p));
+ return ret;
+}
+
+/**
+ * Processes the bounds for a list of paths and a list of splits on them, yielding a list of rects for each.
+ * Each entry i corresponds to path i of the input. The number of rects in each entry is guaranteed to be the
+ * number of splits for that path, subtracted by one.
+ */
+std::vector<std::vector<Rect> > split_bounds(PathVector const &p, std::vector<std::vector<double> > splits) {
+ std::vector<std::vector<Rect> > ret;
+ for(unsigned i = 0; i < p.size(); i++) {
+ std::vector<Rect> res;
+ for(unsigned j = 1; j < splits[i].size(); j++)
+ res.emplace_back(p[i].pointAt(splits[i][j-1]), p[i].pointAt(splits[i][j]));
+ ret.push_back(res);
+ }
+ return ret;
+}
+
+/**
+ * This is the main routine of "MonoCrosser", and implements a monotonic strategy on multiple curves.
+ * Finds crossings between two sets of paths, yielding a CrossingSet. [0, a.size()) of the return correspond
+ * to the sorted crossings of a with paths of b. The rest of the return, [a.size(), a.size() + b.size()],
+ * corresponds to the sorted crossings of b with paths of a.
+ *
+ * This function does two sweeps, one on the bounds of each path, and after that cull, one on the curves within.
+ * This leads to a certain amount of code complexity, however, most of that is factored into the above functions
+ */
+CrossingSet MonoCrosser::crossings(PathVector const &a, PathVector const &b) {
+ if(b.empty()) return CrossingSet(a.size(), Crossings());
+ CrossingSet results(a.size() + b.size(), Crossings());
+ if(a.empty()) return results;
+
+ std::vector<std::vector<double> > splits_a = paths_mono_splits(a), splits_b = paths_mono_splits(b);
+ std::vector<std::vector<Rect> > bounds_a = split_bounds(a, splits_a), bounds_b = split_bounds(b, splits_b);
+
+ std::vector<Rect> bounds_a_union, bounds_b_union;
+ for(auto & i : bounds_a) bounds_a_union.push_back(union_list(i));
+ for(auto & i : bounds_b) bounds_b_union.push_back(union_list(i));
+
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds_a_union, bounds_b_union);
+ Crossings n;
+ for(unsigned i = 0; i < cull.size(); i++) {
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+ unsigned jc = j + a.size();
+ Crossings res;
+
+ //Sweep of the monotonic portions
+ std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bounds_a[i], bounds_b[j]);
+ for(unsigned k = 0; k < cull2.size(); k++) {
+ for(unsigned lx = 0; lx < cull2[k].size(); lx++) {
+ unsigned l = cull2[k][lx];
+ mono_pair(a[i], splits_a[i][k-1], splits_a[i][k],
+ b[j], splits_b[j][l-1], splits_b[j][l],
+ res, .1);
+ }
+ }
+
+ for(auto & re : res) { re.a = i; re.b = jc; }
+
+ merge_crossings(results[i], res, i);
+ merge_crossings(results[i], res, jc);
+ }
+ }
+
+ return results;
+}
+
+/* This function is similar codewise to the MonoCrosser, the main difference is that it deals with
+ * only one set of paths and includes self intersection
+CrossingSet crossings_among(PathVector const &p) {
+ CrossingSet results(p.size(), Crossings());
+ if(p.empty()) return results;
+
+ std::vector<std::vector<double> > splits = paths_mono_splits(p);
+ std::vector<std::vector<Rect> > prs = split_bounds(p, splits);
+ std::vector<Rect> rs;
+ for(unsigned i = 0; i < prs.size(); i++) rs.push_back(union_list(prs[i]));
+
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(rs);
+
+ //we actually want to do the self-intersections, so add em in:
+ for(unsigned i = 0; i < cull.size(); i++) cull[i].push_back(i);
+
+ for(unsigned i = 0; i < cull.size(); i++) {
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+ Crossings res;
+
+ //Sweep of the monotonic portions
+ std::vector<std::vector<unsigned> > cull2 = sweep_bounds(prs[i], prs[j]);
+ for(unsigned k = 0; k < cull2.size(); k++) {
+ for(unsigned lx = 0; lx < cull2[k].size(); lx++) {
+ unsigned l = cull2[k][lx];
+ mono_pair(p[i], splits[i][k-1], splits[i][k],
+ p[j], splits[j][l-1], splits[j][l],
+ res, .1);
+ }
+ }
+
+ for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = j; }
+
+ merge_crossings(results[i], res, i);
+ merge_crossings(results[j], res, j);
+ }
+ }
+
+ return results;
+}
+*/
+
+
+Crossings curve_self_crossings(Curve const &a) {
+ Crossings res;
+ std::vector<double> spl;
+ spl.push_back(0);
+ append(spl, curve_mono_splits(a));
+ spl.push_back(1);
+ for(unsigned i = 1; i < spl.size(); i++)
+ for(unsigned j = i+1; j < spl.size(); j++)
+ pair_intersect(a, spl[i-1], spl[i], a, spl[j-1], spl[j], res);
+ return res;
+}
+
+/*
+void mono_curve_intersect(Curve const & A, double Al, double Ah,
+ Curve const & B, double Bl, double Bh,
+ Crossings &ret, unsigned depth=0) {
+ // std::cout << depth << "(" << Al << ", " << Ah << ")\n";
+ Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah),
+ B0 = B.pointAt(Bl), B1 = B.pointAt(Bh);
+ //inline code that this implies? (without rect/interval construction)
+ if(!Rect(A0, A1).intersects(Rect(B0, B1)) || A0 == A1 || B0 == B1) return;
+
+ //Checks the general linearity of the function
+ if((depth > 12) || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1
+ && B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) {
+ double tA, tB, c;
+ if(linear_intersect(A0, A1, B0, B1, tA, tB, c)) {
+ tA = tA * (Ah - Al) + Al;
+ tB = tB * (Bh - Bl) + Bl;
+ if(depth % 2)
+ ret.push_back(Crossing(tB, tA, c < 0));
+ else
+ ret.push_back(Crossing(tA, tB, c > 0));
+ return;
+ }
+ }
+ if(depth > 12) return;
+ double mid = (Bl + Bh)/2;
+ mono_curve_intersect(B, Bl, mid,
+ A, Al, Ah,
+ ret, depth+1);
+ mono_curve_intersect(B, mid, Bh,
+ A, Al, Ah,
+ ret, depth+1);
+}
+
+std::vector<std::vector<double> > curves_mono_splits(Path const &p) {
+ std::vector<std::vector<double> > ret;
+ for(unsigned i = 0; i <= p.size(); i++) {
+ std::vector<double> spl;
+ spl.push_back(0);
+ append(spl, curve_mono_splits(p[i]));
+ spl.push_back(1);
+ ret.push_back(spl);
+ }
+}
+
+std::vector<std::vector<Rect> > curves_split_bounds(Path const &p, std::vector<std::vector<double> > splits) {
+ std::vector<std::vector<Rect> > ret;
+ for(unsigned i = 0; i < splits.size(); i++) {
+ std::vector<Rect> res;
+ for(unsigned j = 1; j < splits[i].size(); j++)
+ res.push_back(Rect(p.pointAt(splits[i][j-1]+i), p.pointAt(splits[i][j]+i)));
+ ret.push_back(res);
+ }
+ return ret;
+}
+
+Crossings path_self_crossings(Path const &p) {
+ Crossings ret;
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p));
+ std::vector<std::vector<double> > spl = curves_mono_splits(p);
+ std::vector<std::vector<Rect> > bnds = curves_split_bounds(p, spl);
+ for(unsigned i = 0; i < cull.size(); i++) {
+ Crossings res;
+ for(unsigned k = 1; k < spl[i].size(); k++)
+ for(unsigned l = k+1; l < spl[i].size(); l++)
+ mono_curve_intersect(p[i], spl[i][k-1], spl[i][k], p[i], spl[i][l-1], spl[i][l], res);
+ offset_crossings(res, i, i);
+ append(ret, res);
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+ res.clear();
+
+ std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bnds[i], bnds[j]);
+ for(unsigned k = 0; k < cull2.size(); k++) {
+ for(unsigned lx = 0; lx < cull2[k].size(); lx++) {
+ unsigned l = cull2[k][lx];
+ mono_curve_intersect(p[i], spl[i][k-1], spl[i][k], p[j], spl[j][l-1], spl[j][l], res);
+ }
+ }
+
+ //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) {
+ Crossings res2;
+ for(unsigned k = 0; k < res.size(); k++) {
+ if(res[k].ta != 0 && res[k].ta != 1 && res[k].tb != 0 && res[k].tb != 1) {
+ res.push_back(res[k]);
+ }
+ }
+ res = res2;
+ //}
+ offset_crossings(res, i, j);
+ append(ret, res);
+ }
+ }
+ return ret;
+}
+*/
+
+Crossings self_crossings(Path const &p) {
+ Crossings ret;
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p));
+ for(unsigned i = 0; i < cull.size(); i++) {
+ Crossings res = curve_self_crossings(p[i]);
+ offset_crossings(res, i, i);
+ append(ret, res);
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+ res.clear();
+ pair_intersect(p[i], 0, 1, p[j], 0, 1, res);
+
+ //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) {
+ Crossings res2;
+ for(auto & re : res) {
+ if(re.ta != 0 && re.ta != 1 && re.tb != 0 && re.tb != 1) {
+ res2.push_back(re);
+ }
+ }
+ res = res2;
+ //}
+ offset_crossings(res, i, j);
+ append(ret, res);
+ }
+ }
+ return ret;
+}
+
+void flip_crossings(Crossings &crs) {
+ for(auto & cr : crs)
+ cr = Crossing(cr.tb, cr.ta, cr.b, cr.a, !cr.dir);
+}
+
+CrossingSet crossings_among(PathVector const &p) {
+ CrossingSet results(p.size(), Crossings());
+ if(p.empty()) return results;
+
+ SimpleCrosser cc;
+
+ std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p));
+ for(unsigned i = 0; i < cull.size(); i++) {
+ Crossings res = self_crossings(p[i]);
+ for(auto & re : res) { re.a = re.b = i; }
+ merge_crossings(results[i], res, i);
+ flip_crossings(res);
+ merge_crossings(results[i], res, i);
+ for(unsigned jx = 0; jx < cull[i].size(); jx++) {
+ unsigned j = cull[i][jx];
+
+ Crossings res = cc.crossings(p[i], p[j]);
+ for(auto & re : res) { re.a = i; re.b = j; }
+ merge_crossings(results[i], res, i);
+ merge_crossings(results[j], res, j);
+ }
+ }
+ return results;
+}
+
+}
+
+/*
+ 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/src/2geom/path-sink.cpp b/src/2geom/path-sink.cpp
new file mode 100644
index 0000000..1a22c81
--- /dev/null
+++ b/src/2geom/path-sink.cpp
@@ -0,0 +1,104 @@
+/*
+ * callback interface for SVG path data
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * 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, output 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/sbasis-to-bezier.h>
+#include <2geom/path-sink.h>
+#include <2geom/exception.h>
+#include <2geom/circle.h>
+#include <2geom/ellipse.h>
+
+namespace Geom {
+
+void PathSink::feed(Curve const &c, bool moveto_initial)
+{
+ c.feed(*this, moveto_initial);
+}
+
+void PathSink::feed(Path const &path) {
+ flush();
+ moveTo(path.front().initialPoint());
+
+ // never output the closing segment to the sink
+ Path::const_iterator iter = path.begin(), last = path.end_open();
+ for (; iter != last; ++iter) {
+ iter->feed(*this, false);
+ }
+ if (path.closed()) {
+ closePath();
+ }
+ flush();
+}
+
+void PathSink::feed(PathVector const &pv) {
+ for (const auto & i : pv) {
+ feed(i);
+ }
+}
+
+void PathSink::feed(Rect const &r) {
+ moveTo(r.corner(0));
+ lineTo(r.corner(1));
+ lineTo(r.corner(2));
+ lineTo(r.corner(3));
+ closePath();
+}
+
+void PathSink::feed(Circle const &e) {
+ Coord r = e.radius();
+ Point c = e.center();
+ Point a = c + Point(0, +r);
+ Point b = c + Point(0, -r);
+
+ moveTo(a);
+ arcTo(r, r, 0, false, false, b);
+ arcTo(r, r, 0, false, false, a);
+ closePath();
+}
+
+void PathSink::feed(Ellipse const &e) {
+ Point s = e.pointAt(0);
+ moveTo(s);
+ arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, e.pointAt(M_PI));
+ arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, s);
+ closePath();
+}
+
+}
+
+/*
+ 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/src/2geom/path.cpp b/src/2geom/path.cpp
new file mode 100644
index 0000000..aeff503
--- /dev/null
+++ b/src/2geom/path.cpp
@@ -0,0 +1,1161 @@
+/** @file
+ * @brief Path - a sequence of contiguous curves (implementation file)
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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 <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/transforms.h>
+#include <2geom/circle.h>
+#include <2geom/ellipse.h>
+#include <2geom/convex-hull.h>
+#include <2geom/svg-path-writer.h>
+#include <2geom/sweeper.h>
+#include <algorithm>
+#include <limits>
+#include <optional>
+
+using std::swap;
+using namespace Geom::PathInternal;
+
+namespace Geom {
+
+// this represents an empty interval
+PathInterval::PathInterval()
+ : _from(0, 0.0)
+ , _to(0, 0.0)
+ , _path_size(1)
+ , _cross_start(false)
+ , _reverse(false)
+{}
+
+PathInterval::PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size)
+ : _from(from)
+ , _to(to)
+ , _path_size(path_size)
+ , _cross_start(cross_start)
+ , _reverse((to < from) ^ cross_start)
+{
+ if (_reverse) {
+ _to.normalizeForward(_path_size);
+ if (cross_start && _to < to) {
+ // Normalization made us cross start (closed path),
+ // so we don't need to cross the start anymore.
+ _cross_start = false;
+ }
+ if (_from != _to) {
+ _from.normalizeBackward(_path_size);
+ if (cross_start && _from > from) {
+ // Normalization backwards made us logically cross
+ // the start – we shouldn't cross the start again.
+ _cross_start = false;
+ }
+ }
+ } else {
+ _from.normalizeForward(_path_size);
+ if (cross_start && _from < from) {
+ _cross_start = false;
+ }
+ if (_from != _to) {
+ _to.normalizeBackward(_path_size);
+ if (cross_start && _to > to) {
+ _cross_start = false;
+ }
+ }
+ }
+
+ if (_from == _to) {
+ _reverse = false;
+ _cross_start = false;
+ }
+}
+
+bool PathInterval::contains(PathTime const &pos) const {
+ if (_cross_start) {
+ if (_reverse) {
+ return pos >= _to || _from >= pos;
+ } else {
+ return pos >= _from || _to >= pos;
+ }
+ } else {
+ if (_reverse) {
+ return _to <= pos && pos <= _from;
+ } else {
+ return _from <= pos && pos <= _to;
+ }
+ }
+}
+
+PathInterval::size_type PathInterval::curveCount() const
+{
+ if (isDegenerate()) return 0;
+ if (_cross_start) {
+ if (_reverse) {
+ return _path_size - _to.curve_index + _from.curve_index + 1;
+ } else {
+ return _path_size - _from.curve_index + _to.curve_index + 1;
+ }
+ } else {
+ if (_reverse) {
+ return _from.curve_index - _to.curve_index + 1;
+ } else {
+ return _to.curve_index - _from.curve_index + 1;
+ }
+ }
+}
+
+PathTime PathInterval::inside(Coord min_dist) const
+{
+ // If there is some node further than min_dist (in time coord) from the ends,
+ // return that node. Otherwise, return the middle.
+ PathTime result(0, 0.0);
+
+ if (!_cross_start && _from.curve_index == _to.curve_index) {
+ PathTime result(_from.curve_index, lerp(0.5, _from.t, _to.t));
+ return result;
+ }
+ // If _cross_start, then we can be sure that at least one node is in the domain.
+ // If dcurve == 0, it actually means that all curves are included in the domain
+
+ if (_reverse) {
+ size_type dcurve = (_path_size + _from.curve_index - _to.curve_index) % _path_size;
+ bool from_close = _from.t < min_dist;
+ bool to_close = _to.t > 1 - min_dist;
+
+ if (dcurve == 0) {
+ dcurve = _path_size;
+ }
+
+ if (dcurve == 1) {
+ if (from_close || to_close) {
+ result.curve_index = _from.curve_index;
+ Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5;
+ if (tmid < 0) {
+ result.curve_index = (_path_size + result.curve_index - 1) % _path_size;
+ tmid += 1;
+ }
+ result.t = tmid;
+ return result;
+ }
+
+ result.curve_index = _from.curve_index;
+ return result;
+ }
+
+ result.curve_index = (_to.curve_index + 1) % _path_size;
+ if (to_close) {
+ if (dcurve == 2) {
+ result.t = 0.5;
+ } else {
+ result.curve_index = (result.curve_index + 1) % _path_size;
+ }
+ }
+ return result;
+ } else {
+ size_type dcurve = (_path_size + _to.curve_index - _from.curve_index) % _path_size;
+ bool from_close = _from.t > 1 - min_dist;
+ bool to_close = _to.t < min_dist;
+
+ if (dcurve == 0) {
+ dcurve = _path_size;
+ }
+
+ if (dcurve == 1) {
+ if (from_close || to_close) {
+ result.curve_index = _from.curve_index;
+ Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t;
+ if (tmid >= 1) {
+ result.curve_index = (result.curve_index + 1) % _path_size;
+ tmid -= 1;
+ }
+ result.t = tmid;
+ return result;
+ }
+
+ result.curve_index = _to.curve_index;
+ return result;
+ }
+
+ result.curve_index = (_from.curve_index + 1) % _path_size;
+ if (from_close) {
+ if (dcurve == 2) {
+ result.t = 0.5;
+ } else {
+ result.curve_index = (result.curve_index + 1) % _path_size;
+ }
+ }
+ return result;
+ }
+
+ result.curve_index = _reverse ? _from.curve_index : _to.curve_index;
+ return result;
+}
+
+PathInterval PathInterval::from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size)
+{
+ PathInterval result;
+ result._from = from;
+ result._to = to;
+ result._path_size = path_size;
+
+ if (reversed) {
+ result._to.normalizeForward(path_size);
+ if (result._from != result._to) {
+ result._from.normalizeBackward(path_size);
+ }
+ } else {
+ result._from.normalizeForward(path_size);
+ if (result._from != result._to) {
+ result._to.normalizeBackward(path_size);
+ }
+ }
+
+ if (result._from == result._to) {
+ result._reverse = false;
+ result._cross_start = false;
+ } else {
+ result._reverse = reversed;
+ if (reversed) {
+ result._cross_start = from < to;
+ } else {
+ result._cross_start = to < from;
+ }
+ }
+ return result;
+}
+
+
+Path::Path(Rect const &r)
+ : _data(new PathData())
+ , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0)))
+ , _closed(true)
+ , _exception_on_stitch(true)
+{
+ for (unsigned i = 0; i < 3; ++i) {
+ _data->curves.push_back(new LineSegment(r.corner(i), r.corner(i+1)));
+ }
+ _data->curves.push_back(_closing_seg);
+}
+
+Path::Path(ConvexHull const &ch)
+ : _data(new PathData())
+ , _closing_seg(new ClosingSegment(Point(), Point()))
+ , _closed(true)
+ , _exception_on_stitch(true)
+{
+ if (ch.empty()) {
+ _data->curves.push_back(_closing_seg);
+ return;
+ }
+
+ _closing_seg->setInitial(ch.back());
+ _closing_seg->setFinal(ch.front());
+
+ Point last = ch.front();
+
+ for (std::size_t i = 1; i < ch.size(); ++i) {
+ _data->curves.push_back(new LineSegment(last, ch[i]));
+ last = ch[i];
+ }
+
+ _data->curves.push_back(_closing_seg);
+ _closed = true;
+}
+
+Path::Path(Circle const &c)
+ : _data(new PathData())
+ , _closing_seg(NULL)
+ , _closed(true)
+ , _exception_on_stitch(true)
+{
+ Point p1 = c.pointAt(0);
+ Point p2 = c.pointAt(M_PI);
+ _data->curves.push_back(new EllipticalArc(p1, c.radius(), c.radius(), 0, false, true, p2));
+ _data->curves.push_back(new EllipticalArc(p2, c.radius(), c.radius(), 0, false, true, p1));
+ _closing_seg = new ClosingSegment(p1, p1);
+ _data->curves.push_back(_closing_seg);
+}
+
+Path::Path(Ellipse const &e)
+ : _data(new PathData())
+ , _closing_seg(NULL)
+ , _closed(true)
+ , _exception_on_stitch(true)
+{
+ Point p1 = e.pointAt(0);
+ Point p2 = e.pointAt(M_PI);
+ _data->curves.push_back(new EllipticalArc(p1, e.rays(), e.rotationAngle(), false, true, p2));
+ _data->curves.push_back(new EllipticalArc(p2, e.rays(), e.rotationAngle(), false, true, p1));
+ _closing_seg = new ClosingSegment(p1, p1);
+ _data->curves.push_back(_closing_seg);
+}
+
+void Path::close(bool c)
+{
+ if (c == _closed) return;
+ if (c && _data->curves.size() >= 2) {
+ // when closing, if last segment is linear and ends at initial point,
+ // replace it with the closing segment
+ Sequence::iterator last = _data->curves.end() - 2;
+ if (last->isLineSegment() && last->finalPoint() == initialPoint()) {
+ _closing_seg->setInitial(last->initialPoint());
+ _data->curves.erase(last);
+ }
+ }
+ _closed = c;
+}
+
+void Path::clear()
+{
+ _unshare();
+ _data->curves.pop_back().release();
+ _data->curves.clear();
+ _closing_seg->setInitial(Point(0, 0));
+ _closing_seg->setFinal(Point(0, 0));
+ _data->curves.push_back(_closing_seg);
+ _closed = false;
+}
+
+OptRect Path::boundsFast() const
+{
+ OptRect bounds;
+ if (empty()) {
+ return bounds;
+ }
+ // if the path is not empty, we look for cached bounds
+ if (_data->fast_bounds) {
+ return _data->fast_bounds;
+ }
+
+ bounds = front().boundsFast();
+ const_iterator iter = begin();
+ // the closing path segment can be ignored, because it will always
+ // lie within the bbox of the rest of the path
+ if (iter != end()) {
+ for (++iter; iter != end(); ++iter) {
+ bounds.unionWith(iter->boundsFast());
+ }
+ }
+ _data->fast_bounds = bounds;
+ return bounds;
+}
+
+OptRect Path::boundsExact() const
+{
+ OptRect bounds;
+ if (empty())
+ return bounds;
+ bounds = front().boundsExact();
+ const_iterator iter = begin();
+ // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path
+ if (iter != end()) {
+ for (++iter; iter != end(); ++iter) {
+ bounds.unionWith(iter->boundsExact());
+ }
+ }
+ return bounds;
+}
+
+Piecewise<D2<SBasis> > Path::toPwSb() const
+{
+ Piecewise<D2<SBasis> > ret;
+ ret.push_cut(0);
+ unsigned i = 1;
+ bool degenerate = true;
+ // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2.
+ for (const_iterator it = begin(); it != end_default(); ++it) {
+ if (!it->isDegenerate()) {
+ ret.push(it->toSBasis(), i++);
+ degenerate = false;
+ }
+ }
+ if (degenerate) {
+ // if path only contains degenerate curves, no second cut is added
+ // so we need to create at least one segment manually
+ ret = Piecewise<D2<SBasis> >(initialPoint());
+ }
+ return ret;
+}
+
+template <typename iter>
+iter inc(iter const &x, unsigned n) {
+ iter ret = x;
+ for (unsigned i = 0; i < n; i++)
+ ret++;
+ return ret;
+}
+
+bool Path::operator==(Path const &other) const
+{
+ if (this == &other)
+ return true;
+ if (_closed != other._closed)
+ return false;
+ return _data->curves == other._data->curves;
+}
+
+void Path::start(Point const &p) {
+ if (_data->curves.size() > 1) {
+ clear();
+ }
+ _closing_seg->setInitial(p);
+ _closing_seg->setFinal(p);
+}
+
+Interval Path::timeRange() const
+{
+ Interval ret(0, size_default());
+ return ret;
+}
+
+Curve const &Path::curveAt(Coord t, Coord *rest) const
+{
+ PathTime pos = _factorTime(t);
+ if (rest) {
+ *rest = pos.t;
+ }
+ return at(pos.curve_index);
+}
+
+Point Path::pointAt(Coord t) const
+{
+ return pointAt(_factorTime(t));
+}
+
+Coord Path::valueAt(Coord t, Dim2 d) const
+{
+ return valueAt(_factorTime(t), d);
+}
+
+Curve const &Path::curveAt(PathTime const &pos) const
+{
+ return at(pos.curve_index);
+}
+Point Path::pointAt(PathTime const &pos) const
+{
+ return at(pos.curve_index).pointAt(pos.t);
+}
+Coord Path::valueAt(PathTime const &pos, Dim2 d) const
+{
+ return at(pos.curve_index).valueAt(pos.t, d);
+}
+
+std::vector<PathTime> Path::roots(Coord v, Dim2 d) const
+{
+ std::vector<PathTime> res;
+ for (unsigned i = 0; i < size(); i++) {
+ std::vector<Coord> temp = (*this)[i].roots(v, d);
+ for (double j : temp)
+ res.emplace_back(i, j);
+ }
+ return res;
+}
+
+
+// The class below implements sweepline optimization for curve intersection in paths.
+// Instead of O(N^2), this takes O(N + X), where X is the number of overlaps
+// between the bounding boxes of curves.
+
+struct CurveIntersectionSweepSet
+{
+public:
+ struct CurveRecord {
+ boost::intrusive::list_member_hook<> _hook;
+ Curve const *curve;
+ Rect bounds;
+ std::size_t index;
+ unsigned which;
+
+ CurveRecord(Curve const *pc, std::size_t idx, unsigned w)
+ : curve(pc)
+ , bounds(curve->boundsFast())
+ , index(idx)
+ , which(w)
+ {}
+ };
+
+ typedef std::vector<CurveRecord>::const_iterator ItemIterator;
+
+ CurveIntersectionSweepSet(std::vector<PathIntersection> &result,
+ Path const &a, Path const &b, Coord precision)
+ : _result(result)
+ , _precision(precision)
+ , _sweep_dir(X)
+ {
+ std::size_t asz = a.size(), bsz = b.size();
+ _records.reserve(asz + bsz);
+
+ for (std::size_t i = 0; i < asz; ++i) {
+ _records.emplace_back(&a[i], i, 0);
+ }
+ for (std::size_t i = 0; i < bsz; ++i) {
+ _records.emplace_back(&b[i], i, 1);
+ }
+
+ OptRect abb = a.boundsFast() | b.boundsFast();
+ if (abb && abb->height() > abb->width()) {
+ _sweep_dir = Y;
+ }
+ }
+
+ std::vector<CurveRecord> const &items() { return _records; }
+ Interval itemBounds(ItemIterator ii) {
+ return ii->bounds[_sweep_dir];
+ }
+
+ void addActiveItem(ItemIterator ii) {
+ unsigned w = ii->which;
+ unsigned ow = (w+1) % 2;
+
+ _active[w].push_back(const_cast<CurveRecord&>(*ii));
+
+ for (auto & i : _active[ow]) {
+ if (!ii->bounds.intersects(i.bounds)) continue;
+ std::vector<CurveIntersection> cx = ii->curve->intersect(*i.curve, _precision);
+ for (auto & k : cx) {
+ PathTime tw(ii->index, k.first), tow(i.index, k.second);
+ _result.emplace_back(
+ w == 0 ? tw : tow,
+ w == 0 ? tow : tw,
+ k.point());
+ }
+ }
+ }
+ void removeActiveItem(ItemIterator ii) {
+ ActiveCurveList &acl = _active[ii->which];
+ acl.erase(acl.iterator_to(*ii));
+ }
+
+private:
+ typedef boost::intrusive::list
+ < CurveRecord
+ , boost::intrusive::member_hook
+ < CurveRecord
+ , boost::intrusive::list_member_hook<>
+ , &CurveRecord::_hook
+ >
+ > ActiveCurveList;
+
+ std::vector<CurveRecord> _records;
+ std::vector<PathIntersection> &_result;
+ ActiveCurveList _active[2];
+ Coord _precision;
+ Dim2 _sweep_dir;
+};
+
+std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const
+{
+ std::vector<PathIntersection> result;
+
+ CurveIntersectionSweepSet cisset(result, *this, other, precision);
+ Sweeper<CurveIntersectionSweepSet> sweeper(cisset);
+ sweeper.process();
+
+ // preprocessing to remove duplicate intersections at endpoints
+ std::size_t asz = size(), bsz = other.size();
+ for (auto & i : result) {
+ i.first.normalizeForward(asz);
+ i.second.normalizeForward(bsz);
+ }
+ std::sort(result.begin(), result.end());
+ result.erase(std::unique(result.begin(), result.end()), result.end());
+
+ return result;
+}
+
+int Path::winding(Point const &p) const {
+ int wind = 0;
+
+ /* To handle all the edge cases, we consider the maximum Y edge of the bounding box
+ * as not included in box. This way paths that contain linear horizontal
+ * segments will be treated correctly. */
+ for (const_iterator i = begin(); i != end_closed(); ++i) {
+ Rect bounds = i->boundsFast();
+
+ if (bounds.height() == 0) continue;
+ if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) {
+ // Ray doesn't intersect bbox, so we ignore this segment
+ continue;
+ }
+
+ if (p[X] < bounds.left()) {
+ /* Ray intersects the curve's bbox, but the point is outside it.
+ * The winding contribution is exactly the same as that
+ * of a linear segment with the same initial and final points. */
+ Point ip = i->initialPoint();
+ Point fp = i->finalPoint();
+ Rect eqbox(ip, fp);
+
+ if (eqbox[Y].lowerContains(p[Y])) {
+ /* The ray intersects the equivalent linear segment.
+ * Determine winding contribution based on its derivative. */
+ if (ip[Y] < fp[Y]) {
+ wind += 1;
+ } else if (ip[Y] > fp[Y]) {
+ wind -= 1;
+ } else {
+ // should never happen, because bounds.height() was not zero
+ assert(false);
+ }
+ }
+ } else {
+ // point is inside bbox
+ wind += i->winding(p);
+ }
+ }
+ return wind;
+}
+
+std::vector<double> Path::allNearestTimes(Point const &_point, double from, double to) const
+{
+ // TODO from and to are not used anywhere.
+ // rewrite this to simplify.
+ using std::swap;
+
+ if (from > to)
+ swap(from, to);
+ const Path &_path = *this;
+ unsigned int sz = _path.size();
+ if (_path.closed())
+ ++sz;
+ if (from < 0 || to > sz) {
+ THROW_RANGEERROR("[from,to] interval out of bounds");
+ }
+ double sif, st = modf(from, &sif);
+ double eif, et = modf(to, &eif);
+ unsigned int si = static_cast<unsigned int>(sif);
+ unsigned int ei = static_cast<unsigned int>(eif);
+ if (si == sz) {
+ --si;
+ st = 1;
+ }
+ if (ei == sz) {
+ --ei;
+ et = 1;
+ }
+ if (si == ei) {
+ std::vector<double> all_nearest = _path[si].allNearestTimes(_point, st, et);
+ for (double & i : all_nearest) {
+ i = si + i;
+ }
+ return all_nearest;
+ }
+ std::vector<double> all_t;
+ std::vector<std::vector<double> > all_np;
+ all_np.push_back(_path[si].allNearestTimes(_point, st));
+ std::vector<unsigned int> ni;
+ ni.push_back(si);
+ double dsq;
+ double mindistsq = distanceSq(_point, _path[si].pointAt(all_np.front().front()));
+ Rect bb(Geom::Point(0, 0), Geom::Point(0, 0));
+ for (unsigned int i = si + 1; i < ei; ++i) {
+ bb = (_path[i].boundsFast());
+ dsq = distanceSq(_point, bb);
+ if (mindistsq < dsq)
+ continue;
+ all_t = _path[i].allNearestTimes(_point);
+ dsq = distanceSq(_point, _path[i].pointAt(all_t.front()));
+ if (mindistsq > dsq) {
+ all_np.clear();
+ all_np.push_back(all_t);
+ ni.clear();
+ ni.push_back(i);
+ mindistsq = dsq;
+ } else if (mindistsq == dsq) {
+ all_np.push_back(all_t);
+ ni.push_back(i);
+ }
+ }
+ bb = (_path[ei].boundsFast());
+ dsq = distanceSq(_point, bb);
+ if (mindistsq >= dsq) {
+ all_t = _path[ei].allNearestTimes(_point, 0, et);
+ dsq = distanceSq(_point, _path[ei].pointAt(all_t.front()));
+ if (mindistsq > dsq) {
+ for (double & i : all_t) {
+ i = ei + i;
+ }
+ return all_t;
+ } else if (mindistsq == dsq) {
+ all_np.push_back(all_t);
+ ni.push_back(ei);
+ }
+ }
+ std::vector<double> all_nearest;
+ for (unsigned int i = 0; i < all_np.size(); ++i) {
+ for (unsigned int j = 0; j < all_np[i].size(); ++j) {
+ all_nearest.push_back(ni[i] + all_np[i][j]);
+ }
+ }
+ all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), all_nearest.end());
+ return all_nearest;
+}
+
+std::vector<Coord> Path::nearestTimePerCurve(Point const &p) const
+{
+ // return a single nearest time for each curve in this path
+ std::vector<Coord> np;
+ for (const_iterator it = begin(); it != end_default(); ++it) {
+ np.push_back(it->nearestTime(p));
+ }
+ return np;
+}
+
+PathTime Path::nearestTime(Point const &p, Coord *dist) const
+{
+ Coord mindist = std::numeric_limits<Coord>::max();
+ PathTime ret;
+
+ if (_data->curves.size() == 1) {
+ // naked moveto
+ ret.curve_index = 0;
+ ret.t = 0;
+ if (dist) {
+ *dist = distance(_closing_seg->initialPoint(), p);
+ }
+ return ret;
+ }
+
+ for (size_type i = 0; i < size_default(); ++i) {
+ Curve const &c = at(i);
+ if (distance(p, c.boundsFast()) >= mindist) continue;
+
+ Coord t = c.nearestTime(p);
+ Coord d = distance(c.pointAt(t), p);
+ if (d < mindist) {
+ mindist = d;
+ ret.curve_index = i;
+ ret.t = t;
+ }
+ }
+ if (dist) {
+ *dist = mindist;
+ }
+
+ return ret;
+}
+
+std::vector<Point> Path::nodes() const
+{
+ std::vector<Point> result;
+ size_type path_size = size_closed();
+ for (size_type i = 0; i < path_size; ++i) {
+ result.push_back(_data->curves[i].initialPoint());
+ }
+ return result;
+}
+
+void Path::appendPortionTo(Path &ret, double from, double to) const
+{
+ if (!(from >= 0 && to >= 0)) {
+ THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo");
+ }
+ if (to == 0)
+ to = size() + 0.999999;
+ if (from == to) {
+ return;
+ }
+ double fi, ti;
+ double ff = modf(from, &fi), tf = modf(to, &ti);
+ if (tf == 0) {
+ ti--;
+ tf = 1;
+ }
+ const_iterator fromi = inc(begin(), (unsigned)fi);
+ if (fi == ti && from < to) {
+ ret.append(fromi->portion(ff, tf));
+ return;
+ }
+ const_iterator toi = inc(begin(), (unsigned)ti);
+ if (ff != 1.) {
+ // fromv->setInitial(ret.finalPoint());
+ ret.append(fromi->portion(ff, 1.));
+ }
+ if (from >= to) {
+ const_iterator ender = end();
+ if (ender->initialPoint() == ender->finalPoint())
+ ++ender;
+ ret.insert(ret.end(), ++fromi, ender);
+ ret.insert(ret.end(), begin(), toi);
+ } else {
+ ret.insert(ret.end(), ++fromi, toi);
+ }
+ ret.append(toi->portion(0., tf));
+}
+
+void Path::appendPortionTo(Path &target, PathInterval const &ival,
+ std::optional<Point> const &p_from, std::optional<Point> const &p_to) const
+{
+ assert(ival.pathSize() == size_closed());
+
+ if (ival.isDegenerate()) {
+ Point stitch_to = p_from ? *p_from : pointAt(ival.from());
+ target.stitchTo(stitch_to);
+ return;
+ }
+
+ PathTime const &from = ival.from(), &to = ival.to();
+
+ bool reverse = ival.reverse();
+ int di = reverse ? -1 : 1;
+ size_type s = size_closed();
+
+ if (!ival.crossesStart() && from.curve_index == to.curve_index) {
+ Curve *c = (*this)[from.curve_index].portion(from.t, to.t);
+ if (p_from) {
+ c->setInitial(*p_from);
+ }
+ if (p_to) {
+ c->setFinal(*p_to);
+ }
+ target.append(c);
+ } else {
+ Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1);
+ if (p_from) {
+ c_first->setInitial(*p_from);
+ }
+ target.append(c_first);
+
+ for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index;
+ i = (i + s + di) % s)
+ {
+ if (reverse) {
+ target.append((*this)[i].reverse());
+ } else {
+ target.append((*this)[i].duplicate());
+ }
+ }
+
+ Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t);
+ if (p_to) {
+ c_last->setFinal(*p_to);
+ }
+ target.append(c_last);
+ }
+}
+
+Path Path::reversed() const
+{
+ typedef std::reverse_iterator<Sequence::const_iterator> RIter;
+
+ Path ret(finalPoint());
+ if (empty()) return ret;
+
+ ret._data->curves.pop_back(); // this also deletes the closing segment from ret
+
+ RIter iter(_includesClosingSegment() ? _data->curves.end() : _data->curves.end() - 1);
+ RIter rend(_data->curves.begin());
+
+ if (_closed) {
+ // when the path is closed, there are two cases:
+ if (front().isLineSegment()) {
+ // 1. initial segment is linear: it becomes the new closing segment.
+ rend = RIter(_data->curves.begin() + 1);
+ ret._closing_seg = new ClosingSegment(front().finalPoint(), front().initialPoint());
+ } else {
+ // 2. initial segment is not linear: the closing segment becomes degenerate.
+ // However, skip it if it's already degenerate.
+ Point fp = finalPoint();
+ ret._closing_seg = new ClosingSegment(fp, fp);
+ }
+ } else {
+ // when the path is open, we reverse all real curves, and add a reversed closing segment.
+ ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse());
+ }
+
+ for (; iter != rend; ++iter) {
+ ret._data->curves.push_back(iter->reverse());
+ }
+ ret._data->curves.push_back(ret._closing_seg);
+ ret._closed = _closed;
+ return ret;
+}
+
+
+void Path::insert(iterator pos, Curve const &curve)
+{
+ _unshare();
+ Sequence::iterator seq_pos(seq_iter(pos));
+ Sequence source;
+ source.push_back(curve.duplicate());
+ do_update(seq_pos, seq_pos, source);
+}
+
+void Path::erase(iterator pos)
+{
+ _unshare();
+ Sequence::iterator seq_pos(seq_iter(pos));
+
+ Sequence stitched;
+ do_update(seq_pos, seq_pos + 1, stitched);
+}
+
+void Path::erase(iterator first, iterator last)
+{
+ _unshare();
+ Sequence::iterator seq_first = seq_iter(first);
+ Sequence::iterator seq_last = seq_iter(last);
+
+ Sequence stitched;
+ do_update(seq_first, seq_last, stitched);
+}
+
+void Path::stitchTo(Point const &p)
+{
+ if (!empty() && _closing_seg->initialPoint() != p) {
+ if (_exception_on_stitch) {
+ THROW_CONTINUITYERROR();
+ }
+ _unshare();
+ do_append(new StitchSegment(_closing_seg->initialPoint(), p));
+ }
+}
+
+void Path::replace(iterator replaced, Curve const &curve)
+{
+ replace(replaced, replaced + 1, curve);
+}
+
+void Path::replace(iterator first_replaced, iterator last_replaced, Curve const &curve)
+{
+ _unshare();
+ Sequence::iterator seq_first_replaced(seq_iter(first_replaced));
+ Sequence::iterator seq_last_replaced(seq_iter(last_replaced));
+ Sequence source(1);
+ source.push_back(curve.duplicate());
+
+ do_update(seq_first_replaced, seq_last_replaced, source);
+}
+
+void Path::replace(iterator replaced, Path const &path)
+{
+ replace(replaced, path.begin(), path.end());
+}
+
+void Path::replace(iterator first, iterator last, Path const &path)
+{
+ replace(first, last, path.begin(), path.end());
+}
+
+void Path::snapEnds(Coord precision)
+{
+ if (!_closed) return;
+ if (_data->curves.size() > 1 && are_near(_closing_seg->length(precision), 0, precision)) {
+ _unshare();
+ _closing_seg->setInitial(_closing_seg->finalPoint());
+ (_data->curves.end() - 1)->setFinal(_closing_seg->finalPoint());
+ }
+}
+
+Path Path::withoutDegenerateCurves() const
+{
+ Sequence cleaned;
+ cleaned.reserve(size());
+
+ for (auto it = begin(); it != end_open(); ++it) {
+ if (!it->isDegenerate()) {
+ cleaned.push_back(it->duplicate());
+ }
+ }
+
+ Path result;
+ result._closed = _closed;
+ result.do_update(result._data->curves.begin(), result._data->curves.end(), cleaned);
+ return result;
+}
+
+// Replace curves between first and last with the contents of source.
+void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source)
+{
+ // TODO: handle cases where first > last in closed paths?
+ bool last_beyond_closing_segment = (last == _data->curves.end());
+
+ // special case:
+ // if do_update replaces the closing segment, we have to regenerate it
+ if (source.empty()) {
+ if (first == last) return; // nothing to do
+
+ // only removing some segments
+ if ((!_closed && first == _data->curves.begin()) || (!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
+ // just adjust the closing segment
+ // do nothing
+ } else if (first->initialPoint() != (last - 1)->finalPoint()) {
+ if (_exception_on_stitch) {
+ THROW_CONTINUITYERROR();
+ }
+ source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint()));
+ }
+ } else {
+ // replacing
+ if (first == _data->curves.begin() && last == _data->curves.end()) {
+ // special case: replacing everything should work the same in open and closed curves
+ _data->curves.erase(_data->curves.begin(), _data->curves.end() - 1);
+ _closing_seg->setFinal(source.front().initialPoint());
+ _closing_seg->setInitial(source.back().finalPoint());
+ _data->curves.transfer(_data->curves.begin(), source.begin(), source.end(), source);
+ return;
+ }
+
+ // stitch in front
+ if (!_closed && first == _data->curves.begin()) {
+ // not necessary to stitch in front
+ } else if (first->initialPoint() != source.front().initialPoint()) {
+ if (_exception_on_stitch) {
+ THROW_CONTINUITYERROR();
+ }
+ source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint()));
+ }
+
+ // stitch at the end
+ if ((!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
+ // repurpose the closing segment as the stitch segment
+ // do nothing
+ } else if (source.back().finalPoint() != (last - 1)->finalPoint()) {
+ if (_exception_on_stitch) {
+ THROW_CONTINUITYERROR();
+ }
+ source.push_back(new StitchSegment(source.back().finalPoint(), (last - 1)->finalPoint()));
+ }
+ }
+
+ // do not erase the closing segment, adjust it instead
+ if (last_beyond_closing_segment) {
+ --last;
+ }
+ _data->curves.erase(first, last);
+ _data->curves.transfer(first, source.begin(), source.end(), source);
+
+ // adjust closing segment
+ if (size_open() == 0) {
+ _closing_seg->setFinal(_closing_seg->initialPoint());
+ } else {
+ _closing_seg->setInitial(back_open().finalPoint());
+ _closing_seg->setFinal(front().initialPoint());
+ }
+
+ checkContinuity();
+}
+
+void Path::do_append(Curve *c)
+{
+ if (&_data->curves.front() == _closing_seg) {
+ _closing_seg->setFinal(c->initialPoint());
+ } else {
+ // if we can't freely move the closing segment, we check whether
+ // the new curve connects with the last non-closing curve
+ if (c->initialPoint() != _closing_seg->initialPoint()) {
+ THROW_CONTINUITYERROR();
+ }
+ if (_closed && c->isLineSegment() &&
+ c->finalPoint() == _closing_seg->finalPoint())
+ {
+ // appending a curve that matches the closing segment has no effect
+ delete c;
+ return;
+ }
+ }
+ _data->curves.insert(_data->curves.end() - 1, c);
+ _closing_seg->setInitial(c->finalPoint());
+}
+
+void Path::checkContinuity() const
+{
+ Sequence::const_iterator i = _data->curves.begin(), j = _data->curves.begin();
+ ++j;
+ for (; j != _data->curves.end(); ++i, ++j) {
+ if (i->finalPoint() != j->initialPoint()) {
+ THROW_CONTINUITYERROR();
+ }
+ }
+ if (_data->curves.front().initialPoint() != _data->curves.back().finalPoint()) {
+ THROW_CONTINUITYERROR();
+ }
+}
+
+// breaks time value into integral and fractional part
+PathTime Path::_factorTime(Coord t) const
+{
+ size_type sz = size_default();
+ if (t < 0 || t > sz) {
+ THROW_RANGEERROR("parameter t out of bounds");
+ }
+
+ PathTime ret;
+ Coord k;
+ ret.t = modf(t, &k);
+ ret.curve_index = k;
+ if (ret.curve_index == sz) {
+ --ret.curve_index;
+ ret.t = 1;
+ }
+ return ret;
+}
+
+Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths)
+{
+ Piecewise<D2<SBasis> > ret = paths[0].toPwSb();
+ for (unsigned i = 1; i < paths.size(); i++) {
+ ret.concat(paths[i].toPwSb());
+ }
+ return ret;
+}
+
+bool are_near(Path const &a, Path const &b, Coord precision)
+{
+ if (a.size() != b.size()) return false;
+
+ for (unsigned i = 0; i < a.size(); ++i) {
+ if (!a[i].isNear(b[i], precision)) return false;
+ }
+ return true;
+}
+
+std::ostream &operator<<(std::ostream &out, Path const &path)
+{
+ SVGPathWriter pw;
+ pw.feed(path);
+ out << pw.str();
+ return out;
+}
+
+} // 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/src/2geom/pathvector.cpp b/src/2geom/pathvector.cpp
new file mode 100644
index 0000000..0683c31
--- /dev/null
+++ b/src/2geom/pathvector.cpp
@@ -0,0 +1,336 @@
+/** @file
+ * @brief PathVector - a sequence of subpaths
+ *//*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2008-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 <2geom/affine.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-writer.h>
+#include <2geom/sweeper.h>
+#include <optional>
+
+namespace Geom {
+
+//PathVector &PathVector::operator+=(PathVector const &other);
+
+PathVector::size_type PathVector::curveCount() const
+{
+ size_type n = 0;
+ for (const auto & it : *this) {
+ n += it.size_default();
+ }
+ return n;
+}
+
+void PathVector::reverse(bool reverse_paths)
+{
+ if (reverse_paths) {
+ std::reverse(begin(), end());
+ }
+ for (auto & i : *this) {
+ i = i.reversed();
+ }
+}
+
+PathVector PathVector::reversed(bool reverse_paths) const
+{
+ PathVector ret;
+ for (const auto & i : *this) {
+ ret.push_back(i.reversed());
+ }
+ if (reverse_paths) {
+ std::reverse(ret.begin(), ret.end());
+ }
+ return ret;
+}
+
+Path &PathVector::pathAt(Coord t, Coord *rest)
+{
+ return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(t, rest));
+}
+Path const &PathVector::pathAt(Coord t, Coord *rest) const
+{
+ PathVectorTime pos = _factorTime(t);
+ if (rest) {
+ *rest = Coord(pos.curve_index) + pos.t;
+ }
+ return at(pos.path_index);
+}
+Curve const &PathVector::curveAt(Coord t, Coord *rest) const
+{
+ PathVectorTime pos = _factorTime(t);
+ if (rest) {
+ *rest = pos.t;
+ }
+ return at(pos.path_index).at(pos.curve_index);
+}
+Coord PathVector::valueAt(Coord t, Dim2 d) const
+{
+ PathVectorTime pos = _factorTime(t);
+ return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d);
+}
+Point PathVector::pointAt(Coord t) const
+{
+ PathVectorTime pos = _factorTime(t);
+ return at(pos.path_index).at(pos.curve_index).pointAt(pos.t);
+}
+
+OptRect PathVector::boundsFast() const
+{
+ OptRect bound;
+ if (empty()) return bound;
+
+ bound = front().boundsFast();
+ for (const_iterator it = ++begin(); it != end(); ++it) {
+ bound.unionWith(it->boundsFast());
+ }
+ return bound;
+}
+
+OptRect PathVector::boundsExact() const
+{
+ OptRect bound;
+ if (empty()) return bound;
+
+ bound = front().boundsExact();
+ for (const_iterator it = ++begin(); it != end(); ++it) {
+ bound.unionWith(it->boundsExact());
+ }
+ return bound;
+}
+
+void PathVector::snapEnds(Coord precision)
+{
+ for (std::size_t i = 0; i < size(); ++i) {
+ (*this)[i].snapEnds(precision);
+ }
+}
+
+// sweepline optimization
+// this is very similar to CurveIntersectionSweepSet in path.cpp
+// should probably be merged
+class PathIntersectionSweepSet {
+public:
+ struct PathRecord {
+ boost::intrusive::list_member_hook<> _hook;
+ Path const *path;
+ std::size_t index;
+ unsigned which;
+
+ PathRecord(Path const &p, std::size_t i, unsigned w)
+ : path(&p)
+ , index(i)
+ , which(w)
+ {}
+ };
+
+ typedef std::vector<PathRecord>::iterator ItemIterator;
+
+ PathIntersectionSweepSet(std::vector<PVIntersection> &result,
+ PathVector const &a, PathVector const &b, Coord precision)
+ : _result(result)
+ , _precision(precision)
+ {
+ _records.reserve(a.size() + b.size());
+ for (std::size_t i = 0; i < a.size(); ++i) {
+ _records.emplace_back(a[i], i, 0);
+ }
+ for (std::size_t i = 0; i < b.size(); ++i) {
+ _records.emplace_back(b[i], i, 1);
+ }
+ }
+
+ std::vector<PathRecord> &items() { return _records; }
+
+ Interval itemBounds(ItemIterator ii) {
+ OptRect r = ii->path->boundsFast();
+ if (!r) return Interval();
+ return (*r)[X];
+ }
+
+ void addActiveItem(ItemIterator ii) {
+ unsigned w = ii->which;
+ unsigned ow = (ii->which + 1) % 2;
+
+ for (auto & i : _active[ow]) {
+ if (!ii->path->boundsFast().intersects(i.path->boundsFast())) continue;
+ std::vector<PathIntersection> px = ii->path->intersect(*i.path, _precision);
+ for (auto & k : px) {
+ PathVectorTime tw(ii->index, k.first), tow(i.index, k.second);
+ _result.emplace_back(
+ w == 0 ? tw : tow,
+ w == 0 ? tow : tw,
+ k.point());
+ }
+ }
+ _active[w].push_back(*ii);
+ }
+
+ void removeActiveItem(ItemIterator ii) {
+ ActivePathList &apl = _active[ii->which];
+ apl.erase(apl.iterator_to(*ii));
+ }
+
+private:
+ typedef boost::intrusive::list
+ < PathRecord
+ , boost::intrusive::member_hook
+ < PathRecord
+ , boost::intrusive::list_member_hook<>
+ , &PathRecord::_hook
+ >
+ > ActivePathList;
+
+ std::vector<PVIntersection> &_result;
+ std::vector<PathRecord> _records;
+ ActivePathList _active[2];
+ Coord _precision;
+};
+
+std::vector<PVIntersection> PathVector::intersect(PathVector const &other, Coord precision) const
+{
+ std::vector<PVIntersection> result;
+
+ PathIntersectionSweepSet pisset(result, *this, other, precision);
+ Sweeper<PathIntersectionSweepSet> sweeper(pisset);
+ sweeper.process();
+
+ std::sort(result.begin(), result.end());
+
+ return result;
+}
+
+int PathVector::winding(Point const &p) const
+{
+ int wind = 0;
+ for (const auto & i : *this) {
+ if (!i.boundsFast().contains(p)) continue;
+ wind += i.winding(p);
+ }
+ return wind;
+}
+
+std::optional<PathVectorTime> PathVector::nearestTime(Point const &p, Coord *dist) const
+{
+ std::optional<PathVectorTime> retval;
+
+ Coord mindist = infinity();
+ for (size_type i = 0; i < size(); ++i) {
+ Coord d;
+ PathTime pos = (*this)[i].nearestTime(p, &d);
+ if (d < mindist) {
+ mindist = d;
+ retval = PathVectorTime(i, pos.curve_index, pos.t);
+ }
+ }
+
+ if (dist) {
+ *dist = mindist;
+ }
+ return retval;
+}
+
+std::vector<PathVectorTime> PathVector::allNearestTimes(Point const &p, Coord *dist) const
+{
+ std::vector<PathVectorTime> retval;
+
+ Coord mindist = infinity();
+ for (size_type i = 0; i < size(); ++i) {
+ Coord d;
+ PathTime pos = (*this)[i].nearestTime(p, &d);
+ if (d < mindist) {
+ mindist = d;
+ retval.clear();
+ }
+ if (d <= mindist) {
+ retval.emplace_back(i, pos.curve_index, pos.t);
+ }
+ }
+
+ if (dist) {
+ *dist = mindist;
+ }
+ return retval;
+}
+
+std::vector<Point> PathVector::nodes() const
+{
+ std::vector<Point> result;
+ for (size_type i = 0; i < size(); ++i) {
+ size_type path_size = (*this)[i].size_closed();
+ for (size_type j = 0; j < path_size; ++j) {
+ result.push_back((*this)[i][j].initialPoint());
+ }
+ }
+ return result;
+}
+
+PathVectorTime PathVector::_factorTime(Coord t) const
+{
+ PathVectorTime ret;
+ Coord rest = 0;
+ ret.t = modf(t, &rest);
+ ret.curve_index = rest;
+ for (; ret.path_index < size(); ++ret.path_index) {
+ unsigned s = _data.at(ret.path_index).size_default();
+ if (s > ret.curve_index) break;
+ // special case for the last point
+ if (s == ret.curve_index && ret.path_index + 1 == size()) {
+ --ret.curve_index;
+ ret.t = 1;
+ break;
+ }
+ ret.curve_index -= s;
+ }
+ return ret;
+}
+
+std::ostream &operator<<(std::ostream &out, PathVector const &pv)
+{
+ SVGPathWriter wr;
+ wr.feed(pv);
+ out << wr.str();
+ return out;
+}
+
+} // 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/src/2geom/piecewise.cpp b/src/2geom/piecewise.cpp
new file mode 100644
index 0000000..8714bd6
--- /dev/null
+++ b/src/2geom/piecewise.cpp
@@ -0,0 +1,266 @@
+/*
+ * piecewise.cpp - Piecewise function class
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ * Copyright 2007 JF Barraud
+ *
+ * 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, output 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/piecewise.h>
+#include <iterator>
+#include <map>
+
+namespace Geom {
+
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k) {
+ Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<SBasis> ret = Piecewise<SBasis>();
+ assert(pa.size() == pb.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(divide(pa[i], pb[i], k));
+ return ret;
+}
+
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero) {
+ Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<SBasis> ret = Piecewise<SBasis>();
+ assert(pa.size() == pb.size());
+ for (unsigned i = 0; i < pa.size(); i++){
+ Piecewise<SBasis> divi = divide(pa[i], pb[i], tol, k, zero);
+ divi.setDomain(Interval(pa.cuts[i],pa.cuts[i+1]));
+ ret.concat(divi);
+ }
+ return ret;
+}
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero){
+ return divide(a,Piecewise<SBasis>(b),tol,k,zero);
+}
+Piecewise<SBasis> divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero){
+ return divide(Piecewise<SBasis>(a),b,tol,k,zero);
+}
+Piecewise<SBasis> divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero) {
+ if (b.tailError(0)<2*zero){
+ //TODO: have a better look at sgn(b).
+ double sgn= (b(.5)<0.)?-1.:1;
+ return Piecewise<SBasis>(Linear(sgn/zero)*a);
+ }
+
+ if (fabs(b.at0())>zero && fabs(b.at1())>zero ){
+ SBasis c,r=a;
+ //TODO: what is a good relative tol? atm, c=a/b +/- (tol/a)%...
+
+ k+=1;
+ r.resize(k, Linear(0,0));
+ c.resize(k, Linear(0,0));
+
+ //assert(b.at0()!=0 && b.at1()!=0);
+ for (unsigned i=0; i<k; i++){
+ Linear ci = Linear(r[i][0]/b[0][0],r[i][1]/b[0][1]);
+ c[i]=ci;
+ r-=shift(ci*b,i);
+ }
+
+ if (r.tailError(k)<tol) return Piecewise<SBasis>(c);
+ }
+
+ Piecewise<SBasis> c0,c1;
+ c0 = divide(compose(a,Linear(0.,.5)),compose(b,Linear(0.,.5)),tol,k);
+ c1 = divide(compose(a,Linear(.5,1.)),compose(b,Linear(.5,1.)),tol,k);
+ c0.setDomain(Interval(0.,.5));
+ c1.setDomain(Interval(.5,1.));
+ c0.concat(c1);
+ return c0;
+}
+
+
+//-- compose(pw<T>,SBasis) ---------------
+/*
+ the purpose of the following functions is only to reduce the code in piecewise.h
+ TODO: use a vector<pairs<double,unsigned> > instead of a map<double,unsigned>.
+ */
+
+std::map<double,unsigned> compose_pullback(std::vector<double> const &values, SBasis const &g){
+ std::map<double,unsigned> result;
+
+ std::vector<std::vector<double> > roots = multi_roots(g, values);
+ for(unsigned i=0; i<roots.size(); i++){
+ for(unsigned j=0; j<roots[i].size();j++){
+ result[roots[i][j]]=i;
+ }
+ }
+ // Also map 0 and 1 to the first value above(or =) g(0) and g(1).
+ if(result.count(0.)==0){
+ unsigned i=0;
+ while (i<values.size()&&(g.at0()>values[i])) i++;
+ result[0.]=i;
+ }
+ if(result.count(1.)==0){
+ unsigned i=0;
+ while (i<values.size()&&(g.at1()>values[i])) i++;
+ result[1.]=i;
+ }
+ return(result);
+}
+
+int compose_findSegIdx(std::map<double,unsigned>::iterator const &cut,
+ std::map<double,unsigned>::iterator const &next,
+ std::vector<double> const &levels,
+ SBasis const &g){
+ double t0=(*cut).first;
+ unsigned idx0=(*cut).second;
+ double t1=(*next).first;
+ unsigned idx1=(*next).second;
+ assert(t0<t1);
+ int idx; //idx of the relevant f.segs
+ if (std::max(idx0,idx1)==levels.size()){ //g([t0,t1]) is above the top level,
+ idx=levels.size()-1;
+ } else if (idx0 != idx1){ //g([t0,t1]) crosses from level idx0 to idx1,
+ idx=std::min(idx0,idx1);
+ } else if(g((t0+t1)/2) < levels[idx0]) { //g([t0,t1]) is a 'U' under level idx0,
+ idx=idx0-1;
+ } else if(g((t0+t1)/2) > levels[idx0]) { //g([t0,t1]) is a 'bump' over level idx0,
+ idx=idx0;
+ } else { //g([t0,t1]) is contained in level idx0!...
+ idx = (idx0==levels.size())? idx0-1:idx0;
+ }
+
+ //move idx back from levels f.cuts
+ idx+=1;
+ return idx;
+}
+
+
+Piecewise<SBasis> pw_compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero){
+ Piecewise<SBasis> result;
+
+ assert( f.size()>0 && g.size()>0);
+ SBasis g01 = g;
+ bool flip = ( g01.at0() > g01.at1() );
+
+ //OptInterval g_range = bounds_exact(g);
+ OptInterval g_range( Interval( g.at0(), g.at1() ));
+
+ g01 -= g_range->min();
+ g01 /= g_range->extent();
+ if ( flip ){
+ g01 *= -1.;
+ g01 += 1.;
+ }
+#if 1
+ assert( std::abs( g01.at0() - 0. ) < zero );
+ assert( std::abs( g01.at1() - 1. ) < zero );
+ //g[0][0] = 0.;
+ //g[0][1] = 1.;
+#endif
+
+ SBasis foginv = compose_inverse( f, g01, order, zero );
+ SBasis err = compose( foginv, g01) - f;
+
+ if ( err.tailError(0) < zero ){
+ result = Piecewise<SBasis> (foginv);
+ }else{
+ SBasis g_portion = portion( g01, Interval(0.,.5) );
+ SBasis f_portion = portion( f, Interval(0.,.5) );
+ result = pw_compose_inverse(f_portion, g_portion, order, zero);
+
+ g_portion = portion( g01, Interval(.5, 1.) );
+ f_portion = portion( f, Interval(.5, 1.) );
+ Piecewise<SBasis> result_next;
+ result_next = pw_compose_inverse(f_portion, g_portion, order, zero);
+ result.concat( result_next );
+ }
+ if (flip) {
+ result = reverse(result);
+ }
+ result.setDomain(*g_range);
+ return result;
+}
+
+
+std::vector<double> roots(Piecewise<SBasis> const &f){
+ std::vector<double> result;
+ for (unsigned i=0; i<f.size(); i++){
+ std::vector<double> rts=roots(f.segs[i]);
+
+ for (double rt : rts){
+ result.push_back(f.mapToDomain(rt, i));
+ }
+ }
+ return result;
+}
+
+std::vector<std::vector<double> > multi_roots(Piecewise<SBasis> const &f, std::vector<double> const &values) {
+ std::vector<std::vector<double> > result(values.size());
+ for (unsigned i=0; i<f.size(); i++) {
+ std::vector<std::vector<double> > rts = multi_roots(f.segs[i], values);
+ for(unsigned j=0; j<rts.size(); j++) {
+ for(unsigned r=0; r<rts[j].size(); r++){
+ result[j].push_back(f.mapToDomain(rts[j][r], i));
+ }
+ }
+ }
+ return result;
+}
+
+
+std::vector<Interval> level_set(Piecewise<SBasis> const &f, Interval const &level, double tol){
+ std::vector<Interval> result;
+ for (unsigned i=0; i<f.size(); i++){
+ std::vector<Interval> resulti = level_set( f[i], level, 0., 1., tol);
+ for (unsigned j=0; j<resulti.size(); j++){
+ double a = f.cuts[i] + resulti[j].min() * ( f.cuts[i+1] - f.cuts[i] );
+ double b = f.cuts[i] + resulti[j].max() * ( f.cuts[i+1] - f.cuts[i] );
+ Interval domj( a, b );
+ //Interval domj( f.mapToDomain(resulti[j].min(), i ), f.mapToDomain(resulti[j].max(), i ) );
+
+ if ( j==0 && !result.empty() && result.back().intersects(domj) ){
+ result.back().unionWith(domj);
+ }else{
+ result.push_back(domj);
+ }
+ }
+ }
+ return result;
+}
+std::vector<Interval> level_set(Piecewise<SBasis> const &f, double v, double vtol, double tol){
+ Interval level ( v-vtol, v+vtol );
+ return level_set( f, level, tol);
+}
+
+
+}
+/*
+ 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/src/2geom/planar-graph.h b/src/2geom/planar-graph.h
new file mode 100644
index 0000000..fb5f1ac
--- /dev/null
+++ b/src/2geom/planar-graph.h
@@ -0,0 +1,1252 @@
+/** @file PlanarGraph – a graph geometrically embedded in the plane.
+ */
+/*
+ * 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.
+ */
+
+// WARNING: This is a private header. Do not include it directly.
+
+#ifndef LIB2GEOM_SEEN_PLANAR_GRAPH_H
+#define LIB2GEOM_SEEN_PLANAR_GRAPH_H
+
+#include <algorithm>
+#include <iterator>
+#include <list>
+
+#include <2geom/angle.h>
+#include <2geom/coord.h>
+#include <2geom/line.h>
+#include <2geom/point.h>
+#include <2geom/path.h>
+#include <2geom/path-intersection.h>
+#include <2geom/utils.h>
+
+namespace Geom {
+
+/**
+ * \class PlanarGraph
+ * \brief Planar graph - a graph geometrically embedded in the plane.
+ *
+ * A planar graph is composed of vertices with assigned locations (as points in the plane)
+ * and of edges (arcs), which are imagined as non-intersecting paths in the plane connecting
+ * the vertices. The edges can hold user-supplied labels (e.g., weights) which support event
+ * callbacks for when the graph is reconfigured, allowing the labels to be updated accordingly.
+ *
+ * \tparam EdgeLabel A user-supplied type; an object of this type will be attached to each
+ * edge of the planar graph (e.g., a "weight" of the edge). The type must
+ * satisfy requirements described further below.
+ *
+ * In order to construct a planar graph, you should specify the clumping precision (passed as
+ * a constructor argument) and then use the method insertEdge() to add edges to the graph, as
+ * many times as necessary. The graph will automatically figure out the locations of the
+ * vertices based on the endpoints of the inserted edges. Vertices will be combined into one
+ * when they are positioned within the distance specified as the clumping threshold, and the
+ * inserted edges will be attached to them accordingly. It is also possible to insert paths
+ * (typically, closed) not attached to any vertices, using the method insertDetached().
+ *
+ * After the edges are inserted, the graph is in a potentially degenerate state, where several
+ * edges may exactly coincide in part or in full. If this is not desired, you can regularize
+ * the graph by calling regularize(). During the regularization process, any overlapping edges
+ * are combined into one. Partially overlapping edges are first split into overlapping and
+ * non-overlapping portions, after which the overlapping portions are combined. If the edges
+ * or their parts overlap but run in opposite directions, one of them will be reversed before
+ * being merged with the other one. The overlaps are detected using the precision setting passed
+ * as the clumping precision in the constructor argument.
+ *
+ * Note however that the regularization procedure does NOT detect transverse intersections
+ * between the edge paths: if such intersections are not desired, the user must pass non-\
+ * intersecting paths to the insertEdge() method (the paths may still have common endpoints,
+ * which is fine: that's how common vertices are created).
+ *
+ * The insertion of new edges invalidates the regularized status, which you can check at any
+ * time by calling isRegularized().
+ *
+ * The vertices stored by the graph are sorted by increasing X-coordinate, and if they have
+ * equal X-coordinates, by increasing Y-coordinate. Even before regularization, incidences of
+ * edges to each vertex are sorted by increasing azimuth of the outgoing tangent (departure
+ * heading, but in radians, in the interval \f$(-\pi, \pi]\f$). After regularization, the edges
+ * around each vertex are guaranteed to be sorted counterclockwise (when the Y-axis points up)
+ * by where they end up going eventually, even if they're tangent at the vertex and therefore
+ * have equal or nearly equal departure azimuths.
+ *
+ * \note
+ * Requirements on the \c EdgeLabel template parameter type.
+ * In order for the template to instantiate correctly, the following must be satisfied:
+ * \li The \c EdgeLabel type provides a method \c onReverse() which gets called whenever
+ * the orientation of the labeled edge is reversed. This is useful when implementing
+ * a directed graph, since the label can keep track of the logical direction.
+ * \li The \c EdgeLabel type provides a method \c onMergeWith(EdgeLabel const&) which gets
+ * called when the labeled edge is combined with a geometrically identical (coinciding)
+ * edge (both combined edges having the same orientations). The label of the edge merged
+ * with the current one is provided as an argument to the method. This is useful when
+ * implementing a graph with weights: for example, when two edges are merged, you may
+ * want to combine their weights in some way.
+ * \li There is a method \c onDetach() called when the edge is removed from the graph. The
+ * edge objects are never destroyed but may be disconnected from the graph when they're no
+ * longer needed; this allows the user to put the labels of such edges in a "dead" state.
+ * \li The \c EdgeLabel objects must be copy-constructible and copy-assignable. This is
+ * because when an edge is subdivided into two, the new edges replacing it get decorated
+ * with copies of the original edge's label.
+ */
+template<typename EdgeLabel>
+#if __cplusplus >= 202002L
+requires requires(EdgeLabel el, EdgeLabel const &other) {
+ el.onReverse();
+ el.onMergeWith(other);
+ el.onDetach();
+ el = other;
+}
+#endif
+class PlanarGraph
+{
+public:
+
+ /** Represents the joint between an edge and a vertex. */
+ struct Incidence
+ {
+ using Sign = bool;
+ inline static Sign const START = false;
+ inline static Sign const END = true;
+
+ double azimuth; ///< Angle of the edge's departure.
+ unsigned index; ///< Index of the edge in the parent graph.
+ Sign sign; ///< Whether this is the start or end of the edge.
+ bool invalid = false; ///< Whether this incidence has been marked for deletion.
+
+ Incidence(unsigned edge_index, double departure_azimuth, Sign which_end)
+ : azimuth{departure_azimuth}
+ , index{edge_index}
+ , sign{which_end}
+ {
+ }
+ ~Incidence() = default;
+
+ /// Compare incidences based on their azimuths in radians.
+ inline bool operator<(Incidence const &other) const { return azimuth < other.azimuth; }
+
+ /// Compare the azimuth of an incidence with the given angle.
+ inline bool operator<(double angle) const { return azimuth < angle; }
+
+ /// Check equality (only tests edges and their ends).
+ inline bool operator==(Incidence const &other) const
+ {
+ return index == other.index && sign == other.sign;
+ }
+ };
+ using IncIt = typename std::list<Incidence>::iterator;
+ using IncConstIt = typename std::list<Incidence>::const_iterator;
+
+ /** Represents the vertex of a planar graph. */
+ class Vertex
+ {
+ private:
+ Point const _position; ///< Geometric position of the vertex.
+ std::list<Incidence> _incidences; ///< List of incidences of edges to this vertex.
+ unsigned mutable _flags = 0; ///< User-settable flags.
+
+ inline static Point::LexLess<X> const _cmp; ///< Point sorting function object.
+
+ public:
+ Vertex(Point const &pos)
+ : _position{pos}
+ {
+ }
+
+ /** Get the geometric position of the vertex. */
+ Point const &point() const { return _position; }
+
+ /** Get the list of incidences to the vertex. */
+ auto const &getIncidences() const { return _incidences; }
+
+ /** Compare vertices based on their coordinates (lexicographically). */
+ bool operator<(Vertex const &other) const { return _cmp(_position, other._position); }
+
+ unsigned flags() const { return _flags; } ///< Get the user flags.
+ void setFlags(unsigned flags) const { _flags = flags; } ///< Set the user flags.
+
+ /** Get the cyclic-next incidence after the passed one, in the CCW direction. */
+ IncConstIt cyclicNextIncidence(IncConstIt it) const { return cyclic_next(it, _incidences); }
+
+ /** Get the cyclic-next incidence after the passed one, in the CW direction. */
+ IncConstIt cyclicPrevIncidence(IncConstIt it) const { return cyclic_prior(it, _incidences); }
+
+ /** The same but with pointers. */
+ Incidence *cyclicNextIncidence(Incidence *from)
+ {
+ return &(*cyclicNextIncidence(_incidencePtr2It(from)));
+ }
+ Incidence *cyclicPrevIncidence(Incidence *from)
+ {
+ return &(*cyclicPrevIncidence(_incidencePtr2It(from)));
+ }
+
+ private:
+ /** Same as above, but not const (so only for private use). */
+ IncIt cyclicNextIncidence(IncIt it) { return cyclic_next(it, _incidences); }
+ IncIt cyclicPrevIncidence(IncIt it) { return cyclic_prior(it, _incidences); }
+
+ /** Insert an incidence; for internal use by the PlanarGraph class. */
+ Incidence &_addIncidence(unsigned edge_index, double azimuth, typename Incidence::Sign sign)
+ {
+ auto where = std::find_if(_incidences.begin(), _incidences.end(), [=](auto &inc) -> bool {
+ return inc.azimuth >= azimuth;
+ });
+ return *(_incidences.emplace(where, edge_index, azimuth, sign));
+ }
+
+ /** Return a valid iterator to an incidence passed by pointer;
+ * if the pointer is invalid, return a start iterator. */
+ IncIt _incidencePtr2It(Incidence *pointer)
+ {
+ auto it = std::find_if(_incidences.begin(), _incidences.end(),
+ [=](Incidence const &i) -> bool { return &i == pointer; });
+ return (it == _incidences.end()) ? _incidences.begin() : it;
+ }
+
+ friend class PlanarGraph<EdgeLabel>;
+ };
+ using VertexIterator = typename std::list<Vertex>::iterator;
+
+ /** Represents an edge of the planar graph. */
+ struct Edge
+ {
+ Vertex *start = nullptr, *end = nullptr; ///< Start and end vertices.
+ Path path; ///< The path associated to the edge.
+ bool detached = false; ///< Whether the edge is detached from the graph.
+ bool inserted_as_detached = false; ///< Whether the edge was inserted as detached.
+ EdgeLabel mutable label; ///< The user-supplied label of the edge.
+
+ /** Construct an edge with a given label. */
+ Edge(Path &&movein_path, EdgeLabel &&movein_label)
+ : path{movein_path}
+ , label{movein_label}
+ {
+ }
+
+ /** Detach the edge from the graph. */
+ void detach()
+ {
+ detached = true;
+ label.onDetach();
+ }
+ };
+ using EdgeIterator = typename std::vector<Edge>::iterator;
+ using EdgeConstIterator = typename std::vector<Edge>::const_iterator;
+
+private:
+ double const _precision; ///< Numerical epsilon for vertex clumping.
+ std::list<Vertex> _vertices; ///< Vertices of the graph.
+ std::vector<Edge> _edges; ///< Edges of the graph.
+ std::vector< std::pair<Vertex *, Incidence *> > _junk; ///< Incidences that should be purged.
+ bool _regularized = true; // An empty graph is (trivially) regularized.
+
+public:
+ PlanarGraph(Coord precision = EPSILON)
+ : _precision{precision}
+ {
+ }
+
+ std::list<Vertex> const &getVertices() const { return _vertices; }
+ std::vector<Edge> const &getEdges() const { return _edges; }
+ Edge const &getEdge(size_t index) const { return _edges.at(index); }
+ size_t getEdgeIndex(Edge const &edge) const { return &edge - _edges.data(); }
+ double getPrecision() const { return _precision; }
+ size_t numVertices() const { return _vertices.size(); }
+ size_t numEdges(bool include_detached = true) const
+ {
+ if (include_detached) {
+ return _edges.size();
+ }
+ return std::count_if(_edges.begin(), _edges.end(),
+ [](Edge const &e) -> bool { return !e.detached; });
+ }
+
+ /** Check if the graph has been regularized. */
+ bool isRegularized() const { return _regularized; }
+
+ // 0x1p-50 is about twice the distance between M_PI and the next representable double.
+ void regularize(double angle_precision = 0x1p-50, bool remove_collapsed_loops = true);
+
+ /** Allocate memory to store the specified number of edges. */
+ void reserveEdgeCapacity(size_t capacity) { _edges.reserve(capacity); }
+
+ unsigned insertEdge(Path &&path, EdgeLabel &&edge = EdgeLabel());
+ unsigned insertDetached(Path &&path, EdgeLabel &&edge = EdgeLabel());
+
+ /** Edge insertion with a copy of the path. */
+ unsigned insertEdge(Path const &path, EdgeLabel &&edge = EdgeLabel())
+ {
+ return insertEdge(Path(path), std::forward<EdgeLabel>(edge));
+ }
+ unsigned insertDetached(Path const &path, EdgeLabel &&edge = EdgeLabel())
+ {
+ return insertDetached(Path(path), std::forward<EdgeLabel>(edge));
+ }
+
+ /** \brief Find the incidence at the specified endpoint of the edge.
+ *
+ * \param edge_index The index of the edge whose incidence we wish to query.
+ * \param sign Which end of the edge do we want an incidence of.
+ * \return A pair consisting of pointers to the vertex and the incidence.
+ * If not found, both pointers will be null.
+ */
+ std::pair<Vertex *, Incidence *>
+ getIncidence(unsigned edge_index, typename Incidence::Sign sign) const
+ {
+ if (edge_index >= _edges.size() || _edges[edge_index].detached) {
+ return {nullptr, nullptr};
+ }
+ Vertex *vertex = (sign == Incidence::START) ? _edges[edge_index].start
+ : _edges[edge_index].end;
+ if (!vertex) {
+ return {nullptr, nullptr};
+ }
+ auto it = std::find(vertex->_incidences.begin(), vertex->_incidences.end(),
+ Incidence(edge_index, 42, sign)); // azimuth doesn't matter.
+ if (it == vertex->_incidences.end()) {
+ return {nullptr, nullptr};
+ }
+ return {vertex, &(*it)};
+ }
+
+ /**
+ * \brief Go clockwise or counterclockwise around the vertex and find the next incidence.
+ * The notions of "clockwise"/"counterclockwise" correspond to the y-axis pointing up.
+ *
+ * \param vertex The vertex around which to orbit.
+ * \param incidence The incidence from which to start traversal.
+ * \param clockwise Whether to go clockwise instead of (default) counterclockwise.
+ * \return The next incidence encountered going in the specified direction.
+ */
+ inline Incidence const &nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence,
+ bool clockwise = false) const
+ {
+ return clockwise ? *(vertex->_cyclicPrevIncidence(incidence))
+ : *(vertex->_cyclicNextIncidence(incidence));
+ }
+
+ /** As above, but taking references instead of iterators. */
+ inline Incidence const &nextIncidence(Vertex const &vertex, Incidence const &incidence,
+ bool clockwise = false) const
+ {
+ IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
+ if (it == vertex._incidences.end()) {
+ return incidence;
+ }
+ return clockwise ? *(vertex.cyclicPrevIncidence(it))
+ : *(vertex.cyclicNextIncidence(it));
+ }
+
+ /** As above, but return an iterator to a const incidence. */
+ inline IncConstIt nextIncidenceIt(Vertex const &vertex, Incidence const &incidence,
+ bool clockwise = false) const
+ {
+ IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence);
+ if (it == vertex._incidences.end()) {
+ return vertex._incidences.begin();
+ }
+ return clockwise ? vertex.cyclicPrevIncidence(it)
+ : vertex.cyclicNextIncidence(it);
+ }
+ inline IncConstIt nextIncidenceIt(Vertex const &vertex, IncConstIt const &incidence,
+ bool clockwise = false) const
+ {
+ return clockwise ? vertex.cyclicPrevIncidence(incidence)
+ : vertex.cyclicNextIncidence(incidence);
+ }
+
+ /** As above, but start at the prescribed departure azimuth (in radians).
+ *
+ * \return A pointer to the incidence emanating from the vertex at or immediately after
+ * the specified azimuth, when going around the vertex in the specified direction.
+ * If the vertex has no incidences, return value is nullptr.
+ */
+ Incidence *nextIncidence(VertexIterator const &vertex, double azimuth,
+ bool clockwise = false) const;
+
+ /** Get the incident path, always oriented away from the vertex. */
+ Path getOutgoingPath(Incidence const *incidence) const
+ {
+ return incidence ? _getPathImpl(incidence, Incidence::START) : Path();
+ }
+
+ /** Get the incident path, always oriented towards the vertex. */
+ Path getIncomingPath(Incidence const *incidence) const
+ {
+ return incidence ? _getPathImpl(incidence, Incidence::END) : Path();
+ }
+
+private:
+ inline Path _getPathImpl(Incidence const *incidence, typename Incidence::Sign origin) const
+ {
+ return (incidence->sign == origin) ? _edges[incidence->index].path
+ : _edges[incidence->index].path.reversed();
+ }
+
+ /** Earmark an incidence for future deletion. */
+ inline void _throwAway(Vertex *vertex, Incidence *incidence)
+ {
+ if (!vertex || !incidence) {
+ return;
+ }
+ incidence->invalid = true;
+ _junk.emplace_back(vertex, incidence);
+ }
+
+ // Topological reconfiguration functions; see their definitions for documentation.
+ bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
+ Vertex *_ensureVertexAt(Point const &position);
+ void _mergeCoincidingEdges(Incidence *first, Incidence *second);
+ void _mergeShorterLonger(Vertex &vertex, Incidence *shorter, Incidence *longer,
+ PathTime const &time_on_longer);
+ void _mergeWyeConfiguration(Vertex &vertex, Incidence *first, Incidence *second,
+ PathIntersection const &split);
+ void _purgeJunkIncidences();
+ void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second,
+ PathIntersection const &split);
+ bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second, bool deloop);
+ void _reglueTangentFan(Vertex &vertex, IncIt const &first, IncIt const &last, bool deloop);
+ void _regularizeVertex(Vertex &vertex, double angle_precision, bool deloop);
+
+ // === Static stuff ===
+
+ /** Return the angle between the vector and the positive X axis or 0 if undefined. */
+ inline static double _getAzimuth(Point const &vec) { return vec.isZero() ? 0.0 : atan2(vec); }
+
+ /** Return path time corresponding to the same point on the reversed path. */
+ inline static PathTime _reversePathTime(PathTime const &time, Path const &path)
+ {
+ int new_index = path.size() - time.curve_index - 1;
+ Coord new_time = 1.0 - time.t;
+ if (new_index < 0) {
+ new_index = 0;
+ new_time = 0;
+ }
+ return PathTime(new_index, new_time);
+ }
+
+ /** Return path time at the end of the path. */
+ inline static PathTime _pathEnd(Path const &path) { return PathTime(path.size() - 1, 1.0); }
+ inline static auto const PATH_START = PathTime(0, 0);
+
+public:
+ static double closedPathArea(Path const &path);
+ static bool deviatesLeft(Path const &first, Path const &second);
+};
+
+/**
+ * \brief Insert a new vertex or reuse an existing one.
+ *
+ * Ensures that there is a vertex at or near the specified position
+ * (within the distance of _precision).
+ *
+ * \param pos The desired geometric position of the new vertex.
+ * \return A pointer to the inserted vertex or a pre-existing vertex near the
+ * desired position.
+ */
+template<typename EL>
+typename PlanarGraph<EL>::Vertex *PlanarGraph<EL>::_ensureVertexAt(Point const &pos)
+{
+ auto const insert_at_front = [&, this]() -> Vertex* {
+ _vertices.emplace_front(pos);
+ return &(_vertices.front());
+ };
+
+ if (_vertices.empty()) {
+ return insert_at_front();
+ }
+
+ // TODO: Use a heap?
+ auto it = std::find_if(_vertices.begin(), _vertices.end(), [&](Vertex const &v) -> bool {
+ return Vertex::_cmp(pos, v._position); // existing vertex position > pos.
+ });
+
+ if (it != _vertices.end()) {
+ if (are_near(pos, it->_position, _precision)) {
+ return &(*it); // Reuse existing vertex.
+ }
+ if (it == _vertices.begin()) {
+ return insert_at_front();
+ }
+ }
+ // Look at the previous element, reuse if near, insert before `it` otherwise.
+ return &(*(are_near(pos, std::prev(it)->_position, _precision) ? std::prev(it)
+ : _vertices.emplace(it, pos)));
+}
+
+/**
+ * \brief Move-insert a new labeled edge into the planar graph.
+ *
+ * \param path The geometric path of the edge.
+ * \param label Optionally, the label (extra user data) associated to this edge.
+ * If absent, a default-constructed label will be used.
+ * \return The index of the inserted edge.
+ */
+template<typename EdgeLabel>
+unsigned PlanarGraph<EdgeLabel>::insertEdge(Path &&path, EdgeLabel &&label)
+{
+ unsigned edge_index = _edges.size();
+ auto &inserted = _edges.emplace_back(std::forward<Path>(path),
+ std::forward<EdgeLabel>(label));
+
+ // Calculate the outgoing azimuths at both endpoints.
+ double const start_azimuth = _getAzimuth(inserted.path.initialUnitTangent());
+ double const end_azimuth = _getAzimuth(-inserted.path.finalUnitTangent());
+
+ // Get the endpoints into the graph.
+ auto start = _ensureVertexAt(inserted.path.initialPoint());
+ auto end = _ensureVertexAt(inserted.path.finalPoint());
+
+ // Inform the edge about its endpoints.
+ inserted.start = start;
+ inserted.end = end;
+
+ // Add incidences at the endpoints.
+ start->_addIncidence(edge_index, start_azimuth, Incidence::START);
+ end->_addIncidence(edge_index, end_azimuth, Incidence::END);
+
+ _regularized = false;
+ return edge_index;
+}
+
+/**
+ * \brief Move-insert a new labeled edge but do not connect it to the graph.
+ *
+ * Although the graph will hold the path data of an edge inserted in this way, the edge
+ * will not be connected to any vertex. This can be used to store information about closed
+ * paths (loops) in the instance, without having to specify starting points for them.
+ *
+ * \param path The geometric path of the edge.
+ * \param label Optionally, the label (extra user data) associated to this edge; if absent,
+ * the label will be default-constructed.
+ * \return The index of the inserted edge.
+ */
+template<typename EdgeLabel>
+unsigned PlanarGraph<EdgeLabel>::insertDetached(Path &&path, EdgeLabel &&label)
+{
+ unsigned edge_index = _edges.size();
+ auto &inserted = _edges.emplace_back(std::forward<Path>(path),
+ std::forward<EdgeLabel>(label));
+ inserted.detached = true;
+ inserted.inserted_as_detached = true;
+ return edge_index;
+}
+
+/** Remove incidences previously marked as junk. */
+template<typename EdgeLabel>
+void PlanarGraph<EdgeLabel>::_purgeJunkIncidences()
+{
+ for (auto &[vertex, incidence] : _junk) {
+ Incidence *to_remove = incidence;
+ auto it = std::find_if(vertex->_incidences.begin(), vertex->_incidences.end(),
+ [=](Incidence const &inc) -> bool { return &inc == to_remove; });
+ if (it != vertex->_incidences.end()) {
+ vertex->_incidences.erase(it);
+ }
+ }
+ _junk.clear();
+}
+
+/**
+ * \brief Merge overlapping edges or their portions, adding vertices if necessary.
+ *
+ * \param angle_precision The numerical epsilon for radian angle comparisons.
+ * \param remove_collapsed_loops Whether to detach edges with both ends incident to the same
+ * vertex (loops) when these loops don't enclose any area.
+ *
+ * This function performs the following operations:
+ * \li Edges that are tangent at a vertex but don't otherwise overlap are sorted correctly
+ * in the counterclockwise cyclic order around the vertex.
+ * \li Degenerate loops which don't enclose any area are removed if the argument is true.
+ * \li Edges that coincide completely are reversed if needed and merged into one.
+ * \li Edges that coincide partially are split into overlapping and non-overlapping portions.
+ * Any overlapping portions are oriented consistently and then merged.
+ * \li As a sub-case of the above, any non-degenerate loop with an initial self-everlap
+ * (a "lasso") is replaced with a shorter non-overlapping loop and a simple path leading
+ * to it.
+ */
+template<typename EdgeLabel>
+void PlanarGraph<EdgeLabel>::regularize(double angle_precision, bool remove_collapsed_loops)
+{
+ for (auto it = _vertices.begin(); it != _vertices.end(); ++it) {
+ // Note: the list of vertices may grow during the execution of this loop,
+ // so don't replace it with a range-for (which stores the end iterator).
+ // We want the loop to carry on going over the elements it inserted.
+ if (it->_incidences.size() < 2) {
+ continue;
+ }
+ _regularizeVertex(*it, angle_precision, remove_collapsed_loops);
+ }
+ _purgeJunkIncidences();
+ _regularized = true;
+}
+
+/**
+ * \brief Analyze and regularize all edges emanating from a given vertex.
+ *
+ * This function goes through the list of incidences at the vertex (roughly sorted by
+ * azimuth, i.e., departure heading in radians), picking out runs of mutually tangent
+ * edges and calling _reglueTangentFan() on each run. The algorithm is quite complicated
+ * because the incidences have to be treated as a cyclic container and a run of mutually
+ * tangent edges may straddle the "end" of the list, including the possibility that the
+ * entire list is a single such run.
+ *
+ * \param vertex The vertex whose incidences should be analyzed.
+ * \param angle_precision The numerical epsilon for radian angle comparisons.
+ * \param deloop Whether loops that don't enclose any area should be detached.
+ */
+template<typename EdgeLabel>
+void PlanarGraph<EdgeLabel>::_regularizeVertex(typename PlanarGraph<EdgeLabel>::Vertex &vertex,
+ double angle_precision, bool deloop)
+{
+ auto &incidences = vertex._incidences;
+
+ /// Compare two polar angles in the interval [-π, π] modulo 2π to within angle_precision:
+ auto const angles_equal = [=](double az1, double az2) -> bool {
+ static double const twopi = 2.0 * M_PI;
+ return are_near(std::fmod(az1 + twopi, twopi), std::fmod(az2 + twopi, twopi),
+ angle_precision);
+ };
+
+ IncIt run_begin; // First element in the last discovered run of equal azimuths.
+
+ /// Find and reglue runs of nearly identical azimuths in the specified range.
+ auto const process_runs = [&](IncIt begin, IncIt end) -> bool
+ {
+ double current_azimuth = 42; // Invalid radian angle.
+ bool in_a_run = false;
+
+ for (auto it = begin; it != end; ++it) {
+ bool const equal = angles_equal(it->azimuth, current_azimuth);
+ if (equal && !in_a_run) {
+ run_begin = std::prev(it); // Save to enclosing scope.
+ in_a_run = true;
+ } else if (!equal && in_a_run) {
+ _reglueTangentFan(vertex, run_begin, std::prev(it), deloop);
+ in_a_run = false;
+ }
+ current_azimuth = it->azimuth;
+ }
+ return in_a_run;
+ };
+
+ double const last_azimuth = incidences.back().azimuth;
+
+ if (angles_equal(incidences.front().azimuth, last_azimuth)) {
+ // The cyclic list contains a run of equal azimuths which straddles the "end".
+ // This means that we must skip the part of this run on the "begin" side on the
+ // first pass and handle it once we've traversed the remainder of the list.
+
+ bool processed = false; ///< Whether we've cleared the straddling run.
+ double previous_azimuth = last_azimuth;
+ IncIt straddling_run_last;
+
+ for (auto it = incidences.begin(); it != incidences.end(); ++it) {
+ if (!angles_equal(it->azimuth, previous_azimuth)) {
+ straddling_run_last = std::prev(it);
+ process_runs(it, incidences.end());
+ processed = true;
+ break;
+ }
+ previous_azimuth = it->azimuth;
+ }
+ if (processed) {
+ // Find the first element of the straddling run.
+ auto it = std::prev(incidences.end());
+ while (angles_equal(it->azimuth, last_azimuth)) {
+ --it;
+ }
+ ++it; // Now we're at the start of the straddling run.
+ _reglueTangentFan(vertex, it, straddling_run_last, deloop);
+ } else {
+ // We never encountered anything outside of the straddling run: reglue everything.
+ _reglueTangentFan(vertex, incidences.begin(), std::prev(incidences.end()), deloop);
+ }
+ } else if (process_runs(incidences.begin(), incidences.end())) {
+ // Our run got rudely interrupted by the end of the container; reglue till the end.
+ _reglueTangentFan(vertex, run_begin, std::prev(incidences.end()), deloop);
+ }
+}
+
+/**
+ * \brief Regularize a fan of mutually tangent edges emanating from a vertex.
+ *
+ * This function compares the tangent edges pairwise and ensures that the sequence of their
+ * incidences to the vertex ends up being sorted by the ultimate direction in which the
+ * emanating edges fan out, in the counterclockwise order.
+ *
+ * If a partial or complete overlap between edges is detected, these edges are reglued.
+ *
+ * \param vertex The vertex from which the fan emanates.
+ * \param first An iterator pointing to the first incidence in the fan.
+ * \param last An iterator pointing to the last incidence in the fan.
+ * NOTE: This iterator must point to the actual last incidence, not "past" it.
+ * The reason is that we're iterating over a cyclic collection, so there
+ * isn't really a meaningful end.
+ * \param deloop Whether loops that don't enclose any area should be detached.
+ */
+template<typename EL>
+void PlanarGraph<EL>::_reglueTangentFan(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::IncIt const &first,
+ typename PlanarGraph<EL>::IncIt const &last, bool deloop)
+{
+ // Search all pairs (triangular pattern), skipping invalid incidences.
+ for (auto it = first; it != last; it = vertex.cyclicNextIncidence(it)) {
+ if (it->invalid) {
+ continue;
+ }
+ for (auto is = vertex.cyclicNextIncidence(it); true; is = vertex.cyclicNextIncidence(is)) {
+ if (!is->invalid && _compareAndReglue(vertex, &(*it), &(*is), deloop)) {
+ // Swap the incidences, effectively implementing "bubble sort".
+ std::swap(*it, *is);
+ }
+ if (is == last) {
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * \brief Compare a pair of edges emanating from the same vertex in the same direction.
+ *
+ * If the edges overlap in part or in full, they get reglued, which means that the topology
+ * of the graph may get modified. Otherwise, if the detailed comparison shows that the edges
+ * aren't correctly ordered around the vertex (because the second edge deviates to the right
+ * instead of to the left of the first, when looking away from the vertex), then the function
+ * will return true, signalling that the incidences should be swapped.
+ *
+ * \param vertex The vertex where the mutually tangent paths meet.
+ * \param first The incidence appearing as the first one in the provisional cyclic order.
+ * \param second The incidence appearing as the second one in the provisional cyclic order.
+ * \param deloop Whether to detach collapsed loops (backtracks) which don't enclose any area.
+ * \return Whether the incidences should be swapped.
+ */
+template<typename EL>
+bool PlanarGraph<EL>::_compareAndReglue(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::Incidence *first,
+ typename PlanarGraph<EL>::Incidence *second, bool deloop)
+{
+ if (first->index == second->index) {
+ return _reglueTeardrop(vertex, first, second, deloop);
+ }
+
+ // Get paths corresponding to the edges but travelling away from the vertex.
+ auto first_path_out = getOutgoingPath(first);
+ auto second_path_out = getOutgoingPath(second);
+ auto split = parting_point(first_path_out, second_path_out, _precision);
+
+ if (are_near(split.point(), vertex.point(), _precision)) {
+ // Paths deviate immediately, so no gluing is needed. The incidences should
+ // be swapped if the first edge path departs to the left of the second one.
+ return deviatesLeft(first_path_out, second_path_out);
+ }
+
+ // Determine the nature of the initial overlap between the paths.
+ bool const till_end_of_1st = are_near(split.point(), first_path_out.finalPoint(), _precision);
+ bool const till_end_of_2nd = are_near(split.point(), second_path_out.finalPoint(), _precision);
+
+ if (till_end_of_1st && till_end_of_2nd) { // Paths coincide completely.
+ _mergeCoincidingEdges(first, second);
+ } else if (till_end_of_1st) {
+ // Paths coincide until the end of the 1st one, which however isn't the end of the
+ // 2nd one; for example, the first one could be the vertical riser of the letter L
+ // whereas the second one – the entire letter stroke.
+ _mergeShorterLonger(vertex, first, second, split.second);
+ } else if (till_end_of_2nd) {
+ // The same but with with the second edge shorter than the first one.
+ _mergeShorterLonger(vertex, second, first, split.first);
+ } else { // A Y-shaped split.
+ _mergeWyeConfiguration(vertex, first, second, split);
+ }
+ return false; // We've glued so no need to swap anything.
+}
+
+/**
+ * \brief Analyze a loop path a with self-tangency at the attachment point (a teardrop).
+ *
+ * The following steps are taken:
+ * \li If the loop encloses zero area and \c deloop is true, the loop is detached.
+ * \li If the two arms of the loop split out immediately, the loop is left alone and we
+ * only check whether the incidences should be swapped.
+ * \li If the loop overlaps itself near the vertex, resembling a lasso, we split it into
+ * a shorter simple path and a smaller loop attached to the end of the shorter path.
+ *
+ * \param vertex The vertex at which the teardrop originates.
+ * \param first The first incidence of the loop to the vertex.
+ * \param second The second incidence of the loop to the vertex.
+ * \param deloop Whether the loop should be removed if it doesn't enclose any area
+ * (i.e., the path exactly backtracks on itself).
+ * \return Whether the two incidences of the loop to the vertex should be swapped.
+ */
+template<typename EL>
+bool PlanarGraph<EL>::_reglueTeardrop(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::Incidence *first,
+ typename PlanarGraph<EL>::Incidence *second, bool deloop)
+{
+ // Calculate the area enclosed by the teardrop.
+ // The convention is that the unit circle (cos(t), sint(t)), t from 0 to 2pi,
+ // encloses an area of +pi.
+ auto &edge = _edges[first->index];
+ Path loop = edge.path; loop.close();
+ double signed_area = closedPathArea(loop);
+
+ if (deloop && are_near(signed_area, 0.0, _precision)) {
+ edge.detach();
+ _throwAway(&vertex, first);
+ _throwAway(&vertex, second);
+ return false;
+ }
+
+ auto split = parting_point(loop, loop.reversed(), _precision);
+ if (are_near(split.point(), vertex.point(), _precision)) {
+ // The loop spreads out immediately. We simply check if the incidences should be swapped.
+ // We want them to be ordered such that the signed area encircled by the path going out
+ // at the first incidence and coming back at the second (with this orientation) is > 0.
+ return (first->sign == Incidence::START) ^ (signed_area > 0.0);
+ }
+
+ // The loop encloses a nonzero area, but the two branches don't separate at the starting
+ // point. Instead, they travel together for a while before they split like a lasso.
+ _reglueLasso(vertex, first, second, split);
+ return false;
+}
+
+/**
+ * \brief Reglue a lasso-shaped loop, separating it into the "free rope" and the "hoop".
+ *
+ * The lasso is an edge looping back to the same vertex, where the closed path encloses
+ * a non-zero area, but its two branches don't separate at the starting point. Instead,
+ * they travel together for a while (forming the doubled-up "free rope") before they
+ * split like a lasso. This function cuts the lasso at the split point:
+ * \code{.unparsed}
+ * ____ ____
+ * / \ / \
+ * VERTEX =====< | ==> VERTEX ------ NEW + NEW < |
+ * \____/ (lasso) (rope) \____/ (hoop)
+ *
+ * \endcode
+ *
+ * \param vertex A reference to the vertex where the lasso is attached.
+ * \param first The first incidence of the lasso to the vertex.
+ * \param second The second incidence of the lasso to the vertex.
+ * \param split The point where the free rope of the lasso ends and the hoop begins.
+ */
+template<typename EL>
+void PlanarGraph<EL>::_reglueLasso(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::Incidence *first,
+ typename PlanarGraph<EL>::Incidence *second,
+ PathIntersection const &split)
+{
+ unsigned lasso = first->index;
+
+ // Create the "free rope" path.
+ auto rope = _edges[lasso].path.portion(PATH_START, split.first);
+ rope.setInitial(vertex.point());
+ rope.setFinal(split.point());
+ double const rope_final_backward_azimuth = _getAzimuth(-rope.finalUnitTangent());
+
+ // Compute the new label of the rope edge.
+ auto oriented_as_loop = _edges[lasso].label;
+ auto reversed = oriented_as_loop; reversed.onReverse();
+ oriented_as_loop.onMergeWith(reversed);
+
+ // Insert the rope and its endpoint.
+ unsigned const rope_index = _edges.size();
+ auto &rope_edge = _edges.emplace_back(std::move(rope), std::move(oriented_as_loop));
+ auto const new_split_vertex = _ensureVertexAt(split.point());
+
+ // Reuse lasso's first incidence as the incidence to the rope (azimuth can stay).
+ first->index = rope_index;
+ first->sign = Incidence::START;
+
+ // Connect the rope to the newly created split vertex.
+ new_split_vertex->_addIncidence(rope_index, rope_final_backward_azimuth, Incidence::END);
+ rope_edge.start = &vertex;
+ rope_edge.end = new_split_vertex;
+
+ // Insert the hoop
+ auto hoop = _edges[lasso].path.portion(split.first,
+ _reversePathTime(split.second, _edges[lasso].path));
+ hoop.setInitial(split.point());
+ hoop.setFinal(split.point());
+ insertEdge(std::move(hoop), EL(_edges[lasso].label));
+
+ // Detach the original lasso edge and mark the second incidence for cleanup.
+ _edges[lasso].detach();
+ _throwAway(&vertex, second);
+}
+
+/**
+ * \brief Completely coallesce two fully overlapping edges.
+ *
+ * In practice, the first edge stays and the second one gets detached from the graph.
+ *
+ * \param first An iterator to the first edge's incidence to a common vertex.
+ * \param second An iterator to the second edge's incidence to a common vertex.
+ */
+template<typename EL>
+void PlanarGraph<EL>::_mergeCoincidingEdges(typename PlanarGraph<EL>::Incidence *first,
+ typename PlanarGraph<EL>::Incidence *second)
+{
+ auto &surviver = _edges[first->index];
+ auto &casualty = _edges[second->index];
+
+ auto other_label = casualty.label;
+ if (first->sign != second->sign) { // Logically reverse the label before merging.
+ other_label.onReverse();
+ }
+ surviver.label.onMergeWith(other_label);
+
+ // Mark both incidences of the second edge as junk and detach it.
+ auto [start_vertex, start_inc] = getIncidence(second->index, Incidence::START);
+ _throwAway(start_vertex, start_inc);
+ auto [end_vertex, end_inc] = getIncidence(second->index, Incidence::END);
+ _throwAway(end_vertex, end_inc);
+ casualty.detach();
+}
+
+/**
+ * \brief Merge a longer edge with a shorter edge that overlaps it.
+ *
+ * In practice, the shorter edge remains unchanged and the longer one is trimmed to
+ * become just the part extending past the shorter one.
+ *
+ * \param vertex The vertex where the overlap starts.
+ * \param shorter The incidence of the shorter edge to the common vertex.
+ * \param longer The incidence of the longer edge to the common vertex.
+ * \param time_on_longer The PathTime on the longer edge at which it passes through
+ * the endpoint of the shorter edge.
+ */
+template<typename EL>
+void PlanarGraph<EL>::_mergeShorterLonger(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::Incidence *shorter,
+ typename PlanarGraph<EL>::Incidence *longer,
+ PathTime const &time_on_longer)
+{
+ auto &shorter_edge = _edges[shorter->index];
+ auto &longer_edge = _edges[longer->index];
+
+ // Get the vertices at the far ends of both edges.
+ auto shorter_far_end = (shorter->sign == Incidence::START) ? shorter_edge.end
+ : shorter_edge.start;
+ /// Whether the longer edge heads out of the vertex.
+ bool const longer_out = (longer->sign == Incidence::START);
+ auto longer_far_end = longer_out ? longer_edge.end : longer_edge.start;
+
+ // Copy the longer edge's label and merge with that of the shorter.
+ auto longer_label = longer_edge.label;
+ if (shorter->sign != longer->sign) {
+ longer_label.onReverse();
+ }
+ shorter_edge.label.onMergeWith(longer_label);
+
+ // Create the trimmed path (longer minus shorter).
+ Path trimmed;
+ double trimmed_departure_azimuth;
+ if (longer_out) {
+ trimmed = longer_edge.path.portion(time_on_longer, _pathEnd(longer_edge.path));
+ longer_edge.start = shorter_far_end;
+ trimmed.setInitial(shorter_far_end->point());
+ trimmed.setFinal(longer_far_end->point());
+ trimmed_departure_azimuth = _getAzimuth(trimmed.initialUnitTangent());
+ } else {
+ trimmed = longer_edge.path.portion(PATH_START, _reversePathTime(time_on_longer,
+ longer_edge.path));
+ longer_edge.end = shorter_far_end;
+ trimmed.setInitial(longer_far_end->point());
+ trimmed.setFinal(shorter_far_end->point());
+ trimmed_departure_azimuth = _getAzimuth(-trimmed.finalUnitTangent());
+ }
+
+ // Set the trimmed path as the new path of the longer edge and set up the incidences:
+ longer_edge.path = std::move(trimmed);
+ shorter_far_end->_addIncidence(longer->index, trimmed_departure_azimuth, longer->sign);
+
+ // Throw away the old start incidence of the longer edge.
+ _throwAway(&vertex, longer);
+}
+
+/**
+ * \brief Merge a pair of partially overlapping edges, producing a Y-split at a new vertex.
+ *
+ * This topological modification is performed by inserting a new vertex at the three-way
+ * point (where the two paths separate) and clipping the original edges to that point.
+ * In this way, the original edges become the "arms" of the Y-shape. In addition, a new
+ * edge is inserted, forming the "stem" of the Y.
+ *
+ * \param vertex The vertex from which the partially overlapping edges originate (bottom of Y).
+ * \param first The incidence to the first edge (whose path is the stem and one arm of the Y).
+ * \param second The incidence to the second edge (stem and the other arm of the Y).
+ * \param fork The splitting point of the two paths.
+ */
+template<typename EL>
+void PlanarGraph<EL>::_mergeWyeConfiguration(typename PlanarGraph<EL>::Vertex &vertex,
+ typename PlanarGraph<EL>::Incidence *first,
+ typename PlanarGraph<EL>::Incidence *second,
+ PathIntersection const &fork)
+{
+ bool const first_is_out = (first->sign == Incidence::START);
+ bool const second_is_out = (second->sign == Incidence::START);
+
+ auto &first_edge = _edges[first->index];
+ auto &second_edge = _edges[second->index];
+
+ // Calculate the path forming the stem of the Y:
+ auto stem_path = getOutgoingPath(first).portion(PATH_START, fork.first);
+ stem_path.setInitial(vertex.point());
+ stem_path.setFinal(fork.point());
+
+ /// A closure to clip the path of an original edge to the fork point.
+ auto const clip_to_fork = [&](PathTime const &t, Edge &e, bool out) {
+ if (out) { // Trim from time to end
+ e.path = e.path.portion(t, _pathEnd(e.path));
+ e.path.setInitial(fork.point());
+ } else { // Trim from reverse-end to reverse-time
+ e.path = e.path.portion(PATH_START, _reversePathTime(t, e.path));
+ e.path.setFinal(fork.point());
+ }
+ };
+
+ /// A closure to find the departing azimuth of an edge at the fork point.
+ auto const departing_azimuth = [&](Edge const &e, bool out) -> double {
+ return _getAzimuth((out) ? e.path.initialUnitTangent()
+ : -e.path.finalUnitTangent());
+ };
+
+ // Clip the paths obtaining the arms of the Y.
+ clip_to_fork(fork.first, first_edge, first_is_out);
+ clip_to_fork(fork.second, second_edge, second_is_out);
+
+ // Create the fork vertex and set up its incidences.
+ auto const fork_vertex = _ensureVertexAt(fork.point());
+ fork_vertex->_addIncidence(first->index, departing_azimuth(first_edge, first_is_out),
+ first->sign);
+ fork_vertex->_addIncidence(second->index, departing_azimuth(second_edge, second_is_out),
+ second->sign);
+
+ // Repoint the ends of the edges that were clipped
+ (first_is_out ? first_edge.start : first_edge.end) = fork_vertex;
+ (second_is_out ? second_edge.start : second_edge.end) = fork_vertex;
+
+ /// A closure to get a consistently oriented label of an edge.
+ auto upwards_oriented_label = [&](Edge const &e, bool out) -> EL {
+ auto label = e.label;
+ if (!out) {
+ label.onReverse();
+ }
+ return label;
+ };
+
+ auto stem_label = upwards_oriented_label(first_edge, first_is_out);
+ stem_label.onMergeWith(upwards_oriented_label(second_edge, second_is_out));
+ auto stem_departure_from_fork = _getAzimuth(-stem_path.finalUnitTangent());
+
+ // Insert the stem of the Y-configuration.
+ unsigned const stem_index = _edges.size();
+ auto &stem_edge = _edges.emplace_back(std::move(stem_path), std::move(stem_label));
+ stem_edge.start = &vertex;
+ stem_edge.end = fork_vertex;
+
+ // Set up the incidences.
+ fork_vertex->_addIncidence(stem_index, stem_departure_from_fork, Incidence::END);
+ first->index = stem_index;
+ first->sign = Incidence::START;
+ _throwAway(&vertex, second);
+}
+
+template<typename EL>
+typename PlanarGraph<EL>::Incidence*
+PlanarGraph<EL>::nextIncidence(typename PlanarGraph<EL>::VertexIterator const &vertex,
+ double azimuth, bool clockwise) const
+{
+ auto &incidences = vertex._incidences;
+ Incidence *result = nullptr;
+
+ if (incidences.empty()) {
+ return result;
+ }
+ // Normalize azimuth to the interval [-pi; pi].
+ auto angle = Angle(azimuth);
+
+ if (clockwise) { // Go backwards and find a lower bound
+ auto it = std::find_if(incidences.rbegin(), incidences.rend(), [=](auto inc) -> bool {
+ return inc.azimuth <= angle;
+ });
+ if (it == incidences.rend()) {
+ // azimuth is lower than the azimuths of all incidences;
+ // going clockwise we wrap back to the highest azimuth (last incidence).
+ return &incidences.back();
+ }
+ result = &(*it);
+ } else {
+ auto it = std::find_if(incidences.begin(), incidences.end, [=](auto inc) -> bool {
+ return inc.azimuth >= angle;
+ });
+ if (it == incidences.end()) {
+ // azimuth is higher than the azimuths of all incidences;
+ // going counterclockwise we wrap back to the lowest azimuth.
+ return &incidences.front();
+ }
+ result = &(*it);
+ }
+ return result;
+}
+
+/** Return the signed area enclosed by a closed path. */
+template<typename EL>
+double PlanarGraph<EL>::closedPathArea(Path const &path)
+{
+ double area;
+ Point _;
+ centroid(path.toPwSb(), _, area);
+ return -area; // Our convention is that the Y-axis points up
+}
+
+/** \brief Determine whether the first path deviates to the left of the second.
+ *
+ * The two paths are assumed to have identical or nearly identical starting points
+ * but not an overlapping initial portion. The concept of "left" is based on the
+ * y-axis pointing up.
+ *
+ * \param first The first path.
+ * \param second The second path.
+ *
+ * \return True if the first path deviates towards the left of the second;
+ * False if the first path deviates towards the right of the second.
+ */
+template<typename EL>
+bool PlanarGraph<EL>::deviatesLeft(Path const &first, Path const &second)
+{
+ auto start = middle_point(first.initialPoint(), second.initialPoint());
+ auto tangent_between = middle_point(first.initialUnitTangent(), second.initialUnitTangent());
+ if (tangent_between.isZero()) {
+ return false;
+ }
+ auto tangent_line = Line::from_origin_and_vector(start, tangent_between);
+
+ // Find the first non-degenerate curves on both paths
+ std::unique_ptr<Curve> c[2];
+ auto const find_first_nondegen = [](std::unique_ptr<Curve> &pointer, Path const &path) {
+ for (auto const &c : path) {
+ if (!c.isDegenerate()) {
+ pointer.reset(c.duplicate());
+ return;
+ }
+ }
+ };
+
+ find_first_nondegen(c[0], first);
+ find_first_nondegen(c[1], second);
+ if (!c[0] || !c[1]) {
+ return false;
+ }
+
+ // Find the bounding boxes
+ Rect const bounding_boxes[] {
+ c[0]->boundsExact(),
+ c[1]->boundsExact()
+ };
+
+ // For a bounding box, find the corner that goes the furthest in the direction of the
+ // tangent vector.
+ auto const furthest_corner = [&](Rect const &r) -> unsigned {
+ Coord max_dot = dot(r.corner(0) - start, tangent_between);
+ unsigned result = 0;
+ for (unsigned i = 1; i < 4; i++) {
+ auto current_dot = dot(r.corner(i), tangent_between);
+ if (current_dot > max_dot) {
+ max_dot = current_dot;
+ result = i;
+ } else if (current_dot == max_dot) {
+ // Disambiguate based on proximity to the tangent line.
+ auto const offset = start + tangent_between;
+ if (distance(offset, r.corner(i)) < distance(offset, r.corner(result))) {
+ result = i;
+ }
+ }
+ }
+ return result;
+ };
+
+ // Calculate the corner points overlooking the "rift" between the paths.
+ Point corner_points[2];
+ for (size_t i : {0, 1}) {
+ corner_points[i] = bounding_boxes[i].corner(furthest_corner(bounding_boxes[i]));
+ }
+
+ // Find a vantage point from which we can best observe the splitting paths.
+ Point vantage_point;
+ bool found = false;
+ if (corner_points[0] != corner_points[1]) {
+ auto line_connecting_corners = Line(corner_points[0], corner_points[1]);
+ auto xing = line_connecting_corners.intersect(tangent_line);
+ if (!xing.empty()) {
+ vantage_point = xing[0].point();
+ found = true;
+ }
+ }
+ if (!found) {
+ vantage_point = tangent_line.pointAt(tangent_line.timeAtProjection(corner_points[0]));
+ }
+
+ // Move to twice as far in the direction of the vantage point.
+ vantage_point += vantage_point - start;
+
+ // Find the points on both curves that are nearest to the vantage point.
+ Coord nearest[2];
+ for (size_t i : {0, 1}) {
+ nearest[i] = c[i]->nearestTime(vantage_point);
+ }
+
+ // Clip to the nearest points and examine the closed contour.
+ Path closed_contour(start);
+ closed_contour.setStitching(true);
+ closed_contour.append(c[0]->portion(0, nearest[0]));
+ closed_contour = closed_contour.reversed();
+ closed_contour.setStitching(true);
+ closed_contour.append(c[1]->portion(0, nearest[1]));
+ closed_contour.close();
+ return !path_direction(closed_contour); // Reverse to match the convention that y-axis is up.
+}
+
+} // namespace Geom
+
+#endif // LIB2GEOM_SEEN_PLANAR_GRAPH_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/src/2geom/point.cpp b/src/2geom/point.cpp
new file mode 100644
index 0000000..cbe53c4
--- /dev/null
+++ b/src/2geom/point.cpp
@@ -0,0 +1,274 @@
+/**
+ * \file
+ * \brief Cartesian point / 2D vector and related operations
+ *//*
+ * Authors:
+ * Michael G. Sloan <mgsloan@gmail.com>
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2006-2009 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 <assert.h>
+#include <math.h>
+#include <2geom/angle.h>
+#include <2geom/coord.h>
+#include <2geom/point.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+/**
+ * @class Point
+ * @brief Two-dimensional point that doubles as a vector.
+ *
+ * Points in 2Geom are represented in Cartesian coordinates, e.g. as a pair of numbers
+ * that store the X and Y coordinates. Each point is also a vector in \f$\mathbb{R}^2\f$
+ * from the origin (point at 0,0) to the stored coordinates,
+ * and has methods implementing several vector operations (like length()).
+ *
+ * @section OpNotePoint Operator note
+ *
+ * Most operators are provided by Boost operator helpers, so they are not visible in this class.
+ * If @a p, @a q, @a r denote points, @a s a floating-point scalar, and @a m a transformation matrix,
+ * then the following operations are available:
+ * @code
+ p += q; p -= q; r = p + q; r = p - q;
+ p *= s; p /= s; q = p * s; q = s * p; q = p / s;
+ p *= m; q = p * m; q = m * p;
+ @endcode
+ * It is possible to left-multiply a point by a matrix, even though mathematically speaking
+ * this is undefined. The result is a point identical to that obtained by right-multiplying.
+ *
+ * @ingroup Primitives */
+
+Point Point::polar(Coord angle) {
+ Point ret;
+ Coord remainder = Angle(angle).radians0();
+ if (are_near(remainder, 0) || are_near(remainder, 2*M_PI)) {
+ ret[X] = 1;
+ ret[Y] = 0;
+ } else if (are_near(remainder, M_PI/2)) {
+ ret[X] = 0;
+ ret[Y] = 1;
+ } else if (are_near(remainder, M_PI)) {
+ ret[X] = -1;
+ ret[Y] = 0;
+ } else if (are_near(remainder, 3*M_PI/2)) {
+ ret[X] = 0;
+ ret[Y] = -1;
+ } else {
+ sincos(angle, ret[Y], ret[X]);
+ }
+ return ret;
+}
+
+/** @brief Normalize the vector representing the point.
+ * After this method returns, the length of the vector will be 1 (unless both coordinates are
+ * zero - the zero point will be returned then). The function tries to handle infinite
+ * coordinates gracefully. If any of the coordinates are NaN, the function will do nothing.
+ * @post \f$-\epsilon < \left|this\right| - 1 < \epsilon\f$
+ * @see unit_vector(Geom::Point const &) */
+void Point::normalize() {
+ double len = hypot(_pt[0], _pt[1]);
+ if(len == 0) return;
+ if(std::isnan(len)) return;
+ static double const inf = HUGE_VAL;
+ if(len != inf) {
+ *this /= len;
+ } else {
+ unsigned n_inf_coords = 0;
+ /* Delay updating pt in case neither coord is infinite. */
+ Point tmp;
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ if ( _pt[i] == inf ) {
+ ++n_inf_coords;
+ tmp[i] = 1.0;
+ } else if ( _pt[i] == -inf ) {
+ ++n_inf_coords;
+ tmp[i] = -1.0;
+ } else {
+ tmp[i] = 0.0;
+ }
+ }
+ switch (n_inf_coords) {
+ case 0: {
+ /* Can happen if both coords are near +/-DBL_MAX. */
+ *this /= 4.0;
+ len = hypot(_pt[0], _pt[1]);
+ assert(len != inf);
+ *this /= len;
+ break;
+ }
+ case 1: {
+ *this = tmp;
+ break;
+ }
+ case 2: {
+ *this = tmp * sqrt(0.5);
+ break;
+ }
+ }
+ }
+}
+
+/** @brief Compute the first norm (Manhattan distance) of @a p.
+ * This is equal to the sum of absolutes values of the coordinates.
+ * @return \f$|p_X| + |p_Y|\f$
+ * @relates Point */
+Coord L1(Point const &p) {
+ Coord d = 0;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ d += fabs(p[i]);
+ }
+ return d;
+}
+
+/** @brief Compute the infinity norm (maximum norm) of @a p.
+ * @return \f$\max(|p_X|, |p_Y|)\f$
+ * @relates Point */
+Coord LInfty(Point const &p) {
+ Coord const a(fabs(p[0]));
+ Coord const b(fabs(p[1]));
+ return ( a < b || std::isnan(b)
+ ? b
+ : a );
+}
+
+/** @brief True if the point has both coordinates zero.
+ * NaNs are treated as not equal to zero.
+ * @relates Point */
+bool is_zero(Point const &p) {
+ return ( p[0] == 0 &&
+ p[1] == 0 );
+}
+
+/** @brief True if the point has a length near 1. The are_near() function is used.
+ * @relates Point */
+bool is_unit_vector(Point const &p, Coord eps) {
+ return are_near(L2(p), 1.0, eps);
+}
+/** @brief Return the angle between the point and the +X axis.
+ * @return Angle in \f$(-\pi, \pi]\f$.
+ * @relates Point */
+Coord atan2(Point const &p) {
+ return std::atan2(p[Y], p[X]);
+}
+
+/** @brief Compute the angle between a and b relative to the origin.
+ * The computation is done by projecting b onto the basis defined by a, rot90(a).
+ * @return Angle in \f$(-\pi, \pi]\f$.
+ * @relates Point */
+Coord angle_between(Point const &a, Point const &b) {
+ return std::atan2(cross(a,b), dot(a,b));
+}
+
+/** @brief Create a normalized version of a point.
+ * This is equivalent to copying the point and calling its normalize() method.
+ * The returned point will be (0,0) if the argument has both coordinates equal to zero.
+ * If any coordinate is NaN, this function will do nothing.
+ * @param a Input point
+ * @return Point on the unit circle in the same direction from origin as a, or the origin
+ * if a has both coordinates equal to zero
+ * @relates Point */
+Point unit_vector(Point const &a)
+{
+ Point ret(a);
+ ret.normalize();
+ return ret;
+}
+/** @brief Return the "absolute value" of the point's vector.
+ * This is defined in terms of the default lexicographical ordering. If the point is "larger"
+ * that the origin (0, 0), its negation is returned. You can check whether
+ * the points' vectors have the same direction (e.g. lie
+ * on the same line passing through the origin) using
+ * @code abs(a).normalize() == abs(b).normalize() @endcode
+ * To check with some margin of error, use
+ * @code are_near(abs(a).normalize(), abs(b).normalize()) @endcode
+ * Although naively this should take the absolute value of each coordinate, such an operation
+ * is not very useful.
+ * @relates Point */
+Point abs(Point const &b)
+{
+ Point ret;
+ if (b[Y] < 0.0) {
+ ret = -b;
+ } else if (b[Y] == 0.0) {
+ ret = b[X] < 0.0 ? -b : b;
+ } else {
+ ret = b;
+ }
+ return ret;
+}
+
+/** @brief Transform the point by the specified matrix. */
+Point &Point::operator*=(Affine const &m) {
+ double x = _pt[X], y = _pt[Y];
+ for(int i = 0; i < 2; i++) {
+ _pt[i] = x * m[i] + y * m[i + 2] + m[i + 4];
+ }
+ return *this;
+}
+
+/** @brief Snap the angle B - A - dir to multiples of \f$2\pi/n\f$.
+ * The 'dir' argument must be normalized (have unit length), otherwise the result
+ * is undefined.
+ * @return Point with the same distance from A as B, with a snapped angle.
+ * @post distance(A, B) == distance(A, result)
+ * @post angle_between(result - A, dir) == \f$2k\pi/n, k \in \mathbb{N}\f$
+ * @relates Point */
+Point constrain_angle(Point const &A, Point const &B, unsigned int n, Point const &dir)
+{
+ // for special cases we could perhaps use explicit testing (which might be faster)
+ if (n == 0.0) {
+ return B;
+ }
+ Point diff(B - A);
+ double angle = -angle_between(diff, dir);
+ double k = round(angle * (double)n / (2.0*M_PI));
+ return A + dir * Rotate(k * 2.0 * M_PI / (double)n) * L2(diff);
+}
+
+std::ostream &operator<<(std::ostream &out, const Geom::Point &p)
+{
+ out << "(" << format_coord_nice(p[X]) << ", "
+ << format_coord_nice(p[Y]) << ")";
+ return out;
+}
+
+} // 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/src/2geom/polynomial.cpp b/src/2geom/polynomial.cpp
new file mode 100644
index 0000000..9737bd0
--- /dev/null
+++ b/src/2geom/polynomial.cpp
@@ -0,0 +1,337 @@
+/**
+ * \file
+ * \brief Polynomial in canonical (monomial) basis
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2007-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 <algorithm>
+#include <2geom/polynomial.h>
+#include <2geom/math-utils.h>
+#include <math.h>
+
+#ifdef HAVE_GSL
+#include <gsl/gsl_poly.h>
+#endif
+
+namespace Geom {
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+Poly Poly::operator*(const Poly& p) const {
+ Poly result;
+ result.resize(degree() + p.degree()+1);
+
+ for(unsigned i = 0; i < size(); i++) {
+ for(unsigned j = 0; j < p.size(); j++) {
+ result[i+j] += (*this)[i] * p[j];
+ }
+ }
+ return result;
+}
+
+/*double Poly::eval(double x) const {
+ return gsl_poly_eval(&coeff[0], size(), x);
+ }*/
+
+void Poly::normalize() {
+ while(back() == 0)
+ pop_back();
+}
+
+void Poly::monicify() {
+ normalize();
+
+ double scale = 1./back(); // unitize
+
+ for(unsigned i = 0; i < size(); i++) {
+ (*this)[i] *= scale;
+ }
+}
+
+
+#ifdef HAVE_GSL
+std::vector<std::complex<double> > solve(Poly const & pp) {
+ Poly p(pp);
+ p.normalize();
+ gsl_poly_complex_workspace * w
+ = gsl_poly_complex_workspace_alloc (p.size());
+
+ gsl_complex_packed_ptr z = new double[p.degree()*2];
+ double* a = new double[p.size()];
+ for(unsigned int i = 0; i < p.size(); i++)
+ a[i] = p[i];
+ std::vector<std::complex<double> > roots;
+ //roots.resize(p.degree());
+
+ gsl_poly_complex_solve (a, p.size(), w, z);
+ delete[]a;
+
+ gsl_poly_complex_workspace_free (w);
+
+ for (unsigned int i = 0; i < p.degree(); i++) {
+ roots.emplace_back(z[2*i] ,z[2*i+1]);
+ //printf ("z%d = %+.18f %+.18f\n", i, z[2*i], z[2*i+1]);
+ }
+ delete[] z;
+ return roots;
+}
+
+std::vector<double > solve_reals(Poly const & p) {
+ std::vector<std::complex<double> > roots = solve(p);
+ std::vector<double> real_roots;
+
+ for(auto & root : roots) {
+ if(root.imag() == 0) // should be more lenient perhaps
+ real_roots.push_back(root.real());
+ }
+ return real_roots;
+}
+#endif
+
+double polish_root(Poly const & p, double guess, double tol) {
+ Poly dp = derivative(p);
+
+ double fn = p(guess);
+ while(fabs(fn) > tol) {
+ guess -= fn/dp(guess);
+ fn = p(guess);
+ }
+ return guess;
+}
+
+Poly integral(Poly const & p) {
+ Poly result;
+
+ result.reserve(p.size()+1);
+ result.push_back(0); // arbitrary const
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(p[i]/(i+1));
+ }
+ return result;
+
+}
+
+Poly derivative(Poly const & p) {
+ Poly result;
+
+ if(p.size() <= 1)
+ return Poly(0);
+ result.reserve(p.size()-1);
+ for(unsigned i = 1; i < p.size(); i++) {
+ result.push_back(i*p[i]);
+ }
+ return result;
+}
+
+Poly compose(Poly const & a, Poly const & b) {
+ Poly result;
+
+ for(unsigned i = a.size(); i > 0; i--) {
+ result = Poly(a[i-1]) + result * b;
+ }
+ return result;
+
+}
+
+/* This version is backwards - dividing taylor terms
+Poly divide(Poly const &a, Poly const &b, Poly &r) {
+ Poly c;
+ r = a; // remainder
+
+ const unsigned k = a.size();
+ r.resize(k, 0);
+ c.resize(k, 0);
+
+ for(unsigned i = 0; i < k; i++) {
+ double ci = r[i]/b[0];
+ c[i] += ci;
+ Poly bb = ci*b;
+ std::cout << ci <<"*" << b << ", r= " << r << std::endl;
+ r -= bb.shifted(i);
+ }
+
+ return c;
+}
+*/
+
+Poly divide(Poly const &a, Poly const &b, Poly &r) {
+ Poly c;
+ r = a; // remainder
+ assert(b.size() > 0);
+
+ const unsigned k = a.degree();
+ const unsigned l = b.degree();
+ c.resize(k, 0.);
+
+ for(unsigned i = k; i >= l; i--) {
+ //assert(i >= 0);
+ double ci = r.back()/b.back();
+ c[i-l] += ci;
+ Poly bb = ci*b;
+ //std::cout << ci <<"*(" << b.shifted(i-l) << ") = "
+ // << bb.shifted(i-l) << " r= " << r << std::endl;
+ r -= bb.shifted(i-l);
+ r.pop_back();
+ }
+ //std::cout << "r= " << r << std::endl;
+ r.normalize();
+ c.normalize();
+
+ return c;
+}
+
+Poly gcd(Poly const &a, Poly const &b, const double /*tol*/) {
+ if(a.size() < b.size())
+ return gcd(b, a);
+ if(b.size() <= 0)
+ return a;
+ if(b.size() == 1)
+ return a;
+ Poly r;
+ divide(a, b, r);
+ return gcd(b, r);
+}
+
+
+
+
+std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c)
+{
+ std::vector<Coord> result;
+
+ if (a == 0) {
+ // linear equation
+ if (b == 0) return result;
+ result.push_back(-c/b);
+ return result;
+ }
+
+ Coord delta = b*b - 4*a*c;
+
+ if (delta == 0) {
+ // one root
+ result.push_back(-b / (2*a));
+ } else if (delta > 0) {
+ // two roots
+ Coord delta_sqrt = sqrt(delta);
+
+ // Use different formulas depending on sign of b to preserve
+ // numerical stability. See e.g.:
+ // http://people.csail.mit.edu/bkph/articles/Quadratics.pdf
+ int sign = b >= 0 ? 1 : -1;
+ Coord t = -0.5 * (b + sign * delta_sqrt);
+ result.push_back(t / a);
+ result.push_back(c / t);
+ }
+ // no roots otherwise
+
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+
+std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d)
+{
+ // based on:
+ // http://mathworld.wolfram.com/CubicFormula.html
+
+ if (a == 0) {
+ return solve_quadratic(b, c, d);
+ }
+ if (d == 0) {
+ // divide by x
+ std::vector<Coord> result = solve_quadratic(a, b, c);
+ result.push_back(0);
+ std::sort(result.begin(), result.end());
+ return result;
+ }
+
+ std::vector<Coord> result;
+
+ // 1. divide everything by a to bring to canonical form
+ b /= a;
+ c /= a;
+ d /= a;
+
+ // 2. eliminate x^2 term: x^3 + 3Qx - 2R = 0
+ Coord Q = (3*c - b*b) / 9;
+ Coord R = (-27 * d + b * (9*c - 2*b*b)) / 54;
+
+ // 3. compute polynomial discriminant
+ Coord D = Q*Q*Q + R*R;
+ Coord term1 = b/3;
+
+ if (D > 0) {
+ // only one real root
+ Coord S = cbrt(R + sqrt(D));
+ Coord T = cbrt(R - sqrt(D));
+ result.push_back(-b/3 + S + T);
+ } else if (D == 0) {
+ // 3 real roots, 2 of which are equal
+ Coord rroot = cbrt(R);
+ result.reserve(3);
+ result.push_back(-term1 + 2*rroot);
+ result.push_back(-term1 - rroot);
+ result.push_back(-term1 - rroot);
+ } else {
+ // 3 distinct real roots
+ assert(Q < 0);
+ Coord theta = acos(R / sqrt(-Q*Q*Q));
+ Coord rroot = 2 * sqrt(-Q);
+ result.reserve(3);
+ result.push_back(-term1 + rroot * cos(theta / 3));
+ result.push_back(-term1 + rroot * cos((theta + 2*M_PI) / 3));
+ result.push_back(-term1 + rroot * cos((theta + 4*M_PI) / 3));
+ }
+
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+
+/*Poly divide_out_root(Poly const & p, double x) {
+ assert(1);
+ }*/
+
+} //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/src/2geom/rect.cpp b/src/2geom/rect.cpp
new file mode 100644
index 0000000..60dcc87
--- /dev/null
+++ b/src/2geom/rect.cpp
@@ -0,0 +1,187 @@
+/* Axis-aligned rectangle
+ *
+ * Authors:
+ * Michael Sloan <mgsloan@gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2007-2011 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/rect.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+Point align_factors(Align g) {
+ Point p;
+ switch (g) {
+ case ALIGN_XMIN_YMIN:
+ p[X] = 0.0;
+ p[Y] = 0.0;
+ break;
+ case ALIGN_XMID_YMIN:
+ p[X] = 0.5;
+ p[Y] = 0.0;
+ break;
+ case ALIGN_XMAX_YMIN:
+ p[X] = 1.0;
+ p[Y] = 0.0;
+ break;
+ case ALIGN_XMIN_YMID:
+ p[X] = 0.0;
+ p[Y] = 0.5;
+ break;
+ case ALIGN_XMID_YMID:
+ p[X] = 0.5;
+ p[Y] = 0.5;
+ break;
+ case ALIGN_XMAX_YMID:
+ p[X] = 1.0;
+ p[Y] = 0.5;
+ break;
+ case ALIGN_XMIN_YMAX:
+ p[X] = 0.0;
+ p[Y] = 1.0;
+ break;
+ case ALIGN_XMID_YMAX:
+ p[X] = 0.5;
+ p[Y] = 1.0;
+ break;
+ case ALIGN_XMAX_YMAX:
+ p[X] = 1.0;
+ p[Y] = 1.0;
+ break;
+ default:
+ break;
+ }
+ return p;
+}
+
+
+/** @brief Transform the rectangle by an affine.
+ * The result of the transformation might not be axis-aligned. The return value
+ * of this operation will be the smallest axis-aligned rectangle containing
+ * all points of the true result. */
+Rect &Rect::operator*=(Affine const &m) {
+ Point pts[4];
+ for (unsigned i=0; i<4; ++i) pts[i] = corner(i) * m;
+ Coord minx = std::min(std::min(pts[0][X], pts[1][X]), std::min(pts[2][X], pts[3][X]));
+ Coord miny = std::min(std::min(pts[0][Y], pts[1][Y]), std::min(pts[2][Y], pts[3][Y]));
+ Coord maxx = std::max(std::max(pts[0][X], pts[1][X]), std::max(pts[2][X], pts[3][X]));
+ Coord maxy = std::max(std::max(pts[0][Y], pts[1][Y]), std::max(pts[2][Y], pts[3][Y]));
+ f[X].setMin(minx); f[X].setMax(maxx);
+ f[Y].setMin(miny); f[Y].setMax(maxy);
+ return *this;
+}
+
+Affine Rect::transformTo(Rect const &viewport, Aspect const &aspect) const
+{
+ // 1. translate viewbox to origin
+ Geom::Affine total = Translate(-min());
+
+ // 2. compute scale
+ Geom::Point vdims = viewport.dimensions();
+ Geom::Point dims = dimensions();
+ Geom::Scale scale(vdims[X] / dims[X], vdims[Y] / dims[Y]);
+
+ if (aspect.align == ALIGN_NONE) {
+ // apply non-uniform scale
+ total *= scale * Translate(viewport.min());
+ } else {
+ double uscale = 0;
+ if (aspect.expansion == EXPANSION_MEET) {
+ uscale = std::min(scale[X], scale[Y]);
+ } else {
+ uscale = std::max(scale[X], scale[Y]);
+ }
+ scale = Scale(uscale);
+
+ // compute offset for align
+ Geom::Point offset = vdims - dims * scale;
+ offset *= Scale(align_factors(aspect.align));
+ total *= scale * Translate(viewport.min() + offset);
+ }
+
+ return total;
+}
+
+Coord distanceSq(Point const &p, Rect const &rect)
+{
+ double dx = 0, dy = 0;
+ if ( p[X] < rect.left() ) {
+ dx = p[X] - rect.left();
+ } else if ( p[X] > rect.right() ) {
+ dx = rect.right() - p[X];
+ }
+ if (p[Y] < rect.top() ) {
+ dy = rect.top() - p[Y];
+ } else if ( p[Y] > rect.bottom() ) {
+ dy = p[Y] - rect.bottom();
+ }
+ return dx*dx+dy*dy;
+}
+
+/** @brief Returns the smallest distance between p and rect.
+ * @relates Rect */
+Coord distance(Point const &p, Rect const &rect)
+{
+ // copy of distanceSq, because we need to use hypot()
+ double dx = 0, dy = 0;
+ if ( p[X] < rect.left() ) {
+ dx = p[X] - rect.left();
+ } else if ( p[X] > rect.right() ) {
+ dx = rect.right() - p[X];
+ }
+ if (p[Y] < rect.top() ) {
+ dy = rect.top() - p[Y];
+ } else if ( p[Y] > rect.bottom() ) {
+ dy = p[Y] - rect.bottom();
+ }
+ return hypot(dx, dy);
+}
+
+Coord distanceSq(Point const &p, OptRect const &rect)
+{
+ if (!rect) return std::numeric_limits<Coord>::max();
+ return distanceSq(p, *rect);
+}
+Coord distance(Point const &p, OptRect const &rect)
+{
+ if (!rect) return std::numeric_limits<Coord>::max();
+ return distance(p, *rect);
+}
+
+} // 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/src/2geom/recursive-bezier-intersection.cpp b/src/2geom/recursive-bezier-intersection.cpp
new file mode 100644
index 0000000..20b07d9
--- /dev/null
+++ b/src/2geom/recursive-bezier-intersection.cpp
@@ -0,0 +1,476 @@
+
+
+
+#include <2geom/basic-intersection.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/exception.h>
+
+
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_multiroots.h>
+
+
+unsigned intersect_steps = 0;
+
+using std::vector;
+
+namespace Geom {
+
+class OldBezier {
+public:
+ std::vector<Geom::Point> p;
+ OldBezier() {
+ }
+ void split(double t, OldBezier &a, OldBezier &b) const;
+ Point operator()(double t) const;
+
+ ~OldBezier() {}
+
+ void bounds(double &minax, double &maxax,
+ double &minay, double &maxay) {
+ // Compute bounding box for a
+ minax = p[0][X]; // These are the most likely to be extremal
+ maxax = p.back()[X];
+ if( minax > maxax )
+ std::swap(minax, maxax);
+ for(unsigned i = 1; i < p.size()-1; i++) {
+ if( p[i][X] < minax )
+ minax = p[i][X];
+ else if( p[i][X] > maxax )
+ maxax = p[i][X];
+ }
+
+ minay = p[0][Y]; // These are the most likely to be extremal
+ maxay = p.back()[Y];
+ if( minay > maxay )
+ std::swap(minay, maxay);
+ for(unsigned i = 1; i < p.size()-1; i++) {
+ if( p[i][Y] < minay )
+ minay = p[i][Y];
+ else if( p[i][Y] > maxay )
+ maxay = p[i][Y];
+ }
+
+ }
+
+};
+
+static void
+find_intersections_bezier_recursive(std::vector<std::pair<double, double> > & xs,
+ OldBezier a,
+ OldBezier b);
+
+void
+find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs,
+ vector<Geom::Point> const & A,
+ vector<Geom::Point> const & B,
+ double /*precision*/) {
+ OldBezier a, b;
+ a.p = A;
+ b.p = B;
+ return find_intersections_bezier_recursive(xs, a,b);
+}
+
+
+/*
+ * split the curve at the midpoint, returning an array with the two parts
+ * Temporary storage is minimized by using part of the storage for the result
+ * to hold an intermediate value until it is no longer needed.
+ */
+void OldBezier::split(double t, OldBezier &left, OldBezier &right) const {
+ const unsigned sz = p.size();
+ //Geom::Point Vtemp[sz][sz];
+ std::vector< std::vector< Geom::Point > > Vtemp;
+ for (size_t i = 0; i < sz; ++i )
+ Vtemp[i].reserve(sz);
+
+ /* Copy control points */
+ std::copy(p.begin(), p.end(), Vtemp[0].begin());
+
+ /* Triangle computation */
+ for (unsigned i = 1; i < sz; i++) {
+ for (unsigned j = 0; j < sz - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+
+ left.p.resize(sz);
+ right.p.resize(sz);
+ for (unsigned j = 0; j < sz; j++)
+ left.p[j] = Vtemp[j][0];
+ for (unsigned j = 0; j < sz; j++)
+ right.p[j] = Vtemp[sz-1-j][j];
+}
+
+#if 0
+/*
+ * split the curve at the midpoint, returning an array with the two parts
+ * Temporary storage is minimized by using part of the storage for the result
+ * to hold an intermediate value until it is no longer needed.
+ */
+Point OldBezier::operator()(double t) const {
+ const unsigned sz = p.size();
+ Geom::Point Vtemp[sz][sz];
+
+ /* Copy control points */
+ std::copy(p.begin(), p.end(), Vtemp[0]);
+
+ /* Triangle computation */
+ for (unsigned i = 1; i < sz; i++) {
+ for (unsigned j = 0; j < sz - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+ return Vtemp[sz-1][0];
+}
+#endif
+
+// suggested by Sederberg.
+Point OldBezier::operator()(double const t) const {
+ size_t const n = p.size()-1;
+ Point r;
+ for(int dim = 0; dim < 2; dim++) {
+ double const u = 1.0 - t;
+ double bc = 1;
+ double tn = 1;
+ double tmp = p[0][dim]*u;
+ for(size_t i=1; i<n; i++){
+ tn = tn*t;
+ bc = bc*(n-i+1)/i;
+ tmp = (tmp + tn*bc*p[i][dim])*u;
+ }
+ r[dim] = (tmp + tn*t*p[n][dim]);
+ }
+ return r;
+}
+
+
+/*
+ * Test the bounding boxes of two OldBezier curves for interference.
+ * Several observations:
+ * First, it is cheaper to compute the bounding box of the second curve
+ * and test its bounding box for interference than to use a more direct
+ * approach of comparing all control points of the second curve with
+ * the various edges of the bounding box of the first curve to test
+ * for interference.
+ * Second, after a few subdivisions it is highly probable that two corners
+ * of the bounding box of a given Bezier curve are the first and last
+ * control point. Once this happens once, it happens for all subsequent
+ * subcurves. It might be worth putting in a test and then short-circuit
+ * code for further subdivision levels.
+ * Third, in the final comparison (the interference test) the comparisons
+ * should both permit equality. We want to find intersections even if they
+ * occur at the ends of segments.
+ * Finally, there are tighter bounding boxes that can be derived. It isn't
+ * clear whether the higher probability of rejection (and hence fewer
+ * subdivisions and tests) is worth the extra work.
+ */
+
+bool intersect_BB( OldBezier a, OldBezier b ) {
+ double minax, maxax, minay, maxay;
+ a.bounds(minax, maxax, minay, maxay);
+ double minbx, maxbx, minby, maxby;
+ b.bounds(minbx, maxbx, minby, maxby);
+ // Test bounding box of b against bounding box of a
+ // Not >= : need boundary case
+ return !( ( minax > maxbx ) || ( minay > maxby )
+ || ( minbx > maxax ) || ( minby > maxay ) );
+}
+
+/*
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found. The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference. If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected. (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another. Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
+void recursively_intersect( OldBezier a, double t0, double t1, int deptha,
+ OldBezier b, double u0, double u1, int depthb,
+ std::vector<std::pair<double, double> > &parameters)
+{
+ intersect_steps ++;
+ //std::cout << deptha << std::endl;
+ if( deptha > 0 )
+ {
+ OldBezier A[2];
+ a.split(0.5, A[0], A[1]);
+ double tmid = (t0+t1)*0.5;
+ deptha--;
+ if( depthb > 0 )
+ {
+ OldBezier B[2];
+ b.split(0.5, B[0], B[1]);
+ double umid = (u0+u1)*0.5;
+ depthb--;
+ if( intersect_BB( A[0], B[0] ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( A[1], B[0] ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( A[0], B[1] ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ B[1], umid, u1, depthb,
+ parameters );
+ if( intersect_BB( A[1], B[1] ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ B[1], umid, u1, depthb,
+ parameters );
+ }
+ else
+ {
+ if( intersect_BB( A[0], b ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ b, u0, u1, depthb,
+ parameters );
+ if( intersect_BB( A[1], b ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ b, u0, u1, depthb,
+ parameters );
+ }
+ }
+ else
+ if( depthb > 0 )
+ {
+ OldBezier B[2];
+ b.split(0.5, B[0], B[1]);
+ double umid = (u0 + u1)*0.5;
+ depthb--;
+ if( intersect_BB( a, B[0] ) )
+ recursively_intersect( a, t0, t1, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( a, B[1] ) )
+ recursively_intersect( a, t0, t1, deptha,
+ B[0], umid, u1, depthb,
+ parameters );
+ }
+ else // Both segments are fully subdivided; now do line segments
+ {
+ double xlk = a.p.back()[X] - a.p[0][X];
+ double ylk = a.p.back()[Y] - a.p[0][Y];
+ double xnm = b.p.back()[X] - b.p[0][X];
+ double ynm = b.p.back()[Y] - b.p[0][Y];
+ double xmk = b.p[0][X] - a.p[0][X];
+ double ymk = b.p[0][Y] - a.p[0][Y];
+ double det = xnm * ylk - ynm * xlk;
+ if( 1.0 + det == 1.0 )
+ return;
+ else
+ {
+ double detinv = 1.0 / det;
+ double s = ( xnm * ymk - ynm *xmk ) * detinv;
+ double t = ( xlk * ymk - ylk * xmk ) * detinv;
+ if( ( s < 0.0 ) || ( s > 1.0 ) || ( t < 0.0 ) || ( t > 1.0 ) )
+ return;
+ parameters.emplace_back(t0 + s * ( t1 - t0 ),
+ u0 + t * ( u1 - u0 ));
+ }
+ }
+}
+
+inline double log4( double x ) { return log(x)/log(4.); }
+
+/*
+ * Wang's theorem is used to estimate the level of subdivision required,
+ * but only if the bounding boxes interfere at the top level.
+ * Assuming there is a possible intersection, recursively_intersect is
+ * used to find all the parameters corresponding to intersection points.
+ * these are then sorted and returned in an array.
+ */
+
+double Lmax(Point p) {
+ return std::max(fabs(p[X]), fabs(p[Y]));
+}
+
+
+unsigned wangs_theorem(OldBezier /*a*/) {
+ return 6; // seems a good approximation!
+
+ /*
+ const double INV_EPS = (1L<<14); // The value of 1.0 / (1L<<14) is enough for most applications
+
+ double la1 = Lmax( ( a.p[2] - a.p[1] ) - (a.p[1] - a.p[0]) );
+ double la2 = Lmax( ( a.p[3] - a.p[2] ) - (a.p[2] - a.p[1]) );
+ double l0 = std::max(la1, la2);
+ unsigned ra;
+ if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 )
+ ra = 0;
+ else
+ ra = (unsigned)ceil( log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) );
+ //std::cout << ra << std::endl;
+ return ra;*/
+}
+
+struct rparams
+{
+ OldBezier &A;
+ OldBezier &B;
+};
+
+/*static int
+intersect_polish_f (const gsl_vector * x, void *params,
+ gsl_vector * f)
+{
+ const double x0 = gsl_vector_get (x, 0);
+ const double x1 = gsl_vector_get (x, 1);
+
+ Geom::Point dx = ((struct rparams *) params)->A(x0) -
+ ((struct rparams *) params)->B(x1);
+
+ gsl_vector_set (f, 0, dx[0]);
+ gsl_vector_set (f, 1, dx[1]);
+
+ return GSL_SUCCESS;
+}*/
+
+/*union dbl_64{
+ long long i64;
+ double d64;
+};*/
+
+/*static double EpsilonBy(double value, int eps)
+{
+ dbl_64 s;
+ s.d64 = value;
+ s.i64 += eps;
+ return s.d64;
+}*/
+
+/*
+static void intersect_polish_root (OldBezier &A, double &s,
+ OldBezier &B, double &t) {
+ const gsl_multiroot_fsolver_type *T;
+ gsl_multiroot_fsolver *sol;
+
+ int status;
+ size_t iter = 0;
+
+ const size_t n = 2;
+ struct rparams p = {A, B};
+ gsl_multiroot_function f = {&intersect_polish_f, n, &p};
+
+ double x_init[2] = {s, t};
+ gsl_vector *x = gsl_vector_alloc (n);
+
+ gsl_vector_set (x, 0, x_init[0]);
+ gsl_vector_set (x, 1, x_init[1]);
+
+ T = gsl_multiroot_fsolver_hybrids;
+ sol = gsl_multiroot_fsolver_alloc (T, 2);
+ gsl_multiroot_fsolver_set (sol, &f, x);
+
+ do
+ {
+ iter++;
+ status = gsl_multiroot_fsolver_iterate (sol);
+
+ if (status) // check if solver is stuck
+ break;
+
+ status =
+ gsl_multiroot_test_residual (sol->f, 1e-12);
+ }
+ while (status == GSL_CONTINUE && iter < 1000);
+
+ s = gsl_vector_get (sol->x, 0);
+ t = gsl_vector_get (sol->x, 1);
+
+ gsl_multiroot_fsolver_free (sol);
+ gsl_vector_free (x);
+
+ // This code does a neighbourhood search for minor improvements.
+ double best_v = L1(A(s) - B(t));
+ //std::cout << "------\n" << best_v << std::endl;
+ Point best(s,t);
+ while (true) {
+ Point trial = best;
+ double trial_v = best_v;
+ for(int nsi = -1; nsi < 2; nsi++) {
+ for(int nti = -1; nti < 2; nti++) {
+ Point n(EpsilonBy(best[0], nsi),
+ EpsilonBy(best[1], nti));
+ double c = L1(A(n[0]) - B(n[1]));
+ //std::cout << c << "; ";
+ if (c < trial_v) {
+ trial = n;
+ trial_v = c;
+ }
+ }
+ }
+ if(trial == best) {
+ //std::cout << "\n" << s << " -> " << s - best[0] << std::endl;
+ //std::cout << t << " -> " << t - best[1] << std::endl;
+ //std::cout << best_v << std::endl;
+ s = best[0];
+ t = best[1];
+ return;
+ } else {
+ best = trial;
+ best_v = trial_v;
+ }
+ }
+}*/
+
+
+void find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs,
+ OldBezier a, OldBezier b)
+{
+ if( intersect_BB( a, b ) )
+ {
+ recursively_intersect( a, 0., 1., wangs_theorem(a),
+ b, 0., 1., wangs_theorem(b),
+ xs);
+ }
+ /*for(unsigned i = 0; i < xs.size(); i++)
+ intersect_polish_root(a, xs[i].first,
+ b, xs[i].second);*/
+ std::sort(xs.begin(), xs.end());
+}
+
+
+};
+
+/*
+ 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/src/2geom/sbasis-2d.cpp b/src/2geom/sbasis-2d.cpp
new file mode 100644
index 0000000..53b09cd
--- /dev/null
+++ b/src/2geom/sbasis-2d.cpp
@@ -0,0 +1,202 @@
+#include <2geom/sbasis-2d.h>
+#include <2geom/sbasis-geometric.h>
+
+namespace Geom{
+
+SBasis extract_u(SBasis2d const &a, double u) {
+ SBasis sb(a.vs, Linear());
+ double s = u*(1-u);
+
+ for(unsigned vi = 0; vi < a.vs; vi++) {
+ double sk = 1;
+ Linear bo(0,0);
+ for(unsigned ui = 0; ui < a.us; ui++) {
+ bo += (extract_u(a.index(ui, vi), u))*sk;
+ sk *= s;
+ }
+ sb[vi] = bo;
+ }
+
+ return sb;
+}
+
+SBasis extract_v(SBasis2d const &a, double v) {
+ SBasis sb(a.us, Linear());
+ double s = v*(1-v);
+
+ for(unsigned ui = 0; ui < a.us; ui++) {
+ double sk = 1;
+ Linear bo(0,0);
+ for(unsigned vi = 0; vi < a.vs; vi++) {
+ bo += (extract_v(a.index(ui, vi), v))*sk;
+ sk *= s;
+ }
+ sb[ui] = bo;
+ }
+
+ return sb;
+}
+
+SBasis compose(Linear2d const &a, D2<SBasis> const &p) {
+ D2<SBasis> omp(-p[X] + 1, -p[Y] + 1);
+ return multiply(omp[0], omp[1])*a[0] +
+ multiply(p[0], omp[1])*a[1] +
+ multiply(omp[0], p[1])*a[2] +
+ multiply(p[0], p[1])*a[3];
+}
+
+SBasis
+compose(SBasis2d const &fg, D2<SBasis> const &p) {
+ SBasis B;
+ SBasis s[2];
+ SBasis ss[2];
+ for(unsigned dim = 0; dim < 2; dim++)
+ s[dim] = p[dim]*(Linear(1) - p[dim]);
+ ss[1] = Linear(1);
+ for(unsigned vi = 0; vi < fg.vs; vi++) {
+ ss[0] = ss[1];
+ for(unsigned ui = 0; ui < fg.us; ui++) {
+ unsigned i = ui + vi*fg.us;
+ B += ss[0]*compose(fg[i], p);
+ ss[0] *= s[0];
+ }
+ ss[1] *= s[1];
+ }
+ return B;
+}
+
+D2<SBasis>
+compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p) {
+ return D2<SBasis>(compose(fg[X], p), compose(fg[Y], p));
+}
+
+SBasis2d partial_derivative(SBasis2d const &f, int dim) {
+ SBasis2d result;
+ for(unsigned i = 0; i < f.size(); i++) {
+ result.push_back(Linear2d(0,0,0,0));
+ }
+ result.us = f.us;
+ result.vs = f.vs;
+
+ for(unsigned i = 0; i < f.us; i++) {
+ for(unsigned j = 0; j < f.vs; j++) {
+ Linear2d lin = f.index(i,j);
+ Linear2d dlin(lin[1+dim]-lin[0], lin[1+2*dim]-lin[dim], lin[3-dim]-lin[2*(1-dim)], lin[3]-lin[2-dim]);
+ result[i+j*result.us] += dlin;
+ unsigned di = dim?j:i;
+ if (di>=1){
+ float motpi = dim?-1:1;
+ Linear2d ds_lin_low( lin[0], -motpi*lin[1], motpi*lin[2], -lin[3] );
+ result[(i+dim-1)+(j-dim)*result.us] += di*ds_lin_low;
+
+ Linear2d ds_lin_hi( lin[1+dim]-lin[0], lin[1+2*dim]-lin[dim], lin[3]-lin[2-dim], lin[3-dim]-lin[2-dim] );
+ result[i+j*result.us] += di*ds_lin_hi;
+ }
+ }
+ }
+ return result;
+}
+
+/**
+ * Finds a path which traces the 0 contour of f, traversing from A to B as a single d2<sbasis>.
+ * degmax specifies the degree (degree = 2*degmax-1, so a degmax of 2 generates a cubic fit).
+ * The algorithm is based on dividing out derivatives at each end point and does not use the curvature for fitting.
+ * It is less accurate than sb2d_cubic_solve, although this may be fixed in the future.
+ */
+D2<SBasis>
+sb2dsolve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B, unsigned degmax){
+ //g_warning("check f(A)= %f = f(B) = %f =0!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y]));
+
+ SBasis2d dfdu = partial_derivative(f, 0);
+ SBasis2d dfdv = partial_derivative(f, 1);
+ Geom::Point dfA(dfdu.apply(A[X],A[Y]),dfdv.apply(A[X],A[Y]));
+ Geom::Point dfB(dfdu.apply(B[X],B[Y]),dfdv.apply(B[X],B[Y]));
+ Geom::Point nA = dfA/(dfA[X]*dfA[X]+dfA[Y]*dfA[Y]);
+ Geom::Point nB = dfB/(dfB[X]*dfB[X]+dfB[Y]*dfB[Y]);
+
+ D2<SBasis>result(SBasis(degmax, Linear()), SBasis(degmax, Linear()));
+ double fact_k=1;
+ double sign = 1.;
+ for(int dim = 0; dim < 2; dim++)
+ result[dim][0] = Linear(A[dim],B[dim]);
+ for(unsigned k=1; k<degmax; k++){
+ // these two lines make the solutions worse!
+ //fact_k *= k;
+ //sign = -sign;
+ SBasis f_on_curve = compose(f,result);
+ Linear reste = f_on_curve[k];
+ double ax = -reste[0]/fact_k*nA[X];
+ double ay = -reste[0]/fact_k*nA[Y];
+ double bx = -sign*reste[1]/fact_k*nB[X];
+ double by = -sign*reste[1]/fact_k*nB[Y];
+
+ result[X][k] = Linear(ax,bx);
+ result[Y][k] = Linear(ay,by);
+ //sign *= 3;
+ }
+ return result;
+}
+
+/**
+ * Finds a path which traces the 0 contour of f, traversing from A to B as a single cubic d2<sbasis>.
+ * The algorithm is based on matching direction and curvature at each end point.
+ */
+//TODO: handle the case when B is "behind" A for the natural orientation of the level set.
+//TODO: more generally, there might be up to 4 solutions. Choose the best one!
+D2<SBasis>
+sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B){
+ D2<SBasis>result;//(Linear(A[X],B[X]),Linear(A[Y],B[Y]));
+ //g_warning("check 0 = %f = %f!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y]));
+
+ SBasis2d f_u = partial_derivative(f , 0);
+ SBasis2d f_v = partial_derivative(f , 1);
+ SBasis2d f_uu = partial_derivative(f_u, 0);
+ SBasis2d f_uv = partial_derivative(f_v, 0);
+ SBasis2d f_vv = partial_derivative(f_v, 1);
+
+ Geom::Point dfA(f_u.apply(A[X],A[Y]),f_v.apply(A[X],A[Y]));
+ Geom::Point dfB(f_u.apply(B[X],B[Y]),f_v.apply(B[X],B[Y]));
+
+ Geom::Point V0 = rot90(dfA);
+ Geom::Point V1 = rot90(dfB);
+
+ double D2fVV0 = f_uu.apply(A[X],A[Y])*V0[X]*V0[X]+
+ 2*f_uv.apply(A[X],A[Y])*V0[X]*V0[Y]+
+ f_vv.apply(A[X],A[Y])*V0[Y]*V0[Y];
+ double D2fVV1 = f_uu.apply(B[X],B[Y])*V1[X]*V1[X]+
+ 2*f_uv.apply(B[X],B[Y])*V1[X]*V1[Y]+
+ f_vv.apply(B[X],B[Y])*V1[Y]*V1[Y];
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1);
+ if (candidates.empty()) {
+ return D2<SBasis>(SBasis(Linear(A[X],B[X])), SBasis(Linear(A[Y],B[Y])));
+ }
+ //TODO: I'm sure std algorithm could do that for me...
+ double error = -1;
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ Interval bounds = *bounds_fast(compose(f,candidates[i]));
+ double new_error = (fabs(bounds.max())>fabs(bounds.min()) ? fabs(bounds.max()) : fabs(bounds.min()) );
+ if ( new_error < error || error < 0 ){
+ error = new_error;
+ best = i;
+ }
+ }
+ return candidates[best];
+}
+
+
+
+
+};
+
+/*
+ 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/src/2geom/sbasis-geometric.cpp b/src/2geom/sbasis-geometric.cpp
new file mode 100644
index 0000000..7039c6d
--- /dev/null
+++ b/src/2geom/sbasis-geometric.cpp
@@ -0,0 +1,790 @@
+/** Geometric operators on D2<SBasis> (1D->2D).
+ * Copyright 2012 JBC Engelen
+ * Copyright 2007 JF Barraud
+ * Copyright 2007 N Hurst
+ *
+ * The functions defined in this header related to 2d geometric operations such as arc length,
+ * unit_vector, curvature, and centroid. Most are built on top of unit_vector, which takes an
+ * arbitrary D2 and returns a D2 with unit length with the same direction.
+ *
+ * Todo/think about:
+ * arclength D2 -> sbasis (giving arclength function)
+ * does uniform_speed return natural parameterisation?
+ * integrate sb2d code from normal-bundle
+ * angle(md<2>) -> sbasis (gives angle from vector - discontinuous?)
+ * osculating circle center?
+ *
+ **/
+
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-geometric.h>
+
+//namespace Geom{
+using namespace Geom;
+using namespace std;
+
+//Some utils first.
+//TODO: remove this!!
+/**
+ * Return a list of doubles that appear in both a and b to within error tol
+ * a, b, vector of double
+ * tol tolerance
+ */
+static vector<double>
+vect_intersect(vector<double> const &a, vector<double> const &b, double tol=0.){
+ vector<double> inter;
+ unsigned i=0,j=0;
+ while ( i<a.size() && j<b.size() ){
+ if (fabs(a[i]-b[j])<tol){
+ inter.push_back(a[i]);
+ i+=1;
+ j+=1;
+ }else if (a[i]<b[j]){
+ i+=1;
+ }else if (a[i]>b[j]){
+ j+=1;
+ }
+ }
+ return inter;
+}
+
+//------------------------------------------------------------------------------
+static SBasis divide_by_sk(SBasis const &a, int k) {
+ if ( k>=(int)a.size()){
+ //make sure a is 0?
+ return SBasis();
+ }
+ if(k < 0) return shift(a,-k);
+ SBasis c;
+ c.insert(c.begin(), a.begin()+k, a.end());
+ return c;
+}
+
+static SBasis divide_by_t0k(SBasis const &a, int k) {
+ if(k < 0) {
+ SBasis c = Linear(0,1);
+ for (int i=2; i<=-k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(c);
+ }else{
+ SBasis c = Linear(1,0);
+ for (int i=2; i<=k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(divide_by_sk(c,k));
+ }
+}
+
+static SBasis divide_by_t1k(SBasis const &a, int k) {
+ if(k < 0) {
+ SBasis c = Linear(1,0);
+ for (int i=2; i<=-k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(c);
+ }else{
+ SBasis c = Linear(0,1);
+ for (int i=2; i<=k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(divide_by_sk(c,k));
+ }
+}
+
+static D2<SBasis> RescaleForNonVanishingEnds(D2<SBasis> const &MM, double ZERO=1.e-4){
+ D2<SBasis> M = MM;
+ //TODO: divide by all the s at once!!!
+ while ((M[0].size()>1||M[1].size()>1) &&
+ fabs(M[0].at0())<ZERO &&
+ fabs(M[1].at0())<ZERO &&
+ fabs(M[0].at1())<ZERO &&
+ fabs(M[1].at1())<ZERO){
+ M[0] = divide_by_sk(M[0],1);
+ M[1] = divide_by_sk(M[1],1);
+ }
+ while ((M[0].size()>1||M[1].size()>1) &&
+ fabs(M[0].at0())<ZERO && fabs(M[1].at0())<ZERO){
+ M[0] = divide_by_t0k(M[0],1);
+ M[1] = divide_by_t0k(M[1],1);
+ }
+ while ((M[0].size()>1||M[1].size()>1) &&
+ fabs(M[0].at1())<ZERO && fabs(M[1].at1())<ZERO){
+ M[0] = divide_by_t1k(M[0],1);
+ M[1] = divide_by_t1k(M[1],1);
+ }
+ return M;
+}
+
+/*static D2<SBasis> RescaleForNonVanishing(D2<SBasis> const &MM, double ZERO=1.e-4){
+ std::vector<double> levels;
+ levels.push_back(-ZERO);
+ levels.push_back(ZERO);
+ //std::vector<std::vector<double> > mr = multi_roots(MM, levels);
+ }*/
+
+
+//=================================================================
+//TODO: what's this for?!?!
+Piecewise<D2<SBasis> >
+Geom::cutAtRoots(Piecewise<D2<SBasis> > const &M, double ZERO){
+ vector<double> rts;
+ for (unsigned i=0; i<M.size(); i++){
+ vector<double> seg_rts = roots((M.segs[i])[0]);
+ seg_rts = vect_intersect(seg_rts, roots((M.segs[i])[1]), ZERO);
+ Linear mapToDom = Linear(M.cuts[i],M.cuts[i+1]);
+ for (double & seg_rt : seg_rts){
+ seg_rt= mapToDom(seg_rt);
+ }
+ rts.insert(rts.end(),seg_rts.begin(),seg_rts.end());
+ }
+ return partition(M,rts);
+}
+
+/** Return a function which gives the angle of vect at each point.
+ \param vect a piecewise parameteric curve.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates Piecewise
+*/
+Piecewise<SBasis>
+Geom::atan2(Piecewise<D2<SBasis> > const &vect, double tol, unsigned order){
+ Piecewise<SBasis> result;
+ Piecewise<D2<SBasis> > v = cutAtRoots(vect,tol);
+ result.cuts.push_back(v.cuts.front());
+ for (unsigned i=0; i<v.size(); i++){
+
+ D2<SBasis> vi = RescaleForNonVanishingEnds(v.segs[i]);
+ SBasis x=vi[0], y=vi[1];
+ Piecewise<SBasis> angle;
+ angle = divide (x*derivative(y)-y*derivative(x), x*x+y*y, tol, order);
+
+ //TODO: I don't understand this - sign.
+ angle = integral(-angle);
+ Point vi0 = vi.at0();
+ angle += -std::atan2(vi0[1],vi0[0]) - angle[0].at0();
+ //TODO: deal with 2*pi jumps form one seg to the other...
+ //TODO: not exact at t=1 because of the integral.
+ //TODO: force continuity?
+
+ angle.setDomain(Interval(v.cuts[i],v.cuts[i+1]));
+ result.concat(angle);
+ }
+ return result;
+}
+/** Return a function which gives the angle of vect at each point.
+ \param vect a piecewise parameteric curve.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates Piecewise, D2
+*/
+Piecewise<SBasis>
+Geom::atan2(D2<SBasis> const &vect, double tol, unsigned order){
+ return atan2(Piecewise<D2<SBasis> >(vect),tol,order);
+}
+
+/** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle.
+ \param angle a piecewise function of angle wrt t.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates D2, SBasis
+*/
+D2<Piecewise<SBasis> >
+Geom::tan2(SBasis const &angle, double tol, unsigned order){
+ return tan2(Piecewise<SBasis>(angle), tol, order);
+}
+
+/** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle.
+ \param angle a piecewise function of angle wrt t.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates Piecewise, D2
+*/
+D2<Piecewise<SBasis> >
+Geom::tan2(Piecewise<SBasis> const &angle, double tol, unsigned order){
+ return D2<Piecewise<SBasis> >(cos(angle, tol, order), sin(angle, tol, order));
+}
+
+/** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length.
+ \param V_in the original path.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+
+unitVector(x,y) is computed as (b,-a) where a and b are solutions of:
+ ax+by=0 (eqn1) and a^2+b^2=1 (eqn2)
+
+ \relates Piecewise, D2
+*/
+Piecewise<D2<SBasis> >
+Geom::unitVector(D2<SBasis> const &V_in, double tol, unsigned order){
+ //TODO: Handle vanishing vectors...
+ // -This approach is numerically bad. Find a stable way to rescale V_in to have non vanishing ends.
+ // -This done, unitVector will have jumps at zeros: fill the gaps with arcs of circles.
+ D2<SBasis> V = RescaleForNonVanishingEnds(V_in);
+
+ if (V[0].isZero(tol) && V[1].isZero(tol))
+ return Piecewise<D2<SBasis> >(D2<SBasis>(Linear(1),SBasis()));
+ SBasis x = V[0], y = V[1];
+ SBasis r_eqn1, r_eqn2;
+
+ Point v0 = unit_vector(V.at0());
+ Point v1 = unit_vector(V.at1());
+ SBasis a = SBasis(order+1, Linear(0.));
+ a[0] = Linear(-v0[1],-v1[1]);
+ SBasis b = SBasis(order+1, Linear(0.));
+ b[0] = Linear( v0[0], v1[0]);
+
+ r_eqn1 = -(a*x+b*y);
+ r_eqn2 = Linear(1.)-(a*a+b*b);
+
+ for (unsigned k=1; k<=order; k++){
+ double r0 = (k<r_eqn1.size())? r_eqn1.at(k).at0() : 0;
+ double r1 = (k<r_eqn1.size())? r_eqn1.at(k).at1() : 0;
+ double rr0 = (k<r_eqn2.size())? r_eqn2.at(k).at0() : 0;
+ double rr1 = (k<r_eqn2.size())? r_eqn2.at(k).at1() : 0;
+ double a0,a1,b0,b1;// coeffs in a[k] and b[k]
+
+ //the equations to solve at this point are:
+ // a0*x(0)+b0*y(0)=r0 & 2*a0*a(0)+2*b0*b(0)=rr0
+ //and
+ // a1*x(1)+b1*y(1)=r1 & 2*a1*a(1)+2*b1*b(1)=rr1
+ a0 = r0/dot(v0,V.at0())*v0[0]-rr0/2*v0[1];
+ b0 = r0/dot(v0,V.at0())*v0[1]+rr0/2*v0[0];
+ a1 = r1/dot(v1,V.at1())*v1[0]-rr1/2*v1[1];
+ b1 = r1/dot(v1,V.at1())*v1[1]+rr1/2*v1[0];
+
+ a[k] = Linear(a0,a1);
+ b[k] = Linear(b0,b1);
+
+ //TODO: use "incremental" rather than explicit formulas.
+ r_eqn1 = -(a*x+b*y);
+ r_eqn2 = Linear(1)-(a*a+b*b);
+ }
+
+ //our candidate is:
+ D2<SBasis> unitV;
+ unitV[0] = b;
+ unitV[1] = -a;
+
+ //is it good?
+ double rel_tol = std::max(1.,std::max(V_in[0].tailError(0),V_in[1].tailError(0)))*tol;
+ if (r_eqn1.tailError(order)>rel_tol || r_eqn2.tailError(order)>tol){
+ //if not: subdivide and concat results.
+ Piecewise<D2<SBasis> > unitV0, unitV1;
+ unitV0 = unitVector(compose(V,Linear(0,.5)),tol,order);
+ unitV1 = unitVector(compose(V,Linear(.5,1)),tol,order);
+ unitV0.setDomain(Interval(0.,.5));
+ unitV1.setDomain(Interval(.5,1.));
+ unitV0.concat(unitV1);
+ return(unitV0);
+ }else{
+ //if yes: return it as pw.
+ Piecewise<D2<SBasis> > result;
+ result=(Piecewise<D2<SBasis> >)unitV;
+ return result;
+ }
+}
+
+/** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length.
+ \param V_in the original path.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+
+unitVector(x,y) is computed as (b,-a) where a and b are solutions of:
+ ax+by=0 (eqn1) and a^2+b^2=1 (eqn2)
+
+ \relates Piecewise
+*/
+Piecewise<D2<SBasis> >
+Geom::unitVector(Piecewise<D2<SBasis> > const &V, double tol, unsigned order){
+ Piecewise<D2<SBasis> > result;
+ Piecewise<D2<SBasis> > VV = cutAtRoots(V);
+ result.cuts.push_back(VV.cuts.front());
+ for (unsigned i=0; i<VV.size(); i++){
+ Piecewise<D2<SBasis> > unit_seg;
+ unit_seg = unitVector(VV.segs[i],tol, order);
+ unit_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
+ result.concat(unit_seg);
+ }
+ return result;
+}
+
+/** returns a function giving the arclength at each point in M.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \relates Piecewise
+*/
+Piecewise<SBasis>
+Geom::arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol){
+ Piecewise<D2<SBasis> > dM = derivative(M);
+ Piecewise<SBasis> dMlength = sqrt(dot(dM,dM),tol,3);
+ Piecewise<SBasis> length = integral(dMlength);
+ length-=length.segs.front().at0();
+ return length;
+}
+
+/** returns a function giving the arclength at each point in M.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \relates Piecewise, D2
+*/
+Piecewise<SBasis>
+Geom::arcLengthSb(D2<SBasis> const &M, double tol){
+ return arcLengthSb(Piecewise<D2<SBasis> >(M), tol);
+}
+
+#if 0
+double
+Geom::length(D2<SBasis> const &M,
+ double tol){
+ Piecewise<SBasis> length = arcLengthSb(M, tol);
+ return length.segs.back().at1();
+}
+double
+Geom::length(Piecewise<D2<SBasis> > const &M,
+ double tol){
+ Piecewise<SBasis> length = arcLengthSb(M, tol);
+ return length.segs.back().at1();
+}
+#endif
+
+/** returns a function giving the curvature at each point in M.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \relates Piecewise, D2
+ \todo claimed incomplete. Check.
+*/
+Piecewise<SBasis>
+Geom::curvature(D2<SBasis> const &M, double tol) {
+ D2<SBasis> dM=derivative(M);
+ Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
+ Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
+ Piecewise<SBasis> k = cross(derivative(unitv),unitv);
+ k = divide(k,dMlength,tol,3);
+ return(k);
+}
+
+/** returns a function giving the curvature at each point in M.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \relates Piecewise
+ \todo claimed incomplete. Check.
+*/
+Piecewise<SBasis>
+Geom::curvature(Piecewise<D2<SBasis> > const &V, double tol){
+ Piecewise<SBasis> result;
+ Piecewise<D2<SBasis> > VV = cutAtRoots(V);
+ result.cuts.push_back(VV.cuts.front());
+ for (unsigned i=0; i<VV.size(); i++){
+ Piecewise<SBasis> curv_seg;
+ curv_seg = curvature(VV.segs[i],tol);
+ curv_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
+ result.concat(curv_seg);
+ }
+ return result;
+}
+
+//=================================================================
+
+/** Reparameterise M to have unit speed.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates Piecewise, D2
+*/
+Piecewise<D2<SBasis> >
+Geom::arc_length_parametrization(D2<SBasis> const &M,
+ unsigned order,
+ double tol){
+ Piecewise<D2<SBasis> > u;
+ u.push_cut(0);
+
+ Piecewise<SBasis> s = arcLengthSb(Piecewise<D2<SBasis> >(M),tol);
+ for (unsigned i=0; i < s.size();i++){
+ double t0=s.cuts[i],t1=s.cuts[i+1];
+ if ( are_near(s(t0),s(t1)) ) {
+ continue;
+ }
+ D2<SBasis> sub_M = compose(M,Linear(t0,t1));
+ D2<SBasis> sub_u;
+ for (unsigned dim=0;dim<2;dim++){
+ SBasis sub_s = s.segs[i];
+ sub_s-=sub_s.at0();
+ sub_s/=sub_s.at1();
+ sub_u[dim]=compose_inverse(sub_M[dim],sub_s, order, tol);
+ }
+ u.push(sub_u,s(t1));
+ }
+ return u;
+}
+
+/** Reparameterise M to have unit speed.
+ \param M the Element.
+ \param tol the maximum error allowed.
+ \param order the maximum degree to use for approximation
+ \relates Piecewise
+*/
+Piecewise<D2<SBasis> >
+Geom::arc_length_parametrization(Piecewise<D2<SBasis> > const &M,
+ unsigned order,
+ double tol){
+ Piecewise<D2<SBasis> > result;
+ for (unsigned i=0; i<M.size(); i++) {
+ result.concat( arc_length_parametrization(M[i],order,tol) );
+ }
+ return result;
+}
+
+#include <gsl/gsl_integration.h>
+static double sb_length_integrating(double t, void* param) {
+ SBasis* pc = (SBasis*)param;
+ return sqrt((*pc)(t));
+}
+
+/** Calculates the length of a D2<SBasis> through gsl integration.
+ \param B the Element.
+ \param tol the maximum error allowed.
+ \param result variable to be incremented with the length of the path
+ \param abs_error variable to be incremented with the estimated error
+ \relates D2
+If you only want the length, this routine may be faster/more accurate.
+*/
+void Geom::length_integrating(D2<SBasis> const &B, double &result, double &abs_error, double tol) {
+ D2<SBasis> dB = derivative(B);
+ SBasis dB2 = dot(dB, dB);
+
+ gsl_function F;
+ gsl_integration_workspace * w
+ = gsl_integration_workspace_alloc (20);
+ F.function = &sb_length_integrating;
+ F.params = (void*)&dB2;
+ double quad_result, err;
+ /* We could probably use the non adaptive code here if we removed any cusps first. */
+
+ gsl_integration_qag (&F, 0, 1, 0, tol, 20,
+ GSL_INTEG_GAUSS21, w, &quad_result, &err);
+
+ abs_error += err;
+ result += quad_result;
+}
+
+/** Calculates the length of a D2<SBasis> through gsl integration.
+ \param s the Element.
+ \param tol the maximum error allowed.
+ \relates D2
+If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb.
+*/
+double
+Geom::length(D2<SBasis> const &s,
+ double tol){
+ double result = 0;
+ double abs_error = 0;
+ length_integrating(s, result, abs_error, tol);
+ return result;
+}
+/** Calculates the length of a Piecewise<D2<SBasis> > through gsl integration.
+ \param s the Element.
+ \param tol the maximum error allowed.
+ \relates Piecewise
+If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb.
+*/
+double
+Geom::length(Piecewise<D2<SBasis> > const &s,
+ double tol){
+ double result = 0;
+ double abs_error = 0;
+ for (unsigned i=0; i < s.size();i++){
+ length_integrating(s[i], result, abs_error, tol);
+ }
+ return result;
+}
+
+/**
+ * Centroid using sbasis integration.
+ \param p the Element.
+ \param centroid on return contains the centroid of the shape
+ \param area on return contains the signed area of the shape.
+ \relates Piecewise
+This approach uses green's theorem to compute the area and centroid using integrals. For curved shapes this is much faster than converting to polyline. Note that without an uncross operation the output is not the absolute area.
+
+ * Returned values:
+ 0 for normal execution;
+ 2 if area is zero, meaning centroid is meaningless.
+
+ */
+unsigned Geom::centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area) {
+ Point centroid_tmp(0,0);
+ double atmp = 0;
+ for(unsigned i = 0; i < p.size(); i++) {
+ SBasis curl = dot(p[i], rot90(derivative(p[i])));
+ SBasis A = integral(curl);
+ D2<SBasis> C = integral(multiply(curl, p[i]));
+ atmp += A.at1() - A.at0();
+ centroid_tmp += C.at1()- C.at0(); // first moment.
+ }
+// join ends
+ centroid_tmp *= 2;
+ Point final = p[p.size()-1].at1(), initial = p[0].at0();
+ const double ai = cross(final, initial);
+ atmp += ai;
+ centroid_tmp += (final + initial)*ai; // first moment.
+
+ area = atmp / 2;
+ if (atmp != 0) {
+ centroid = centroid_tmp / (3 * atmp);
+ return 0;
+ }
+ return 2;
+}
+
+/**
+ * Find cubics with prescribed curvatures at both ends.
+ *
+ * this requires to solve a system of the form
+ *
+ * \f[
+ * \lambda_1 = a_0 \lambda_0^2 + c_0
+ * \lambda_0 = a_1 \lambda_1^2 + c_1
+ * \f]
+ *
+ * which is a deg 4 equation in lambda 0.
+ * Below are basic functions dedicated to solving this assuming a0 and a1 !=0.
+ */
+
+static OptInterval
+find_bounds_for_lambda0(double aa0,double aa1,double cc0,double cc1,
+ int insist_on_speeds_signs){
+
+ double a0=aa0,a1=aa1,c0=cc0,c1=cc1;
+ Interval result;
+ bool flip = a1<0;
+ if (a1<0){a1=-a1; c1=-c1;}
+ if (a0<0){a0=-a0; c0=-c0;}
+ double a = (a0<a1 ? a0 : a1);
+ double c = (c0<c1 ? c0 : c1);
+ double delta = 1-4*a*c;
+ if ( delta < 0 )
+ return OptInterval();//return empty interval
+ double lambda_max = (1+std::sqrt(delta))/2/a;
+
+ result = Interval(c,lambda_max);
+ if (flip)
+ result *= -1;
+ if (insist_on_speeds_signs == 1){
+ if (result.max() < 0)//Caution: setMin with max<new min...
+ return OptInterval();//return empty interval
+ result.setMin(0);
+ }
+ result = Interval(result.min()-.1,result.max()+.1);//just in case all our approx. were exact...
+ return result;
+}
+
+static
+std::vector<double>
+solve_lambda0(double a0,double a1,double c0,double c1,
+ int insist_on_speeds_signs){
+
+ SBasis p(3, Linear());
+ p[0] = Linear( a1*c0*c0+c1, a1*a0*(a0+ 2*c0) +a1*c0*c0 +c1 -1 );
+ p[1] = Linear( -a1*a0*(a0+2*c0), -a1*a0*(3*a0+2*c0) );
+ p[2] = Linear( a1*a0*a0 );
+
+ OptInterval domain = find_bounds_for_lambda0(a0,a1,c0,c1,insist_on_speeds_signs);
+ if ( !domain )
+ return std::vector<double>();
+ p = compose(p,Linear(domain->min(),domain->max()));
+ std::vector<double>rts = roots(p);
+ for (double & rt : rts){
+ rt = domain->min() + rt * domain->extent();
+ }
+ return rts;
+}
+
+/**
+* \brief returns the cubics fitting direction and curvature of a given
+* input curve at two points.
+*
+* The input can be the
+* value, speed, and acceleration
+* or
+* value, speed, and cross(acceleration,speed)
+* of the original curve at the both ends.
+* (the second is often technically useful, as it avoids unnecessary division by |v|^2)
+* Recall that K=1/R=cross(acceleration,speed)/|speed|^3.
+*
+* Moreover, a 7-th argument 'insist_on_speed_signs' can be supplied to select solutions:
+* If insist_on_speed_signs == 1, only consider solutions where speeds at both ends are positively
+* proportional to the given ones.
+* If insist_on_speed_signs == 0, allow speeds to point in the opposite direction (both at the same time)
+* If insist_on_speed_signs == -1, allow speeds to point in both direction independently.
+*
+* \relates D2
+*/
+std::vector<D2<SBasis> >
+Geom::cubics_fitting_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ double d2M0xdM0, double d2M1xdM1,
+ int insist_on_speed_signs,
+ double epsilon){
+ std::vector<D2<SBasis> > result;
+
+ //speed of cubic bezier will be lambda0*dM0 and lambda1*dM1,
+ //with lambda0 and lambda1 s.t. curvature at both ends is the same
+ //as the curvature of the given curve.
+ std::vector<double> lambda0,lambda1;
+ double dM1xdM0=cross(dM1,dM0);
+ if (fabs(dM1xdM0)<epsilon){
+ if (fabs(d2M0xdM0)<epsilon || fabs(d2M1xdM1)<epsilon){
+ return result;
+ }
+ double lbda02 = 6.*cross(M1-M0,dM0)/d2M0xdM0;
+ double lbda12 =-6.*cross(M1-M0,dM1)/d2M1xdM1;
+ if (lbda02<0 || lbda12<0){
+ return result;
+ }
+ lambda0.push_back(std::sqrt(lbda02) );
+ lambda1.push_back(std::sqrt(lbda12) );
+ }else{
+ //solve: lambda1 = a0 lambda0^2 + c0
+ // lambda0 = a1 lambda1^2 + c1
+ double a0,c0,a1,c1;
+ a0 = -d2M0xdM0/2/dM1xdM0;
+ c0 = 3*cross(M1-M0,dM0)/dM1xdM0;
+ a1 = -d2M1xdM1/2/dM1xdM0;
+ c1 = -3*cross(M1-M0,dM1)/dM1xdM0;
+
+ if (fabs(a0)<epsilon){
+ lambda1.push_back( c0 );
+ lambda0.push_back( a1*c0*c0 + c1 );
+ }else if (fabs(a1)<epsilon){
+ lambda0.push_back( c1 );
+ lambda1.push_back( a0*c1*c1 + c0 );
+ }else{
+ //find lamda0 by solving a deg 4 equation d0+d1*X+...+d4*X^4=0
+ vector<double> solns=solve_lambda0(a0,a1,c0,c1,insist_on_speed_signs);
+ for (double lbda0 : solns){
+ double lbda1=c0+a0*lbda0*lbda0;
+ //is this solution pointing in the + direction at both ends?
+ if (lbda0>=0. && lbda1>=0.){
+ lambda0.push_back( lbda0);
+ lambda1.push_back( lbda1);
+ }
+ //is this solution pointing in the - direction at both ends?
+ else if (lbda0<=0. && lbda1<=0. && insist_on_speed_signs<=0){
+ lambda0.push_back( lbda0);
+ lambda1.push_back( lbda1);
+ }
+ //ok,this solution is pointing in the + and - directions.
+ else if (insist_on_speed_signs<0){
+ lambda0.push_back( lbda0);
+ lambda1.push_back( lbda1);
+ }
+ }
+ }
+ }
+
+ for (unsigned i=0; i<lambda0.size(); i++){
+ Point V0 = lambda0[i]*dM0;
+ Point V1 = lambda1[i]*dM1;
+ D2<SBasis> cubic;
+ for(unsigned dim=0;dim<2;dim++){
+ SBasis c(2, Linear());
+ c[0] = Linear(M0[dim],M1[dim]);
+ c[1] = Linear( M0[dim]-M1[dim]+V0[dim],
+ -M0[dim]+M1[dim]-V1[dim]);
+ cubic[dim] = c;
+ }
+#if 0
+ Piecewise<SBasis> k = curvature(result);
+ double dM0_l = dM0.length();
+ double dM1_l = dM1.length();
+ g_warning("Target radii: %f, %f", dM0_l*dM0_l*dM0_l/d2M0xdM0,dM1_l*dM1_l*dM1_l/d2M1xdM1);
+ g_warning("Obtained radii: %f, %f",1/k.valueAt(0),1/k.valueAt(1));
+#endif
+ result.push_back(cubic);
+ }
+ return(result);
+}
+
+std::vector<D2<SBasis> >
+Geom::cubics_fitting_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ Point const &d2M0, Point const &d2M1,
+ int insist_on_speed_signs,
+ double epsilon){
+ double d2M0xdM0 = cross(d2M0,dM0);
+ double d2M1xdM1 = cross(d2M1,dM1);
+ return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon);
+}
+
+std::vector<D2<SBasis> >
+Geom::cubics_with_prescribed_curvature(Point const &M0, Point const &M1,
+ Point const &dM0, Point const &dM1,
+ double k0, double k1,
+ int insist_on_speed_signs,
+ double epsilon){
+ double length;
+ length = dM0.length();
+ double d2M0xdM0 = k0*length*length*length;
+ length = dM1.length();
+ double d2M1xdM1 = k1*length*length*length;
+ return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon);
+}
+
+
+namespace Geom {
+/**
+* \brief returns all the parameter values of A whose tangent passes through P.
+* \relates D2
+*/
+std::vector<double> find_tangents(Point P, D2<SBasis> const &A) {
+ SBasis crs (cross(A - P, derivative(A)));
+ return roots(crs);
+}
+
+/**
+* \brief returns all the parameter values of A whose normal passes through P.
+* \relates D2
+*/
+std::vector<double> find_normals(Point P, D2<SBasis> const &A) {
+ SBasis crs (dot(A - P, derivative(A)));
+ return roots(crs);
+}
+
+/**
+* \brief returns all the parameter values of A whose normal is parallel to vector V.
+* \relates D2
+*/
+std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A) {
+ SBasis crs = dot(derivative(A), V);
+ return roots(crs);
+}
+/**
+* \brief returns all the parameter values of A whose tangent is parallel to vector V.
+* \relates D2
+*/
+std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A) {
+ SBasis crs = dot(derivative(A), rot90(V));
+ return roots(crs);
+}
+
+}
+//}; // namespace
+
+
+/*
+ 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/src/2geom/sbasis-math.cpp b/src/2geom/sbasis-math.cpp
new file mode 100644
index 0000000..547f9af
--- /dev/null
+++ b/src/2geom/sbasis-math.cpp
@@ -0,0 +1,379 @@
+/*
+ * sbasis-math.cpp - some std functions to work with (pw)s-basis
+ *
+ * Authors:
+ * Jean-Francois Barraud
+ *
+ * Copyright (C) 2006-2007 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.
+ */
+
+//this a first try to define sqrt, cos, sin, etc...
+//TODO: define a truncated compose(sb,sb, order) and extend it to pw<sb>.
+//TODO: in all these functions, compute 'order' according to 'tol'.
+
+#include <2geom/d2.h>
+#include <2geom/sbasis-math.h>
+#include <stdio.h>
+#include <math.h>
+//#define ZERO 1e-3
+
+
+namespace Geom {
+
+
+//-|x|-----------------------------------------------------------------------
+/** Return the absolute value of a function pointwise.
+ \param f function
+*/
+Piecewise<SBasis> abs(SBasis const &f){
+ return abs(Piecewise<SBasis>(f));
+}
+/** Return the absolute value of a function pointwise.
+ \param f function
+*/
+Piecewise<SBasis> abs(Piecewise<SBasis> const &f){
+ Piecewise<SBasis> absf=partition(f,roots(f));
+ for (unsigned i=0; i<absf.size(); i++){
+ if (absf.segs[i](.5)<0) absf.segs[i]*=-1;
+ }
+ return absf;
+}
+
+//-max(x,y), min(x,y)--------------------------------------------------------
+/** Return the greater of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis> max( SBasis const &f, SBasis const &g){
+ return max(Piecewise<SBasis>(f),Piecewise<SBasis>(g));
+}
+/** Return the greater of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis> max(Piecewise<SBasis> const &f, SBasis const &g){
+ return max(f,Piecewise<SBasis>(g));
+}
+/** Return the greater of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis> max( SBasis const &f, Piecewise<SBasis> const &g){
+ return max(Piecewise<SBasis>(f),g);
+}
+/** Return the greater of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis> max(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){
+ Piecewise<SBasis> max=partition(f,roots(f-g));
+ Piecewise<SBasis> gg =partition(g,max.cuts);
+ max = partition(max,gg.cuts);
+ for (unsigned i=0; i<max.size(); i++){
+ if (max.segs[i](.5)<gg.segs[i](.5)) max.segs[i]=gg.segs[i];
+ }
+ return max;
+}
+
+/** Return the more negative of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis>
+min( SBasis const &f, SBasis const &g){ return -max(-f,-g); }
+/** Return the more negative of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis>
+min(Piecewise<SBasis> const &f, SBasis const &g){ return -max(-f,-g); }
+/** Return the more negative of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis>
+min( SBasis const &f, Piecewise<SBasis> const &g){ return -max(-f,-g); }
+/** Return the more negative of the two functions pointwise.
+ \param f, g two functions
+*/
+Piecewise<SBasis>
+min(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){ return -max(-f,-g); }
+
+
+//-sign(x)---------------------------------------------------------------
+/** Return the sign of the two functions pointwise.
+ \param f function
+*/
+Piecewise<SBasis> signSb(SBasis const &f){
+ return signSb(Piecewise<SBasis>(f));
+}
+/** Return the sign of the two functions pointwise.
+ \param f function
+*/
+Piecewise<SBasis> signSb(Piecewise<SBasis> const &f){
+ Piecewise<SBasis> sign=partition(f,roots(f));
+ for (unsigned i=0; i<sign.size(); i++){
+ sign.segs[i] = (sign.segs[i](.5)<0)? Linear(-1.):Linear(1.);
+ }
+ return sign;
+}
+
+//-Sqrt----------------------------------------------------------
+static Piecewise<SBasis> sqrt_internal(SBasis const &f,
+ double tol,
+ int order){
+ SBasis sqrtf;
+ if(f.isZero() || order == 0){
+ return Piecewise<SBasis>(sqrtf);
+ }
+ if (f.at0()<-tol*tol && f.at1()<-tol*tol){
+ return sqrt_internal(-f,tol,order);
+ }else if (f.at0()>tol*tol && f.at1()>tol*tol){
+ sqrtf.resize(order+1, Linear(0,0));
+ sqrtf[0] = Linear(std::sqrt(f[0][0]), std::sqrt(f[0][1]));
+ SBasis r = f - multiply(sqrtf, sqrtf); // remainder
+ for(unsigned i = 1; int(i) <= order && i<r.size(); i++) {
+ Linear ci(r[i][0]/(2*sqrtf[0][0]), r[i][1]/(2*sqrtf[0][1]));
+ SBasis cisi = shift(ci, i);
+ r -= multiply(shift((sqrtf*2 + cisi), i), SBasis(ci));
+ r.truncate(order+1);
+ sqrtf[i] = ci;
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+ }else{
+ sqrtf = Linear(std::sqrt(fabs(f.at0())), std::sqrt(fabs(f.at1())));
+ }
+
+ double err = (f - multiply(sqrtf, sqrtf)).tailError(0);
+ if (err<tol){
+ return Piecewise<SBasis>(sqrtf);
+ }
+
+ Piecewise<SBasis> sqrtf0,sqrtf1;
+ sqrtf0 = sqrt_internal(compose(f,Linear(0.,.5)),tol,order);
+ sqrtf1 = sqrt_internal(compose(f,Linear(.5,1.)),tol,order);
+ sqrtf0.setDomain(Interval(0.,.5));
+ sqrtf1.setDomain(Interval(.5,1.));
+ sqrtf0.concat(sqrtf1);
+ return sqrtf0;
+}
+
+/** Compute the sqrt of a function.
+ \param f function
+*/
+Piecewise<SBasis> sqrt(SBasis const &f, double tol, int order){
+ return sqrt(max(f,Linear(tol*tol)),tol,order);
+}
+
+/** Compute the sqrt of a function.
+ \param f function
+*/
+Piecewise<SBasis> sqrt(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> result;
+ Piecewise<SBasis> zero = Piecewise<SBasis>(Linear(tol*tol));
+ zero.setDomain(f.domain());
+ Piecewise<SBasis> ff=max(f,zero);
+
+ for (unsigned i=0; i<ff.size(); i++){
+ Piecewise<SBasis> sqrtfi = sqrt_internal(ff.segs[i],tol,order);
+ sqrtfi.setDomain(Interval(ff.cuts[i],ff.cuts[i+1]));
+ result.concat(sqrtfi);
+ }
+ return result;
+}
+
+//-Yet another sin/cos--------------------------------------------------------------
+
+/** Compute the sine of a function.
+ \param f function
+ \param tol maximum error
+ \param order maximum degree polynomial to use
+*/
+Piecewise<SBasis> sin( SBasis const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));}
+/** Compute the sine of a function.
+ \param f function
+ \param tol maximum error
+ \param order maximum degree polynomial to use
+*/
+Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));}
+
+/** Compute the cosine of a function.
+ \param f function
+ \param tol maximum error
+ \param order maximum degree polynomial to use
+*/
+Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> result;
+ for (unsigned i=0; i<f.size(); i++){
+ Piecewise<SBasis> cosfi = cos(f.segs[i],tol,order);
+ cosfi.setDomain(Interval(f.cuts[i],f.cuts[i+1]));
+ result.concat(cosfi);
+ }
+ return result;
+}
+
+/** Compute the cosine of a function.
+ \param f function
+ \param tol maximum error
+ \param order maximum degree polynomial to use
+*/
+Piecewise<SBasis> cos( SBasis const &f, double tol, int order){
+ double alpha = (f.at0()+f.at1())/2.;
+ SBasis x = f-alpha;
+ double d = x.tailError(0),err=1;
+ //estimate cos(x)-sum_0^order (-1)^k x^2k/2k! by the first neglicted term
+ for (int i=1; i<=2*order; i++) err*=d/i;
+
+ if (err<tol){
+ SBasis xk=Linear(1), c=Linear(1), s=Linear(0);
+ for (int k=1; k<=2*order; k+=2){
+ xk*=x/k;
+ //take also truncature errors into account...
+ err+=xk.tailError(order);
+ xk.truncate(order);
+ s+=xk;
+ xk*=-x/(k+1);
+ //take also truncature errors into account...
+ err+=xk.tailError(order);
+ xk.truncate(order);
+ c+=xk;
+ }
+ if (err<tol){
+ return Piecewise<SBasis>(std::cos(alpha)*c-std::sin(alpha)*s);
+ }
+ }
+ Piecewise<SBasis> c0,c1;
+ c0 = cos(compose(f,Linear(0.,.5)),tol,order);
+ c1 = cos(compose(f,Linear(.5,1.)),tol,order);
+ c0.setDomain(Interval(0.,.5));
+ c1.setDomain(Interval(.5,1.));
+ c0.concat(c1);
+ return c0;
+}
+
+//--1/x------------------------------------------------------------
+//TODO: this implementation is just wrong. Remove or redo!
+
+void truncateResult(Piecewise<SBasis> &f, int order){
+ if (order>=0){
+ for (auto & seg : f.segs){
+ seg.truncate(order);
+ }
+ }
+}
+
+Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol){
+ Piecewise<SBasis> reciprocal_fn;
+ //TODO: deduce R from tol...
+ double R=2.;
+ SBasis reciprocal1_R=reciprocal(Linear(1,R),3);
+ double a=range.min(), b=range.max();
+ if (a*b<0){
+ b=std::max(fabs(a),fabs(b));
+ a=0;
+ }else if (b<0){
+ a=-range.max();
+ b=-range.min();
+ }
+
+ if (a<=tol){
+ reciprocal_fn.push_cut(0);
+ int i0=(int) floor(std::log(tol)/std::log(R));
+ a = std::pow(R,i0);
+ reciprocal_fn.push(Linear(1/a),a);
+ }else{
+ int i0=(int) floor(std::log(a)/std::log(R));
+ a = std::pow(R,i0);
+ reciprocal_fn.cuts.push_back(a);
+ }
+
+ while (a<b){
+ reciprocal_fn.push(reciprocal1_R/a,R*a);
+ a*=R;
+ }
+ if (range.min()<0 || range.max()<0){
+ Piecewise<SBasis>reciprocal_fn_neg;
+ //TODO: define reverse(pw<sb>);
+ reciprocal_fn_neg.cuts.push_back(-reciprocal_fn.cuts.back());
+ for (unsigned i=0; i<reciprocal_fn.size(); i++){
+ int idx=reciprocal_fn.segs.size()-1-i;
+ reciprocal_fn_neg.push_seg(-reverse(reciprocal_fn.segs.at(idx)));
+ reciprocal_fn_neg.push_cut(-reciprocal_fn.cuts.at(idx));
+ }
+ if (range.max()>0){
+ reciprocal_fn_neg.concat(reciprocal_fn);
+ }
+ reciprocal_fn=reciprocal_fn_neg;
+ }
+
+ return(reciprocal_fn);
+}
+
+Piecewise<SBasis> reciprocal(SBasis const &f, double tol, int order){
+ Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(*bounds_fast(f), tol);
+ Piecewise<SBasis> result=compose(reciprocal_fn,f);
+ truncateResult(result,order);
+ return(result);
+}
+Piecewise<SBasis> reciprocal(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(*bounds_fast(f), tol);
+ Piecewise<SBasis> result=compose(reciprocal_fn,f);
+ truncateResult(result,order);
+ return(result);
+}
+
+/**
+ * \brief Returns a Piecewise SBasis with prescribed values at prescribed times.
+ *
+ * \param times: vector of times at which the values are given. Should be sorted in increasing order.
+ * \param values: vector of prescribed values. Should have the same size as times and be sorted accordingly.
+ * \param smoothness: (defaults to 1) regularity class of the result: 0=piecewise linear, 1=continuous derivative, etc...
+ */
+Piecewise<SBasis> interpolate(std::vector<double> times, std::vector<double> values, unsigned smoothness){
+ assert ( values.size() == times.size() );
+ if ( values.empty() ) return Piecewise<SBasis>();
+ if ( values.size() == 1 ) return Piecewise<SBasis>(values[0]);//what about time??
+
+ SBasis sk = shift(Linear(1.),smoothness);
+ SBasis bump_in = integral(sk);
+ bump_in -= bump_in.at0();
+ bump_in /= bump_in.at1();
+ SBasis bump_out = reverse( bump_in );
+
+ Piecewise<SBasis> result;
+ result.cuts.push_back(times[0]);
+ for (unsigned i = 0; i<values.size()-1; i++){
+ result.push(bump_out*values[i]+bump_in*values[i+1],times[i+1]);
+ }
+ return result;
+}
+
+}
+
+/*
+ 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/src/2geom/sbasis-poly.cpp b/src/2geom/sbasis-poly.cpp
new file mode 100644
index 0000000..ffee43f
--- /dev/null
+++ b/src/2geom/sbasis-poly.cpp
@@ -0,0 +1,59 @@
+#include <2geom/sbasis-poly.h>
+
+namespace Geom{
+
+/** Changes the basis of p to be sbasis.
+ \param p the Monomial basis polynomial
+ \returns the Symmetric basis polynomial
+
+This algorithm is horribly slow and numerically terrible. Only for testing.
+*/
+SBasis poly_to_sbasis(Poly const & p) {
+ SBasis x = Linear(0, 1);
+ SBasis r;
+
+ for(int i = p.size()-1; i >= 0; i--) {
+ r = SBasis(Linear(p[i], p[i])) + multiply(x, r);
+ }
+ r.normalize();
+ return r;
+
+}
+
+/** Changes the basis of p to be monomial.
+ \param p the Symmetric basis polynomial
+ \returns the Monomial basis polynomial
+
+This algorithm is horribly slow and numerically terrible. Only for testing.
+*/
+Poly sbasis_to_poly(SBasis const & sb) {
+ if(sb.isZero())
+ return Poly();
+ Poly S; // (1-x)x = -1*x^2 + 1*x + 0
+ Poly A, B;
+ B.push_back(0);
+ B.push_back(1);
+ A.push_back(1);
+ A.push_back(-1);
+ S = A*B;
+ Poly r;
+
+ for(int i = sb.size()-1; i >= 0; i--) {
+ r = S*r + sb[i][0]*A + sb[i][1]*B;
+ }
+ r.normalize();
+ return r;
+}
+
+};
+
+/*
+ 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/src/2geom/sbasis-roots.cpp b/src/2geom/sbasis-roots.cpp
new file mode 100644
index 0000000..ee006d2
--- /dev/null
+++ b/src/2geom/sbasis-roots.cpp
@@ -0,0 +1,656 @@
+/**
+ * @file
+ * @brief Root finding for sbasis functions.
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ * JF Barraud
+ * Copyright 2006-2007 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.
+ *
+ */
+
+ /*
+ * It is more efficient to find roots of f(t) = c_0, c_1, ... all at once, rather than iterating.
+ *
+ * Todo/think about:
+ * multi-roots using bernstein method, one approach would be:
+ sort c
+ take median and find roots of that
+ whenever a segment lies entirely on one side of the median,
+ find the median of the half and recurse.
+
+ in essence we are implementing quicksort on a continuous function
+
+ * the gsl poly roots finder is faster than bernstein too, but we don't use it for 3 reasons:
+
+ a) it requires conversion to poly, which is numerically unstable
+
+ b) it requires gsl (which is currently not a dependency, and would bring in a whole slew of unrelated stuff)
+
+ c) it finds all roots, even complex ones. We don't want to accidentally treat a nearly real root as a real root
+
+From memory gsl poly roots was about 10 times faster than bernstein in the case where all the roots
+are in [0,1] for polys of order 5. I spent some time working out whether eigenvalue root finding
+could be done directly in sbasis space, but the maths was too hard for me. -- njh
+
+jfbarraud: eigenvalue root finding could be done directly in sbasis space ?
+
+njh: I don't know, I think it should. You would make a matrix whose characteristic polynomial was
+correct, but do it by putting the sbasis terms in the right spots in the matrix. normal eigenvalue
+root finding makes a matrix that is a diagonal + a row along the top. This matrix has the property
+that its characteristic poly is just the poly whose coefficients are along the top row.
+
+Now an sbasis function is a linear combination of the poly coeffs. So it seems to me that you
+should be able to put the sbasis coeffs directly into a matrix in the right spots so that the
+characteristic poly is the sbasis. You'll still have problems b) and c).
+
+We might be able to lift an eigenvalue solver and include that directly into 2geom. Eigenvalues
+also allow you to find intersections of multiple curves but require solving n*m x n*m matrices.
+
+ **/
+
+#include <cmath>
+#include <map>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/solver.h>
+
+using namespace std;
+
+namespace Geom{
+
+/** Find the smallest interval that bounds a
+ \param a sbasis function
+ \returns interval
+
+*/
+
+#ifdef USE_SBASIS_OF
+OptInterval bounds_exact(SBasisOf<double> const &a) {
+ Interval result = Interval(a.at0(), a.at1());
+ SBasisOf<double> df = derivative(a);
+ vector<double>extrema = roots(df);
+ for (unsigned i=0; i<extrema.size(); i++){
+ result.extendTo(a(extrema[i]));
+ }
+ return result;
+}
+#else
+OptInterval bounds_exact(SBasis const &a) {
+ Interval result = Interval(a.at0(), a.at1());
+ SBasis df = derivative(a);
+ vector<double>extrema = roots(df);
+ for (double i : extrema){
+ result.expandTo(a(i));
+ }
+ return result;
+}
+#endif
+
+/** Find a small interval that bounds a
+ \param a sbasis function
+ \returns interval
+
+*/
+// I have no idea how this works, some clever bounding argument by jfb.
+#ifdef USE_SBASIS_OF
+OptInterval bounds_fast(const SBasisOf<double> &sb, int order) {
+#else
+OptInterval bounds_fast(const SBasis &sb, int order) {
+#endif
+ Interval res(0,0); // an empty sbasis is 0.
+
+ for(int j = sb.size()-1; j>=order; j--) {
+ double a=sb[j][0];
+ double b=sb[j][1];
+
+ double v, t = 0;
+ v = res.min();
+ if (v<0) t = ((b-a)/v+1)*0.5;
+ if (v>=0 || t<0 || t>1) {
+ res.setMin(std::min(a,b));
+ } else {
+ res.setMin(lerp(t, a+v*t, b));
+ }
+
+ v = res.max();
+ if (v>0) t = ((b-a)/v+1)*0.5;
+ if (v<=0 || t<0 || t>1) {
+ res.setMax(std::max(a,b));
+ }else{
+ res.setMax(lerp(t, a+v*t, b));
+ }
+ }
+ if (order>0) res*=std::pow(.25,order);
+ return res;
+}
+
+/** Find a small interval that bounds a(t) for t in i to order order
+ \param sb sbasis function
+ \param i domain interval
+ \param order number of terms
+ \return interval
+
+*/
+#ifdef USE_SBASIS_OF
+OptInterval bounds_local(const SBasisOf<double> &sb, const OptInterval &i, int order) {
+#else
+OptInterval bounds_local(const SBasis &sb, const OptInterval &i, int order) {
+#endif
+ double t0=i->min(), t1=i->max(), lo=0., hi=0.;
+ for(int j = sb.size()-1; j>=order; j--) {
+ double a=sb[j][0];
+ double b=sb[j][1];
+
+ double t = 0;
+ if (lo<0) t = ((b-a)/lo+1)*0.5;
+ if (lo>=0 || t<t0 || t>t1) {
+ lo = std::min(a*(1-t0)+b*t0+lo*t0*(1-t0),a*(1-t1)+b*t1+lo*t1*(1-t1));
+ }else{
+ lo = lerp(t, a+lo*t, b);
+ }
+
+ if (hi>0) t = ((b-a)/hi+1)*0.5;
+ if (hi<=0 || t<t0 || t>t1) {
+ hi = std::max(a*(1-t0)+b*t0+hi*t0*(1-t0),a*(1-t1)+b*t1+hi*t1*(1-t1));
+ }else{
+ hi = lerp(t, a+hi*t, b);
+ }
+ }
+ Interval res = Interval(lo,hi);
+ if (order>0) res*=std::pow(.25,order);
+ return res;
+}
+
+//-- multi_roots ------------------------------------
+// goal: solve f(t)=c for several c at once.
+/* algo: -compute f at both ends of the given segment [a,b].
+ -compute bounds m<df(t)<M for df on the segment.
+ let c and C be the levels below and above f(a):
+ going from f(a) down to c with slope m takes at least time (f(a)-c)/m
+ going from f(a) up to C with slope M takes at least time (C-f(a))/M
+ From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M).
+ Do the same for b: compute some b' such that there are no roots in (b',b].
+ -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
+ unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
+ making things tricky and unpleasant...
+*/
+//TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots!
+
+
+static int upper_level(vector<double> const &levels,double x,double tol=0.){
+ return(upper_bound(levels.begin(),levels.end(),x-tol)-levels.begin());
+}
+
+#ifdef USE_SBASIS_OF
+static void multi_roots_internal(SBasis const &f,
+ SBasis const &df,
+#else
+static void multi_roots_internal(SBasis const &f,
+ SBasis const &df,
+#endif
+ std::vector<double> const &levels,
+ std::vector<std::vector<double> > &roots,
+ double htol,
+ double vtol,
+ double a,
+ double fa,
+ double b,
+ double fb){
+
+ if (f.isZero(0)){
+ int idx;
+ idx=upper_level(levels,0,vtol);
+ if (idx<(int)levels.size()&&fabs(levels.at(idx))<=vtol){
+ roots[idx].push_back(a);
+ roots[idx].push_back(b);
+ }
+ return;
+ }
+////useful?
+// if (f.size()==1){
+// int idxa=upper_level(levels,fa);
+// int idxb=upper_level(levels,fb);
+// if (fa==fb){
+// if (fa==levels[idxa]){
+// roots[a]=idxa;
+// roots[b]=idxa;
+// }
+// return;
+// }
+// int idx_min=std::min(idxa,idxb);
+// int idx_max=std::max(idxa,idxb);
+// if (idx_max==levels.size()) idx_max-=1;
+// for(int i=idx_min;i<=idx_max; i++){
+// double t=a+(b-a)*(levels[i]-fa)/(fb-fa);
+// if(a<t&&t<b) roots[t]=i;
+// }
+// return;
+// }
+ if ((b-a)<htol){
+ //TODO: use different tol for t and f ?
+ //TODO: unsigned idx ? (remove int casts when fixed)
+ int idx=std::min(upper_level(levels,fa,vtol),upper_level(levels,fb,vtol));
+ if (idx==(int)levels.size()) idx-=1;
+ double c=levels.at(idx);
+ if((fa-c)*(fb-c)<=0||fabs(fa-c)<vtol||fabs(fb-c)<vtol){
+ roots[idx].push_back((a+b)/2);
+ }
+ return;
+ }
+
+ int idxa=upper_level(levels,fa,vtol);
+ int idxb=upper_level(levels,fb,vtol);
+
+ Interval bs = *bounds_local(df,Interval(a,b));
+
+ //first times when a level (higher or lower) can be reached from a or b.
+ double ta_hi,tb_hi,ta_lo,tb_lo;
+ ta_hi=ta_lo=b+1;//default values => no root there.
+ tb_hi=tb_lo=a-1;//default values => no root there.
+
+ if (idxa<(int)levels.size() && fabs(fa-levels.at(idxa))<vtol){//a can be considered a root.
+ //ta_hi=ta_lo=a;
+ roots[idxa].push_back(a);
+ ta_hi=ta_lo=a+htol;
+ }else{
+ if (bs.max()>0 && idxa<(int)levels.size())
+ ta_hi=a+(levels.at(idxa )-fa)/bs.max();
+ if (bs.min()<0 && idxa>0)
+ ta_lo=a+(levels.at(idxa-1)-fa)/bs.min();
+ }
+ if (idxb<(int)levels.size() && fabs(fb-levels.at(idxb))<vtol){//b can be considered a root.
+ //tb_hi=tb_lo=b;
+ roots[idxb].push_back(b);
+ tb_hi=tb_lo=b-htol;
+ }else{
+ if (bs.min()<0 && idxb<(int)levels.size())
+ tb_hi=b+(levels.at(idxb )-fb)/bs.min();
+ if (bs.max()>0 && idxb>0)
+ tb_lo=b+(levels.at(idxb-1)-fb)/bs.max();
+ }
+
+ double t0,t1;
+ t0=std::min(ta_hi,ta_lo);
+ t1=std::max(tb_hi,tb_lo);
+ //hum, rounding errors frighten me! so I add this +tol...
+ if (t0>t1+htol) return;//no root here.
+
+ if (fabs(t1-t0)<htol){
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t0,f(t0),t1,f(t1));
+ }else{
+ double t,t_left,t_right,ft,ft_left,ft_right;
+ t_left =t_right =t =(t0+t1)/2;
+ ft_left=ft_right=ft=f(t);
+ int idx=upper_level(levels,ft,vtol);
+ if (idx<(int)levels.size() && fabs(ft-levels.at(idx))<vtol){//t can be considered a root.
+ roots[idx].push_back(t);
+ //we do not want to count it twice (from the left and from the right)
+ t_left =t-htol/2;
+ t_right=t+htol/2;
+ ft_left =f(t_left);
+ ft_right=f(t_right);
+ }
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t0 ,f(t0) ,t_left,ft_left);
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t_right,ft_right,t1 ,f(t1) );
+ }
+}
+
+/** Solve f(t)=c for several c at once.
+ \param f sbasis function
+ \param levels vector of 'y' values
+ \param htol, vtol
+ \param a, b left and right bounds
+ \returns a vector of vectors, one for each y giving roots
+
+Effectively computes:
+results = roots(f(y_i)) for all y_i
+
+* algo: -compute f at both ends of the given segment [a,b].
+ -compute bounds m<df(t)<M for df on the segment.
+ let c and C be the levels below and above f(a):
+ going from f(a) down to c with slope m takes at least time (f(a)-c)/m
+ going from f(a) up to C with slope M takes at least time (C-f(a))/M
+ From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M).
+ Do the same for b: compute some b' such that there are no roots in (b',b].
+ -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
+ unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
+ making things tricky and unpleasant...
+
+TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots!
+*/
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol,
+ double vtol,
+ double a,
+ double b){
+
+ std::vector<std::vector<double> > roots(levels.size(), std::vector<double>());
+
+ SBasis df=derivative(f);
+ multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b));
+
+ return(roots);
+}
+
+
+static bool compareIntervalMin( Interval I, Interval J ){
+ return I.min()<J.min();
+}
+static bool compareIntervalMax( Interval I, Interval J ){
+ return I.max()<J.max();
+}
+
+//find the first interval whose max is >= x
+static unsigned upper_level(vector<Interval> const &levels, double x ){
+ return( lower_bound( levels.begin(), levels.end(), Interval(x,x), compareIntervalMax) - levels.begin() );
+}
+
+static std::vector<Interval> fuseContiguous(std::vector<Interval> const &sets, double tol=0.){
+ std::vector<Interval> result;
+ if (sets.empty() ) return result;
+ result.push_back( sets.front() );
+ for (unsigned i=1; i < sets.size(); i++ ){
+ if ( result.back().max() + tol >= sets[i].min() ){
+ result.back().unionWith( sets[i] );
+ }else{
+ result.push_back( sets[i] );
+ }
+ }
+ return result;
+}
+
+/** level_sets internal method.
+* algorithm: (~adaptation of Newton method versus 'accroissements finis')
+ -compute f at both ends of the given segment [a,b].
+ -compute bounds m<df(t)<M for df on the segment.
+ Suppose f(a) is between two 'levels' c and C. Then
+ f won't enter c before a + (f(a)-c.max())/m
+ f won't enter C before a + (C.min()-f(a))/M
+ From this we conclude nothing happens before a'=a+min((f(a)-c.max())/m,(C.min()-f(a))/M).
+ We do the same for b: compute some b' such that nothing happens in (b',b].
+ -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
+
+ If f(a) or f(b) belongs to some 'level' C, then use the same argument to find a' or b' such
+ that f remains in C on [a,a'] or [b',b]. In case f is monotonic, we also know f won't enter another
+ level before or after some time, allowing us to restrict the search a little more.
+
+ unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
+ making things tricky and unpleasant...
+*/
+
+static void level_sets_internal(SBasis const &f,
+ SBasis const &df,
+ std::vector<Interval> const &levels,
+ std::vector<std::vector<Interval> > &solsets,
+ double a,
+ double fa,
+ double b,
+ double fb,
+ double tol=1e-5){
+
+ if (f.isZero(0)){
+ unsigned idx;
+ idx=upper_level( levels, 0. );
+ if (idx<levels.size() && levels[idx].contains(0.)){
+ solsets[idx].push_back( Interval(a,b) ) ;
+ }
+ return;
+ }
+
+ unsigned idxa=upper_level(levels,fa);
+ unsigned idxb=upper_level(levels,fb);
+
+ Interval bs = *bounds_local(df,Interval(a,b));
+
+ //first times when a level (higher or lower) can be reached from a or b.
+ double ta_hi; // f remains below next level for t<ta_hi
+ double ta_lo; // f remains above prev level for t<ta_lo
+ double tb_hi; // f remains below next level for t>tb_hi
+ double tb_lo; // f remains above next level for t>tb_lo
+
+ ta_hi=ta_lo=b+1;//default values => no root there.
+ tb_hi=tb_lo=a-1;//default values => no root there.
+
+ //--- if f(a) belongs to a level.-------
+ if ( idxa < levels.size() && levels[idxa].contains( fa ) ){
+ //find the first time when we may exit this level.
+ ta_lo = a + ( levels[idxa].min() - fa)/bs.min();
+ ta_hi = a + ( levels[idxa].max() - fa)/bs.max();
+ if ( ta_lo < a || ta_lo > b ) ta_lo = b;
+ if ( ta_hi < a || ta_hi > b ) ta_hi = b;
+ //move to that time for the next iteration.
+ solsets[idxa].push_back( Interval( a, std::min( ta_lo, ta_hi ) ) );
+ }else{
+ //--- if f(b) does not belong to a level.-------
+ if ( idxa == 0 ){
+ ta_lo = b;
+ }else{
+ ta_lo = a + ( levels[idxa-1].max() - fa)/bs.min();
+ if ( ta_lo < a ) ta_lo = b;
+ }
+ if ( idxa == levels.size() ){
+ ta_hi = b;
+ }else{
+ ta_hi = a + ( levels[idxa].min() - fa)/bs.max();
+ if ( ta_hi < a ) ta_hi = b;
+ }
+ }
+
+ //--- if f(b) belongs to a level.-------
+ if (idxb<levels.size() && levels.at(idxb).contains(fb)){
+ //find the first time from b when we may exit this level.
+ tb_lo = b + ( levels[idxb].min() - fb ) / bs.max();
+ tb_hi = b + ( levels[idxb].max() - fb ) / bs.min();
+ if ( tb_lo > b || tb_lo < a ) tb_lo = a;
+ if ( tb_hi > b || tb_hi < a ) tb_hi = a;
+ //move to that time for the next iteration.
+ solsets[idxb].push_back( Interval( std::max( tb_lo, tb_hi ), b) );
+ }else{
+ //--- if f(b) does not belong to a level.-------
+ if ( idxb == 0 ){
+ tb_lo = a;
+ }else{
+ tb_lo = b + ( levels[idxb-1].max() - fb)/bs.max();
+ if ( tb_lo > b ) tb_lo = a;
+ }
+ if ( idxb == levels.size() ){
+ tb_hi = a;
+ }else{
+ tb_hi = b + ( levels[idxb].min() - fb)/bs.min();
+ if ( tb_hi > b ) tb_hi = a;
+ }
+
+
+ if ( bs.min() < 0 && idxb < levels.size() )
+ tb_hi = b + ( levels[idxb ].min() - fb ) / bs.min();
+ if ( bs.max() > 0 && idxb > 0 )
+ tb_lo = b + ( levels[idxb-1].max() - fb ) / bs.max();
+ }
+
+ //let [t0,t1] be the next interval where to search.
+ double t0=std::min(ta_hi,ta_lo);
+ double t1=std::max(tb_hi,tb_lo);
+
+ if (t0>=t1) return;//no root here.
+
+ //if the interval is smaller than our resolution:
+ //pretend f simultaneously meets all the levels between f(t0) and f(t1)...
+ if ( t1 - t0 <= tol ){
+ Interval f_t0t1 ( f(t0), f(t1) );
+ unsigned idxmin = std::min(idxa, idxb);
+ unsigned idxmax = std::max(idxa, idxb);
+ //push [t0,t1] into all crossed level. Cheat to avoid overlapping intervals on different levels?
+ if ( idxmax > idxmin ){
+ for (unsigned idx = idxmin; idx < idxmax; idx++){
+ solsets[idx].push_back( Interval( t0, t1 ) );
+ }
+ }
+ if ( idxmax < levels.size() && f_t0t1.intersects( levels[idxmax] ) ){
+ solsets[idxmax].push_back( Interval( t0, t1 ) );
+ }
+ return;
+ }
+
+ //To make sure we finally exit the level jump at least by tol:
+ t0 = std::min( std::max( t0, a + tol ), b );
+ t1 = std::max( std::min( t1, b - tol ), a );
+
+ double t =(t0+t1)/2;
+ double ft=f(t);
+ level_sets_internal( f, df, levels, solsets, t0, f(t0), t, ft );
+ level_sets_internal( f, df, levels, solsets, t, ft, t1, f(t1) );
+}
+
+std::vector<std::vector<Interval> > level_sets(SBasis const &f,
+ std::vector<Interval> const &levels,
+ double a, double b, double tol){
+
+ std::vector<std::vector<Interval> > solsets(levels.size(), std::vector<Interval>());
+
+ SBasis df=derivative(f);
+ level_sets_internal(f,df,levels,solsets,a,f(a),b,f(b),tol);
+ // Fuse overlapping intervals...
+ for (auto & solset : solsets){
+ if ( solset.size() == 0 ) continue;
+ std::sort( solset.begin(), solset.end(), compareIntervalMin );
+ solset = fuseContiguous( solset, tol );
+ }
+ return solsets;
+}
+
+std::vector<Interval> level_set (SBasis const &f, double level, double vtol, double a, double b, double tol){
+ Interval fat_level( level - vtol, level + vtol );
+ return level_set(f, fat_level, a, b, tol);
+}
+std::vector<Interval> level_set (SBasis const &f, Interval const &level, double a, double b, double tol){
+ std::vector<Interval> levels(1,level);
+ return level_sets(f,levels, a, b, tol).front() ;
+}
+std::vector<std::vector<Interval> > level_sets (SBasis const &f, std::vector<double> const &levels, double vtol, double a, double b, double tol){
+ std::vector<Interval> fat_levels( levels.size(), Interval());
+ for (unsigned i = 0; i < levels.size(); i++){
+ fat_levels[i] = Interval( levels[i]-vtol, levels[i]+vtol);
+ }
+ return level_sets(f, fat_levels, a, b, tol);
+}
+
+
+//-------------------------------------
+//-------------------------------------
+
+
+void subdiv_sbasis(SBasis const & s,
+ std::vector<double> & roots,
+ double left, double right) {
+ OptInterval bs = bounds_fast(s);
+ if(!bs || bs->min() > 0 || bs->max() < 0)
+ return; // no roots here
+ if(s.tailError(1) < 1e-7) {
+ double t = s[0][0] / (s[0][0] - s[0][1]);
+ roots.push_back(left*(1-t) + t*right);
+ return;
+ }
+ double middle = (left + right)/2;
+ subdiv_sbasis(compose(s, Linear(0, 0.5)), roots, left, middle);
+ subdiv_sbasis(compose(s, Linear(0.5, 1.)), roots, middle, right);
+}
+
+// It is faster to use the bernstein root finder for small degree polynomials (<100?.
+
+std::vector<double> roots1(SBasis const & s) {
+ std::vector<double> res;
+ double d = s[0][0] - s[0][1];
+ if(d != 0) {
+ double r = s[0][0] / d;
+ if(0 <= r && r <= 1)
+ res.push_back(r);
+ }
+ return res;
+}
+
+std::vector<double> roots1(SBasis const & s, Interval const ivl) {
+ std::vector<double> res;
+ double d = s[0][0] - s[0][1];
+ if(d != 0) {
+ double r = s[0][0] / d;
+ if(ivl.contains(r))
+ res.push_back(r);
+ }
+ return res;
+}
+
+/** Find all t s.t s(t) = 0
+ \param a sbasis function
+ \see Bezier::roots
+ \returns vector of zeros (roots)
+
+*/
+std::vector<double> roots(SBasis const & s) {
+ switch(s.size()) {
+ case 0:
+ assert(false);
+ return std::vector<double>();
+ case 1:
+ return roots1(s);
+ default:
+ {
+ Bezier bz;
+ sbasis_to_bezier(bz, s);
+ return bz.roots();
+ }
+ }
+}
+std::vector<double> roots(SBasis const & s, Interval const ivl) {
+ switch(s.size()) {
+ case 0:
+ assert(false);
+ return std::vector<double>();
+ case 1:
+ return roots1(s, ivl);
+ default:
+ {
+ Bezier bz;
+ sbasis_to_bezier(bz, s);
+ return bz.roots(ivl);
+ }
+ }
+}
+
+};
+
+/*
+ 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/src/2geom/sbasis-to-bezier.cpp b/src/2geom/sbasis-to-bezier.cpp
new file mode 100644
index 0000000..5580956
--- /dev/null
+++ b/src/2geom/sbasis-to-bezier.cpp
@@ -0,0 +1,584 @@
+/*
+ * Symmetric Power Basis - Bernstein Basis conversion routines
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * Copyright 2007-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/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/choose.h>
+#include <2geom/path-sink.h>
+#include <2geom/exception.h>
+#include <2geom/convex-hull.h>
+
+#include <iostream>
+
+
+
+
+namespace Geom
+{
+
+/*
+ * Symmetric Power Basis - Bernstein Basis conversion routines
+ *
+ * some remark about precision:
+ * interval [0,1], subdivisions: 10^3
+ * - bezier_to_sbasis : up to degree ~72 precision is at least 10^-5
+ * up to degree ~87 precision is at least 10^-3
+ * - sbasis_to_bezier : up to order ~63 precision is at least 10^-15
+ * precision is at least 10^-14 even beyond order 200
+ *
+ * interval [-1,1], subdivisions: 10^3
+ * - bezier_to_sbasis : up to degree ~21 precision is at least 10^-5
+ * up to degree ~24 precision is at least 10^-3
+ * - sbasis_to_bezier : up to order ~11 precision is at least 10^-5
+ * up to order ~13 precision is at least 10^-3
+ *
+ * interval [-10,10], subdivisions: 10^3
+ * - bezier_to_sbasis : up to degree ~7 precision is at least 10^-5
+ * up to degree ~8 precision is at least 10^-3
+ * - sbasis_to_bezier : up to order ~3 precision is at least 10^-5
+ * up to order ~4 precision is at least 10^-3
+ *
+ * references:
+ * this implementation is based on the following article:
+ * J.Sanchez-Reyes - The Symmetric Analogue of the Polynomial Power Basis
+ */
+
+/** Changes the basis of p to be bernstein.
+ \param p the Symmetric basis polynomial
+ \returns the Bernstein basis polynomial
+
+ if the degree is even q is the order in the symmetrical power basis,
+ if the degree is odd q is the order + 1
+ n is always the polynomial degree, i. e. the Bezier order
+ sz is the number of bezier handles.
+*/
+void sbasis_to_bezier (Bezier & bz, SBasis const& sb, size_t sz)
+{
+ assert(sb.size() > 0);
+
+ size_t q, n;
+ bool even;
+ if (sz == 0)
+ {
+ q = sb.size();
+ if (sb[q-1][0] == sb[q-1][1])
+ {
+ even = true;
+ --q;
+ n = 2*q;
+ }
+ else
+ {
+ even = false;
+ n = 2*q-1;
+ }
+ }
+ else
+ {
+ q = (sz > 2*sb.size()-1) ? sb.size() : (sz+1)/2;
+ n = sz-1;
+ even = false;
+ }
+ bz.clear();
+ bz.resize(n+1);
+ for (size_t k = 0; k < q; ++k)
+ {
+ int Tjk = 1;
+ for (size_t j = k; j < n-k; ++j) // j <= n-k-1
+ {
+ bz[j] += (Tjk * sb[k][0]);
+ bz[n-j] += (Tjk * sb[k][1]); // n-k <-> [k][1]
+ // assert(Tjk == binomial(n-2*k-1, j-k));
+ binomial_increment_k(Tjk, n-2*k-1, j-k);
+ }
+ }
+ if (even)
+ {
+ bz[q] += sb[q][0];
+ }
+ // the resulting coefficients are with respect to the scaled Bernstein
+ // basis so we need to divide them by (n, j) binomial coefficient
+ int bcj = n;
+ for (size_t j = 1; j < n; ++j)
+ {
+ bz[j] /= bcj;
+ // assert(bcj == binomial(n, j));
+ binomial_increment_k(bcj, n, j);
+ }
+ bz[0] = sb[0][0];
+ bz[n] = sb[0][1];
+}
+
+void sbasis_to_bezier(D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz)
+{
+ if (sz == 0) {
+ sz = std::max(sb[X].size(), sb[Y].size())*2;
+ }
+ sbasis_to_bezier(bz[X], sb[X], sz);
+ sbasis_to_bezier(bz[Y], sb[Y], sz);
+}
+
+/** Changes the basis of p to be Bernstein.
+ \param p the D2 Symmetric basis polynomial
+ \returns the D2 Bernstein basis polynomial
+
+ sz is always the polynomial degree, i. e. the Bezier order
+*/
+void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz)
+{
+ D2<Bezier> bez;
+ sbasis_to_bezier(bez, sb, sz);
+ bz = bezier_points(bez);
+}
+
+/** Changes the basis of p to be Bernstein.
+ \param p the D2 Symmetric basis polynomial
+ \returns the D2 Bernstein basis cubic polynomial
+
+Bezier is always cubic.
+For general asymmetric case, fit the SBasis function value at midpoint
+For parallel, symmetric case, find the point of closest approach to the midpoint
+For parallel, anti-symmetric case, fit the SBasis slope at midpoint
+*/
+void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb)
+{
+ double delx[2], dely[2];
+ double xprime[2], yprime[2];
+ double midx = 0;
+ double midy = 0;
+ double midx_0, midy_0;
+ double numer[2], numer_0[2];
+ double denom;
+ double div;
+
+ if ((sb[X].size() == 0) || (sb[Y].size() == 0)) {
+ THROW_RANGEERROR("size of sb is too small");
+ }
+
+ sbasis_to_bezier(bz, sb, 4); // zeroth-order estimate
+ if ((sb[X].size() < 3) && (sb[Y].size() < 3))
+ return; // cubic bezier estimate is exact
+ Geom::ConvexHull bezhull(bz);
+
+// calculate first derivatives of x and y wrt t
+
+ for (int i = 0; i < 2; ++i) {
+ xprime[i] = sb[X][0][1] - sb[X][0][0];
+ yprime[i] = sb[Y][0][1] - sb[Y][0][0];
+ }
+ if (sb[X].size() > 1) {
+ xprime[0] += sb[X][1][0];
+ xprime[1] -= sb[X][1][1];
+ }
+ if (sb[Y].size() > 1) {
+ yprime[0] += sb[Y][1][0];
+ yprime[1] -= sb[Y][1][1];
+ }
+
+// calculate midpoint at t = 0.5
+
+ div = 2;
+ for (auto i : sb[X]) {
+ midx += (i[0] + i[1])/div;
+ div *= 4;
+ }
+
+ div = 2;
+ for (auto i : sb[Y]) {
+ midy += (i[0] + i[1])/div;
+ div *= 4;
+ }
+
+// is midpoint in hull: if not, the solution will be ill-conditioned, LP Bug 1428683
+
+ if (!bezhull.contains(Geom::Point(midx, midy)))
+ return;
+
+// calculate Bezier control arms
+
+ midx = 8*midx - 4*bz[0][X] - 4*bz[3][X]; // re-define relative to center
+ midy = 8*midy - 4*bz[0][Y] - 4*bz[3][Y];
+ midx_0 = sb[X].size() > 1 ? sb[X][1][0] + sb[X][1][1] : 0; // zeroth order estimate
+ midy_0 = sb[Y].size() > 1 ? sb[Y][1][0] + sb[Y][1][1] : 0;
+
+ if ((std::abs(xprime[0]) < EPSILON) && (std::abs(yprime[0]) < EPSILON)
+ && ((std::abs(xprime[1]) > EPSILON) || (std::abs(yprime[1]) > EPSILON))) { // degenerate handle at 0 : use distance of closest approach
+ numer[0] = midx*xprime[1] + midy*yprime[1];
+ denom = 3.0*(xprime[1]*xprime[1] + yprime[1]*yprime[1]);
+ delx[0] = 0;
+ dely[0] = 0;
+ delx[1] = -xprime[1]*numer[0]/denom;
+ dely[1] = -yprime[1]*numer[0]/denom;
+ } else if ((std::abs(xprime[1]) < EPSILON) && (std::abs(yprime[1]) < EPSILON)
+ && ((std::abs(xprime[0]) > EPSILON) || (std::abs(yprime[0]) > EPSILON))) { // degenerate handle at 1 : ditto
+ numer[1] = midx*xprime[0] + midy*yprime[0];
+ denom = 3.0*(xprime[0]*xprime[0] + yprime[0]*yprime[0]);
+ delx[0] = xprime[0]*numer[1]/denom;
+ dely[0] = yprime[0]*numer[1]/denom;
+ delx[1] = 0;
+ dely[1] = 0;
+ } else if (std::abs(xprime[1]*yprime[0] - yprime[1]*xprime[0]) > // general case : fit mid fxn value
+ 0.002 * std::abs(xprime[1]*xprime[0] + yprime[1]*yprime[0])) { // approx. 0.1 degree of angle
+ double test1 = (bz[1][Y] - bz[0][Y])*(bz[3][X] - bz[0][X]) - (bz[1][X] - bz[0][X])*(bz[3][Y] - bz[0][Y]);
+ double test2 = (bz[2][Y] - bz[0][Y])*(bz[3][X] - bz[0][X]) - (bz[2][X] - bz[0][X])*(bz[3][Y] - bz[0][Y]);
+ if (test1*test2 < 0) // reject anti-symmetric case, LP Bug 1428267 & Bug 1428683
+ return;
+ denom = 3.0*(xprime[1]*yprime[0] - yprime[1]*xprime[0]);
+ for (int i = 0; i < 2; ++i) {
+ numer_0[i] = xprime[1 - i]*midy_0 - yprime[1 - i]*midx_0;
+ numer[i] = xprime[1 - i]*midy - yprime[1 - i]*midx;
+ delx[i] = xprime[i]*numer[i]/denom;
+ dely[i] = yprime[i]*numer[i]/denom;
+ if (numer_0[i]*numer[i] < 0) // check for reversal of direction, LP Bug 1544680
+ return;
+ }
+ if (std::abs((numer[0] - numer_0[0])*numer_0[1]) > 10.0*std::abs((numer[1] - numer_0[1])*numer_0[0]) // check for asymmetry
+ || std::abs((numer[1] - numer_0[1])*numer_0[0]) > 10.0*std::abs((numer[0] - numer_0[0])*numer_0[1]))
+ return;
+ } else if ((xprime[0]*xprime[1] < 0) || (yprime[0]*yprime[1] < 0)) { // symmetric case : use distance of closest approach
+ numer[0] = midx*xprime[0] + midy*yprime[0];
+ denom = 6.0*(xprime[0]*xprime[0] + yprime[0]*yprime[0]);
+ delx[0] = xprime[0]*numer[0]/denom;
+ dely[0] = yprime[0]*numer[0]/denom;
+ delx[1] = -delx[0];
+ dely[1] = -dely[0];
+ } else { // anti-symmetric case : fit mid slope
+ // calculate slope at t = 0.5
+ midx = 0;
+ div = 1;
+ for (auto i : sb[X]) {
+ midx += (i[1] - i[0])/div;
+ div *= 4;
+ }
+ midy = 0;
+ div = 1;
+ for (auto i : sb[Y]) {
+ midy += (i[1] - i[0])/div;
+ div *= 4;
+ }
+ if (midx*yprime[0] != midy*xprime[0]) {
+ denom = midx*yprime[0] - midy*xprime[0];
+ numer[0] = midx*(bz[3][Y] - bz[0][Y]) - midy*(bz[3][X] - bz[0][X]);
+ for (int i = 0; i < 2; ++i) {
+ delx[i] = xprime[0]*numer[0]/denom;
+ dely[i] = yprime[0]*numer[0]/denom;
+ }
+ } else { // linear case
+ for (int i = 0; i < 2; ++i) {
+ delx[i] = (bz[3][X] - bz[0][X])/3;
+ dely[i] = (bz[3][Y] - bz[0][Y])/3;
+ }
+ }
+ }
+ bz[1][X] = bz[0][X] + delx[0];
+ bz[1][Y] = bz[0][Y] + dely[0];
+ bz[2][X] = bz[3][X] - delx[1];
+ bz[2][Y] = bz[3][Y] - dely[1];
+}
+
+/** Changes the basis of p to be sbasis.
+ \param p the Bernstein basis polynomial
+ \returns the Symmetric basis polynomial
+
+ if the degree is even q is the order in the symmetrical power basis,
+ if the degree is odd q is the order + 1
+ n is always the polynomial degree, i. e. the Bezier order
+*/
+void bezier_to_sbasis (SBasis & sb, Bezier const& bz)
+{
+ size_t n = bz.order();
+ size_t q = (n+1) / 2;
+ size_t even = (n & 1u) ? 0 : 1;
+ sb.clear();
+ sb.resize(q + even, Linear(0, 0));
+ int nck = 1;
+ for (size_t k = 0; k < q; ++k)
+ {
+ int Tjk = nck;
+ for (size_t j = k; j < q; ++j)
+ {
+ sb[j][0] += (Tjk * bz[k]);
+ sb[j][1] += (Tjk * bz[n-k]); // n-j <-> [j][1]
+ // assert(Tjk == sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k));
+ binomial_increment_k(Tjk, n-j-k, j-k);
+ binomial_decrement_n(Tjk, n-j-k, j-k+1);
+ Tjk = -Tjk;
+ }
+ Tjk = -nck;
+ for (size_t j = k+1; j < q; ++j)
+ {
+ sb[j][0] += (Tjk * bz[n-k]);
+ sb[j][1] += (Tjk * bz[k]); // n-j <-> [j][1]
+ // assert(Tjk == sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k));
+ binomial_increment_k(Tjk, n-j-k-1, j-k-1);
+ binomial_decrement_n(Tjk, n-j-k-1, j-k);
+ Tjk = -Tjk;
+ }
+ // assert(nck == binomial(n, k));
+ binomial_increment_k(nck, n, k);
+ }
+ if (even)
+ {
+ int Tjk = q & 1 ? -1 : 1;
+ for (size_t k = 0; k < q; ++k)
+ {
+ sb[q][0] += (Tjk * (bz[k] + bz[n-k]));
+ // assert(Tjk == sgn(q,k) * binomial(n, k));
+ binomial_increment_k(Tjk, n, k);
+ Tjk = -Tjk;
+ }
+ // assert(Tjk == binomial(n, q));
+ sb[q][0] += Tjk * bz[q];
+ sb[q][1] = sb[q][0];
+ }
+ sb[0][0] = bz[0];
+ sb[0][1] = bz[n];
+}
+
+/** Changes the basis of d2 p to be sbasis.
+ \param p the d2 Bernstein basis polynomial
+ \returns the d2 Symmetric basis polynomial
+
+ if the degree is even q is the order in the symmetrical power basis,
+ if the degree is odd q is the order + 1
+ n is always the polynomial degree, i. e. the Bezier order
+*/
+void bezier_to_sbasis (D2<SBasis> & sb, std::vector<Point> const& bz)
+{
+ size_t n = bz.size() - 1;
+ size_t q = (n+1) / 2;
+ size_t even = (n & 1u) ? 0 : 1;
+ sb[X].clear();
+ sb[Y].clear();
+ sb[X].resize(q + even, Linear(0, 0));
+ sb[Y].resize(q + even, Linear(0, 0));
+ int nck = 1;
+ for (size_t k = 0; k < q; ++k)
+ {
+ int Tjk = nck;
+ for (size_t j = k; j < q; ++j)
+ {
+ sb[X][j][0] += (Tjk * bz[k][X]);
+ sb[X][j][1] += (Tjk * bz[n-k][X]);
+ sb[Y][j][0] += (Tjk * bz[k][Y]);
+ sb[Y][j][1] += (Tjk * bz[n-k][Y]);
+ // assert(Tjk == sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k));
+ binomial_increment_k(Tjk, n-j-k, j-k);
+ binomial_decrement_n(Tjk, n-j-k, j-k+1);
+ Tjk = -Tjk;
+ }
+ Tjk = -nck;
+ for (size_t j = k+1; j < q; ++j)
+ {
+ sb[X][j][0] += (Tjk * bz[n-k][X]);
+ sb[X][j][1] += (Tjk * bz[k][X]);
+ sb[Y][j][0] += (Tjk * bz[n-k][Y]);
+ sb[Y][j][1] += (Tjk * bz[k][Y]);
+ // assert(Tjk == sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k));
+ binomial_increment_k(Tjk, n-j-k-1, j-k-1);
+ binomial_decrement_n(Tjk, n-j-k-1, j-k);
+ Tjk = -Tjk;
+ }
+ // assert(nck == binomial(n, k));
+ binomial_increment_k(nck, n, k);
+ }
+ if (even)
+ {
+ int Tjk = q & 1 ? -1 : 1;
+ for (size_t k = 0; k < q; ++k)
+ {
+ sb[X][q][0] += (Tjk * (bz[k][X] + bz[n-k][X]));
+ sb[Y][q][0] += (Tjk * (bz[k][Y] + bz[n-k][Y]));
+ // assert(Tjk == sgn(q,k) * binomial(n, k));
+ binomial_increment_k(Tjk, n, k);
+ Tjk = -Tjk;
+ }
+ // assert(Tjk == binomial(n, q));
+ sb[X][q][0] += Tjk * bz[q][X];
+ sb[X][q][1] = sb[X][q][0];
+ sb[Y][q][0] += Tjk * bz[q][Y];
+ sb[Y][q][1] = sb[Y][q][0];
+ }
+ sb[X][0][0] = bz[0][X];
+ sb[X][0][1] = bz[n][X];
+ sb[Y][0][0] = bz[0][Y];
+ sb[Y][0][1] = bz[n][Y];
+}
+
+} // namespace Geom
+
+#if 0
+/*
+* This version works by inverting a reasonable upper bound on the error term after subdividing the
+* curve at $a$. We keep biting off pieces until there is no more curve left.
+*
+* Derivation: The tail of the power series is $a_ks^k + a_{k+1}s^{k+1} + \ldots = e$. A
+* subdivision at $a$ results in a tail error of $e*A^k, A = (1-a)a$. Let this be the desired
+* tolerance tol $= e*A^k$ and invert getting $A = e^{1/k}$ and $a = 1/2 - \sqrt{1/4 - A}$
+*/
+void
+subpath_from_sbasis_incremental(Geom::OldPathSetBuilder &pb, D2<SBasis> B, double tol, bool initial) {
+ const unsigned k = 2; // cubic bezier
+ double te = B.tail_error(k);
+ assert(B[0].std::isfinite());
+ assert(B[1].std::isfinite());
+
+ //std::cout << "tol = " << tol << std::endl;
+ while(1) {
+ double A = std::sqrt(tol/te); // pow(te, 1./k)
+ double a = A;
+ if(A < 1) {
+ A = std::min(A, 0.25);
+ a = 0.5 - std::sqrt(0.25 - A); // quadratic formula
+ if(a > 1) a = 1; // clamp to the end of the segment
+ } else
+ a = 1;
+ assert(a > 0);
+ //std::cout << "te = " << te << std::endl;
+ //std::cout << "A = " << A << "; a=" << a << std::endl;
+ D2<SBasis> Bs = compose(B, Linear(0, a));
+ assert(Bs.tail_error(k));
+ std::vector<Geom::Point> bez = sbasis_to_bezier(Bs, 2);
+ reverse(bez.begin(), bez.end());
+ if (initial) {
+ pb.start_subpath(bez[0]);
+ initial = false;
+ }
+ pb.push_cubic(bez[1], bez[2], bez[3]);
+
+// move to next piece of curve
+ if(a >= 1) break;
+ B = compose(B, Linear(a, 1));
+ te = B.tail_error(k);
+ }
+}
+
+#endif
+
+namespace Geom{
+
+/** Make a path from a d2 sbasis.
+ \param p the d2 Symmetric basis polynomial
+ \returns a Path
+
+ If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves.
+*/
+void build_from_sbasis(Geom::PathBuilder &pb, D2<SBasis> const &B, double tol, bool only_cubicbeziers) {
+ if (!B.isFinite()) {
+ THROW_EXCEPTION("assertion failed: B.isFinite()");
+ }
+ if(tail_error(B, 3) < tol || sbasis_size(B) == 2) { // nearly cubic enough
+ if( !only_cubicbeziers && (sbasis_size(B) <= 1) ) {
+ pb.lineTo(B.at1());
+ } else {
+ std::vector<Geom::Point> bez;
+// sbasis_to_bezier(bez, B, 4);
+ sbasis_to_cubic_bezier(bez, B);
+ pb.curveTo(bez[1], bez[2], bez[3]);
+ }
+ } else {
+ build_from_sbasis(pb, compose(B, Linear(0, 0.5)), tol, only_cubicbeziers);
+ build_from_sbasis(pb, compose(B, Linear(0.5, 1)), tol, only_cubicbeziers);
+ }
+}
+
+/** Make a path from a d2 sbasis.
+ \param p the d2 Symmetric basis polynomial
+ \returns a Path
+
+ If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves.
+*/
+Path
+path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers) {
+ PathBuilder pb;
+ pb.moveTo(B.at0());
+ build_from_sbasis(pb, B, tol, only_cubicbeziers);
+ pb.flush();
+ return pb.peek().front();
+}
+
+/** Make a path from a d2 sbasis.
+ \param p the d2 Symmetric basis polynomial
+ \returns a Path
+
+ If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves.
+ TODO: some of this logic should be lifted into svg-path
+*/
+PathVector
+path_from_piecewise(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol, bool only_cubicbeziers) {
+ Geom::PathBuilder pb;
+ if(B.size() == 0) return pb.peek();
+ Geom::Point start = B[0].at0();
+ pb.moveTo(start);
+ for(unsigned i = 0; ; i++) {
+ if ( (i+1 == B.size())
+ || !are_near(B[i+1].at0(), B[i].at1(), tol) )
+ {
+ //start of a new path
+ if (are_near(start, B[i].at1()) && sbasis_size(B[i]) <= 1) {
+ pb.closePath();
+ //last line seg already there (because of .closePath())
+ goto no_add;
+ }
+ build_from_sbasis(pb, B[i], tol, only_cubicbeziers);
+ if (are_near(start, B[i].at1())) {
+ //it's closed, the last closing segment was not a straight line so it needed to be added, but still make it closed here with degenerate straight line.
+ pb.closePath();
+ }
+ no_add:
+ if (i+1 >= B.size()) {
+ break;
+ }
+ start = B[i+1].at0();
+ pb.moveTo(start);
+ } else {
+ build_from_sbasis(pb, B[i], tol, only_cubicbeziers);
+ }
+ }
+ pb.flush();
+ return pb.peek();
+}
+
+}
+
+/*
+ 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/src/2geom/sbasis.cpp b/src/2geom/sbasis.cpp
new file mode 100644
index 0000000..ceaae3f
--- /dev/null
+++ b/src/2geom/sbasis.cpp
@@ -0,0 +1,681 @@
+/*
+ * sbasis.cpp - S-power basis function class + supporting classes
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 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 <cmath>
+
+#include <2geom/sbasis.h>
+#include <2geom/math-utils.h>
+
+namespace Geom {
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+/** bound the error from term truncation
+ \param tail first term to chop
+ \returns the largest possible error this truncation could give
+*/
+double SBasis::tailError(unsigned tail) const {
+ Interval bs = *bounds_fast(*this, tail);
+ return std::max(fabs(bs.min()),fabs(bs.max()));
+}
+
+/** test all coefficients are finite
+*/
+bool SBasis::isFinite() const {
+ for(unsigned i = 0; i < size(); i++) {
+ if(!(*this)[i].isFinite())
+ return false;
+ }
+ return true;
+}
+
+/** Compute the value and the first n derivatives
+ \param t position to evaluate
+ \param n number of derivatives (not counting value)
+ \returns a vector with the value and the n derivative evaluations
+
+There is an elegant way to compute the value and n derivatives for a polynomial using a variant of horner's rule. Someone will someday work out how for sbasis.
+*/
+std::vector<double> SBasis::valueAndDerivatives(double t, unsigned n) const {
+ std::vector<double> ret(n+1);
+ ret[0] = valueAt(t);
+ SBasis tmp = *this;
+ for(unsigned i = 1; i < n+1; i++) {
+ tmp.derive();
+ ret[i] = tmp.valueAt(t);
+ }
+ return ret;
+}
+
+
+/** Compute the pointwise sum of a and b (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a+b
+
+*/
+SBasis operator+(const SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ SBasis result(out_size, Linear());
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result[i] = a[i] + b[i];
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result[i] = a[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ result[i] = b[i];
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+/** Compute the pointwise difference of a and b (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a-b
+
+*/
+SBasis operator-(const SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ SBasis result(out_size, Linear());
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result[i] = a[i] - b[i];
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result[i] = a[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ result[i] = -b[i];
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+/** Compute the pointwise sum of a and b and store in a (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a+b
+
+*/
+SBasis& operator+=(SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ a.resize(out_size);
+
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] += b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a[i] = b[i];
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+/** Compute the pointwise difference of a and b and store in a (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a-b
+
+*/
+SBasis& operator-=(SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ a.resize(out_size);
+
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] -= b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a[i] = -b[i];
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+/** Compute the pointwise product of a and b (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a*b
+
+*/
+SBasis operator*(SBasis const &a, double k) {
+ SBasis c(a.size(), Linear());
+ for(unsigned i = 0; i < a.size(); i++)
+ c[i] = a[i] * k;
+ return c;
+}
+
+/** Compute the pointwise product of a and b and store the value in a (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a*b
+
+*/
+SBasis& operator*=(SBasis& a, double b) {
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(auto & i : a)
+ i *= b;
+ return a;
+}
+
+/** multiply a by x^sh in place (Exact)
+ \param a sbasis function
+ \param sh power
+ \returns a
+
+*/
+SBasis shift(SBasis const &a, int sh) {
+ size_t n = a.size()+sh;
+ SBasis c(n, Linear());
+ size_t m = std::max(0, sh);
+
+ for(int i = 0; i < sh; i++)
+ c[i] = Linear(0,0);
+ for(size_t i = m, j = std::max(0,-sh); i < n; i++, j++)
+ c[i] = a[j];
+ return c;
+}
+
+/** multiply a by x^sh (Exact)
+ \param a linear function
+ \param sh power
+ \returns a* x^sh
+
+*/
+SBasis shift(Linear const &a, int sh) {
+ size_t n = 1+sh;
+ SBasis c(n, Linear());
+
+ for(int i = 0; i < sh; i++)
+ c[i] = Linear(0,0);
+ if(sh >= 0)
+ c[sh] = a;
+ return c;
+}
+
+#if 0
+SBasis multiply(SBasis const &a, SBasis const &b) {
+ // c = {a0*b0 - shift(1, a.Tri*b.Tri), a1*b1 - shift(1, a.Tri*b.Tri)}
+
+ // shift(1, a.Tri*b.Tri)
+ SBasis c(a.size() + b.size(), Linear(0,0));
+ if(a.isZero() || b.isZero())
+ return c;
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ double tri = b[j].tri()*a[i-j].tri();
+ c[i+1/*shift*/] += Linear(-tri);
+ }
+ }
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ for(unsigned dim = 0; dim < 2; dim++)
+ c[i][dim] += b[j][dim]*a[i-j][dim];
+ }
+ }
+ c.normalize();
+ //assert(!(0 == c.back()[0] && 0 == c.back()[1]));
+ return c;
+}
+#else
+
+/** Compute the pointwise product of a and b adding c (Exact)
+ \param a,b,c sbasis functions
+ \returns sbasis equal to a*b+c
+
+The added term is almost free
+*/
+SBasis multiply_add(SBasis const &a, SBasis const &b, SBasis c) {
+ if(a.isZero() || b.isZero())
+ return c;
+ c.resize(a.size() + b.size(), Linear(0,0));
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ double tri = b[j].tri()*a[i-j].tri();
+ c[i+1/*shift*/] += Linear(-tri);
+ }
+ }
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ for(unsigned dim = 0; dim < 2; dim++)
+ c[i][dim] += b[j][dim]*a[i-j][dim];
+ }
+ }
+ c.normalize();
+ //assert(!(0 == c.back()[0] && 0 == c.back()[1]));
+ return c;
+}
+
+/** Compute the pointwise product of a and b (Exact)
+ \param a,b sbasis functions
+ \returns sbasis equal to a*b
+
+*/
+SBasis multiply(SBasis const &a, SBasis const &b) {
+ if(a.isZero() || b.isZero()) {
+ SBasis c(1, Linear(0,0));
+ return c;
+ }
+ SBasis c(a.size() + b.size(), Linear(0,0));
+ return multiply_add(a, b, c);
+}
+#endif
+/** Compute the integral of a (Exact)
+ \param a sbasis functions
+ \returns sbasis integral(a)
+
+*/
+SBasis integral(SBasis const &c) {
+ SBasis a;
+ a.resize(c.size() + 1, Linear(0,0));
+ a[0] = Linear(0,0);
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ double ahat = -c[k-1].tri()/(2*k);
+ a[k][0] = a[k][1] = ahat;
+ }
+ double aTri = 0;
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (c[k].hat() + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+
+/** Compute the derivative of a (Exact)
+ \param a sbasis functions
+ \returns sbasis da/dt
+
+*/
+SBasis derivative(SBasis const &a) {
+ SBasis c;
+ c.resize(a.size(), Linear(0,0));
+ if(a.isZero())
+ return c;
+
+ for(unsigned k = 0; k < a.size()-1; k++) {
+ double d = (2*k+1)*(a[k][1] - a[k][0]);
+
+ c[k][0] = d + (k+1)*a[k+1][0];
+ c[k][1] = d - (k+1)*a[k+1][1];
+ }
+ int k = a.size()-1;
+ double d = (2*k+1)*(a[k][1] - a[k][0]);
+ if (d == 0 && k > 0) {
+ c.pop_back();
+ } else {
+ c[k][0] = d;
+ c[k][1] = d;
+ }
+
+ return c;
+}
+
+/** Compute the derivative of this inplace (Exact)
+
+*/
+void SBasis::derive() { // in place version
+ if(isZero()) return;
+ for(unsigned k = 0; k < size()-1; k++) {
+ double d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+
+ (*this)[k][0] = d + (k+1)*(*this)[k+1][0];
+ (*this)[k][1] = d - (k+1)*(*this)[k+1][1];
+ }
+ int k = size()-1;
+ double d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+ if (d == 0 && k > 0) {
+ pop_back();
+ } else {
+ (*this)[k][0] = d;
+ (*this)[k][1] = d;
+ }
+}
+
+/** Compute the sqrt of a
+ \param a sbasis functions
+ \returns sbasis \f[ \sqrt{a} \f]
+
+It is recommended to use the piecewise version unless you have good reason.
+TODO: convert int k to unsigned k, and remove cast
+*/
+SBasis sqrt(SBasis const &a, int k) {
+ SBasis c;
+ if(a.isZero() || k == 0)
+ return c;
+ c.resize(k, Linear(0,0));
+ c[0] = Linear(std::sqrt(a[0][0]), std::sqrt(a[0][1]));
+ SBasis r = a - multiply(c, c); // remainder
+
+ for(unsigned i = 1; i <= (unsigned)k && i<r.size(); i++) {
+ Linear ci(r[i][0]/(2*c[0][0]), r[i][1]/(2*c[0][1]));
+ SBasis cisi = shift(ci, i);
+ r -= multiply(shift((c*2 + cisi), i), SBasis(ci));
+ r.truncate(k+1);
+ c += cisi;
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+
+ return c;
+}
+
+/** Compute the recpirocal of a
+ \param a sbasis functions
+ \returns sbasis 1/a
+
+It is recommended to use the piecewise version unless you have good reason.
+*/
+SBasis reciprocal(Linear const &a, int k) {
+ SBasis c;
+ assert(!a.isZero());
+ c.resize(k, Linear(0,0));
+ double r_s0 = (a.tri()*a.tri())/(-a[0]*a[1]);
+ double r_s0k = 1;
+ for(unsigned i = 0; i < (unsigned)k; i++) {
+ c[i] = Linear(r_s0k/a[0], r_s0k/a[1]);
+ r_s0k *= r_s0;
+ }
+ return c;
+}
+
+/** Compute a / b to k terms
+ \param a,b sbasis functions
+ \returns sbasis a/b
+
+It is recommended to use the piecewise version unless you have good reason.
+*/
+SBasis divide(SBasis const &a, SBasis const &b, int k) {
+ SBasis c;
+ assert(!a.isZero());
+ SBasis r = a; // remainder
+
+ k++;
+ r.resize(k, Linear(0,0));
+ c.resize(k, Linear(0,0));
+
+ for(unsigned i = 0; i < (unsigned)k; i++) {
+ Linear ci(r[i][0]/b[0][0], r[i][1]/b[0][1]); //H0
+ c[i] += ci;
+ r -= shift(multiply(ci,b), i);
+ r.truncate(k+1);
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+
+ return c;
+}
+
+/** Compute a composed with b
+ \param a,b sbasis functions
+ \returns sbasis a(b(t))
+
+ return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k
+*/
+SBasis compose(SBasis const &a, SBasis const &b) {
+ SBasis s = multiply((SBasis(Linear(1,1))-b), b);
+ SBasis r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasis(Linear(a[i][0])) - b*a[i][0] + b*a[i][1]);
+ }
+ return r;
+}
+
+/** Compute a composed with b to k terms
+ \param a,b sbasis functions
+ \returns sbasis a(b(t))
+
+ return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k
+*/
+SBasis compose(SBasis const &a, SBasis const &b, unsigned k) {
+ SBasis s = multiply((SBasis(Linear(1,1))-b), b);
+ SBasis r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasis(Linear(a[i][0])) - b*a[i][0] + b*a[i][1]);
+ }
+ r.truncate(k);
+ return r;
+}
+
+SBasis portion(const SBasis &t, double from, double to) {
+ double fv = t.valueAt(from);
+ double tv = t.valueAt(to);
+ SBasis ret = compose(t, Linear(from, to));
+ ret.at0() = fv;
+ ret.at1() = tv;
+ return ret;
+}
+
+/*
+Inversion algorithm. The notation is certainly very misleading. The
+pseudocode should say:
+
+c(v) := 0
+r(u) := r_0(u) := u
+for i:=0 to k do
+ c_i(v) := H_0(r_i(u)/(t_1)^i; u)
+ c(v) := c(v) + c_i(v)*t^i
+ r(u) := r(u) ? c_i(u)*(t(u))^i
+endfor
+*/
+
+//#define DEBUG_INVERSION 1
+
+/** find the function a^-1 such that a^-1 composed with a to k terms is the identity function
+ \param a sbasis function
+ \returns sbasis a^-1 s.t. a^-1(a(t)) = 1
+
+ The function must have 'unit range'("a00 = 0 and a01 = 1") and be monotonic.
+*/
+SBasis inverse(SBasis a, int k) {
+ assert(a.size() > 0);
+ double a0 = a[0][0];
+ if(a0 != 0) {
+ a -= a0;
+ }
+ double a1 = a[0][1];
+ assert(a1 != 0); // not invertable.
+
+ if(a1 != 1) {
+ a /= a1;
+ }
+ SBasis c(k, Linear()); // c(v) := 0
+ if(a.size() >= 2 && k == 2) {
+ c[0] = Linear(0,1);
+ Linear t1(1+a[1][0], 1-a[1][1]); // t_1
+ c[1] = Linear(-a[1][0]/t1[0], -a[1][1]/t1[1]);
+ } else if(a.size() >= 2) { // non linear
+ SBasis r = Linear(0,1); // r(u) := r_0(u) := u
+ Linear t1(1./(1+a[1][0]), 1./(1-a[1][1])); // 1./t_1
+ Linear one(1,1);
+ Linear t1i = one; // t_1^0
+ SBasis one_minus_a = SBasis(one) - a;
+ SBasis t = multiply(one_minus_a, a); // t(u)
+ SBasis ti(one); // t(u)^0
+#ifdef DEBUG_INVERSION
+ std::cout << "a=" << a << std::endl;
+ std::cout << "1-a=" << one_minus_a << std::endl;
+ std::cout << "t1=" << t1 << std::endl;
+ //assert(t1 == t[1]);
+#endif
+
+ //c.resize(k+1, Linear(0,0));
+ for(unsigned i = 0; i < (unsigned)k; i++) { // for i:=0 to k do
+#ifdef DEBUG_INVERSION
+ std::cout << "-------" << i << ": ---------" <<std::endl;
+ std::cout << "r=" << r << std::endl
+ << "c=" << c << std::endl
+ << "ti=" << ti << std::endl
+ << std::endl;
+#endif
+ if(r.size() <= i) // ensure enough space in the remainder, probably not needed
+ r.resize(i+1, Linear(0,0));
+ Linear ci(r[i][0]*t1i[0], r[i][1]*t1i[1]); // c_i(v) := H_0(r_i(u)/(t_1)^i; u)
+#ifdef DEBUG_INVERSION
+ std::cout << "t1i=" << t1i << std::endl;
+ std::cout << "ci=" << ci << std::endl;
+#endif
+ for(int dim = 0; dim < 2; dim++) // t1^-i *= 1./t1
+ t1i[dim] *= t1[dim];
+ c[i] = ci; // c(v) := c(v) + c_i(v)*t^i
+ // change from v to u parameterisation
+ SBasis civ = one_minus_a*ci[0] + a*ci[1];
+ // r(u) := r(u) - c_i(u)*(t(u))^i
+ // We can truncate this to the number of final terms, as no following terms can
+ // contribute to the result.
+ r -= multiply(civ,ti);
+ r.truncate(k);
+ if(r.tailError(i) == 0)
+ break; // yay!
+ ti = multiply(ti,t);
+ }
+#ifdef DEBUG_INVERSION
+ std::cout << "##########################" << std::endl;
+#endif
+ } else
+ c = Linear(0,1); // linear
+ c -= a0; // invert the offset
+ c /= a1; // invert the slope
+ return c;
+}
+
+/** Compute the sine of a to k terms
+ \param b linear function
+ \returns sbasis sin(a)
+
+It is recommended to use the piecewise version unless you have good reason.
+*/
+SBasis sin(Linear b, int k) {
+ SBasis s(k+2, Linear());
+ s[0] = Linear(std::sin(b[0]), std::sin(b[1]));
+ double tr = s[0].tri();
+ double t2 = b.tri();
+ s[1] = Linear(std::cos(b[0])*t2 - tr, -std::cos(b[1])*t2 + tr);
+
+ t2 *= t2;
+ for(int i = 0; i < k; i++) {
+ Linear bo(4*(i+1)*s[i+1][0] - 2*s[i+1][1],
+ -2*s[i+1][0] + 4*(i+1)*s[i+1][1]);
+ bo -= s[i]*(t2/(i+1));
+
+
+ s[i+2] = bo/double(i+2);
+ }
+
+ return s;
+}
+
+/** Compute the cosine of a
+ \param b linear function
+ \returns sbasis cos(a)
+
+It is recommended to use the piecewise version unless you have good reason.
+*/
+SBasis cos(Linear bo, int k) {
+ return sin(Linear(bo[0] + M_PI/2,
+ bo[1] + M_PI/2),
+ k);
+}
+
+/** compute fog^-1.
+ \param f,g sbasis functions
+ \returns sbasis f(g^-1(t)).
+
+("zero" = double comparison threshold. *!*we might divide by "zero"*!*)
+TODO: compute order according to tol?
+TODO: requires g(0)=0 & g(1)=1 atm... adaptation to other cases should be obvious!
+*/
+SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero){
+ SBasis result(order, Linear(0.)); //result
+ SBasis r=f; //remainder
+ SBasis Pk=Linear(1)-g,Qk=g,sg=Pk*Qk;
+ Pk.truncate(order);
+ Qk.truncate(order);
+ Pk.resize(order,Linear(0.));
+ Qk.resize(order,Linear(0.));
+ r.resize(order,Linear(0.));
+
+ int vs = valuation(sg,zero);
+ if (vs == 0) { // to prevent infinite loop
+ return result;
+ }
+
+ for (unsigned k=0; k<order; k+=vs){
+ double p10 = Pk.at(k)[0];// we have to solve the linear system:
+ double p01 = Pk.at(k)[1];//
+ double q10 = Qk.at(k)[0];// p10*a + q10*b = r10
+ double q01 = Qk.at(k)[1];// &
+ double r10 = r.at(k)[0];// p01*a + q01*b = r01
+ double r01 = r.at(k)[1];//
+ double a,b;
+ double det = p10*q01-p01*q10;
+
+ //TODO: handle det~0!!
+ if (fabs(det)<zero){
+ a=b=0;
+ }else{
+ a=( q01*r10-q10*r01)/det;
+ b=(-p01*r10+p10*r01)/det;
+ }
+ result[k] = Linear(a,b);
+ r=r-Pk*a-Qk*b;
+
+ Pk=Pk*sg;
+ Qk=Qk*sg;
+
+ Pk.resize(order,Linear(0.)); // truncates if too high order, expands with zeros if too low
+ Qk.resize(order,Linear(0.));
+ r.resize(order,Linear(0.));
+
+ }
+ result.normalize();
+ return result;
+}
+
+}
+
+/*
+ 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/src/2geom/self-intersect.cpp b/src/2geom/self-intersect.cpp
new file mode 100644
index 0000000..4fe4d9e
--- /dev/null
+++ b/src/2geom/self-intersect.cpp
@@ -0,0 +1,313 @@
+/**
+ * @file Implementation of Path::intersectSelf() and PathVector::intersectSelf().
+ */
+/* An algorithm for finding self-intersections of paths and path-vectors.
+ *
+ * Authors:
+ * Rafał Siejakowski <rs@rs-math.net>
+ *
+ * (C) 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 <list>
+
+#include <2geom/coord.h>
+#include <2geom/curve.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/point.h>
+#include <2geom/sweeper.h>
+
+namespace Geom {
+
+/** @brief The PathSelfIntersector class is a sweepset class used for intersecting curves in the
+ * same path with one another. It is intended to be used as the template parameter of Sweeper.
+ */
+class PathSelfIntersector
+{
+public:
+ using ItemIterator = Path::iterator;
+
+private:
+ Path _path; ///< The path searched for self-crossings, cleaned of degenerate curves.
+ std::list<ItemIterator> _active; ///< List of active curves during the sweepline passage.
+ std::vector<PathIntersection> _crossings; ///< Stores the crossings found.
+ std::vector<size_t> _original_indices; ///< Curve indices before removal of degenerate curves.
+ double const _precision; ///< Numerical epsilon.
+
+public:
+ PathSelfIntersector(Path const &path, double precision)
+ : _path{path.initialPoint()}
+ , _precision{precision}
+ {
+ _original_indices.reserve(path.size());
+ for (size_t i = 0; i < path.size(); i++) {
+ if (!path[i].isDegenerate()) {
+ _path.append(path[i]);
+ _original_indices.push_back(i);
+ }
+ }
+ _path.close(path.closed());
+ }
+
+ // === SweepSet API ===
+ auto &items() { return _path; }
+ Interval itemBounds(ItemIterator curve) const { return curve->boundsFast()[X]; }
+ /// Callback for when the sweepline starts intersecting a new item.
+ void addActiveItem(ItemIterator incoming)
+ {
+ _intersectWithActive(incoming);
+ _intersectWithSelf(incoming);
+ _active.push_back(incoming);
+ }
+ /// Callback for when the sweepline stops intersecting an item.
+ void removeActiveItem(ItemIterator to_remove)
+ {
+ auto it = std::find(_active.begin(), _active.end(), to_remove);
+ _active.erase(it);
+ }
+ // ===
+
+ std::vector<PathIntersection> &&moveOutCrossings() { return std::move(_crossings); }
+
+private:
+ /** Find and store all intersections of a curve with itself. */
+ void _intersectWithSelf(ItemIterator curve)
+ {
+ size_t const index = std::distance(_path.begin(), curve);
+ for (auto &&self_x : curve->intersectSelf(_precision)) {
+ _appendCurveCrossing(std::move(self_x), index, index);
+ }
+ }
+
+ /** Find and store all intersections of a curve with the active curves. */
+ void _intersectWithActive(ItemIterator curve)
+ {
+ size_t const index = std::distance(_path.begin(), curve);
+ for (auto const &other : _active) {
+ if (!curve->boundsFast().intersects(other->boundsFast())) {
+ continue;
+ }
+
+ size_t const other_index = std::distance(_path.begin(), other);
+ auto const &[smaller, larger] = std::minmax(index, other_index);
+ /// Whether the curves meet at a common node in the path.
+ bool consecutive = smaller + 1 == larger;
+ /// Whether the curves meet at the closure point of the path.
+ bool wraparound = _path.closed() && smaller == 0 && larger + 1 == _path.size();
+ for (auto &&xing : curve->intersect(*other, _precision)) {
+ _appendCurveCrossing(std::move(xing), index, other_index, consecutive, wraparound);
+ }
+ }
+ }
+
+ /** Append a curve crossing to the store as long as it satisfies nondegeneracy criteria. */
+ void _appendCurveCrossing(CurveIntersection &&xing, size_t first_index, size_t second_index,
+ bool consecutive = false, bool wraparound = false)
+ {
+ // Filter out crossings that aren't real but rather represent the agreement of final
+ // and initial points of consecutive curves – a consequence of the path's continuity.
+ auto const should_exclude = [&](bool flipped) -> bool {
+ // Filter out spurious self-intersections by using squared geometric average.
+ bool const first_is_first = (first_index < second_index) ^ flipped;
+ double const geom2 = first_is_first ? (1.0 - xing.first) * xing.second
+ : (1.0 - xing.second) * xing.first;
+ return geom2 < EPSILON;
+ };
+
+ if ((consecutive && should_exclude(false)) || (wraparound && should_exclude(true))) {
+ return;
+ }
+
+ // Convert curve indices to the original ones (before the removal of degenerate curves).
+ _crossings.emplace_back(PathTime(_original_indices[first_index], xing.first),
+ PathTime(_original_indices[second_index], xing.second),
+ xing.point());
+ }
+};
+
+// Compute all crossings of a path with itself.
+std::vector<PathIntersection> Path::intersectSelf(Coord precision) const
+{
+ auto intersector = PathSelfIntersector(*this, precision);
+ Sweeper(intersector).process();
+ auto result = intersector.moveOutCrossings();
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+/**
+ * @brief The PathVectorSelfIntersector class is an implementation of a SweepSet whose intended
+ * use is the search for self-intersections in a single PathVector. It's designed to be used as
+ * the template parameter for the Sweeper class template.
+ */
+class PathVectorSelfIntersector
+{
+public:
+ using ItemIterator = PathVector::const_iterator;
+
+private:
+ PathVector const &_pathvector; ///< A reference to the path-vector searched for self-crossings.
+ std::list<ItemIterator> _active; ///< A list of active paths during sweepline passage.
+ std::vector<PathVectorIntersection> _crossings; ///< Stores the crossings found.
+ double const _precision; ///< Numerical epsilon.
+
+public:
+ PathVectorSelfIntersector(PathVector const &subject, double precision)
+ : _pathvector{subject}
+ , _precision{precision}
+ {
+ }
+
+ // == SweepSet API ===
+ auto const &items() { return _pathvector; }
+ Interval itemBounds(ItemIterator path)
+ {
+ auto const r = path->boundsFast();
+ return r ? (*r)[X] : Interval(); // Sweeplines are vertical
+ }
+
+ /// Callback for when the sweepline starts intersecting a new item.
+ void addActiveItem(ItemIterator incoming)
+ {
+ _intersectWithActive(incoming);
+ _intersectWithSelf(incoming);
+ _active.push_back(incoming);
+ }
+
+ /// Callback for when the sweepline stops intersecting an item.
+ void removeActiveItem(ItemIterator to_remove)
+ {
+ auto it = std::find(_active.begin(), _active.end(), to_remove);
+ _active.erase(it);
+ }
+ // ===
+
+ std::vector<PathVectorIntersection> &&moveOutCrossings() { return std::move(_crossings); }
+
+private:
+ /**
+ * @brief Find all intersections of the path pointed to by the given
+ * iterator with all currently active paths and store results
+ * in the instance of the class.
+ *
+ * @param it An iterator to a path to be intersected with the active ones.
+ */
+ void _intersectWithActive(ItemIterator &it);
+
+ /**
+ * @brief Find all intersections of the path pointed to by the given
+ * iterator with itself and store the results in the class instance.
+ *
+ * @param it An iterator to a path which will be intersected with itself.
+ */
+ void _intersectWithSelf(ItemIterator &it);
+
+ /// Append a path crossing to the store.
+ void _appendPathCrossing(PathIntersection const &xing, size_t first_index, size_t second_index)
+ {
+ auto const first_time = PathVectorTime(first_index, xing.first);
+ auto const second_time = PathVectorTime(second_index, xing.second);
+ _crossings.emplace_back(first_time, second_time, xing.point());
+ }
+
+public:
+
+ std::vector<PathVectorIntersection>
+ filterDeduplicate(std::vector<PathVectorIntersection> &&xings) const;
+};
+
+/** Remove duplicate intersections (artifacts of the path/curve crossing algorithms). */
+std::vector<PathVectorIntersection>
+PathVectorSelfIntersector::filterDeduplicate(std::vector<PathVectorIntersection> &&xings) const
+{
+ std::vector<PathVectorIntersection> result;
+ result.reserve(xings.size());
+
+ auto const are_same_times = [&](Coord a1, Coord a2, Coord b1, Coord b2) -> bool {
+ return (are_near(a1, b1) && are_near(a2, b2)) ||
+ (are_near(a1, b2) && are_near(a2, b1));
+ };
+
+ Coord last_time_1 = -1.0, last_time_2 = -1.0; // Invalid path times
+ for (auto &&x : xings) {
+ auto const current_1 = x.first.asFlatTime(), current_2 = x.second.asFlatTime();
+ if (!are_same_times(current_1, current_2, last_time_1, last_time_2)) {
+ result.push_back(std::move(x));
+ }
+ last_time_1 = current_1;
+ last_time_2 = current_2;
+ }
+
+ return result;
+}
+
+/** Compute and store intersections of a path with all active paths. */
+void PathVectorSelfIntersector::_intersectWithActive(ItemIterator &it)
+{
+ auto const start = _pathvector.begin();
+ for (auto &path : _active) {
+ if (!path->boundsFast().intersects(it->boundsFast())) {
+ continue;
+ }
+ for (auto &&xing : path->intersect(*it, _precision)) {
+ _appendPathCrossing(std::move(xing), std::distance(start, path),
+ std::distance(start, it));
+ }
+ }
+}
+
+/** Compute and store intersections of a constituent path with itself. */
+void PathVectorSelfIntersector::_intersectWithSelf(ItemIterator &it)
+{
+ size_t const path_index = std::distance(_pathvector.begin(), it);
+ for (auto &&xing : it->intersectSelf(_precision)) {
+ _appendPathCrossing(std::move(xing), path_index, path_index);
+ }
+}
+
+// Compute self-intersections in a path-vector.
+std::vector<PathVectorIntersection> PathVector::intersectSelf(Coord precision) const
+{
+ auto intersector = PathVectorSelfIntersector(*this, precision);
+ Sweeper(intersector).process();
+ auto result = intersector.moveOutCrossings();
+ std::sort(result.begin(), result.end());
+ return (result.size() > 1) ? intersector.filterDeduplicate(std::move(result)) : result;
+}
+
+} // 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/src/2geom/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp
new file mode 100644
index 0000000..b82d20b
--- /dev/null
+++ b/src/2geom/solve-bezier-one-d.cpp
@@ -0,0 +1,243 @@
+
+#include <2geom/solver.h>
+#include <2geom/choose.h>
+#include <2geom/bezier.h>
+#include <2geom/point.h>
+
+#include <cmath>
+#include <algorithm>
+//#include <valarray>
+
+/*** Find the zeros of the bernstein function. The code subdivides until it is happy with the
+ * linearity of the function. This requires an O(degree^2) subdivision for each step, even when
+ * there is only one solution.
+ */
+
+namespace Geom {
+
+template<class t>
+static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); }
+
+//const unsigned MAXDEPTH = 23; // Maximum depth for recursion. Using floats means 23 bits precision max
+
+//const double BEPSILON = ldexp(1.0,(-MAXDEPTH-1)); /*Flatness control value */
+//const double SECANT_EPSILON = 1e-13; // secant method converges much faster, get a bit more precision
+/**
+ * This function is called _a lot_. We have included various manual memory management stuff to reduce the amount of mallocing that goes on. In the future it is possible that this will hurt performance.
+ **/
+class Bernsteins
+{
+public:
+ static constexpr size_t MAX_DEPTH = 53;
+ size_t degree, N;
+ std::vector<double> &solutions;
+
+ Bernsteins(size_t _degree, std::vector<double> &sol)
+ : degree(_degree), N(degree+1), solutions(sol)
+ {
+ }
+
+ unsigned
+ control_poly_flat_enough(double const *V);
+
+ void
+ find_bernstein_roots(double const *w, /* The control points */
+ unsigned depth, /* The depth of the recursion */
+ double left_t, double right_t);
+};
+/*
+ * find_bernstein_roots : Given an equation in Bernstein-Bernstein form, find all
+ * of the roots in the open interval (0, 1). Return the number of roots found.
+ */
+void
+find_bernstein_roots(double const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> &solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t, double right_t, bool /*use_secant*/)
+{
+ Bernsteins B(degree, solutions);
+ B.find_bernstein_roots(w, depth, left_t, right_t);
+}
+
+void
+find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-values */
+ Geom::Bezier const &bz, /* The control points */
+ double left_t, double right_t)
+{
+ Bernsteins B(bz.degree(), solutions);
+ Geom::Bezier& bzl = const_cast<Geom::Bezier&>(bz);
+ double* w = &(bzl[0]);
+ B.find_bernstein_roots(w, 0, left_t, right_t);
+}
+
+
+
+void Bernsteins::find_bernstein_roots(double const *w, /* The control points */
+ unsigned depth, /* The depth of the recursion */
+ double left_t,
+ double right_t)
+{
+
+ size_t n_crossings = 0;
+
+ int old_sign = SGN(w[0]);
+ //std::cout << "w[0] = " << w[0] << std::endl;
+ for (size_t i = 1; i < N; i++)
+ {
+ //std::cout << "w[" << i << "] = " << w[i] << std::endl;
+ int sign = SGN(w[i]);
+ if (sign != 0)
+ {
+ if (sign != old_sign && old_sign != 0)
+ {
+ ++n_crossings;
+ }
+ old_sign = sign;
+ }
+ }
+ //std::cout << "n_crossings = " << n_crossings << std::endl;
+ if (n_crossings == 0) return; // no solutions here
+
+ if (n_crossings == 1) /* Unique solution */
+ {
+ //std::cout << "depth = " << depth << std::endl;
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth > MAX_DEPTH)
+ {
+ //printf("bottom out %d\n", depth);
+ const double Ax = right_t - left_t;
+ const double Ay = w[degree] - w[0];
+
+ solutions.push_back(left_t - Ax*w[0] / Ay);
+ return;
+ }
+
+
+ double s = 0, t = 1;
+ double e = 1e-10;
+ int side = 0;
+ double r, fs = w[0], ft = w[degree];
+
+ for (size_t n = 0; n < 100; ++n)
+ {
+ r = (fs*t - ft*s) / (fs - ft);
+ if (fabs(t-s) < e * fabs(t+s)) break;
+
+ double fr = bernstein_value_at(r, w, degree);
+
+ if (fr * ft > 0)
+ {
+ t = r; ft = fr;
+ if (side == -1) fs /= 2;
+ side = -1;
+ }
+ else if (fs * fr > 0)
+ {
+ s = r; fs = fr;
+ if (side == +1) ft /= 2;
+ side = +1;
+ }
+ else break;
+ }
+ solutions.push_back(r*right_t + (1-r)*left_t);
+ return;
+
+ }
+
+ /* Otherwise, solve recursively after subdividing control polygon */
+// double Left[N], /* New left and right */
+// Right[N]; /* control polygons */
+ //const double t = 0.5;
+ double* LR = new double[2*N];
+ double* Left = LR;
+ double* Right = LR + N;
+
+ std::copy(w, w + N, Right);
+
+ Left[0] = Right[0];
+ for (size_t i = 1; i < N; ++i)
+ {
+ for (size_t j = 0; j < N-i; ++j)
+ {
+ Right[j] = (Right[j] + Right[j+1]) * 0.5;
+ }
+ Left[i] = Right[0];
+ }
+
+ double mid_t = (left_t + right_t) * 0.5;
+
+
+ find_bernstein_roots(Left, depth+1, left_t, mid_t);
+
+
+ /* Solution is exactly on the subdivision point. */
+ if (Right[0] == 0)
+ {
+ solutions.push_back(mid_t);
+ }
+
+ find_bernstein_roots(Right, depth+1, mid_t, right_t);
+ delete[] LR;
+}
+
+#if 0
+/*
+ * control_poly_flat_enough :
+ * Check if the control polygon of a Bernstein curve is flat enough
+ * for recursive subdivision to bottom out.
+ *
+ */
+unsigned
+Bernsteins::control_poly_flat_enough(double const *V)
+{
+ /* Find the perpendicular distance from each interior control point to line connecting V[0] and
+ * V[degree] */
+
+ /* Derive the implicit equation for line connecting first */
+ /* and last control points */
+ const double a = V[0] - V[degree];
+
+ double max_distance_above = 0.0;
+ double max_distance_below = 0.0;
+ double ii = 0, dii = 1./degree;
+ for (unsigned i = 1; i < degree; i++) {
+ ii += dii;
+ /* Compute distance from each of the points to that line */
+ const double d = (a + V[i]) * ii - a;
+ double dist = d*d;
+ // Find the largest distance
+ if (d < 0.0)
+ max_distance_below = std::min(max_distance_below, -dist);
+ else
+ max_distance_above = std::max(max_distance_above, dist);
+ }
+
+ const double abSquared = 1./((a * a) + 1);
+
+ const double intercept_1 = (a - max_distance_above * abSquared);
+ const double intercept_2 = (a - max_distance_below * abSquared);
+
+ /* Compute bounding interval*/
+ const double left_intercept = std::min(intercept_1, intercept_2);
+ const double right_intercept = std::max(intercept_1, intercept_2);
+
+ const double error = 0.5 * (right_intercept - left_intercept);
+ //printf("error %g %g %g\n", error, a, BEPSILON * a);
+ return error < BEPSILON * a;
+}
+#endif
+
+} // 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/src/2geom/solve-bezier-parametric.cpp b/src/2geom/solve-bezier-parametric.cpp
new file mode 100644
index 0000000..2fb3f41
--- /dev/null
+++ b/src/2geom/solve-bezier-parametric.cpp
@@ -0,0 +1,189 @@
+#include <2geom/bezier.h>
+#include <2geom/point.h>
+#include <2geom/solver.h>
+#include <algorithm>
+
+namespace Geom {
+
+/*** Find the zeros of the parametric function in 2d defined by two beziers X(t), Y(t). The code subdivides until it happy with the linearity of the bezier. This requires an n^2 subdivision for each step, even when there is only one solution.
+ *
+ * Perhaps it would be better to subdivide particularly around nodes with changing sign, rather than simply cutting in half.
+ */
+
+#define SGN(a) (((a)<0) ? -1 : 1)
+
+/*
+ * Forward declarations
+ */
+unsigned
+crossing_count(Geom::Point const *V, unsigned degree);
+static unsigned
+control_poly_flat_enough(Geom::Point const *V, unsigned degree);
+static double
+compute_x_intercept(Geom::Point const *V, unsigned degree);
+
+const unsigned MAXDEPTH = 64; /* Maximum depth for recursion */
+
+const double BEPSILON = ldexp(1.0,-MAXDEPTH-1); /*Flatness control value */
+
+unsigned total_steps, total_subs;
+
+/*
+ * find_bezier_roots : Given an equation in Bernstein-Bezier form, find all
+ * of the roots in the interval [0, 1]. Return the number of roots found.
+ */
+void
+find_parametric_bezier_roots(Geom::Point const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> &solutions, /* RETURN candidate t-values */
+ unsigned depth) /* The depth of the recursion */
+{
+ total_steps++;
+ const unsigned max_crossings = crossing_count(w, degree);
+ switch (max_crossings) {
+ case 0: /* No solutions here */
+ return;
+
+ case 1:
+ /* Unique solution */
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth >= MAXDEPTH) {
+ solutions.push_back((w[0][Geom::X] + w[degree][Geom::X]) / 2.0);
+ return;
+ }
+
+ // I thought secant method would be faster here, but it'aint. -- njh
+
+ if (control_poly_flat_enough(w, degree)) {
+ solutions.push_back(compute_x_intercept(w, degree));
+ return;
+ }
+ break;
+ }
+
+ /* Otherwise, solve recursively after subdividing control polygon */
+
+ //Geom::Point Left[degree+1], /* New left and right */
+ // Right[degree+1]; /* control polygons */
+ std::vector<Geom::Point> Left( degree+1 ), Right(degree+1);
+
+ casteljau_subdivision(0.5, w, Left.data(), Right.data(), degree);
+ total_subs ++;
+ find_parametric_bezier_roots(Left.data(), degree, solutions, depth+1);
+ find_parametric_bezier_roots(Right.data(), degree, solutions, depth+1);
+}
+
+
+/*
+ * crossing_count:
+ * Count the number of times a Bezier control polygon
+ * crosses the 0-axis. This number is >= the number of roots.
+ *
+ */
+unsigned
+crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */
+ unsigned degree) /* Degree of Bezier curve */
+{
+ unsigned n_crossings = 0; /* Number of zero-crossings */
+
+ int old_sign = SGN(V[0][Geom::Y]);
+ for (unsigned i = 1; i <= degree; i++) {
+ int sign = SGN(V[i][Geom::Y]);
+ if (sign != old_sign)
+ n_crossings++;
+ old_sign = sign;
+ }
+ return n_crossings;
+}
+
+
+
+/*
+ * control_poly_flat_enough :
+ * Check if the control polygon of a Bezier curve is flat enough
+ * for recursive subdivision to bottom out.
+ *
+ */
+static unsigned
+control_poly_flat_enough(Geom::Point const *V, /* Control points */
+ unsigned degree) /* Degree of polynomial */
+{
+ /* Find the perpendicular distance from each interior control point to line connecting V[0] and
+ * V[degree] */
+
+ /* Derive the implicit equation for line connecting first */
+ /* and last control points */
+ const double a = V[0][Geom::Y] - V[degree][Geom::Y];
+ const double b = V[degree][Geom::X] - V[0][Geom::X];
+ const double c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y];
+
+ const double abSquared = (a * a) + (b * b);
+
+ //double distance[degree]; /* Distances from pts to line */
+ std::vector<double> distance(degree); /* Distances from pts to line */
+ for (unsigned i = 1; i < degree; i++) {
+ /* Compute distance from each of the points to that line */
+ double & dist(distance[i-1]);
+ const double d = a * V[i][Geom::X] + b * V[i][Geom::Y] + c;
+ dist = d*d / abSquared;
+ if (d < 0.0)
+ dist = -dist;
+ }
+
+
+ // Find the largest distance
+ double max_distance_above = 0.0;
+ double max_distance_below = 0.0;
+ for (unsigned i = 0; i < degree-1; i++) {
+ const double d = distance[i];
+ if (d < 0.0)
+ max_distance_below = std::min(max_distance_below, d);
+ if (d > 0.0)
+ max_distance_above = std::max(max_distance_above, d);
+ }
+
+ const double intercept_1 = (c + max_distance_above) / -a;
+ const double intercept_2 = (c + max_distance_below) / -a;
+
+ /* Compute bounding interval*/
+ const double left_intercept = std::min(intercept_1, intercept_2);
+ const double right_intercept = std::max(intercept_1, intercept_2);
+
+ const double error = 0.5 * (right_intercept - left_intercept);
+
+ if (error < BEPSILON)
+ return 1;
+
+ return 0;
+}
+
+
+
+/*
+ * compute_x_intercept :
+ * Compute intersection of chord from first control point to last
+ * with 0-axis.
+ *
+ */
+static double
+compute_x_intercept(Geom::Point const *V, /* Control points */
+ unsigned degree) /* Degree of curve */
+{
+ const Geom::Point A = V[degree] - V[0];
+
+ return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y];
+}
+
+};
+
+/*
+ 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/src/2geom/solve-bezier.cpp b/src/2geom/solve-bezier.cpp
new file mode 100644
index 0000000..4ff42bb
--- /dev/null
+++ b/src/2geom/solve-bezier.cpp
@@ -0,0 +1,304 @@
+
+#include <2geom/solver.h>
+#include <2geom/choose.h>
+#include <2geom/bezier.h>
+#include <2geom/point.h>
+
+#include <cmath>
+#include <algorithm>
+
+/*** Find the zeros of a Bezier. The code subdivides until it is happy with the linearity of the
+ * function. This requires an O(degree^2) subdivision for each step, even when there is only one
+ * solution.
+ *
+ * We try fairly hard to correctly handle multiple roots.
+ */
+
+//#define debug(x) do{x;}while(0)
+#define debug(x)
+
+namespace Geom{
+
+template<class t>
+static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); }
+
+class Bernsteins{
+public:
+ static const size_t MAX_DEPTH = 22;
+ std::vector<double> &solutions;
+ //std::vector<double> dsolutions;
+
+ Bernsteins(std::vector<double> & sol)
+ : solutions(sol)
+ {}
+
+ void subdivide(double const *V,
+ double t,
+ double *Left,
+ double *Right);
+
+ double secant(Bezier const &bz);
+
+
+ void find_bernstein_roots(Bezier const &bz, unsigned depth,
+ double left_t, double right_t);
+};
+
+template <typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const std::vector<T> & b) {
+ out_file << "[";
+ for(unsigned i = 0; i < b.size(); i++) {
+ out_file << b[i] << ", ";
+ }
+ return out_file << "]";
+}
+
+void convex_hull_marching(Bezier const &src_bz, Bezier bz,
+ std::vector<double> &solutions,
+ double left_t,
+ double right_t)
+{
+ while(bz.order() > 0 && bz[0] == 0) {
+ std::cout << "deflate\n";
+ bz = bz.deflate();
+ solutions.push_back(left_t);
+ }
+ std::cout << std::endl;
+ if (bz.order() > 0) {
+
+ int old_sign = SGN(bz[0]);
+
+ double left_bound = 0;
+ double dt = 0;
+ for (size_t i = 1; i < bz.size(); i++)
+ {
+ int sign = SGN(bz[i]);
+ if (sign != old_sign)
+ {
+ dt = double(i) / bz.order();
+ left_bound = dt * bz[0] / (bz[0] - bz[i]);
+ break;
+ }
+ old_sign = sign;
+ }
+ if (dt == 0) return;
+ std::cout << bz << std::endl;
+ std::cout << "dt = " << dt << std::endl;
+ std::cout << "left_t = " << left_t << std::endl;
+ std::cout << "right_t = " << right_t << std::endl;
+ std::cout << "left bound = " << left_bound
+ << " = " << bz(left_bound) << std::endl;
+ double new_left_t = left_bound * (right_t - left_t) + left_t;
+ std::cout << "new_left_t = " << new_left_t << std::endl;
+ Bezier bzr = portion(src_bz, new_left_t, 1);
+ while(bzr.order() > 0 && bzr[0] == 0) {
+ std::cout << "deflate\n";
+ bzr = bzr.deflate();
+ solutions.push_back(new_left_t);
+ }
+ if (left_t < new_left_t) {
+ convex_hull_marching(src_bz, bzr,
+ solutions,
+ new_left_t, right_t);
+ } else {
+ std::cout << "epsilon reached\n";
+ while(bzr.order() > 0 && fabs(bzr[0]) <= 1e-10) {
+ std::cout << "deflate\n";
+ bzr = bzr.deflate();
+ std::cout << bzr << std::endl;
+ solutions.push_back(new_left_t);
+ }
+
+ }
+ }
+}
+
+void
+Bezier::find_bezier_roots(std::vector<double> &solutions,
+ double left_t, double right_t) const {
+ Bezier bz = *this;
+ //convex_hull_marching(bz, bz, solutions, left_t, right_t);
+ //return;
+
+ // a constant bezier, even if identically zero, has no roots
+ if (bz.isConstant()) {
+ return;
+ }
+
+ while(bz[0] == 0) {
+ debug(std::cout << "deflate\n");
+ bz = bz.deflate();
+ solutions.push_back(0);
+ }
+ if (bz.degree() == 1) {
+ debug(std::cout << "linear\n");
+
+ if (SGN(bz[0]) != SGN(bz[1])) {
+ double d = bz[0] - bz[1];
+ if(d != 0) {
+ double r = bz[0] / d;
+ if(0 <= r && r <= 1)
+ solutions.push_back(r);
+ }
+ }
+ return;
+ }
+
+ //std::cout << "initial = " << bz << std::endl;
+ Bernsteins B(solutions);
+ B.find_bernstein_roots(bz, 0, left_t, right_t);
+ //std::cout << solutions << std::endl;
+}
+
+void Bernsteins::find_bernstein_roots(Bezier const &bz,
+ unsigned depth,
+ double left_t,
+ double right_t)
+{
+ debug(std::cout << left_t << ", " << right_t << std::endl);
+ size_t n_crossings = 0;
+
+ int old_sign = SGN(bz[0]);
+ //std::cout << "w[0] = " << bz[0] << std::endl;
+ for (size_t i = 1; i < bz.size(); i++)
+ {
+ //std::cout << "w[" << i << "] = " << w[i] << std::endl;
+ int sign = SGN(bz[i]);
+ if (sign != 0)
+ {
+ if (sign != old_sign && old_sign != 0)
+ {
+ ++n_crossings;
+ }
+ old_sign = sign;
+ }
+ }
+ // if last control point is zero, that counts as crossing too
+ if (SGN(bz[bz.size()-1]) == 0) {
+ ++n_crossings;
+ }
+
+ //std::cout << "n_crossings = " << n_crossings << std::endl;
+ if (n_crossings == 0) return; // no solutions here
+
+ if (n_crossings == 1) /* Unique solution */
+ {
+ //std::cout << "depth = " << depth << std::endl;
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth > MAX_DEPTH)
+ {
+ //printf("bottom out %d\n", depth);
+ const double Ax = right_t - left_t;
+ const double Ay = bz.at1() - bz.at0();
+
+ solutions.push_back(left_t - Ax*bz.at0() / Ay);
+ return;
+ }
+
+ double r = secant(bz);
+ solutions.push_back(r*right_t + (1-r)*left_t);
+ return;
+ }
+ /* Otherwise, solve recursively after subdividing control polygon */
+ Bezier::Order o(bz);
+ Bezier Left(o), Right = bz;
+ double split_t = (left_t + right_t) * 0.5;
+
+ // If subdivision is working poorly, split around the leftmost root of the derivative
+ if (depth > 2) {
+ debug(std::cout << "derivative mode\n");
+ Bezier dbz = derivative(bz);
+
+ debug(std::cout << "initial = " << dbz << std::endl);
+ std::vector<double> dsolutions = dbz.roots(Interval(left_t, right_t));
+ debug(std::cout << "dsolutions = " << dsolutions << std::endl);
+
+ double dsplit_t = 0.5;
+ if(!dsolutions.empty()) {
+ dsplit_t = dsolutions[0];
+ split_t = left_t + (right_t - left_t)*dsplit_t;
+ debug(std::cout << "split_value = " << bz(split_t) << std::endl);
+ debug(std::cout << "splitting around " << dsplit_t << " = "
+ << split_t << "\n");
+
+ }
+ std::pair<Bezier, Bezier> LR = bz.subdivide(dsplit_t);
+ Left = LR.first;
+ Right = LR.second;
+ } else {
+ // split at midpoint, because it is cheap
+ Left[0] = Right[0];
+ for (size_t i = 1; i < bz.size(); ++i)
+ {
+ for (size_t j = 0; j < bz.size()-i; ++j)
+ {
+ Right[j] = (Right[j] + Right[j+1]) * 0.5;
+ }
+ Left[i] = Right[0];
+ }
+ }
+ debug(std::cout << "Solution is exactly on the subdivision point.\n");
+ debug(std::cout << Left << " , " << Right << std::endl);
+ Left = reverse(Left);
+ while(Right.order() > 0 && fabs(Right[0]) <= 1e-10) {
+ debug(std::cout << "deflate\n");
+ Right = Right.deflate();
+ Left = Left.deflate();
+ solutions.push_back(split_t);
+ }
+ Left = reverse(Left);
+ if (Right.order() > 0) {
+ debug(std::cout << Left << " , " << Right << std::endl);
+ find_bernstein_roots(Left, depth+1, left_t, split_t);
+ find_bernstein_roots(Right, depth+1, split_t, right_t);
+ }
+}
+
+double Bernsteins::secant(Bezier const &bz) {
+ double s = 0, t = 1;
+ double e = 1e-14;
+ int side = 0;
+ double r, fs = bz.at0(), ft = bz.at1();
+
+ for (size_t n = 0; n < 100; ++n)
+ {
+ r = (fs*t - ft*s) / (fs - ft);
+ if (fabs(t-s) < e * fabs(t+s)) {
+ debug(std::cout << "error small " << fabs(t-s)
+ << ", accepting solution " << r
+ << "after " << n << "iterations\n");
+ return r;
+ }
+
+ double fr = bz.valueAt(r);
+
+ if (fr * ft > 0)
+ {
+ t = r; ft = fr;
+ if (side == -1) fs /= 2;
+ side = -1;
+ }
+ else if (fs * fr > 0)
+ {
+ s = r; fs = fr;
+ if (side == +1) ft /= 2;
+ side = +1;
+ }
+ else break;
+ }
+ return r;
+}
+
+};
+
+/*
+ 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/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp
new file mode 100644
index 0000000..6a1cb15
--- /dev/null
+++ b/src/2geom/svg-path-parser.cpp
@@ -0,0 +1,1615 @@
+
+#line 1 "svg-path-parser.rl"
+/**
+ * \file
+ * \brief parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 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.
+ *
+ */
+
+#include <cstdio>
+#include <cmath>
+#include <vector>
+#include <glib.h>
+
+#include <2geom/point.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/angle.h>
+
+namespace Geom {
+
+
+#line 48 "svg-path-parser.cpp"
+static const char _svg_path_actions[] = {
+ 0, 1, 0, 1, 1, 1, 2, 1,
+ 3, 1, 4, 1, 5, 1, 15, 2,
+ 1, 0, 2, 1, 6, 2, 1, 7,
+ 2, 1, 8, 2, 1, 9, 2, 1,
+ 10, 2, 1, 11, 2, 1, 12, 2,
+ 1, 13, 2, 1, 14, 2, 2, 0,
+ 2, 3, 0, 2, 4, 0, 2, 5,
+ 0, 3, 1, 6, 0, 3, 1, 7,
+ 0, 3, 1, 8, 0, 3, 1, 9,
+ 0, 3, 1, 10, 0, 3, 1, 11,
+ 0, 3, 1, 12, 0, 3, 1, 13,
+ 0, 3, 1, 14, 0
+};
+
+static const short _svg_path_key_offsets[] = {
+ 0, 0, 9, 18, 21, 23, 35, 45,
+ 48, 50, 53, 55, 67, 77, 80, 82,
+ 91, 103, 112, 121, 130, 133, 135, 147,
+ 157, 160, 162, 174, 184, 187, 189, 198,
+ 205, 211, 218, 225, 231, 241, 251, 254,
+ 256, 268, 278, 281, 283, 295, 304, 316,
+ 325, 335, 339, 341, 348, 352, 354, 364,
+ 368, 370, 380, 389, 398, 401, 403, 415,
+ 425, 428, 430, 442, 452, 455, 457, 469,
+ 479, 482, 484, 496, 506, 509, 511, 523,
+ 533, 536, 538, 550, 559, 571, 580, 592,
+ 601, 613, 622, 634, 643, 647, 649, 658,
+ 667, 670, 672, 676, 678, 687, 696, 705,
+ 708, 710, 722, 732, 735, 737, 749, 759,
+ 762, 764, 776, 786, 789, 791, 803, 812,
+ 824, 833, 845, 854, 858, 860, 869, 878,
+ 881, 883, 895, 905, 908, 910, 922, 932,
+ 935, 937, 949, 959, 962, 964, 976, 985,
+ 997, 1006, 1018, 1027, 1031, 1033, 1042, 1051,
+ 1054, 1056, 1068, 1078, 1081, 1083, 1095, 1104,
+ 1108, 1110, 1119, 1128, 1131, 1133, 1137, 1139,
+ 1148, 1157, 1166, 1175, 1184, 1196, 1205, 1209,
+ 1211, 1220, 1229, 1238, 1247, 1251, 1253, 1263,
+ 1267, 1269, 1279, 1283, 1285, 1295, 1299, 1301,
+ 1311, 1315, 1317, 1327, 1331, 1333, 1343, 1347,
+ 1349, 1359, 1363, 1365, 1375, 1379, 1381, 1391,
+ 1395, 1397, 1407, 1411, 1413, 1423, 1427, 1429,
+ 1439, 1443, 1445, 1455, 1459, 1461, 1470, 1474,
+ 1476, 1486, 1498, 1507, 1517, 1524, 1528, 1530,
+ 1534, 1536, 1546, 1552, 1584, 1614, 1646, 1678,
+ 1710, 1740, 1772, 1802, 1834, 1864, 1896, 1926,
+ 1958, 1988, 2020, 2050, 2082, 2112, 2144, 2174,
+ 2206, 2236, 2268, 2298, 2330, 2360, 2392, 2422,
+ 2454, 2484, 2508, 2532, 2564, 2594, 2624, 2656
+};
+
+static const char _svg_path_trans_keys[] = {
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 46, 48, 57,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 46, 48, 57,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 46, 48, 57,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 46, 48, 57, 48, 57, 13, 32, 44,
+ 69, 101, 9, 10, 48, 57, 13, 32,
+ 44, 48, 49, 9, 10, 13, 32, 48,
+ 49, 9, 10, 13, 32, 44, 48, 49,
+ 9, 10, 13, 32, 44, 48, 49, 9,
+ 10, 13, 32, 48, 49, 9, 10, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 13, 32, 44, 46, 9, 10, 43,
+ 45, 48, 57, 46, 48, 57, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 44,
+ 46, 69, 101, 9, 10, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 9, 10, 48, 57, 43, 45, 48, 57,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 43, 45, 48, 57,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 46,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 46, 48, 57,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 43, 45, 48, 57, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 46, 48, 57, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 46,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 43, 45,
+ 48, 57, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 46,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 43, 45, 48, 57, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 46, 48, 57, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 46, 48, 57, 48, 57, 43, 45, 48,
+ 57, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 43, 45, 48,
+ 57, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 43, 45,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 48, 49, 9, 10, 43, 45, 48, 57,
+ 48, 57, 43, 45, 48, 57, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 13, 32, 77, 109, 9, 10,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 69, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 101, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 69, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 101, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 69, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 101, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 13, 32, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 13, 32, 44, 46,
+ 65, 67, 69, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 101, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 65, 67, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 0
+};
+
+static const char _svg_path_single_lengths[] = {
+ 0, 5, 5, 1, 0, 6, 4, 1,
+ 0, 1, 0, 6, 4, 1, 0, 5,
+ 6, 5, 5, 5, 1, 0, 6, 4,
+ 1, 0, 6, 4, 1, 0, 5, 5,
+ 4, 5, 5, 4, 4, 4, 1, 0,
+ 6, 4, 1, 0, 6, 5, 6, 5,
+ 6, 2, 0, 3, 2, 0, 4, 2,
+ 0, 4, 5, 5, 1, 0, 6, 4,
+ 1, 0, 6, 4, 1, 0, 6, 4,
+ 1, 0, 6, 4, 1, 0, 6, 4,
+ 1, 0, 6, 5, 6, 5, 6, 5,
+ 6, 5, 6, 5, 2, 0, 5, 5,
+ 1, 0, 2, 0, 5, 5, 5, 1,
+ 0, 6, 4, 1, 0, 6, 4, 1,
+ 0, 6, 4, 1, 0, 6, 5, 6,
+ 5, 6, 5, 2, 0, 5, 5, 1,
+ 0, 6, 4, 1, 0, 6, 4, 1,
+ 0, 6, 4, 1, 0, 6, 5, 6,
+ 5, 6, 5, 2, 0, 5, 5, 1,
+ 0, 6, 4, 1, 0, 6, 5, 2,
+ 0, 5, 5, 1, 0, 2, 0, 5,
+ 5, 5, 5, 5, 6, 5, 2, 0,
+ 5, 5, 5, 5, 2, 0, 4, 2,
+ 0, 4, 2, 0, 4, 2, 0, 4,
+ 2, 0, 4, 2, 0, 4, 2, 0,
+ 4, 2, 0, 4, 2, 0, 4, 2,
+ 0, 4, 2, 0, 4, 2, 0, 4,
+ 2, 0, 4, 2, 0, 5, 2, 0,
+ 4, 6, 5, 4, 5, 2, 0, 2,
+ 0, 4, 4, 26, 24, 26, 26, 26,
+ 24, 26, 24, 26, 24, 26, 24, 26,
+ 24, 26, 24, 26, 24, 26, 24, 26,
+ 24, 26, 24, 26, 24, 26, 24, 26,
+ 24, 22, 22, 26, 24, 24, 26, 24
+};
+
+static const char _svg_path_range_lengths[] = {
+ 0, 2, 2, 1, 1, 3, 3, 1,
+ 1, 1, 1, 3, 3, 1, 1, 2,
+ 3, 2, 2, 2, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 2, 1,
+ 1, 1, 1, 1, 3, 3, 1, 1,
+ 3, 3, 1, 1, 3, 2, 3, 2,
+ 2, 1, 1, 2, 1, 1, 3, 1,
+ 1, 3, 2, 2, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 1, 1, 3, 2, 3, 2, 3, 2,
+ 3, 2, 3, 2, 1, 1, 2, 2,
+ 1, 1, 1, 1, 2, 2, 2, 1,
+ 1, 3, 3, 1, 1, 3, 3, 1,
+ 1, 3, 3, 1, 1, 3, 2, 3,
+ 2, 3, 2, 1, 1, 2, 2, 1,
+ 1, 3, 3, 1, 1, 3, 3, 1,
+ 1, 3, 3, 1, 1, 3, 2, 3,
+ 2, 3, 2, 1, 1, 2, 2, 1,
+ 1, 3, 3, 1, 1, 3, 2, 1,
+ 1, 2, 2, 1, 1, 1, 1, 2,
+ 2, 2, 2, 2, 3, 2, 1, 1,
+ 2, 2, 2, 2, 1, 1, 3, 1,
+ 1, 3, 1, 1, 3, 1, 1, 3,
+ 1, 1, 3, 1, 1, 3, 1, 1,
+ 3, 1, 1, 3, 1, 1, 3, 1,
+ 1, 3, 1, 1, 3, 1, 1, 3,
+ 1, 1, 3, 1, 1, 2, 1, 1,
+ 3, 3, 2, 3, 1, 1, 1, 1,
+ 1, 3, 1, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 1, 3, 3, 3, 3, 3
+};
+
+static const short _svg_path_index_offsets[] = {
+ 0, 0, 8, 16, 19, 21, 31, 39,
+ 42, 44, 47, 49, 59, 67, 70, 72,
+ 80, 90, 98, 106, 114, 117, 119, 129,
+ 137, 140, 142, 152, 160, 163, 165, 173,
+ 180, 186, 193, 200, 206, 214, 222, 225,
+ 227, 237, 245, 248, 250, 260, 268, 278,
+ 286, 295, 299, 301, 307, 311, 313, 321,
+ 325, 327, 335, 343, 351, 354, 356, 366,
+ 374, 377, 379, 389, 397, 400, 402, 412,
+ 420, 423, 425, 435, 443, 446, 448, 458,
+ 466, 469, 471, 481, 489, 499, 507, 517,
+ 525, 535, 543, 553, 561, 565, 567, 575,
+ 583, 586, 588, 592, 594, 602, 610, 618,
+ 621, 623, 633, 641, 644, 646, 656, 664,
+ 667, 669, 679, 687, 690, 692, 702, 710,
+ 720, 728, 738, 746, 750, 752, 760, 768,
+ 771, 773, 783, 791, 794, 796, 806, 814,
+ 817, 819, 829, 837, 840, 842, 852, 860,
+ 870, 878, 888, 896, 900, 902, 910, 918,
+ 921, 923, 933, 941, 944, 946, 956, 964,
+ 968, 970, 978, 986, 989, 991, 995, 997,
+ 1005, 1013, 1021, 1029, 1037, 1047, 1055, 1059,
+ 1061, 1069, 1077, 1085, 1093, 1097, 1099, 1107,
+ 1111, 1113, 1121, 1125, 1127, 1135, 1139, 1141,
+ 1149, 1153, 1155, 1163, 1167, 1169, 1177, 1181,
+ 1183, 1191, 1195, 1197, 1205, 1209, 1211, 1219,
+ 1223, 1225, 1233, 1237, 1239, 1247, 1251, 1253,
+ 1261, 1265, 1267, 1275, 1279, 1281, 1289, 1293,
+ 1295, 1303, 1313, 1321, 1329, 1336, 1340, 1342,
+ 1346, 1348, 1356, 1362, 1392, 1420, 1450, 1480,
+ 1510, 1538, 1568, 1596, 1626, 1654, 1684, 1712,
+ 1742, 1770, 1800, 1828, 1858, 1886, 1916, 1944,
+ 1974, 2002, 2032, 2060, 2090, 2118, 2148, 2176,
+ 2206, 2234, 2258, 2282, 2312, 2340, 2368, 2398
+};
+
+static const short _svg_path_indicies[] = {
+ 0, 0, 2, 2, 3, 0, 4, 1,
+ 5, 5, 6, 6, 7, 5, 8, 1,
+ 9, 10, 1, 11, 1, 12, 12, 14,
+ 15, 16, 16, 12, 13, 11, 1, 17,
+ 17, 19, 20, 17, 18, 21, 1, 22,
+ 23, 1, 24, 1, 25, 26, 1, 27,
+ 1, 28, 28, 30, 31, 32, 32, 28,
+ 29, 27, 1, 33, 33, 35, 36, 33,
+ 34, 37, 1, 38, 39, 1, 40, 1,
+ 41, 41, 42, 42, 43, 41, 44, 1,
+ 28, 28, 30, 27, 32, 32, 28, 29,
+ 26, 1, 35, 35, 34, 34, 36, 35,
+ 37, 1, 45, 45, 46, 46, 47, 45,
+ 48, 1, 49, 49, 50, 50, 51, 49,
+ 52, 1, 53, 54, 1, 55, 1, 56,
+ 56, 58, 59, 60, 60, 56, 57, 55,
+ 1, 61, 61, 63, 64, 61, 62, 65,
+ 1, 66, 67, 1, 68, 1, 69, 69,
+ 71, 72, 73, 73, 69, 70, 68, 1,
+ 74, 74, 76, 77, 74, 75, 78, 1,
+ 79, 80, 1, 81, 1, 82, 82, 83,
+ 84, 84, 82, 81, 1, 85, 85, 86,
+ 87, 88, 85, 1, 86, 86, 87, 88,
+ 86, 1, 89, 89, 90, 91, 92, 89,
+ 1, 93, 93, 94, 95, 96, 93, 1,
+ 94, 94, 95, 96, 94, 1, 97, 97,
+ 99, 100, 97, 98, 101, 1, 102, 102,
+ 104, 105, 102, 103, 106, 1, 107, 108,
+ 1, 109, 1, 110, 110, 112, 113, 114,
+ 114, 110, 111, 109, 1, 115, 115, 117,
+ 118, 115, 116, 119, 1, 120, 121, 1,
+ 122, 1, 56, 56, 58, 55, 60, 60,
+ 56, 57, 54, 1, 63, 63, 62, 62,
+ 64, 63, 65, 1, 69, 69, 71, 68,
+ 73, 73, 69, 70, 67, 1, 76, 76,
+ 75, 75, 77, 76, 78, 1, 82, 82,
+ 83, 81, 84, 84, 82, 80, 1, 123,
+ 123, 124, 1, 124, 1, 82, 82, 83,
+ 82, 124, 1, 125, 125, 126, 1, 126,
+ 1, 69, 69, 71, 72, 69, 70, 126,
+ 1, 127, 127, 128, 1, 128, 1, 56,
+ 56, 58, 59, 56, 57, 128, 1, 129,
+ 129, 130, 130, 131, 129, 132, 1, 133,
+ 133, 134, 134, 135, 133, 136, 1, 137,
+ 138, 1, 139, 1, 140, 140, 142, 143,
+ 144, 144, 140, 141, 139, 1, 145, 145,
+ 147, 148, 145, 146, 149, 1, 150, 151,
+ 1, 152, 1, 153, 153, 155, 156, 157,
+ 157, 153, 154, 152, 1, 158, 158, 160,
+ 161, 158, 159, 162, 1, 163, 164, 1,
+ 165, 1, 166, 166, 168, 169, 170, 170,
+ 166, 167, 165, 1, 171, 171, 173, 174,
+ 171, 172, 175, 1, 176, 177, 1, 178,
+ 1, 179, 179, 181, 182, 183, 183, 179,
+ 180, 178, 1, 184, 184, 186, 187, 184,
+ 185, 188, 1, 189, 190, 1, 191, 1,
+ 192, 192, 194, 195, 196, 196, 192, 193,
+ 191, 1, 197, 197, 199, 200, 197, 198,
+ 201, 1, 202, 203, 1, 204, 1, 140,
+ 140, 142, 139, 144, 144, 140, 141, 138,
+ 1, 147, 147, 146, 146, 148, 147, 149,
+ 1, 153, 153, 155, 152, 157, 157, 153,
+ 154, 151, 1, 160, 160, 159, 159, 161,
+ 160, 162, 1, 166, 166, 168, 165, 170,
+ 170, 166, 167, 164, 1, 173, 173, 172,
+ 172, 174, 173, 175, 1, 179, 179, 181,
+ 178, 183, 183, 179, 180, 177, 1, 186,
+ 186, 185, 185, 187, 186, 188, 1, 192,
+ 192, 194, 191, 196, 196, 192, 193, 190,
+ 1, 199, 199, 198, 198, 200, 199, 201,
+ 1, 205, 205, 206, 1, 206, 1, 207,
+ 207, 208, 208, 209, 207, 210, 1, 211,
+ 211, 212, 212, 213, 211, 214, 1, 215,
+ 216, 1, 217, 1, 218, 218, 219, 1,
+ 219, 1, 220, 220, 221, 221, 222, 220,
+ 223, 1, 224, 224, 225, 225, 226, 224,
+ 227, 1, 228, 228, 229, 229, 230, 228,
+ 231, 1, 232, 233, 1, 234, 1, 235,
+ 235, 237, 238, 239, 239, 235, 236, 234,
+ 1, 240, 240, 242, 243, 240, 241, 244,
+ 1, 245, 246, 1, 247, 1, 248, 248,
+ 250, 251, 252, 252, 248, 249, 247, 1,
+ 253, 253, 255, 256, 253, 254, 257, 1,
+ 258, 259, 1, 260, 1, 261, 261, 263,
+ 264, 265, 265, 261, 262, 260, 1, 266,
+ 266, 268, 269, 266, 267, 270, 1, 271,
+ 272, 1, 273, 1, 235, 235, 237, 234,
+ 239, 239, 235, 236, 233, 1, 242, 242,
+ 241, 241, 243, 242, 244, 1, 248, 248,
+ 250, 247, 252, 252, 248, 249, 246, 1,
+ 255, 255, 254, 254, 256, 255, 257, 1,
+ 261, 261, 263, 260, 265, 265, 261, 262,
+ 259, 1, 268, 268, 267, 267, 269, 268,
+ 270, 1, 274, 274, 275, 1, 275, 1,
+ 276, 276, 277, 277, 278, 276, 279, 1,
+ 280, 280, 281, 281, 282, 280, 283, 1,
+ 284, 285, 1, 286, 1, 287, 287, 289,
+ 290, 291, 291, 287, 288, 286, 1, 292,
+ 292, 294, 295, 292, 293, 296, 1, 297,
+ 298, 1, 299, 1, 300, 300, 302, 303,
+ 304, 304, 300, 301, 299, 1, 305, 305,
+ 307, 308, 305, 306, 309, 1, 310, 311,
+ 1, 312, 1, 313, 313, 315, 316, 317,
+ 317, 313, 314, 312, 1, 318, 318, 320,
+ 321, 318, 319, 322, 1, 323, 324, 1,
+ 325, 1, 287, 287, 289, 286, 291, 291,
+ 287, 288, 285, 1, 294, 294, 293, 293,
+ 295, 294, 296, 1, 300, 300, 302, 299,
+ 304, 304, 300, 301, 298, 1, 307, 307,
+ 306, 306, 308, 307, 309, 1, 313, 313,
+ 315, 312, 317, 317, 313, 314, 311, 1,
+ 320, 320, 319, 319, 321, 320, 322, 1,
+ 326, 326, 327, 1, 327, 1, 328, 328,
+ 329, 329, 330, 328, 331, 1, 332, 332,
+ 333, 333, 334, 332, 335, 1, 336, 337,
+ 1, 338, 1, 339, 339, 341, 342, 343,
+ 343, 339, 340, 338, 1, 344, 344, 346,
+ 347, 344, 345, 348, 1, 349, 350, 1,
+ 351, 1, 339, 339, 341, 338, 343, 343,
+ 339, 340, 337, 1, 346, 346, 345, 345,
+ 347, 346, 348, 1, 352, 352, 353, 1,
+ 353, 1, 354, 354, 355, 355, 356, 354,
+ 357, 1, 358, 358, 359, 359, 360, 358,
+ 361, 1, 362, 363, 1, 364, 1, 365,
+ 365, 366, 1, 366, 1, 367, 367, 368,
+ 368, 369, 367, 370, 1, 371, 371, 372,
+ 372, 373, 371, 374, 1, 375, 375, 376,
+ 376, 377, 375, 378, 1, 379, 379, 380,
+ 380, 381, 379, 382, 1, 383, 383, 384,
+ 384, 385, 383, 386, 1, 12, 12, 14,
+ 11, 16, 16, 12, 13, 10, 1, 19,
+ 19, 18, 18, 20, 19, 21, 1, 387,
+ 387, 388, 1, 388, 1, 389, 389, 390,
+ 390, 391, 389, 392, 1, 393, 393, 394,
+ 394, 395, 393, 396, 1, 397, 397, 398,
+ 398, 399, 397, 400, 1, 401, 401, 402,
+ 402, 403, 401, 404, 1, 405, 405, 406,
+ 1, 406, 1, 12, 12, 14, 15, 12,
+ 13, 406, 1, 407, 407, 408, 1, 408,
+ 1, 339, 339, 341, 342, 339, 340, 408,
+ 1, 409, 409, 410, 1, 410, 1, 313,
+ 313, 315, 316, 313, 314, 410, 1, 411,
+ 411, 412, 1, 412, 1, 300, 300, 302,
+ 303, 300, 301, 412, 1, 413, 413, 414,
+ 1, 414, 1, 287, 287, 289, 290, 287,
+ 288, 414, 1, 415, 415, 416, 1, 416,
+ 1, 261, 261, 263, 264, 261, 262, 416,
+ 1, 417, 417, 418, 1, 418, 1, 248,
+ 248, 250, 251, 248, 249, 418, 1, 419,
+ 419, 420, 1, 420, 1, 235, 235, 237,
+ 238, 235, 236, 420, 1, 421, 421, 422,
+ 1, 422, 1, 192, 192, 194, 195, 192,
+ 193, 422, 1, 423, 423, 424, 1, 424,
+ 1, 179, 179, 181, 182, 179, 180, 424,
+ 1, 425, 425, 426, 1, 426, 1, 166,
+ 166, 168, 169, 166, 167, 426, 1, 427,
+ 427, 428, 1, 428, 1, 153, 153, 155,
+ 156, 153, 154, 428, 1, 429, 429, 430,
+ 1, 430, 1, 140, 140, 142, 143, 140,
+ 141, 430, 1, 431, 431, 432, 1, 432,
+ 1, 117, 117, 116, 116, 118, 117, 119,
+ 1, 433, 433, 434, 1, 434, 1, 110,
+ 110, 112, 113, 110, 111, 434, 1, 110,
+ 110, 112, 109, 114, 114, 110, 111, 108,
+ 1, 104, 104, 103, 103, 105, 104, 106,
+ 1, 435, 435, 437, 438, 435, 436, 439,
+ 1, 440, 440, 441, 442, 443, 440, 1,
+ 444, 444, 445, 1, 445, 1, 446, 446,
+ 447, 1, 447, 1, 28, 28, 30, 31,
+ 28, 29, 447, 1, 448, 448, 449, 450,
+ 448, 1, 451, 451, 453, 454, 455, 456,
+ 457, 458, 459, 460, 461, 462, 463, 464,
+ 465, 466, 467, 457, 468, 469, 470, 471,
+ 472, 473, 474, 465, 451, 452, 24, 1,
+ 475, 475, 41, 43, 476, 477, 478, 479,
+ 449, 480, 481, 482, 483, 484, 485, 486,
+ 487, 488, 450, 489, 490, 491, 492, 484,
+ 475, 42, 44, 1, 493, 493, 495, 496,
+ 497, 498, 499, 500, 501, 502, 503, 504,
+ 505, 506, 507, 508, 509, 499, 510, 511,
+ 512, 513, 514, 515, 516, 507, 493, 494,
+ 40, 1, 493, 493, 495, 40, 497, 498,
+ 499, 500, 501, 502, 503, 504, 505, 506,
+ 507, 508, 509, 499, 510, 511, 512, 513,
+ 514, 515, 516, 507, 493, 494, 39, 1,
+ 517, 517, 519, 520, 521, 522, 523, 524,
+ 525, 526, 527, 528, 529, 530, 531, 532,
+ 533, 523, 534, 535, 536, 537, 538, 539,
+ 540, 531, 517, 518, 122, 1, 541, 541,
+ 49, 51, 476, 477, 478, 479, 449, 480,
+ 481, 482, 483, 484, 485, 486, 487, 488,
+ 450, 489, 490, 491, 492, 484, 541, 50,
+ 52, 1, 542, 542, 544, 545, 546, 547,
+ 548, 549, 550, 551, 552, 553, 554, 555,
+ 556, 557, 558, 548, 559, 560, 561, 562,
+ 563, 564, 565, 556, 542, 543, 204, 1,
+ 566, 566, 133, 135, 476, 477, 478, 479,
+ 449, 480, 481, 482, 483, 484, 485, 486,
+ 487, 488, 450, 489, 490, 491, 492, 484,
+ 566, 134, 136, 1, 542, 542, 544, 204,
+ 546, 547, 548, 549, 550, 551, 552, 553,
+ 554, 555, 556, 557, 558, 548, 559, 560,
+ 561, 562, 563, 564, 565, 556, 542, 543,
+ 203, 1, 542, 542, 544, 545, 546, 547,
+ 549, 550, 551, 552, 553, 554, 555, 556,
+ 557, 558, 559, 560, 561, 562, 563, 564,
+ 565, 556, 542, 543, 206, 1, 567, 567,
+ 569, 570, 571, 572, 573, 574, 575, 576,
+ 577, 578, 579, 580, 581, 582, 583, 573,
+ 584, 585, 586, 587, 588, 589, 590, 581,
+ 567, 568, 217, 1, 591, 591, 211, 213,
+ 476, 477, 478, 479, 449, 480, 481, 482,
+ 483, 484, 485, 486, 487, 488, 450, 489,
+ 490, 491, 492, 484, 591, 212, 214, 1,
+ 567, 567, 569, 217, 571, 572, 573, 574,
+ 575, 576, 577, 578, 579, 580, 581, 582,
+ 583, 573, 584, 585, 586, 587, 588, 589,
+ 590, 581, 567, 568, 216, 1, 567, 567,
+ 569, 570, 571, 572, 574, 575, 576, 577,
+ 578, 579, 580, 581, 582, 583, 584, 585,
+ 586, 587, 588, 589, 590, 581, 567, 568,
+ 219, 1, 592, 592, 594, 595, 596, 597,
+ 598, 599, 600, 601, 602, 603, 604, 605,
+ 606, 607, 608, 598, 609, 610, 611, 612,
+ 613, 614, 615, 606, 592, 593, 273, 1,
+ 616, 616, 228, 230, 476, 477, 478, 479,
+ 449, 480, 481, 482, 483, 484, 485, 486,
+ 487, 488, 450, 489, 490, 491, 492, 484,
+ 616, 229, 231, 1, 592, 592, 594, 273,
+ 596, 597, 598, 599, 600, 601, 602, 603,
+ 604, 605, 606, 607, 608, 598, 609, 610,
+ 611, 612, 613, 614, 615, 606, 592, 593,
+ 272, 1, 592, 592, 594, 595, 596, 597,
+ 599, 600, 601, 602, 603, 604, 605, 606,
+ 607, 608, 609, 610, 611, 612, 613, 614,
+ 615, 606, 592, 593, 275, 1, 617, 617,
+ 619, 620, 621, 622, 623, 624, 625, 626,
+ 627, 628, 629, 630, 631, 632, 633, 623,
+ 634, 635, 636, 637, 638, 639, 640, 631,
+ 617, 618, 325, 1, 641, 641, 280, 282,
+ 476, 477, 478, 479, 449, 480, 481, 482,
+ 483, 484, 485, 486, 487, 488, 450, 489,
+ 490, 491, 492, 484, 641, 281, 283, 1,
+ 617, 617, 619, 325, 621, 622, 623, 624,
+ 625, 626, 627, 628, 629, 630, 631, 632,
+ 633, 623, 634, 635, 636, 637, 638, 639,
+ 640, 631, 617, 618, 324, 1, 617, 617,
+ 619, 620, 621, 622, 624, 625, 626, 627,
+ 628, 629, 630, 631, 632, 633, 634, 635,
+ 636, 637, 638, 639, 640, 631, 617, 618,
+ 327, 1, 642, 642, 644, 645, 646, 647,
+ 648, 649, 650, 651, 652, 653, 654, 655,
+ 656, 657, 658, 648, 659, 660, 661, 662,
+ 663, 664, 665, 656, 642, 643, 351, 1,
+ 666, 666, 332, 334, 476, 477, 478, 479,
+ 449, 480, 481, 482, 483, 484, 485, 486,
+ 487, 488, 450, 489, 490, 491, 492, 484,
+ 666, 333, 335, 1, 642, 642, 644, 351,
+ 646, 647, 648, 649, 650, 651, 652, 653,
+ 654, 655, 656, 657, 658, 648, 659, 660,
+ 661, 662, 663, 664, 665, 656, 642, 643,
+ 350, 1, 642, 642, 644, 645, 646, 647,
+ 649, 650, 651, 652, 653, 654, 655, 656,
+ 657, 658, 659, 660, 661, 662, 663, 664,
+ 665, 656, 642, 643, 353, 1, 667, 667,
+ 669, 670, 671, 672, 673, 674, 675, 676,
+ 677, 678, 679, 680, 681, 682, 683, 673,
+ 684, 685, 686, 687, 688, 689, 690, 681,
+ 667, 668, 364, 1, 691, 691, 358, 360,
+ 476, 477, 478, 479, 449, 480, 481, 482,
+ 483, 484, 485, 486, 487, 488, 450, 489,
+ 490, 491, 492, 484, 691, 359, 361, 1,
+ 667, 667, 669, 364, 671, 672, 673, 674,
+ 675, 676, 677, 678, 679, 680, 681, 682,
+ 683, 673, 684, 685, 686, 687, 688, 689,
+ 690, 681, 667, 668, 363, 1, 667, 667,
+ 669, 670, 671, 672, 674, 675, 676, 677,
+ 678, 679, 680, 681, 682, 683, 684, 685,
+ 686, 687, 688, 689, 690, 681, 667, 668,
+ 366, 1, 692, 692, 693, 694, 695, 696,
+ 697, 698, 699, 700, 701, 702, 703, 704,
+ 705, 706, 707, 708, 709, 710, 711, 702,
+ 692, 1, 712, 712, 476, 477, 478, 479,
+ 449, 480, 481, 482, 483, 484, 485, 486,
+ 487, 488, 450, 489, 490, 491, 492, 484,
+ 712, 1, 451, 451, 453, 24, 455, 456,
+ 457, 458, 459, 460, 461, 462, 463, 464,
+ 465, 466, 467, 457, 468, 469, 470, 471,
+ 472, 473, 474, 465, 451, 452, 23, 1,
+ 451, 451, 453, 454, 455, 456, 458, 459,
+ 460, 461, 462, 463, 464, 465, 466, 467,
+ 468, 469, 470, 471, 472, 473, 474, 465,
+ 451, 452, 388, 1, 517, 517, 519, 520,
+ 521, 522, 524, 525, 526, 527, 528, 529,
+ 530, 531, 532, 533, 534, 535, 536, 537,
+ 538, 539, 540, 531, 517, 518, 432, 1,
+ 517, 517, 519, 122, 521, 522, 523, 524,
+ 525, 526, 527, 528, 529, 530, 531, 532,
+ 533, 523, 534, 535, 536, 537, 538, 539,
+ 540, 531, 517, 518, 121, 1, 493, 493,
+ 495, 496, 497, 498, 500, 501, 502, 503,
+ 504, 505, 506, 507, 508, 509, 510, 511,
+ 512, 513, 514, 515, 516, 507, 493, 494,
+ 445, 1, 0
+};
+
+static const short _svg_path_trans_targs[] = {
+ 2, 0, 3, 4, 172, 2, 3, 4,
+ 172, 4, 172, 5, 6, 7, 173, 8,
+ 180, 6, 7, 173, 8, 267, 8, 267,
+ 235, 10, 16, 11, 12, 13, 17, 14,
+ 231, 12, 13, 17, 14, 238, 14, 238,
+ 237, 15, 9, 10, 16, 19, 20, 21,
+ 44, 19, 20, 21, 44, 21, 44, 22,
+ 23, 24, 45, 25, 55, 23, 24, 45,
+ 25, 46, 25, 46, 26, 27, 28, 47,
+ 29, 52, 27, 28, 47, 29, 48, 29,
+ 48, 30, 31, 32, 49, 31, 32, 33,
+ 228, 34, 35, 36, 227, 34, 35, 36,
+ 227, 37, 38, 226, 39, 225, 37, 38,
+ 226, 39, 225, 39, 225, 40, 41, 42,
+ 221, 43, 222, 41, 42, 221, 43, 270,
+ 43, 270, 239, 50, 51, 53, 54, 56,
+ 57, 59, 60, 61, 82, 59, 60, 61,
+ 82, 61, 82, 62, 63, 64, 83, 65,
+ 216, 63, 64, 83, 65, 84, 65, 84,
+ 66, 67, 68, 85, 69, 213, 67, 68,
+ 85, 69, 86, 69, 86, 70, 71, 72,
+ 87, 73, 210, 71, 72, 87, 73, 88,
+ 73, 88, 74, 75, 76, 89, 77, 207,
+ 75, 76, 89, 77, 90, 77, 90, 78,
+ 79, 80, 91, 81, 204, 79, 80, 91,
+ 81, 243, 81, 243, 241, 93, 244, 95,
+ 96, 97, 247, 95, 96, 97, 247, 97,
+ 247, 245, 99, 248, 15, 9, 10, 16,
+ 102, 103, 104, 117, 102, 103, 104, 117,
+ 104, 117, 105, 106, 107, 118, 108, 201,
+ 106, 107, 118, 108, 119, 108, 119, 109,
+ 110, 111, 120, 112, 198, 110, 111, 120,
+ 112, 121, 112, 121, 113, 114, 115, 122,
+ 116, 195, 114, 115, 122, 116, 251, 116,
+ 251, 249, 124, 252, 126, 127, 128, 141,
+ 126, 127, 128, 141, 128, 141, 129, 130,
+ 131, 142, 132, 192, 130, 131, 142, 132,
+ 143, 132, 143, 133, 134, 135, 144, 136,
+ 189, 134, 135, 144, 136, 145, 136, 145,
+ 137, 138, 139, 146, 140, 186, 138, 139,
+ 146, 140, 255, 140, 255, 253, 148, 256,
+ 150, 151, 152, 157, 150, 151, 152, 157,
+ 152, 157, 153, 154, 155, 158, 156, 183,
+ 154, 155, 158, 156, 259, 156, 259, 257,
+ 160, 260, 162, 163, 164, 263, 162, 163,
+ 164, 263, 164, 263, 261, 166, 264, 19,
+ 20, 21, 44, 59, 60, 61, 82, 95,
+ 96, 97, 247, 15, 9, 10, 16, 2,
+ 3, 4, 172, 175, 268, 102, 103, 104,
+ 117, 126, 127, 128, 141, 150, 151, 152,
+ 157, 162, 163, 164, 263, 181, 182, 184,
+ 185, 187, 188, 190, 191, 193, 194, 196,
+ 197, 199, 200, 202, 203, 205, 206, 208,
+ 209, 211, 212, 214, 215, 217, 218, 220,
+ 269, 223, 224, 37, 38, 226, 39, 225,
+ 34, 35, 36, 227, 230, 271, 232, 233,
+ 234, 1, 171, 236, 9, 15, 10, 18,
+ 58, 174, 94, 100, 1, 101, 125, 149,
+ 161, 265, 167, 168, 169, 170, 171, 176,
+ 177, 178, 179, 236, 18, 58, 94, 100,
+ 101, 125, 149, 161, 265, 167, 168, 169,
+ 170, 176, 177, 178, 179, 236, 9, 15,
+ 10, 18, 58, 229, 94, 100, 1, 101,
+ 125, 149, 161, 265, 167, 168, 169, 170,
+ 171, 176, 177, 178, 179, 240, 20, 19,
+ 21, 18, 58, 219, 94, 100, 1, 101,
+ 125, 149, 161, 265, 167, 168, 169, 170,
+ 171, 176, 177, 178, 179, 240, 242, 60,
+ 59, 61, 18, 58, 92, 94, 100, 1,
+ 101, 125, 149, 161, 265, 167, 168, 169,
+ 170, 171, 176, 177, 178, 179, 242, 246,
+ 96, 95, 97, 18, 58, 98, 94, 100,
+ 1, 101, 125, 149, 161, 265, 167, 168,
+ 169, 170, 171, 176, 177, 178, 179, 246,
+ 250, 103, 102, 104, 18, 58, 123, 94,
+ 100, 1, 101, 125, 149, 161, 265, 167,
+ 168, 169, 170, 171, 176, 177, 178, 179,
+ 250, 254, 127, 126, 128, 18, 58, 147,
+ 94, 100, 1, 101, 125, 149, 161, 265,
+ 167, 168, 169, 170, 171, 176, 177, 178,
+ 179, 254, 258, 151, 150, 152, 18, 58,
+ 159, 94, 100, 1, 101, 125, 149, 161,
+ 265, 167, 168, 169, 170, 171, 176, 177,
+ 178, 179, 258, 262, 163, 162, 164, 18,
+ 58, 165, 94, 100, 1, 101, 125, 149,
+ 161, 265, 167, 168, 169, 170, 171, 176,
+ 177, 178, 179, 262, 266, 18, 58, 94,
+ 100, 1, 101, 125, 149, 161, 265, 167,
+ 168, 169, 170, 171, 176, 177, 178, 179,
+ 266
+};
+
+static const char _svg_path_trans_actions[] = {
+ 9, 0, 51, 51, 51, 0, 1, 1,
+ 1, 0, 0, 0, 3, 15, 3, 15,
+ 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 3, 15, 3, 15,
+ 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 0, 1, 1, 1, 9, 51, 51,
+ 51, 0, 1, 1, 1, 0, 0, 0,
+ 3, 15, 3, 15, 0, 0, 1, 0,
+ 1, 1, 0, 0, 0, 3, 15, 3,
+ 15, 0, 0, 1, 0, 1, 1, 0,
+ 0, 0, 3, 3, 0, 0, 0, 0,
+ 0, 7, 7, 7, 7, 0, 0, 0,
+ 0, 7, 48, 7, 48, 48, 0, 1,
+ 0, 1, 1, 0, 0, 0, 3, 15,
+ 3, 15, 0, 0, 1, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 9, 51, 51, 51, 0, 1, 1,
+ 1, 0, 0, 0, 3, 15, 3, 15,
+ 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 3, 15, 3, 15, 0, 0, 1,
+ 0, 1, 1, 0, 0, 0, 3, 15,
+ 3, 15, 0, 0, 1, 0, 1, 1,
+ 0, 0, 0, 3, 15, 3, 15, 0,
+ 0, 1, 0, 1, 1, 0, 0, 0,
+ 3, 15, 3, 15, 0, 0, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 9,
+ 51, 51, 51, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 9, 51, 51, 51,
+ 9, 51, 51, 51, 0, 1, 1, 1,
+ 0, 0, 0, 3, 15, 3, 15, 0,
+ 0, 1, 0, 1, 1, 0, 0, 0,
+ 3, 15, 3, 15, 0, 0, 1, 0,
+ 1, 1, 0, 0, 0, 3, 15, 3,
+ 15, 0, 0, 1, 0, 1, 1, 0,
+ 0, 0, 0, 0, 9, 51, 51, 51,
+ 0, 1, 1, 1, 0, 0, 0, 3,
+ 15, 3, 15, 0, 0, 1, 0, 1,
+ 1, 0, 0, 0, 3, 15, 3, 15,
+ 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 3, 15, 3, 15, 0, 0, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0,
+ 9, 51, 51, 51, 0, 1, 1, 1,
+ 0, 0, 0, 3, 15, 3, 15, 0,
+ 0, 1, 0, 1, 1, 0, 0, 0,
+ 0, 0, 9, 51, 51, 51, 0, 1,
+ 1, 1, 0, 0, 0, 0, 0, 11,
+ 54, 54, 54, 11, 54, 54, 54, 11,
+ 54, 54, 54, 11, 54, 54, 54, 11,
+ 54, 54, 54, 0, 0, 11, 54, 54,
+ 54, 11, 54, 54, 54, 11, 54, 54,
+ 54, 11, 54, 54, 54, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 5, 45, 5, 45, 45,
+ 5, 5, 5, 5, 0, 0, 0, 0,
+ 0, 0, 0, 18, 57, 18, 57, 18,
+ 18, 0, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 21, 61, 21,
+ 61, 21, 21, 0, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 42, 89, 42,
+ 89, 42, 42, 0, 42, 42, 42, 42,
+ 42, 42, 42, 42, 42, 42, 42, 42,
+ 42, 42, 42, 42, 42, 0, 30, 73,
+ 30, 73, 30, 30, 0, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 0, 24,
+ 65, 24, 65, 24, 24, 0, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 0,
+ 36, 81, 36, 81, 36, 36, 0, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36,
+ 0, 33, 77, 33, 77, 33, 33, 0,
+ 33, 33, 33, 33, 33, 33, 33, 33,
+ 33, 33, 33, 33, 33, 33, 33, 33,
+ 33, 0, 39, 85, 39, 85, 39, 39,
+ 0, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 0, 27, 69, 27, 69, 27,
+ 27, 0, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 0, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13,
+ 0
+};
+
+static const char _svg_path_eof_actions[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 18, 0, 21, 21, 42,
+ 0, 30, 0, 30, 30, 24, 0, 24,
+ 24, 36, 0, 36, 36, 33, 0, 33,
+ 33, 39, 0, 39, 39, 27, 0, 27,
+ 27, 13, 0, 18, 18, 42, 42, 21
+};
+
+static const int svg_path_start = 234;
+static const int svg_path_first_final = 234;
+
+static const int svg_path_en_main = 234;
+
+
+#line 47 "svg-path-parser.rl"
+
+
+SVGPathParser::SVGPathParser(PathSink &sink)
+ : _absolute(false)
+ , _sink(sink)
+ , _z_snap_threshold(0)
+ , _curve(NULL)
+{
+ reset();
+}
+
+SVGPathParser::~SVGPathParser()
+{
+ delete _curve;
+}
+
+void SVGPathParser::reset() {
+ _absolute = false;
+ _current = _initial = Point(0, 0);
+ _quad_tangent = _cubic_tangent = Point(0, 0);
+ _params.clear();
+ delete _curve;
+ _curve = NULL;
+
+
+#line 1113 "svg-path-parser.cpp"
+ {
+ cs = svg_path_start;
+ }
+
+#line 73 "svg-path-parser.rl"
+
+}
+
+void SVGPathParser::parse(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, true);
+}
+
+void SVGPathParser::parse(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), true);
+}
+
+void SVGPathParser::feed(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, false);
+}
+
+void SVGPathParser::feed(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), false);
+}
+
+void SVGPathParser::finish()
+{
+ char const *empty = "";
+ _parse(empty, empty, true);
+}
+
+void SVGPathParser::_push(Coord value)
+{
+ _params.push_back(value);
+}
+
+Coord SVGPathParser::_pop()
+{
+ Coord value = _params.back();
+ _params.pop_back();
+ return value;
+}
+
+bool SVGPathParser::_pop_flag()
+{
+ return _pop() != 0.0;
+}
+
+Coord SVGPathParser::_pop_coord(Dim2 axis)
+{
+ if (_absolute) {
+ return _pop();
+ } else {
+ return _pop() + _current[axis];
+ }
+}
+
+Point SVGPathParser::_pop_point()
+{
+ Coord y = _pop_coord(Y);
+ Coord x = _pop_coord(X);
+ return Point(x, y);
+}
+
+void SVGPathParser::_moveTo(Point const &p)
+{
+ _pushCurve(NULL); // flush
+ _sink.moveTo(p);
+ _quad_tangent = _cubic_tangent = _current = _initial = p;
+}
+
+void SVGPathParser::_lineTo(Point const &p)
+{
+ _pushCurve(new LineSegment(_current, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p)
+{
+ _pushCurve(new CubicBezier(_current, c0, c1, p));
+ _quad_tangent = _current = p;
+ _cubic_tangent = p + ( p - c1 );
+}
+
+void SVGPathParser::_quadTo(Point const &c, Point const &p)
+{
+ _pushCurve(new QuadraticBezier(_current, c, p));
+ _cubic_tangent = _current = p;
+ _quad_tangent = p + ( p - c );
+}
+
+void SVGPathParser::_arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p)
+{
+ if (_current == p) {
+ return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec)
+ }
+
+ _pushCurve(new EllipticalArc(_current, fabs(rx), fabs(ry), angle, large_arc, sweep, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_closePath()
+{
+ if (_curve && (!_absolute || !_moveto_was_absolute) &&
+ are_near(_initial, _current, _z_snap_threshold))
+ {
+ _curve->setFinal(_initial);
+ }
+
+ _pushCurve(NULL); // flush
+ _sink.closePath();
+ _quad_tangent = _cubic_tangent = _current = _initial;
+}
+
+void SVGPathParser::_pushCurve(Curve *c)
+{
+ if (_curve) {
+ _sink.feed(*_curve, false);
+ delete _curve;
+ }
+ _curve = c;
+}
+
+void SVGPathParser::_parse(char const *str, char const *strend, bool finish)
+{
+ char const *p = str;
+ char const *pe = strend;
+ char const *eof = finish ? pe : NULL;
+ char const *start = NULL;
+
+
+#line 1255 "svg-path-parser.cpp"
+ {
+ int _klen;
+ unsigned int _trans;
+ const char *_acts;
+ unsigned int _nacts;
+ const char *_keys;
+
+ if ( p == pe )
+ goto _test_eof;
+ if ( cs == 0 )
+ goto _out;
+_resume:
+ _keys = _svg_path_trans_keys + _svg_path_key_offsets[cs];
+ _trans = _svg_path_index_offsets[cs];
+
+ _klen = _svg_path_single_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + _klen - 1;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( (*p) < *_mid )
+ _upper = _mid - 1;
+ else if ( (*p) > *_mid )
+ _lower = _mid + 1;
+ else {
+ _trans += (unsigned int)(_mid - _keys);
+ goto _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _svg_path_range_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + (_klen<<1) - 2;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( (*p) < _mid[0] )
+ _upper = _mid - 2;
+ else if ( (*p) > _mid[1] )
+ _lower = _mid + 2;
+ else {
+ _trans += (unsigned int)((_mid - _keys)>>1);
+ goto _match;
+ }
+ }
+ _trans += _klen;
+ }
+
+_match:
+ _trans = _svg_path_indicies[_trans];
+ cs = _svg_path_trans_targs[_trans];
+
+ if ( _svg_path_trans_actions[_trans] == 0 )
+ goto _again;
+
+ _acts = _svg_path_actions + _svg_path_trans_actions[_trans];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 )
+ {
+ switch ( *_acts++ )
+ {
+ case 0:
+#line 209 "svg-path-parser.rl"
+ {
+ start = p;
+ }
+ break;
+ case 1:
+#line 213 "svg-path-parser.rl"
+ {
+ if (start) {
+ std::string buf(start, p);
+ _push(g_ascii_strtod(buf.c_str(), NULL));
+ start = NULL;
+ } else {
+ std::string buf(str, p);
+ _push(g_ascii_strtod((_number_part + buf).c_str(), NULL));
+ _number_part.clear();
+ }
+ }
+ break;
+ case 2:
+#line 225 "svg-path-parser.rl"
+ {
+ _push(1.0);
+ }
+ break;
+ case 3:
+#line 229 "svg-path-parser.rl"
+ {
+ _push(0.0);
+ }
+ break;
+ case 4:
+#line 233 "svg-path-parser.rl"
+ {
+ _absolute = true;
+ }
+ break;
+ case 5:
+#line 237 "svg-path-parser.rl"
+ {
+ _absolute = false;
+ }
+ break;
+ case 6:
+#line 241 "svg-path-parser.rl"
+ {
+ _moveto_was_absolute = _absolute;
+ _moveTo(_pop_point());
+ }
+ break;
+ case 7:
+#line 246 "svg-path-parser.rl"
+ {
+ _lineTo(_pop_point());
+ }
+ break;
+ case 8:
+#line 250 "svg-path-parser.rl"
+ {
+ _lineTo(Point(_pop_coord(X), _current[Y]));
+ }
+ break;
+ case 9:
+#line 254 "svg-path-parser.rl"
+ {
+ _lineTo(Point(_current[X], _pop_coord(Y)));
+ }
+ break;
+ case 10:
+#line 258 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ Point c0 = _pop_point();
+ _curveTo(c0, c1, p);
+ }
+ break;
+ case 11:
+#line 265 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ _curveTo(_cubic_tangent, c1, p);
+ }
+ break;
+ case 12:
+#line 271 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c = _pop_point();
+ _quadTo(c, p);
+ }
+ break;
+ case 13:
+#line 277 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ _quadTo(_quad_tangent, p);
+ }
+ break;
+ case 14:
+#line 282 "svg-path-parser.rl"
+ {
+ Point point = _pop_point();
+ bool sweep = _pop_flag();
+ bool large_arc = _pop_flag();
+ double angle = rad_from_deg(_pop());
+ double ry = _pop();
+ double rx = _pop();
+
+ _arcTo(rx, ry, angle, large_arc, sweep, point);
+ }
+ break;
+ case 15:
+#line 293 "svg-path-parser.rl"
+ {
+ _closePath();
+ }
+ break;
+#line 1449 "svg-path-parser.cpp"
+ }
+ }
+
+_again:
+ if ( cs == 0 )
+ goto _out;
+ if ( ++p != pe )
+ goto _resume;
+ _test_eof: {}
+ if ( p == eof )
+ {
+ const char *__acts = _svg_path_actions + _svg_path_eof_actions[cs];
+ unsigned int __nacts = (unsigned int) *__acts++;
+ while ( __nacts-- > 0 ) {
+ switch ( *__acts++ ) {
+ case 1:
+#line 213 "svg-path-parser.rl"
+ {
+ if (start) {
+ std::string buf(start, p);
+ _push(g_ascii_strtod(buf.c_str(), NULL));
+ start = NULL;
+ } else {
+ std::string buf(str, p);
+ _push(g_ascii_strtod((_number_part + buf).c_str(), NULL));
+ _number_part.clear();
+ }
+ }
+ break;
+ case 6:
+#line 241 "svg-path-parser.rl"
+ {
+ _moveto_was_absolute = _absolute;
+ _moveTo(_pop_point());
+ }
+ break;
+ case 7:
+#line 246 "svg-path-parser.rl"
+ {
+ _lineTo(_pop_point());
+ }
+ break;
+ case 8:
+#line 250 "svg-path-parser.rl"
+ {
+ _lineTo(Point(_pop_coord(X), _current[Y]));
+ }
+ break;
+ case 9:
+#line 254 "svg-path-parser.rl"
+ {
+ _lineTo(Point(_current[X], _pop_coord(Y)));
+ }
+ break;
+ case 10:
+#line 258 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ Point c0 = _pop_point();
+ _curveTo(c0, c1, p);
+ }
+ break;
+ case 11:
+#line 265 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ _curveTo(_cubic_tangent, c1, p);
+ }
+ break;
+ case 12:
+#line 271 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c = _pop_point();
+ _quadTo(c, p);
+ }
+ break;
+ case 13:
+#line 277 "svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ _quadTo(_quad_tangent, p);
+ }
+ break;
+ case 14:
+#line 282 "svg-path-parser.rl"
+ {
+ Point point = _pop_point();
+ bool sweep = _pop_flag();
+ bool large_arc = _pop_flag();
+ double angle = rad_from_deg(_pop());
+ double ry = _pop();
+ double rx = _pop();
+
+ _arcTo(rx, ry, angle, large_arc, sweep, point);
+ }
+ break;
+ case 15:
+#line 293 "svg-path-parser.rl"
+ {
+ _closePath();
+ }
+ break;
+#line 1555 "svg-path-parser.cpp"
+ }
+ }
+ }
+
+ _out: {}
+ }
+
+#line 435 "svg-path-parser.rl"
+
+
+ if (finish) {
+ if (cs < svg_path_first_final) {
+ throw SVGPathParseError();
+ }
+ } else if (start != NULL) {
+ _number_part = std::string(start, pe);
+ }
+
+ if (finish) {
+ _pushCurve(NULL);
+ _sink.flush();
+ reset();
+ }
+}
+
+void parse_svg_path(char const *str, PathSink &sink)
+{
+ SVGPathParser parser(sink);
+ parser.parse(str);
+}
+
+void parse_svg_path_file(FILE *fi, PathSink &sink)
+{
+ static const size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t bytes_read;
+ SVGPathParser parser(sink);
+
+ while (true) {
+ bytes_read = fread(buffer, 1, BUFFER_SIZE, fi);
+ if (bytes_read < BUFFER_SIZE) {
+ parser.parse(buffer, bytes_read);
+ break;
+ } else {
+ parser.feed(buffer, bytes_read);
+ }
+ }
+}
+
+} // 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=ragel:cindent:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path-parser.rl b/src/2geom/svg-path-parser.rl
new file mode 100644
index 0000000..7b3eb5a
--- /dev/null
+++ b/src/2geom/svg-path-parser.rl
@@ -0,0 +1,487 @@
+/**
+ * \file
+ * \brief parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 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.
+ *
+ */
+
+#include <cstdio>
+#include <cmath>
+#include <vector>
+#include <glib.h>
+
+#include <2geom/point.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/angle.h>
+
+namespace Geom {
+
+%%{
+ machine svg_path;
+ write data noerror;
+}%%
+
+SVGPathParser::SVGPathParser(PathSink &sink)
+ : _absolute(false)
+ , _sink(sink)
+ , _z_snap_threshold(0)
+ , _curve(NULL)
+{
+ reset();
+}
+
+SVGPathParser::~SVGPathParser()
+{
+ delete _curve;
+}
+
+void SVGPathParser::reset() {
+ _absolute = false;
+ _current = _initial = Point(0, 0);
+ _quad_tangent = _cubic_tangent = Point(0, 0);
+ _params.clear();
+ delete _curve;
+ _curve = NULL;
+
+ %%{
+ write init;
+ }%%
+}
+
+void SVGPathParser::parse(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, true);
+}
+
+void SVGPathParser::parse(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), true);
+}
+
+void SVGPathParser::feed(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, false);
+}
+
+void SVGPathParser::feed(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), false);
+}
+
+void SVGPathParser::finish()
+{
+ char const *empty = "";
+ _parse(empty, empty, true);
+}
+
+void SVGPathParser::_push(Coord value)
+{
+ _params.push_back(value);
+}
+
+Coord SVGPathParser::_pop()
+{
+ Coord value = _params.back();
+ _params.pop_back();
+ return value;
+}
+
+bool SVGPathParser::_pop_flag()
+{
+ return _pop() != 0.0;
+}
+
+Coord SVGPathParser::_pop_coord(Dim2 axis)
+{
+ if (_absolute) {
+ return _pop();
+ } else {
+ return _pop() + _current[axis];
+ }
+}
+
+Point SVGPathParser::_pop_point()
+{
+ Coord y = _pop_coord(Y);
+ Coord x = _pop_coord(X);
+ return Point(x, y);
+}
+
+void SVGPathParser::_moveTo(Point const &p)
+{
+ _pushCurve(NULL); // flush
+ _sink.moveTo(p);
+ _quad_tangent = _cubic_tangent = _current = _initial = p;
+}
+
+void SVGPathParser::_lineTo(Point const &p)
+{
+ _pushCurve(new LineSegment(_current, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p)
+{
+ _pushCurve(new CubicBezier(_current, c0, c1, p));
+ _quad_tangent = _current = p;
+ _cubic_tangent = p + ( p - c1 );
+}
+
+void SVGPathParser::_quadTo(Point const &c, Point const &p)
+{
+ _pushCurve(new QuadraticBezier(_current, c, p));
+ _cubic_tangent = _current = p;
+ _quad_tangent = p + ( p - c );
+}
+
+void SVGPathParser::_arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p)
+{
+ if (_current == p) {
+ return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec)
+ }
+
+ _pushCurve(new EllipticalArc(_current, fabs(rx), fabs(ry), angle, large_arc, sweep, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_closePath()
+{
+ if (_curve && (!_absolute || !_moveto_was_absolute) &&
+ are_near(_initial, _current, _z_snap_threshold))
+ {
+ _curve->setFinal(_initial);
+ }
+
+ _pushCurve(NULL); // flush
+ _sink.closePath();
+ _quad_tangent = _cubic_tangent = _current = _initial;
+}
+
+void SVGPathParser::_pushCurve(Curve *c)
+{
+ if (_curve) {
+ _sink.feed(*_curve, false);
+ delete _curve;
+ }
+ _curve = c;
+}
+
+void SVGPathParser::_parse(char const *str, char const *strend, bool finish)
+{
+ char const *p = str;
+ char const *pe = strend;
+ char const *eof = finish ? pe : NULL;
+ char const *start = NULL;
+
+ %%{
+ action start_number {
+ start = p;
+ }
+
+ action push_number {
+ if (start) {
+ std::string buf(start, p);
+ _push(g_ascii_strtod(buf.c_str(), NULL));
+ start = NULL;
+ } else {
+ std::string buf(str, p);
+ _push(g_ascii_strtod((_number_part + buf).c_str(), NULL));
+ _number_part.clear();
+ }
+ }
+
+ action push_true {
+ _push(1.0);
+ }
+
+ action push_false {
+ _push(0.0);
+ }
+
+ action mode_abs {
+ _absolute = true;
+ }
+
+ action mode_rel {
+ _absolute = false;
+ }
+
+ action moveto {
+ _moveto_was_absolute = _absolute;
+ _moveTo(_pop_point());
+ }
+
+ action lineto {
+ _lineTo(_pop_point());
+ }
+
+ action horizontal_lineto {
+ _lineTo(Point(_pop_coord(X), _current[Y]));
+ }
+
+ action vertical_lineto {
+ _lineTo(Point(_current[X], _pop_coord(Y)));
+ }
+
+ action curveto {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ Point c0 = _pop_point();
+ _curveTo(c0, c1, p);
+ }
+
+ action smooth_curveto {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ _curveTo(_cubic_tangent, c1, p);
+ }
+
+ action quadratic_bezier_curveto {
+ Point p = _pop_point();
+ Point c = _pop_point();
+ _quadTo(c, p);
+ }
+
+ action smooth_quadratic_bezier_curveto {
+ Point p = _pop_point();
+ _quadTo(_quad_tangent, p);
+ }
+
+ action elliptical_arc {
+ Point point = _pop_point();
+ bool sweep = _pop_flag();
+ bool large_arc = _pop_flag();
+ double angle = rad_from_deg(_pop());
+ double ry = _pop();
+ double rx = _pop();
+
+ _arcTo(rx, ry, angle, large_arc, sweep, point);
+ }
+
+ action closepath {
+ _closePath();
+ }
+
+ wsp = (' ' | 9 | 10 | 13);
+ sign = ('+' | '-');
+ digit_sequence = digit+;
+ exponent = ('e' | 'E') sign? digit_sequence;
+ fractional_constant =
+ digit_sequence? '.' digit_sequence
+ | digit_sequence '.';
+ floating_point_constant =
+ fractional_constant exponent?
+ | digit_sequence exponent;
+ integer_constant = digit_sequence;
+ comma = ',';
+ comma_wsp = (wsp+ comma? wsp*) | (comma wsp*);
+
+ flag = ('0' %push_false | '1' %push_true);
+
+ number =
+ ( sign? integer_constant
+ | sign? floating_point_constant )
+ >start_number %push_number;
+
+ nonnegative_number =
+ ( integer_constant
+ | floating_point_constant)
+ >start_number %push_number;
+
+ coordinate = number $(number,1) %(number,0);
+ coordinate_pair = (coordinate $(coordinate_pair_a,1) %(coordinate_pair_a,0) comma_wsp? coordinate $(coordinate_pair_b,1) %(coordinate_pair_b,0)) $(coordinate_pair,1) %(coordinate_pair,0);
+ elliptical_arc_argument =
+ (number $(elliptical_arg_a,1) %(elliptical_arg_a,0) comma_wsp?
+ number $(elliptical_arg_b,1) %(elliptical_arg_b,0) comma_wsp?
+ number comma_wsp
+ flag comma_wsp? flag comma_wsp?
+ coordinate_pair)
+ %elliptical_arc;
+ elliptical_arc_argument_sequence =
+ elliptical_arc_argument $1 %0
+ (comma_wsp? elliptical_arc_argument $1 %0)*;
+ elliptical_arc =
+ ('A' %mode_abs| 'a' %mode_rel) wsp*
+ elliptical_arc_argument_sequence;
+
+ smooth_quadratic_bezier_curveto_argument =
+ coordinate_pair %smooth_quadratic_bezier_curveto;
+ smooth_quadratic_bezier_curveto_argument_sequence =
+ smooth_quadratic_bezier_curveto_argument $1 %0
+ (comma_wsp?
+ smooth_quadratic_bezier_curveto_argument $1 %0)*;
+ smooth_quadratic_bezier_curveto =
+ ('T' %mode_abs| 't' %mode_rel) wsp*
+ smooth_quadratic_bezier_curveto_argument_sequence;
+
+ quadratic_bezier_curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp? coordinate_pair)
+ %quadratic_bezier_curveto;
+ quadratic_bezier_curveto_argument_sequence =
+ quadratic_bezier_curveto_argument $1 %0
+ (comma_wsp? quadratic_bezier_curveto_argument $1 %0)*;
+ quadratic_bezier_curveto =
+ ('Q' %mode_abs| 'q' %mode_rel) wsp*
+ quadratic_bezier_curveto_argument_sequence;
+
+ smooth_curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp? coordinate_pair)
+ %smooth_curveto;
+ smooth_curveto_argument_sequence =
+ smooth_curveto_argument $1 %0
+ (comma_wsp? smooth_curveto_argument $1 %0)*;
+ smooth_curveto =
+ ('S' %mode_abs| 's' %mode_rel)
+ wsp* smooth_curveto_argument_sequence;
+
+ curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp?
+ coordinate_pair $1 %0 comma_wsp?
+ coordinate_pair)
+ %curveto;
+ curveto_argument_sequence =
+ curveto_argument $1 %0
+ (comma_wsp? curveto_argument $1 %0)*;
+ curveto =
+ ('C' %mode_abs| 'c' %mode_rel)
+ wsp* curveto_argument_sequence;
+
+ vertical_lineto_argument = coordinate %vertical_lineto;
+ vertical_lineto_argument_sequence =
+ vertical_lineto_argument $(vertical_lineto_argument_a,1) %(vertical_lineto_argument_a,0)
+ (comma_wsp? vertical_lineto_argument $(vertical_lineto_argument_b,1) %(vertical_lineto_argument_b,0))*;
+ vertical_lineto =
+ ('V' %mode_abs| 'v' %mode_rel)
+ wsp* vertical_lineto_argument_sequence;
+
+ horizontal_lineto_argument = coordinate %horizontal_lineto;
+ horizontal_lineto_argument_sequence =
+ horizontal_lineto_argument $(horizontal_lineto_argument_a,1) %(horizontal_lineto_argument_a,0)
+ (comma_wsp? horizontal_lineto_argument $(horizontal_lineto_argument_b,1) %(horizontal_lineto_argument_b,0))*;
+ horizontal_lineto =
+ ('H' %mode_abs| 'h' %mode_rel)
+ wsp* horizontal_lineto_argument_sequence;
+
+ lineto_argument = coordinate_pair %lineto;
+ lineto_argument_sequence =
+ lineto_argument $1 %0
+ (comma_wsp? lineto_argument $1 %0)*;
+ lineto =
+ ('L' %mode_abs| 'l' %mode_rel) wsp*
+ lineto_argument_sequence;
+
+ closepath = ('Z' | 'z') %closepath;
+
+ moveto_argument = coordinate_pair %moveto;
+ moveto_argument_sequence =
+ moveto_argument $1 %0
+ (comma_wsp? lineto_argument $1 %0)*;
+ moveto =
+ ('M' %mode_abs | 'm' %mode_rel)
+ wsp* moveto_argument_sequence;
+
+ drawto_command =
+ closepath | lineto |
+ horizontal_lineto | vertical_lineto |
+ curveto | smooth_curveto |
+ quadratic_bezier_curveto |
+ smooth_quadratic_bezier_curveto |
+ elliptical_arc;
+
+ drawto_commands = drawto_command (wsp* drawto_command)*;
+ moveto_drawto_command_group = moveto wsp* drawto_commands?;
+ moveto_drawto_command_groups =
+ moveto_drawto_command_group
+ (wsp* moveto_drawto_command_group)*;
+
+ svg_path = wsp* moveto_drawto_command_groups? wsp*;
+
+
+ main := svg_path;
+
+ write exec;
+ }%%
+
+ if (finish) {
+ if (cs < svg_path_first_final) {
+ throw SVGPathParseError();
+ }
+ } else if (start != NULL) {
+ _number_part = std::string(start, pe);
+ }
+
+ if (finish) {
+ _pushCurve(NULL);
+ _sink.flush();
+ reset();
+ }
+}
+
+void parse_svg_path(char const *str, PathSink &sink)
+{
+ SVGPathParser parser(sink);
+ parser.parse(str);
+}
+
+void parse_svg_path_file(FILE *fi, PathSink &sink)
+{
+ static const size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t bytes_read;
+ SVGPathParser parser(sink);
+
+ while (true) {
+ bytes_read = fread(buffer, 1, BUFFER_SIZE, fi);
+ if (bytes_read < BUFFER_SIZE) {
+ parser.parse(buffer, bytes_read);
+ break;
+ } else {
+ parser.feed(buffer, bytes_read);
+ }
+ }
+}
+
+} // 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=ragel:cindent:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path-writer.cpp b/src/2geom/svg-path-writer.cpp
new file mode 100644
index 0000000..1b8cabe
--- /dev/null
+++ b/src/2geom/svg-path-writer.cpp
@@ -0,0 +1,296 @@
+/** @file
+ * @brief Path sink which writes an SVG-compatible command string
+ *//*
+ * 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 <cmath>
+#include <iomanip>
+#include <2geom/coord.h>
+#include <2geom/svg-path-writer.h>
+#include <glib.h>
+
+namespace Geom {
+
+static inline bool is_digit(char c) {
+ return c >= '0' && c <= '9';
+}
+
+SVGPathWriter::SVGPathWriter()
+ : _epsilon(0)
+ , _precision(-1)
+ , _optimize(false)
+ , _use_shorthands(true)
+ , _command(0)
+{
+ // always use C locale for number formatting
+ _ns.imbue(std::locale::classic());
+ _ns.unsetf(std::ios::floatfield);
+}
+
+void SVGPathWriter::moveTo(Point const &p)
+{
+ _setCommand('M');
+ _current_pars.push_back(p[X]);
+ _current_pars.push_back(p[Y]);
+
+ _current = _subpath_start = _quad_tangent = _cubic_tangent = p;
+ if (!_optimize) {
+ flush();
+ }
+}
+
+void SVGPathWriter::lineTo(Point const &p)
+{
+ // The weird setting of _current is to avoid drift with many almost-aligned segments
+ // The additional conditions ensure that the smaller dimension is rounded to zero
+ bool written = false;
+ if (_use_shorthands) {
+ Point r = _current - p;
+ if (are_near(p[X], _current[X], _epsilon) && std::abs(r[X]) < std::abs(r[Y])) {
+ // emit vlineto
+ _setCommand('V');
+ _current_pars.push_back(p[Y]);
+ _current[Y] = p[Y];
+ written = true;
+ } else if (are_near(p[Y], _current[Y], _epsilon) && std::abs(r[Y]) < std::abs(r[X])) {
+ // emit hlineto
+ _setCommand('H');
+ _current_pars.push_back(p[X]);
+ _current[X] = p[X];
+ written = true;
+ }
+ }
+
+ if (!written) {
+ // emit normal lineto
+ if (_command != 'M' && _command != 'L') {
+ _setCommand('L');
+ }
+ _current_pars.push_back(p[X]);
+ _current_pars.push_back(p[Y]);
+ _current = p;
+ }
+
+ _cubic_tangent = _quad_tangent = _current;
+ if (!_optimize) {
+ flush();
+ }
+}
+
+void SVGPathWriter::quadTo(Point const &c, Point const &p)
+{
+ bool shorthand = _use_shorthands && are_near(c, _quad_tangent, _epsilon);
+
+ _setCommand(shorthand ? 'T' : 'Q');
+ if (!shorthand) {
+ _current_pars.push_back(c[X]);
+ _current_pars.push_back(c[Y]);
+ }
+ _current_pars.push_back(p[X]);
+ _current_pars.push_back(p[Y]);
+
+ _current = _cubic_tangent = p;
+ _quad_tangent = p + (p - c);
+ if (!_optimize) {
+ flush();
+ }
+}
+
+void SVGPathWriter::curveTo(Point const &p1, Point const &p2, Point const &p3)
+{
+ bool shorthand = _use_shorthands && are_near(p1, _cubic_tangent, _epsilon);
+
+ _setCommand(shorthand ? 'S' : 'C');
+ if (!shorthand) {
+ _current_pars.push_back(p1[X]);
+ _current_pars.push_back(p1[Y]);
+ }
+ _current_pars.push_back(p2[X]);
+ _current_pars.push_back(p2[Y]);
+ _current_pars.push_back(p3[X]);
+ _current_pars.push_back(p3[Y]);
+
+ _current = _quad_tangent = p3;
+ _cubic_tangent = p3 + (p3 - p2);
+ if (!_optimize) {
+ flush();
+ }
+}
+
+void SVGPathWriter::arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point const &p)
+{
+ _setCommand('A');
+ _current_pars.push_back(rx);
+ _current_pars.push_back(ry);
+ _current_pars.push_back(deg_from_rad(angle));
+ _current_pars.push_back(large_arc ? 1. : 0.);
+ _current_pars.push_back(sweep ? 1. : 0.);
+ _current_pars.push_back(p[X]);
+ _current_pars.push_back(p[Y]);
+
+ _current = _quad_tangent = _cubic_tangent = p;
+ if (!_optimize) {
+ flush();
+ }
+}
+
+void SVGPathWriter::closePath()
+{
+ flush();
+ if (_optimize) {
+ _s << "z";
+ } else {
+ _s << " z";
+ }
+ _current = _quad_tangent = _cubic_tangent = _subpath_start;
+}
+
+void SVGPathWriter::flush()
+{
+ if (_command == 0 || _current_pars.empty()) return;
+
+ if (_optimize) {
+ _s << _command;
+ } else {
+ if (_s.tellp() != 0) {
+ _s << ' ';
+ }
+ _s << _command;
+ }
+
+ char lastchar = _command;
+ bool contained_dot = false;
+
+ for (double _current_par : _current_pars) {
+ // TODO: optimize the use of absolute / relative coords
+ std::string cs = _formatCoord(_current_par);
+
+ // Separator handling logic.
+ // Floating point values can end with a digit or dot
+ // and start with a digit, a plus or minus sign, or a dot.
+ // The following cases require a separator:
+ // * digit-digit
+ // * digit-dot (only if the previous number didn't contain a dot)
+ // * dot-digit
+ if (_optimize) {
+ // C++11: change to front()
+ char firstchar = cs[0];
+ if (is_digit(lastchar)) {
+ if (is_digit(firstchar)) {
+ _s << " ";
+ } else if (firstchar == '.' && !contained_dot) {
+ _s << " ";
+ }
+ } else if (lastchar == '.' && is_digit(firstchar)) {
+ _s << " ";
+ }
+ _s << cs;
+
+ // C++11: change to back()
+ lastchar = cs[cs.length()-1];
+ contained_dot = cs.find('.') != std::string::npos;
+ } else {
+ _s << " " << cs;
+ }
+ }
+ _current_pars.clear();
+ _command = 0;
+}
+
+void SVGPathWriter::clear()
+{
+ _s.clear();
+ _s.str("");
+ _ns.clear();
+ _ns.str("");
+ _command = 0;
+ _current_pars.clear();
+ _current = Point(0,0);
+ _subpath_start = Point(0,0);
+}
+
+void SVGPathWriter::setPrecision(int prec)
+{
+ _precision = prec;
+ if (prec < 0) {
+ _epsilon = 0;
+ } else {
+ _epsilon = std::pow(10., -prec);
+ _ns << std::setprecision(_precision);
+ }
+}
+
+void SVGPathWriter::_setCommand(char cmd)
+{
+ if (_command != 0 && _command != cmd) {
+ flush();
+ }
+ _command = cmd;
+}
+
+std::string SVGPathWriter::_formatCoord(Coord par)
+{
+ std::string ret;
+ if (_precision < 0) {
+ ret = format_coord_shortest(par);
+ } else {
+ _ns << par;
+ ret = _ns.str();
+ _ns.clear();
+ _ns.str("");
+ }
+ return ret;
+}
+
+
+std::string write_svg_path(PathVector const &pv, int prec, bool optimize, bool shorthands)
+{
+ SVGPathWriter writer;
+ writer.setPrecision(prec);
+ writer.setOptimize(optimize);
+ writer.setUseShorthands(shorthands);
+
+ writer.feed(pv);
+ return writer.str();
+}
+
+} // 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/src/2geom/sweep-bounds.cpp b/src/2geom/sweep-bounds.cpp
new file mode 100644
index 0000000..2f31b67
--- /dev/null
+++ b/src/2geom/sweep-bounds.cpp
@@ -0,0 +1,154 @@
+#include <2geom/sweep-bounds.h>
+
+#include <algorithm>
+
+namespace Geom {
+
+struct Event {
+ double x;
+ unsigned ix;
+ bool closing;
+ Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {}
+// Lexicographic ordering by x then closing
+ bool operator<(Event const &other) const {
+ if(x < other.x) return true;
+ if(x > other.x) return false;
+ return closing < other.closing;
+ }
+ bool operator==(Event const &other) const {
+ return other.x == x && other.ix == ix && other.closing == closing;
+ }
+};
+
+std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b);
+
+/**
+ * \brief Make a list of pairs of self intersections in a list of Rects.
+ *
+ * \param rs: vector of Rect.
+ * \param d: dimension to sweep along
+ *
+ * [(A = rs[i], B = rs[j]) for i,J in enumerate(pairs) for j in J]
+ * then A.left <= B.left
+ */
+
+std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> rs, Dim2 d) {
+ std::vector<Event> events; events.reserve(rs.size()*2);
+ std::vector<std::vector<unsigned> > pairs(rs.size());
+
+ for(unsigned i = 0; i < rs.size(); i++) {
+ events.emplace_back(rs[i][d].min(), i, false);
+ events.emplace_back(rs[i][d].max(), i, true);
+ }
+ std::sort(events.begin(), events.end());
+
+ std::vector<unsigned> open;
+ for(auto & event : events) {
+ unsigned ix = event.ix;
+ if(event.closing) {
+ std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix);
+ //if(iter != open.end())
+ open.erase(iter);
+ } else {
+ for(unsigned int jx : open) {
+ if(rs[jx][1-d].intersects(rs[ix][1-d])) {
+ pairs[jx].push_back(ix);
+ }
+ }
+ open.push_back(ix);
+ }
+ }
+ return pairs;
+}
+
+/**
+ * \brief Make a list of pairs of red-blue intersections between two lists of Rects.
+ *
+ * \param a: vector of Rect.
+ * \param b: vector of Rect.
+ * \param d: dimension to scan along
+ *
+ * [(A = rs[i], B = rs[j]) for i,J in enumerate(pairs) for j in J]
+ * then A.left <= B.left, A in a, B in b
+ */
+std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> a, std::vector<Rect> b, Dim2 d) {
+ std::vector<std::vector<unsigned> > pairs(a.size());
+ if(a.empty() || b.empty()) return pairs;
+ std::vector<Event> events[2];
+ events[0].reserve(a.size()*2);
+ events[1].reserve(b.size()*2);
+
+ for(unsigned n = 0; n < 2; n++) {
+ unsigned sz = n ? b.size() : a.size();
+ events[n].reserve(sz*2);
+ for(unsigned i = 0; i < sz; i++) {
+ Rect r = n ? b[i] : a[i];
+ events[n].emplace_back(r[d].min(), i, false);
+ events[n].emplace_back(r[d].max(), i, true);
+ }
+ std::sort(events[n].begin(), events[n].end());
+ }
+
+ std::vector<unsigned> open[2];
+ bool n = events[1].front() < events[0].front();
+ {// As elegant as putting the initialiser in the for was, it upsets some legacy compilers (MS VS C++)
+ unsigned i[] = {0,0};
+ for(; i[n] < events[n].size();) {
+ unsigned ix = events[n][i[n]].ix;
+ bool closing = events[n][i[n]].closing;
+ //std::cout << n << "[" << ix << "] - " << (closing ? "closer" : "opener") << "\n";
+ if(closing) {
+ open[n].erase(std::find(open[n].begin(), open[n].end(), ix));
+ } else {
+ if(n) {
+ //n = 1
+ //opening a B, add to all open a
+ for(unsigned int jx : open[0]) {
+ if(a[jx][1-d].intersects(b[ix][1-d])) {
+ pairs[jx].push_back(ix);
+ }
+ }
+ } else {
+ //n = 0
+ //opening an A, add all open b
+ for(unsigned int jx : open[1]) {
+ if(b[jx][1-d].intersects(a[ix][1-d])) {
+ pairs[ix].push_back(jx);
+ }
+ }
+ }
+ open[n].push_back(ix);
+ }
+ i[n]++;
+ if(i[n]>=events[n].size()) {break;}
+ n = (events[!n][i[!n]] < events[n][i[n]]) ? !n : n;
+ }}
+ return pairs;
+}
+
+//Fake cull, until the switch to the real sweep is made.
+std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b) {
+ std::vector<std::vector<unsigned> > ret;
+
+ std::vector<unsigned> all;
+ for(unsigned j = 0; j < b; j++)
+ all.push_back(j);
+
+ for(unsigned i = 0; i < a; i++)
+ ret.push_back(all);
+
+ return ret;
+}
+
+}
+
+/*
+ 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/src/2geom/transforms.cpp b/src/2geom/transforms.cpp
new file mode 100644
index 0000000..41d3952
--- /dev/null
+++ b/src/2geom/transforms.cpp
@@ -0,0 +1,205 @@
+/**
+ * @file
+ * @brief Affine transformation classes
+ *//*
+ * Authors:
+ * ? <?@?.?>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Johan Engelen
+ *
+ * Copyright ?-2012 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 <boost/concept_check.hpp>
+#include <2geom/point.h>
+#include <2geom/transforms.h>
+#include <2geom/rect.h>
+
+namespace Geom {
+
+/** @brief Zoom between rectangles.
+ * Given two rectangles, compute a zoom that maps one to the other.
+ * Rectangles are assumed to have the same aspect ratio. */
+Zoom Zoom::map_rect(Rect const &old_r, Rect const &new_r)
+{
+ Zoom ret;
+ ret._scale = new_r.width() / old_r.width();
+ ret._trans = new_r.min() - old_r.min();
+ return ret;
+}
+
+// Point transformation methods.
+Point &Point::operator*=(Translate const &t)
+{
+ _pt[X] += t.vec[X];
+ _pt[Y] += t.vec[Y];
+ return *this;
+}
+Point &Point::operator*=(Scale const &s)
+{
+ _pt[X] *= s.vec[X];
+ _pt[Y] *= s.vec[Y];
+ return *this;
+}
+Point &Point::operator*=(Rotate const &r)
+{
+ double x = _pt[X], y = _pt[Y];
+ _pt[X] = x * r.vec[X] - y * r.vec[Y];
+ _pt[Y] = y * r.vec[X] + x * r.vec[Y];
+ return *this;
+}
+Point &Point::operator*=(HShear const &h)
+{
+ _pt[X] += h.f * _pt[X];
+ return *this;
+}
+Point &Point::operator*=(VShear const &v)
+{
+ _pt[Y] += v.f * _pt[Y];
+ return *this;
+}
+Point &Point::operator*=(Zoom const &z)
+{
+ _pt[X] += z._trans[X];
+ _pt[Y] += z._trans[Y];
+ _pt[X] *= z._scale;
+ _pt[Y] *= z._scale;
+ return *this;
+}
+
+// Affine multiplication methods.
+
+/** @brief Combine this transformation with a translation. */
+Affine &Affine::operator*=(Translate const &t) {
+ _c[4] += t[X];
+ _c[5] += t[Y];
+ return *this;
+}
+
+/** @brief Combine this transformation with scaling. */
+Affine &Affine::operator*=(Scale const &s) {
+ _c[0] *= s[X]; _c[1] *= s[Y];
+ _c[2] *= s[X]; _c[3] *= s[Y];
+ _c[4] *= s[X]; _c[5] *= s[Y];
+ return *this;
+}
+
+/** @brief Combine this transformation a rotation. */
+Affine &Affine::operator*=(Rotate const &r) {
+ // TODO: we just convert the Rotate to an Affine and use the existing operator*=()
+ // is there a better way?
+ *this *= (Affine) r;
+ return *this;
+}
+
+/** @brief Combine this transformation with horizontal shearing (skew). */
+Affine &Affine::operator*=(HShear const &h) {
+ _c[0] += h.f * _c[1];
+ _c[2] += h.f * _c[3];
+ _c[4] += h.f * _c[5];
+ return *this;
+}
+
+/** @brief Combine this transformation with vertical shearing (skew). */
+Affine &Affine::operator*=(VShear const &v) {
+ _c[1] += v.f * _c[0];
+ _c[3] += v.f * _c[2];
+ _c[5] += v.f * _c[4];
+ return *this;
+}
+
+Affine &Affine::operator*=(Zoom const &z) {
+ _c[0] *= z._scale; _c[1] *= z._scale;
+ _c[2] *= z._scale; _c[3] *= z._scale;
+ _c[4] += z._trans[X]; _c[5] += z._trans[Y];
+ _c[4] *= z._scale; _c[5] *= z._scale;
+ return *this;
+}
+
+Affine Rotate::around(Point const &p, Coord angle)
+{
+ Affine result = Translate(-p) * Rotate(angle) * Translate(p);
+ return result;
+}
+
+Affine reflection(Point const & vector, Point const & origin)
+{
+ Geom::Point vn = unit_vector(vector);
+ Coord cx2 = vn[X] * vn[X];
+ Coord cy2 = vn[Y] * vn[Y];
+ Coord c2xy = 2 * vn[X] * vn[Y];
+ Affine mirror ( cx2 - cy2, c2xy,
+ c2xy, cy2 - cx2,
+ 0, 0 );
+ return Translate(-origin) * mirror * Translate(origin);
+}
+
+// this checks whether the requirements of TransformConcept are satisfied for all transforms.
+// if you add a new transform type, include it here!
+void check_transforms()
+{
+#ifdef BOOST_CONCEPT_ASSERT
+ BOOST_CONCEPT_ASSERT((TransformConcept<Translate>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<Scale>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<Rotate>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<HShear>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<VShear>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<Zoom>));
+ BOOST_CONCEPT_ASSERT((TransformConcept<Affine>)); // Affine is also a transform
+#endif
+
+ // check inter-transform multiplication
+ Affine m;
+ Translate t(Translate::identity());
+ Scale s(Scale::identity());
+ Rotate r(Rotate::identity());
+ HShear h(HShear::identity());
+ VShear v(VShear::identity());
+ Zoom z(Zoom::identity());
+
+ // notice that the first column is always the same and enumerates all transform types,
+ // while the second one changes to each transform type in turn.
+ // cppcheck-suppress redundantAssignment
+ m = t * t; m = t * s; m = t * r; m = t * h; m = t * v; m = t * z;
+ m = s * t; m = s * s; m = s * r; m = s * h; m = s * v; m = s * z;
+ m = r * t; m = r * s; m = r * r; m = r * h; m = r * v; m = r * z;
+ m = h * t; m = h * s; m = h * r; m = h * h; m = h * v; m = h * z;
+ m = v * t; m = v * s; m = v * r; m = v * h; m = v * v; m = v * z;
+ m = z * t; m = z * s; m = z * r; m = z * h; m = z * v; m = z * z;
+}
+
+} // 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/src/2geom/utils.cpp b/src/2geom/utils.cpp
new file mode 100644
index 0000000..83d93cc
--- /dev/null
+++ b/src/2geom/utils.cpp
@@ -0,0 +1,86 @@
+/** Various utility functions.
+ *
+ * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>
+ * Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * 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/utils.h>
+
+
+namespace Geom
+{
+
+// return a vector that contains all the binomial coefficients of degree n
+void binomial_coefficients(std::vector<size_t>& bc, std::size_t n)
+{
+ size_t s = n+1;
+ bc.clear();
+ bc.resize(s);
+ bc[0] = 1;
+ for (size_t i = 1; i < n; ++i)
+ {
+ size_t k = i >> 1;
+ if (i & 1u)
+ {
+ bc[k+1] = bc[k] << 1;
+ }
+ for (size_t j = k; j > 0; --j)
+ {
+ bc[j] += bc[j-1];
+ }
+ }
+ s >>= 1;
+ for (size_t i = 0; i < s; ++i)
+ {
+ bc[n-i] = bc[i];
+ }
+}
+
+} // 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/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..268d4df
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_subdirectory(2geom)
+add_subdirectory(performance-tests EXCLUDE_FROM_ALL)
+if(GTK3_FOUND)
+ add_subdirectory(toys EXCLUDE_FROM_ALL)
+else()
+ message("Not building toys as they require GTK3.")
+endif()
+
+add_subdirectory(cython EXCLUDE_FROM_ALL)
+add_subdirectory(py2geom EXCLUDE_FROM_ALL)
diff --git a/src/cython/CMakeLists.txt b/src/cython/CMakeLists.txt
new file mode 100644
index 0000000..9187d35
--- /dev/null
+++ b/src/cython/CMakeLists.txt
@@ -0,0 +1,131 @@
+#TODO - rewrite to use ALLCAPS?
+
+OPTION(2GEOM_CYTHON_BINDINGS
+ "Build a python binding with Cython."
+ OFF)
+OPTION(2GEOM_CYTHON_BUILD_SHARED
+ "Build cython shared libraries."
+ ON)
+IF(2GEOM_CYTHON_BUILD_SHARED)
+ SET(LIB_TYPE SHARED)
+ SET (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -shared")
+ELSE(2GEOM_CYTHON_BUILD_SHARED)
+ SET(LIB_TYPE STATIC)
+ENDIF(2GEOM_CYTHON_BUILD_SHARED)
+
+
+IF(2GEOM_CYTHON_BINDINGS)
+
+ include( UseCython )
+
+ # With CMake, a clean separation can be made between the source tree and the
+ # build tree. When all source is compiled, as with pure C/C++, the source is
+ # no-longer needed in the build tree. However, with pure *.py source, the
+ # source is processed directly. To handle this, we reproduce the availability
+ # of the source files in the build tree.
+ #add_custom_target( ReplicatePythonSourceTree ALL ${CMAKE_COMMAND} -P
+ # ${CMAKE_CURRENT_SOURCE_DIR}/CMakeScripts/ReplicatePythonSourceTree.cmake
+ # ${CMAKE_CURRENT_BINARY_DIR}
+ # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} )
+
+ #include_directories( ${CYTHON_CMAKE_EXAMPLE_SOURCE_DIR}/include )
+
+ # Process the CMakeLists.txt in the 'src' and 'bin' directory.
+
+ set_source_files_properties(
+ _common_decl.pxd
+ _common_decl.pyx
+
+ _cy_primitives.pxd
+ _cy_primitives.pyx
+
+ _cy_rectangle.pxd
+ _cy_rectangle.pyx
+
+ _cy_affine.pxd
+ _cy_affine.pyx
+
+ _cy_curves.pxd
+ _cy_curves.pyx
+
+ _cy_path.pxd
+ _cy_path.pyx
+
+ _cy_conicsection.pxd
+ _cy_conicsection.pyx
+
+ cy2geom.pyx
+
+ PROPERTIES CYTHON_IS_CXX 1)
+
+ # Multi-file cython modules do not appear to be working at the moment.
+ cython_add_module( _common_decl _common_decl.pyx)
+
+
+ cython_add_module( _cy_primitives _cy_primitives.pyx)
+
+ cython_add_module( _cy_rectangle _cy_rectangle.pyx)
+
+ cython_add_module( _cy_affine _cy_affine.pyx)
+
+ cython_add_module( _cy_curves _cy_curves.pyx)
+
+ cython_add_module( _cy_path _cy_path.pyx)
+
+ #not finished for now
+ #~ cython_add_module( _cy_shape _cy_shape.pyx)
+
+ cython_add_module( _cy_conicsection _cy_conicsection.pyx)
+
+ target_link_libraries(_cy_primitives
+ #TODO! linking to static lib2geom.a gives -fPIC error, to compile
+ #you have to enable building dynamic library in cmake . -i
+ gsl gslcblas 2geom
+ )
+ target_link_libraries(_cy_rectangle
+ gsl gslcblas 2geom
+ )
+
+ target_link_libraries(_cy_affine
+ gsl gslcblas 2geom
+ )
+
+ target_link_libraries(_cy_curves
+ gsl gslcblas 2geom
+ )
+
+ target_link_libraries(_cy_path
+ gsl gslcblas 2geom
+ )
+
+ #~ target_link_libraries(_cy_shape
+ #~ gsl gslcblas 2geom
+ #~ )
+
+ target_link_libraries(_cy_conicsection
+ gsl gslcblas 2geom
+ )
+
+ cython_add_module( cy2geom cy2geom.pyx)
+
+ add_test(cython-primitives python2 test-primitives.py)
+ add_test(cython-rectangle python2 test-rectangle.py)
+ add_test(cython-affine python2 test-affine.py)
+ add_test(cython-curves python2 test-curves.py)
+ add_test(cython-path python2 test-path.py)
+ add_test(cython-conicsection python2 test-conicsection.py)
+
+ # stuff to install the cy2geom package in the Python site-packages directory
+ FIND_PACKAGE(PythonLibs)
+ IF (WIN32)
+ GET_FILENAME_COMPONENT(PYTHON_LIB_INSTALL "${PYTHON_LIBRARY}" PATH)
+ GET_FILENAME_COMPONENT(SITEPACKAGE "${PYTHON_LIB_INSTALL}/../Lib/site-packages" ABSOLUTE)
+ ELSE (WIN32)
+ SET(PYTHON_LIB_INSTALL "/usr/local/lib/python2.7/dist-packages" CACHE STRING "Where to install the cy2geom module?")
+ SET(SITEPACKAGE ${PYTHON_LIB_INSTALL})
+ ENDIF(WIN32)
+
+ INSTALL(TARGETS _common_decl _cy_primitives _cy_rectangle _cy_affine _cy_curves _cy_path _cy_conicsection cy2geom
+ DESTINATION "${SITEPACKAGE}/cy2geom")
+
+ENDIF(2GEOM_CYTHON_BINDINGS)
diff --git a/src/cython/README.md b/src/cython/README.md
new file mode 100644
index 0000000..a4a0847
--- /dev/null
+++ b/src/cython/README.md
@@ -0,0 +1,29 @@
+# Installing:
+
+In addition to 2geom dependencies, cython bindings need `cython >= 0.16`.
+
+You can turn them on using cmake. Please note that you need to enable
+shared library option as well.
+
+Building on Windows is not tested yet, should be done shortly. Extrapolating
+from other projects using cython, this should not be major problem.
+
+# Usage:
+
+Bindings are almost 1-1 to 2geom, so using them is pretty straightforward.
+Classes and methods are documented shortly. It's generally good idea to
+look at 2geom docs and, if problems persist, at their source.
+
+To look at simple use cases, I suggest looking at tests and utils.py, located
+in cython-bindings directory.
+
+# Hacking:
+
+cython is pretty straightforward to pick up, but its docs usually cover only
+the simplest example. Looking at source of other project can be helpful
+(cython bindings for SFML 2 are good example).
+
+Don't hesitate to contact me or 2geom mailinglist with any requests concerning
+design of bindings and bug reports.
+
+Jan Pulmann - jan.pulmann@gmail.com
diff --git a/src/cython/_common_decl.pxd b/src/cython/_common_decl.pxd
new file mode 100644
index 0000000..8877048
--- /dev/null
+++ b/src/cython/_common_decl.pxd
@@ -0,0 +1,14 @@
+from libcpp.vector cimport vector
+from libcpp.pair cimport pair
+
+ctypedef double Coord
+ctypedef int IntCoord
+
+cdef extern from "2geom/coord.h" namespace "Geom":
+ cdef Coord EPSILON
+ cdef enum Dim2:
+ X = 0
+ Y = 1
+
+cdef object wrap_vector_double(vector[double] v)
+cdef vector[double] make_vector_double(object l)
diff --git a/src/cython/_common_decl.pyx b/src/cython/_common_decl.pyx
new file mode 100644
index 0000000..1f1544e
--- /dev/null
+++ b/src/cython/_common_decl.pyx
@@ -0,0 +1,12 @@
+cdef object wrap_vector_double(vector[double] v):
+ r = []
+ cdef unsigned int i
+ for i in range(v.size()):
+ r.append(v[i])
+ return r
+
+cdef vector[double] make_vector_double(object l):
+ cdef vector[double] ret
+ for i in l:
+ ret.push_back( float(i) )
+ return ret
diff --git a/src/cython/_cy_affine.pxd b/src/cython/_cy_affine.pxd
new file mode 100644
index 0000000..91eb662
--- /dev/null
+++ b/src/cython/_cy_affine.pxd
@@ -0,0 +1,247 @@
+from _common_decl cimport *
+
+from _cy_rectangle cimport Rect, cy_Rect, wrap_Rect
+from _cy_primitives cimport Point, cy_Point, wrap_Point
+
+
+cdef extern from "2geom/affine.h" namespace "Geom":
+ cdef cppclass Affine:
+
+ Affine(Affine &)
+ Affine()
+ Affine(Coord, Coord, Coord, Coord, Coord, Coord)
+
+ Coord operator[](unsigned int)
+
+ Affine & operator*(Affine &)
+ Affine & operator*(Translate &)
+ Affine & operator*(Scale &)
+ Affine & operator*(Rotate &)
+ Affine & operator*(HShear &)
+ Affine & operator*(VShear &)
+ Affine & operator*(Zoom &)
+
+ bint operator==(Affine &)
+ bint operator!=(Affine &)
+
+ Point xAxis()
+ Point yAxis()
+ Point translation()
+ Coord expansionX()
+ Coord expansionY()
+ Point expansion()
+
+ void setXAxis(Point &)
+ void setYAxis(Point &)
+ void setTranslation(Point &)
+ void setExpansionX(Coord)
+ void setExpansionY(Coord)
+ void setIdentity()
+
+ bint isIdentity(Coord)
+ bint isTranslation(Coord)
+ bint isScale(Coord)
+ bint isUniformScale(Coord)
+ bint isRotation(Coord)
+ bint isHShear(Coord)
+ bint isVShear(Coord)
+ bint isNonzeroTranslation(Coord)
+ bint isNonzeroScale(Coord)
+ bint isNonzeroUniformScale(Coord)
+ bint isNonzeroRotation(Coord)
+ bint isNonzeroHShear(Coord)
+ bint isNonzeroVShear(Coord)
+ bint isZoom(Coord)
+ bint preservesArea(Coord)
+ bint preservesAngles(Coord)
+ bint preservesDistances(Coord)
+ bint flips()
+ bint isSingular(Coord)
+
+ Affine withoutTranslation()
+ Affine inverse()
+
+ Coord det()
+ Coord descrim2()
+ Coord descrim()
+
+ bint are_near(Affine &, Affine &, Coord)
+ Affine a_identity "Geom::Affine::identity" ()
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ Affine reflection(Point &, Point &)
+ #TODO find out how cython __pow__ works
+ Affine pow(Affine &, int)
+ Translate pow(Translate &, int)
+ Scale pow(Scale &, int)
+ Rotate pow(Rotate &, int)
+ HShear pow(HShear &, int)
+ VShear pow(VShear &, int)
+ Zoom pow(Zoom &, int)
+
+cdef class cy_Affine:
+ cdef Affine* thisptr
+
+cdef cy_Affine wrap_Affine(Affine)
+
+#helper functions
+cdef Affine get_Affine(t)
+cdef bint is_transform(t)
+
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass Translate:
+ Translate(Translate &)
+ Translate()
+ Translate(Point &)
+ Translate(Coord, Coord)
+ Coord operator[](Dim2)
+ Coord operator[](unsigned int)
+ Translate & operator*(Translate &)
+ Affine & operator*(Affine &)
+ bint operator==(Translate &)
+ bint operator!=(Translate &)
+
+ Affine operator()
+
+ Point vector()
+ Translate inverse()
+
+ Translate t_identity "Geom::Translate::identity" ()
+
+cdef class cy_Translate:
+ cdef Translate* thisptr
+
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass Scale:
+ Scale(Scale &)
+ Scale()
+ Scale(Point &)
+ Scale(Coord, Coord)
+ Scale(Coord)
+ Coord operator[](Dim2)
+ Scale & operator*(Scale &)
+ Affine & operator*(Affine &)
+ bint operator==(Scale &)
+ bint operator!=(Scale &)
+
+ Affine operator()
+
+ Point vector()
+ Scale inverse()
+ Scale identity()
+
+ Scale s_identity "Geom::Scale::identity" ()
+
+cdef class cy_Scale:
+ cdef Scale* thisptr
+
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass Rotate:
+ Rotate(Rotate &)
+ Rotate()
+ Rotate(Coord)
+ Rotate(Point &)
+ Rotate(Coord, Coord)
+ Point vector()
+
+ Coord operator[](Dim2)
+ Coord operator[](unsigned int)
+ Rotate & operator*(Rotate &)
+ Affine & operator*(Affine &)
+ bint operator==(Rotate &)
+ bint operator!=(Rotate &)
+
+ Affine operator()
+ Rotate inverse()
+
+ Rotate r_identity "Geom::Rotate::identity" ()
+
+cdef extern from "2geom/transforms.h" namespace "Geom::Rotate":
+ Rotate from_degrees(Coord)
+
+
+cdef class cy_Rotate:
+ cdef Rotate* thisptr
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass VShear:
+ VShear(VShear &)
+ VShear(Coord)
+ Coord factor()
+ void setFactor(Coord)
+
+ VShear &operator*(VShear)
+ Affine & operator*(Affine &)
+ bint operator==(VShear &)
+ bint operator!=(VShear &)
+ Affine operator()
+
+ VShear inverse()
+
+ VShear vs_identity "Geom::VShear::identity"()
+
+cdef class cy_VShear:
+ cdef VShear* thisptr
+
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass HShear:
+ HShear(HShear &)
+ HShear(Coord)
+ Coord factor()
+ void setFactor(Coord)
+ HShear &operator*(HShear)
+ Affine & operator*(Affine &)
+ bint operator==(HShear &)
+ bint operator!=(HShear &)
+ Affine operator()
+
+ HShear inverse()
+
+ HShear hs_identity "Geom::HShear::identity"()
+
+cdef class cy_HShear:
+ cdef HShear* thisptr
+
+
+cdef extern from "2geom/transforms.h" namespace "Geom":
+ cdef cppclass Zoom:
+ Zoom(Zoom &)
+ Zoom(Coord)
+ Zoom(Translate &)
+ Zoom(Coord, Translate &)
+
+ Zoom & operator*(Zoom &)
+ Affine & operator*(Affine &)
+ bint operator==(Zoom &)
+ bint operator!=(Zoom &)
+
+ Affine operator()
+
+ Coord scale()
+ void setScale(Coord)
+ Point translation()
+ void setTranslation(Point &)
+
+ Zoom inverse()
+
+ Zoom()
+
+ Zoom z_identity "Geom::Zoom::identity" ()
+
+cdef extern from "2geom/transforms.h" namespace "Geom::Zoom":
+ Zoom map_rect(Rect &, Rect &)
+
+cdef class cy_Zoom:
+ cdef Zoom* thisptr
+
+
+cdef extern from "2geom/affine.h" namespace "Geom":
+ cdef cppclass Eigen:
+ Point *vectors
+ double *values
+ Eigen(Affine &)
+ Eigen(double[2][2])
diff --git a/src/cython/_cy_affine.pyx b/src/cython/_cy_affine.pyx
new file mode 100644
index 0000000..19aa9ef
--- /dev/null
+++ b/src/cython/_cy_affine.pyx
@@ -0,0 +1,736 @@
+from cython.operator cimport dereference as deref
+
+from numbers import Number
+
+
+cdef class cy_Affine:
+
+ """Class representing affine transform in 2D plane.
+
+ Corresponds to Affine class in 2geom.
+ """
+
+ def __cinit__(self, c0=None,
+ Coord c1=0,
+ Coord c2=0,
+ Coord c3=1,
+ Coord c4=0,
+ Coord c5=0):
+ """Create Affine instance from either transform or from coefficients."""
+ if c0 is None:
+ self.thisptr = new Affine()
+ elif is_transform(c0):
+ self.thisptr = new Affine( get_Affine(c0) )
+ else:
+ self.thisptr = new Affine(<Coord> float(c0) ,c1 ,c2 ,c3 ,c4 ,c5)
+
+ def __str__(self):
+ """str(self)"""
+ return "Affine({}, {}, {}, {}, {}, {})".format( self[0],
+ self[1],
+ self[2],
+ self[3],
+ self[4],
+ self[5],
+ )
+ def __repr__(self):
+ """repr(self)"""
+ return str(self)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __getitem__(self, int i):
+ """Get coefficients."""
+ if i >= 6:
+ raise IndexError("Affine has only 6 coefficients.")
+ return deref(self.thisptr) [i]
+
+ def __mul__(cy_Affine self, other):
+ """Compose with another transformation."""
+ if isinstance(other, cy_Affine):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_Affine> other).thisptr ) )
+ elif isinstance(other, cy_Translate):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_Translate> other).thisptr ) )
+ elif isinstance(other, cy_Scale):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_Scale> other).thisptr ) )
+ elif isinstance(other, cy_Rotate):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_Rotate> other).thisptr ) )
+ elif isinstance(other, cy_HShear):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_HShear> other).thisptr ) )
+ elif isinstance(other, cy_VShear):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_VShear> other).thisptr ) )
+ elif isinstance(other, cy_Zoom):
+ return wrap_Affine( deref(self.thisptr) * deref( (<cy_Zoom> other).thisptr ) )
+
+ def __pow__(cy_Affine self, int n, z):
+ """Compose with self n times."""
+ return wrap_Affine(pow( deref(self.thisptr), n ))
+
+ def __richcmp__(cy_Affine self, cy_Affine other, int op):
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+
+ def x_axis(self):
+ """Transformation of unit x vector without translation."""
+ return wrap_Point(self.thisptr.xAxis())
+
+ def y_axis(self):
+ """Transformation of unit y vector without translation."""
+ return wrap_Point(self.thisptr.yAxis())
+
+ def translation(self):
+ """Translation of transformation."""
+ return wrap_Point(self.thisptr.translation())
+
+ def expansion_X(self):
+ """Expansion of unit x vector."""
+ return self.thisptr.expansionX()
+
+ def expansion_Y(self):
+ """Expansion of unit y vector."""
+ return self.thisptr.expansionY()
+
+ def expansion(self):
+ """Point( self.expansion_X(), self.expansion_Y() )"""
+ return wrap_Point(self.thisptr.expansion())
+
+ def set_X_axis(self, cy_Point vec):
+ """Set transformation of x unit vector without translation."""
+ self.thisptr.setXAxis(deref( vec.thisptr ))
+
+ def set_Y_axis(self, cy_Point vec):
+ """Set transformation of y unit vector without translation."""
+ self.thisptr.setYAxis(deref( vec.thisptr ))
+
+ def set_translation(self, cy_Point loc):
+ """Set translation of origin."""
+ self.thisptr.setTranslation(deref( loc.thisptr ))
+
+ def set_expansion_X(self, Coord val):
+ """Set expansion of x unit vector."""
+ self.thisptr.setExpansionX(val)
+
+ def set_expansion_Y(self, Coord val):
+ """Set expansion of y unit vector."""
+ self.thisptr.setExpansionY(val)
+
+ def set_identity(self):
+ """Set self to identity transformation."""
+ self.thisptr.setIdentity()
+
+ def is_identity(self, Coord eps=EPSILON):
+ """Return true if self is close to identity transform.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isIdentity(eps)
+
+ def is_translation(self, Coord eps=EPSILON):
+ """Return true if self is close to transformation.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isTranslation(eps)
+
+ def is_scale(self, Coord eps=EPSILON):
+ """Return true if self is close to scale.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isScale(eps)
+
+ def is_uniform_scale(self, Coord eps=EPSILON):
+ """Return true if self is close to uniform scale.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isUniformScale(eps)
+
+ def is_rotation(self, Coord eps=EPSILON):
+ """Return true if self is close to rotation.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isRotation(eps)
+
+ def is_HShear(self, Coord eps=EPSILON):
+ """Return true if self is close to horizontal shear.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isHShear(eps)
+
+ def is_VShear(self, Coord eps=EPSILON):
+ """Return true if self is close to vertical shear.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isVShear(eps)
+
+ def is_nonzero_translation(self, Coord eps=EPSILON):
+ """Return true if self is close to translation and is identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroTranslation(eps)
+
+ def is_nonzero_scale(self, Coord eps=EPSILON):
+ """Return true if self is close to scale and is identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroScale(eps)
+
+ def is_nonzero_uniform_scale(self, Coord eps=EPSILON):
+ """Return true if self is close to scale and is identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroUniformScale(eps)
+
+ def is_nonzero_rotation(self, Coord eps=EPSILON):
+ """Return true if self is close to rotation and is identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroRotation(eps)
+
+ def is_nonzero_HShear(self, Coord eps=EPSILON):
+ """Return true if self is close to horizontal shear and is not identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroHShear(eps)
+
+ def is_nonzero_VShear(self, Coord eps=EPSILON):
+ """Return true if self is close to vertical shear and is not identity.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isNonzeroVShear(eps)
+
+ def is_zoom(self, Coord eps=EPSILON):
+ """Return true if self is close to zoom.
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.isZoom(eps)
+
+ def preserves_area(self, Coord eps=EPSILON):
+ """Return true if areas are preserved after transformation
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.preservesArea(eps)
+
+ def preserves_angles(self, Coord eps=EPSILON):
+ """Return true if angles are preserved after transformation
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.preservesAngles(eps)
+
+ def preserves_distances(self, Coord eps=EPSILON):
+ """Return true if distances are preserved after transformation
+
+ Use second argument eps to specify tolerance.
+ """
+ return self.thisptr.preservesDistances(eps)
+
+ def flips(self):
+ """Return true if transformation flips - it has negative scaling."""
+ return self.thisptr.flips()
+
+ def is_singular(self, Coord eps=EPSILON):
+ """Check whether transformation matrix is singular."""
+ return self.thisptr.isSingular(eps)
+
+ def without_translation(self):
+ """Return transformation without translation part."""
+ return wrap_Affine(self.thisptr.withoutTranslation())
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_Affine(self.thisptr.inverse())
+
+ def det(self):
+ """Return determinant of transformation matrix."""
+ return self.thisptr.det()
+
+ def descrim2(self):
+ """Return absolute value of self.det()"""
+ return self.thisptr.descrim2()
+
+ def descrim(self):
+ """Return square root of self.descrim2()"""
+ return self.thisptr.descrim()
+
+ @classmethod
+ def identity(self):
+ """Create identity transformation."""
+ return wrap_Affine(a_identity())
+
+ @classmethod
+ def are_near(cls, A, B, Coord eps=EPSILON):
+ """Test if two transforms are near."""
+ if is_transform(A) & is_transform(B):
+ return are_near(get_Affine(A), get_Affine(B), eps)
+
+ @classmethod
+ def reflection(cls, cy_Point vector, cy_Point origin):
+ """Create transformation reflecting along line specified by vector and origin."""
+ return wrap_Affine( reflection( deref(vector.thisptr), deref(origin.thisptr) ) )
+
+cdef cy_Affine wrap_Affine(Affine p):
+ cdef Affine * retp = new Affine()
+ retp[0] = p
+ cdef cy_Affine r = cy_Affine.__new__(cy_Affine)
+ r.thisptr = retp
+ return r
+
+cdef Affine get_Affine(t):
+ if isinstance(t, cy_Affine ):
+ return deref( (<cy_Affine> t).thisptr )
+ elif isinstance(t, cy_Translate):
+ return <Affine> deref( (<cy_Translate> t).thisptr )
+ elif isinstance(t, cy_Scale):
+ return <Affine> deref( (<cy_Scale> t).thisptr )
+ elif isinstance(t, cy_Rotate):
+ return <Affine> deref( (<cy_Rotate> t).thisptr )
+ elif isinstance(t, cy_HShear):
+ return <Affine> deref( (<cy_HShear> t).thisptr )
+ elif isinstance(t, cy_VShear):
+ return <Affine> deref( (<cy_VShear> t).thisptr )
+ elif isinstance(t, cy_Zoom):
+ return <Affine> deref( (<cy_Zoom> t).thisptr )
+
+cdef bint is_transform(t):
+ return any([isinstance(t, cy_Affine),
+ isinstance(t, cy_Translate),
+ isinstance(t, cy_Scale),
+ isinstance(t, cy_Rotate),
+ isinstance(t, cy_HShear),
+ isinstance(t, cy_VShear),
+ isinstance(t, cy_Zoom)
+ ])
+
+
+cdef class cy_Translate:
+
+ """Translation in 2D plane
+
+ Corresponds to Translate class in 2geom.
+ """
+
+ def __cinit__(self, *args):
+ """Create Translate instance form point or it's two coordinates."""
+ if len(args) == 0:
+ self.thisptr = new Translate()
+ elif len(args) == 1:
+ self.thisptr = new Translate( deref( (<cy_Point> args[0]).thisptr ) )
+ elif len(args) == 2:
+ self.thisptr = new Translate(float(args[0]), float(args[1]))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+
+ def __getitem__(self, Dim2 dim):
+ """Get components of displacement vector."""
+ return deref( self.thisptr ) [dim]
+
+ def __mul__(cy_Translate self, o):
+ """Compose with another transformation."""
+ if isinstance(o, cy_Translate):
+ return wrap_Translate(deref( self.thisptr ) * deref( (<cy_Translate>o).thisptr ))
+ elif is_transform(o):
+ return wrap_Affine(deref(self.thisptr) * get_Affine(o))
+
+ def __pow__(cy_Translate self, int n, z):
+ """Compose with self n times."""
+ return wrap_Translate(pow( deref(self.thisptr), n ))
+
+ def vector(self):
+ """Get displacement vector."""
+ return wrap_Point(self.thisptr.vector())
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_Translate(self.thisptr.inverse())
+
+ @classmethod
+ def identity(self):
+ """Create identity translation."""
+ return wrap_Translate(t_identity())
+
+ def __richcmp__(cy_Translate self, cy_Translate t, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(t.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(t.thisptr)
+
+cdef cy_Translate wrap_Translate(Translate p):
+ cdef Translate * retp = new Translate()
+ retp[0] = p
+ cdef cy_Translate r = cy_Translate.__new__(cy_Translate)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Scale:
+
+ """Scale in 2D plane.
+
+ Corresponds to Scale in 2geom.
+ """
+
+ def __cinit__(self, *args):
+ """Create scale from number, point or it's two coordinates.
+
+ One number creates uniform scale, point or two numbers create
+ scale with different x and y scale factor.
+ """
+ if len(args) == 0:
+ self.thisptr = new Scale()
+ elif len(args) == 1:
+ if isinstance(args[0], Number):
+ self.thisptr = new Scale(<Coord> float(args[0]))
+ elif isinstance(args[0], cy_Point):
+ self.thisptr = new Scale( deref( (<cy_Point> args[0]).thisptr ) )
+ elif len(args) == 2:
+ self.thisptr = new Scale(float(args[0]), float(args[1]))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __getitem__(self, Dim2 d):
+ """Get scale factors for each axis."""
+ return deref( self.thisptr ) [d]
+
+ def __mul__(cy_Scale self, o):
+ """Compose with another transformation."""
+ if isinstance(o, cy_Scale):
+ return wrap_Scale(deref( self.thisptr ) * deref( (<cy_Scale>o).thisptr ))
+ elif is_transform(o):
+ return wrap_Affine(deref(self.thisptr) * get_Affine(o))
+
+ def __pow__(cy_Scale self, int n, z):
+ """Compose with self n times."""
+ return wrap_Scale(pow( deref(self.thisptr), n ))
+
+ def vector(self):
+ """Get both scale factors as a point."""
+ return wrap_Point(self.thisptr.vector())
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_Scale(self.thisptr.inverse())
+
+ @classmethod
+ def identity(self):
+ """Create identity scale."""
+ return wrap_Scale(s_identity())
+
+ def __richcmp__(cy_Scale self, cy_Scale s, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(s.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(s.thisptr)
+
+cdef cy_Scale wrap_Scale(Scale p):
+ cdef Scale * retp = new Scale()
+ retp[0] = p
+ cdef cy_Scale r = cy_Scale.__new__(cy_Scale)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Rotate:
+
+ """Rotation in 2D plane.
+
+ Corresponds to Rotate in 2geom.
+ """
+
+ def __cinit__(self, *args):
+ """Create new Rotate instance, specifying angle.
+
+ Use one number to set the angle, or point/its two coordinates,
+ using point's angle with x-axis as a rotation angle.
+ """
+ if len(args) == 0:
+ self.thisptr = new Rotate()
+ elif len(args) == 1:
+ if isinstance(args[0], Number):
+ self.thisptr = new Rotate(<Coord> float(args[0]))
+ elif isinstance(args[0], cy_Point):
+ self.thisptr = new Rotate( deref( (<cy_Point> args[0]).thisptr ) )
+ elif len(args) == 2:
+ self.thisptr = new Rotate(float(args[0]), float(args[1]))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def vector(self):
+ """Return Point(1, 0)*self."""
+ return wrap_Point(self.thisptr.vector())
+
+ def __getitem__(self, Dim2 dim):
+ """Get components of self.vector()"""
+ return deref( self.thisptr ) [dim]
+
+ def __mul__(cy_Rotate self, o):
+ """Compose with another transformation."""
+ if isinstance(o, cy_Rotate):
+ return wrap_Rotate(deref( self.thisptr ) * deref( (<cy_Rotate>o).thisptr ))
+ elif is_transform(o):
+ return wrap_Affine(deref(self.thisptr) * get_Affine(o))
+
+ def __pow__(cy_Rotate self, int n, z):
+ """Compose with self n times."""
+ return wrap_Rotate(pow( deref(self.thisptr), n ))
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_Rotate(self.thisptr.inverse())
+
+ @classmethod
+ def identity(cls):
+ """Create identity rotation."""
+ return wrap_Rotate(r_identity())
+
+ @classmethod
+ def from_degrees(cls, Coord deg):
+ """Create rotation from angle in degrees."""
+ return wrap_Rotate(from_degrees(deg))
+
+ def __richcmp__(cy_Rotate self, cy_Rotate r, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(r.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(r.thisptr)
+
+cdef cy_Rotate wrap_Rotate(Rotate p):
+ cdef Rotate * retp = new Rotate()
+ retp[0] = p
+ cdef cy_Rotate r = cy_Rotate.__new__(cy_Rotate)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_VShear:
+
+ """Vertical shear in 2D plane
+
+ Corresponds to VShear in 2geom.
+ """
+
+ def __cinit__(self, Coord h):
+ """Create VShear instance form shearing factor."""
+ self.thisptr = new VShear(h)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def factor(self):
+ """Get shearing factor."""
+ return self.thisptr.factor()
+
+ def set_factor(self, Coord nf):
+ """Set shearing factor."""
+ self.thisptr.setFactor(nf)
+
+ def __mul__(cy_VShear self, o):
+ """Compose with another transformation."""
+ if isinstance(o, cy_VShear):
+ return wrap_VShear(deref( self.thisptr ) * deref( (<cy_VShear>o).thisptr ))
+ elif is_transform(o):
+ return wrap_Affine(deref(self.thisptr) * get_Affine(o))
+
+ def __pow__(cy_VShear self, int n, z):
+ """Compose with self n times."""
+ return wrap_VShear(pow( deref(self.thisptr), n ))
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_VShear(self.thisptr.inverse())
+
+ @classmethod
+ def identity(cls):
+ """Create identity VShear."""
+ return wrap_VShear( vs_identity() )
+
+ def __richcmp__(cy_VShear self, cy_VShear hs, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(hs.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(hs.thisptr)
+
+
+
+cdef cy_VShear wrap_VShear(VShear p):
+ cdef VShear * retp = new VShear(0)
+ retp[0] = p
+ cdef cy_VShear r = cy_VShear.__new__(cy_VShear, 0)
+ r.thisptr = retp
+ return r
+
+cdef class cy_HShear:
+
+ """Horizontal shear in 2D plane
+
+ Corresponds to HShear in 2geom.
+ """
+
+ def __cinit__(self, Coord h):
+ """Create HShear instance form shearing factor."""
+ self.thisptr = new HShear(h)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def factor(self):
+ """Get shearing factor."""
+ return self.thisptr.factor()
+
+ def set_factor(self, Coord nf):
+ """Set shearing factor."""
+ self.thisptr.setFactor(nf)
+
+ def __mul__(cy_HShear self, o):
+ """Compose with another transformation."""
+ if isinstance(o, cy_HShear):
+ return wrap_HShear(deref( self.thisptr ) * deref( (<cy_HShear>o).thisptr ))
+ elif is_transform(o):
+ return wrap_Affine(deref(self.thisptr) * get_Affine(o))
+
+ def __pow__(cy_HShear self, int n, z):
+ """Compose with self n times."""
+ return wrap_HShear(pow( deref(self.thisptr), n ))
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_HShear(self.thisptr.inverse())
+
+ @classmethod
+ def identity(cls):
+ """Create identity HShear."""
+ return wrap_HShear( hs_identity() )
+
+ def __richcmp__(cy_HShear self, cy_HShear hs, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(hs.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(hs.thisptr)
+
+cdef cy_HShear wrap_HShear(HShear p):
+ cdef HShear * retp = new HShear(0)
+ retp[0] = p
+ cdef cy_HShear r = cy_HShear.__new__(cy_HShear, 0)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Zoom:
+
+ """Zoom in 2D plane, consisting of uniform scale and translation.
+
+ Corresponds to Zoom in 2geom.
+ """
+
+ def __cinit__(self, Coord scale=1, cy_Translate translate=cy_Translate()):
+ """Create Zoom from scale factor and translation"""
+ self.thisptr = new Zoom( scale, deref( translate.thisptr ) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __mul__(cy_Zoom self, cy_Zoom z):
+ """Compose with another transformation."""
+ return wrap_Zoom( deref(self.thisptr) * deref( z.thisptr ))
+
+ def __pow__(cy_Zoom self, int n, z):
+ """Compose with self n times."""
+ return wrap_Zoom(pow( deref(self.thisptr), n ))
+
+ def __richcmp__(cy_Zoom self, cy_Zoom z, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(z.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(z.thisptr)
+
+ def scale(self):
+ """Get scale factor."""
+ return self.thisptr.scale()
+
+ def set_scale(self, Coord s):
+ """Set scale factor."""
+ self.thisptr.setScale(s)
+
+ def translation(self):
+ """Get translation as a point."""
+ return wrap_Point(self.thisptr.translation())
+
+ def set_translation(self, cy_Point p):
+ """Set translation."""
+ self.thisptr.setTranslation(deref( p.thisptr ))
+
+ def inverse(self):
+ """Return inverse transformation."""
+ return wrap_Zoom(self.thisptr.inverse())
+
+ @classmethod
+ def identity(cls):
+ """Create identity zoom."""
+ return wrap_Zoom(z_identity())
+
+ @classmethod
+ def map_rect(self, cy_Rect old_r, cy_Rect new_r):
+ """Create zooming used to go from old rectangle to new."""
+ return wrap_Zoom(map_rect(deref( old_r.thisptr ) ,deref( new_r.thisptr )))
+
+cdef cy_Zoom wrap_Zoom(Zoom p):
+ cdef Zoom * retp = new Zoom(0)
+ retp[0] = p
+ cdef cy_Zoom r = cy_Zoom.__new__(cy_Zoom, 0)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Eigen:
+
+ """Class computing eigenvalues and eigenvectors of 2x2 matrix.
+
+ Corresponds to Eigen class in 2geom.
+ """
+
+ cdef Eigen* thisptr
+
+ def __cinit__(self, a):
+ """Create Eigen form 2D transform or 2x2 list - matrix."""
+ cdef Affine at
+ cdef double m[2][2]
+ if is_transform(a):
+ at = get_Affine(a)
+ self.thisptr = new Eigen(at)
+ else:
+ for i in range(2):
+ for j in range(2):
+ m[i][j] = a[i][j]
+ self.thisptr = new Eigen(m)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @property
+ def vectors(self):
+ """Eigenvectors of matrix."""
+ return (wrap_Point(self.thisptr.vectors[0]), wrap_Point(self.thisptr.vectors[1]))
+
+ @property
+ def values(self):
+ """Eigenvalues of matrix."""
+ return (self.thisptr.values[0], self.thisptr.values[1])
diff --git a/src/cython/_cy_conicsection.pxd b/src/cython/_cy_conicsection.pxd
new file mode 100644
index 0000000..f330cd3
--- /dev/null
+++ b/src/cython/_cy_conicsection.pxd
@@ -0,0 +1,50 @@
+from _common_decl cimport *
+
+from libcpp.vector cimport vector
+from libcpp.pair cimport pair
+
+from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect
+from _cy_rectangle cimport cy_OptRect
+from _cy_affine cimport is_transform, get_Affine, Affine
+from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p
+from _cy_curves cimport SBasis, cy_SBasis
+from _cy_curves cimport EllipticalArc, cy_EllipticalArc, wrap_EllipticalArc
+
+from _cy_path cimport Path, cy_Path
+
+from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point
+
+
+cdef extern from "2geom/circle.h" namespace "Geom":
+ cdef cppclass Circle:
+ Circle()
+ Circle(double, double, double)
+ Circle(Point, double)
+ Circle(double, double, double, double)
+ Circle(vector[Point] &)
+ void setCenter(Point &)
+ void setRadius(double)
+ void set(double, double, double, double)
+ void fit(vector[Point] &)
+ EllipticalArc * arc(Point &, Point &, Point &, bint)
+ Point center()
+ Coord center(Dim2)
+ Coord radius()
+
+cdef extern from "2geom/ellipse.h" namespace "Geom":
+ cdef cppclass Ellipse:
+ Ellipse()
+ Ellipse(double, double, double, double, double)
+ Ellipse(double, double, double, double, double, double)
+ Ellipse(vector[Point] &)
+ Ellipse(Circle &)
+ void set(double, double, double, double, double)
+ void set(double, double, double, double, double, double)
+ void set(vector[Point] &)
+ EllipticalArc * arc(Point &, Point &, Point &, bint)
+ Point center()
+ Coord center(Dim2)
+ Coord ray(Dim2)
+ Coord rot_angle()
+ vector[double] implicit_form_coefficients()
+ Ellipse transformed(Affine &)
diff --git a/src/cython/_cy_conicsection.pyx b/src/cython/_cy_conicsection.pyx
new file mode 100644
index 0000000..91bbd73
--- /dev/null
+++ b/src/cython/_cy_conicsection.pyx
@@ -0,0 +1,183 @@
+from cython.operator cimport dereference as deref
+from numbers import Number
+from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect
+from _cy_rectangle cimport cy_Interval, wrap_Interval
+
+from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform
+
+from _cy_curves cimport is_Curve, get_Curve_p
+from _cy_path cimport wrap_Path
+
+
+cdef class cy_Circle:
+
+ """Circle in 2D plane.
+
+ Corresponds to Circle class in 2geom.
+ """
+
+ cdef Circle* thisptr
+
+ def __cinit__(self, cy_Point center=None, double r=0):
+ """Create circle from center and radius."""
+ if center is None:
+ self.thisptr = new Circle()
+ else:
+ self.thisptr = new Circle(deref( center.thisptr ), r)
+
+ @classmethod
+ def from_coefficients(self, double A, double B, double C, double D):
+ """Create circle form implicit equation coefficients:
+
+ Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0
+ """
+ return wrap_Circle( Circle(A, B, C, D) )
+
+ @classmethod
+ def from_points(self, points):
+ """Create best fitting circle from at least three points."""
+ return wrap_Circle( Circle( make_vector_point(points) ) )
+
+ def set_center(self, cy_Point c):
+ """Set coordinates of center."""
+ self.thisptr.setCenter(deref(c.thisptr))
+
+ def set_radius(self, double r):
+ """Set the circle's radius."""
+ self.thisptr.setRadius(r)
+
+ def set_coefficients(self, double A, double B, double C, double D):
+ """Set implicit equation coefficients:
+
+ Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0
+ """
+ self.thisptr.set(A, B, C, D)
+
+ def fit(self, points):
+ """Set circle to the best fit of at least three points."""
+ self.thisptr.fit( make_vector_point(points) )
+
+ def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint _svg_compliant=True):
+ """Get (SVG)EllipticalArc.
+
+ Args:
+ initial: Initial point of arc
+ inner: Inner point of arc.
+ final: Final point of arc.
+ """
+ return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) )
+
+ def center(self):
+ """Get center of circle in point."""
+ return wrap_Point(self.thisptr.center())
+
+ def radius(self):
+ """Get radius of circle."""
+ return self.thisptr.radius()
+
+cdef cy_Circle wrap_Circle(Circle p):
+ cdef Circle * retp = new Circle()
+ retp[0] = p
+ cdef cy_Circle r = cy_Circle.__new__(cy_Circle)
+ r.thisptr = retp
+ return r
+
+cdef class cy_Ellipse:
+ """Ellipse in 2D plane.
+
+ Corresponds to Ellipse class in 2geom.
+ """
+
+ cdef Ellipse* thisptr
+
+ def __cinit__(self, cy_Point center=None, rx=0, ry=0, double a=0):
+ """Create new ellipse:
+
+ Args:
+ center: Center of ellipse (between foci)
+ rx, ry: major and minor semi-axis.
+ a: angle of major axis.
+ """
+ if center is None:
+ self.thisptr = new Ellipse()
+ else:
+ self.thisptr = new Ellipse(center.x, center.y, rx, ry, a)
+
+ @classmethod
+ def from_coefficients(cls, double A, double B, double C, double D, double E, double F):
+ """Create ellipse from coefficients of implicit equation.
+
+ Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0
+ """
+ return wrap_Ellipse(Ellipse(A, B, C, D, E, F))
+
+ @classmethod
+ def from_circle(cls, cy_Circle c):
+ """Create ellipse identical to circle."""
+ return wrap_Ellipse(Ellipse(deref( c.thisptr )))
+
+ @classmethod
+ def from_points(cls, points):
+ """Create ellipse fitting at least 5 points."""
+ return wrap_Ellipse( Ellipse( make_vector_point(points) ) )
+
+ def set(self, cy_Point center, double rx, double ry, double a):
+ """Set center, rays and angle of ellipse.
+
+ Args:
+ center: Center of ellipse.
+ rx, ry: Major and minor semi-axis.
+ a: angle of major axis.
+ self.thisptr.set(center.x, center.y, rx, ry, a)
+ """
+
+ def set_coefficients(self, double A, double B, double C, double D, double E, double F):
+ """Set coefficients of implicit equation.
+
+ Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0
+ """
+ self.thisptr.set(A, B, C, D, E, F)
+
+ def set_points(self, points):
+ """Set ellipse to the best fit of at least five points."""
+ self.thisptr.set( make_vector_point(points) )
+
+ def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint svg_compliant=True):
+ """Get (SVG)EllipticalArc.
+
+ Args:
+ initial: Initial point of arc
+ inner: Inner point of arc.
+ final: Final point of arc.
+ """
+ return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) )
+
+ def center(self):
+ """Get center of ellipse."""
+ return wrap_Point(self.thisptr.center())
+
+ def ray(self, Dim2 d):
+ """Get major/minor semi-axis."""
+ return self.thisptr.ray(d)
+
+ def rot_angle(self):
+ """Get angle of major axis."""
+ return self.thisptr.rot_angle()
+
+ def implicit_form_coefficients(self):
+ """Get coefficients of implicit equation in list."""
+ return wrap_vector_double(self.thisptr.implicit_form_coefficients())
+
+ def transformed(self, m):
+ """Return transformed ellipse."""
+ cdef Affine at
+ if is_transform(m):
+ at = get_Affine(m)
+ return wrap_Ellipse(self.thisptr.transformed(at))
+
+cdef cy_Ellipse wrap_Ellipse(Ellipse p):
+ cdef Ellipse * retp = new Ellipse()
+ retp[0] = p
+ cdef cy_Ellipse r = cy_Ellipse.__new__(cy_Ellipse)
+ r.thisptr = retp
+ return r
diff --git a/src/cython/_cy_curves.pxd b/src/cython/_cy_curves.pxd
new file mode 100644
index 0000000..dd13c06
--- /dev/null
+++ b/src/cython/_cy_curves.pxd
@@ -0,0 +1,421 @@
+from _common_decl cimport *
+
+from libcpp.vector cimport vector
+from libcpp.pair cimport pair
+
+from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect
+from _cy_affine cimport Affine
+from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point
+from _cy_primitives cimport Angle, cy_Angle, wrap_Angle
+from _cy_primitives cimport AngleInterval
+
+
+cdef extern from "2geom/d2.h" namespace "Geom":
+ cdef cppclass D2[T]:
+ D2()
+ D2(T &, T &)
+ T& operator[](unsigned i)
+
+cdef extern from "2geom/curve.h" namespace "Geom":
+ cdef cppclass Curve:
+ Curve()
+ Point initialPoint()
+ Point finalPoint()
+ bint isDegenerate()
+ Point pointAt(Coord)
+ Coord valueAt(Coord, Dim2)
+ Point operator()(Coord)
+ vector[Point] pointAndDerivatives(Coord, unsigned int)
+ void setInitial(Point &)
+ void setFinal(Point &)
+ Rect boundsFast()
+ Rect boundsExact()
+ OptRect boundsLocal(OptInterval &, unsigned int)
+ OptRect boundsLocal(OptInterval &)
+ Curve * duplicate()
+ Curve * transformed(Affine &)
+ Curve * portion(Coord, Coord)
+ Curve * portion(Interval &)
+ Curve * reverse()
+ Curve * derivative()
+ Coord nearestTime(Point &, Coord, Coord)
+ Coord nearestTime(Point &, Interval &)
+ vector[double] allNearestTimes(Point &, Coord, Coord)
+ vector[double] allNearestTimes(Point &, Interval &)
+ Coord length(Coord)
+ vector[double] roots(Coord, Dim2)
+ int winding(Point &)
+ Point unitTangentAt(Coord, unsigned int)
+ D2[SBasis] toSBasis()
+ int degreesOfFreedom()
+ bint operator==(Curve &)
+
+cdef class cy_Curve:
+ cdef Curve* thisptr
+
+#~ cdef cy_Curve wrap_Curve(Curve & p)
+cdef cy_Curve wrap_Curve_p(Curve * p)
+
+
+cdef extern from "2geom/linear.h" namespace "Geom":
+ cdef cppclass Linear:
+#~ Linear(Linear &)
+ Linear()
+ Linear(double, double)
+ Linear(double)
+ double operator[](int const)
+ bint isZero(double)
+ bint isConstant(double)
+ bint isFinite()
+ double at0()
+ double at1()
+ double valueAt(double)
+ double operator()(double)
+ SBasis toSBasis()
+ OptInterval bounds_exact()
+ OptInterval bounds_fast()
+ OptInterval bounds_local(double, double)
+ double tri()
+ double hat()
+
+ bint operator==(Linear &, Linear &)
+ bint operator!=(Linear &, Linear &)
+ Linear operator*(Linear &, double)
+ Linear operator+(Linear &, double)
+ Linear operator+(Linear &, Linear &)
+ #cython has trouble resolving these
+ Linear L_neg "operator-" (Linear &)
+ Linear L_sub_Ld "operator-"(Linear &, double)
+ Linear L_sub_LL "operator-"(Linear &, Linear &)
+ Linear operator/(Linear &, double)
+
+ double lerp(double, double, double)
+ Linear reverse(Linear &)
+
+
+cdef extern from "2geom/sbasis.h" namespace "Geom":
+ cdef cppclass SBasis:
+
+ SBasis()
+ SBasis(double)
+ SBasis(double, double)
+ SBasis(SBasis &)
+ SBasis(vector[Linear] &)
+ SBasis(Linear &)
+
+ size_t size()
+ Linear operator[](unsigned int)
+ bint empty()
+ Linear & back()
+ void pop_back()
+ void resize(unsigned int)
+ void resize(unsigned int, Linear &)
+ void reserve(unsigned int)
+ void clear()
+#~ void insert(::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >)
+ Linear & at(unsigned int)
+ bint operator==(SBasis &)
+ bint operator!=(SBasis &)
+
+ bint isZero(double)
+ bint isConstant(double)
+ bint isFinite()
+ double at0()
+ double at1()
+ int degreesOfFreedom()
+ double valueAt(double)
+ double operator()(double)
+ vector[double] valueAndDerivatives(double, unsigned int)
+ SBasis toSBasis()
+ double tailError(unsigned int)
+ SBasis operator()(SBasis &)
+ void normalize()
+ void truncate(unsigned int)
+
+ SBasis operator*(SBasis &, SBasis &)
+ SBasis operator*(double, SBasis &)
+ SBasis operator*(SBasis &, double)
+ SBasis operator+(SBasis &, double)
+ SBasis operator+(SBasis &, SBasis &)
+ SBasis SB_sub_Sd "operator-" (SBasis &, double)
+ SBasis SB_sub_SS "operator-" (SBasis &, SBasis &)
+ SBasis SB_neg "operator-" (SBasis &)
+ SBasis operator/(SBasis &, double)
+
+ SBasis sin(Linear, int)
+ SBasis cos(Linear, int)
+ SBasis sqrt(SBasis &, int)
+ SBasis reciprocal(Linear &, int)
+ SBasis shift(Linear &, int)
+ SBasis shift(SBasis &, int)
+ SBasis inverse(SBasis, int)
+
+ SBasis portion(SBasis &, Interval)
+ SBasis portion(SBasis &, double, double)
+ SBasis compose(SBasis &, SBasis &, unsigned int)
+ SBasis compose(SBasis &, SBasis &)
+ SBasis truncate(SBasis &, unsigned int)
+
+ unsigned int valuation(SBasis &, double)
+
+ vector[ vector[double] ] multi_roots(SBasis &, vector[double] &, double, double, double, double) #TODO
+ vector[Interval] level_set(SBasis &, Interval &, double, double, double)
+ vector[Interval] level_set(SBasis &, double, double, double, double, double)
+ vector[double] roots(SBasis &, Interval const)
+ vector[double] roots(SBasis &)
+ vector[ vector[Interval] ] level_sets(SBasis &, vector[Interval] &, double, double, double) #TODO
+ vector[ vector[Interval] ] level_sets(SBasis &, vector[double] &, double, double, double, double) #TODO
+
+ SBasis reverse(SBasis &)
+ SBasis derivative(SBasis &)
+ SBasis integral(SBasis &)
+ SBasis divide(SBasis &, SBasis &, int)
+ SBasis compose_inverse(SBasis &, SBasis &, unsigned int, double)
+ SBasis multiply(SBasis &, SBasis &)
+ SBasis multiply_add(SBasis &, SBasis &, SBasis)
+
+ OptInterval bounds_exact(SBasis &)
+ OptInterval bounds_local(SBasis &, OptInterval &, int)
+ OptInterval bounds_fast(SBasis &, int)
+
+cdef class cy_SBasis:
+ cdef SBasis* thisptr
+
+cdef extern from "2geom/sbasis-curve.h" namespace "Geom":
+ cdef cppclass SBasisCurve:
+ SBasisCurve(D2[SBasis] &)
+ SBasisCurve(Curve &)
+ Curve * duplicate()
+ Point initialPoint()
+ Point finalPoint()
+ bint isDegenerate()
+ Point pointAt(Coord)
+ Point operator()(double)
+ vector[Point] pointAndDerivatives(Coord, unsigned int)
+ Coord valueAt(Coord, Dim2)
+ void setInitial(Point &)
+ void setFinal(Point &)
+ Rect boundsFast()
+ Rect boundsExact()
+ OptRect boundsLocal(OptInterval &, unsigned int)
+ vector[double] roots(Coord, Dim2)
+ Coord nearestTime(Point &, Coord, Coord)
+ vector[double] allNearestTimes(Point &, Coord, Coord)
+ Coord length(Coord)
+ Curve * portion(Coord, Coord)
+ Curve * transformed(Affine &)
+ Curve * derivative()
+ D2[SBasis] toSBasis()
+ int degreesOfFreedom()
+
+
+cdef extern from "2geom/bezier.h" namespace "Geom":
+ cdef cppclass Bezier:
+#~ struct Order
+#~ pass
+ unsigned int order()
+ unsigned int size()
+ Bezier()
+#~ Bezier(Bezier.Order)
+ Bezier(Coord)
+ Bezier(Coord, Coord)
+ Bezier(Coord, Coord, Coord)
+ Bezier(Coord, Coord, Coord, Coord)
+ void resize(unsigned int, Coord)
+ void clear()
+ unsigned int degree()
+ bint isZero(double)
+ bint isConstant(double)
+ bint isFinite()
+ Coord at0()
+ Coord at1()
+ Coord valueAt(double)
+ Coord operator()(double)
+ SBasis toSBasis()
+ Coord & operator[](unsigned int)
+ void setPoint(unsigned int, double)
+ vector[double] valueAndDerivatives(Coord, unsigned int)
+ pair[Bezier, Bezier] subdivide(Coord)
+ vector[double] roots()
+ vector[double] roots(Interval const)
+ Bezier forward_difference(unsigned int)
+ Bezier elevate_degree()
+ Bezier reduce_degree()
+ Bezier elevate_to_degree(unsigned int)
+ Bezier deflate()
+ Bezier(Coord *, unsigned int)
+
+ Bezier operator*(Bezier &, double)
+ Bezier operator+(Bezier &, double)
+ Bezier operator-(Bezier &, double)
+ Bezier operator/(Bezier &, double)
+
+ Bezier portion(Bezier &, double, double)
+ OptInterval bounds_local(Bezier &, OptInterval)
+ Coord casteljau_subidivision(Coord, Coord *, Coord *, Coord *, unsigned int)
+ void bezier_to_sbasis(SBasis &, Bezier &)
+ Bezier integral(Bezier &)
+ Bezier derivative(Bezier &)
+ OptInterval bounds_exact(Bezier &)
+ double bernsteinValueAt(double, double *, unsigned int)
+ vector[Point] bezier_points(D2[Bezier] &)
+ OptInterval bounds_fast(Bezier &)
+ Bezier multiply(Bezier &, Bezier &)
+ Bezier reverse(Bezier &)
+
+#This is ugly workaround around cython's lack of support for integer template parameters
+cdef extern from *:
+ ctypedef int n_0 "0"
+ ctypedef int n_1 "1"
+
+cdef extern from "2geom/bezier-curve.h" namespace "Geom":
+ cdef cppclass BezierCurve:
+ unsigned int order()
+ vector[Point] controlPoints()
+ void setPoint(unsigned int, Point)
+ void setPoints(vector[Point] &)
+ Point operator[](unsigned int)
+ Point initialPoint()
+ Point finalPoint()
+ bint isDegenerate()
+ void setInitial(Point &)
+ void setFinal(Point &)
+ Rect boundsFast()
+ Rect boundsExact()
+ OptRect boundsLocal(OptInterval &, unsigned int)
+ Curve * duplicate()
+ Curve * portion(Coord, Coord)
+ Curve * reverse()
+ Curve * transformed(Affine &)
+ Curve * derivative()
+ int degreesOfFreedom()
+ vector[double] roots(Coord, Dim2)
+ Coord length(Coord)
+ Point pointAt(Coord)
+ vector[Point] pointAndDerivatives(Coord, unsigned int)
+ Coord valueAt(Coord, Dim2)
+ D2[SBasis] toSBasis()
+ #protected:
+#~ BezierCurve()
+#~ BezierCurve(D2[Bezier] &)
+#~ BezierCurve(Bezier &, Bezier &)
+#~ BezierCurve(vector[Point] &)
+ #~ Point middle_point(LineSegment &)
+ #~ Coord length(LineSegment &)
+ Coord bezier_length(Point, Point, Point, Point, Coord)
+ Coord bezier_length(Point, Point, Point, Coord)
+ Coord bezier_length(vector[Point] &, Coord)
+
+ cdef cppclass LineSegment:
+ LineSegment()
+ LineSegment(D2[Bezier] &)
+ LineSegment(Bezier, Bezier)
+ LineSegment(vector[Point] &)
+ LineSegment(Point, Point)
+ pair[LineSegment, LineSegment] subdivide(Coord)
+
+ Curve * duplicate()
+ Curve * reverse()
+ Curve * transformed(Affine &)
+ Curve * derivative()
+
+ cdef cppclass QuadraticBezier:
+ QuadraticBezier()
+ QuadraticBezier(D2[Bezier] &)
+ QuadraticBezier(Bezier, Bezier)
+ QuadraticBezier(vector[Point] &)
+ QuadraticBezier(Point, Point, Point)
+ pair[QuadraticBezier, QuadraticBezier] subdivide(Coord)
+
+ Curve * duplicate()
+ Curve * reverse()
+ Curve * transformed(Affine &)
+ Curve * derivative()
+
+
+ cdef cppclass CubicBezier:
+ CubicBezier()
+ CubicBezier(D2[Bezier] &)
+ CubicBezier(Bezier, Bezier)
+ CubicBezier(vector[Point] &)
+ CubicBezier(Point, Point, Point, Point)
+ pair[CubicBezier, CubicBezier] subdivide(Coord)
+
+ Curve * duplicate()
+ Curve * reverse()
+ Curve * transformed(Affine &)
+ Curve * derivative()
+
+#~ cdef cppclass BezierCurveN[n_1]:
+#~ BezierCurveN ()
+#~ BezierCurveN(Bezier &, Bezier &)
+#~ #BezierCurveN(Point &, Point &)
+#~ BezierCurveN(vector[Point] &)
+#~ pair[BezierCurveN, BezierCurveN] subdivide(Coord)
+#~
+#~ Curve * duplicate()
+#~ Curve * reverse()
+#~ Curve * transformed(Affine &)
+#~ Curve * derivative()
+
+cdef class cy_BezierCurve:
+ cdef BezierCurve* thisptr
+
+cdef class cy_LineSegment(cy_BezierCurve):
+ pass
+
+cdef cy_LineSegment wrap_LineSegment(LineSegment p)
+
+cdef extern from "2geom/bezier-curve.h" namespace "Geom::BezierCurve":
+ BezierCurve * create(vector[Point] &)
+
+cdef extern from "2geom/elliptical-arc.h" namespace "Geom":
+ cdef cppclass EllipticalArc:
+ EllipticalArc(EllipticalArc &)
+ EllipticalArc()
+ EllipticalArc(Point, Coord, Coord, Coord, bint, bint, Point)
+ Interval angleInterval()
+ Angle rotationAngle()
+ Coord ray(Dim2)
+ Point rays()
+ bint largeArc()
+ bint sweep()
+ LineSegment chord()
+ void set(Point &, double, double, double, bint, bint, Point &)
+ void setExtremes(Point &, Point &)
+ Coord center(Dim2)
+ Point center()
+ Coord sweepAngle()
+ bint containsAngle(Coord)
+ Point pointAtAngle(Coord)
+ Coord valueAtAngle(Coord, Dim2)
+ Affine unitCircleTransform()
+ bint isSVGCompliant()
+ pair[EllipticalArc, EllipticalArc] subdivide(Coord)
+ Point initialPoint()
+ Point finalPoint()
+ Curve * duplicate()
+ void setInitial(Point &)
+ void setFinal(Point &)
+ bint isDegenerate()
+ Rect boundsFast()
+ Rect boundsExact()
+ OptRect boundsLocal(OptInterval &, unsigned int)
+ vector[double] roots(double, Dim2)
+ double nearestTime(Point &, double, double)
+ int degreesOfFreedom()
+ Curve * derivative()
+ Curve * transformed(Affine &)
+ vector[Point] pointAndDerivatives(Coord, unsigned int)
+ D2[SBasis] toSBasis()
+ double valueAt(Coord, Dim2)
+ Point pointAt(Coord)
+ Curve * portion(double, double)
+ Curve * reverse()
+
+cdef class cy_EllipticalArc:
+ cdef EllipticalArc* thisptr
+cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p)
+
+cdef Curve * get_Curve_p(object c)
+cdef bint is_Curve(object c)
diff --git a/src/cython/_cy_curves.pyx b/src/cython/_cy_curves.pyx
new file mode 100644
index 0000000..3584a87
--- /dev/null
+++ b/src/cython/_cy_curves.pyx
@@ -0,0 +1,1945 @@
+from numbers import Number
+
+from cython.operator cimport dereference as deref
+
+from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect
+from _cy_rectangle cimport cy_Interval, wrap_Interval
+
+from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale
+from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom
+from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform
+
+
+cdef class cy_Curve:
+
+ """Class representing generic curve.
+
+ Curve maps unit interval to real plane. All curves should implement
+ these methods.
+
+ This class corresponds to Curve class in 2geom. It's children in cython
+ aren't actually derived from it, it would make code more unreadable.
+ """
+
+ def __cinit__(self):
+ """Create new Curve.
+
+ You shouldn't create Curve this way, it usually wraps existing
+ curves (f. e. in Path).
+ """
+ self.thisptr = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(0), SBasis(0) ))
+
+ def __call__(self, Coord t):
+ """Get point at time value t."""
+ return wrap_Point( deref(self.thisptr)(t) )
+
+ def initial_point(self):
+ """Get self(0)."""
+ return wrap_Point(self.thisptr.initialPoint())
+
+ def final_point(self):
+ """Get self(1)."""
+ return wrap_Point(self.thisptr.finalPoint())
+ def is_degenerate(self):
+ """Curve is degenerate if it's length is zero."""
+ return self.thisptr.isDegenerate()
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return self.thisptr.valueAt(t, d)
+
+ def point_and_derivatives(self, Coord t, unsigned int n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n))
+
+ def set_initial(self, cy_Point v):
+ """Set initial point of curve."""
+ self.thisptr.setInitial(deref( v.thisptr ))
+
+ def set_final(self, cy_Point v):
+ """Set final point of curve."""
+ self.thisptr.setFinal(deref( v.thisptr ))
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect(self.thisptr.boundsFast())
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect(self.thisptr.boundsExact())
+
+ def bounds_local(self, cy_OptInterval i, unsigned int deg=0):
+ """Return bounding rectangle to portion of curve."""
+ return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg))
+
+ #TODO rewrite all duplicates to copy."""
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_Curve_p( self.thisptr.duplicate() )
+
+ def transformed(self, m):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(m):
+ at = get_Affine(m)
+ return wrap_Curve_p( self.thisptr.transformed(at) )
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_Curve_p( self.thisptr.portion(deref( interval.thisptr )) )
+ else:
+ return wrap_Curve_p( self.thisptr.portion(fr, to) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_Curve_p( self.thisptr.reverse() )
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_Curve_p( self.thisptr.derivative() )
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return self.thisptr.nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return self.thisptr.nearestTime(deref( p.thisptr ), deref( interval.thisptr ))
+
+ def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return all values of t that |self(t) - point| is minimized."""
+ if interval is None:
+ return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to))
+ else:
+ return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ),
+ deref( interval.thisptr )))
+
+ def length(self, Coord tolerance):
+ """Return length of curve, within give tolerance."""
+ return self.thisptr.length(tolerance)
+
+ def roots(self, Coord v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double(self.thisptr.roots(v, d))
+
+ def winding(self, cy_Point p):
+ """Return winding number around specified point."""
+ return self.thisptr.winding(deref( p.thisptr ))
+
+ def unit_tangent_at(self, Coord t, unsigned int n):
+ """Return tangent at self(t).
+
+ Parameter n specifies how many derivatives to take into account."""
+ return wrap_Point(self.thisptr.unitTangentAt(t, n))
+
+ def to_SBasis(self):
+ """Return tuple of SBasis functions."""
+ cdef D2[SBasis] ret = self.thisptr.toSBasis()
+ return ( wrap_SBasis(ret[0]), wrap_SBasis(ret[1]) )
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return self.thisptr.degreesOfFreedom()
+#~ def operator==(self, cy_Curve c):
+#~ return deref( self.thisptr ) == deref( c.thisptr )
+
+#~ cdef cy_Curve wrap_Curve(Curve & p):
+#~ cdef Curve * retp = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(), SBasis() ))
+#~ retp[0] = p
+#~ cdef cy_Curve r = cy_Curve.__new__(cy_Curve)
+#~ r.thisptr = retp
+#~ return r
+
+cdef cy_Curve wrap_Curve_p(Curve * p):
+ cdef cy_Curve r = cy_Curve.__new__(cy_Curve)
+ r.thisptr = p
+ return r
+
+cdef class cy_Linear:
+ """Function mapping linearly between two values.
+
+ Corresponds to Linear class in 2geom.
+ """
+
+ cdef Linear* thisptr
+
+ def __cinit__(self, aa = None, b = None):
+ """Create new Linear from two end values.
+
+ No arguments create zero constant, one value creates constant.
+ """
+ if aa is None:
+ self.thisptr = new Linear()
+ elif b is None:
+ self.thisptr = new Linear(float(aa))
+ else:
+ self.thisptr = new Linear(float(aa), float(b))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __call__(self, Coord t):
+ """Get value at time value t."""
+ return deref(self.thisptr)(t)
+
+ def __getitem__(self, i):
+ """Get end values."""
+ return deref( self.thisptr ) [i]
+
+ def __richcmp__(cy_Linear self, cy_Linear other, int op):
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+
+
+ def __neg__(cy_Linear self):
+ """Negate all values of self."""
+ return wrap_Linear( L_neg(deref(self.thisptr)) )
+
+
+ def __add__(cy_Linear self, other):
+ """Add number or other linear."""
+ if isinstance(other, Number):
+ return wrap_Linear( deref(self.thisptr) + float(other) )
+ elif isinstance(other, cy_Linear):
+ return wrap_Linear( deref(self.thisptr) + deref( (<cy_Linear> other).thisptr ) )
+
+ def __sub__(cy_Linear self, other):
+ """Substract number or other linear."""
+ if isinstance(other, Number):
+ return wrap_Linear( L_sub_Ld(deref(self.thisptr), float(other)) )
+ elif isinstance(other, cy_Linear):
+ return wrap_Linear( L_sub_LL(deref(self.thisptr), deref( (<cy_Linear> other).thisptr )) )
+
+
+ def __mul__(cy_Linear self, double b):
+ """Multiply linear by number."""
+ return wrap_Linear(deref( self.thisptr ) * b)
+
+ def __div__(cy_Linear self, double b):
+ """Divide linear by value."""
+ return wrap_Linear(deref( self.thisptr ) / b)
+
+ def is_zero(self, double eps = EPSILON):
+ """Test whether linear is zero within given tolerance."""
+ return self.thisptr.isZero(eps)
+
+ def is_constant(self, double eps = EPSILON):
+ """Test whether linear is constant within given tolerance."""
+ return self.thisptr.isConstant(eps)
+
+ def is_finite(self):
+ """Test whether linear is finite."""
+ return self.thisptr.isFinite()
+
+ def at0(self):
+ """Equivalent to self(0)."""
+ return self.thisptr.at0()
+
+ def at1(self):
+ """Equivalent to self(1)."""
+ return self.thisptr.at1()
+
+ def value_at(self, double t):
+ """Equivalent to self(t)."""
+ return self.thisptr.valueAt(t)
+
+ def to_SBasis(self):
+ """Convert to SBasis."""
+ return wrap_SBasis(self.thisptr.toSBasis())
+
+ def bounds_exact(self):
+ """Return exact bounding interval
+
+ This may take a while.
+ """
+ return wrap_OptInterval(self.thisptr.bounds_exact())
+
+ def bounds_fast(self):
+ """Return bounding interval
+
+ This method is fast, but does not guarantee to give smallest
+ interval.
+ """
+ return wrap_OptInterval(self.thisptr.bounds_fast())
+
+ def bounds_local(self, double u, double v):
+ """Return bounding interval to the portion of Linear."""
+ return wrap_OptInterval(self.thisptr.bounds_local(u, v))
+
+ def tri(self):
+ """Return difference between end values."""
+ return self.thisptr.tri()
+
+ def hat(self):
+ """Return value at (0.5)."""
+ return self.thisptr.hat()
+
+ @classmethod
+ def sin(cls, cy_Linear bo, int k):
+ """Return sine of linear."""
+ return wrap_SBasis(sin(deref( bo.thisptr ), k))
+
+ @classmethod
+ def cos(cls, cy_Linear bo, int k):
+ """Return cosine of linear."""
+ return wrap_SBasis(cos(deref( bo.thisptr ), k))
+
+ @classmethod
+ def reciprocal(cls, cy_Linear a, int k):
+ """Return reciprocical of linear."""
+ return wrap_SBasis(reciprocal(deref( a.thisptr ), k))
+
+ @classmethod
+ def shift(cls, cy_Linear a, int sh):
+ """Multiply by x**sh."""
+ return wrap_SBasis(shift(deref( a.thisptr ), sh))
+
+#leave these in cy2geom napespace?
+def cy_lerp(double t, double a, double b):
+ return lerp(t, a, b)
+
+cdef cy_Linear wrap_Linear(Linear p):
+ cdef Linear * retp = new Linear()
+ retp[0] = p
+ cdef cy_Linear r = cy_Linear.__new__(cy_Linear)
+ r.thisptr = retp
+ return r
+
+cdef vector[Linear] make_vector_linear(object l):
+ cdef vector[Linear] ret
+ for i in l:
+ ret.push_back( deref( (<cy_Linear> i).thisptr ) )
+ return ret
+
+
+cdef class cy_SBasis:
+
+ """Class representing SBasis polynomial.
+
+ Corresponds to SBasis class in 2geom."""
+
+ def __cinit__(self, a=None, b=None):
+ """Create new SBasis.
+
+ This constructor only creates linear SBasis, specifying endpoints.
+ """
+ if a is None:
+ self.thisptr = new SBasis()
+ elif b is None:
+ self.thisptr = new SBasis( float(a) )
+ else:
+ self.thisptr = new SBasis( float(a), float(b) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_linear(cls, cy_Linear l):
+ """Create SBasis from Linear."""
+ return wrap_SBasis( SBasis(deref( l.thisptr )) )
+
+ @classmethod
+ def from_linears(cls, lst):
+ """Create SBasis from list of Linears."""
+ return wrap_SBasis( SBasis( make_vector_linear(lst) ) )
+
+ def size(self):
+ """Return number of linears SBasis consists of."""
+ return self.thisptr.size()
+
+ def __call__(self, o):
+ """Get point at time value t."""
+ if isinstance(o, Number):
+ return deref(self.thisptr)(float(o))
+ elif isinstance(self, cy_SBasis):
+ return wrap_SBasis(deref(self.thisptr)( deref( (<cy_SBasis> o).thisptr ) ))
+
+ def __getitem__(self, unsigned int i):
+ """Get Linear at i th position."""
+ if i>=self.size():
+ raise IndexError
+ else:
+ return wrap_Linear(deref( self.thisptr ) [i])
+
+ def __neg__(self):
+ """Return SBasis with negated values."""
+ return wrap_SBasis( SB_neg(deref(self.thisptr)) )
+
+ #cython doesn't use __rmul__, it switches the arguments instead
+ def __add__(cy_SBasis self, other):
+ """Add number or other SBasis to SBasis."""
+ if isinstance(other, Number):
+ return wrap_SBasis( deref(self.thisptr) + float(other) )
+ elif isinstance(other, cy_SBasis):
+ return wrap_SBasis( deref(self.thisptr) + deref( (<cy_SBasis> other).thisptr ) )
+
+ def __sub__(cy_SBasis self, other):
+ """Substract number or other SBasis from SBasis."""
+ if isinstance(other, Number):
+ return wrap_SBasis( SB_sub_Sd(deref(self.thisptr), float(other) ) )
+ elif isinstance(other, cy_SBasis):
+ return wrap_SBasis( SB_sub_SS(deref(self.thisptr), deref( (<cy_SBasis> other).thisptr ) ) )
+
+ def __mul__(self, other):
+ """Multiply SBasis by number or other SBasis."""
+ if isinstance(other, Number):
+ return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * float(other) )
+ elif isinstance(other, cy_SBasis):
+ if isinstance(self, cy_SBasis):
+ return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * deref( (<cy_SBasis> other).thisptr ) )
+ elif isinstance(self, Number):
+ return wrap_SBasis( float(self) * deref( (<cy_SBasis> other).thisptr ) )
+
+ def __div__(cy_SBasis self, double other):
+ """Divide SBasis by number."""
+ return wrap_SBasis( deref(self.thisptr)/other )
+
+
+ def empty(self):
+ """Test whether SBasis has no linears."""
+ return self.thisptr.empty()
+
+ def back(self):
+ """Return last linear in SBasis."""
+ return wrap_Linear(self.thisptr.back())
+
+ def pop_back(self):
+ """Remove last linear in SBasis."""
+ self.thisptr.pop_back()
+
+ def resize(self, unsigned int n, cy_Linear l = None):
+ """Resize SBasis, optionally filling created slots with linear."""
+ if l is None:
+ self.thisptr.resize(n)
+ else:
+ self.thisptr.resize(n, deref( l.thisptr ))
+
+#~ def reserve(self, unsigned int n):
+#~ self.thisptr.reserve(n)
+
+ def clear(self):
+ """Make SBasis empty."""
+ self.thisptr.clear()
+#~ def insert(self, cy_::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > before, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_begin, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_end):
+#~ self.thisptr.insert(deref( before.thisptr ), deref( src_begin.thisptr ), deref( src_end.thisptr ))
+
+ def at(self, unsigned int i):
+ """Equivalent to self[i]."""
+ return wrap_Linear(self.thisptr.at(i))
+
+ def __richcmp__(cy_SBasis self, cy_SBasis B, int op):
+ if op == 2:
+ return deref( self.thisptr ) == deref( B.thisptr )
+ elif op == 3:
+ return deref( self.thisptr ) != deref( B.thisptr )
+
+ def is_zero(self, double eps = EPSILON):
+ """Test whether linear is zero within given tolerance."""
+ return self.thisptr.isZero(eps)
+
+ def is_constant(self, double eps = EPSILON):
+ """Test whether linear is constant within given tolerance."""
+ return self.thisptr.isConstant(eps)
+
+ def is_finite(self):
+ """Test whether linear is finite."""
+ return self.thisptr.isFinite()
+
+ def at0(self):
+ """Equivalent to self(0)."""
+ return self.thisptr.at0()
+
+ def at1(self):
+ """Equivalent to self(1)."""
+ return self.thisptr.at1()
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return self.thisptr.degreesOfFreedom()
+
+ def value_at(self, double t):
+ """Equivalent to self(t)[d]."""
+ return self.thisptr.valueAt(t)
+
+ def value_and_derivatives(self, double t, unsigned int n):
+ """Return value and at least n derivatives at time t."""
+ return wrap_vector_double (self.thisptr.valueAndDerivatives(t, n))
+
+ def to_SBasis(self):
+ """Just return self."""
+ return wrap_SBasis(self.thisptr.toSBasis())
+
+ def tail_error(self, unsigned int tail):
+ """Return largest error after truncating linears from tail."""
+ return self.thisptr.tailError(tail)
+
+ def normalize(self):
+ """Remove zero linears at the end."""
+ self.thisptr.normalize()
+
+ def truncate(self, unsigned int k):
+ """Truncate SBasis to have k elements."""
+ self.thisptr.truncate(k)
+
+ @classmethod
+ def sqrt(cls, cy_SBasis a, int k):
+ """Return square root of SBasis.
+
+ Use k to specify degree of resulting SBasis.
+ """
+ return wrap_SBasis(sqrt(deref( a.thisptr ), k))
+
+ @classmethod
+ def inverse(cls, cy_SBasis a, int k):
+ """Return inverse function to SBasis.
+
+ Passed SBasis must be function [1-1] -> [1-1] bijection.
+ """
+ return wrap_SBasis(inverse(deref( a.thisptr ), k))
+
+ @classmethod
+ def valuation(cls, cy_SBasis a, double tol = 0):
+ """Return the degree of the first non zero coefficient."""
+ return valuation(deref( a.thisptr ), tol)
+
+ #call with level_set(SBasis(1, 5), 2, a = 0.2, b = 0.4, tol = 0.02)
+ @classmethod
+ def level_set(cls, cy_SBasis f, level, a = 0, b = 1, tol = 1e-5, vtol = 1e-5):
+ """Return intervals where SBasis is in specified level.
+
+ Specify range and tolerance in other arguments.
+ """
+ if isinstance(level, cy_Interval):
+ return wrap_vector_interval(level_set(deref( f.thisptr ), deref( (<cy_Interval> level).thisptr ), a, b, tol)) #a, b, tol
+ else:
+ return wrap_vector_interval(level_set(deref( f.thisptr ), float(level), vtol, a, b, tol)) #vtol, a, b, tol
+
+ @classmethod
+ def shift(cls, cy_SBasis a, int sh):
+ """Multiply by x**sh."""
+ return wrap_SBasis(shift(deref( a.thisptr ), sh))
+
+ @classmethod
+ def compose(cls, cy_SBasis a, cy_SBasis b, k = None):
+ """Compose two SBasis.
+
+ Specify order of resulting SBasis by parameter k.
+ """
+ if k is None:
+ return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr )))
+ else:
+ return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr ), int(k)))
+
+ @classmethod
+ def roots(cls, cy_SBasis s, cy_Interval inside = None):
+ """Return time values where self equals 0.
+
+ inside intervals specifies subset of domain.
+ """
+ if inside is None:
+ return wrap_vector_double(roots(deref( s.thisptr )))
+ else:
+ return wrap_vector_double(roots(deref( s.thisptr ), deref( inside.thisptr )))
+
+ @classmethod
+ def multi_roots(cls, cy_SBasis f, levels, double htol = 1e-7, double vtol = 1e-7, double a = 0, double b = 1):
+ """Return lists of roots for different levels."""
+ cdef vector[double] l = make_vector_double(levels)
+ cdef vector[ vector[double] ] r = multi_roots(deref( f.thisptr ), l, htol, vtol, a, b)
+ lst = []
+ for i in range(r.size()):
+ lst.append( wrap_vector_double(r[i]) )
+ return lst
+
+ @classmethod
+ def multiply_add(cls, cy_SBasis a, cy_SBasis b, cy_SBasis c):
+ """Return a*b+c."""
+ return wrap_SBasis(multiply_add(deref( a.thisptr ), deref( b.thisptr ), deref( c.thisptr )))
+
+ @classmethod
+ def divide(cls, cy_SBasis a, cy_SBasis b, int k):
+ """Divide two SBasis functions.
+
+ Use k to specify degree of resulting SBasis.
+ """
+ return wrap_SBasis(divide(deref( a.thisptr ), deref( b.thisptr ), k))
+
+ @classmethod
+ def compose_inverse(cls, cy_SBasis f, cy_SBasis g, unsigned int order, double tol):
+ """Compose f with g's inverse.
+
+ Requires g to be bijection g: [0, 1] -> [0, 1]
+ """
+ return wrap_SBasis(compose_inverse(deref( f.thisptr ), deref( g.thisptr ), order, tol))
+
+ @classmethod
+ def multiply(cls, cy_SBasis a, cy_SBasis b):
+ """Multiply two SBasis functions."""
+ return wrap_SBasis(multiply(deref( (<cy_SBasis> a).thisptr ), deref( (<cy_SBasis> b).thisptr )))
+
+ @classmethod
+ def derivative(cls, cy_SBasis a):
+ """Return derivative os SBasis."""
+ return wrap_SBasis(derivative(deref( (<cy_SBasis> a).thisptr )))
+
+ @classmethod
+ def integral(cls, a):
+ """Return integral of SBasis."""
+ return wrap_SBasis(integral(deref( (<cy_SBasis> a).thisptr )))
+
+ @classmethod
+ def portion(cls, cy_SBasis a, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of SBasis, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_SBasis( portion( deref( a.thisptr ), fr, to ) )
+ else:
+ return wrap_SBasis( portion( deref( a.thisptr ), deref( interval.thisptr ) ) )
+
+ @classmethod
+ def bounds_fast(cls, cy_SBasis a, int order = 0):
+ """Return bounding interval
+
+ This method is fast, but does not guarantee to give smallest
+ interval.
+ """
+ return wrap_OptInterval(bounds_fast(deref( a.thisptr ), order))
+
+ @classmethod
+ def bounds_exact(cls, cy_SBasis a):
+ """Return exact bounding interval
+
+ This may take a while.
+ """
+ return wrap_OptInterval(bounds_exact(deref( a.thisptr )))
+
+ @classmethod
+ def bounds_local(cls, cy_SBasis a, cy_OptInterval t, int order = 0):
+ """Return bounding interval to the portion of SBasis."""
+ return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr ), order))
+
+#~ def cy_level_sets(cy_SBasis f, vector[Interval] levels, double a, double b, double tol):
+#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, tol))
+#~ def cy_level_sets(cy_SBasis f, vector[vector] levels, double a, double b, double vtol, double tol):
+#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, vtol, tol))
+
+def cy_reverse(a):
+ if isinstance(a, cy_Linear):
+ return wrap_Linear( reverse(deref( (<cy_Linear> a).thisptr )))
+ elif isinstance(a, cy_SBasis):
+ return wrap_SBasis( reverse(deref( (<cy_SBasis> a).thisptr )))
+ elif isinstance(a, cy_Bezier):
+ return wrap_Bezier( reverse(deref( (<cy_Bezier> a).thisptr )))
+
+#already implemented
+#~ def cy_truncate(cy_SBasis a, unsigned int terms):
+#~ return wrap_SBasis(truncate(deref( a.thisptr ), terms))
+
+cdef cy_SBasis wrap_SBasis(SBasis p):
+ cdef SBasis * retp = new SBasis()
+ retp[0] = p
+ cdef cy_SBasis r = cy_SBasis.__new__(cy_SBasis, 0, 0)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_SBasisCurve:
+
+ """Curve mapping two SBasis functions to point (s1(t), s2(t)).
+
+ Corresponds to SBasisCurve in 2geom.
+ """
+
+ cdef SBasisCurve* thisptr
+
+#~ def __init__(self, cy_Curve other):
+#~ self.thisptr = self.thisptr.SBasisCurve(deref( other.thisptr ))
+
+ def __cinit__(self, cy_SBasis s1, cy_SBasis s2):
+ """Create new SBasisCurve from two SBasis functions."""
+ self.thisptr = new SBasisCurve( D2[SBasis](
+ deref( s1.thisptr ),
+ deref( s2.thisptr ) ) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __call__(self, double t):
+ """Get point at time value t."""
+ return wrap_Point(deref(self.thisptr)(t))
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.duplicate()) )
+
+ def initial_point(self):
+ """Get self(0)."""
+ return wrap_Point(self.thisptr.initialPoint())
+
+ def final_point(self):
+ """Get self(1)."""
+ return wrap_Point(self.thisptr.finalPoint())
+
+ def is_degenerate(self):
+ """Curve is degenerate if it's length is zero."""
+ return self.thisptr.isDegenerate()
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+ def point_and_derivatives(self, Coord t, unsigned int n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n))
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return self.thisptr.valueAt(t, d)
+
+ def set_initial(self, cy_Point v):
+ """Set initial point of curve."""
+ self.thisptr.setInitial(deref( v.thisptr ))
+
+ def set_final(self, cy_Point v):
+ """Set final point of curve."""
+ self.thisptr.setFinal(deref( v.thisptr ))
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect(self.thisptr.boundsFast())
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect(self.thisptr.boundsExact())
+
+ def bounds_local(self, cy_OptInterval i, unsigned int deg):
+ """Return bounding rectangle to portion of curve."""
+ return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg))
+
+ def roots(self, Coord v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double( self.thisptr.roots(v, d) )
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return self.thisptr.nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) )
+
+ def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return all values of t that |self(t) - point| is minimized."""
+ if interval is None:
+ return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to))
+ else:
+ return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ),
+ deref( interval.thisptr ) ))
+
+ def length(self, Coord tolerance = 0.01):
+ """Return length of curve, within give tolerance."""
+ return self.thisptr.length(tolerance)
+
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_SBasisCurve( <SBasisCurve>
+ deref( (<Curve *> self.thisptr).portion( deref( interval.thisptr ))) )
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.transformed( at )))
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_SBasisCurve( <SBasisCurve> deref( (<Curve *> self.thisptr).reverse() ) )
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.derivative()) )
+
+
+ def winding(self, cy_Point p):
+ """Return winding number around specified point."""
+ return (<Curve *> self.thisptr).winding(deref(p.thisptr))
+
+ def unit_tangent_at(self, Coord t, int n = 3):
+ """Return tangent at self(t).
+
+ Parameter n specifies how many derivatives to take into account."""
+ return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n))
+
+ def to_SBasis(self):
+ """Return tuple containing it's SBasis functions."""
+ return wrap_D2_SBasis(self.thisptr.toSBasis())
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return self.thisptr.degreesOfFreedom()
+
+cdef object wrap_D2_SBasis(D2[SBasis] p):
+ return ( wrap_SBasis(p[0]), wrap_SBasis(p[1]) )
+
+cdef cy_SBasisCurve wrap_SBasisCurve(SBasisCurve p):
+ cdef SBasisCurve * retp = new SBasisCurve(D2[SBasis]( SBasis(), SBasis() ))
+ retp[0] = p
+ cdef cy_SBasisCurve r = cy_SBasisCurve.__new__(cy_SBasisCurve, cy_SBasis(), cy_SBasis())
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Bezier:
+
+ """Bezier polynomial.
+
+ Corresponds to Bezier class in 2geom.
+ """
+
+ cdef Bezier* thisptr
+
+ def __cinit__(self, *args):
+ """Create Bezier polynomial specifying it's coeffincients
+
+ This constructor takes up to four coefficients.
+ """
+ if len(args) == 0:
+ #new Bezier() causes segfault
+ self.thisptr = new Bezier(0)
+ elif len(args) == 1:
+ self.thisptr = new Bezier( float(args[0]) )
+ elif len(args) == 2:
+ self.thisptr = new Bezier( float(args[0]), float(args[1]) )
+ elif len(args) == 3:
+ self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]) )
+ elif len(args) == 4:
+ self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]), float(args[3]) )
+ else:
+ raise ValueError("Passed list has too many points")
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __call__(self, double t):
+ """Get point at time value t."""
+ return deref( self.thisptr ) (t)
+
+
+ def __getitem__(self, unsigned int ix):
+ """Get coefficient by accessing list."""
+ if ix >= self.size():
+ raise IndexError
+ return deref( self.thisptr ) [ix]
+
+ def order(self):
+ """Return order of Bezier."""
+ return self.thisptr.order()
+
+ def size(self):
+ """Return number of coefficients."""
+ return self.thisptr.size()
+
+ def __mul__( cy_Bezier self, double v):
+ """Multiply Bezier by number."""
+ return wrap_Bezier(deref( self.thisptr ) * v)
+
+ def __add__( cy_Bezier self, double v):
+ """Add number to Bezier."""
+ return wrap_Bezier(deref( self.thisptr ) + v)
+
+ def __sub__( cy_Bezier self, double v):
+ """Substract number from Bezier."""
+ return wrap_Bezier(deref( self.thisptr ) - v)
+
+ def __div__( cy_Bezier self, double v):
+ """Divide Bezier number."""
+ return wrap_Bezier(deref( self.thisptr ) / v)
+
+
+ def resize(self, unsigned int n, Coord v):
+ """Change order of Bezier."""
+ self.thisptr.resize(n, v)
+
+ def clear(self):
+ """Create empty Bezier."""
+ self.thisptr.clear()
+
+ def degree(self):
+ """Return degree of Bezier polynomial."""
+ return self.thisptr.degree()
+
+ def is_zero(self, double eps = EPSILON):
+ """Test whether linear is zero within given tolerance."""
+ return self.thisptr.isZero(eps)
+
+ def is_constant(self, double eps = EPSILON):
+ """Test whether linear is constant within given tolerance."""
+ return self.thisptr.isConstant(eps)
+
+ def is_finite(self):
+ """Test whether linear is finite."""
+ return self.thisptr.isFinite()
+
+ def at0(self):
+ """Equivalent to self(0)."""
+ return self.thisptr.at0()
+
+ def at1(self):
+ """Equivalent to self(1)."""
+ return self.thisptr.at1()
+
+ def value_at(self, double t):
+ """Equivalent to self(t)."""
+ return self.thisptr.valueAt(t)
+
+ def to_SBasis(self):
+ """Convert to SBasis."""
+ return wrap_SBasis(self.thisptr.toSBasis())
+
+ def set_point(self, unsigned int ix, double val):
+ """Set self[ix] to val."""
+ self.thisptr.setPoint(ix, val)
+
+ def value_and_derivatives(self, Coord t, unsigned int n_derivs):
+ """Return value and at least n derivatives at time t."""
+ return wrap_vector_double(self.thisptr.valueAndDerivatives(t, n_derivs))
+
+ def subdivide(self, Coord t):
+ """Get two beziers, from 0 to t and from t to 1."""
+ cdef pair[Bezier, Bezier] p = self.thisptr.subdivide(t)
+ return ( wrap_Bezier(p.first), wrap_Bezier(p.second) )
+
+ def roots(self, cy_Interval ivl = None):
+ """Find time values where self(t)[d] == v."""
+ if ivl is None:
+ return wrap_vector_double(self.thisptr.roots())
+ else:
+ return wrap_vector_double(self.thisptr.roots(deref( ivl.thisptr )))
+
+ def forward_difference(self, unsigned int k):
+#TODO: ask someone what this function does.
+#~ """Compute forward difference of degree k.
+#~
+#~ First forward difference of B is roughly function B'(t) = B(t+h)-B(t)
+#~ for fixed step h"""
+ return wrap_Bezier(self.thisptr.forward_difference(k))
+
+ def elevate_degree(self):
+ """Increase degree of Bezier by 1."""
+ return wrap_Bezier(self.thisptr.elevate_degree())
+
+ def reduce_degree(self):
+ """Decrease degree of Bezier by 1."""
+ return wrap_Bezier(self.thisptr.reduce_degree())
+
+ def elevate_to_degree(self, unsigned int new_degree):
+ """Increase degree of Bezier to new_degree."""
+ return wrap_Bezier(self.thisptr.elevate_to_degree(new_degree))
+
+ def deflate(self):
+#TODO: ask someone what this function does.
+ #It looks like integral(self)*self.size()
+ return wrap_Bezier(self.thisptr.deflate())
+
+ @classmethod
+ def bezier_points(cls, cy_Bezier a, cy_Bezier b):
+ """Return control points of BezierCurve consisting of two beziers.
+
+ Passed bezier must have same degree."""
+ return wrap_vector_point(bezier_points( D2[Bezier]( deref(a.thisptr), deref(b.thisptr) ) ))
+
+ @classmethod
+ def multiply(cls, cy_Bezier a, cy_Bezier b):
+ """Multiply two Bezier functions."""
+ return wrap_Bezier(multiply(deref( (<cy_Bezier> a).thisptr ),
+ deref( (<cy_Bezier> b).thisptr )))
+
+ @classmethod
+ def portion(cls, cy_Bezier a, Coord fr=0, Coord to=1, interval=None):
+ """Return portion of bezier, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_Bezier(portion(deref( a.thisptr ), fr, to))
+ else:
+ return wrap_Bezier(portion(deref( a.thisptr ), float(interval.min()),
+ float(interval.max()) ))
+
+ @classmethod
+ def derivative(cls, cy_Bezier a):
+ """Return derivative of a bezier."""
+ return wrap_Bezier(derivative(deref( a.thisptr )))
+
+ @classmethod
+ def integral(cls, cy_Bezier a):
+ """Return derivative of a bezier."""
+ return wrap_Bezier(integral(deref( a.thisptr )))
+
+ @classmethod
+ def bounds_fast(cls, cy_Bezier a):
+ """Return bounding interval
+
+ This method is fast, but does not guarantee to give smallest
+ interval.
+ """
+ return wrap_OptInterval(bounds_fast(deref( a.thisptr )))
+
+ @classmethod
+ def bounds_exact(cls, cy_Bezier a):
+ """Return exact bounding interval
+
+ This may take a while.
+ """
+ return wrap_OptInterval(bounds_exact(deref( a.thisptr )))
+
+ @classmethod
+ def bounds_local(cls, cy_Bezier a, cy_OptInterval t):
+ """Return bounding interval to the portion of bezier."""
+ return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr )))
+
+#This is the same as bz.to_SBasis()
+#~ def cy_bezier_to_sbasis(cy_SBasis sb, cy_Bezier bz):
+#~ bezier_to_sbasis(deref( sb.thisptr ), deref( bz.thisptr ))
+
+#These are look like internal functions.
+#~ def cy_casteljau_subdivision(Coord t, cy_Coord * v, cy_Coord * left, cy_Coord * right, unsigned int order):
+#~ return subdivideArr(t, v.thisptr, left.thisptr, right.thisptr, order)
+#~ def cy_bernsteinValueAt(double t, cy_double * c_, unsigned int n):
+#~ return bernsteinValueAt(t, c_.thisptr, n)
+
+cdef cy_Bezier wrap_Bezier(Bezier p):
+ cdef Bezier * retp = new Bezier()
+ retp[0] = p
+ cdef cy_Bezier r = cy_Bezier.__new__(cy_Bezier)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_BezierCurve:
+
+ """Bezier curve, consisting of two Bezier functions.
+
+ Corresponds to BezierCurve class in 2geom.
+ """
+
+ #This flag is due to this class children
+ def __cinit__(self, *args, **kwargs):
+ """Don't use this constructor, use create instead."""
+ pass
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __call__(self, Coord t):
+ """Get point at time value t."""
+ return wrap_Point(deref( <Curve *> self.thisptr )(t))
+
+ def __getitem__(self, unsigned int ix):
+ """Get control point by list access."""
+ return wrap_Point(deref( self.thisptr ) [ix])
+
+ @classmethod
+ def create(cls, pts):
+ """Create new BezierCurve from control points."""
+ return wrap_BezierCurve( deref( create( make_vector_point(pts) ) ) )
+
+ def order(self):
+ """Get order of curve."""
+ return self.thisptr.order()
+
+ def control_points(self):
+ """Get control points."""
+ return wrap_vector_point(self.thisptr.controlPoints())
+
+ def set_point(self, unsigned int ix, cy_Point v):
+ """Set control point."""
+ self.thisptr.setPoint(ix, deref( v.thisptr ))
+
+ def set_points(self, ps):
+ """Set control points"""
+ self.thisptr.setPoints( make_vector_point(ps) )
+
+ def initial_point(self):
+ """Get self(0)."""
+ return wrap_Point(self.thisptr.initialPoint())
+
+ def final_point(self):
+ """Get self(1)."""
+ return wrap_Point(self.thisptr.finalPoint())
+
+ def is_degenerate(self):
+ """Curve is degenerate if it's length is zero."""
+ return self.thisptr.isDegenerate()
+
+ def set_initial(self, cy_Point v):
+ """Set initial point of curve."""
+ self.thisptr.setInitial(deref( v.thisptr ))
+
+ def set_final(self, cy_Point v):
+ """Set final point of curve."""
+ self.thisptr.setFinal(deref( v.thisptr ))
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect(self.thisptr.boundsFast())
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect(self.thisptr.boundsExact())
+
+ def bounds_local(cy_BezierCurve self, cy_OptInterval i, unsigned int deg):
+ """Return bounding rectangle to portion of curve."""
+ return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg))
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) )
+
+ def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return all values of t that |self(t) - point| is minimized."""
+ if interval is None:
+ return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to))
+ else:
+ return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ),
+ deref( interval.thisptr ) ))
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *>
+ (<Curve *> self.thisptr).portion( fr, to )
+ ) )
+ else:
+ return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *>
+ (<Curve *> self.thisptr).portion(deref( interval.thisptr ))
+ ) )
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.duplicate()))
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.reverse()))
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.transformed( at )))
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.derivative()))
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return self.thisptr.degreesOfFreedom()
+
+ def roots(self, Coord v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double(self.thisptr.roots(v, d))
+
+ def length(self, Coord tolerance = 0.01):
+ """Return length of curve, within give tolerance."""
+ return self.thisptr.length(tolerance)
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+ def point_and_derivatives(self, Coord t, unsigned int n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n))
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return self.thisptr.valueAt(t, d)
+
+ def to_SBasis(self):
+ """Convert self to pair of SBasis functions."""
+ return wrap_D2_SBasis(self.thisptr.toSBasis())
+
+ def winding(self, cy_Point p):
+ """Return winding number around specified point."""
+ return (<Curve *> self.thisptr).winding(deref(p.thisptr))
+
+ def unit_tangent_at(self, Coord t, int n = 3):
+ """Return tangent at self(t).
+
+ Parameter n specifies how many derivatives to take into account."""
+ return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n))
+
+cdef cy_BezierCurve wrap_BezierCurve(BezierCurve p):
+ cdef vector[Point] points = make_vector_point([cy_Point(), cy_Point()])
+ cdef BezierCurve * retp = create(p.controlPoints())
+ cdef cy_BezierCurve r = cy_BezierCurve.__new__(cy_BezierCurve, [cy_Point(), cy_Point()])
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_LineSegment(cy_BezierCurve):
+
+ """Bezier curve with fixed order 1.
+
+ This class inherits from BezierCurve.
+
+ Corresponds to LineSegment in 2geom. BezierCurveN is not wrapped.
+ """
+
+ def __cinit__(self, cy_Point p0=None,
+ cy_Point p1=cy_Point()):
+ """Create new LineSegment from it's endpoints."""
+ if p0 is None:
+ self.thisptr = <BezierCurve *> new LineSegment()
+ else:
+ self.thisptr = <BezierCurve *> new LineSegment( deref(p0.thisptr),
+ deref(p1.thisptr))
+
+ @classmethod
+ def from_beziers(cls, cy_Bezier b0, cy_Bezier b1):
+ """Create LineSegment from two linear beziers."""
+ return wrap_LineSegment( LineSegment(deref(b0.thisptr), deref(b1.thisptr)) )
+
+ def subdivide(self, Coord t):
+ """Get two LineSegments, from 0 to t and from t to 1."""
+ cdef pair[LineSegment, LineSegment] p = (<LineSegment *> self.thisptr).subdivide(t)
+ return ( wrap_LineSegment(p.first), wrap_LineSegment(p.second) )
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.duplicate()))
+
+ def portion(self, double fr=0, double to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_LineSegment( deref( <LineSegment *>
+ (<Curve *> self.thisptr).portion( deref( interval.thisptr ))
+ ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.reverse()))
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.transformed( at )))
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative()))
+
+cdef cy_LineSegment wrap_LineSegment(LineSegment p):
+ cdef LineSegment * retp = new LineSegment()
+ retp[0] = p
+ cdef cy_LineSegment r = cy_LineSegment.__new__(cy_LineSegment)
+ r.thisptr = <BezierCurve* > retp
+ return r
+
+
+cdef class cy_QuadraticBezier(cy_BezierCurve):
+
+ """Bezier curve with fixed order 2.
+
+ This class inherits from BezierCurve.
+
+ Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped.
+ """
+
+ def __cinit__(self, cy_Point p0=None,
+ cy_Point p1=cy_Point(),
+ cy_Point p2=cy_Point()):
+ """Create new QuadraticBezier from three control points."""
+ if p0 is None:
+ self.thisptr = <BezierCurve *> new QuadraticBezier()
+ else:
+ self.thisptr = <BezierCurve *> new QuadraticBezier( deref( p0.thisptr ),
+ deref( p1.thisptr ),
+ deref( p2.thisptr ) )
+
+ @classmethod
+ def from_beziers(cls, cy_Bezier b0, cy_Bezier b1):
+ """Create QuadraticBezier from two quadratic bezier functions."""
+ return wrap_QuadraticBezier( QuadraticBezier(deref(b0.thisptr), deref(b1.thisptr)) )
+
+ def subdivide(self, Coord t):
+ """Get two QuadraticBeziers, from 0 to t and from t to 1."""
+ cdef pair[QuadraticBezier, QuadraticBezier] p = (<QuadraticBezier *> self.thisptr).subdivide(t)
+ return ( wrap_QuadraticBezier(p.first), wrap_QuadraticBezier(p.second) )
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.duplicate()))
+
+ def portion(self, double fr=0, double to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *>
+ (<Curve *> self.thisptr).portion( deref( interval.thisptr ))
+ ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.reverse()))
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.transformed( at )))
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative()))
+
+cdef cy_QuadraticBezier wrap_QuadraticBezier(QuadraticBezier p):
+ cdef QuadraticBezier * retp = new QuadraticBezier()
+ retp[0] = p
+ cdef cy_QuadraticBezier r = cy_QuadraticBezier.__new__(cy_QuadraticBezier)
+ r.thisptr = <BezierCurve* > retp
+ return r
+
+cdef class cy_CubicBezier(cy_BezierCurve):
+
+ """Bezier curve with fixed order 2.
+
+ This class inherits from BezierCurve.
+
+ Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped.
+ """
+
+ def __cinit__(self, cy_Point p0=None,
+ cy_Point p1=cy_Point(),
+ cy_Point p2=cy_Point(),
+ cy_Point p3=cy_Point()):
+ """Create new CubicBezier from four control points."""
+ if p0 is None:
+ self.thisptr = <BezierCurve *> new CubicBezier()
+ else:
+ self.thisptr = <BezierCurve *> new CubicBezier( deref( p0.thisptr ),
+ deref( p1.thisptr ),
+ deref( p2.thisptr ),
+ deref( p3.thisptr ) )
+
+ @classmethod
+ def from_beziers(cls, cy_Bezier b0, cy_Bezier b1):
+ """Create CubicBezier from two cubic bezier functions."""
+ return wrap_CubicBezier( CubicBezier(deref(b0.thisptr), deref(b1.thisptr)) )
+
+ def subdivide(self, Coord t):
+ """Get two CubicBeziers, from 0 to t and from t to 1."""
+ cdef pair[CubicBezier, CubicBezier] p = (<CubicBezier *> self.thisptr).subdivide(t)
+ return ( wrap_CubicBezier(p.first), wrap_CubicBezier(p.second) )
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.duplicate()))
+
+ def portion(self, double fr=0, double to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_CubicBezier( deref( <CubicBezier *>
+ (<Curve *> self.thisptr).portion( deref( interval.thisptr ))
+ ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.reverse()))
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.transformed( at )))
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.derivative()))
+
+cdef cy_CubicBezier wrap_CubicBezier(CubicBezier p):
+ cdef CubicBezier * retp = new CubicBezier()
+ retp[0] = p
+ cdef cy_CubicBezier r = cy_CubicBezier.__new__(cy_CubicBezier)
+ r.thisptr = <BezierCurve* > retp
+ return r
+
+#~ cdef class cy_BezierCurveN(cy_BezierCurve):
+
+
+cdef class cy_HLineSegment(cy_LineSegment):
+
+ """Horizontal line segment.
+
+ This class corresponds to HLineSegment in 2geom.
+ """
+
+ def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()):
+ """Create HLineSegment from it's endpoints."""
+ if p0 is None:
+ self.thisptr = <BezierCurve *> new HLineSegment()
+ else:
+ self.thisptr = <BezierCurve *> new HLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) )
+
+ @classmethod
+ def from_points(cls, cy_Point p0, cy_Point p1):
+ """Create HLineSegment from it's endpoints."""
+ return wrap_HLineSegment( HLineSegment( deref(p0.thisptr),
+ deref(p1.thisptr)) )
+
+ @classmethod
+ def from_point_length(cls, cy_Point p, Coord length):
+ return wrap_HLineSegment( HLineSegment( deref( p.thisptr ), length ) )
+
+ def set_initial(self, cy_Point p):
+ """Set initial point of curve."""
+ (<AxisLineSegment_X *> self.thisptr).setInitial( deref(p.thisptr) )
+
+ def set_final(self, cy_Point p):
+ """Set final point of curve."""
+ (<AxisLineSegment_X *> self.thisptr).setFinal( deref(p.thisptr) )
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsFast() )
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsExact() )
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return (<AxisLineSegment_X *> self.thisptr).degreesOfFreedom()
+
+ def roots(self, Coord v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double( (<AxisLineSegment_X *> self.thisptr).roots(v, d) )
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return (<AxisLineSegment_X *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ),
+ deref( ( interval.thisptr ) ) )
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point((<AxisLineSegment_X *> self.thisptr).pointAt(t))
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return (<AxisLineSegment_X *> self.thisptr).valueAt(t, d)
+
+ def point_and_derivatives(self, Coord t, unsigned n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point( (<AxisLineSegment_X *> self.thisptr).pointAndDerivatives(t, n) )
+
+ def get_Y(self):
+ """Get distance of self from y-axis."""
+ return (<HLineSegment *> self.thisptr).getY()
+
+ def set_initial_X(self, Coord x):
+ """Set initial point's X coordinate."""
+ (<HLineSegment *> self.thisptr).setInitialX(x)
+
+ def set_final_X(self, Coord x):
+ """Set final point's X coordinate."""
+ (<HLineSegment *> self.thisptr).setFinalX(x)
+
+ def set_Y(self, Coord y):
+ """Set Y coordinate of points."""
+ (<HLineSegment *> self.thisptr).setY(y)
+
+ def subdivide(self, Coord t):
+ """Return two HLineSegments subdivided at t."""
+ cdef pair[HLineSegment, HLineSegment] p = (<HLineSegment *> self.thisptr).subdivide(t)
+ return (wrap_HLineSegment(p.first), wrap_HLineSegment(p.second))
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.duplicate()) )
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_HLineSegment( deref( <HLineSegment *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_HLineSegment( deref( <HLineSegment *>
+ (<Curve *> self.thisptr).portion( deref( interval.thisptr ) )
+ ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.reverse()) )
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) )
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.derivative()) )
+
+cdef cy_HLineSegment wrap_HLineSegment(HLineSegment p):
+ cdef HLineSegment * retp = new HLineSegment()
+ retp[0] = p
+ cdef cy_HLineSegment r = cy_HLineSegment.__new__(cy_HLineSegment)
+ r.thisptr = <BezierCurve *> retp
+ return r
+
+cdef class cy_VLineSegment(cy_LineSegment):
+
+ """Vertical line segment.
+
+ This class corresponds to HLineSegment in 2geom.
+ """
+
+ def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()):
+ """Create VLineSegment from it's endpoints."""
+ if p0 is None:
+ self.thisptr = <BezierCurve *> new VLineSegment()
+ else:
+ self.thisptr = <BezierCurve *> new VLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) )
+
+ @classmethod
+ def from_points(cls, cy_Point p0, cy_Point p1):
+ """Create VLineSegment from it's endpoints."""
+ return wrap_VLineSegment( VLineSegment( deref(p0.thisptr),
+ deref(p1.thisptr)) )
+
+ @classmethod
+ def from_point_length(cls, cy_Point p, Coord length):
+ return wrap_VLineSegment( VLineSegment( deref( p.thisptr ), length ) )
+
+ def set_initial(self, cy_Point p):
+ """Set initial point of curve."""
+ (<AxisLineSegment_Y *> self.thisptr).setInitial( deref(p.thisptr) )
+
+ def set_final(self, cy_Point p):
+ """Set final point of curve."""
+ (<AxisLineSegment_Y *> self.thisptr).setFinal( deref(p.thisptr) )
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsFast() )
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsExact() )
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return (<AxisLineSegment_Y *> self.thisptr).degreesOfFreedom()
+
+ def roots(self, Coord v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double( (<AxisLineSegment_Y *> self.thisptr).roots(v, d) )
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return (<AxisLineSegment_Y *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ),
+ deref( ( interval.thisptr ) ) )
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point((<AxisLineSegment_Y *> self.thisptr).pointAt(t))
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return (<AxisLineSegment_Y *> self.thisptr).valueAt(t, d)
+
+ def point_and_derivatives(self, Coord t, unsigned n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point( (<AxisLineSegment_Y *> self.thisptr).pointAndDerivatives(t, n) )
+
+ def get_X(self):
+ return (<VLineSegment *> self.thisptr).getX()
+
+ def set_initial_Y(self, Coord y):
+ (<VLineSegment *> self.thisptr).setInitialY(y)
+
+ def set_final_Y(self, Coord y):
+ (<VLineSegment *> self.thisptr).setFinalY(y)
+
+ def set_X(self, Coord x):
+ (<VLineSegment *> self.thisptr).setX(x)
+
+ def subdivide(self, Coord t):
+ """Return two HLineSegments subdivided at t."""
+ cdef pair[VLineSegment, VLineSegment] p = (<VLineSegment *> self.thisptr).subdivide(t)
+ return (wrap_VLineSegment(p.first), wrap_VLineSegment(p.second))
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.duplicate()) )
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_VLineSegment( deref( <VLineSegment *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_VLineSegment( deref( <VLineSegment *>
+ (<Curve *> self.thisptr).portion( deref( interval.thisptr ) )
+ ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.reverse()) )
+
+ def transformed(self, t):
+ """Transform curve by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) )
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.derivative()) )
+
+cdef cy_VLineSegment wrap_VLineSegment(VLineSegment p):
+ cdef VLineSegment * retp = new VLineSegment()
+ retp[0] = p
+ cdef cy_VLineSegment r = cy_VLineSegment.__new__(cy_VLineSegment)
+ r.thisptr = <BezierCurve *> retp
+ return r
+
+cdef class cy_EllipticalArc:
+
+ """Elliptical arc.
+
+ Corresponds to EllipticalArc class in 2geom.
+ """
+
+ def __cinit__(self, cy_Point ip = cy_Point(0, 0),
+ Coord rx = 0,
+ Coord ry = 0,
+ Coord rot_angle = 0,
+ bint large_arc = True,
+ bint sweep = True,
+ cy_Point fp = cy_Point(0, 0)):
+ """Create Elliptical arc from it's major axis and rays."""
+ self.thisptr = new EllipticalArc(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr ))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __call__(self, Coord t):
+ """Get point at time value t."""
+ return wrap_Point( deref(<Curve *> self.thisptr)(t) )
+ #Curve methods
+
+ def length(self, Coord tolerance = 0.01):
+ """Return length of curve, within give tolerance."""
+ return (<Curve *> self.thisptr).length(tolerance)
+
+ #AngleInterval methods
+
+ def initial_angle(self):
+ """Get initial Angle of arc."""
+ return wrap_Angle((<AngleInterval *> self.thisptr).initialAngle())
+
+ def final_angle(self):
+ """Get final Angle of arc."""
+ return wrap_Angle((<AngleInterval *> self.thisptr).finalAngle())
+
+ def angle_at(self, Coord t):
+ """Get Angle from time value."""
+ return wrap_Angle((<AngleInterval *> self.thisptr).angleAt(t))
+
+ def contains(self, cy_Angle a):
+ """Test whether arc contains angle."""
+ return (<AngleInterval *> self.thisptr).contains(deref( a.thisptr ))
+
+ def extent(self):
+ """Get extent of angle interval."""
+ return (<AngleInterval *> self.thisptr).extent()
+
+ def angle_interval(self):
+ """Get underlying angle Interval."""
+ return wrap_Interval(self.thisptr.angleInterval())
+
+ def rotation_angle(self):
+ """Return rotation angle of major axis."""
+ return wrap_Angle(self.thisptr.rotationAngle())
+
+ def ray(self, Dim2 d):
+ """Access rays with X or Y."""
+ return self.thisptr.ray(d)
+
+ def rays(self):
+ """Get rays as a point."""
+ return wrap_Point(self.thisptr.rays())
+
+ def large_arc(self):
+ """Check if large arc flag is set."""
+ return self.thisptr.largeArc()
+
+ def sweep(self):
+ """Check if sweep flag is set."""
+ return self.thisptr.sweep()
+
+ def chord(self):
+ """Return chord of arc."""
+ return wrap_LineSegment(self.thisptr.chord())
+
+ def set(self, cy_Point ip, double rx, double ry, double rot_angle, bint large_arc, bint sweep, cy_Point fp):
+ """Set arc's properties."""
+ self.thisptr.set(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr ))
+
+ def set_extremes(self, cy_Point ip, cy_Point fp):
+ """Set endpoints of arc."""
+ self.thisptr.setExtremes(deref( ip.thisptr ), deref( fp.thisptr ))
+
+ def center(self, coordinate=None):
+ """Return center of ellipse, or it's coordinate."""
+ if coordinate is None:
+ return wrap_Point(self.thisptr.center())
+ else:
+ return self.thisptr.center(int(coordinate))
+
+ def sweep_angle(self):
+ """Equivalent to self.extent()"""
+ return self.thisptr.sweepAngle()
+
+ def contains_angle(self, Coord angle):
+ """Test whether arc contains angle.
+
+ Equivalent to self.contains(Angle(a))
+ """
+ return self.thisptr.containsAngle(angle)
+
+ def point_at_angle(self, Coord a):
+ """Get point of arc at specified angle."""
+ return wrap_Point(self.thisptr.pointAtAngle(a))
+
+ def value_at_angle(self, Coord a, Dim2 d):
+ """Equivalent to self.point_at_angle(a)[d]"""
+ return self.thisptr.valueAtAngle(a, d)
+
+ def unit_circle_transform(self):
+ """Get Affine transform needed to transform unit circle to ellipse."""
+ return wrap_Affine(self.thisptr.unitCircleTransform())
+
+ def is_SVG_compliant(self):
+ """Check whether arc is SVG compliant
+
+ SVG has special specification for degenerated ellipse."""
+ return self.thisptr.isSVGCompliant()
+
+ def subdivide(self, Coord t):
+ """Return two arcs, subdivided at time t."""
+ cdef pair[EllipticalArc, EllipticalArc] r = self.thisptr.subdivide(t)
+ return (wrap_EllipticalArc(r.first), wrap_EllipticalArc(r.second))
+
+ def initial_point(self):
+ """Get self(0)."""
+ return wrap_Point(self.thisptr.initialPoint())
+
+ def final_point(self):
+ """Get self(1)."""
+ return wrap_Point(self.thisptr.finalPoint())
+
+ def duplicate(self):
+ """Duplicate the curve."""
+ return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.duplicate()) )
+
+ def set_initial(self, cy_Point p):
+ """Set initial point of curve."""
+ self.thisptr.setInitial(deref( p.thisptr ))
+
+ def set_final(self, cy_Point p):
+ """Set final point of curve."""
+ self.thisptr.setFinal(deref( p.thisptr ))
+
+ def is_degenerate(self):
+ """Curve is degenerate if its length is zero."""
+ return self.thisptr.isDegenerate()
+
+ def bounds_fast(self):
+ """Return bounding rectangle for curve.
+
+ This method is fast, but does not guarantee to give smallest
+ rectangle.
+ """
+ return wrap_Rect(self.thisptr.boundsFast())
+
+ def bounds_exact(self):
+ """Return exact bounding rectangle for curve.
+
+ This may take a while.
+ """
+ return wrap_Rect(self.thisptr.boundsExact())
+
+ def bounds_local(self, cy_OptInterval i, unsigned int deg):
+ """Return bounding rectangle to portion of curve."""
+ return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg))
+
+ def roots(self, double v, Dim2 d):
+ """Find time values where self(t)[d] == v."""
+ return wrap_vector_double(self.thisptr.roots(v, d))
+
+ def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return such t that |self(t) - point| is minimized."""
+ if interval is None:
+ return self.thisptr.nearestTime(deref( p.thisptr ), fr, to)
+ else:
+ return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ),
+ deref( interval.thisptr ) )
+
+ def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return all values of t that |self(t) - point| is minimized."""
+ if interval is None:
+ return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to))
+ else:
+ return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), deref( interval.thisptr ) ))
+
+ def degrees_of_freedom(self):
+ """Return number of independent parameters needed to specify the curve."""
+ return self.thisptr.degreesOfFreedom()
+
+ def derivative(self):
+ """Return curve's derivative."""
+ return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.derivative()) )
+
+ def transformed(self, cy_Affine m):
+ """Transform curve by affine transform."""
+ return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.transformed(deref( m.thisptr ))) )
+
+ def point_and_derivatives(self, Coord t, unsigned int n):
+ """Return point and at least first n derivatives at point t in list."""
+ return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n))
+
+ def to_SBasis(self):
+ """Convert to pair of SBasis polynomials."""
+ cdef D2[SBasis] r = self.thisptr.toSBasis()
+ return ( wrap_SBasis(r[0]), wrap_SBasis(r[1]) )
+
+ def value_at(self, Coord t, Dim2 d):
+ """Equivalent to self(t)[d]."""
+ return self.thisptr.valueAt(t, d)
+
+ def point_at(self, Coord t):
+ """Equivalent to self(t)."""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve, specified by endpoints or interval."""
+ if interval is None:
+ return wrap_EllipticalArc( deref( <EllipticalArc *> self.thisptr.portion( fr, to ) ) )
+ else:
+ return wrap_EllipticalArc( deref( <EllipticalArc *> (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) ) )
+
+ def reverse(self):
+ """Return curve with reversed time."""
+ return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.reverse()) )
+
+ def winding(self, cy_Point p):
+ """Return winding number around specified point."""
+ return (<Curve *> self.thisptr).winding(deref(p.thisptr))
+
+ def unit_tangent_at(self, Coord t, int n = 3):
+ """Return tangent at self(t).
+
+ Parameter n specifies how many derivatives to take into account."""
+ return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n))
+
+cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p):
+ cdef EllipticalArc * retp = new EllipticalArc()
+ retp[0] = p
+ cdef cy_EllipticalArc r = cy_EllipticalArc.__new__(cy_EllipticalArc)
+ r.thisptr = retp
+ return r
+
+#TODO move somewhere else
+
+cdef object wrap_vector_interval(vector[Interval] v):
+ r = []
+ cdef unsigned int i
+ for i in range(v.size()):
+ r.append( wrap_Interval(v[i]))
+ return r
+
+
+cdef bint is_Curve(object c):
+ return any([
+ isinstance(c, cy_Curve),
+ isinstance(c, cy_SBasisCurve),
+ isinstance(c, cy_BezierCurve),
+ isinstance(c, cy_EllipticalArc)])
+
+cdef Curve * get_Curve_p(object c):
+ if isinstance(c, cy_Curve):
+ return (<cy_Curve> c).thisptr
+ elif isinstance(c, cy_SBasisCurve):
+ return <Curve *> (<cy_SBasisCurve> c).thisptr
+ elif isinstance(c, cy_BezierCurve):
+ return <Curve *> (<cy_BezierCurve> c).thisptr
+ elif isinstance(c, cy_EllipticalArc):
+ return <Curve *> (<cy_EllipticalArc> c).thisptr
+ return NULL
+
diff --git a/src/cython/_cy_path.pxd b/src/cython/_cy_path.pxd
new file mode 100644
index 0000000..2d41581
--- /dev/null
+++ b/src/cython/_cy_path.pxd
@@ -0,0 +1,124 @@
+from _common_decl cimport *
+
+from libcpp.vector cimport vector
+from libcpp.pair cimport pair
+
+from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect
+from _cy_affine cimport Affine
+from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p
+from _cy_curves cimport SBasis, cy_SBasis
+
+from _cy_primitives cimport Point, cy_Point, wrap_Point
+
+
+cdef extern from "2geom/d2.h" namespace "Geom":
+ cdef cppclass D2[T]:
+ D2()
+ D2(T &, T &)
+ T& operator[](unsigned i)
+
+#~ ctypedef int BaseIteratorConst "BaseIterator<ConstIterator, Path const>"
+
+cdef extern from "2geom/path.h" namespace "Geom::PathInternal":
+ cdef cppclass BaseIterator[C, P]:
+ Curve & c_item "operator*" ()
+ C & c_next "operator++" ()
+ C & c_next "operator++" (int)
+ bint operator==(BaseIterator[C, P])
+
+
+ cdef cppclass ConstIterator:
+ ConstIterator()
+ cdef cppclass Iterator:
+ Iterator()
+ ConstIterator & operator()
+
+ cdef cppclass BaseIteratorConst "Geom::PathInternal::BaseIterator<Geom::PathInternal::ConstIterator, Path const>":
+#~ Curve & c_item "operator*" ()
+ ConstIterator & c_next "operator++" ()
+ ConstIterator & c_next "operator++" (int)
+#~ bint operator==(BaseIterator[C, P])
+
+
+cdef extern from "2geom/path.h" namespace "Geom::Path":
+ cdef enum Stitching:
+ c_NO_STITCHING "Path::NO_STITCHING" = 0,
+ c_STITCH_DISCONTINUOUS "Path::STITCH_DISCONTINUOUS"
+
+cdef extern from "2geom/path.h" namespace "Geom":
+ cdef cppclass Path:
+ Path(Path &)
+ Path(Point)
+ Path(ConstIterator &, ConstIterator &, bint)
+ void swap(Path &)
+ Curve & operator[](unsigned int)
+#~ Curve & at_index(unsigned int)
+#~ ::boost::shared_ptr<Geom::Curve const> get_ref_at_index(unsigned int)
+ Curve & front()
+ Curve & back()
+ Curve & back_open()
+ Curve & back_closed()
+ Curve & back_default()
+ ConstIterator begin_const "begin" ()
+ ConstIterator end()
+ Iterator begin()
+#~ Iterator end()
+ ConstIterator end_open()
+ ConstIterator end_closed()
+ ConstIterator end_default()
+ size_t size_open()
+ size_t size_closed()
+ size_t size_default()
+ size_t size()
+ size_t max_size()
+ bint empty()
+ bint closed()
+ void close(bint)
+ OptRect boundsFast()
+ OptRect boundsExact()
+#~ Piecewise<Geom::D2<Geom::SBasis> > toPwSb()
+ bint operator==(Path &)
+ bint operator!=(Path &)
+ Path operator*(Affine &)
+#~ Path & operator*=(Affine &)
+ Point pointAt(double)
+ double valueAt(double, Dim2)
+ Point operator()(double) except +
+ vector[double] roots(double, Dim2)
+ vector[double] allNearestTimes(Point &, double, double)
+ vector[double] allNearestTimes(Point &)
+ vector[double] nearestTimePerCurve(Point &)
+ double nearestTime(Point &, double, double, double *)
+ double nearestTime(Point &, double *)
+ void appendPortionTo(Path &, double, double)
+ Path portion(double, double)
+ Path portion(Interval)
+ Path reversed()
+ void insert(Iterator &, Curve &, Stitching) except +
+ void insert(Iterator &, ConstIterator &, ConstIterator &, Stitching)
+ void clear()
+ void erase(Iterator &, Stitching)
+ void erase(Iterator &, Iterator &, Stitching)
+ void erase_last()
+ void replace(Iterator &, Curve &, Stitching)
+ void replace(Iterator &, Iterator &, Curve &, Stitching)
+ void replace(Iterator &, ConstIterator &, ConstIterator &, Stitching)
+ void replace(Iterator &, Iterator &, ConstIterator &, ConstIterator &, Stitching)
+ void start(Point)
+ Point initialPoint()
+ Point finalPoint()
+ void setInitial(Point &)
+ void setFinal(Point &)
+ void append(Curve &, Stitching)
+ void append(D2[SBasis] &, Stitching)
+ void append(Path &, Stitching)
+ void stitchTo(Point &)
+
+cdef class cy_Path:
+#~ NO_STITCHING = c_NO_STITCHING
+#~ STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS
+ cdef Path* thisptr
+ cdef ConstIterator _const_iterator_at_index(self, int i)
+ cdef Iterator _iterator_at_index(self, int i)
+
+cdef cy_Path wrap_Path(Path p)
diff --git a/src/cython/_cy_path.pyx b/src/cython/_cy_path.pyx
new file mode 100644
index 0000000..9f3f88e
--- /dev/null
+++ b/src/cython/_cy_path.pyx
@@ -0,0 +1,457 @@
+from cython.operator cimport dereference as deref
+from numbers import Number
+from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect
+from _cy_rectangle cimport cy_Interval, wrap_Interval
+
+from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform
+
+from _cy_curves cimport is_Curve, get_Curve_p
+
+
+cdef class cy_Iterator:
+ cdef Iterator* thisptr
+ cdef ConstIterator* startptr
+ cdef ConstIterator* endptr
+
+ def __cinit__(self):
+ self.thisptr = new Iterator()
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if deref(<BaseIterator[ConstIterator, Path] *> self.endptr) == deref(<BaseIterator[ConstIterator, Path] *> self.thisptr):
+ raise StopIteration
+ cdef Curve * r = <Curve *> &(<BaseIterator[Iterator, Path] * > self.thisptr).c_item()
+ (<BaseIterator[Iterator, Path] *> self.thisptr).c_next()
+ return wrap_Curve_p ( r )
+
+
+
+
+cdef cy_Iterator wrap_Iterator(Iterator i, ConstIterator starti, ConstIterator endi):
+ cdef Iterator * retp = new Iterator()
+ retp[0] = i
+ cdef cy_Iterator r = cy_Iterator.__new__(cy_Iterator)
+ r.thisptr = retp
+
+ cdef ConstIterator * endp = new ConstIterator()
+ endp[0] = endi
+ r.endptr = endp
+
+ cdef ConstIterator * startp = new ConstIterator()
+ startp[0] = starti
+ r.startptr = startp
+
+ return r
+
+cdef class cy_Path:
+
+ """Path is a ordered sequence of curves.
+
+ You can iterate this class, accessing curves one at time, or access
+ them using indices.
+
+ Two constants, NO_STITCHING and STITCH_DISCONTINUOUS are members of
+ Path namespace, and are used to specify type of stitching, if
+ necessary.
+
+ Path is either open or closed, but in both cases carries closing
+ segment, connecting last and first point.
+
+ Corresponds to Path class in 2geom.
+ """
+
+ NO_STITCHING = c_NO_STITCHING
+ STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS
+
+ def __cinit__(self, cy_Point p=cy_Point()):
+ """Create Path containing only one point."""
+ self.thisptr = new Path(deref( p.thisptr ))
+
+ @classmethod
+ def fromList(cls, l, Stitching stitching=NO_STITCHING, closed=False):
+ """Create path from list of curves.
+
+ Specify stithing and closed flag in additional arguments.
+ """
+ p = cy_Path()
+ for curve in l:
+ p.append_curve(curve, stitching)
+ p.close(closed)
+ return p
+
+ @classmethod
+ def fromPath(cls, cy_Path p, fr=-1, to = 1, bint closed=False):
+ """Create path copying another's path curves.
+
+ Either copy all curves, or ones from
+ fr - index of first curve copied
+ to
+ to - index of first curve not copied.
+
+ Also takes closed flag.
+ """
+ if fr == -1:
+ return wrap_Path( Path(p.thisptr.begin_const(), p.thisptr.end_default(), closed) )
+ else:
+ return wrap_Path( Path(p._const_iterator_at_index(fr), p._const_iterator_at_index(to), closed) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ def __getitem__(self, unsigned int i):
+ """Get curve with index i."""
+ cdef Curve * r = <Curve *> & deref(self.thisptr)[i]
+ return wrap_Curve_p(r)
+
+ def __call__(self, double t):
+ """Evaluate path at time t.
+
+ Note: t can be greater than 1 here, it can go to self.size()
+ """
+ return wrap_Point(deref( self.thisptr ) (t) )
+
+ def __richcmp__(cy_Path self, cy_Path other, int op):
+ if op == 2:
+ return deref( self.thisptr ) == deref( other.thisptr )
+ elif op == 3:
+ return deref( self.thisptr ) != deref( other.thisptr )
+
+ def __mul__(cy_Path self, m):
+ """Transform path with a transform."""
+ cdef Affine at
+ if is_transform(m):
+ at = get_Affine(m)
+ return wrap_Path( deref(self.thisptr) * at )
+
+ #This is not the fastest way, but it's pretty nice from python's perspective
+ #Anyway, I would expect that performance hit is minimal, since i is generally really small
+ cdef ConstIterator _const_iterator_at_index(self, int i):
+ cdef ConstIterator ci = self.thisptr.begin_const()
+ cdef ConstIterator * cip = &ci
+ for ii in range(i):
+ (<BaseIteratorConst *> cip).c_next()
+ return ci
+
+ cdef Iterator _iterator_at_index(self, int i):
+ cdef Iterator ci = self.thisptr.begin()
+ cdef Iterator * cip = &ci
+ for ii in range(i):
+ (<BaseIterator[Iterator, Path] *> cip).c_next()
+ return ci
+
+ def swap(self, cy_Path other):
+ """Swap curves with another path."""
+ self.thisptr.swap(deref( other.thisptr ))
+
+#This is the same as __getitem__
+#~ def at_index(self, unsigned int i):
+#~ return wrap_Curve(self.thisptr.at_index(i))
+
+ def front(self):
+ """Get first curve."""
+ #this is AFAIK the shortest way to do this
+ cdef Curve * r = <Curve *> &self.thisptr.front()
+ return wrap_Curve_p(r)
+
+ def back(self):
+ """Same as back_open."""
+ cdef Curve * r = <Curve *> &self.thisptr.back()
+ return wrap_Curve_p(r)
+
+ def back_open(self):
+ """Get last curve, treating self as open."""
+ cdef Curve * r = <Curve *> &self.thisptr.back_open()
+ return wrap_Curve_p(r)
+
+ def back_closed(self):
+ """Get last curve, treating self as closed."""
+ cdef Curve * r = <Curve *> &self.thisptr.back_closed()
+ return wrap_Curve_p(r)
+
+ def back_default(self):
+ """Get last curve."""
+ cdef Curve * r = <Curve *> &self.thisptr.back_default()
+ return wrap_Curve_p(r)
+
+ def curves(self):
+ """Same as curves_open"""
+ return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end())
+
+ def curves_open(self):
+ """Return all curves as iterable, treating self as open."""
+ return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_open())
+
+ def curves_closed(self):
+ """Return all curves as iterable, treating self as closed."""
+ return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_closed())
+
+ def curves_default(self):
+ """Return all curves as iterable."""
+ return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_default())
+
+ def __iter__(self):
+ return self.curves_default()
+
+ def size_open(self):
+ """Get number of curves, treating self as open."""
+ return self.thisptr.size_open()
+
+ def size_closed(self):
+ """Get number of curves, treating self as closed."""
+ return self.thisptr.size_closed()
+
+ def size_default(self):
+ """Get number of curves."""
+ return self.thisptr.size_default()
+
+ def size(self):
+ """Same as size_open."""
+ return self.thisptr.size()
+
+#Does the same as size_open, which doesn't correspond with name.
+#~ def max_size(self):
+#~ return self.thisptr.max_size()
+
+ def empty(self):
+ """Test whether path contains no curves."""
+ return self.thisptr.empty()
+
+ def closed(self):
+ """Return state of closed flag."""
+ return self.thisptr.closed()
+
+ def close(self, bint closed):
+ """Set closed flag."""
+ self.thisptr.close(closed)
+
+ def bounds_fast(self):
+ """Return fast bounding rectangle for path.
+
+ It's not guaranteed to give the tighest bound.
+ """
+ return wrap_OptRect(self.thisptr.boundsFast())
+
+ def bounds_exact(self):
+ """Give the tighest bounding rectangle for path."""
+ return wrap_OptRect(self.thisptr.boundsExact())
+
+#~ def toPwSb(self):
+#~ return wrap_Piecewise<Geom::D2<Geom::SBasis> >(self.thisptr.toPwSb())
+
+ def point_at(self, double t):
+ """Same as self(t)."""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+ def value_at(self, double t, Dim2 d):
+ """Same as self(t)[d]."""
+ return self.thisptr.valueAt(t, d)
+
+ def __call__(self, double t):
+ """Evaluate path at time t.
+
+ Equivalent to self[floor(t)](t-floor(t))
+ """
+ return wrap_Point(deref( self.thisptr ) (t) )
+
+ def roots(self, double v, Dim2 d):
+ """Find time values where self(t)[d] == v"""
+ return wrap_vector_double(self.thisptr.roots(v, d))
+
+ def all_nearest_times(self, cy_Point _point, double fr=-1, double to=1):
+ """Return all values of t that |self(t) - point| is minimized."""
+ if fr == -1:
+ return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr )))
+ return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ), fr, to))
+
+
+ def nearest_time_per_curve(self, cy_Point _point):
+ """Find nearest points, return one time value per each curve."""
+ return wrap_vector_double(self.thisptr.nearestTimePerCurve(deref( _point.thisptr )))
+
+ def nearest_time(self, cy_Point _point, double fr=-1, double to=1):#, cy_double * distance_squared):
+ """Return such t that |self(t) - point| is minimized."""
+ if fr == -1:
+ return self.thisptr.nearestTime(deref( _point.thisptr ), NULL)
+ return self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, NULL)
+
+ def nearest_time_and_dist_sq(self, cy_Point _point, double fr=-1, double to=1):
+ """Return such t that |self(t) - point| is minimized and square of that distance."""
+ cdef double t, dist
+ if fr == -1:
+ t = self.thisptr.nearestTime(deref( _point.thisptr ), &dist )
+ else:
+ t = self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, &dist)
+ return (t, dist)
+
+ def append_portion_to(self, cy_Path p, double f, double t):
+ """Append portion of path to self."""
+ self.thisptr.appendPortionTo(deref( p.thisptr ), f, t)
+
+ def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
+ """Return portion of curve between two time values.
+
+ Alternatively use argument interval.
+ """
+ if interval is None:
+ return wrap_Path(self.thisptr.portion(fr, to))
+ else:
+ return wrap_Path(self.thisptr.portion(deref( interval.thisptr )))
+
+ def reversed(self):
+ """Return reversed curve."""
+ return wrap_Path(self.thisptr.reversed())
+
+ def insert(self, int pos, curve, Stitching stitching=NO_STITCHING):
+ """Insert curve into position pos.
+
+ Args:
+ pos: Position of inserted curve.
+ curve: Curve to insert.
+ stitching=NO_STITCHING
+ """
+ cdef Curve * cptr = get_Curve_p(curve)
+ if cptr:
+ self.thisptr.insert( self._iterator_at_index(pos), deref( cptr ), stitching )
+ else:
+ raise TypeError("passed curve is not C++ Curve")
+
+ def insert_slice(self, int pos, cy_Path p, int first, int last, Stitching stitching=NO_STITCHING):
+ """Insert curves to position pos.
+
+ Args:
+ pos: Position of inserted slice.
+ p: Path from which slice is inserted.
+ first: First inserted curve position (in p).
+ last: Fist not inserted curve position (in p).
+ stiching=NO_STITCHING
+ """
+ self.thisptr.insert(self._iterator_at_index(pos), p._const_iterator_at_index(first), p._const_iterator_at_index(last), stitching)
+
+ def clear(self):
+ """Clear all curves."""
+ self.thisptr.clear()
+
+ def erase(self, int pos, Stitching stitching=NO_STITCHING):
+ """Erase curve at position pos.
+
+ Args:
+ pos: Position of erased curve.
+ stitching=NO_STITCHING
+ """
+ self.thisptr.erase(self._iterator_at_index(pos), stitching)
+
+ def erase_slice(self, int start, int end, Stitching stitching=NO_STITCHING):
+ """Erase curves with indices [start, end).
+
+ Args:
+ start, end: Curves with indices start...end-1 are erased
+ stitching=NO_STITCHING
+ """
+ self.thisptr.erase(self._iterator_at_index(start), self._iterator_at_index(end), stitching)
+
+ def erase_last(self):
+ """Erase last curve."""
+ self.thisptr.erase_last()
+
+ def replace(self, int replaced, curve, Stitching stitching=NO_STITCHING):
+ """Replace curve at position replaced with another curve.
+
+ Args:
+ replaced: Position of replaced curve.
+ curve: New curve.
+ stitching=NO_STITCHING
+ """
+ cdef Curve * cptr = get_Curve_p(curve)
+ if cptr:
+ self.thisptr.replace(self._iterator_at_index(replaced), deref( cptr ), stitching)
+ else:
+ raise TypeError("passed curve is not C++ Curve")
+
+ def replace_slice(self, int first_replaced, int last_replaced, curve, Stitching stitching=NO_STITCHING):
+ """Replace slice of curves by new curve.
+
+ Args:
+ first_replaced, last_replace: Curves with indices
+ first_replaced ... last_replaced
+ curve: New curve.
+ stitching=NO_STITCHING
+ """
+ cdef Curve * cptr = get_Curve_p(curve)
+ if cptr:
+ self.thisptr.replace(self._iterator_at_index(first_replaced), self._iterator_at_index(last_replaced), deref( cptr ), stitching)
+ else:
+ raise TypeError("passed curve is not C++ Curve")
+
+#How to implement this nicely?
+#~ def replaceByList(self, int replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching):
+#~ self.thisptr.replace(deref( replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching)
+#~ def replace(self, cy_Iterator first_replaced, cy_Iterator last_replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching):
+#~ self.thisptr.replace(deref( first_replaced.thisptr ), deref( last_replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching)
+
+ def start(self, cy_Point p):
+ """Erase all curves and set first point."""
+ self.thisptr.start(deref( p.thisptr ))
+
+ def initial_point(self):
+ """Get initial point."""
+ return wrap_Point(self.thisptr.initialPoint())
+
+ def final_point(self):
+ """Get final point."""
+ return wrap_Point(self.thisptr.finalPoint())
+
+ def set_initial(self, cy_Point p):
+ """Set initial point."""
+ self.thisptr.setInitial(deref( p.thisptr ))
+
+ def set_final(self, cy_Point p):
+ """Set final point."""
+ self.thisptr.setFinal(deref( p.thisptr ))
+
+ def append_curve(self, curve, Stitching stitching=NO_STITCHING):
+ """Append curve to path.
+
+ Args:
+ curve: Curve to append.
+ stitching=NO_STITCHING
+ """
+ cdef Curve * cptr = get_Curve_p(curve)
+ if cptr:
+ self.thisptr.append( deref( cptr ), stitching)
+ else:
+ raise TypeError("passed curve is not C++ Curve")
+
+ def append_SBasis(self, cy_SBasis x, cy_SBasis y, Stitching stitching=NO_STITCHING):
+ """Append two SBasis functions to path.
+
+ Args:
+ x, y: SBasis functions to append.
+ stitching=NO_STITCHING
+ """
+ cdef D2[SBasis] sb = D2[SBasis]( deref(x.thisptr), deref(y.thisptr) )
+ self.thisptr.append(sb, stitching)
+
+ def append_path(self, cy_Path other, Stitching stitching=NO_STITCHING):
+ """Append another path to path.
+
+ Args:
+ other: Path to append.
+ stitching=NO_STITCHING
+ """
+ self.thisptr.append(deref( other.thisptr ), stitching)
+
+ def stitch_to(self, cy_Point p):
+ """Set last point to p, creating stitching segment to it."""
+ self.thisptr.stitchTo(deref( p.thisptr ))
+
+cdef cy_Path wrap_Path(Path p):
+ cdef Path * retp = new Path(Point())
+ retp[0] = p
+ cdef cy_Path r = cy_Path.__new__(cy_Path)
+ r.thisptr = retp
+ return r
diff --git a/src/cython/_cy_primitives.pxd b/src/cython/_cy_primitives.pxd
new file mode 100644
index 0000000..87df1c6
--- /dev/null
+++ b/src/cython/_cy_primitives.pxd
@@ -0,0 +1,237 @@
+from _common_decl cimport *
+
+
+cdef extern from "2geom/affine.h" namespace "Geom":
+ cdef cppclass Affine:
+ pass
+ cdef cppclass Translate
+ cdef cppclass Scale
+ cdef cppclass Rotate
+ cdef cppclass VShear
+ cdef cppclass HShear
+ cdef cppclass Zoom
+
+
+cdef extern from "2geom/angle.h" namespace "Geom":
+ cdef cppclass Angle:
+ Angle()
+ Angle(Coord)
+ Angle(Point)
+ Coord radians()
+ Coord radians0()
+ Coord degrees()
+ Coord degreesClock()
+
+ Coord operator()
+ Angle &operator+(Angle &)
+ Angle &operator-(Angle &)
+ bint operator==(Angle &)
+ bint operator!=(Angle &)
+
+ Coord rad_from_deg(Coord)
+ Coord deg_from_rad(Coord)
+
+cdef extern from "2geom/angle.h" namespace "Geom::Angle":
+ Angle from_radians(Coord d)
+ Angle from_degrees(Coord d)
+ Angle from_degrees_clock(Coord d)
+
+cdef class cy_Angle:
+ cdef Angle* thisptr
+
+cdef cy_Angle wrap_Angle(Angle)
+
+
+cdef extern from "2geom/angle.h" namespace "Geom":
+ cdef cppclass AngleInterval:
+ AngleInterval(AngleInterval &)
+ AngleInterval(Angle &, Angle &, bint)
+ AngleInterval(double, double, bint)
+ Angle & initialAngle()
+ Angle & finalAngle()
+ bint isDegenerate()
+ Angle angleAt(Coord)
+ Angle operator()(Coord)
+ bint contains(Angle &)
+ Coord extent()
+
+
+cdef extern from "2geom/point.h" namespace "Geom":
+ cdef cppclass Point:
+ Point()
+ Point(Coord, Coord)
+ Coord length()
+ Point ccw()
+ Point cw()
+ Coord x()
+ Coord y()
+ IntPoint round()
+ IntPoint floor()
+ IntPoint ceil()
+
+ bint isFinite()
+ bint isZero()
+ bint isNormalized(Coord)
+
+ bint operator==(Point &)
+ bint operator!=(Point &)
+ bint operator<(Point &)
+ bint operator>(Point &)
+ bint operator<=(Point &)
+ bint operator>=(Point &)
+
+ Coord &operator[](int)
+ Point operator-()
+ Point &operator+(Point &)
+ Point &operator-(Point &)
+ Point &operator*(Coord)
+ Point &operator/(Coord)
+
+ Point &operator*(Affine &)
+ Point &operator*(Translate &)
+ Point &operator*(Scale &)
+ Point &operator*(Rotate &)
+ Point &operator*(HShear &)
+ Point &operator*(VShear &)
+ Point &operator*(Zoom &)
+
+ Coord L2(Point &)
+ Coord L2sq(Point &)
+
+ bint are_near(Point &, Point &, double)
+
+ Point middle_point(Point &, Point &)
+ Point rot90(Point &)
+ Point lerp(double, Point &, Point &)
+
+ Coord dot(Point &, Point &)
+ Coord cross(Point &, Point &)
+ Coord distance (Point &, Point &)
+ Coord distanceSq (Point &, Point &)
+
+ Point unit_vector(Point &)
+ Coord L1(Point &)
+ Coord LInfty(Point &)
+ bint is_zero(Point &)
+ bint is_unit_vector(Point &)
+ double atan2(Point &)
+ double angle_between(Point &, Point &)
+ Point abs(Point &)
+ Point constrain_angle(Point &, Point &, unsigned int, Point &)
+
+cdef extern from "2geom/point.h" namespace "Geom::Point":
+ Point polar(Coord angle, Coord radius)
+
+cdef class cy_Point:
+ cdef Point* thisptr
+
+cdef cy_Point wrap_Point(Point p)
+cdef object wrap_vector_point(vector[Point] v)
+cdef vector[Point] make_vector_point(object l)
+
+
+cdef extern from "2geom/int-point.h" namespace "Geom":
+ cdef cppclass IntPoint:
+ IntPoint()
+ IntPoint(IntCoord, IntCoord)
+ IntPoint(IntPoint &)
+ IntCoord operator[](unsigned int)
+ IntCoord x()
+ IntCoord y()
+ #why doesn't IntPoint have unary -?
+ IntPoint & operator+(IntPoint &)
+ IntPoint & operator-(IntPoint &)
+ bint operator==(IntPoint &)
+ bint operator!=(IntPoint &)
+ bint operator<=(IntPoint &)
+ bint operator>=(IntPoint &)
+ bint operator>(IntPoint &)
+ bint operator<(IntPoint &)
+
+cdef class cy_IntPoint:
+ cdef IntPoint* thisptr
+
+cdef cy_IntPoint wrap_IntPoint(IntPoint p)
+
+
+cdef extern from "2geom/curve.h" namespace "Geom":
+ cdef cppclass Curve
+
+cdef extern from "2geom/bezier.h" namespace "Geom":
+ cdef cppclass LineSegment
+
+cdef extern from "2geom/line.h" namespace "Geom":
+ cdef cppclass Line:
+ Line()
+ Line(Point &, Coord)
+ Line(Point &, Point &)
+
+ Line(LineSegment &)
+ Line(Ray &)
+ Line* duplicate()
+
+ Point origin()
+ Point versor()
+ Coord angle()
+ void setOrigin(Point &)
+ void setVersor(Point &)
+ void setAngle(Coord)
+ void setPoints(Point &, Point &)
+ void setCoefficients(double, double, double)
+ bint isDegenerate()
+ Point pointAt(Coord)
+ Coord valueAt(Coord, Dim2)
+ Coord timeAt(Point &)
+ Coord timeAtProjection(Point &)
+ Coord nearestTime(Point &)
+ vector[Coord] roots(Coord, Dim2)
+ Line reverse()
+ Curve* portion(Coord, Coord)
+ LineSegment segment(Coord, Coord)
+ Ray ray(Coord)
+ Line derivative()
+ Line transformed(Affine &)
+ Point normal()
+ Point normalAndDist(double &)
+
+ double distance(Point &, Line &)
+ bint are_near(Point &, Line &, double)
+ bint are_parallel(Line &, Line &, double)
+ bint are_same(Line &, Line &, double)
+ bint are_orthogonal(Line &, Line &, double)
+ bint are_collinear(Point &, Point &, Point &, double)
+
+ double angle_between(Line &, Line &)
+ double distance(Point &, LineSegment &)
+
+cdef extern from "2geom/line.h" namespace "Geom::Line":
+ Line from_origin_and_versor(Point, Point)
+ Line from_normal_distance(Point, double)
+
+
+cdef extern from "2geom/ray.h" namespace "Geom":
+ cdef cppclass Ray:
+ Ray()
+ Ray(Point &, Coord)
+ Ray(Point&, Point &)
+ Point origin()
+ Point versor()
+ void setOrigin(Point &)
+ void setVersor(Point &)
+ void setAngle(Coord)
+ Coord angle()
+ void setPoints(Point &, Point &)
+ bint isDegenerate()
+ Point pointAt(Coord)
+ Coord valueAt(Coord, Dim2)
+ vector[Coord] roots(Coord, Dim2)
+ Coord nearestTime(Point &)
+ Ray reverse()
+ Curve *portion(Coord, Coord)
+ LineSegment segment(Coord, Coord)
+ Ray transformed(Affine &)
+ double distance(Point &, Ray &)
+ bint are_near(Point &, Ray &, double)
+ bint are_same(Ray&, Ray &, double)
+ double angle_between(Ray &, Ray &, bint)
+ Ray make_angle_bisector_ray(Ray &, Ray&)
diff --git a/src/cython/_cy_primitives.pyx b/src/cython/_cy_primitives.pyx
new file mode 100644
index 0000000..62f7f41
--- /dev/null
+++ b/src/cython/_cy_primitives.pyx
@@ -0,0 +1,846 @@
+from numbers import Number
+
+from _common_decl cimport *
+from cython.operator cimport dereference as deref
+
+from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale
+from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom
+from _cy_affine cimport cy_Affine, get_Affine, is_transform
+
+from _cy_curves cimport cy_Curve, wrap_Curve_p
+from _cy_curves cimport cy_LineSegment, wrap_LineSegment
+
+cdef class cy_Angle:
+
+ """Class representig angle.
+
+ Angles can be tested for equality, but they are not ordered.
+
+ Corresponds to Angle class in 2geom. Most members are direct
+ calls to Angle methods, otherwise C++ call is specified.
+ """
+
+ def __cinit__(self, double x):
+ """Create new angle from value in radians."""
+ self.thisptr = new Angle(x)
+
+ def __repr__(self):
+ """repr(self)"""
+ return "Angle({0:2.f})".format(self.radians())
+
+ def __str__(self):
+ """str(self)"""
+ return "{0:.2f} radians".format(self.radians())
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_radians(cls, rad):
+ """Construnct angle from radians."""
+ return wrap_Angle(from_radians(rad))
+
+ @classmethod
+ def from_degrees(cls, d):
+ """Construnct angle from degrees."""
+ return wrap_Angle(from_degrees(d))
+
+ @classmethod
+ def from_degrees_clock(cls, d):
+ """Construnct angle from degrees in clock convention."""
+ return wrap_Angle(from_degrees_clock(d))
+
+ @classmethod
+ def from_Point(cls, cy_Point p):
+ """Construct angle from Point. Calls Angle(Point) in 2geom."""
+ cdef Point * pp = p.thisptr
+ return wrap_Angle( Angle( deref(p.thisptr) ))
+
+ def __float__(self):
+ """float(self)"""
+ return <Coord> deref(self.thisptr)
+
+ def __add__(cy_Angle self, cy_Angle other):
+ """alpha + beta"""
+ return wrap_Angle(deref(other.thisptr) + deref(self.thisptr))
+
+ def __sub__(cy_Angle self, cy_Angle other):
+ """alpha - beta"""
+ return wrap_Angle(deref(other.thisptr) - deref(self.thisptr))
+
+ def __richcmp__(cy_Angle self, cy_Angle other, int op):
+ """Test equality of angles. Note: angles are not ordered."""
+ if op == 2:
+ return deref(other.thisptr) == deref(self.thisptr)
+ elif op == 3:
+ return deref(other.thisptr) != deref(self.thisptr)
+ return NotImplemented
+
+ def radians(self):
+ """Return the angle in radians."""
+ return self.thisptr.radians()
+
+ def radians0(self):
+ """Return the angle in positive radians."""
+ return self.thisptr.radians0()
+
+ def degrees(self):
+ """Return the angle in degrees."""
+ return self.thisptr.degrees()
+
+ def degrees_clock(self):
+ """Return the angle in clock convention. Calls degreesClock() in 2geom."""
+ return self.thisptr.degreesClock()
+
+ @classmethod
+ def rad_from_deg(cls, deg):
+ """Convert degrees to radians."""
+ return rad_from_deg(deg)
+
+ @classmethod
+ def deg_from_rad(cls, rad):
+ """Convert radians to degrees."""
+ return deg_from_rad(rad)
+
+cdef cy_Angle wrap_Angle(Angle p):
+ cdef Angle * retp = new Angle()
+ retp[0] = p
+ cdef cy_Angle r = cy_Angle.__new__(cy_Angle, 0)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_AngleInterval:
+
+ """ Class representing interval of angles.
+
+ Corresponds to AngleInterval class in 2geom. Most members are direct
+ calls to AngleInterval methods, otherwise C++ call is specified.
+ """
+
+ cdef AngleInterval* thisptr
+
+ def __cinit__(self, start, end, bint cw=False):
+ """Create AngleInterval from starting and ending value.
+
+ Optional argument cw specifies direction - counter-clockwise
+ is default.
+ """
+ self.thisptr = new AngleInterval(float(start), float(end), cw)
+
+ def __call__(self, Coord t):
+ """A(t), maps unit interval to Angle."""
+ return wrap_Angle(deref( self.thisptr ) (t))
+
+ def initial_angle(self):
+ """Return initial angle as Angle instance."""
+ return wrap_Angle(self.thisptr.initialAngle())
+
+ def final_angle(self):
+ """Return final angle as Angle instance."""
+ return wrap_Angle(self.thisptr.finalAngle())
+
+ def is_degenerate(self):
+ """Test for empty interval."""
+ return self.thisptr.isDegenerate()
+
+ def angle_at(self, Coord t):
+ """A.angle_at(t) <==> A(t)"""
+ return wrap_Angle(self.thisptr.angleAt(t))
+
+ def contains(self, cy_Angle a):
+ """Test whether interval contains Angle."""
+ return self.thisptr.contains(deref( a.thisptr ))
+
+ def extent(self):
+ """Calculate interval's extent."""
+ return self.thisptr.extent()
+
+cdef cy_AngleInterval wrap_AngleInterval(AngleInterval p):
+ cdef AngleInterval * retp = new AngleInterval(0, 0, 0)
+ retp[0] = p
+ cdef cy_AngleInterval r = cy_AngleInterval.__new__(cy_AngleInterval)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Point:
+
+ """Represents point or vector in 2D plane.
+
+ Points are ordered lexicographically, with y coordinate being
+ more significant.
+
+ Corresponds to Point class in 2geom. Most members are direct
+ calls to Point methods, otherwise C++ call is specified.
+ """
+
+ def __cinit__(self, double x=0.0, double y=0.0):
+ """Create Point from it's cartesian coordinates."""
+ self.thisptr = new Point(x, y)
+
+ def __repr__(self):
+ """repr(self)"""
+ return "Point ({0:.3f}, {1:.3f})".format(self[0], self[1])
+
+ def __str__(self):
+ """str(self)"""
+ return "[{0:.3f}, {1:.3f}]".format(self[0], self[1])
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def polar(cls, Coord angle, Coord radius = 1.0):
+ """Create Point from polar coordinates."""
+ return wrap_Point(polar(angle, radius))
+
+ def length(self):
+ """Return distance from origin or length of vector."""
+ return self.thisptr.length()
+
+ def ccw(self):
+ """Return point rotated counter-clockwise."""
+ return wrap_Point(self.thisptr.ccw())
+
+ def cw(self):
+ """Return point rotated clockwise."""
+ return wrap_Point(self.thisptr.cw())
+
+ def __getitem__(self, key):
+ """Access coordinates of point."""
+ return deref(self.thisptr)[key]
+
+ @property
+ def x(self):
+ """First coordinate of point."""
+ return self.thisptr.x()
+
+ @property
+ def y(self):
+ """Second coordinate of point."""
+ return self.thisptr.y()
+
+ def round(self):
+ """Create IntPoint rounding coordinates."""
+ return wrap_IntPoint(self.thisptr.round())
+
+ def floor(self):
+ """Create IntPoint flooring coordinates."""
+ return wrap_IntPoint(self.thisptr.floor())
+
+ def ceil(self):
+ """Create IntPoint ceiling coordinates."""
+ return wrap_IntPoint(self.thisptr.ceil())
+
+ def __neg__(self):
+ """-P"""
+ return(wrap_Point(-deref(self.thisptr)))
+
+ def __abs__(self):
+ """abs(P)"""
+ return self.length()
+
+ def __add__(cy_Point self, cy_Point other):
+ """P + Q"""
+ return wrap_Point(deref(self.thisptr) + deref(other.thisptr))
+
+ def __sub__(cy_Point self, cy_Point other):
+ """P - Q"""
+ return wrap_Point(deref(self.thisptr) - deref(other.thisptr))
+
+ #TODO exceptions
+ def __mul__(cy_Point self, s):
+ """Multiply point by number or Affine transform."""
+ if isinstance(s, Number):
+ return wrap_Point(deref(self.thisptr)* (<Coord> float(s)))
+ elif isinstance(s, cy_Affine):
+ return wrap_Point( deref(self.thisptr) * <Affine &> deref( (<cy_Affine> s).thisptr ) )
+ elif isinstance(s, cy_Translate):
+ return wrap_Point( deref(self.thisptr) * <Translate &> deref( (<cy_Translate> s).thisptr ) )
+ elif isinstance(s, cy_Scale):
+ return wrap_Point( deref(self.thisptr) * <Scale &> deref( (<cy_Scale> s).thisptr ) )
+ elif isinstance(s, cy_Rotate):
+ return wrap_Point( deref(self.thisptr) * <Rotate &> deref( (<cy_Rotate> s).thisptr ) )
+ elif isinstance(s, cy_HShear):
+ return wrap_Point( deref(self.thisptr) * <HShear &> deref( (<cy_HShear> s).thisptr ) )
+ elif isinstance(s, cy_VShear):
+ return wrap_Point( deref(self.thisptr) * <VShear &> deref( (<cy_VShear> s).thisptr ) )
+ elif isinstance(s, cy_Zoom):
+ return wrap_Point( deref(self.thisptr) * <Zoom &> deref( (<cy_Zoom> s).thisptr ) )
+ return NotImplemented
+
+ def __div__(cy_Point self, Coord s):
+ """P / s"""
+ return wrap_Point(deref(self.thisptr)/s)
+
+ def __richcmp__(cy_Point self, cy_Point other, int op):
+ if op == 0:
+ return deref(self.thisptr) < deref(other.thisptr)
+ if op == 1:
+ return deref(self.thisptr) <= deref(other.thisptr)
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ if op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+ if op == 4:
+ return deref(self.thisptr) > deref(other.thisptr)
+ if op == 5:
+ return deref(self.thisptr) >= deref(other.thisptr)
+
+ def isFinite(self):
+ """Test whether point is finite."""
+ return self.thisptr.isFinite()
+
+ def isZero(self):
+ """Test whether point is origin"""
+ return self.thisptr.isZero()
+
+ def isNormalized(self, eps=EPSILON):
+ """Test whether point's norm is close to 1."""
+ return self.thisptr.isNormalized(eps)
+
+ @classmethod
+ def L2(cls, cy_Point p):
+ """Compute L2 (Euclidean) norm of point.
+
+ L2(P) = sqrt( P.x**2 + P.y**2 )
+ """
+ return L2(deref(p.thisptr))
+
+ @classmethod
+ def L2sq(cls, cy_Point p):
+ """Compute square of L2 (Euclidean) norm."""
+ return L2sq(deref(p.thisptr))
+
+ @classmethod
+ def are_near(cls, cy_Point a, cy_Point b, double eps=EPSILON):
+ """Test if two points are close."""
+ return are_near(deref(a.thisptr), deref(b.thisptr), eps)
+
+ @classmethod
+ def middle_point(cls, cy_Point a, cy_Point b):
+ """Return point between two points."""
+ return wrap_Point(middle_point(deref(a.thisptr), deref(b.thisptr)))
+
+ @classmethod
+ def rot90(cls, cy_Point a):
+ """Rotate point by 90 degrees."""
+ return wrap_Point(rot90(deref(a.thisptr)))
+
+ @classmethod
+ def lerp(cls, double t, cy_Point a, cy_Point b):
+ """Linearly interpolate between too points."""
+ return wrap_Point(lerp(t, deref(a.thisptr), deref(b.thisptr)))
+
+ @classmethod
+ def dot(cls, cy_Point a, cy_Point b):
+ """Calculate dot product of two points."""
+ return dot(deref(a.thisptr), deref(b.thisptr))
+
+ @classmethod
+ def cross(cls, cy_Point a, cy_Point b):
+ """Calculate (z-coordinate of) cross product of two points."""
+ return cross(deref(a.thisptr), deref(b.thisptr))
+
+ @classmethod
+ def distance(cls, cy_Point a, cy_Point b):
+ """Compute distance between two points."""
+ return distance(deref(a.thisptr), deref(b.thisptr))
+
+ @classmethod
+ def distanceSq(cls, cy_Point a, cy_Point b):
+ """Compute square of distance between two points."""
+ return distanceSq(deref(a.thisptr), deref(b.thisptr))
+
+ @classmethod
+ def unit_vector(cls, cy_Point p):
+ """Normalise point."""
+ return wrap_Point(unit_vector(deref(p.thisptr)))
+
+ @classmethod
+ def L1(cls, cy_Point p):
+ """Compute L1 (Manhattan) norm of a point.
+
+ L1(P) = |P.x| + |P.y|
+ """
+ return L1(deref(p.thisptr))
+
+ @classmethod
+ def LInfty(cls, cy_Point p):
+ """Compute Infinity norm of a point.
+
+ LInfty(P) = max(|P.x|, |P.y|)
+ """
+ return LInfty(deref(p.thisptr))
+
+ @classmethod
+ def is_zero(cls, cy_Point p):
+ """Test whether point is origin."""
+ return is_zero(deref(p.thisptr))
+
+ @classmethod
+ def is_unit_vector(cls, cy_Point p):
+ """Test whether point's length equal 1."""
+ return is_unit_vector(deref(p.thisptr))
+
+ @classmethod
+ def atan2(cls, cy_Point p):
+ """Return angle between point and x-axis."""
+ return atan2(deref(p.thisptr))
+
+ @classmethod
+ def angle_between(cls, cy_Point a, cy_Point b):
+ """Return angle between two point."""
+ return angle_between(deref(a.thisptr), deref(b.thisptr))
+
+ @classmethod
+ def abs(cls, cy_Point p):
+ """Return length of a point."""
+ return wrap_Point(abs(deref(p.thisptr)))
+
+ @classmethod
+ def constrain_angle(cls, cy_Point a, cy_Point b, unsigned int n, cy_Point direction):
+ """Rotate B around A to have specified angle wrt. direction."""
+ return wrap_Point(constrain_angle(deref(a.thisptr), deref(b.thisptr), n, deref(direction.thisptr)))
+
+cdef cy_Point wrap_Point(Point p):
+ cdef Point * retp = new Point()
+ retp[0] = p
+ cdef cy_Point r = cy_Point.__new__(cy_Point)
+ r.thisptr = retp
+ return r
+
+cdef object wrap_vector_point(vector[Point] v):
+ r = []
+ cdef unsigned int i
+ for i in range(v.size()):
+ r.append( wrap_Point(v[i]) )
+ return r
+
+cdef vector[Point] make_vector_point(object l):
+ cdef vector[Point] ret
+ for i in l:
+ ret.push_back( deref( (<cy_Point> i).thisptr ) )
+ return ret
+
+
+cdef class cy_IntPoint:
+
+ """Represents point with integer coordinates
+
+ IntPoints are ordered lexicographically, with y coordinate being
+ more significant.
+
+ Corresponds to IntPoint class in 2geom. Most members are direct
+ calls to IntPoint methods, otherwise C++ call is specified.
+ """
+
+ def __init__(self, IntCoord x = 0, IntCoord y = 0):
+ """Create new IntPoint from it's cartesian coordinates."""
+ self.thisptr = new IntPoint(x ,y)
+
+ def __getitem__(self, key):
+ """Get coordinates of IntPoint."""
+ return deref(self.thisptr)[key]
+
+ def __repr__(self):
+ """repr(self)"""
+ return "IntPoint ({0}, {1})".format(self[0], self[1])
+
+ def __str__(self):
+ """str(self)"""
+ return "[{0}, {1}]".format(self[0], self[1])
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @property
+ def x(self):
+ """First coordinate of IntPoint."""
+ return self.thisptr.x()
+
+ @property
+ def y(self):
+ """Second coordinate of IntPoint."""
+ return self.thisptr.y()
+
+ def __add__(cy_IntPoint self, cy_IntPoint o):
+ """P + Q"""
+ return wrap_IntPoint(deref(self.thisptr)+deref( o.thisptr ))
+
+ def __sub__(cy_IntPoint self, cy_IntPoint o):
+ """P - Q"""
+ return wrap_IntPoint(deref(self.thisptr)-deref( o.thisptr ))
+
+ def __richcmp__(cy_IntPoint self, cy_IntPoint other, int op):
+ if op == 0:
+ return deref(self.thisptr) < deref(other.thisptr)
+ if op == 1:
+ return deref(self.thisptr) <= deref(other.thisptr)
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ if op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+ if op == 4:
+ return deref(self.thisptr) > deref(other.thisptr)
+ if op == 5:
+ return deref(self.thisptr) >= deref(other.thisptr)
+
+cdef cy_IntPoint wrap_IntPoint(IntPoint p):
+ cdef IntPoint * retp = new IntPoint()
+ retp[0] = p
+ cdef cy_IntPoint r = cy_IntPoint.__new__(cy_IntPoint)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Line:
+
+ """Class representing line in plane.
+
+ Corresponds to Line class in 2geom. Most members are direct
+ calls to Line methods, otherwise C++ call is specified.
+ """
+
+ cdef Line* thisptr
+
+ def __cinit__(self, cy_Point cp = None, double x = 0):
+ """Create Line from point and angle to x-axis.
+
+ Constructor with no arguments calls Line() in 2geom, otherwise
+ Line(Point &, double) is called.
+ """
+ if cp is None:
+ self.thisptr = new Line()
+ else:
+ self.thisptr = new Line( deref(cp.thisptr), x)
+
+ def __repr__(self):
+ """repr(self)."""
+ return "Line({0}, {1:3f})".format(repr(self.origin()), self.angle())
+
+ def __str__(self):
+ """str(self)"""
+ return repr(self)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_Point cp, cy_Point cq):
+ """Create Line passing through two points.
+
+ Calls Line(Point &, Point &) in 2geom.
+ """
+ return wrap_Line( Line( deref(cp.thisptr), deref(cq.thisptr) ) )
+
+ @classmethod
+ def from_origin_and_versor(cls, cy_Point o, cy_Point v):
+ """Create Line passing through point with specified versor."""
+ return wrap_Line( from_origin_and_versor(deref(o.thisptr), deref(v.thisptr)) )
+
+ @classmethod
+ def from_normal_distance(cls, cy_Point normal, double dist):
+ """Create Line from it's normal and distance from origin."""
+ return wrap_Line( from_normal_distance( deref(normal.thisptr), dist ) )
+
+ @classmethod
+ def from_LineSegment(cls, cy_LineSegment LS):
+ """Create Line from LineSegment.
+
+ Calls Line(LineSegment &) in 2geom.
+ """
+ return wrap_Line( Line( deref(<LineSegment *> LS.thisptr) ) )
+
+ @classmethod
+ def from_Ray(cls, cy_Ray R):
+ """Create Line from Ray.
+
+ Calls Line(Ray &) in 2geom.
+ """
+ return wrap_Line( Line( deref(R.thisptr) ) )
+
+ #maybe implement as properties.
+
+ def origin(self):
+ """Return origin of line."""
+ return wrap_Point(self.thisptr.origin())
+
+ def versor(self):
+ """Return versor of line."""
+ return wrap_Point(self.thisptr.versor())
+
+ def angle(self):
+ """Return angle between line and x-axis."""
+ return self.thisptr.angle()
+
+ def set_origin(self, cy_Point origin):
+ """Set origin."""
+ self.thisptr.setOrigin( deref(origin.thisptr) )
+
+ def set_versor(self, cy_Point versor):
+ """Set versor."""
+ self.thisptr.setVersor( deref(versor.thisptr) )
+
+ def set_angle(self, Coord a):
+ """Set angle."""
+ self.thisptr.setAngle(a)
+
+ def set_points(self, cy_Point cp, cy_Point cq):
+ """Set two points line passes through."""
+ self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) )
+
+ def set_coefficients(self, a, b, c):
+ """Set coefficients in parametric equation of line."""
+ self.thisptr.setCoefficients(a, b, c)
+
+ def is_degenerate(self):
+ """Test whether line's versor is zero vector."""
+ return self.thisptr.isDegenerate()
+
+ def point_at(self, t):
+ """origin + t*versor"""
+ return wrap_Point(self.thisptr.pointAt(t))
+
+ def value_at(self, t, Dim2 d):
+ """Coordinates of point_at(t)."""
+ return self.thisptr.valueAt(t, d)
+
+ def time_at(self, cy_Point cp):
+ """Find time value corresponding to point on line."""
+ return self.thisptr.timeAt( deref(cp.thisptr) )
+
+ def time_at_projection(self, cy_Point cp):
+ """Find time value corresponding to orthogonal projection of point."""
+ return self.thisptr.timeAtProjection( deref(cp.thisptr) )
+
+ def nearest_time(self, cy_Point cp):
+ """Alias for time_at_projection."""
+ return self.thisptr.nearestTime( deref(cp.thisptr) )
+
+ def roots(self, Coord v, Dim2 d):
+ """Return time values where self.value_at(t, dim) == v."""
+ return wrap_vector_double( self.thisptr.roots(v, d) )
+
+ def reverse(self):
+ """Reverse line."""
+ return wrap_Line( self.thisptr.reverse() )
+
+ def derivative(self):
+ """Take line's derivative."""
+ return wrap_Line( self.thisptr.derivative() )
+
+ def normal(self):
+ """Return line's normal."""
+ return wrap_Point( self.thisptr.normal() )
+
+ def normal_and_dist(self):
+ """return tuple containing normal vector and distance from origin.
+
+ Calls normal_and_dist(x) and return it's result and x as a tuple.
+ """
+ cdef double x = 0
+ cdef Point p = self.thisptr.normalAndDist(x)
+ return (wrap_Point(p), x)
+
+ def portion(self, Coord f, Coord t):
+ """Return Curve corresponding to portion of line."""
+ return wrap_Curve_p( self.thisptr.portion(f, t) )
+
+ def ray(self, Coord t):
+ """Return Ray continuing from time value t."""
+ return wrap_Ray( self.thisptr.ray(t) )
+
+ def segment(self, Coord f, Coord t):
+ """Return LineSegment corresponding to portion of line."""
+ return wrap_LineSegment( self.thisptr.segment(f, t) )
+
+ def transformed(self, t):
+ """Return line transformed by transform."""
+ #doing this because transformed(t) takes reference
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_Line(self.thisptr.transformed( at ))
+
+ @classmethod
+ def distance(cls, cy_Point cp, cy_Line cl):
+ """Calculate distance between point and line."""
+ return distance( deref(cp.thisptr), deref(cl.thisptr))
+
+ @classmethod
+ def are_near(cls, cy_Point cp, cy_Line cl, double eps=EPSILON):
+ """Test if point is near line."""
+ return are_near( deref(cp.thisptr), deref(cl.thisptr), eps)
+
+ @classmethod
+ def are_parallel(cls, cy_Line cl, cy_Line ck, eps=EPSILON):
+ """Test if lines are almost parallel."""
+ return are_parallel( deref(cl.thisptr), deref(ck.thisptr), eps)
+
+ @classmethod
+ def are_same(cls, cy_Line cl, cy_Line ck, double eps=EPSILON):
+ """Test if lines represent the same line."""
+ return are_same( deref(cl.thisptr), deref(ck.thisptr), eps)
+
+ @classmethod
+ def are_orthogonal(cls, cy_Line cl, cy_Line ck, eps=EPSILON):
+ """Test two lines for orthogonality."""
+ return are_orthogonal( deref(cl.thisptr), deref(ck.thisptr), eps)
+
+ @classmethod
+ def are_collinear(cls, cy_Point cp, cy_Point cq, cy_Point cr, eps=EPSILON):
+ """Test for collinearity of vectors (cq-cp) and (cr-cp)"""
+ return are_collinear( deref(cp.thisptr), deref(cq.thisptr), deref(cr.thisptr), eps)
+
+ @classmethod
+ def angle_between(cls, cy_Line cl, cy_Line ck):
+ """Calculate angle between two lines"""
+ return angle_between( deref(cl.thisptr), deref(ck.thisptr) )
+
+cdef cy_Line wrap_Line(Line p):
+ cdef Line * retp = new Line()
+ retp[0] = p
+ cdef cy_Line r = cy_Line.__new__(cy_Line)
+ r.thisptr = retp
+ return r
+
+#-- Ray --
+
+cdef class cy_Ray:
+
+ """Ray represents half of line, starting at origin and going to
+ infinity.
+
+ Corresponds to Ray class in 2geom. Most members are direct
+ calls to Ray methods, otherwise C++ call is specified.
+ """
+
+ cdef Ray* thisptr
+
+ def __cinit__(self, cy_Point cp = None, double x = 0):
+ """Create Ray from origin and angle with x-axis.
+
+ Empty constructor calls Ray() in 2geom.
+ """
+ if cp is None:
+ self.thisptr = new Ray()
+ else:
+ self.thisptr = new Ray( deref(cp.thisptr), x)
+
+ def __repr__(self):
+ """repr(self)."""
+ return "Ray({0}, {1:3f})".format(repr(self.origin()), self.angle())
+
+ def __str__(self):
+ """str(self)"""
+ return repr(self)
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_Point cp, cy_Point cq):
+ """Create ray passing through two points, starting at first one."""
+ return wrap_Ray( (Ray( deref(cp.thisptr), deref(cq.thisptr) )) )
+
+ def origin(self):
+ """Return origin."""
+ return wrap_Point(self.thisptr.origin())
+
+ def versor(self):
+ """Return versor."""
+ return wrap_Point(self.thisptr.versor())
+
+ def angle(self):
+ """Return angle between ray and x-axis."""
+ return self.thisptr.angle()
+
+ def set_origin(self, cy_Point cp):
+ """Set origin."""
+ self.thisptr.setOrigin( deref(cp.thisptr) )
+
+ def set_versor(self, cy_Point cp):
+ """Set versor."""
+ self.thisptr.setVersor( deref(cp.thisptr) )
+
+ def set_angle(self, Coord a):
+ """Set angle."""
+ self.thisptr.setAngle(a)
+
+ def set_points(self, cy_Point cp, cy_Point cq):
+ """Set origin and second point of ray."""
+ self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) )
+
+ def is_degenerate(self):
+ """Check for zero versor."""
+ return self.thisptr.isDegenerate()
+
+ def point_at(self, t):
+ """origin + t * versor"""
+ return wrap_Point(self.thisptr.pointAt(t))
+ def value_at(self, t, Dim2 d):
+ """Access coordinates of point_at(t)."""
+ return self.thisptr.valueAt(t, d)
+
+ def nearest_time(self, cy_Point cp):
+ """Get time value of nearest point of ray."""
+ return self.thisptr.nearestTime( deref(cp.thisptr) )
+ def reverse(self):
+ """Reverse the ray."""
+ return wrap_Ray( self.thisptr.reverse() )
+
+ def roots(self, Coord v, Dim2 d):
+ """Return time values for which self.value_at(t, d) == v."""
+ return wrap_vector_double( self.thisptr.roots(v, d) )
+
+ def transformed(self, t):
+ """Return ray transformed by affine transform."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_Ray(self.thisptr.transformed( at ))
+
+ def portion(self, Coord f, Coord t):
+ """Return Curve corresponding to portion of ray."""
+ return wrap_Curve_p( self.thisptr.portion(f, t) )
+
+ def segment(self, Coord f, Coord t):
+ """Return LineSegment corresponding to portion of ray."""
+ return wrap_LineSegment( self.thisptr.segment(f, t) )
+
+ @classmethod
+ def distance(cls, cy_Point cp, cy_Ray cl):
+ """Compute distance between point and ray."""
+ return distance( deref(cp.thisptr), deref(cl.thisptr))
+
+ @classmethod
+ def are_near(cls, cy_Point cp, cy_Ray cl, double eps=EPSILON):
+ """Check if distance between point and ray is small."""
+ return are_near( deref(cp.thisptr), deref(cl.thisptr), eps)
+
+ @classmethod
+ def are_same(cls, cy_Ray cl, cy_Ray ck, double eps=EPSILON):
+ """Check if two ray are same."""
+ return are_same( deref(cl.thisptr), deref(ck.thisptr), eps)
+
+ @classmethod
+ def angle_between(cls, cy_Ray cl, cy_Ray ck, bint cw=True):
+ """Compute angle between two rays.
+
+ Can specify direction using parameter cw.
+ """
+ return angle_between( deref(cl.thisptr), deref(ck.thisptr), cw)
+
+ @classmethod
+ def make_angle_bisector_ray(cls, cy_Ray cl, cy_Ray ck):
+ """Make ray bisecting smaller angle formed by two rays."""
+ return wrap_Ray( make_angle_bisector_ray(deref(cl.thisptr), deref(ck.thisptr) ))
+
+cdef cy_Ray wrap_Ray(Ray p):
+ cdef Ray * retp = new Ray()
+ retp[0] = p
+ cdef cy_Ray r = cy_Ray.__new__(cy_Ray)
+ r.thisptr = retp
+ return r
diff --git a/src/cython/_cy_rectangle.pxd b/src/cython/_cy_rectangle.pxd
new file mode 100644
index 0000000..ac18169
--- /dev/null
+++ b/src/cython/_cy_rectangle.pxd
@@ -0,0 +1,442 @@
+from _common_decl cimport *
+from cpython.ref cimport PyObject
+
+from _cy_primitives cimport Point, cy_Point, wrap_Point
+from _cy_primitives cimport IntPoint, cy_IntPoint, wrap_IntPoint
+
+cdef extern from "2geom/affine.h" namespace "Geom":
+ cdef cppclass Affine:
+ pass
+
+cdef extern from "2geom/cython-bindings/wrapped-pyobject.h" namespace "Geom":
+
+ cdef cppclass WrappedPyObject:
+ WrappedPyObject()
+ WrappedPyObject(object)
+ object getObj()
+
+ cdef cppclass PyPoint:
+ PyPoint()
+ PyPoint(WrappedPyObject, WrappedPyObject)
+ WrappedPyObject operator[](int)
+
+
+cdef extern from "2geom/generic-interval.h" namespace "Geom":
+
+ cdef cppclass GenericInterval[C]:
+
+ GenericInterval()
+ GenericInterval(C)
+ GenericInterval(C, C)
+
+ C min()
+ C max()
+ C extent()
+ C middle()
+
+ bint isSingular()
+ bint contains(C)
+ bint contains(GenericInterval[C] &)
+ bint intersects(GenericInterval[C] &)
+
+ void setMin(C)
+ void setMax(C)
+ void expandTo(C)
+ void expandBy(C)
+ void unionWith(GenericInterval[C] &)
+
+ GenericInterval[C] &operator+(C)
+ GenericInterval[C] &operator-(C)
+ GenericInterval[C] &operator-()
+ GenericInterval[C] &operator+(GenericInterval[C] &)
+ GenericInterval[C] &operator-(GenericInterval[C] &)
+ GenericInterval[C] &operator|(GenericInterval[C] &)
+ bint operator==(GenericInterval[C] &)
+ bint operator!=(GenericInterval[C] &)
+
+
+ cdef cppclass GenericOptInterval[C]:
+
+ GenericOptInterval()
+ GenericOptInterval(C)
+ GenericOptInterval(C, C)
+
+ GenericInterval get()
+
+ C min()
+ C max()
+ C extent()
+ C middle()
+
+ bint isEmpty()
+ bint isSingular()
+ bint contains(C)
+ bint contains(GenericOptInterval[C] &)
+ bint intersects(GenericOptInterval[C] &)
+
+ void setMin(C)
+ void setMax(C)
+ void expandTo(C)
+ void expandBy(C)
+ void unionWith(GenericOptInterval[C] &)
+ void intersectWith(GenericOptInterval &)
+
+
+ GenericOptInterval[C] &operator+(C)
+ GenericOptInterval[C] &operator-(C)
+ GenericOptInterval[C] &operator-()
+ GenericOptInterval[C] &operator+(GenericOptInterval[C] &)
+ GenericOptInterval[C] &operator-(GenericOptInterval[C] &)
+ GenericOptInterval[C] &operator|(GenericOptInterval[C] &)
+ GenericOptInterval[C] &operator&(GenericOptInterval[C] &)
+ bint operator==(GenericOptInterval[C] &)
+ bint operator!=(GenericOptInterval[C] &)
+
+
+cdef extern from "2geom/int-interval.h" namespace "Geom":
+ ctypedef GenericInterval[IntCoord] IntInterval
+ ctypedef GenericOptInterval[IntCoord] OptIntInterval
+
+
+#redeclaring inherited methods, other option is to cast all
+#pointers from Interval to GenericInterval[Coord] in extension class
+cdef extern from "2geom/interval.h" namespace "Geom":
+
+ cdef cppclass Interval:
+
+ Interval()
+ Interval(Coord)
+ Interval(Coord, Coord)
+
+ Coord min()
+ Coord max()
+ Coord extent()
+ Coord middle()
+
+ bint isSingular()
+ bint contains(Coord)
+ bint contains(Interval &)
+ bint intersects(Interval &)
+
+ void setMin(Coord)
+ void setMax(Coord)
+ void expandTo(Coord)
+ void expandBy(Coord)
+ void unionWith(Interval &)
+
+ bint isFinite()
+ bint interiorContains(Coord)
+ bint interiorContains(Interval &)
+ bint interiorIntersects(Interval &)
+
+ Interval & operator*(Coord)
+ Interval & operator/(Coord)
+ Interval & operator*(Interval &)
+ bint operator==(IntInterval &)
+ bint operator==(Interval &)
+ bint operator!=(Interval &)
+ bint operator!=(IntInterval &)
+ Interval &operator+(Coord)
+ Interval &operator-(Coord)
+ Interval &operator-()
+ Interval &operator+(Interval &)
+ Interval &operator-(Interval &)
+ Interval &operator|(Interval &)
+
+ IntInterval roundOutwards()
+ OptIntInterval roundInwards()
+
+
+ cdef cppclass OptInterval:
+
+ OptInterval(OptInterval &)
+ OptInterval()
+ OptInterval(Interval &)
+ OptInterval(Coord)
+ OptInterval(Coord, Coord)
+ OptInterval(GenericOptInterval[double] &)
+ OptInterval(IntInterval &)
+ OptInterval(OptIntInterval &)
+
+ Interval get()
+ bint isEmpty()
+ void unionWith(OptInterval &)
+ void intersectWith(OptInterval &)
+
+ OptInterval &operator|(OptInterval &)
+ OptInterval &operator&(OptInterval &)
+
+cdef class cy_Interval:
+ cdef Interval* thisptr
+
+cdef cy_Interval wrap_Interval(Interval p)
+
+cdef class cy_OptInterval:
+ cdef OptInterval* thisptr
+
+cdef cy_OptInterval wrap_OptInterval(OptInterval p)
+
+
+cdef extern from "2geom/generic-rect.h" namespace "Geom":
+ cdef cppclass GenericRect[C]:
+ GenericRect()
+ GenericRect(GenericInterval[C] &, GenericInterval[C] &)
+ GenericRect(Point &, Point &)
+ GenericRect(PyPoint &, PyPoint &)
+ GenericRect(IntPoint &, IntPoint &)
+ GenericRect(C, C, C, C)
+
+ GenericInterval[C] & operator[](Dim2)
+
+ PyPoint min()
+ PyPoint max()
+ PyPoint corner(unsigned int)
+
+ IntPoint i_min "min" ()
+ IntPoint i_max "max" ()
+ IntPoint i_corner "corner" (unsigned int)
+
+ C top()
+ C bottom()
+ C left()
+ C right()
+ C width()
+ C height()
+ Coord aspectRatio()
+
+ PyPoint dimensions()
+ PyPoint midpoint()
+
+ IntPoint i_dimensions "dimensions" ()
+ IntPoint i_midpoint "midpoint" ()
+
+ C area()
+ bint hasZeroArea()
+ C maxExtent()
+ C minExtent()
+ bint intersects(GenericRect[C] &)
+ bint contains(GenericRect[C] &)
+
+ bint contains(PyPoint &)
+ bint contains(IntPoint &)
+
+ void setLeft(C)
+ void setRight(C)
+ void setTop(C)
+ void setBottom(C)
+
+ void setMin(PyPoint &)
+ void setMax(PyPoint &)
+ void expandTo(PyPoint &)
+ void setMin(IntPoint &)
+ void setMax(IntPoint &)
+ void expandTo(IntPoint &)
+
+ void unionWith(GenericRect[C] &)
+ void expandBy(C)
+ void expandBy(C, C)
+
+ void expandBy(PyPoint &)
+ void expandBy(IntPoint &)
+
+ GenericRect[C] & operator+(PyPoint &)
+ GenericRect[C] & operator+(IntPoint &)
+
+ GenericRect[C] & operator-(PyPoint &)
+ GenericRect[C] & operator-(IntPoint &)
+
+ GenericRect[C] & operator|(GenericRect[C] &)
+ bint operator==(GenericRect[C] &)
+ bint operator!=(GenericRect[C] &)
+
+
+ cdef cppclass GenericOptRect[C]:
+ GenericOptRect()
+ GenericOptRect(GenericRect[C] &)
+ GenericOptRect(C, C, C, C)
+ GenericOptRect(Point &, Point &)
+ GenericOptRect(GenericOptInterval[C] &, GenericOptInterval[C] &)
+
+ GenericRect[C] get()
+
+ bint isEmpty()
+ bint intersects(GenericRect[C] &)
+ bint contains(GenericRect[C] &)
+ bint intersects(GenericOptRect[C] &)
+ bint contains(GenericOptRect[C] &)
+
+ bint contains(Point &)
+ bint contains(IntPoint &)
+
+ void unionWith(GenericRect[C] &)
+ void unionWith(GenericOptRect[C] &)
+ void intersectWith(GenericRect[C] &)
+ void intersectWith(GenericOptRect[C] &)
+
+ void expandTo(Point &)
+
+ GenericOptRect[C] &operator|(GenericOptRect[C] &)
+ GenericOptRect[C] &operator&(GenericRect[C] &)
+ GenericOptRect[C] &operator&(GenericOptRect[C] &)
+
+ bint operator==(GenericOptRect[C] &)
+ bint operator==(GenericRect[C] &)
+
+ bint operator!=(GenericOptRect[C] &)
+ bint operator!=(GenericRect[C] &)
+
+cdef extern from "2geom/generic-rect.h" namespace "Geom::GenericRect<Geom::WrappedPyObject>":
+ GenericRect[WrappedPyObject] from_xywh(WrappedPyObject, WrappedPyObject, WrappedPyObject, WrappedPyObject)
+ GenericRect[WrappedPyObject] from_xywh(PyPoint &, PyPoint &)
+
+cdef extern from "2geom/int-rect.h" namespace "Geom":
+ ctypedef GenericRect[IntCoord] IntRect
+
+cdef extern from "2geom/rect.h" namespace "Geom":
+ cdef cppclass Rect:
+ Rect()
+ Rect(Interval &, Interval &)
+ Rect(Point &, Point &)
+ Rect(Coord, Coord, Coord, Coord)
+
+ Interval & operator[](Dim2)
+ Point min()
+ Point max()
+ Point corner(unsigned int)
+ Coord top()
+ Coord bottom()
+ Coord left()
+ Coord right()
+ Coord width()
+ Coord height()
+ Coord aspectRatio()
+ Point dimensions()
+ Point midpoint()
+ Coord area()
+ bint hasZeroArea()
+ Coord maxExtent()
+ Coord minExtent()
+
+ bint intersects(Rect &)
+ bint contains(Rect &)
+ bint contains(Point &)
+
+ void setLeft(Coord)
+ void setRight(Coord)
+ void setTop(Coord)
+ void setBottom(Coord)
+ void setMin(Point &)
+ void setMax(Point &)
+ void expandTo(Point &)
+ void unionWith(Rect &)
+ void expandBy(Coord)
+ void expandBy(Coord, Coord)
+ void expandBy(Point &)
+
+ Rect & operator+(Point &)
+ Rect & operator-(Point &)
+ Rect & operator|(Rect &)
+ bint operator==(Rect &)
+ bint operator!=(Rect &)
+ bint operator==(IntRect &)
+ bint operator!=(IntRect &)
+
+ bint hasZeroArea(Coord)
+ bint interiorIntersects(Rect &)
+ bint interiorContains(Point &)
+ bint interiorContains(Rect &)
+ bint interiorContains(OptRect &)
+
+ IntRect roundOutwards()
+ OptIntRect roundInwards()
+ Rect &operator*(Affine &)
+
+ Coord distanceSq(Point &, Rect &)
+ Coord distance(Point &, Rect &)
+
+
+ cdef cppclass OptRect:
+ OptRect()
+ OptRect(Rect &)
+ OptRect(Coord, Coord, Coord, Coord)
+ OptRect(Point &, Point &)
+ OptRect(OptInterval &, OptInterval &)
+
+ Rect get()
+
+ bint isEmpty()
+ bint intersects(Rect &)
+ bint contains(Rect &)
+ bint intersects(OptRect &)
+ bint contains(OptRect &)
+ bint contains(Point &)
+
+ void unionWith(Rect &)
+ void unionWith(OptRect &)
+ void intersectWith(Rect &)
+ void intersectWith(OptRect &)
+ void expandTo(Point &)
+
+ OptRect &operator|(OptRect &)
+ OptRect &operator&(Rect &)
+ OptRect &operator&(OptRect &)
+
+ bint operator==(OptRect &)
+ bint operator==(Rect &)
+
+ bint operator!=(OptRect &)
+ bint operator!=(Rect &)
+
+cdef extern from "2geom/rect.h" namespace "Geom::Rect":
+ Rect from_xywh(Coord, Coord, Coord, Coord)
+ Rect from_xywh(Point &, Point &)
+ Rect infinite()
+
+cdef class cy_Rect:
+ cdef Rect* thisptr
+
+cdef cy_Rect wrap_Rect(Rect p)
+
+cdef class cy_OptRect:
+ cdef OptRect* thisptr
+
+cdef cy_OptRect wrap_OptRect(OptRect p)
+
+
+cdef extern from "2geom/int-rect.h" namespace "Geom":
+ #redeclaring because cython complains about ambiguous overloading otherwise
+ cdef cppclass OptIntRect:
+ OptIntRect()
+ OptIntRect(IntRect &)
+ OptIntRect(IntCoord, IntCoord, IntCoord, IntCoord)
+ OptIntRect(IntPoint &, IntPoint &)
+ OptIntRect(OptIntInterval &, OptIntInterval &)
+
+ IntRect get()
+
+ bint isEmpty()
+ bint intersects(IntRect &)
+ bint contains(IntRect &)
+ bint intersects(OptIntRect &)
+ bint contains(OptIntRect &)
+
+ bint contains(Point &)
+ bint contains(IntPoint &)
+
+ void unionWith(IntRect &)
+ void unionWith(OptIntRect &)
+ void intersectWith(IntRect &)
+ void intersectWith(OptIntRect &)
+
+ void expandTo(IntPoint &)
+
+ OptIntRect &operator|(OptIntRect &)
+ OptIntRect &operator&(IntRect &)
+ OptIntRect &operator&(OptIntRect &)
+
+ bint operator==(OptIntRect &)
+ bint operator==(IntRect &)
+
+ bint operator!=(OptIntRect &)
+ bint operator!=(IntRect &)
+
diff --git a/src/cython/_cy_rectangle.pyx b/src/cython/_cy_rectangle.pyx
new file mode 100644
index 0000000..1d1b687
--- /dev/null
+++ b/src/cython/_cy_rectangle.pyx
@@ -0,0 +1,2202 @@
+from numbers import Number
+
+from _common_decl cimport *
+from cython.operator cimport dereference as deref
+
+from _cy_affine cimport cy_Affine, get_Affine, is_transform
+
+
+cdef class cy_GenericInterval:
+ """
+ Represents all numbers between min and max.
+
+ Corresponds to GenericInterval in 2geom. min and max can be arbitrary
+ python object, they just have to implement arithmetic operations and
+ comparison - fractions.Fraction works. This is a bit experimental,
+ it leak memory right now.
+ """
+
+ cdef GenericInterval[WrappedPyObject]* thisptr
+
+ def __cinit__(self, u = 0, v = None):
+ """Create GenericInterval from either one or two values."""
+ if v is None:
+ self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u) )
+ else:
+ self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u), WrappedPyObject(v) )
+
+ def __str__(self):
+ """str(self)"""
+ return "[{}, {}]".format(self.min(), self.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if self.is_singular():
+ return "GenericInterval({})".format( str(self.min()) )
+ return "GenericInterval({}, {})".format( str(self.min()) , str(self.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(self, i):
+ """Create GenericInterval with same minimum and maximum as argument."""
+ return cy_GenericInterval( i.min(), i.max() )
+
+ @classmethod
+ def from_list(self, lst):
+ """Create GenericInterval containing all values in list."""
+ if len(lst) == 0:
+ return cy_GenericInterval()
+ ret = cy_GenericInterval(lst[0])
+ for i in lst[1:]:
+ ret.expand_to(i)
+ return ret
+
+ def min(self):
+ """Return minimal value of interval."""
+ return self.thisptr.min().getObj()
+
+ def max(self):
+ """Return maximal value of interval."""
+ return self.thisptr.max().getObj()
+ def extent(self):
+ """Return difference between maximal and minimal value."""
+ return self.thisptr.extent().getObj()
+
+ def middle(self):
+ """Return midpoint of interval."""
+ return self.thisptr.middle().getObj()
+
+ def is_singular(self):
+ """Test for one-valued interval."""
+ return self.thisptr.isSingular()
+
+ def set_min(self, val):
+ """Set minimal value."""
+ self.thisptr.setMin( WrappedPyObject(val) )
+
+ def set_max(self, val):
+ """Set maximal value."""
+ self.thisptr.setMax( WrappedPyObject(val) )
+
+ def expand_to(self, val):
+ """Create smallest superset of self containing value."""
+ self.thisptr.expandTo( WrappedPyObject(val) )
+
+ def expand_by(self, val):
+ """Push both boundaries by value."""
+ self.thisptr.expandBy( WrappedPyObject(val) )
+ def union_with(self, cy_GenericInterval interval):
+ """self = self | other"""
+ self.thisptr.unionWith( deref(interval.thisptr) )
+
+ def contains(self, other):
+ """Check if interval contains value."""
+ return self.thisptr.contains( WrappedPyObject(other) )
+
+ def contains_interval(self, cy_GenericInterval other):
+ """Check if interval contains every point of interval."""
+ return self.thisptr.contains( deref(other.thisptr) )
+
+ def intersects(self, cy_GenericInterval other):
+ """Check for intersecting intervals."""
+ return self.thisptr.intersects(deref( other.thisptr ))
+
+ def __neg__(self):
+ """Return interval with negated boundaries."""
+ return wrap_GenericInterval(-deref(self.thisptr))
+
+ def _add_pyobj(self, X):
+ return wrap_GenericInterval(deref(self.thisptr) + WrappedPyObject(X) )
+ def _sub_pyobj(self, X):
+ return wrap_GenericInterval(deref(self.thisptr) - WrappedPyObject(X) )
+
+ def _add_interval(self, cy_GenericInterval I):
+ return wrap_GenericInterval(deref(self.thisptr)+deref(I.thisptr))
+ def _sub_interval(self, cy_GenericInterval I):
+ return wrap_GenericInterval(deref(self.thisptr)-deref(I.thisptr))
+
+ def __add__(cy_GenericInterval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ if isinstance(other, cy_GenericInterval):
+ return self._add_interval(other)
+ else:
+ return self._add_pyobj(other)
+
+ def __sub__(cy_GenericInterval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ if isinstance(other, cy_GenericInterval):
+ return self._sub_interval(other)
+ else:
+ return self._sub_pyobj(other)
+
+ def __or__(cy_GenericInterval self, cy_GenericInterval I):
+ """Return a union of two intervals"""
+ return wrap_GenericInterval(deref(self.thisptr)|deref(I.thisptr))
+
+ def _eq(self, cy_GenericInterval other):
+ return deref(self.thisptr)==deref(other.thisptr)
+
+ def _neq(self, cy_GenericInterval other):
+ return deref(self.thisptr)!=deref(other.thisptr)
+
+ def __richcmp__(cy_GenericInterval self, other, op):
+ """Intervals are not ordered."""
+ if op == 2:
+ return self._eq(other)
+ elif op == 3:
+ return self._neq(other)
+
+cdef cy_GenericInterval wrap_GenericInterval(GenericInterval[WrappedPyObject] p):
+ cdef GenericInterval[WrappedPyObject] * retp = new GenericInterval[WrappedPyObject](WrappedPyObject(0))
+ retp[0] = p
+ cdef cy_GenericInterval r = cy_GenericInterval.__new__(
+ cy_GenericInterval, 0, 0)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_GenericOptInterval:
+
+ """Class representing optionally empty interval.
+
+ Empty interval has False bool value, and using methods that require
+ non-empty interval will result in ValueError. This is supposed to be
+ used this way:
+
+ >>> C = A & B
+ >>> if C:
+ >>> print C.min()
+
+ This class represents GenericOptInterval with python object boundaries.
+ It tries to model behaviour of std::optional.
+ """
+
+ cdef GenericOptInterval[WrappedPyObject]* thisptr
+
+ def __cinit__(self, u = None, v = None):
+ """Create interval from boundaries.
+
+ Using no arguments, you will end up with empty interval."""
+ if u is None:
+ self.thisptr = new GenericOptInterval[WrappedPyObject]()
+ elif v is None:
+ self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u))
+ else:
+ self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u), WrappedPyObject(v) )
+
+ def __bool__(self):
+ """Logical value of interval, False only for empty interval."""
+ return not self.thisptr.isEmpty()
+
+ def __str__(self):
+ """str(self)"""
+ if not self:
+ return "[]"
+ return "[{}, {}]".format(self.Interval.min(), self.Interval.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if not self:
+ return "GenericOptInterval()"
+ if self.Interval.isSingular():
+ return "GenericOptInterval({})".format( str(self.Interval.min()) )
+ return "GenericOptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(self, i):
+ """Create interval from existing interval."""
+ if hasattr(i, "isEmpty"):
+ if i.isEmpty():
+ return cy_GenericOptInterval()
+ else:
+ return cy_GenericOptInterval.from_Interval(i.Interval)
+ return cy_GenericOptInterval( i.min(), i.max() )
+
+ @classmethod
+ def from_list(self, lst):
+ """Create interval containing all values in list.
+
+ Empty list will result in empty interval."""
+ if len(lst) == 0:
+ return cy_GenericOptInterval()
+ ret = cy_GenericOptInterval(lst[0])
+ for i in lst[1:]:
+ ret.Interval.expandTo(i)
+ return ret
+
+ property Interval:
+ """Get underlying GenericInterval."""
+ def __get__(self):
+ if self.is_empty():
+ raise ValueError("Interval is empty.")
+ else:
+ return wrap_GenericInterval(self.thisptr.get())
+
+ def is_empty(self):
+ """Check whether interval is empty set."""
+ return self.thisptr.isEmpty()
+
+ def union_with(self, cy_GenericOptInterval o):
+ """self = self | other"""
+ self.thisptr.unionWith( deref(o.thisptr) )
+
+ def intersect_with(cy_GenericOptInterval self, cy_GenericOptInterval o):
+ """self = self & other"""
+ self.thisptr.intersectWith( deref(o.thisptr) )
+
+ def __or__(cy_GenericOptInterval self, cy_GenericOptInterval o):
+ """Return a union of two intervals."""
+ return wrap_GenericOptInterval(deref(self.thisptr) | deref(o.thisptr))
+
+ def __and__(cy_GenericOptInterval self, cy_GenericOptInterval o):
+ """Return an intersection of two intervals."""
+ return wrap_GenericOptInterval(deref(self.thisptr) & deref(o.thisptr))
+
+ def __richcmp__(cy_GenericOptInterval self, cy_GenericOptInterval o, int op):
+ """Intervals are not ordered."""
+ if op == 2:
+ return deref(self.thisptr) == deref(o.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(o.thisptr)
+ return NotImplemented
+
+
+ def _get_Interval_method(self, name):
+ def f(*args, **kwargs):
+ if self.is_empty():
+ raise ValueError("GenericOptInterval is empty.")
+ else:
+ return self.Interval.__getattribute__(name)(*args, **kwargs)
+ return f
+
+ def __getattr__(self, name):
+
+ Interval_methods = set(['contains', 'contains_interval',
+ 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list',
+ 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max',
+ 'set_min', 'union_with'])
+
+ if name in Interval_methods:
+ return self._get_Interval_method(name)
+ else:
+ raise AttributeError("GenericOptInterval instance has no attribute \"{}\"".format(name))
+
+ def _wrap_Interval_method(self, name, *args, **kwargs):
+ if self.isEmpty():
+ raise ValueError("GenericOptInterval is empty.")
+ else:
+ return self.Interval.__getattr__(name)(*args, **kwargs)
+
+ #declaring these by hand, because they take fixed number of arguments,
+ #which is enforced by cython
+
+ def __neg__(self):
+ """Return interval with negated boundaries."""
+ return self._wrap_Interval_method("__sub__")
+
+ def __add__(cy_Interval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ return self._wrap_Interval_method("__add__", other)
+
+ def __sub__(cy_Interval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ return self._wrap_Interval_method("__sub__", other)
+
+cdef cy_GenericOptInterval wrap_GenericOptInterval(GenericOptInterval[WrappedPyObject] p):
+ cdef GenericOptInterval[WrappedPyObject] * retp = new GenericOptInterval[WrappedPyObject]()
+ retp[0] = p
+ cdef cy_GenericOptInterval r = cy_GenericOptInterval.__new__(cy_GenericOptInterval)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Interval:
+
+ """Class representing interval on real line.
+
+ Corresponds to Interval class in 2geom.
+ """
+
+ def __cinit__(self, u = None, v = None):
+ """Create interval from it's boundaries.
+
+ One argument will create interval consisting that value, no
+ arguments create Interval(0).
+ """
+ if u is None:
+ self.thisptr = new Interval()
+ elif v is None:
+ self.thisptr = new Interval(<Coord>float(u))
+ else:
+ self.thisptr = new Interval(<Coord>float(u), <Coord>float(v))
+
+ def __str__(self):
+ """str(self)"""
+ return "[{}, {}]".format(self.min(), self.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if self.is_singular():
+ return "Interval({})".format( str(self.min()) )
+ return "Interval({}, {})".format( str(self.min()) , str(self.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(c, i):
+ """Create Interval with same boundaries as argument."""
+ return cy_Interval( i.min(), i.max() )
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create interval containing all values in a list."""
+ if len(lst) == 0:
+ return cy_Interval()
+ ret = cy_Interval(lst[0])
+ for i in lst[1:]:
+ ret.expand_to(i)
+ return ret
+
+ def min(self):
+ """Return minimal boundary."""
+ return self.thisptr.min()
+
+ def max(self):
+ """Return maximal boundary."""
+ return self.thisptr.max()
+
+ def extent(self):
+ """Return length of interval."""
+ return self.thisptr.extent()
+
+ def middle(self):
+ """Return middle value."""
+ return self.thisptr.middle()
+
+ def set_min(self, Coord val):
+ """Set minimal value."""
+ self.thisptr.setMin(val)
+
+ def set_max(self, Coord val):
+ """Set maximal value."""
+ self.thisptr.setMax(val)
+
+ def expand_to(self, Coord val):
+ """Set self to smallest superset of set containing value."""
+ self.thisptr.expandTo(val)
+
+ def expand_by(self, Coord amount):
+ """Move both boundaries by amount."""
+ self.thisptr.expandBy(amount)
+
+ def union_with(self, cy_Interval a):
+ """self = self | other"""
+ self.thisptr.unionWith(deref( a.thisptr ))
+
+# Not exposing this - deprecated
+# def __getitem__(self, unsigned int i):
+# return deref(self.thisptr)[i]
+
+ def is_singular(self):
+ """Test if interval contains only one value."""
+ return self.thisptr.isSingular()
+
+ def isFinite(self):
+ """Test for finiteness of interval's extent."""
+ return self.thisptr.isFinite()
+
+ def contains(cy_Interval self, other):
+ """Test if interval contains number."""
+ return self.thisptr.contains(float(other))
+
+ def contains_interval(cy_Interval self, cy_Interval other):
+ """Test if interval contains another interval."""
+ return self.thisptr.contains( deref(other.thisptr) )
+
+ def intersects(self, cy_Interval val):
+ """Test for intersection of intervals."""
+ return self.thisptr.intersects(deref( val.thisptr ))
+
+ def interior_contains(cy_Interval self, other):
+ """Test if interior of iterval contains number."""
+ return self.thisptr.interiorContains(float(other))
+
+ def interior_contains_interval(cy_Interval self, cy_Interval other):
+ """Test if interior of interval contains another interval."""
+ return self.thisptr.interiorContains( <Interval &> deref(other.thisptr) )
+
+
+ def interior_intersects(self, cy_Interval val):
+ """Test for intersection of interiors of two points."""
+ return self.thisptr.interiorIntersects(deref( val.thisptr ))
+
+ def _cmp_Interval(cy_Interval self, cy_Interval other, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+ def _cmp_IntInterval(cy_Interval self, cy_IntInterval other, op):
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+
+ def __richcmp__(cy_Interval self, other, op):
+ """Intervals are not ordered."""
+ if isinstance(other, cy_Interval):
+ return self._cmp_Interval(other, op)
+ elif isinstance(other, cy_IntInterval):
+ return self._cmp_IntInterval(other, op)
+
+ def __neg__(self):
+ """Return interval with negated boundaries."""
+ return wrap_Interval(-deref(self.thisptr))
+
+ def _add_number(self, Coord X):
+ return wrap_Interval(deref(self.thisptr)+X)
+ def _sub_number(self, Coord X):
+ return wrap_Interval(deref(self.thisptr)-X)
+ def _mul_number(self, Coord X):
+ return wrap_Interval(deref(self.thisptr)*X)
+
+ def _add_interval(self, cy_Interval I):
+ return wrap_Interval(deref(self.thisptr)+deref(I.thisptr))
+ def _sub_interval(self, cy_Interval I):
+ return wrap_Interval(deref(self.thisptr)-deref(I.thisptr))
+ def _mul_interval(self, cy_Interval I):
+ return wrap_Interval(deref(self.thisptr)*deref(I.thisptr))
+
+ def __mul__(cy_Interval self, other):
+ """Multiply interval by interval or number.
+
+ Multiplying by number simply multiplies boundaries,
+ multiplying intervals creates all values that can be written as
+ product i*j of i in I and j in J.
+ """
+ if isinstance(other, Number):
+ return self._mul_number(float(other))
+ else:
+ return self._mul_interval(other)
+
+ def __add__(cy_Interval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ if isinstance(other, Number):
+ return self._add_number(float(other))
+ else:
+ return self._add_interval(other)
+
+ def __sub__(cy_Interval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ if isinstance(other, Number):
+ return self._sub_number(float(other))
+ else:
+ return self._sub_interval(other)
+
+ def __div__(cy_Interval self, Coord s):
+ """Divide boundaries by number."""
+ return wrap_Interval(deref(self.thisptr)/s)
+
+ def __or__(cy_Interval self, cy_Interval I):
+ """Return union of two intervals."""
+ return wrap_Interval(deref(self.thisptr)|deref(I.thisptr))
+
+ def round_outwards(self):
+ """Create the smallest IntIterval that is superset."""
+ return wrap_IntInterval(self.thisptr.roundOutwards())
+
+ def round_inwards(self):
+ """Create the largest IntInterval that is subset."""
+ return wrap_OptIntInterval(self.thisptr.roundInwards())
+
+cdef cy_Interval wrap_Interval(Interval p):
+ cdef Interval * retp = new Interval()
+ retp[0] = p
+ cdef cy_Interval r = cy_Interval.__new__(cy_Interval)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_OptInterval:
+
+ """Class representing optionally empty interval on real line.
+
+ Empty interval has False bool value, and using methods that require
+ non-empty interval will result in ValueError. This is supposed to be
+ used this way:
+
+ >>> C = A & B
+ >>> if C:
+ >>> print C.min()
+
+ This class represents OptInterval. It tries to model behaviour of
+ std::optional.
+ """
+
+ def __cinit__(self, u = None, v = None):
+ """Create optionally empty interval form it's endpoints.
+
+ No arguments will result in empty interval.
+ """
+ if u is None:
+ self.thisptr = new OptInterval()
+ elif v is None:
+ self.thisptr = new OptInterval(<Coord>float(u))
+ else:
+ self.thisptr = new OptInterval(<Coord>float(u), <Coord>float(v))
+
+ def __bool__(self):
+ """Only empty interval is False."""
+ return not self.thisptr.isEmpty()
+
+ def __str__(self):
+ """str(self)"""
+ if not self:
+ return "[]"
+ return "[{}, {}]".format(self.Interval.min(), self.Interval.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if not self:
+ return "OptInterval()"
+ if self.Interval.isSingular():
+ return "OptInterval({})".format( str(self.Interval.min()) )
+ return "OptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(cls, i):
+ """Create interval from other (possibly empty) interval."""
+ if hasattr(i, "isEmpty"):
+ if i.isEmpty():
+ return cy_OptInterval()
+ else:
+ return cy_OptInterval.from_Interval(i.Interval)
+ return cy_OptInterval( i.min(), i.max() )
+
+ @classmethod
+ def from_list(self, lst):
+ """Create interval containing all values in list.
+
+ Empty list will result in empty interval."""
+ if len(lst) == 0:
+ return cy_OptInterval()
+ ret = cy_OptInterval(lst[0])
+ for i in lst[1:]:
+ ret.Interval.expandTo(i)
+ return ret
+
+ property Interval:
+ """Get underlying Interval."""
+ def __get__(self):
+ if self.is_empty():
+ raise ValueError("Interval is empty.")
+ else:
+ return wrap_Interval(self.thisptr.get())
+
+ def is_empty(self):
+ """Test for empty interval."""
+ return self.thisptr.isEmpty()
+
+ def union_with(self, cy_OptInterval o):
+ """self = self | other"""
+ self.thisptr.unionWith( deref(o.thisptr) )
+
+ def intersect_with(cy_OptInterval self, cy_OptInterval o):
+ """self = self & other"""
+ self.thisptr.intersectWith( deref(o.thisptr) )
+
+ def __or__(cy_OptInterval self, cy_OptInterval o):
+ """Return union of intervals."""
+ return wrap_OptInterval(deref(self.thisptr) | deref(o.thisptr))
+
+ def __and__(cy_OptInterval self, cy_OptInterval o):
+ """Return intersection of intervals."""
+ return wrap_OptInterval(deref(self.thisptr) & deref(o.thisptr))
+
+ def _get_Interval_method(self, name):
+ def f(*args, **kwargs):
+ if self.is_empty():
+ raise ValueError("OptInterval is empty.")
+ else:
+ return self.Interval.__getattribute__(name)(*args, **kwargs)
+ return f
+
+ def __getattr__(self, name):
+
+ Interval_methods = set(['contains', 'contains_interval', 'expand_by',
+ 'expand_to', 'extent', 'from_Interval', 'from_list',
+ 'interior_contains', 'interior_contains_interval',
+ 'interior_intersects', 'intersects', 'isFinite', 'is_singular',
+ 'max', 'middle', 'min', 'round_inwards', 'round_outwards',
+ 'set_max', 'set_min', 'union_with'])
+
+ if name in Interval_methods:
+ return self._get_Interval_method(name)
+ else:
+ raise AttributeError("OptInterval instance has no attribute \"{}\"".format(name))
+
+ def _wrap_Interval_method(self, name, *args, **kwargs):
+ if self.isEmpty():
+ raise ValueError("OptInterval is empty.")
+ else:
+ return self.Interval.__getattr__(name)(*args, **kwargs)
+
+ #declaring these by hand, because they take fixed number of arguments,
+ #which is enforced by cython
+
+ def __neg__(self):
+ """Return interval with negated boundaries."""
+ return self._wrap_Interval_method("__sub__")
+
+ def __mul__(cy_Interval self, other):
+ """Multiply interval by interval or number.
+
+ Multiplying by number simply multiplies boundaries,
+ multiplying intervals creates all values that can be written as
+ product i*j of i in I and j in J.
+ """
+ return self._wrap_Interval_method("__mul__", other)
+
+ def __add__(cy_Interval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ return self._wrap_Interval_method("__add__", other)
+
+ def __sub__(cy_Interval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ return self._wrap_Interval_method("__sub__", other)
+
+ def __div__(cy_Interval self, other):
+ """Divide boundaries by number."""
+ return self._wrap_Interval_method("__div__", other)
+
+cdef cy_OptInterval wrap_OptInterval(OptInterval p):
+ cdef OptInterval * retp = new OptInterval()
+ retp[0] = p
+ cdef cy_OptInterval r = cy_OptInterval.__new__(cy_OptInterval)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_IntInterval:
+
+ """Class representing interval of integers.
+
+ Corresponds to IntInterval class in 2geom.
+ """
+
+ cdef IntInterval* thisptr
+
+ def __cinit__(self, u = None, v = None):
+ """Create interval from it's boundaries.
+
+ One argument will create interval consisting that value, no
+ arguments create IntInterval(0).
+ """
+ if u is None:
+ self.thisptr = new IntInterval()
+ elif v is None:
+ self.thisptr = new IntInterval(<IntCoord>int(u))
+ else:
+ self.thisptr = new IntInterval(<IntCoord>int(u), <IntCoord>int(v))
+
+ def __str__(self):
+ """str(self)"""
+ return "[{}, {}]".format(self.min(), self.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if self.is_singular():
+ return "IntInterval({})".format( str(self.min()) )
+ return "IntInterval({}, {})".format( str(self.min()) , str(self.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(cls, i):
+ return cy_IntInterval( int(i.min()), int(i.max()) )
+
+ @classmethod
+ def from_list(cls, lst):
+ if len(lst) == 0:
+ return cy_IntInterval()
+ ret = cy_IntInterval(lst[0])
+ for i in lst[1:]:
+ ret.expand_to(i)
+ return ret
+
+ def min(self):
+ """Return minimal boundary."""
+ return self.thisptr.min()
+
+ def max(self):
+ """Return maximal boundary."""
+ return self.thisptr.max()
+
+ def extent(self):
+ """Return length of interval."""
+ return self.thisptr.extent()
+
+ def middle(self):
+ """Return middle value."""
+ return self.thisptr.middle()
+
+ def set_min(self, IntCoord val):
+ """Set minimal value."""
+ self.thisptr.setMin(val)
+
+ def set_max(self, IntCoord val):
+ """Set maximal value."""
+ self.thisptr.setMax(val)
+
+ def expand_to(self, IntCoord val):
+ """Set self to smallest superset of set containing value."""
+ self.thisptr.expandTo(val)
+
+ def expand_by(self, IntCoord amount):
+ """Move both boundaries by amount."""
+ self.thisptr.expandBy(amount)
+
+ def union_with(self, cy_IntInterval a):
+ """self = self | other"""
+ self.thisptr.unionWith(deref( a.thisptr ))
+
+# Not exposing this - deprecated
+# def __getitem__(self, unsigned int i):
+# return deref(self.thisptr)[i]
+
+ def is_singular(self):
+ """Test if interval contains only one value."""
+ return self.thisptr.isSingular()
+
+ def contains(cy_IntInterval self, other):
+ """Test if interval contains number."""
+ return self.thisptr.contains(<IntCoord> int(other))
+
+ def contains_interval(cy_IntInterval self, cy_IntInterval other):
+ """Test if interval contains another interval."""
+ return self.thisptr.contains( deref(other.thisptr) )
+
+ def intersects(self, cy_IntInterval val):
+ """Test for intersection with other interval."""
+ return self.thisptr.intersects(deref( val.thisptr ))
+
+ def __richcmp__(cy_IntInterval self, cy_IntInterval other, op):
+ """Intervals are not ordered."""
+ if op == 2:
+ return deref(self.thisptr) == deref(other.thisptr)
+ elif op == 3:
+ return deref(self.thisptr) != deref(other.thisptr)
+
+ def __neg__(self):
+ """Negate interval's endpoints."""
+ return wrap_IntInterval(-deref(self.thisptr))
+
+ def _add_number(self, IntCoord X):
+ return wrap_IntInterval(deref(self.thisptr)+X)
+ def _sub_number(self, IntCoord X):
+ return wrap_IntInterval(deref(self.thisptr)-X)
+
+ def _add_interval(self, cy_IntInterval I):
+ return wrap_IntInterval(deref(self.thisptr)+deref(I.thisptr))
+ def _sub_interval(self, cy_IntInterval I):
+ return wrap_IntInterval(deref(self.thisptr)-deref(I.thisptr))
+
+ def __add__(cy_IntInterval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ if isinstance(other, Number):
+ return self._add_number(int(other))
+ else:
+ return self._add_interval(other)
+
+ def __sub__(cy_IntInterval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ if isinstance(other, Number):
+ return self._sub_number(int(other))
+ else:
+ return self._sub_interval(other)
+
+ def __or__(cy_IntInterval self, cy_IntInterval I):
+ """Return union of two intervals."""
+ return wrap_IntInterval(deref(self.thisptr)|deref(I.thisptr))
+
+cdef cy_IntInterval wrap_IntInterval(IntInterval p):
+ cdef IntInterval * retp = new IntInterval()
+ retp[0] = p
+ cdef cy_IntInterval r = cy_IntInterval.__new__(cy_IntInterval)
+ r.thisptr = retp
+ return r
+
+cdef class cy_OptIntInterval:
+
+ """Class representing optionally empty interval of integers.
+
+ Empty interval has False bool value, and using methods that require
+ non-empty interval will result in ValueError. This is supposed to be
+ used this way:
+
+ >>> C = A & B
+ >>> if C:
+ >>> print C.min()
+
+ This class represents OptIntInterval. It tries to model behaviour of
+ std::optional.
+ """
+
+ cdef OptIntInterval* thisptr
+
+ def __cinit__(self, u = None, v = None):
+ """Create optionally empty interval form it's endpoints.
+
+ No arguments will result in empty interval.
+ """
+ if u is None:
+ self.thisptr = new OptIntInterval()
+ elif v is None:
+ self.thisptr = new OptIntInterval(<IntCoord>int(u))
+ else:
+ self.thisptr = new OptIntInterval(<IntCoord>int(u), <IntCoord>int(v))
+
+ def __bool__(self):
+ """Only empty interval is False."""
+ return not self.thisptr.isEmpty()
+
+ def __str__(self):
+ """str(self)"""
+ if not self:
+ return "[]"
+ return "[{}, {}]".format(self.Interval.min(), self.Interval.max())
+
+ def __repr__(self):
+ """repr(self)"""
+ if not self:
+ return "OptIntInterval()"
+ if self.Interval.isSingular():
+ return "OptIntInterval({})".format( str(self.Interval.min()) )
+ return "OptIntInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) )
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_Interval(self, i):
+ """Create interval from other (possibly empty) interval."""
+ if hasattr(i, "isEmpty"):
+ if i.isEmpty():
+ return cy_OptIntInterval()
+ else:
+ return cy_OptIntInterval.from_Interval(i.Interval)
+ return cy_OptIntInterval( i.min(), i.max() )
+
+ @classmethod
+ def from_list(self, lst):
+ """Create interval containing all values in list.
+
+ Empty list will result in empty interval."""
+ if len(lst) == 0:
+ return cy_OptIntInterval()
+ ret = cy_OptIntInterval(lst[0])
+ for i in lst[1:]:
+ ret.Interval.expandTo(i)
+ return ret
+
+ property Interval:
+ """Get underlying interval."""
+ def __get__(self):
+ return wrap_IntInterval(self.thisptr.get())
+
+ def is_empty(self):
+ """Test for empty interval."""
+ return self.thisptr.isEmpty()
+
+ def union_with(self, cy_OptIntInterval o):
+ """self = self | other"""
+ self.thisptr.unionWith( deref(o.thisptr) )
+
+ def intersect_with(cy_OptIntInterval self, cy_OptIntInterval o):
+ """self = self & other"""
+ self.thisptr.intersectWith( deref(o.thisptr) )
+
+ def __or__(cy_OptIntInterval self, cy_OptIntInterval o):
+ """Return a union of two intervals."""
+ return wrap_OptIntInterval(deref(self.thisptr) | deref(o.thisptr))
+
+ def __and__(cy_OptIntInterval self, cy_OptIntInterval o):
+ """Return an intersection of two intervals."""
+ return wrap_OptIntInterval(deref(self.thisptr) & deref(o.thisptr))
+
+ #TODO decide how to implement various combinations of comparisons!
+
+ def _get_Interval_method(self, name):
+ def f(*args, **kwargs):
+ if self.is_empty():
+ raise ValueError("OptInterval is empty.")
+ else:
+ return self.Interval.__getattribute__(name)(*args, **kwargs)
+ return f
+
+ def __getattr__(self, name):
+
+ Interval_methods = set(['contains', 'contains_interval',
+ 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list',
+ 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max',
+ 'set_min', 'union_with'])
+
+ if name in Interval_methods:
+ return self._get_Interval_method(name)
+ else:
+ raise AttributeError("OptIntInterval instance has no attribute \"{}\"".format(name))
+
+ def _wrap_Interval_method(self, name, *args, **kwargs):
+ if self.isEmpty():
+ raise ValueError("OptIntInterval is empty.")
+ else:
+ return self.Interval.__getattr__(name)(*args, **kwargs)
+
+ #declaring these by hand, because they take fixed number of arguments,
+ #which is enforced by cython
+
+ def __neg__(self):
+ """Negate interval's endpoints."""
+ return self._wrap_Interval_method("__sub__")
+
+
+ def __add__(cy_Interval self, other):
+ """Add interval or value to self.
+
+ Interval I+J consists of all values i+j such that i is in I and
+ j is in J
+
+ Interval I+x consists of all values i+x such that i is in I.
+ """
+ return self._wrap_Interval_method("__add__", other)
+
+ def __sub__(cy_Interval self, other):
+ """Substract interval or value.
+
+ Interval I-J consists of all values i-j such that i is in I and
+ j is in J
+
+ Interval I-x consists of all values i-x such that i is in I.
+ """
+ return self._wrap_Interval_method("__sub__", other)
+
+cdef cy_OptIntInterval wrap_OptIntInterval(OptIntInterval p):
+ cdef OptIntInterval * retp = new OptIntInterval()
+ retp[0] = p
+ cdef cy_OptIntInterval r = cy_OptIntInterval.__new__(cy_OptIntInterval)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_GenericRect:
+
+ """Class representing axis aligned rectangle, with arbitrary corners.
+
+ Plane in which the rectangle lies can have any object as a coordinates,
+ as long as they implement arithmetic operations and comparison.
+
+ This is a bit experimental, corresponds to GenericRect[C] templated
+ with (wrapped) python object.
+ """
+
+ cdef GenericRect[WrappedPyObject]* thisptr
+
+ def __cinit__(self, x0=0, y0=0, x1=0, y1=0):
+ """Create rectangle from it's top-left and bottom-right corners."""
+ self.thisptr = new GenericRect[WrappedPyObject](WrappedPyObject(x0),
+ WrappedPyObject(y0),
+ WrappedPyObject(x1),
+ WrappedPyObject(y1))
+
+ def __str__(self):
+ """str(self)"""
+ return "Rectangle with dimensions {}, topleft point {}".format(
+ str(self.dimensions()),
+ str(self.min()))
+
+ def __repr__(self):
+ """repr(self)"""
+ return "Rect({}, {}, {}, {})".format( str(self.left()),
+ str(self.top()),
+ str(self.right()),
+ str(self.bottom()) )
+
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_intervals(self, I, J):
+ """Create rectangle from two intervals.
+
+ First interval corresponds to side parallel with x-axis,
+ second one with side parallel with y-axis."""
+ return cy_GenericRect(I.min(), I.max(), J.min(), J.max())
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create rectangle containing all points in list.
+
+ These points are represented simply by 2-tuples.
+ """
+ ret = cy_GenericRect()
+ for a in lst:
+ ret.expand_to(a)
+ return ret
+
+ @classmethod
+ def from_xywh(cls, x, y, w, h):
+ """Create rectangle from topleft point and dimensions."""
+ return wrap_GenericRect( from_xywh(WrappedPyObject(x),
+ WrappedPyObject(y),
+ WrappedPyObject(w),
+ WrappedPyObject(h)))
+
+ def __getitem__(self, Dim2 d):
+ """self[i]"""
+ return wrap_GenericInterval( deref(self.thisptr)[d] )
+
+ def min(self):
+ """Get top-left point."""
+ return wrap_PyPoint( self.thisptr.min() )
+
+ def max(self):
+ """Get bottom-right point."""
+ return wrap_PyPoint( self.thisptr.max() )
+
+ def corner(self, unsigned int i):
+ """Get corners (modulo) indexed from 0 to 3."""
+ return wrap_PyPoint( self.thisptr.corner(i) )
+
+ def top(self):
+ """Get top coordinate."""
+ return self.thisptr.top().getObj()
+
+ def bottom(self):
+ """Get bottom coordinate."""
+ return self.thisptr.bottom().getObj()
+
+ def left(self):
+ """Get left coordinate."""
+ return self.thisptr.left().getObj()
+
+ def right(self):
+ """Get right coordinate."""
+ return self.thisptr.right().getObj()
+
+ def width(self):
+ """Get width."""
+ return self.thisptr.width().getObj()
+
+ def height(self):
+ """Get height."""
+ return self.thisptr.height().getObj()
+
+ #For some reason, Cpp aspectRatio returns Coord.
+ def aspectRatio(self):
+ """Get ratio between width and height."""
+ return float(self.width())/float(self.height())
+
+ def dimensions(self):
+ """Get dimensions as tuple."""
+ return wrap_PyPoint( self.thisptr.dimensions() )
+
+ def midpoint(self):
+ """Get midpoint as tuple."""
+ return wrap_PyPoint( self.thisptr.midpoint() )
+
+ def area(self):
+ """Get area."""
+ return self.thisptr.area().getObj()
+
+ def has_zero_area(self):
+ """Test for area being zero."""
+ return self.thisptr.hasZeroArea()
+
+ def max_extent(self):
+ """Get bigger value from width, height."""
+ return self.thisptr.maxExtent().getObj()
+
+ def min_extent(self):
+ """Get smaller value from width, height."""
+ return self.thisptr.minExtent().getObj()
+
+ def intersects(self, cy_GenericRect r):
+ """Check if rectangle intersects another rectangle."""
+ return self.thisptr.intersects(deref( r.thisptr ))
+
+ def contains(self, r):
+ """Check if rectangle contains point represented as tuple."""
+ if not isinstance(r, tuple):
+ raise TypeError("Tuple required to create point.")
+ return self.thisptr.contains( make_PyPoint(r) )
+
+ def contains_rect(self, cy_GenericRect r):
+ """Check if rectangle contains another rect."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def set_left(self, val):
+ """Set left coordinate."""
+ self.thisptr.setLeft( WrappedPyObject(val) )
+
+ def set_right(self, val):
+ """Set right coordinate."""
+ self.thisptr.setRight( WrappedPyObject(val) )
+
+ def set_top(self, val):
+ """Set top coordinate."""
+ self.thisptr.setTop( WrappedPyObject(val) )
+
+ def set_bottom(self, val):
+ """Set bottom coordinate."""
+ self.thisptr.setBottom( WrappedPyObject(val) )
+
+ def set_min(self, p):
+ """Set top-left point."""
+ self.thisptr.setMin(make_PyPoint(p))
+
+ def set_max(self, p):
+ """Set bottom-right point."""
+ self.thisptr.setMax(make_PyPoint(p))
+
+ def expand_to(self, p):
+ """Expand rectangle to contain point represented as tuple."""
+ self.thisptr.expandTo(make_PyPoint(p))
+
+ def union_with(self, cy_GenericRect b):
+ """self = self | other."""
+ self.thisptr.unionWith(deref( b.thisptr ))
+
+ def expand_by(self, x, y = None):
+ """Expand both intervals.
+
+ Either expand them both by one value, or each by different value.
+ """
+ if y is None:
+ self.thisptr.expandBy(WrappedPyObject(x))
+ else:
+ self.thisptr.expandBy(WrappedPyObject(x),
+ WrappedPyObject(y))
+
+
+ def __add__(cy_GenericRect self, p):
+ """Offset rectangle by point."""
+ return wrap_GenericRect( deref(self.thisptr) + make_PyPoint(p) )
+
+ def __sub__(cy_GenericRect self, p):
+ """Offset rectangle by -point."""
+ return wrap_GenericRect( deref(self.thisptr) - make_PyPoint(p) )
+
+ def __or__(cy_GenericRect self, cy_GenericRect o):
+ """Return union of two rects - it's actually bounding rect of union."""
+ return wrap_GenericRect( deref(self.thisptr) | deref( o.thisptr ))
+
+ def __richcmp__(cy_GenericRect self, cy_GenericRect o, int op):
+ """Rectangles are not ordered."""
+ if op == 2:
+ return deref(self.thisptr) == deref(o.thisptr)
+ if op == 3:
+ return deref(self.thisptr) != deref(o.thisptr)
+
+cdef PyPoint make_PyPoint(p):
+ return PyPoint( WrappedPyObject(p[0]), WrappedPyObject(p[1]) )
+
+#D2[WrappedPyObject] is converted to tuple
+cdef wrap_PyPoint(PyPoint p):
+ return (p[0].getObj(), p[1].getObj())
+
+cdef cy_GenericRect wrap_GenericRect(GenericRect[WrappedPyObject] p):
+ cdef WrappedPyObject zero = WrappedPyObject(0)
+ cdef GenericRect[WrappedPyObject] * retp = new GenericRect[WrappedPyObject](zero, zero, zero, zero)
+ retp[0] = p
+ cdef cy_GenericRect r = cy_GenericRect.__new__(cy_GenericRect)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_Rect:
+
+ """Class representing axis-aligned rectangle in 2D real plane.
+
+ Corresponds to Rect class in 2geom."""
+
+ def __cinit__(self, Coord x0=0, Coord y0=0, Coord x1=0, Coord y1=0):
+ """Create Rect from coordinates of its top-left and bottom-right corners."""
+ self.thisptr = new Rect(x0, y0, x1, y1)
+
+ def __str__(self):
+ """str(self)"""
+ return "Rectangle with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min()))
+
+ def __repr__(self):
+ """repr(self)"""
+ return "Rect({}, {}, {}, {})".format( str(self.left()),
+ str(self.top()),
+ str(self.right()),
+ str(self.bottom()))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_Point p0, cy_Point p1):
+ """Create rectangle from it's top-left and bottom-right corners."""
+ return wrap_Rect( Rect(deref(p0.thisptr), deref(p1.thisptr)) )
+
+ @classmethod
+ def from_intervals(cls, I, J):
+ """Create rectangle from two intervals representing its sides."""
+ return wrap_Rect( Rect( float(I.min()),
+ float(J.min()),
+ float(I.max()),
+ float(J.max()) ) )
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create rectangle containing all points in list."""
+ if lst == []:
+ return cy_Rect()
+ if len(lst) == 1:
+ return cy_Rect.from_points(lst[0], lst[0])
+ ret = cy_Rect.from_points(lst[0], lst[1])
+ for a in lst:
+ ret.expand_to(a)
+ return ret
+
+ @classmethod
+ def from_xywh(cls, x, y, w, h):
+ """Create rectangle from it's topleft point and dimensions."""
+ return wrap_Rect( from_xywh(<Coord> x,
+ <Coord> y,
+ <Coord> w,
+ <Coord> h) )
+
+ @classmethod
+ def infinite(self):
+ """Create infinite rectangle."""
+ return wrap_Rect(infinite())
+
+ def __getitem__(self, Dim2 d):
+ """self[d]"""
+ return wrap_Interval( deref(self.thisptr)[d] )
+
+ def min(self):
+ """Get top-left point."""
+ return wrap_Point( self.thisptr.min() )
+
+ def max(self):
+ """Get bottom-right point."""
+ return wrap_Point( self.thisptr.max() )
+
+ def corner(self, unsigned int i):
+ """Get corners (modulo) indexed from 0 to 3."""
+ return wrap_Point( self.thisptr.corner(i) )
+
+ def top(self):
+ """Get top coordinate."""
+ return self.thisptr.top()
+
+ def bottom(self):
+ """Get bottom coordinate."""
+ return self.thisptr.bottom()
+
+ def left(self):
+ """Get left coordinate."""
+ return self.thisptr.left()
+
+ def right(self):
+ """Get right coordinate."""
+ return self.thisptr.right()
+
+ def width(self):
+ """Get width."""
+ return self.thisptr.width()
+
+ def height(self):
+ """Get height."""
+ return self.thisptr.height()
+
+ def aspect_ratio(self):
+ """Get ratio between width and height."""
+ return self.thisptr.aspectRatio()
+
+ def dimensions(self):
+ """Get dimensions as point."""
+ return wrap_Point( self.thisptr.dimensions() )
+
+ def midpoint(self):
+ """Get midpoint."""
+ return wrap_Point( self.thisptr.midpoint() )
+
+ def area(self):
+ """Get area."""
+ return self.thisptr.area()
+
+ def has_zero_area(self, Coord eps = EPSILON):
+ """Test for area being zero."""
+ return self.thisptr.hasZeroArea(eps)
+
+ def max_extent(self):
+ """Get bigger value from width, height."""
+ return self.thisptr.maxExtent()
+
+ def min_extent(self):
+ """Get smaller value from width, height."""
+ return self.thisptr.minExtent()
+
+ def intersects(self, cy_Rect r):
+ """Check if rectangle intersects another rectangle."""
+ return self.thisptr.intersects(deref( r.thisptr ))
+
+ def contains(self, cy_Point r):
+ """Check if rectangle contains point."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def contains_rect(self, cy_Rect r):
+ """Check if rectangle contains another rect."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def interior_intersects(self, cy_Rect r):
+ """Check if interior of self intersects another rectangle."""
+ return self.thisptr.interiorIntersects(deref( r.thisptr ))
+
+ def interior_contains(self, cy_Point other):
+ """Check if interior of self contains point."""
+ return self.thisptr.interiorContains( deref( (<cy_Point> other).thisptr ) )
+
+ def interior_contains_rect(self, other):
+ """Check if interior of self contains another rectangle."""
+ if isinstance(other, cy_Rect):
+ return self.thisptr.interiorContains( deref( (<cy_Rect> other).thisptr ) )
+ elif isinstance(other, cy_OptRect):
+ return self.thisptr.interiorContains( deref( (<cy_OptRect> other).thisptr ) )
+
+ def set_left(self, Coord val):
+ """Set left coordinate."""
+ self.thisptr.setLeft(val)
+
+ def set_right(self, Coord val):
+ """Set right coordinate."""
+ self.thisptr.setRight(val)
+
+ def set_top(self, Coord val):
+ """Set top coordinate."""
+ self.thisptr.setTop(val)
+
+ def set_bottom(self, Coord val):
+ """Set bottom coordinate."""
+ self.thisptr.setBottom(val)
+
+ def set_min(self, cy_Point p):
+ """Set top-left point."""
+ self.thisptr.setMin( deref( p.thisptr ) )
+
+ def set_max(self, cy_Point p):
+ """Set bottom-right point."""
+ self.thisptr.setMax( deref( p.thisptr ))
+
+ def expand_to(self, cy_Point p):
+ """Expand rectangle to contain point represented as tuple."""
+ self.thisptr.expandTo( deref( p.thisptr ) )
+
+ def union_with(self, cy_Rect b):
+ """self = self | other."""
+ self.thisptr.unionWith(deref( b.thisptr ))
+
+ def expand_by(cy_Rect self, x, y = None):
+ """Expand both intervals.
+
+ Either expand them both by one value, or each by different value.
+ """
+ if y is None:
+ if isinstance(x, cy_Point):
+ self.thisptr.expandBy( deref( (<cy_Point> x).thisptr ) )
+ else:
+ self.thisptr.expandBy( <Coord> x)
+ else:
+ self.thisptr.expandBy( <Coord> x,
+ <Coord> y)
+
+ def __add__(cy_Rect self, cy_Point p):
+ """Offset rectangle by point."""
+ return wrap_Rect( deref(self.thisptr) + deref( p.thisptr ) )
+
+ def __sub__(cy_Rect self, cy_Point p):
+ """Offset rectangle by -point."""
+ return wrap_Rect( deref(self.thisptr) - deref( p.thisptr ) )
+
+ def __mul__(cy_Rect self, t):
+ """Apply transform to rectangle."""
+ cdef Affine at
+ if is_transform(t):
+ at = get_Affine(t)
+ return wrap_Rect( deref(self.thisptr) * at )
+
+ def __or__(cy_Rect self, cy_Rect o):
+ """Return union of two rects - it's actually bounding rect of union."""
+ return wrap_Rect( deref(self.thisptr) | deref( o.thisptr ))
+
+ def __richcmp__(cy_Rect self, o, int op):
+ """Rectangles are not ordered."""
+ if op == 2:
+ if isinstance(o, cy_Rect):
+ return deref(self.thisptr) == deref( (<cy_Rect> o).thisptr)
+ elif isinstance(o, cy_IntRect):
+ return deref(self.thisptr) == deref( (<cy_IntRect> o).thisptr)
+ if op == 3:
+ if isinstance(o, cy_Rect):
+ return deref(self.thisptr) != deref( (<cy_Rect> o).thisptr)
+ elif isinstance(o, cy_IntRect):
+ return deref(self.thisptr) != deref( (<cy_IntRect> o).thisptr)
+
+ def round_inwards(self):
+ """Create OptIntRect rounding inwards."""
+ return wrap_OptIntRect(self.thisptr.roundInwards())
+
+ def round_outwards(self):
+ """Create IntRect rounding outwards."""
+ return wrap_IntRect(self.thisptr.roundOutwards())
+
+ @classmethod
+ def distanceSq(cls, cy_Point p, cy_Rect rect):
+ """Compute square of distance between point and rectangle."""
+ return distanceSq( deref(p.thisptr), deref(rect.thisptr) )
+
+ @classmethod
+ def distance(cls, cy_Point p, cy_Rect rect):
+ """Compute distance between point and rectangle."""
+ return distance( deref(p.thisptr), deref(rect.thisptr) )
+
+cdef cy_Rect wrap_Rect(Rect p):
+ cdef Rect* retp = new Rect()
+ retp[0] = p
+ cdef cy_Rect r = cy_Rect.__new__(cy_Rect)
+ r.thisptr = retp
+ return r
+
+
+cdef class cy_OptRect:
+
+ """Class representing optionally empty rect in real plane.
+
+ This class corresponds to OptRect in 2geom, and it tries to mimic
+ the behaviour of std::optional. In addition to OptRect methods,
+ this class passes calls for Rect methods to underlying Rect class,
+ or throws ValueError when it's empty.
+ """
+
+ def __cinit__(self, x0=None, y0=None, x1=None, y1=None):
+ """Create OptRect from coordinates of top-left and bottom-right corners.
+
+ No arguments will result in empty rectangle.
+ """
+ if x0 is None:
+ self.thisptr = new OptRect()
+ else:
+ self.thisptr = new OptRect( float(x0),
+ float(y0),
+ float(x1),
+ float(y1) )
+
+ def __str__(self):
+ """str(self)"""
+ if self.is_empty():
+ return "Empty OptRect."
+ return "OptRect with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min()))
+
+ def __repr__(self):
+ """repr(self)"""
+ if self.is_empty():
+ return "OptRect()"
+ return "OptRect({}, {}, {}, {})".format( str(self.left()),
+ str(self.top()),
+ str(self.right()),
+ str(self.bottom()))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_Point p0, cy_Point p1):
+ """Create rectangle from it's top-left and bottom-right corners."""
+ return wrap_OptRect( OptRect(deref(p0.thisptr), deref(p1.thisptr)) )
+
+ @classmethod
+ def from_intervals(cls, I, J):
+ """Create rectangle from two intervals representing its sides."""
+ if hasattr(I, "isEmpty"):
+ if I.isEmpty():
+ return cy_OptRect()
+
+ if hasattr(J, "isEmpty"):
+ if J.isEmpty():
+ return cy_OptRect()
+
+ return wrap_OptRect( OptRect( float(I.min()),
+ float(J.min()),
+ float(I.max()),
+ float(J.max()) ) )
+
+ @classmethod
+ def from_rect(cls, r):
+ """Create OptRect from other, possibly empty, rectangle."""
+ if hasattr(r, "isEmpty"):
+ if r.isEmpty():
+ return cy_OptRect()
+
+ return cy_OptRect( r.min().x,
+ r.min().y,
+ r.max().x,
+ r.max().y )
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create OptRect containing all points in the list.
+
+ Empty list will result in empty OptRect.
+ """
+ if lst == []:
+ return cy_OptRect()
+ if len(lst) == 1:
+ return cy_OptRect.from_points(lst[0], lst[0])
+ ret = cy_OptRect.from_points(lst[0], lst[1])
+ for a in lst:
+ ret.expand_to(a)
+ return ret
+
+ property Rect:
+ """Get underlying Rect."""
+ def __get__(self):
+ if self.is_empty():
+ raise ValueError("Rect is empty.")
+ else:
+ return wrap_Rect(self.thisptr.get())
+
+ def __bool__(self):
+ """OptRect is False only when it's empty."""
+ return not self.thisptr.isEmpty()
+
+ def is_empty(self):
+ """Check for OptRect containing no points."""
+ return self.thisptr.isEmpty()
+
+ def intersects(self, other):
+ """Check if rectangle intersects another rectangle."""
+ if isinstance(other, cy_Rect):
+ return self.thisptr.intersects( deref( (<cy_Rect> other).thisptr ) )
+ elif isinstance(other, cy_OptRect):
+ return self.thisptr.intersects( deref( (<cy_OptRect> other).thisptr ) )
+
+ def contains(self, cy_Point r):
+ """Check if rectangle contains point."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def contains_rect(self, other):
+ """Check if rectangle contains another rect."""
+ if isinstance(other, cy_Rect):
+ return self.thisptr.contains( deref( (<cy_Rect> other).thisptr ) )
+ elif isinstance(other, cy_OptRect):
+ return self.thisptr.contains( deref( (<cy_OptRect> other).thisptr ) )
+
+ def union_with(self, other):
+ """self = self | other."""
+ if isinstance(other, cy_Rect):
+ self.thisptr.unionWith( deref( (<cy_Rect> other).thisptr ) )
+ elif isinstance(other, cy_OptRect):
+ self.thisptr.unionWith( deref( (<cy_OptRect> other).thisptr ) )
+
+ def intersect_with(self, other):
+ """self = self & other."""
+ if isinstance(other, cy_Rect):
+ self.thisptr.intersectWith( deref( (<cy_Rect> other).thisptr ) )
+ elif isinstance(other, cy_OptRect):
+ self.thisptr.intersectWith( deref( (<cy_OptRect> other).thisptr ) )
+
+ def expand_to(self, cy_Point p):
+ """Expand rectangle to contain point represented as tuple."""
+ self.thisptr.expandTo( deref(p.thisptr) )
+
+ def __or__(cy_OptRect self, cy_OptRect other):
+ """Return union of two rects - it's actually bounding rect of union."""
+ return wrap_OptRect( deref(self.thisptr) | deref(other.thisptr) )
+
+ def __and__(cy_OptRect self, other):
+ """Return intersection of two rectangles."""
+ if isinstance(other, cy_Rect):
+ return wrap_OptRect( deref(self.thisptr) & deref( (<cy_Rect> other).thisptr) )
+ elif isinstance(other, cy_OptRect):
+ return wrap_OptRect( deref(self.thisptr) & deref( (<cy_OptRect> other).thisptr) )
+
+ def __richcmp__(cy_OptRect self, other, op):
+ """Rectangles are not ordered."""
+ if op == 2:
+ if isinstance(other, cy_Rect):
+ return deref(self.thisptr) == deref( (<cy_Rect> other).thisptr )
+ elif isinstance(other, cy_OptRect):
+ return deref(self.thisptr) == deref( (<cy_OptRect> other).thisptr )
+ elif op == 3:
+ if isinstance(other, cy_Rect):
+ return deref(self.thisptr) != deref( (<cy_Rect> other).thisptr )
+ elif isinstance(other, cy_OptRect):
+ return deref(self.thisptr) != deref( (<cy_OptRect> other).thisptr )
+ return NotImplemented
+
+ def _get_Rect_method(self, name):
+ def f(*args, **kwargs):
+ if self.is_empty():
+ raise ValueError("OptRect is empty.")
+ else:
+ return self.Rect.__getattribute__(name)(*args, **kwargs)
+ return f
+
+ def __getattr__(self, name):
+ Rect_methods = set(['area', 'aspectRatio', 'bottom', 'contains',
+ 'contains_rect', 'corner', 'dimensions', 'distance', 'distanceSq',
+ 'expand_by', 'expand_to', 'has_zero_area', 'height', 'infinite',
+ 'interior_contains', 'interior_contains_rect',
+ 'interior_intersects', 'intersects', 'left', 'max', 'max_extent',
+ 'midpoint', 'min', 'min_extent', 'right', 'round_inwards',
+ 'round_outwards', 'set_bottom', 'set_left', 'set_max', 'set_min',
+ 'set_right', 'set_top', 'top', 'union_with', 'width'])
+
+ if name in Rect_methods:
+ return self._get_Rect_method(name)
+ else:
+ raise AttributeError("OptRect instance has no attribute \"{}\"".format(name))
+
+ def _wrap_Rect_method(self, name, *args, **kwargs):
+ if self.isEmpty():
+ raise ValueError("OptRect is empty.")
+ else:
+ return self.Rect.__getattr__(name)(*args, **kwargs)
+
+ #declaring these by hand, because they take fixed number of arguments,
+ #which is enforced by cython
+
+ def __getitem__(self, i):
+ """self[d]"""
+ return self._wrap_Rect_method("__getitem__", i)
+
+ def __add__(self, other):
+ """Offset rectangle by point."""
+ return self._wrap_Rect_method("__add__", other)
+
+ def __mul__(self, other):
+ """Apply transform to rectangle."""
+ return self._wrap_Rect_method("__mul__", other)
+
+ def __sub__(self, other):
+ """Offset rectangle by -point."""
+ return self._wrap_Rect_method("__sub__", other)
+
+
+cdef cy_OptRect wrap_OptRect(OptRect p):
+ cdef OptRect* retp = new OptRect()
+ retp[0] = p
+ cdef cy_OptRect r = cy_OptRect.__new__(cy_OptRect)
+ r.thisptr = retp
+ return r
+
+
+
+cdef class cy_IntRect:
+
+ """Class representing axis-aligned rectangle in 2D with integer coordinates.
+
+ Corresponds to IntRect class (typedef) in 2geom."""
+
+ cdef IntRect* thisptr
+
+ def __cinit__(self, IntCoord x0=0, IntCoord y0=0, IntCoord x1=0, IntCoord y1=0):
+ """Create IntRect from coordinates of its top-left and bottom-right corners."""
+ self.thisptr = new IntRect(x0, y0, x1, y1)
+
+ def __str__(self):
+ """str(self)"""
+ return "IntRect with dimensions {}, topleft point {}".format(
+ str(self.dimensions()),
+ str(self.min()))
+
+ def __repr__(self):
+ """repr(self)"""
+ return "IntRect({}, {}, {}, {})".format( str(self.left()),
+ str(self.top()),
+ str(self.right()),
+ str(self.bottom()))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_IntPoint p0, cy_IntPoint p1):
+ """Create rectangle from it's top-left and bottom-right corners."""
+ return wrap_IntRect( IntRect(deref(p0.thisptr), deref(p1.thisptr)) )
+
+ @classmethod
+ def from_intervals(cls, I, J):
+ """Create rectangle from two intervals representing its sides."""
+ return cy_IntRect( int(I.min()),
+ int(J.min()),
+ int(I.max()),
+ int(J.max()) )
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create rectangle containing all points in list."""
+ if lst == []:
+ return cy_IntRect()
+ if len(lst) == 1:
+ return cy_IntRect(lst[0], lst[0])
+ ret = cy_IntRect(lst[0], lst[1])
+ for a in lst:
+ ret.expand_to(a)
+ return ret
+
+ #didn't manage to declare from_xywh for IntRect
+ @classmethod
+ def from_xywh(cls, x, y, w, h):
+ """Create rectangle from it's topleft point and dimensions."""
+ return cy_IntRect(int(x),
+ int(y),
+ int(x) + int(w),
+ int(y) + int(h) )
+
+ def __getitem__(self, Dim2 d):
+ """self[d]"""
+ return wrap_IntInterval( deref(self.thisptr)[d] )
+
+ def min(self):
+ """Get top-left point."""
+ return wrap_IntPoint( self.thisptr.i_min() )
+
+ def max(self):
+ """Get bottom-right point."""
+ return wrap_IntPoint( self.thisptr.i_max() )
+
+ def corner(self, unsigned int i):
+ """Get corners (modulo) indexed from 0 to 3."""
+ return wrap_IntPoint( self.thisptr.i_corner(i) )
+
+ def top(self):
+ """Get top coordinate."""
+ return self.thisptr.top()
+
+ def bottom(self):
+ """Get bottom coordinate."""
+ return self.thisptr.bottom()
+
+ def left(self):
+ """Get left coordinate."""
+ return self.thisptr.left()
+
+ def right(self):
+ """Get right coordinate."""
+ return self.thisptr.right()
+
+ def width(self):
+ """Get width."""
+ return self.thisptr.width()
+
+ def height(self):
+ """Get height."""
+ return self.thisptr.height()
+
+ def aspect_ratio(self):
+ """Get ratio between width and height."""
+ return self.thisptr.aspectRatio()
+
+ def dimensions(self):
+ """Get dimensions as IntPoint."""
+ return wrap_IntPoint( self.thisptr.i_dimensions() )
+
+ def midpoint(self):
+ """Get midpoint."""
+ return wrap_IntPoint( self.thisptr.i_midpoint() )
+
+ def area(self):
+ """Get area."""
+ return self.thisptr.area()
+
+ def has_zero_area(self):
+ """Test for area being zero."""
+ return self.thisptr.hasZeroArea()
+
+ def max_extent(self):
+ """Get bigger value from width, height."""
+ return self.thisptr.maxExtent()
+
+ def min_extent(self):
+ """Get smaller value from width, height."""
+ return self.thisptr.minExtent()
+
+ def intersects(self, cy_IntRect r):
+ """Check if rectangle intersects another rectangle."""
+ return self.thisptr.intersects(deref( r.thisptr ))
+
+ def contains(self, cy_IntPoint r):
+ """Check if rectangle contains point."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def contains_rect(self, cy_IntRect r):
+ """Check if rectangle contains another rect."""
+ return self.thisptr.contains( deref(r.thisptr) )
+
+ def set_left(self, IntCoord val):
+ """Set left coordinate."""
+ self.thisptr.setLeft(val)
+
+ def set_right(self, IntCoord val):
+ """Set right coordinate."""
+ self.thisptr.setRight(val)
+
+ def set_top(self, IntCoord val):
+ """Set top coordinate."""
+ self.thisptr.setTop(val)
+
+ def set_bottom(self, IntCoord val):
+ """Set bottom coordinate."""
+ self.thisptr.setBottom(val)
+
+ def set_min(self, cy_IntPoint p):
+ """Set top-left point."""
+ self.thisptr.setMin( deref( p.thisptr ) )
+
+ def set_max(self, cy_IntPoint p):
+ """Set bottom-right point."""
+ self.thisptr.setMax( deref( p.thisptr ))
+
+ def expand_to(self, cy_IntPoint p):
+ """Expand rectangle to contain point represented as tuple."""
+ self.thisptr.expandTo( deref( p.thisptr ) )
+
+ def union_with(self, cy_IntRect b):
+ """self = self | other."""
+ self.thisptr.unionWith(deref( b.thisptr ))
+
+ def expand_by(cy_IntRect self, x, y = None):
+ """Expand both intervals.
+
+ Either expand them both by one value, or each by different value.
+ """
+ if y is None:
+ if isinstance(x, cy_IntPoint):
+ self.thisptr.expandBy( deref( (<cy_IntPoint> x).thisptr ) )
+ else:
+ self.thisptr.expandBy( <IntCoord> x)
+ else:
+ self.thisptr.expandBy( <IntCoord> x,
+ <IntCoord> y)
+
+ def __add__(cy_IntRect self, cy_IntPoint p):
+ """Offset rectangle by point."""
+ return wrap_IntRect( deref(self.thisptr) + deref( p.thisptr ) )
+
+ def __sub__(cy_IntRect self, cy_IntPoint p):
+ """Offset rectangle by -point."""
+ return wrap_IntRect( deref(self.thisptr) - deref( p.thisptr ) )
+
+ def __or__(cy_IntRect self, cy_IntRect o):
+ """Return union of two rects - it's actually bounding rect of union."""
+ return wrap_IntRect( deref(self.thisptr) | deref( o.thisptr ))
+
+ def __richcmp__(cy_IntRect self, cy_IntRect o, int op):
+ """Rectangles are not ordered."""
+ if op == 2:
+ return deref(self.thisptr) == deref(o.thisptr)
+ if op == 3:
+ return deref(self.thisptr) != deref(o.thisptr)
+
+cdef cy_IntRect wrap_IntRect(IntRect p):
+ cdef IntRect* retp = new IntRect()
+ retp[0] = p
+ cdef cy_IntRect r = cy_IntRect.__new__(cy_IntRect)
+ r.thisptr = retp
+ return r
+
+
+
+cdef class cy_OptIntRect:
+
+ """Class representing optionally empty rect in with integer coordinates.
+
+ This class corresponds to OptIntRect in 2geom, and it tries to mimic
+ the behaviour of std::optional. In addition to OptIntRect methods,
+ this class passes calls for IntRect methods to underlying IntRect class,
+ or throws ValueError when it's empty.
+ """
+
+ cdef OptIntRect* thisptr
+
+ def __cinit__(self, x0=None, y0=None, x1=None, y1=None):
+ """Create OptIntRect from coordinates of top-left and bottom-right corners.
+
+ No arguments will result in empty rectangle.
+ """
+ if x0 is None:
+ self.thisptr = new OptIntRect()
+ else:
+ self.thisptr = new OptIntRect( int(x0),
+ int(y0),
+ int(x1),
+ int(y1) )
+
+ def __str__(self):
+ """str(self)"""
+ if self.isEmpty():
+ return "Empty OptIntRect"
+ return "OptIntRect with dimensions {}, topleft point {}".format(
+ str(self.Rect.dimensions()),
+ str(self.Rect.min()))
+
+ def __repr__(self):
+ """repr(self)"""
+ if self.isEmpty():
+ return "OptIntRect()"
+ return "OptIntRect({}, {}, {}, {})".format( str(self.Rect.left()),
+ str(self.Rect.top()),
+ str(self.Rect.right()),
+ str(self.Rect.bottom()))
+
+ def __dealloc__(self):
+ del self.thisptr
+
+ @classmethod
+ def from_points(cls, cy_IntPoint p0, cy_IntPoint p1):
+ """Create rectangle from it's top-left and bottom-right corners."""
+ return wrap_OptIntRect( OptIntRect(deref(p0.thisptr), deref(p1.thisptr)) )
+
+ @classmethod
+ def from_intervals(cls, I, J):
+ """Create rectangle from two intervals representing its sides."""
+ if hasattr(I, "isEmpty"):
+ if I.isEmpty():
+ return cy_OptIntRect()
+
+ if hasattr(J, "isEmpty"):
+ if J.isEmpty():
+ return cy_OptIntRect()
+
+ return wrap_OptIntRect( OptIntRect( int(I.min()),
+ int(J.min()),
+ int(I.max()),
+ int(J.max()) ) )
+
+ @classmethod
+ def from_rect(cls, r):
+ """Create OptIntRect from other, possibly empty, rectangle."""
+ if hasattr(r, "isEmpty"):
+ if r.isEmpty():
+ return cy_OptIntRect()
+ return cy_OptIntRect( r.min().x,
+ r.min().y,
+ r.max().x,
+ r.max().y )
+
+ @classmethod
+ def from_list(cls, lst):
+ """Create OptIntRect containing all points in the list.
+
+ Empty list will result in empty OptIntRect.
+ """
+ if lst == []:
+ return cy_OptIntRect()
+ if len(lst) == 1:
+ return cy_OptIntRect.from_points(lst[0], lst[0])
+ ret = cy_OptIntRect.from_points(lst[0], lst[1])
+ for a in lst:
+ ret.expand_to(a)
+ return ret
+
+ property Rect:
+ """Get underlying IntRect."""
+ def __get__(self):
+ return wrap_IntRect(self.thisptr.get())
+
+ def __bool__(self):
+ """OptIntRect is False only when it's empty."""
+ return not self.thisptr.isEmpty()
+
+ def is_empty(self):
+ """Check for OptIntRect containing no points."""
+ return self.thisptr.isEmpty()
+
+ def intersects(cy_OptIntRect self, other):
+ """Check if rectangle intersects another rectangle."""
+ if isinstance(other, cy_IntRect):
+ return self.thisptr.intersects( deref( (<cy_IntRect> other).thisptr ) )
+ elif isinstance(other, cy_OptIntRect):
+ return self.thisptr.intersects( deref( (<cy_OptIntRect> other).thisptr ) )
+
+ def contains(self, cy_IntPoint other):
+ """Check if rectangle contains point."""
+ return self.thisptr.contains( deref(other.thisptr) )
+
+ def contains_rect(cy_OptIntRect self, other):
+ """Check if rectangle contains another rectangle."""
+ if isinstance(other, cy_IntRect):
+ return self.thisptr.contains( deref( (<cy_IntRect> other).thisptr ) )
+ elif isinstance(other, cy_OptIntRect):
+ return self.thisptr.contains( deref( (<cy_OptIntRect> other).thisptr ) )
+
+
+ def union_with(cy_OptIntRect self, other):
+ """self = self | other."""
+ if isinstance(other, cy_IntRect):
+ self.thisptr.unionWith( deref( (<cy_IntRect> other).thisptr ) )
+ elif isinstance(other, cy_OptIntRect):
+ self.thisptr.unionWith( deref( (<cy_OptIntRect> other).thisptr ) )
+
+ def intersect_with(cy_OptIntRect self, other):
+ """self = self & other."""
+ if isinstance(other, cy_IntRect):
+ self.thisptr.intersectWith( deref( (<cy_IntRect> other).thisptr ) )
+ elif isinstance(other, cy_OptIntRect):
+ self.thisptr.intersectWith( deref( (<cy_OptIntRect> other).thisptr ) )
+
+ def expand_to(self, cy_IntPoint p):
+ """Expand rectangle to contain point."""
+ self.thisptr.expandTo( deref(p.thisptr) )
+
+ def __or__(cy_OptIntRect self, cy_OptIntRect other):
+ """Return union of two rects - it's actually bounding rect of union."""
+ return wrap_OptIntRect( deref(self.thisptr) | deref(other.thisptr) )
+
+ def __and__(cy_OptIntRect self, other):
+ """Return intersection of two rectangles."""
+ if isinstance(other, cy_IntRect):
+ return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_IntRect> other).thisptr) )
+ elif isinstance(other, cy_OptIntRect):
+ return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_OptIntRect> other).thisptr) )
+
+ def __richcmp__(cy_OptIntRect self, other, op):
+ """Rectangles are not ordered."""
+ if op == 2:
+ if isinstance(other, cy_IntRect):
+ return deref(self.thisptr) == deref( (<cy_IntRect> other).thisptr )
+ elif isinstance(other, cy_OptIntRect):
+ return deref(self.thisptr) == deref( (<cy_OptIntRect> other).thisptr )
+ elif op == 3:
+ if isinstance(other, cy_IntRect):
+ return deref(self.thisptr) != deref( (<cy_IntRect> other).thisptr )
+ elif isinstance(other, cy_OptIntRect):
+ return deref(self.thisptr) != deref( (<cy_OptIntRect> other).thisptr )
+
+ def _get_Rect_method(self, name):
+ def f(*args, **kwargs):
+ if self.is_empty():
+ raise ValueError("OptIntRect is empty.")
+ else:
+ return self.Rect.__getattribute__(name)(*args, **kwargs)
+ return f
+
+ def __getattr__(self, name):
+
+ Rect_methods = set(['area', 'aspect_ratio', 'bottom', 'contains',
+ 'contains_rect', 'corner', 'dimensions', 'expand_by', 'expand_to',
+ 'from_intervals', 'from_list', 'from_points', 'from_xywh',
+ 'has_zero_area', 'height', 'intersects', 'left', 'max',
+ 'max_extent', 'midpoint', 'min', 'min_extent', 'right',
+ 'set_bottom', 'set_left', 'set_max', 'set_min', 'set_right',
+ 'set_top', 'top', 'union_with', 'width'])
+
+ if name in Rect_methods:
+ return self._get_Rect_method(name)
+ else:
+ raise AttributeError("OptIntRect instance has no attribute \"{}\"".format(name))
+
+ def _wrap_Rect_method(self, name, *args, **kwargs):
+ if self.isEmpty():
+ raise ValueError("OptIntRect is empty.")
+ else:
+ return self.Rect.__getattr__(name)(*args, **kwargs)
+
+ #declaring these by hand, because they take fixed number of arguments,
+ #which is enforced by cython
+
+ def __getitem__(self, i):
+ """self[d]"""
+ return self._wrap_Rect_method("__getitem__", i)
+
+ def __add__(self, other):
+ """Offset rectangle by point."""
+ return self._wrap_Rect_method("__add__", other)
+
+ def __sub__(self, other):
+ """Offset rectangle by -point."""
+ return self._wrap_Rect_method("__sub__", other)
+
+cdef cy_OptIntRect wrap_OptIntRect(OptIntRect p):
+ cdef OptIntRect* retp = new OptIntRect()
+ retp[0] = p
+ cdef cy_OptIntRect r = cy_OptIntRect.__new__(cy_OptIntRect)
+ r.thisptr = retp
+ return r
diff --git a/src/cython/cy2geom.pyx b/src/cython/cy2geom.pyx
new file mode 100644
index 0000000..f1ba7a6
--- /dev/null
+++ b/src/cython/cy2geom.pyx
@@ -0,0 +1,71 @@
+#Axis specifiers for Dim2
+X = 0
+Y = 1
+
+from _cy_primitives import cy_Angle as Angle
+from _cy_primitives import cy_AngleInterval as AngleInterval
+from _cy_primitives import cy_Point as Point
+from _cy_primitives import cy_Line as Line
+from _cy_primitives import cy_Ray as Ray
+from _cy_primitives import cy_IntPoint as IntPoint
+
+
+from _cy_rectangle import cy_Interval as Interval
+from _cy_rectangle import cy_OptInterval as OptInterval
+from _cy_rectangle import cy_IntInterval as IntInterval
+from _cy_rectangle import cy_OptIntInterval as OptIntInterval
+
+from _cy_rectangle import cy_GenericInterval as GenericInterval
+from _cy_rectangle import cy_GenericOptInterval as GenericOptInterval
+
+from _cy_rectangle import cy_GenericRect as GenericRect
+
+from _cy_rectangle import cy_Rect as Rect
+from _cy_rectangle import cy_OptRect as OptRect
+from _cy_rectangle import cy_IntRect as IntRect
+from _cy_rectangle import cy_OptIntRect as OptIntRect
+
+
+from _cy_affine import cy_Affine as Affine
+from _cy_affine import cy_Translate as Translate
+from _cy_affine import cy_Rotate as Rotate
+from _cy_affine import cy_VShear as VShear
+from _cy_affine import cy_HShear as HShear
+from _cy_affine import cy_Scale as Scale
+from _cy_affine import cy_Zoom as Zoom
+
+from _cy_affine import cy_Eigen as Eigen
+
+from _cy_curves import cy_Curve as Curve
+
+from _cy_curves import cy_Linear as Linear
+from _cy_curves import cy_SBasis as SBasis
+from _cy_curves import cy_SBasisCurve as SBasisCurve
+
+from _cy_curves import cy_Bezier as Bezier
+from _cy_curves import cy_BezierCurve as BezierCurve
+from _cy_curves import cy_LineSegment as LineSegment
+from _cy_curves import cy_QuadraticBezier as QuadraticBezier
+from _cy_curves import cy_CubicBezier as CubicBezier
+
+from _cy_curves import cy_HLineSegment as HLineSegment
+from _cy_curves import cy_VLineSegment as VLineSegment
+
+from _cy_curves import cy_EllipticalArc as EllipticalArc
+#Wrap this? It doesn't fit into python's dynamic nature and
+#BezierCurve covers most of it's functionality
+#Maybe implement constructors for BezierCurve similar to those
+#seen in BezierCurveN
+#TODO
+#from _cy_curves import cy_BezierCurveN as BezierCurveN
+
+from _cy_curves import cy_lerp as lerp
+from _cy_curves import cy_reverse as reverse
+#~ from _cy_curves import cy_level_sets as level_sets
+
+
+from _cy_path import cy_Path as Path
+
+
+from _cy_conicsection import cy_Circle as Circle
+from _cy_conicsection import cy_Ellipse as Ellipse
diff --git a/src/cython/report.md b/src/cython/report.md
new file mode 100644
index 0000000..e1f8353
--- /dev/null
+++ b/src/cython/report.md
@@ -0,0 +1,237 @@
+# State of cython Bindings
+
+This report summarizes work done on [lib2geom][2g] cython bindings during
+GSoC 2012 mostly from technical standpoint. It should serve for anyone
+doing any further work on these bindings, and for me to help my memory.
+
+Prior to the project, there were bindings using boost.python. These
+bindigs, however, uncovered only part of the lib2geom functionality and
+were not actively developed. I decided to go for a fresh start using
+[cython][cy]. Cython is programming language capable of calling C and C++
+methods and making so-called "extension types", which act as python
+classes. Reasons for choosing python include bit of experience I had with
+it, its cleanliness and activity in development.
+
+## Overview of Bindings
+
+All work was done in [trunk][tr], since it mostly added new functionality
+without altering existing code. Whole bindings are located in directory
+
+ 2geom_dir/src/2geom/cython-bindings
+
+Code is located in files ending with `.pxd` and `.pyx`, roughly cython's
+equivalent of header and implementation files. Classes in cython are
+divided into logical groups, which are wrapped in corresponding classes.
+cy2geom.pyx imports all classes and methods, and it's what one sees after
+
+ >>> import cy2geom
+
+In addition to this, there are some C++ files, necessary to wrap some of
+the code for cython, unit-tests covering most of the functionality and
+wrapper.py script, used to skip tedious part of creating extension types.
+
+Building of bindings was integrated with cmake, which is used to build
+the bindings, using [thewext's code][cm].
+
+I will now describe files/logical groups of them, their significance and
+state. I will try to write down most of the technical non-trivialities that
+I encountered.
+
+Pattern used for wrapping all classes is taken from cython docs and various
+other cython bindings. In .pxd file C++ classes, methods and functions are
+declared. In corresponding .pyx file there is an extension type which holds
+pointer (called `thisptr`) to it's underlying C++ class. Instance of this
+class is created with cython's `__cinit__`, and deleted with `__dealloc__`.
+Methods of class are called with `self.thisptr.methodname`, which cython
+translates to `thisptr->methodname`. Argument are stripped of their python
+wrapping using `deref(arg.thisptr)`, or they are translated automatically
+(these are basic numerical types).
+
+There is a function for each class, named `wrap_classname()`, which takes
+C++ instance and creates new python object around it.
+
+## `_common_decl`
+These files (.pxd/.pyx) include basic declarations, common to all files.
+Functions for creating and wrapping `vector[double]` should become redundant
+in next cython release (0.17), since it should do this conversion
+automatically.
+
+All extension types (cdef classes) have prefix `_cy_`, to avoid clash with
+C++ classes. This is removed when importing to `_common_decl.pyx`, but is still
+somewhat visible to the user of bindings, for example in traces. Other
+option would be renaming the C++ classes.
+
+## `_cy_primitives`
+Geometric primitives are wrapped here. These are relatively simple, few
+things that are worth mentioning are:
+
+* Some of the methods for Line and Ray could be rewritten using properties.
+* General points about exceptions, docstrings and classmethod apply here.
+
+## `_cy_rectangle`
+These files include intervals and rectangles, which all inherit from
+`GenericInterval[C]` or `GenericRect[C]`, respectively.
+
+For intervals, classes Interval and IntInterval are wrapped, with added
+methods for interval too. Opt variants are supported by rewriting
+`__getattr__` method. When calling the method that Interval provides,
+OptInterval checks whether it's not empty and passes arguments to Interval's
+method. If it is empty, it will raise ValueError. Due to this, Interval's
+method do not appear in OptInterval's namespace, which might be not ideal.
+
+GenericInterval is supported by setting the template type to WrappedPyObject,
+defined in `wrapped-pyobject.h`. This thin wrapper of `PyObject *` overloads
+operators to support arithmetic operations, comparisons and similar. When
+adding two WrappedPyObjects, these objects call corresponding addition using
+Python's C-API. This is a bit unstable feature, because it still leaks these
+python objects, and propagating error from Python, through C-API to cython
+back to Python is not done yet correctly. However, constraints on
+parameter of GenericInterval's type are pretty tight - it has to support
+arithmetic operations within itself, multiplication by reals and has to be
+ordered (ordered linear space with multiplication and division between its
+elements), so having GenericInterval only with reals and integers covers most
+cases.
+
+For rectangles, situations is almost identical as for intervals. GenericRect,
+return 2-tuples as a points, which is a bit of disadvantage, because adding
+tuples doesn't add element-wise, but append them one after another. It might
+be convenient to implement PyPoint overloading this functionality.
+
+A bit problematic region is in comparing all the intervals and rectangles.
+Should every interval be comparable to every other type, Interval with
+OptIntInterval? This would probably require writing the logic in cython.
+
+GenericOptRect is not there yet, because I got GenericRect only working
+only recently. It should be, however, quick to add.
+
+## `_cy_affine`
+Affine and specialised transforms. This is in pretty good state. Eigenvalues
+seem to be a bit off sometimes.
+
+## `_cy_curves`
+All of the curves are wrapped here, together with functions like Linear,
+SBasis and Bezier.
+
+Curve implements method every curve should implement, but other curve
+classes do not inherit from Curve. This is mainly because of technical
+details, this inheritance would only add complexity to code. However, I
+might consider rewriting curves, so they would actually inherit from
+Curve, if this appears to be needed.
+
+State of functions and classmethods is a bit strange. I tried to copy
+2geom's methods, but there are differences for Beziers and SBasis
+polynomials - to name a few, SBasis implements multiplication with SBasis
+as a operator, but Bezier only as a function, or bounds are implemented
+as instance methods for Linear, but as a module level functions for Bezier
+and SBasis. In case I wasn't sure what to do with a method, I put it, as
+a classmethod, to corresponding class.
+
+BezierCurveN is not implemented. It would be major hurdle, as cython has
+problems with templated classes - one would have to declare each type
+separately. First three orders are wrapped, and BezierCurve, with variable
+degree, is also wrapped.
+
+`hacks.h` is used to go around cython's problems with integer template
+parameters.
+
+## `_cy_path`
+Path is wrapped here. Biggest problem with path was it's heavy use of
+iterators, which are not the same as python's iterators. In most of the
+cases, I replaced them with integers marking the position of iterator,
+and go to the position step-by-step. This makes potentially O(1) operations
+actually O(n) where n is size of Path. Best way to deal with this would be
+to allow iterators to be shifted by arbitrary number.
+
+Not everything is provided, but there should be enough functionality to
+have total control over Path's curves.
+
+## `_cy_conicsection`
+Circle and ellipse classes. ratQuad and xAx from conic-section should also
+go there.
+
+## `utils.py`
+Only a simple function to draw curve/path to Tk windows and regular
+N-agon creating function reside there, useful mainly for debugging
+
+## `wrapper.py`
+Script used to cut the most trivial part of creating bindings, writing
+.pxd declarations, extension type and other functions. It's pretty tailored
+into 2geom, but I guess after some work one could make it more generic.
+
+
+## General Issues
+Exceptions coming from C++ code are not handled yet, they actually crash
+the program. Simplest solution is adding `except +` after every method that
+possibly raises exceptions, but traces won't look that nice.
+
+cython has some kind of problem with docstrings, it doesn't write function's
+argument to them, just ellipsis. This can be addressed by specifying all
+arguments in the docstring.
+
+Design decision has been made to put most of function not belonging to any
+class to extension type's namespace using classmethods. Most apt extension
+type is chosen, distance(Point, Point) goes to Point class. Other options
+are making them check for all possible types (this is ugly and error-prone)
+or differentiating using different names, which is more or less the same as
+classmethods, just not that systematic
+
+## What's missing
+Piecewise missing and all of crossings/boolops stuff are biggest things
+that didn't make. Concerning Piecewise, I think best option is to
+implement only `Piecewise<SBasis>` and `Piecewise<D2<SBasis>>` as those are
+only ones actually useful (SBasis, Bezier and their D2 versions are AFAIK
+only classes implementing required concepts).
+
+Crossings are working, boolops not so much. Work on Shape and Region has
+been started.
+
+# Personal Notes
+I am also summarizing a bit on my personal experience with GSoC.
+
+## What Did I Learn
+I learned a lot, not only new technology, but also gained new perspective
+on long-time work.
+
+Speaking of technology, I am much more confident with cython, and I am not
+really scared of python C-API. I got a grasp of bazaar, CMake, python's
+unittest module and various (mainly template) C++ features, heavily used
+in 2geom.
+
+I got a grasp of what a polished product looks like, and I realize how
+hard is to get your product to that state. I can now appreciate testing,
+at first seemingly just a boring and not trivial chore, turned out to be
+very helpful both for my understanding of code and for making changes more
+quickly. I also read a lot about design of python API, what's idiomatic
+and what does various norms (PEPs) say about python.
+
+I think I never did something of this size before (not that monolithic),
+so I learned lot about my work habits and importance of both work ethic
+and taking a rest.
+
+## What I Liked & Disliked
+I liked mainly the process of learning new things. I disliked the really
+technical scope of this project - I can imagine more exciting things to
+work on than doing bindings, but I guess it doesn't hurt to have this kind
+of experience. I certainly won't spend my whole life doing only exciting
+things :)
+
+## What Would I Do Differently
+I would have planned a bit more. I like to write it on account of my
+inexperience, I felt many times that knowing how should final product
+look like would save me tons of work - this way, I approached state
+with which I was more-or-less content at the end of doing most of the tasks.
+Luckily, I got time to go through the code at the end, so I ended up
+correcting the largest mistakes.
+
+I would also discuss design of API bit more, since mapping the C++ sometimes
+turned out to be cumbersome from python's perspective. Often, I would spend
+lots of time thinking through some detail (Iterator for Path, std::optional,
+most of the templated classes), only doing ad-hoc solution at the end in
+order to I to keep progressing.
+
+Jan Pulmann - jan.pulmann@gmail.com
+
+[2g]: http://lib2geom.sourceforge.net/
+[cy]: http://www.cython.org/
+[cm]: https://github.com/thewtex/cython-cmake-example
+[tr]: https://code.launchpad.net/~lib2geom-hackers/lib2geom/trunk
diff --git a/src/cython/test-affine.py b/src/cython/test-affine.py
new file mode 100644
index 0000000..a020d73
--- /dev/null
+++ b/src/cython/test-affine.py
@@ -0,0 +1,249 @@
+import unittest
+from math import pi, sqrt, sin, cos
+from random import randint, uniform
+
+import cy2geom
+
+from cy2geom import Point, IntPoint
+from cy2geom import Line, Ray, Rect
+
+from cy2geom import Rect
+
+from cy2geom import Affine
+from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom
+from cy2geom import Eigen
+
+class TestPrimitives(unittest.TestCase):
+ def affine(self, A, B):
+ c0, c1, c2, c3, c4, c5 = A[0], A[1], A[2], A[3], A[4], A[5]
+ C = Affine(c0, c1, c2, c3, c4, c5)
+ self.assertEqual(C, A)
+ E = Affine.identity()
+ self.assertEqual(C, C*E)
+ self.assertEqual(E*B, B)
+ self.assertEqual(E.det(), 1)
+
+ self.assertAlmostEqual(A.det(), c0*c3-c1*c2)
+ self.assertAlmostEqual(abs(A.det()), A.descrim2())
+ self.assertAlmostEqual(abs(A.det())**0.5, A.descrim())
+ #xor
+ self.assertFalse( A.flips() ^ (A.det() < 0) )
+
+ if A.is_singular():
+ self.assertAlmostEqual(A.det(), 0)
+ else:
+ self.assertTrue( Affine.are_near (A*A.inverse(), E) )
+ self.assertAlmostEqual(A.det(), 1/A.inverse().det())
+ self.assertEqual( A.x_axis(), Point(c0, c1) )
+ self.assertEqual( A.y_axis(), Point(c2, c3) )
+ self.assertEqual( A.translation(), Point(c4, c5) )
+
+ self.assertAlmostEqual(A.expansion_X(), A.x_axis().length())
+ self.assertAlmostEqual(A.expansion_Y(), A.y_axis().length())
+
+ if abs(A.expansion_X()) > 1e-7 and abs(A.expansion_Y()) > 1e-7:
+ A.set_expansion_X(2)
+ A.set_expansion_Y(3)
+ self.assertAlmostEqual(A.expansion_X(), 2)
+ self.assertAlmostEqual(A.expansion_Y(), 3)
+
+ A.set_identity()
+
+ self.assertTrue(A.is_identity())
+ self.assertTrue(A.is_translation())
+ self.assertFalse(A.is_nonzero_translation())
+ self.assertTrue(A.is_scale())
+ self.assertTrue(A.is_uniform_scale())
+ self.assertFalse(A.is_nonzero_scale())
+ self.assertFalse(A.is_nonzero_uniform_scale())
+ self.assertTrue(A.is_rotation())
+ self.assertFalse(A.is_nonzero_rotation())
+ self.assertTrue(A.is_HShear())
+ self.assertTrue(A.is_VShear())
+ self.assertFalse(A.is_nonzero_HShear())
+ self.assertFalse(A.is_nonzero_VShear())
+ self.assertTrue(A.is_zoom())
+
+ self.assertTrue(A.preserves_area() and A.preserves_angles() and A.preserves_distances())
+
+ self.assertFalse( A.flips() )
+ self.assertFalse( A.is_singular() )
+
+ A.set_X_axis(Point(c0, c1))
+ A.set_Y_axis(Point(c2, c3))
+
+ self.assertEqual(A.without_translation(), A)
+
+ A.set_translation(Point(c4, c5))
+ self.assertEqual(C, A)
+
+ self.assertAlmostEqual( (A*B).det(), A.det()*B.det() )
+
+ self.assertEqual( A.translation(), Point()*A )
+ self.assertEqual( Point(1, 1)*A, Point( c0+c2+c4, c1+c3+c5 ))
+
+ l = Line(Point(1, 1), 2)
+ self.assertEqual( (l.transformed(A)).origin(), l.origin()*A )
+ self.assertTrue( Line.are_near( l.point_at(3)*A, l.transformed(A) ) )
+
+ r = Ray(Point(2, 3), 4)
+ self.assertEqual( (r.transformed(A)).origin(), r.origin()*A )
+ self.assertTrue( Ray.are_near( r.point_at(3)*A, r.transformed(A) ) )
+
+
+
+ def test_affine(self):
+ al = []
+ for i in range(10):
+ al.append(Affine( uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10)))
+ for A in al:
+ for B in al:
+ self.affine(A, B)
+
+ o = Point(2, 4)
+ v = Point(-1, 1)/sqrt(2)
+ l = Line.from_origin_and_versor(o, v)
+
+ R = Affine.reflection(v, o)
+ for i in range(100):
+ p = Point(randint(0, 100), randint(0, 100))
+ self.assertAlmostEqual(Line.distance(p, l), Line.distance(p*R, l))
+ self.assertTrue( Affine.are_near( R, R.inverse() ) )
+
+ self.affine(R, R.inverse())
+
+ def test_translate(self):
+ T = Translate()
+ U = Translate(Point(2, 4))
+ V = Translate(1, -9)
+
+ self.assertTrue(Affine(T).is_translation())
+ self.assertTrue(Affine(U).is_nonzero_translation())
+
+ self.assertEqual( (U*V).vector(), U.vector()+V.vector() )
+ self.assertEqual( U.inverse().vector(), -U.vector() )
+ self.assertEqual(T, Translate.identity())
+ self.assertEqual( U.vector(), Point(U[0], U[1]) )
+
+ self.affine(Affine(V), Affine(U))
+ self.affine(Affine(U), Affine(V))
+
+ r = Rect.from_points( Point(0, 2), Point(4, 8) )
+
+ self.assertEqual( ( r*(U*V) ).min(), r.min()+U.vector()+V.vector())
+
+ def test_scale(self):
+ S = Scale()
+ T = Scale( Point (3, 8) )
+ U = Scale( -3, 1)
+ V = Scale(sqrt(2))
+
+ self.assertTrue( Affine(T).is_scale() )
+ self.assertTrue( Affine(T).is_nonzero_scale() )
+ self.assertTrue( Affine(V).is_nonzero_uniform_scale())
+
+ self.assertEqual( (T*V).vector(), T.vector()*sqrt(2) )
+ self.assertEqual( (T*U)[0], T[0]*U[0] )
+ self.assertAlmostEqual( 1/U.inverse()[1], U[1] )
+
+ r = Rect.from_points( Point(0, 2), Point(4, 8) )
+ self.assertAlmostEqual((r*V).area(), 2*r.area())
+ self.assertFalse(Affine(U).preserves_area())
+ self.assertTrue(Affine(V).preserves_angles())
+
+ self.affine(Affine(T), Affine(U))
+ self.affine(Affine(U), Affine(V))
+ self.affine(Affine(V), Affine(T))
+
+ def test_rotate(self):
+ R = Rotate()
+ S = Rotate(pi/3)
+ T = Rotate(Point( 1, 1 ))
+ U = Rotate( -1, 1 )
+
+ self.assertTrue(S.vector(), Point(cos(pi/3), sin(pi/3)) )
+ self.assertEqual( Point(T[0], T[1]), T.vector() )
+ self.assertTrue( Affine.are_near( Rotate.from_degrees(60), S ) )
+ self.assertEqual(R, Rotate.identity())
+ self.assertTrue( Point.are_near( ( S * T ).vector(),
+ Point( cos( pi/3 + pi/4 ), sin( pi/3 + pi/4 ) ) ) )
+
+ self.affine( Affine(R), Affine(S))
+ self.affine( Affine(S), Affine(T))
+ self.affine( Affine(T), Affine(U))
+ self.affine( Affine(U), Affine(R))
+
+ def test_shear(self):
+ H = HShear(2.98)
+ V = VShear(-sqrt(2))
+
+ self.assertAlmostEqual(H.factor(), 2.98)
+ self.assertAlmostEqual(V.inverse().factor(), sqrt(2))
+
+ G = HShear.identity()
+ H.set_factor(0)
+ self.assertEqual(G, H)
+
+ G.set_factor(2)
+ H.set_factor(4)
+ self.assertAlmostEqual((G*H).factor(), G.factor()+H.factor())
+
+ W = VShear.identity()
+ V.set_factor(0)
+ self.assertEqual(W, V)
+
+ W.set_factor(-2)
+ V.set_factor(3)
+ self.assertAlmostEqual((W*V).factor(), W.factor()+V.factor())
+
+ def test_zoom(self):
+ Z = Zoom(3)
+ Y = Zoom(translate=Translate(3,2))
+ X = Zoom(sqrt(3), Translate(-1, 3))
+
+ self.assertEqual(
+ Zoom(Z.scale(), Translate(Y.translation())),
+ Y*Z )
+
+ Z.set_translation(Y.translation())
+ Y.set_scale(Z.scale())
+ self.assertEqual(Z, Y)
+
+ self.assertEqual(Y.inverse().scale(), 1/Y.scale())
+
+ r = Rect.from_xywh( 1, 1, 3, 6)
+ q = Rect.from_xywh( 0, -1, 1, 2)
+ W = Zoom.map_rect(r, q)
+
+ self.assertAlmostEqual(W.scale()*r.width(), q.width())
+ self.assertTrue(Point.are_near(
+ r.min()+W.translation(),
+ q.min()))
+ def test_eigen(self):
+ #TODO looks like bug in eigen - (1, 0) should be eigenvector too
+ #~ S = Scale(1, 2)
+ #~ E_S = Eigen(S)
+ #~ print E_S.vectors, E_S.values
+ #~ print Affine(S)
+ #~ for i in E_S.vectors:
+ #~ print i, i*S, Point(1, 0) * S
+
+ B = Affine(-2, 2, 2, 1, 0, 0)
+ G1 = Eigen(B)
+ G2 = Eigen( [[-2, 2], [2, 1]] )
+
+ self.assertAlmostEqual(min(G1.values), min(G2.values))
+ self.assertAlmostEqual(max(G1.values), max(G2.values))
+
+ if Point.are_near( G1.vectors[0]*G1.values[0], G1.vectors[0]*B ):
+ self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[1], G1.vectors[1]*B ) )
+ else:
+ self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[0], G1.vectors[1]*B ) )
+ self.assertTrue( Point.are_near( G1.vectors[0]*G1.values[1], G1.vectors[0]*B ) )
+
+unittest.main()
diff --git a/src/cython/test-conicsection.py b/src/cython/test-conicsection.py
new file mode 100644
index 0000000..9fc1c20
--- /dev/null
+++ b/src/cython/test-conicsection.py
@@ -0,0 +1,137 @@
+import unittest
+import math
+from random import randint, uniform
+
+import cy2geom
+
+from cy2geom import Point, IntPoint
+from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval
+
+from cy2geom import Affine
+from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom
+from cy2geom import Eigen
+
+from cy2geom import Curve
+from cy2geom import Linear
+from cy2geom import SBasis, SBasisCurve
+from cy2geom import Bezier, BezierCurve
+
+from cy2geom import LineSegment, QuadraticBezier, CubicBezier
+from cy2geom import HLineSegment, VLineSegment
+
+from cy2geom import EllipticalArc
+
+from cy2geom import Path
+
+from cy2geom import Circle, Ellipse
+
+
+class TestPrimitives(unittest.TestCase):
+ def test_circle(self):
+ C = Circle()
+ self.assertEqual(C.center(), Point())
+
+ D = Circle(Point(2, 4), 2)
+ Dp = D.getPath()
+ self.assertEqual(D.center(), Point(2, 4))
+ self.assertEqual(D.ray(), 2)
+
+ for i in range(11):
+ t = i/10.0
+ #Circle approximated by SBasis is not perfect
+ self.assertAlmostEqual( abs(D.center()-Dp(t)), D.ray(), delta=0.1 )
+
+ half_circle = D.arc(Dp(0), Dp(0.3), Dp(0.5))
+
+ self.assertTrue(half_circle.is_SVG_compliant())
+
+ self.assertAlmostEqual(Dp(0.25), half_circle(0.5), delta=0.1)
+
+ points = [Point(2, 5), Point(1, 4), Point(9, 0)]
+ D.set_points(points)
+ for p in points:
+ self.assertAlmostEqual( abs(p-D.center()), D.ray() )
+ Dc = Circle.from_points(points)
+ self.assertAlmostEqual(Dc.center(), D.center())
+ self.assertAlmostEqual(Dc.ray(), D.ray())
+
+ coeffs = (2, 4, 1, -4)
+ E = Circle.from_coefficients(*coeffs)
+ def param(x, y):
+ A, B, C, D = coeffs
+ return A*x**2 + A*y**2 + B*x + C*y + D
+ Ec = E.arc(E.center()+Point(E.ray(), 0), E.center()-Point(E.ray(), 0), E.center()+Point(E.ray(), 0) )
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual(param(Ec.value_at(t, 0), Ec.value_at(t, 1)), 0)
+
+ E.set(3, 5, 9)
+ self.assertAlmostEqual(E.center(), Point(3, 5))
+ self.assertAlmostEqual(E.ray(), 9)
+
+ E.set_coefficients(*coeffs)
+ #radius and center from parametric equation
+ ca = float(coeffs[1])/coeffs[0]
+ cb = float(coeffs[2])/coeffs[0]
+ cc = float(coeffs[3])/coeffs[0]
+ self.assertAlmostEqual( 4*E.ray()**2 , ca**2 + cb**2 -4*cc )
+ self.assertAlmostEqual( E.center(), -Point(ca, cb)/2)
+
+ def test_ellipse(self):
+ #TODO: maybe a bug in arc? get_curve(F) returns different ellipse than F
+ def get_curve(ellipse):
+ p = Point(ellipse.ray(0), 0)*Rotate(ellipse.rot_angle())
+ return ellipse.arc(ellipse.center()+p, ellipse.center()-p, ellipse.center()+p*(1-1e-7))
+ E = Ellipse()
+ self.assertAlmostEqual(E.center(), Point())
+ self.assertAlmostEqual(E.ray(0), 0)
+ self.assertAlmostEqual(E.ray(1), 0)
+
+ F = Ellipse(Point(), 3, 2, 0)
+ self.assertAlmostEqual(F.center(), Point())
+ self.assertAlmostEqual(F.ray(0), 3)
+ self.assertAlmostEqual(F.ray(1), 2)
+ self.assertAlmostEqual(F.rot_angle(), 0)
+ # x**2/9 + y**2/4 = 1
+ self.assertAlmostEqual(F.implicit_form_coefficients()[0], 1/9.0)
+ self.assertAlmostEqual(F.implicit_form_coefficients()[2], 1/4.0)
+ self.assertAlmostEqual(F.implicit_form_coefficients()[5], -1)
+
+ coeffs = (1/3.0, 0, 1/16.0, 1, 0, -1/4.0)
+ G = Ellipse.from_coefficients(*coeffs)
+ self.assertAlmostEqual(G.center(), Point(-3/2.0, 0))
+ self.assertAlmostEqual(G.ray(0), math.sqrt(3))
+ self.assertAlmostEqual(G.ray(1), 4)
+ self.assertAlmostEqual(G.rot_angle(), 0)
+
+ points = [Point(1, 2), Point(2 ,9), Point(0, 3), Point(-3, 8), Point(5, 8)]
+ G.set_points(points)
+ coeffs_G = tuple(G.implicit_form_coefficients())
+ def paramG(x, y):
+ A, B, C, D, E, F = coeffs_G
+ return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F
+ for p in points:
+ self.assertAlmostEqual(paramG(p.x, p.y), 0)
+
+ G2 = Ellipse.from_points(points)
+ coeffs_G2 = tuple(G.implicit_form_coefficients())
+ def paramG2(x, y):
+ A, B, C, D, E, F = coeffs_G2
+ return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F
+ for p in points:
+ self.assertAlmostEqual(paramG2(p.x, p.y), 0)
+
+ E.set_coefficients(*coeffs_G2)
+ for a1, a2 in zip(E.implicit_form_coefficients(), G2.implicit_form_coefficients()):
+ self.assertAlmostEqual(a1, a2)
+
+ H = Ellipse.from_circle(Circle(Point(2, 8), 5))
+ self.assertAlmostEqual(H.center(), Point(2, 8))
+ self.assertAlmostEqual(H.ray(0), 5)
+ self.assertAlmostEqual(H.ray(1), 5)
+
+ Ft = F.transformed( Rotate(math.pi/2) )
+ self.assertAlmostEqual(F.ray(0), Ft.ray(1))
+ self.assertAlmostEqual(F.ray(1), Ft.ray(0))
+
+unittest.main()
diff --git a/src/cython/test-curves.py b/src/cython/test-curves.py
new file mode 100644
index 0000000..8f9b870
--- /dev/null
+++ b/src/cython/test-curves.py
@@ -0,0 +1,458 @@
+import unittest
+import math
+from random import randint, uniform
+
+import cy2geom
+
+from cy2geom import Angle
+from cy2geom import Point, IntPoint
+from cy2geom import Line, Ray, Rect
+from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval
+
+
+from cy2geom import Affine
+from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom
+from cy2geom import Eigen
+
+from cy2geom import Linear
+from cy2geom import SBasis, SBasisCurve
+from cy2geom import Bezier, BezierCurve
+from cy2geom import lerp
+
+from cy2geom import LineSegment, QuadraticBezier, CubicBezier
+from cy2geom import HLineSegment, VLineSegment
+
+from cy2geom import EllipticalArc
+
+class TestPrimitives(unittest.TestCase):
+ def test_linear(self):
+ L = Linear(0, 1)
+ M = Linear(2)
+ N = Linear()
+ self.assertEqual( (L+M), L+2 )
+ self.assertEqual( (L-M), L-2 )
+ self.assertAlmostEqual(L(0.5), lerp(.5, 0, 1))
+ #~ self.assertTrue(N.is_zero())
+ self.assertTrue(M.is_constant())
+ self.assertTrue(L.is_finite())
+ self.assertAlmostEqual(L(0), L.at0())
+ self.assertAlmostEqual(L(1), L.at1())
+ self.assertAlmostEqual(L.value_at(0.3), L(0.3))
+ self.assertTrue( isinstance(M.to_SBasis(), SBasis ))
+
+ self.assertAlmostEqual(L.tri(), L(1) - L(0))
+ self.assertAlmostEqual(L.hat(), (L(1) + L(0))/2)
+
+ for i in range(11):
+ t = i/10.0
+ self.assertTrue(L.bounds_exact().Interval.contains(L(t)))
+ self.assertTrue(L.bounds_fast().Interval.contains(L(t)))
+ self.assertTrue(L.bounds_local(t-0.05, t+0.05).Interval.contains(L(t)))
+ self.assertAlmostEqual(lerp(t, 0, 4), t*4)
+ self.assertAlmostEqual(L(t), cy2geom.reverse(L)(1-t))
+ self.assertAlmostEqual( L(t)*t, (L*t)(t) )
+ self.assertAlmostEqual( L(t)+t, (L+t)(t) )
+ self.assertAlmostEqual( L(t)-t, (L-t)(t) )
+ self.assertAlmostEqual( -( L(t) ), (-L)(t) )
+ self.assertAlmostEqual( (L/2)(t), L(t)/2 )
+
+ def test_sBasis(self):
+ S = SBasis()
+ T = SBasis(2)
+ U = SBasis(1, 7)
+ V = SBasis.from_linear( Linear(2, 8) )
+
+ self.assertEqual(V[0], Linear(2, 8))
+ self.assertEqual(V.back(), Linear(2, 8))
+
+ #~ self.assertTrue(S.empty())
+ self.assertFalse(T.empty())
+
+ T.pop_back()
+ self.assertTrue(T.empty())
+
+ self.assertEqual(S.size(), 0)
+ self.assertEqual(U.size(), 1)
+ self.assertEqual((U*V).size(), 2)
+
+ T.resize(1, Linear(2, 3))
+ self.assertEqual(T[0], Linear(2, 3))
+ T.clear()
+ self.assertTrue(T.empty())
+ #TODO
+ #~ T.reserve(5)
+ #~ print T.size()
+ self.assertEqual(V.at(0), V[0])
+ self.assertEqual(V, U+1)
+ self.assertNotEqual(V, U)
+ self.assertTrue(T.is_zero())
+ self.assertTrue(SBasis(1).is_constant())
+ def f(A, B):
+ return (-A)*(A+B*2.2)*(A*B-B*B/3)
+ W = f(U, V)
+ self.assertAlmostEqual(W(0), W.at0())
+ self.assertAlmostEqual(W(1), W.at1())
+
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual(W(t), W.value_at(t))
+ self.assertAlmostEqual(W(t), f(U(t), V(t)))
+
+ vd_UV = (U*V).value_and_derivatives(t, 1)
+ vd_U = U.value_and_derivatives(t, 1)
+ vd_V = V.value_and_derivatives(t, 1)
+ self.assertAlmostEqual( vd_UV[1], vd_U[1]*V(t)+U(t)*vd_V[1] )
+
+ self.assertAlmostEqual( U(V)(t), U(V(t)) )
+ self.assertEqual(T.degrees_of_freedom(), 0)
+ self.assertEqual(U.degrees_of_freedom(), 2)
+
+ self.assertEqual(T, T.to_SBasis())
+
+ U2 = SBasis(U(0), U(1))
+ U2.resize(10)
+ self.assertNotEqual(U2, U)
+ U2.truncate(U.size())
+ self.assertEqual(U2, U)
+ #TODO: normalize()
+ sL = Linear.sin(Linear(0, 1), 3)
+ cL = Linear.cos(Linear(0, 1), 3)
+ sqrtU = SBasis.sqrt( U, 3 )
+ rL = Linear.reciprocal(Linear(1,2), 3)
+ # cy2geom.inverse seems to return nans for degrees > 1
+ #~ asin = cy2geom.inverse( cy2geom.sqrt( SBasis(Linear(0, 1)), 3 ), 1)
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual(sL(t), math.sin(t))
+ self.assertAlmostEqual(cL(t), math.cos(t))
+ #cy2geom.sqrt is not that precise
+ self.assertAlmostEqual(sqrtU(t), math.sqrt(U(t)), places = 1)
+ self.assertAlmostEqual(rL(t), 1/(1+t), places = 1 )
+ #~ self.assertAlmostEqual( asin(t), math.asin(t) )
+ self.assertAlmostEqual( SBasis.compose(U, V)(t), U(V)(t) )
+ self.assertAlmostEqual( SBasis.divide(U, V, 3)(t), U(t)/V(t), places = 1)
+
+ self.assertAlmostEqual( SBasis.derivative(SBasis.integral(W))(t), W(t))
+ self.assertAlmostEqual( cy2geom.reverse(W)(t), W(1-t) )
+ self.assertAlmostEqual( SBasis.multiply(U, V)(t), (U*V)(t))
+ #TODO looks like bug in 2geom
+ #~ print cy2geom.multiply_add(U, V, W)(t), (U*V+W)(t)
+ self.assertAlmostEqual( SBasis.multiply_add(U, W, V)(t), (U*W+V)(t))
+
+ self.assertTrue( SBasis.bounds_exact(U).Interval.contains(U(t)) )
+ self.assertTrue( SBasis.bounds_fast(U).Interval.contains(U(t)) )
+ self.assertTrue( SBasis.bounds_local(U, OptInterval(t-0.05, t+0.05)).Interval.contains(U(t)) )
+
+
+ for r in SBasis.roots(W):
+ self.assertAlmostEqual(W(r), 0)
+ for r in SBasis.roots(W, Interval(0, 0.7)):
+ self.assertAlmostEqual(W(r), 0)
+ self.assertTrue(Interval(0, 0.7).contains(r))
+
+ levels = [0, 3, 22, -21]
+ for i, roots in enumerate( SBasis.multi_roots(W, levels) ):
+ level = levels[i]
+ for r in roots:
+ self.assertAlmostEqual(W(r), level)
+
+ self.assertEqual(SBasis.valuation(W), 0)
+ #TODO: why is this still 0?
+ #~ print cy2geom.valuation(cy2geom.shift(W, 6))
+ self.assertEqual( U[0], SBasis.shift(U, 2)[2] )
+
+ for I in SBasis.level_set(W, 2, tol = 1e-7):
+ self.assertAlmostEqual( W(I.mid()), 2 )
+ for I in SBasis.level_set(W, Interval(0, 1), tol = 1e-7, vtol = 1e-7):
+ self.assertTrue( 0 <= W(I.begin()) <= 1 )
+ self.assertTrue( 0 <= W(I.mid()) <= 1 )
+ self.assertTrue( 0 <= W(I.end()) <= 1 )
+
+ def test_bezier(self):
+ B = Bezier()
+ C = Bezier(2)
+ D = Bezier(2, 4)
+ E = Bezier(1, 3, 9)
+ F = Bezier(-2, 5, -1, 2)
+ self.assertTrue( B.is_zero() )
+ self.assertTrue( C.is_constant() )
+ self.assertTrue( D.is_finite() )
+ C.clear()
+ self.assertEqual(D.degree(), 1)
+ self.assertEqual(E.at0(), 1)
+ self.assertEqual(E.at1(), 9)
+ self.assertEqual(E[2], 9)
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual( D(t), lerp(t, 2, 4) )
+ self.assertAlmostEqual( D(t), D.value_at(t))
+ self.assertAlmostEqual( D.value_and_derivatives(t, 0)[0], D(t) )
+ self.assertAlmostEqual( D.value_and_derivatives(t, 1)[1], Bezier.derivative(D)(t) )
+ self.assertAlmostEqual( Bezier.integral(D).value_and_derivatives(t, 1)[1], D(t) )
+ #~ self.assertAlmostEqual( D.elevate_degree().reduce_degree()(t), D(t) )
+ self.assertAlmostEqual( (D+2)(t), D(t)+2 )
+ self.assertAlmostEqual( (D-1)(t), D(t)-1 )
+ self.assertAlmostEqual( (D*2)(t), D(t)*2 )
+ self.assertAlmostEqual( (D/4)(t), D(t)/4 )
+ self.assertTrue( Bezier.bounds_fast(F).Interval.contains(F(t)) )
+ self.assertTrue( Bezier.bounds_exact(F).Interval.contains(F(t)) )
+ self.assertTrue( Bezier.bounds_local(F, OptInterval(t-0.05, t+0.05)).Interval.contains(F(t)) )
+ for r in F.roots():
+ self.assertAlmostEqual(F(r), 0)
+ #TODO: bug in 2geom?
+ #~ for r in F.roots(Interval(0.1, 0.8)):
+ #~ self.assertAlmostEqual(F(r), 0)
+ #~ self.assertTrue( 0.1 <= r <= 0.8 )
+ self.assertIsInstance(F.forward_difference(1), Bezier)
+ self.assertIsInstance(F.elevate_degree(), Bezier)
+ self.assertIsInstance(E.reduce_degree(), Bezier)
+ #F.reduce_degree() fails with
+ # *** glibc detected *** python2: malloc(): memory corruption:
+ self.assertIsInstance(F.elevate_to_degree(4), Bezier)
+ self.assertIsInstance(F.deflate(), Bezier)
+ S = F.to_SBasis()
+ self.assertIsInstance(S, SBasis)
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual(S(t), F(t))
+
+ def curve(self, C):
+ self.assertAlmostEqual(C.initial_point(), C(0))
+ self.assertAlmostEqual(C.final_point(), C.point_at(1))
+ #Doesn't have to be true
+ #~ if C.length() > 0.01:
+ #~ self.assertFalse(C.is_degenerate())
+
+ if C.is_degenerate():
+ #trivial special case
+ return
+
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual(C(t).x, C.point_at(t).x)
+ self.assertAlmostEqual(C(t).y, C.value_at(t, 1))
+ self.assertEqual( C(t), C.point_and_derivatives(t, 1)[0] )
+ self.assertTrue( C.bounds_exact().contains(C(t)) )
+ self.assertTrue( C.bounds_fast().contains(C(t)) )
+ #TODO why this works only with degree = 0?
+ if C.bounds_local(OptInterval(t-0.05, t+0.05), 0
+ ) and (
+ C.bounds_local(OptInterval(t-0.05, t+0.05), 0).Rect.area() > 1e-10):
+ #ruling out too small rectangles, they have problems with precision
+ self.assertTrue( C.bounds_local( OptInterval(t-0.05, t+0.05), 0 ).Rect.contains(C(t)))
+ D = C.duplicate()
+
+ D.set_initial(Point())
+ self.assertAlmostEqual(D.initial_point(), Point())
+
+ D.set_final(Point(1, 1))
+ self.assertAlmostEqual(D.final_point(), Point(1, 1))
+
+ A = Affine( uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10),
+ uniform(-10, 10))
+ E = C.transformed(A)
+ for i in range(11):
+ t = i/10.0
+ # self.assertAlmostEqual( E(t), C(t)*A )
+ G1 = C.portion(0.2, 0.8)
+ G2 = C.portion( interval=Interval(2, 8)/10 )
+ self.assertAlmostEqual( G1(0), C(0.2) )
+ self.assertAlmostEqual( G2(0.5), C( lerp(0.5, 0.2, 0.8) ))
+ self.assertAlmostEqual( G1(1), G2(1) )
+
+ for i in range(11):
+ t = i/10.0
+ self.assertAlmostEqual( C.reverse()(t), C(1-t) )
+ self.assertAlmostEqual( C.point_and_derivatives(0.3, 1)[1], C.derivative()(0.3) )
+
+ self.assertAlmostEqual( C.nearest_time(C(0)), 0 )
+ self.assertAlmostEqual( C( C.nearest_time(C(0.5), interval=Interval(0.2, 0.5)) ), C(0.5) )
+ self.assertAlmostEqual( C( C.nearest_time(C(0.5), 0.2, 0.5) ), C(0.5) )
+ for p in C.all_nearest_times( C(0), 0, 1):
+ self.assertEqual(C(p), C(0))
+ for p in C.all_nearest_times( C(1), interval=Interval(0, 1)):
+ self.assertEqual(C(p), C(1))
+ for r in C.roots(0, 0):
+ self.assertAlmostEqual(C.value_at(r, 0), 0)
+
+ self.assertGreaterEqual(C.length(), abs(C(1) - C(0)))
+ self.assertEqual(C.winding(Point()), int(C.winding(Point())) )
+ self.assertAlmostEqual( C.unit_tangent_at(0.5),
+ Point.unit_vector(C.derivative()(0.5)) )
+ self.assertTrue(isinstance(C.to_SBasis()[0], SBasis))
+
+ def test_sBasisCurve(self):
+ S = SBasisCurve(SBasis(0, 2), SBasis(3, 7)*SBasis(1, 8))
+ a = SBasis(3, 9)*SBasis(4, 6)
+ b = SBasis(2, 0)
+ c = a(b)
+ self.curve(S)
+ self.curve(S.derivative())
+ self.curve(S.reverse())
+ self.curve(S.transformed( Scale(4) ))
+ self.curve(S.transformed( Zoom(9, Translate(3, 6)) ))
+ self.curve(SBasisCurve(a*b*c, a+b+c))
+ self.curve(S.derivative().derivative())
+
+ def test_bezierCurve(self):
+ B = BezierCurve.create( [ Point(0, 5), Point(3, 65), Point(-3, 2), Point(1, 9) ] )
+ C = BezierCurve.create( [ Point(0,1), Point(1, 0) ] )
+ self.curve(B)
+ self.curve(C)
+ self.curve(C.reverse())
+ self.curve(B.portion(0, 2))
+ self.curve(B.transformed(Zoom(9, Translate(3, 6))))
+ self.curve(B.derivative())
+
+ def ntest_lineSegment(self):
+ L = LineSegment(Point(2, 8), Point(1, 9))
+ K = LineSegment.from_beziers(Bezier(2, 8), Bezier(-1, 9))
+ self.curve(L)
+ self.curve(K)
+ self.curve(L.reverse())
+ self.curve(L.portion(Interval(0.2, 0.4)))
+ self.curve(L.subdivide(0.3)[0])
+ self.curve(L.subdivide(0.3)[1])
+ self.curve(L.derivative())
+ self.curve(L.transformed(Scale(30)*Translate(3, 9)))
+
+ self.curve(LineSegment())
+
+ def test_quadraticBezier(self):
+ Q = QuadraticBezier(Point(2, 8), Point(1, 9), Point(-2, 3))
+ R = QuadraticBezier.from_beziers(Bezier(2, 8, 4), Bezier(-1, 9, 9))
+ self.curve(Q)
+ self.curve(R)
+ self.curve(Q.reverse())
+ self.curve(Q.portion(interval=Interval(0.1, 0.9)))
+ self.curve(Q.subdivide(0.8)[0])
+ self.curve(Q.subdivide(0.8)[1])
+ self.curve(Q.derivative())
+ self.curve(Q.transformed(Scale(-3)*Translate(4, 8)))
+
+ self.curve(QuadraticBezier())
+
+ def test_cubicBezier(self):
+ C = CubicBezier(Point(2, 0), Point(-1, 2.9), Point(-2, 3), Point(3, 1))
+ D = CubicBezier.from_beziers(Bezier(2, 8, 4, 7), Bezier(-1, 9, 9, 8))
+ print 343
+ self.curve(C)
+ self.curve(D)
+ self.curve(C.reverse())
+ #Some kind of numerical instability imo
+ #~ self.curve(C.portion(Interval(0.1, 0.9)))
+ self.curve(C.subdivide(0.8)[0])
+ self.curve(C.subdivide(0.8)[1])
+ self.curve(C.derivative())
+ self.curve(C.transformed(Scale(-3)*Translate(4, 8)))
+
+ self.curve(CubicBezier())
+
+ def test_hLineSegment(self):
+ H = HLineSegment(Point(3, 9), Point(9, 9))
+ I = HLineSegment(Point(1, 3), Point(92, 3))
+ J = HLineSegment.from_point_length( Point(2, 4), 1)
+ self.curve( H )
+ self.curve( I )
+ self.curve( J )
+ self.curve( H.portion(0, .25) )
+ self.curve( H.derivative() )
+ self.curve( H.transformed(Rotate(20)) )
+ self.curve( HLineSegment() )
+ self.curve( I.reverse() )
+ map(self.curve, I.subdivide(0.8))
+
+ self.assertAlmostEqual(I.get_Y(), 3)
+ J.set_Y(2)
+ J.set_initial_X(0)
+ J.set_final_X(1)
+ self.assertAlmostEqual( J(0), Point(0, 2) )
+ self.assertAlmostEqual( J(1), Point(1, 2) )
+
+ def test_vLineSegment(self):
+ V = VLineSegment(Point(2, 9), Point(2, 6))
+ W = VLineSegment(Point(1, 2), Point(1, 8))
+ X = VLineSegment.from_point_length( Point(2, 4), 1)
+ #~ self.curve( V )
+ #~ self.curve( W )
+ #~ self.curve( X )
+ #~ self.curve( V.portion(0, .25) )
+ #~ self.curve( V.derivative() )
+ #~ self.curve( V.transformed(Rotate(20)) )
+ #~ self.curve( VLineSegment() )
+ #~ self.curve( W.reverse() )
+ #~ map(self.curve, W.subdivide(0.8))
+ #~
+ #~ self.assertAlmostEqual(I.get_Y(), 3)
+ #~ X.set_Y(2)
+ #~ X.set_initialX(0)
+ #~ X.set_finalX(1)
+ #~ self.assertAlmostEqual( X(0), Point(0, 2) )
+ #~ self.assertAlmostEqual( X(1), Point(1, 2) )
+ #~ print V(0.5)
+ #~ print V.nearest_time(V(0.5), 0.1, 0.4 )
+ #~ print V.nearest_time(V(0.5), Interval(0.2, 0.5))
+ #~ print V(0.5), V(0.2)
+ #TODO:
+ #this is likely a bug in 2geom, following code
+
+ #~ VLineSegment V(Point(0, 0), 2);
+ #~ printf("%f\n", V.nearest_time(V(0.5), 0.2, 0.5));
+
+ #prints
+ #0.2
+
+ def test_ellipticalArc(self):
+ E = EllipticalArc()
+ self.curve(E)
+ F = EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1))
+
+ self.assertTrue(F.sweep())
+ self.assertTrue(F.large_arc())
+ self.assertAlmostEqual(F.chord()(0), Point())
+ self.assertAlmostEqual(F.chord()(1), Point(1, 1))
+
+ F.set_extremes(Point(1, 1), Point(-1, 1))
+ self.assertAlmostEqual(F.initial_point(), Point(1, 1))
+ self.assertAlmostEqual(F.final_point(), Point(-1, 1))
+ self.assertEqual(F.initial_angle(), F.angle_at(0))
+ self.assertEqual(F.final_angle(), F.angle_at(1))
+ self.assertTrue(F.contains(F.angle_at(0.5)))
+
+ G = EllipticalArc(Point(), 1, 1, 0, True, True, Point(2, 0))
+ for i in range(11):
+ t = i/10.0
+ print G(t)
+ self.assertAlmostEqual(G.extent(), math.pi)
+ self.assertAlmostEqual(G.extent(), G.sweep_angle())
+ self.assertAlmostEqual(float(G.angle_at(0.5)), -math.pi/2)
+
+ self.assertAlmostEqual(Point(1, 1), G.rays())
+ self.assertAlmostEqual(1, G.ray(1))
+ self.assertAlmostEqual(0, float(G.rotation_angle()))
+
+ self.assertAlmostEqual(G.extent(), G.angle_interval().extent())
+
+ self.assertAlmostEqual(G.center(), Point(1, 0))
+ #unit half-circle
+ U = EllipticalArc(Point(1, 0), 1, 1, 0, True, True, Point(-1, 0))
+
+ G.set(Point(), 1, 1, 0, True, False, Point(1, 0))
+
+ A = G.unit_circle_transform()
+
+ self.assertAlmostEqual( G(0.5), U.transformed(A)(0.5) )
+ self.assertAlmostEqual( G.value_at_angle(G.angle_at(0.32), 0), G.value_at(0.32, 0) )
+
+ self.assertTrue(G.contains_angle(Angle(math.pi/4)))
+ self.assertFalse(G.is_SVG_compliant())
+ #~ self.curve(F)
+ #TODO:
+ #F.point_and_derivatives(t, 1)[0] differs from F(0) and F.bounds_exact,
+ #F.bounds_fast doesn't contain F(1)
+
+unittest.main()
diff --git a/src/cython/test-path.py b/src/cython/test-path.py
new file mode 100644
index 0000000..6ad6af4
--- /dev/null
+++ b/src/cython/test-path.py
@@ -0,0 +1,218 @@
+import unittest
+import math
+from random import randint, uniform
+
+import cy2geom
+
+from cy2geom import Point, IntPoint
+from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval
+
+from cy2geom import Affine
+from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom
+from cy2geom import Eigen
+
+from cy2geom import Curve
+from cy2geom import Linear
+from cy2geom import SBasis, SBasisCurve
+from cy2geom import Bezier, BezierCurve
+
+from cy2geom import LineSegment, QuadraticBezier, CubicBezier
+from cy2geom import HLineSegment, VLineSegment
+
+from cy2geom import EllipticalArc
+
+from cy2geom import Path
+
+#TODO! move drawing elsewhere, it nice to see paths, but is not very suitable for automatic testing
+draw = False
+
+try:
+ import utils
+except ImportError:
+ print "No drawing with Tk"
+ draw = False
+
+class TestPrimitives(unittest.TestCase):
+ def curves_equal(self, C1, C2):
+ for i in range(101):
+ t = i/100.0
+ self.assertAlmostEqual(C1(t), C2(t))
+ def path(self, P):
+ for curve in P:
+ self.assertIsInstance(curve, Curve)
+
+ self.assertAlmostEqual(P(0), P.front()(0))
+ self.curves_equal(P.front(), P[0])
+
+ self.curves_equal(P.back_default(), P[P.size_default()-1])
+ self.curves_equal(P.back_open(), P.back())
+ self.assertEqual(P.size_open(), P.size())
+
+ self.assertFalse(P.empty() ^ (P.size()==0))
+
+ exact = P.bounds_exact().Rect
+ exact.expand_by(1e-5)
+
+ fast = P.bounds_fast().Rect
+ fast.expand_by(1e-5)
+ A1 = Affine(3, 1, 8, 3, 9, 9)
+ A2 = Rotate(0.231)
+
+ for i in range(100 * P.size_open() + 1):
+ t = i/100.0
+ self.assertTrue(exact.contains(P(t)))
+ self.assertTrue(fast.contains(P(t)))
+ self.assertAlmostEqual( (P*A1)(t) , P(t)*A1 )
+ self.assertAlmostEqual( (P*A2)(t) , P(t)*A2 )
+
+ self.assertAlmostEqual(P(t), P.point_at(t))
+ self.assertAlmostEqual(P(t).x, P.value_at(t, 0))
+ self.assertAlmostEqual(P(t).y, P.value_at(t, 1))
+
+ if P.closed():
+ self.curves_equal(P.back_default(), P.back_closed())
+ self.assertEqual(P.size_default(), P.size_closed())
+ else:
+ self.curves_equal(P.back_default(), P.back_open())
+ self.assertEqual(P.size_default(), P.size_open())
+
+ for i in range(10):
+ for root in P.roots(i, 0):
+ if root < P.size_default():
+ self.assertAlmostEqual(P.value_at(root, 0), i)
+ for root in P.roots(i, 1):
+ if root < P.size_default():
+ self.assertAlmostEqual(P.value_at(root, 1), i)
+
+ for t in P.all_nearest_times(P(0)):
+ self.assertAlmostEqual(P(t), P(0))
+ self.assertAlmostEqual(min(P.all_nearest_times( P(0) )), 0)
+ self.assertAlmostEqual(P.nearest_time(P(0), 0, 0.2), 0)
+ self.assertEqual( len(P.nearest_time_per_curve(Point())), P.size_default() )
+
+ t, distSq = P.nearest_time_and_dist_sq(Point(-1, -1), 0, P.size())
+ self.assertAlmostEqual(distSq**0.5, abs(P(t)-Point(-1, -1)) )
+
+ self.assertAlmostEqual(P.portion(0.3, 0.4)(0), P(0.3))
+ self.assertAlmostEqual( P.portion( interval=Interval(P.size(), P.size() * 2) / 3 )(0),
+ P(P.size()/3.0))
+
+ self.assertAlmostEqual(P(0.23), P.reverse()(P.size()-0.23))
+
+ self.assertAlmostEqual(P.initial_point(), P(0))
+ self.assertAlmostEqual(P.final_point(), P(P.size()))
+ def test_path(self):
+ a = Path()
+ a.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) )
+
+ self.assertEqual(a.size(), 1)
+ self.assertFalse(a.closed())
+ self.path(a)
+
+ a.close(True)
+ self.assertTrue(a.closed())
+ self.path(a)
+
+ a.close(False)
+ a.append_curve( LineSegment(a.final_point(), Point(3, 5)) )
+ self.assertEqual(a.size(), 2)
+ self.path(a)
+
+ a.append_SBasis( SBasis(3, 6)*SBasis(1, 0), SBasis(5, 2))
+ self.path(a)
+
+ a.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS)
+ #Stitching adds new segment
+ self.assertEqual(a.size(), 5)
+
+ b = Path()
+ for c in a:
+ b.append_curve(c)
+
+ #TODO: This fails with STITCH_DISCONTINUOUS, but also does so in C++, so
+ #it's either correct behaviour or bug in 2geom
+ #~ self.path(b)
+
+ b.insert(2, LineSegment(b[2-1](1), b[2](0))) #, Path.STITCH_DISCONTINUOUS)
+ self.curves_equal(LineSegment(b[2-1](1), b[2](0)), b[2])
+ #TODO! fails on root finding
+ #self.path(b)
+
+ b.set_initial(a[2](1))
+ b.set_final(a[3](0))
+
+ a.insert_slice(3, b, 0, b.size())
+ self.assertEqual(a.size(), b.size()*2-1)
+
+ for i in range(b.size()):
+ self.curves_equal(a[3+i], b[i])
+
+ #Looks like bug:
+# A = Path()
+# A.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) )
+# A.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS)
+# print A.roots(0, 1)
+
+ #Roots are [1.0, 2.768305708350847, 3.25], Point at second root is
+ #Point (2.32, -0.48)
+ #and third root is > 3 - it corresponds to root on closing segment, but A is open,
+ #and computing A(3.25) results in RangeError - this might be bug or feature.
+
+ self.path(a.portion(0.232, 3.12))
+ self.path(a.portion( interval=Interval(0.1, 4.7) ))
+ self.path(a.portion(0.232, 3.12).reverse())
+
+ b.clear()
+ self.assertTrue(b.empty())
+
+ aa = Path()
+ for c in a:
+ aa.append_curve(c)
+
+ a.erase(0)
+ self.assertEqual(a.size(), aa.size() - 1)
+ self.assertAlmostEqual(a(0), aa(1))
+
+ a.erase_last()
+ self.assertEqual(a.size(), aa.size() - 2)
+ self.assertAlmostEqual(a.final_point(), aa[aa.size()-2](1))
+
+ a.replace(3, QuadraticBezier(a(3), Point(), a(4)))
+ self.assertEqual(a.size(), aa.size() - 2)
+
+ cs = [LineSegment(Point(-0.5, 0), Point(0.5, 0)).transformed( Rotate(-math.pi/3 * i)*Translate(Point(0, math.sqrt(3)/2)*Rotate(-math.pi/3 * i)) ) for i in range(6)]
+
+ hexagon = Path.fromList(cs, stitching = Path.STITCH_DISCONTINUOUS, closed = True)
+
+ if draw:
+ utils.draw(hexagon, scale = 100)
+
+ #to = 5 because each corner contains one stitching segment
+ half_hexagon = Path.fromPath(hexagon, fr = 0, to = 5)
+ if draw:
+ utils.draw(half_hexagon, scale = 100)
+
+ half_hexagon.replace_slice(1, 5, LineSegment(half_hexagon(1), half_hexagon(5)))
+ self.assertEqual(half_hexagon.size(), 2)
+ self.assertAlmostEqual(half_hexagon(1.5), Point(0.5, 0))
+
+ half_hexagon.stitch_to(half_hexagon(0))
+ self.assertAlmostEqual(half_hexagon(2.5), Point())
+
+ a.start(Point(2, 2))
+ a.append_SBasis( SBasis(2, 6), SBasis(1, 5)*SBasis(2, 9) )
+ self.assertAlmostEqual(a(1), Point(6, 5*9))
+
+ l = Path.fromList([QuadraticBezier(Point(6, 5*9), Point(1, 2), Point(-2, .21))])
+ a.append_path(l)
+ self.assertAlmostEqual(a.final_point(), l.final_point())
+
+ k = Path.fromList([QuadraticBezier(Point(), Point(2, 1), Point(-2, .21)).reverse()])
+ k.append_portion_to(l, 0, 0.3)
+ self.assertAlmostEqual(l.final_point(), k(0.3))
+
+ def test_read_svgd(self):
+ p = Path.read_svgd("../toys/spiral.svgd")
+ if draw:
+ utils.draw(p[0], scale=0.4)
+unittest.main()
diff --git a/src/cython/test-primitives.py b/src/cython/test-primitives.py
new file mode 100644
index 0000000..0edf218
--- /dev/null
+++ b/src/cython/test-primitives.py
@@ -0,0 +1,288 @@
+import unittest
+from math import pi, sqrt
+
+import cy2geom
+from cy2geom import Angle, AngleInterval, Point, IntPoint, Line, Ray
+from cy2geom import LineSegment, Curve
+
+class TestPrimitives(unittest.TestCase):
+
+ def test_angle(self):
+ self.assertAlmostEqual(Angle.rad_from_deg(45), pi/4)
+ self.assertAlmostEqual(Angle.deg_from_rad(pi/6), 30)
+
+ p = Point(1, sqrt(3))
+ alpha = Angle.from_Point(p)
+ self.assertAlmostEqual(alpha.degrees(), 60)
+
+ beta = Angle.from_radians(pi/5)
+ gamma = Angle.from_degrees(36)
+ self.assertAlmostEqual(beta.radians0(), gamma.radians0())
+ self.assertTrue(beta==gamma)
+ omega = Angle.from_degrees_clock(0)
+ self.assertAlmostEqual(omega.radians(), pi/2)
+
+ delta = Angle(-pi * 0.5)
+ self.assertAlmostEqual(delta.degrees(), -90)
+ self.assertAlmostEqual(delta.radians0(), 1.5*pi)
+ #degreesClock roughly means [ 90 - Angle.degrees() ] mod 360
+ self.assertAlmostEqual(delta.degrees_clock(), 180)
+
+ self.assertAlmostEqual(
+ (beta + gamma).radians(),
+ beta.radians()+gamma.radians() )
+ self.assertAlmostEqual( (beta - gamma).degrees(), 0)
+
+ def test_angleInterval(self):
+ A = AngleInterval(Angle(pi/6), Angle(pi/4))
+ B = AngleInterval( 0, pi/4, cw = True )
+ self.assertEqual(A(0), Angle(pi/6))
+ self.assertEqual(A(0.5), A.angle_at(0.5))
+ self.assertEqual(A(0), A.initial_angle())
+ self.assertEqual(B(1), B.final_angle())
+ self.assertFalse(B.is_degenerate())
+ self.assertTrue(B.contains(Angle(pi/6)))
+ self.assertTrue(A.contains(Angle(pi)))
+
+ self.assertAlmostEqual( B.extent(), pi/4 )
+ def test_point(self):
+ p = Point(3, 4)
+ q = Point(8, 16)
+ p_inf = Point(float('inf'), 1)
+ #y axis points downwards
+ p_ccw = Point(4, -3)
+
+ self.assertAlmostEqual(p.length(), 5)
+ self.assertAlmostEqual(p.ccw(), p_ccw)
+ self.assertAlmostEqual(p_ccw.cw(), p)
+ self.assertAlmostEqual(p[0], 3)
+ self.assertAlmostEqual(p[1], 4)
+
+ self.assertFalse(p_inf.isFinite())
+ self.assertTrue(p.isFinite())
+ self.assertFalse(p.isNormalized())
+ self.assertTrue((p/p.length()).isNormalized())
+ self.assertFalse(p.isZero())
+ self.assertTrue((p*0).isZero())
+
+ self.assertTrue( (p + p.ccw().ccw()).isZero)
+ self.assertAlmostEqual( (q-p).length(), 13)
+
+ self.assertGreater(q, p)
+ self.assertGreaterEqual(q, p)
+ self.assertEqual(p, p)
+ self.assertNotEqual(p, q)
+ self.assertLess( Point(1, 1), Point(1, 2) )
+ self.assertLessEqual(p, p)
+
+ self.assertTrue( Point.are_near(
+ Point.polar(pi/4, sqrt(2)),
+ Point(1, 1) ))
+ self.assertAlmostEqual(sqrt(2), Point.L2(Point(1, 1)))
+ self.assertAlmostEqual(2, Point.L2sq(Point(1, 1)))
+ self.assertAlmostEqual( Point.middle_point(Point(), q), q/2 )
+ self.assertAlmostEqual( Point.rot90(p), p.cw() )
+ self.assertAlmostEqual(
+ Point.lerp(0.2, Point(), Point(3,4)).length(),
+ 1)
+ self.assertAlmostEqual(Point.dot(p, p_ccw), 0)
+ self.assertAlmostEqual(Point.dot(p, p_inf), float('inf'))
+ self.assertAlmostEqual(Point.dot(p, q), 88)
+ #TODO this might be implemented incorrectly in lib2geom!
+ self.assertAlmostEqual(Point.cross(p, q), -16)
+
+ self.assertAlmostEqual(Point.distance(p, q), 13)
+ self.assertAlmostEqual(Point.distanceSq(p, p_ccw), 50)
+ self.assertAlmostEqual(Point.unit_vector(p), p/5)
+
+ self.assertAlmostEqual(Point.L1(p), 7)
+ self.assertAlmostEqual(Point.L1(p_inf), float('inf'))
+ self.assertAlmostEqual(Point.LInfty(q), 16)
+ self.assertAlmostEqual(Point.LInfty(p_inf), float('inf'))
+
+ self.assertTrue(Point.is_zero(Point()))
+ self.assertFalse(Point.is_zero(p))
+ self.assertTrue(Point.is_unit_vector(p/5))
+ self.assertFalse(Point.is_unit_vector(q))
+
+ self.assertAlmostEqual(Point.atan2(Point(1, 1)), pi/4)
+ self.assertAlmostEqual(Point.angle_between(p, p_ccw), -pi/2)
+ self.assertAlmostEqual(Point.abs(-p), p)
+ #TODO I have no idea what should this function do
+ # self.assertAlmostEqual(
+ # Point.constrain_angle(Point(1, 0), Point(0, 1), 1, Point(sqrt(2)/2, sqrt(2)/2)),
+ #
+ # ))
+
+ def test_intPoint(self):
+ p = Point(4.89, 3.21)
+ self.assertEqual(p.round(), IntPoint(5, 3))
+ self.assertEqual(p.floor(), IntPoint(4, 3))
+ self.assertEqual(p.ceil(), IntPoint(5, 4))
+
+ self.assertEqual(p.ceil().x, 5)
+ self.assertEqual(p.floor().y, 3)
+ self.assertEqual(IntPoint(), p.floor()-p.floor())
+
+ a = IntPoint(2, -5)
+ b = IntPoint(5, 3)
+ self.assertEqual(IntPoint(7, -2), a+b)
+ self.assertEqual(IntPoint(3, 8), b-a)
+ self.assertGreater(b, a)
+ self.assertGreaterEqual(b, b)
+ self.assertNotEqual(a, b)
+
+ def test_line(self):
+
+ l = Line(Point(), pi/4)
+ self.assertAlmostEqual( l.origin(), Point() )
+ self.assertAlmostEqual( l.versor(), Point(1, 1)/sqrt(2) )
+ self.assertAlmostEqual( l.angle(), pi/4 )
+
+ k = Line.from_points(Point(), Point(2, 1))
+ self.assertFalse(k.is_degenerate())
+ self.assertFalse(Line().is_degenerate())
+ self.assertAlmostEqual( l.point_at(sqrt(2)), Point(1,1) )
+ self.assertAlmostEqual(
+ k.point_at(43),
+ Point(k.value_at(43, 0), k.value_at(43, 1)))
+ self.assertAlmostEqual(k.time_at(Point(4, 2)), sqrt(20))
+ self.assertAlmostEqual(
+ k.time_at_projection(Point(4, 2) + Point(2, -4)),
+ sqrt(20))
+ self.assertAlmostEqual(
+ k.point_at(k.nearest_time(Point(4, 2) + Point(2, -4))),
+ Point(4,2))
+ self.assertAlmostEqual(
+ k.time_at_projection(Point(3, 3)),
+ -k.reverse().time_at_projection(Point(3, 3)))
+ self.assertAlmostEqual( k.derivative().origin(), k.versor())
+ self.assertAlmostEqual(k.normal(), k.versor().cw())
+
+ roots = k.roots( 3, 0 )
+ for root in roots:
+ self.assertAlmostEqual( k.value_at(root, 0), 3)
+
+ self.assertAlmostEqual(l.normal(), l.normal_and_dist()[0])
+ self.assertAlmostEqual(Line.distance(Point(), l), l.normal_and_dist()[1])
+
+ self.assertAlmostEqual(Line.distance(Point(-1, 1), l), sqrt(2))
+ self.assertTrue(Line.are_near(Point(0), l))
+ self.assertFalse(Line.are_near(Point(1, 1), k))
+ self.assertTrue(Line.are_near(Point(1, 1), k, 2))
+
+ p = Line(Point(1, 1))
+ p_orto = Line(Point(2, 3), pi/2)
+ p_para = Line(Point(2, 3))
+ p_same = Line.from_points(Point(1, 1), Point(5, 1))
+
+ self.assertTrue(Line.are_orthogonal(p, p_orto))
+ self.assertFalse(Line.are_orthogonal(p, p_para))
+ self.assertTrue(Line.are_parallel(p, p_para))
+ self.assertFalse(Line.are_parallel(p, p_orto))
+ self.assertTrue(Line.are_same(p, p_same))
+ self.assertFalse(Line.are_same(p, p_para))
+
+ self.assertTrue(Line.are_collinear(
+ Point(1,1),
+ Point(2, 3),
+ Point(4, 7)))
+ self.assertAlmostEqual(Line.angle_between(p, p_orto), pi/2)
+
+ m = Line.from_normal_distance(Point(1, -1), 1)
+ self.assertAlmostEqual(m.angle(), pi/4)
+
+ m = Line.from_LineSegment( LineSegment( Point(2, 2), Point(4, 4) ) )
+ self.assertAlmostEqual(m.angle(), pi/4)
+
+ m = Line.from_Ray( Ray(Point(2, 3), 0.2) )
+ self.assertAlmostEqual(m.angle(), 0.2)
+ self.assertAlmostEqual(m.origin(), Point(2, 3))
+
+ self.assertIsInstance(m.portion(2, 4), Curve)
+ self.assertAlmostEqual(m.portion(2, 4)(0), m.point_at(2))
+
+ self.assertIsInstance(m.segment(1, 5), LineSegment)
+ self.assertAlmostEqual(m.segment(1, 5)(1), m.point_at(5))
+
+ self.assertAlmostEqual(m.ray(4).origin(), m.point_at(4))
+
+ m.set_origin(Point())
+ self.assertAlmostEqual(m.origin(), Point())
+
+ m.set_angle(0.2)
+ self.assertAlmostEqual(m.angle(), 0.2)
+
+ m.set_versor(Point())
+ self.assertTrue(m.is_degenerate())
+
+ m.set_points(Point(2, 9), Point(1, 8))
+ self.assertAlmostEqual(m.versor(), Point.unit_vector(Point(1, 8) - Point(2, 9)))
+
+ def test_ray(self):
+ r = Ray(Point(1,1), pi/4)
+ self.assertAlmostEqual(r.origin(), Point(1, 1))
+ self.assertAlmostEqual(r.versor(), Point(1, 1)/sqrt(2))
+ self.assertAlmostEqual(r.angle(), pi/4)
+
+ r.set_origin(Point(4, 3))
+ #TODO this should maybe normalize the versor!
+ r.set_versor(Point(1, -1)/sqrt(2))
+ self.assertAlmostEqual(r.origin(), Point(4, 3))
+ self.assertAlmostEqual(r.versor(), Point(1, -1)/sqrt(2))
+ self.assertAlmostEqual(r.angle(), -pi/4)
+
+ r.set_points(Point(1, 1), Point(1, 3))
+ self.assertFalse(r.is_degenerate())
+ self.assertFalse(Ray().is_degenerate())
+ self.assertAlmostEqual(r.point_at(4), Point(1, 5))
+
+ #TODO I think this should be expected behaviour
+# self.assertAlmostEqual(
+# r.pointAt(-3),
+# Point(1, 1)))
+ self.assertAlmostEqual(r.value_at(4, 0), 1)
+ self.assertAlmostEqual(r.value_at(4, 1), 5)
+
+ roots = r.roots( 3, 1 )
+ for root in roots:
+ self.assertAlmostEqual( r.value_at(root, 1), 3)
+
+ self.assertAlmostEqual(
+ r.point_at(3) - r.origin(),
+ r.origin()-r.reverse().point_at(3))
+
+ self.assertAlmostEqual(Ray.distance(Point(), r), sqrt(2))
+ self.assertAlmostEqual(Ray.distance(Point()+r.versor(), r), 1)
+
+ self.assertTrue(Ray.are_near(Point(), r, 2))
+ self.assertFalse(Ray.are_near(Point(), r))
+ self.assertTrue(Ray.are_same(r, r))
+
+ q = Ray(r.origin(), r.angle())
+ self.assertTrue(Ray.are_same(r, q))
+
+ q.set_origin(r.origin()+Point(0, 1))
+ self.assertFalse(Ray.are_same(r, q))
+ #TODO shouldn't this really be 0?
+ self.assertAlmostEqual(Ray.angle_between(r, q), 2*pi)
+
+ q.set_versor(Point(1, 0))
+ q.set_origin(r.origin())
+ self.assertAlmostEqual(
+ Point(1, 1)/sqrt(2),
+ Ray.make_angle_bisector_ray(q, r).versor())
+
+ q.set_angle(pi/7)
+ self.assertAlmostEqual(q.angle(), pi/7)
+
+ self.assertIsInstance(q.portion(2, 4), Curve)
+ self.assertAlmostEqual(q.portion(2, 4)(0), q.point_at(2))
+
+ self.assertIsInstance(q.segment(1, 5), LineSegment)
+
+ self.assertAlmostEqual(q.segment(1, 5)(1), q.point_at(5))
+
+
+unittest.main()
+
diff --git a/src/cython/test-rectangle.py b/src/cython/test-rectangle.py
new file mode 100644
index 0000000..51f3c50
--- /dev/null
+++ b/src/cython/test-rectangle.py
@@ -0,0 +1,601 @@
+import unittest
+from math import pi, sqrt
+from random import randint
+
+import cy2geom
+
+from cy2geom import Point, IntPoint
+
+from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval
+from cy2geom import GenericInterval, GenericOptInterval
+
+from cy2geom import Rect, OptRect, IntRect, OptIntRect
+from cy2geom import GenericRect
+
+from fractions import Fraction
+
+
+class TestPrimitives(unittest.TestCase):
+ def interval_basic(self, I, J):
+ #for simplicity
+ self.assertTrue(I.min() >= 0)
+ self.assertTrue(J.min() >= 0)
+ a = I.min()
+ b = I.max();
+ self.assertAlmostEqual(I.middle(), (a+b)/2);
+ self.assertAlmostEqual(I.extent(), (b-a));
+ if a != b:
+ self.assertFalse(I.is_singular())
+ else:
+ self.assertTrue(I.is_singular())
+
+ I.expand_by(a)
+ self.assertAlmostEqual(I.min(), 0);
+ self.assertAlmostEqual(I.max(), a+b)
+ I.set_min(a)
+ I.set_max(b)
+
+ self.assertTrue(I.contains(a+ (b-a)/3 ))
+ self.assertTrue(I.contains(a))
+ self.assertTrue(I.contains_interval(I))
+
+ if (not I.is_singular()) or I.min() != 0 :
+ pass
+ self.assertFalse(I.contains_interval(I+I))
+ self.assertFalse(I.contains(a-1))
+
+ c = J.min()
+ d = J.max()
+ self.assertAlmostEqual( (I+J).min(), a+c )
+ self.assertAlmostEqual((I|J).min(), min(a, c))
+ J.set_min(a+2)
+ J.set_max(b+2)
+ self.assertEqual(I+2, J)
+ I.expand_to(2*b)
+ self.assertAlmostEqual(I.max(), 2*b)
+
+ def test_interval(self):
+ I = Interval(1.2, 5)
+ J = Interval(0, 0.3)
+ self.interval_basic(I, J)
+
+ self.assertTrue(I.interior_contains(I.middle()))
+ self.assertFalse(I.interior_contains(I.min()))
+ self.assertFalse(I.interior_contains_interval(I))
+ self.assertTrue(I.interior_contains_interval(Interval(I.min()+1, I.max()-1)))
+
+ self.assertTrue(I.interior_intersects(I))
+ self.assertFalse(I.interior_intersects(-I))
+ p = [1, 2, 3.442, 3]
+ K = Interval.from_list(p)
+ self.assertAlmostEqual(K.max(), max(p))
+ self.assertAlmostEqual((K+Interval(1.0)).min(), min(p)+1)
+ L = Interval(10/3.0)
+ for i in range(3):
+ K+=L
+ self.assertAlmostEqual(K.max(), max(p)+10)
+
+ #TODO This 2geom behaviour is a bit strange
+ self.assertEqual(Interval(3.0)|Interval(5.0),
+ Interval(3.0, 5.0))
+ self.assertAlmostEqual((K-L).max(), (K-10/3.0).max())
+
+ self.assertAlmostEqual((K*3.4).max(), 3.4*K.max())
+ self.assertAlmostEqual((K/3).extent(), K.extent()/3)
+
+ def test_optInterval(self):
+ I = OptInterval(2.2, 9.3)
+ J = Interval(3, 13)
+ K = OptInterval.from_Interval(J)
+ self.assertEqual(K.Interval, J)
+ self.interval_basic(K.Interval, I.Interval)
+
+ L = OptInterval()
+
+ self.assertFalse(L)
+ self.assertTrue( (L&I).is_empty() )
+ L.intersect_with(I)
+ self.assertFalse(L)
+
+ L |= I
+
+ self.assertEqual(L.Interval, I.Interval)
+
+ self.assertEqual((I & K).Interval, Interval(3, 9.3))
+
+ def test_intInterval(self):
+ I = IntInterval(2, 6)
+ J = IntInterval(0, 1)
+ self.interval_basic(I, J)
+ p = [3, 2.3, 65.3, 43]
+ K = IntInterval.from_list(p)
+ self.assertAlmostEqual(K.max(), int(max(p)))
+ self.assertAlmostEqual(int((K+IntInterval(1.0)).min()), int(min(p)+1))
+ L = IntInterval(3)
+ for i in range(3):
+ K+=L
+ self.assertAlmostEqual(K.max(), int(max(p))+9)
+
+ self.assertEqual(Interval(3)|Interval(5),
+ Interval(3, 5))
+ self.assertAlmostEqual((K-L).max(), (K-3).max())
+
+ def test_optIntInterval(self):
+ I = OptIntInterval(2, 9)
+ J = IntInterval(3, 13)
+ K = OptIntInterval.from_Interval(J)
+ self.assertEqual(K.Interval, J)
+ self.interval_basic(K.Interval, I.Interval)
+
+ L = OptIntInterval()
+
+ self.assertFalse(L)
+ self.assertTrue( (L&I).is_empty() )
+ L.intersect_with(I)
+ self.assertFalse(L)
+
+ L |= I
+
+ self.assertEqual(L.Interval, I.Interval)
+
+ self.assertEqual((I & K).Interval, IntInterval(3, 9))
+
+ def test_genericInterval(self):
+ maxv = 100000
+ test_vars = [
+ ( (randint(0, maxv), randint(0, maxv)), (randint(0, maxv), randint(0, maxv)) ),
+ ( (3,), (2, 0) ),
+ ( (0.0, 9), (4, 1.3)),
+ ((2.98, sqrt(2)), (sqrt(7),)),
+ ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) )
+ ]
+ for a,b in test_vars:
+ self.interval_basic( GenericInterval(*a), GenericInterval(*b) )
+
+ def test_genericOptInterval(self):
+ test_vars = [
+ ( (3,), (2, 0) ),
+ ( (0.0, 9), (4, 1.3)),
+ ((2.98, sqrt(2)), (sqrt(7),)),
+ ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) )
+ ]
+
+ for a, b in test_vars:
+ I = GenericOptInterval(*a)
+ J = GenericInterval(*b)
+ K = GenericOptInterval.from_Interval(J)
+
+ self.assertEqual(I, GenericOptInterval.from_Interval(I))
+
+ self.assertEqual(K.Interval, J)
+ self.interval_basic(K.Interval, I.Interval)
+
+ L = GenericOptInterval()
+
+ self.assertFalse(L)
+ self.assertTrue( (L&I).is_empty() )
+ L.intersect_with(I)
+ self.assertFalse(L)
+
+ L |= I
+
+ self.assertEqual(L.Interval, I.Interval)
+
+ if I.intersect_with(K):
+ if I.Interval.min() <= K.Interval.min():
+ if I.Interval.max() >= K.Interval.max():
+ self.assertEqual( I & K, K)
+ else:
+ self.assertEqual( I & K, GenericInterval(K.min(), I.max()))
+ else:
+ if I.Interval.max() >= K.Interval.max():
+ self.assertEqual( I & K, GenericInterval(I.min(), K.max()))
+ else:
+ self.assertEqual( I & K, I)
+
+ def test_genericRect(self):
+ A = GenericRect(1, 1, 4, 4)
+ self.assertEqual( A.min(), (1, 1) )
+ B = GenericRect(Fraction(1,4), Fraction(9, 94), Fraction(2, 3), Fraction(23, 37))
+
+ amin = A.min()
+ amax = A.max()
+
+ self.assertAlmostEqual(amin[0], A[0].min())
+ self.assertAlmostEqual(amax[1], A[1].max())
+ self.assertEqual(amin, A.corner(0))
+
+ self.assertEqual(amin, (A.left(), A.top()))
+ self.assertEqual(amax, (A.right(), A.bottom()))
+
+ self.assertAlmostEqual( A.width(), A[0].extent() )
+ self.assertAlmostEqual( A.height(), A[1].extent() )
+
+ self.assertEqual( A.dimensions(), ( A.width(), A.height() ) )
+ #~ self.assertEqual( A.midpoint(), (A.min() + A.max())/2 )
+ self.assertAlmostEqual(A.area(), A.width()*A.height())
+ #TODO export EPSILON from 2geom
+ if A.area() > 0:
+ self.assertFalse(A.has_zero_area())
+ else:
+ self.assertTrue(A.has_zero_area())
+ self.assertAlmostEqual(A.max_extent(), max(A.width(), A.height()))
+ self.assertGreaterEqual(A.max_extent(), A.min_extent())
+
+ bmin = B.min()
+ bmax = B.max()
+
+ pdiag = sqrt((amax[0]-amin[0])**2+(amax[1]-amin[1])**2)
+
+ B.set_min(A.midpoint())
+ B.set_max(A.midpoint())
+
+ self.assertTrue(B.has_zero_area())
+
+ B.expand_by(A.min_extent()/3.0)
+
+ self.assertTrue(A.contains_rect(B))
+ self.assertTrue(A.intersects(B))
+ self.assertTrue(B.intersects(A))
+ self.assertFalse(B.contains_rect(A))
+
+ self.assertTrue(A.contains(A.midpoint()))
+ self.assertFalse(A.contains( (A.midpoint()[0]*3, A.midpoint()[1]*3) ))
+
+ A.union_with(B)
+
+ self.assertEqual( A.min(), amin )
+
+ B.set_left(bmin[0])
+ B.set_top(bmin[1])
+ B.set_right(bmax[0])
+ B.set_bottom(bmax[1])
+
+ self.assertEqual(B.min(), bmin)
+ self.assertEqual(B.max(), bmax)
+
+ B.expand_to( (0, 0) )
+ self.assertEqual((0, 0), B.min())
+
+ B.expand_by(*bmax)
+ self.assertEqual(bmax, (- (B.min()[0]), - (B.min()[1])) )
+
+ B.expand_by(-bmax[0], -bmax[1])
+ self.assertEqual(B.max(), bmax)
+
+ self.assertEqual( (A+B.min()).max()[0], A.max()[0] + B.min()[0] )
+
+ #~ self.assertEqual( (A-B.max()).min(), A.min() - B.max() )
+
+ self.assertEqual( A|A, A )
+
+ self.assertFalse( A != A )
+
+ B.set_left(bmin[0])
+ B.set_top(bmin[1])
+ B.set_right(bmax[0])
+ B.set_bottom(bmax[1])
+
+ #~ self.assertAlmostEqual(Rect.distance(Point(), A), A.min().length())
+ #~ self.assertAlmostEqual(Rect.distanceSq(B.min(), A), Rect.distance(B.min(), A)**2 )
+
+
+
+ def rect_basic(self, P, Q):
+ pmin = P.min()
+ pmax = P.max()
+ #for simplicity
+ self.assertTrue(pmin.x > 0)
+ self.assertTrue(pmin.y > 0)
+
+ self.assertAlmostEqual(pmin.x, P[0].min())
+ self.assertAlmostEqual(pmax.y, P[1].max())
+ self.assertEqual(pmin, P.corner(0))
+
+ self.assertEqual(pmin, Point(P.left(), P.top()))
+ self.assertEqual(pmax, Point(P.right(), P.bottom()))
+
+ self.assertAlmostEqual( P.width(), P[0].extent() )
+ self.assertAlmostEqual( P.height(), P[1].extent() )
+
+ self.assertAlmostEqual( P.aspect_ratio(), P.width()/P.height() )
+ self.assertEqual( P.dimensions(), Point( P.width(), P.height() ) )
+ self.assertEqual( P.midpoint(), (P.min() + P.max())/2 )
+ self.assertAlmostEqual(P.area(), P.width()*P.height())
+ #TODO export EPSILON from 2geom
+ if P.area() > 1e-7:
+ self.assertFalse(P.has_zero_area())
+ self.assertTrue(P.has_zero_area(P.area()))
+ else:
+ self.assertTrue(P.has_zero_area())
+ self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height()))
+ self.assertGreaterEqual(P.max_extent(), P.min_extent())
+
+ qmin = Q.min()
+ qmax = Q.max()
+
+ pdiag = (pmax-pmin).length()
+
+ Q.set_min(P.midpoint())
+ Q.set_max(P.midpoint())
+ self.assertTrue(Q.has_zero_area())
+
+ #print P,Q
+ Q.expand_by(P.min_extent()/3.0)
+
+ #print P, Q
+
+ self.assertTrue(P.contains_rect(Q))
+ self.assertTrue(P.intersects(Q))
+ self.assertTrue(Q.intersects(P))
+ self.assertFalse(Q.contains_rect(P))
+
+ self.assertTrue(P.interior_contains_rect(Q))
+ self.assertFalse(P.interior_contains_rect(P))
+ self.assertTrue(P.interior_intersects(Q))
+ self.assertTrue(P.interior_intersects(P))
+
+ self.assertTrue(P.contains(P.midpoint()))
+ self.assertFalse(P.contains(P.midpoint()*3))
+
+ P.union_with(Q)
+
+ self.assertEqual( P.min(), pmin )
+
+ Q.set_left(qmin.x)
+ Q.set_top(qmin.y)
+ Q.set_right(qmax.x)
+ Q.set_bottom(qmax.y)
+
+ self.assertEqual(Q.min(), qmin)
+ self.assertEqual(Q.max(), qmax)
+
+ Q.expand_to( Point() )
+ self.assertEqual(Point(), Q.min())
+ Q.expand_by(qmax)
+ self.assertEqual(qmax, -Q.min())
+
+ Q.expand_by(-qmax.x, -qmax.y)
+ self.assertEqual(Q.max(), qmax)
+
+ self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() )
+
+ self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() )
+
+ self.assertEqual( P|P, P )
+
+ self.assertFalse( P != P )
+
+ Q.set_left(qmin.x)
+ Q.set_top(qmin.y)
+ Q.set_right(qmax.x)
+ Q.set_bottom(qmax.y)
+
+ self.assertAlmostEqual(Rect.distance(Point(), P), P.min().length())
+ self.assertAlmostEqual(Rect.distanceSq(Q.min(), P), Rect.distance(Q.min(), P)**2 )
+
+ self.assertEqual(P.round_outwards()[0], P[0].round_outwards())
+ if P.round_inwards():
+ self.assertEqual(P.round_inwards().Rect[1], P[1].round_inwards().Interval)
+
+
+ def intrect_basic(self, P, Q):
+ pmin = P.min()
+ pmax = P.max()
+ #for simplicity
+ self.assertTrue(pmin.x > 0)
+ self.assertTrue(pmin.y > 0)
+
+ self.assertAlmostEqual(pmin.x, P[0].min())
+ self.assertAlmostEqual(pmax.y, P[1].max())
+ self.assertEqual(pmin, P.corner(0))
+
+ self.assertEqual(pmin, IntPoint(P.left(), P.top()))
+ self.assertEqual(pmax, IntPoint(P.right(), P.bottom()))
+
+ self.assertAlmostEqual( P.width(), P[0].extent() )
+ self.assertAlmostEqual( P.height(), P[1].extent() )
+
+ self.assertAlmostEqual( P.aspect_ratio(), float(P.width())/float(P.height()) )
+ self.assertEqual( P.dimensions(), IntPoint( P.width(), P.height() ) )
+ self.assertEqual( P.midpoint().x, (P.min() + P.max()).x/2 )
+ self.assertAlmostEqual(P.area(), P.width()*P.height())
+
+ if P.area() > 0:
+ self.assertFalse(P.has_zero_area())
+ else:
+ self.assertTrue(P.has_zero_area())
+ self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height()))
+ self.assertGreaterEqual(P.max_extent(), P.min_extent())
+
+ qmin = Q.min()
+ qmax = Q.max()
+
+ Q.set_min(P.midpoint())
+ Q.set_max(P.midpoint())
+ self.assertTrue(Q.has_zero_area())
+
+ Q.expand_by(P.min_extent()/3.0)
+
+ self.assertTrue(P.contains_rect(Q))
+ self.assertTrue(P.intersects(Q))
+ self.assertTrue(Q.intersects(P))
+ self.assertFalse(Q.contains_rect(P))
+
+ self.assertTrue(P.contains(P.midpoint()))
+ self.assertFalse(P.contains(P.midpoint()+P.midpoint()+P.midpoint()))
+
+ P.union_with(Q)
+
+ self.assertEqual( P.min(), pmin )
+
+ Q.set_left(qmin.x)
+ Q.set_top(qmin.y)
+ Q.set_right(qmax.x)
+ Q.set_bottom(qmax.y)
+
+ self.assertEqual(Q.min(), qmin)
+ self.assertEqual(Q.max(), qmax)
+
+ Q.expand_to( IntPoint() )
+ self.assertEqual(IntPoint(), Q.min())
+ Q.expand_by(qmax)
+ self.assertEqual(qmax, IntPoint()-Q.min())
+
+ Q.expand_by(-qmax.x, -qmax.y)
+ self.assertEqual(Q.max(), qmax)
+
+ self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() )
+
+ self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() )
+
+ self.assertEqual( P|P, P )
+
+ self.assertFalse( P != P )
+
+ Q.set_left(qmin.x)
+ Q.set_top(qmin.y)
+ Q.set_right(qmax.x)
+ Q.set_bottom(qmax.y)
+
+
+
+ def test_rect(self):
+
+ P = Rect(0.298, 2, 4, 5)
+
+ self.interval_basic(P[0], P[1])
+ G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3))
+ H = Rect.from_xywh(3.43232, 9.23214, 21.523, -0.31232)
+
+ self.rect_basic(P, G)
+ self.rect_basic(G, H)
+
+ lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)]
+
+ R = Rect.from_list(lst)
+
+ for p in lst:
+ self.assertTrue(R.contains(p))
+
+ self.assertAlmostEqual(min(lst).y, R.min().y)
+ self.assertAlmostEqual(max(lst).y, R.max().y)
+
+
+
+ def test_optRect(self):
+
+ P = OptRect(0.298, 2, 4, 5)
+ self.interval_basic(P.Rect[0], P.Rect[1])
+
+
+ G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3))
+ H = OptRect.from_rect(G)
+
+ self.rect_basic(P.Rect, G)
+
+ lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)]
+
+ R = OptRect.from_list(lst)
+
+ for p in lst:
+ self.assertTrue(R.Rect.contains(p))
+
+ self.assertAlmostEqual(min(lst).y, R.Rect.min().y)
+ self.assertAlmostEqual(max(lst).y, R.Rect.max().y)
+
+ Q = OptRect()
+ self.assertFalse(Q)
+ self.assertTrue(P)
+
+ self.assertTrue(Q.is_empty())
+ self.assertFalse(P.is_empty())
+
+ self.assertTrue(P.contains_rect( P ))
+ self.assertTrue(P.contains_rect(Q))
+ self.assertFalse(Q.contains_rect(P))
+ self.assertFalse(P.intersects(Q))
+ self.assertTrue(P.contains_rect(P.Rect))
+ self.assertTrue(P.contains(P.Rect.midpoint()))
+
+ self.assertEqual(P, OptRect.from_rect(P))
+
+ P.union_with(G)
+ P.union_with(H)
+ self.assertTrue(P.contains_rect(H))
+
+ P.intersect_with(G)
+ self.assertEqual(P, G)
+
+ self.assertEqual( P|H, G )
+ self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x ))
+
+ self.assertFalse(P & Q)
+ self.assertEqual(P, P&P)
+
+ self.assertEqual( P & (R | H), (P & R) | (P & H) )
+
+ def test_intRect(self):
+ A = IntRect(2, 6, 9, 23)
+ B = IntRect.from_intervals(IntInterval(1, 5), IntInterval(8, 9))
+ C = IntRect.from_points(IntPoint(1, 8), IntPoint(5, 9))
+
+ self.assertEqual(B, C)
+
+ self.intrect_basic(A, B)
+ self.intrect_basic(B, C)
+ self.intrect_basic(C, A)
+
+ def test_optIntRect(self):
+ P = OptIntRect(1, 2, 4, 5)
+ self.interval_basic(P.Rect[0], P.Rect[1])
+
+
+ G = IntRect(2, 2, 3, 3)
+ H = OptIntRect.from_rect(G)
+
+ self.intrect_basic(P.Rect, G)
+
+ lst = [IntPoint(randint(-100, 100), randint(-100, 100)) for i in range(10)]
+
+ R = OptIntRect.from_list(lst)
+
+ for p in lst:
+ self.assertTrue(R.Rect.contains(p))
+
+ self.assertAlmostEqual(min(lst).y, R.Rect.min().y)
+ self.assertAlmostEqual(max(lst).y, R.Rect.max().y)
+
+ Q = OptIntRect()
+ self.assertFalse(Q)
+ self.assertTrue(P)
+
+ self.assertTrue(Q.is_empty())
+ self.assertFalse(P.is_empty())
+
+ self.assertTrue(P.contains_rect( P ))
+ self.assertTrue(P.contains_rect( P.Rect ))
+ self.assertTrue(P.contains_rect(Q))
+ self.assertFalse(Q.contains_rect(P))
+ self.assertFalse(P.intersects(Q))
+ self.assertTrue(P.contains_rect(P.Rect))
+ self.assertTrue(P.contains(P.Rect.midpoint()))
+
+ self.assertEqual(P, OptIntRect.from_rect(P))
+
+ P.union_with(G)
+ P.union_with(H)
+ self.assertTrue(P.contains_rect(H))
+
+ P.intersect_with(G)
+ self.assertEqual(P, G)
+
+ self.assertEqual( P|H, G )
+ self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x ))
+
+ self.assertFalse(P & Q)
+ self.assertEqual(P, P&P)
+
+ self.assertEqual( P & (R | H), (P & R) | (P & H) )
+
+unittest.main()
diff --git a/src/cython/utils.py b/src/cython/utils.py
new file mode 100644
index 0000000..1115d32
--- /dev/null
+++ b/src/cython/utils.py
@@ -0,0 +1,52 @@
+from Tkinter import *
+
+import math
+
+import cy2geom
+from cy2geom import Point, Path
+
+
+def Nagon(N):
+ """Return N-agon with side of length 1."""
+ side = cy2geom.LineSegment(Point(-0.5, 0), Point(0.5, 0))
+ angle = 2*math.pi/N
+ distance_to_center = 0.5 / math.tan(math.pi/N)
+ return Path.fromList(
+ [ side.transformed(
+ cy2geom.Translate(Point(0, -distance_to_center))*
+ cy2geom.Rotate(angle*i))
+ for i in range(N)
+ ],
+ stitching = Path.STITCH_DISCONTINUOUS,
+ closed = True )
+
+def draw(c, dt=0.001, batch=10, scale=20, x_offset = 400, y_offset = 300):
+ """Draw curve or path."""
+ master = Tk()
+ w = Canvas(master, width=800, height=600)
+ w.pack()
+ n = 0
+ t = 0
+ points = []
+
+ if isinstance(c, Path):
+ maxt = c.size_default()
+ else:
+ maxt = 1
+
+ while (t < maxt):
+ t = n*dt
+ n+=1
+ p = c(t)
+ points.extend( [ p.x * scale + x_offset, p.y * scale + y_offset] )
+
+ while points:
+ draw_points = tuple(points[:batch*2])
+ if len(points) == 2:
+ break
+ del points[:batch*2]
+
+ l = w.create_line(*draw_points)
+ w.grid()
+
+ master.mainloop()
diff --git a/src/cython/wrapped-pyobject.h b/src/cython/wrapped-pyobject.h
new file mode 100644
index 0000000..bd316fb
--- /dev/null
+++ b/src/cython/wrapped-pyobject.h
@@ -0,0 +1,237 @@
+#include "Python.h"
+#include "2geom/generic-interval.h"
+#include "2geom/generic-rect.h"
+#include "2geom/d2.h"
+
+
+namespace Geom{
+
+//TODO! looks like memory leak
+class WrappedPyObject{
+private:
+ PyObject* self;
+public:
+ //~ int code;
+
+ WrappedPyObject(){
+ //~ printf("Created empty WPO at address %p\n", this);
+ self = NULL;
+ //~ code = 0;
+ }
+
+ WrappedPyObject(WrappedPyObject const &other){
+ self = other.getObj();
+ //~ code = other.code;
+ //~ printf("COPY-CONSTRUCTOR %p, this WPO %p, other WPO %p\n", self, this, &other);
+ Py_XINCREF(self);
+ }
+
+ WrappedPyObject(PyObject* arg){
+ if (arg == NULL){
+ //PyErr_Print();
+ //~ code = c;
+ }
+ else{
+ //~ printf("CONSTRUCTOR %p\n", arg);
+ Py_INCREF(arg);
+ self = arg;
+ //~ code = c;
+ }
+ }
+
+ WrappedPyObject(int c){
+ self = Py_BuildValue("i", c);
+ //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other);
+ Py_INCREF(self);
+ }
+
+
+ ~WrappedPyObject(){
+ //TODO Leaking memory
+ //~ printf("DECREF %p\n", self);
+ //Py_DECREF(self);
+ }
+
+ PyObject* getObj() const {
+ return self;
+ }
+
+ WrappedPyObject operator=(WrappedPyObject other){
+ if (this != &other){
+ self = other.getObj();
+ //~ printf("OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other);
+ Py_XINCREF(self);
+ }
+ return *this;
+ }
+
+ WrappedPyObject operator=(int other){
+
+ self = Py_BuildValue("i", other);
+ //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other);
+ Py_INCREF(self);
+
+ return *this;
+ }
+
+ WrappedPyObject operator-() const {
+ PyObject * ret;
+ ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", "__neg__"), NULL);
+ if (ret == NULL){
+ Py_INCREF(Py_None);
+ return WrappedPyObject(Py_None);
+ }
+ //Py_INCREF(ret);
+ WrappedPyObject * retw = new WrappedPyObject(ret);
+ //Py_DECREF(ret);
+ return *retw;
+ }
+
+ WrappedPyObject arithmetic(PyObject* const other, std::string method, std::string rmethod) const {
+ PyObject * ret;
+ //printf("%p %p\n", self, other);
+ ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", method.c_str()), other, NULL);
+ if (ret == NULL){
+ Py_INCREF(Py_None);
+ return WrappedPyObject(Py_None);
+ }
+ PyObject * isNI = PyObject_RichCompare(ret, Py_NotImplemented, Py_EQ);
+ if ( PyInt_AsLong(isNI) ){
+ ret = PyObject_CallMethodObjArgs(other, Py_BuildValue("s", rmethod.c_str()), self, NULL);
+ }
+ if (ret == NULL){
+ Py_INCREF(Py_None);
+ return WrappedPyObject(Py_None);
+ }
+ WrappedPyObject * retw = new WrappedPyObject(ret);
+ return *retw;
+
+ }
+
+ WrappedPyObject operator+(WrappedPyObject const other) const {
+ return arithmetic(other.getObj(), "__add__", "__radd__");
+ }
+
+ WrappedPyObject operator-(WrappedPyObject const other){
+ return arithmetic(other.getObj(), "__sub__", "__rsub__");
+ }
+
+ WrappedPyObject operator*(WrappedPyObject const other){
+ return arithmetic(other.getObj(), "__mul__", "__rmul__");
+ }
+
+ WrappedPyObject operator*(int other){
+ PyObject* other_obj = Py_BuildValue("i", other);
+ //Py_INCREF(other_obj);
+ WrappedPyObject ret = arithmetic(other_obj, "__mul__", "__rmul__");
+ //Py_DECREF(other_obj);
+ return ret;
+ }
+
+ WrappedPyObject operator/(int other){
+ PyObject* other_obj = Py_BuildValue("i", other);
+ Py_INCREF(other_obj);
+ WrappedPyObject ret = arithmetic(other_obj, "__div__", "__rdiv__");
+ Py_DECREF(other_obj);
+ return ret;
+ }
+
+ bool richcmp(WrappedPyObject const &other, int op) const {
+ PyObject * ret;
+ long retv;
+ ret = PyObject_RichCompare(self, other.getObj(), op);
+ retv = PyInt_AsLong(ret);
+ return retv;
+ }
+
+ bool operator<(WrappedPyObject const &other) const {
+ return richcmp(other, Py_LT);
+ }
+
+ bool operator<=(WrappedPyObject const &other) const {
+ return richcmp(other, Py_LE);
+ }
+
+ bool operator>=(WrappedPyObject const &other) const{
+ return richcmp(other, Py_GE);
+ }
+
+ bool operator>(WrappedPyObject const &other) const {
+ return richcmp(other, Py_GT);
+ }
+
+ bool operator==(WrappedPyObject const &other) const {
+ return richcmp(other, Py_EQ);
+ }
+
+
+ bool operator<(int const c) const {
+ return richcmp(WrappedPyObject(c), Py_LT);
+ }
+
+ bool operator<=(int const c) const {
+ return richcmp(WrappedPyObject(c), Py_LE);
+ }
+
+ bool operator>=(int const c) const{
+ return richcmp(WrappedPyObject(c), Py_GE);
+ }
+
+ bool operator>(int const c) const {
+ return richcmp(WrappedPyObject(c), Py_GT);
+ }
+
+ bool operator==(int const c) const {
+ return richcmp(WrappedPyObject(c), Py_EQ);
+ }
+
+
+ WrappedPyObject operator+=(WrappedPyObject other){
+ *this = *this + other;
+ return *this;
+
+ }
+
+ WrappedPyObject operator-=(WrappedPyObject other){
+ *this = *this - other;
+ return *this;
+ }
+};
+
+typedef GenericInterval<WrappedPyObject> PyInterval;
+typedef GenericOptInterval<WrappedPyObject> PyOptInterval;
+
+typedef GenericRect<WrappedPyObject> PyRect;
+typedef GenericOptRect<WrappedPyObject> PyOptRect;
+
+typedef D2<WrappedPyObject> PyPoint;
+
+template<>
+struct CoordTraits<WrappedPyObject> {
+ typedef PyPoint PointType;
+ typedef PyInterval IntervalType;
+ typedef PyOptInterval OptIntervalType;
+ typedef PyRect RectType;
+ typedef PyOptRect OptRectType;
+
+ typedef
+ boost::equality_comparable< PyInterval
+ , boost::additive< PyInterval
+ , boost::multipliable< PyInterval
+ , boost::orable< PyInterval
+ , boost::arithmetic< PyInterval, WrappedPyObject
+ > > > > >
+ IntervalOps;
+
+ typedef
+ boost::equality_comparable< PyRect
+ //, boost::equality_comparable< PyRect, IntRect
+ , boost::orable< PyRect
+ , boost::orable< PyRect, PyOptRect
+ , boost::additive< PyRect, PyPoint
+ //, boost::multipliable< Rect, Affine
+ > > > > //> >
+ RectOps;
+};
+
+}
diff --git a/src/cython/wrapper.py b/src/cython/wrapper.py
new file mode 100644
index 0000000..241782a
--- /dev/null
+++ b/src/cython/wrapper.py
@@ -0,0 +1,360 @@
+import os
+import sys
+import argparse
+
+from pygccxml import parser, declarations
+
+class wType:
+ def __init__(self, c_str):
+ self.c_str = c_str
+ self.wrapped_types = ["bint", "double", "int", "unsigned int",
+ "Coord", "IntCoord", "Dim2", "size_t"]
+
+ def get_C(self):
+ """returns plain c string"""
+ return self.c_str
+
+ def get_Cython_type(self):
+ """Returns corresponding Cython type string"""
+ typestr= self.get_C()
+ typestr= typestr.replace(" const ", " ")
+ if typestr.find("::Geom::")==0:
+ typestr = typestr[len("::Geom::"):]
+ if typestr == "bool":
+ typestr = "bint"
+ return typestr
+
+ def get_Python_function_argument(self):
+ """Returns argument type to Python function call"""
+
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return typestr+" "
+ return "cy_"+typestr+" "
+ def get_Python_return(self):
+ """ Returns a string used to wrap a function returning this type
+ returns a string to be called with format( 'function call to c function ')
+ """
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "return {}"
+ if typestr == "void":
+ return "{}"
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ return "return wrap_"+typestr+"({})"
+
+ def get_wrap_method(self, delim):
+ """Returns wrap method in a form of a list of lines"""
+ ret = []
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return []
+ if typestr == "void":
+ return []
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ ret.append("cdef cy_{} wrap_{}({} p):".format(typestr, typestr, typestr))
+ ret.append(delim+"cdef {} * retp = new {}()".format(typestr, typestr))
+ ret.append(delim+"retp[0] = p")
+ ret.append(delim+"cdef cy_{} r = cy_{}.__new__(cy_{})".format(typestr, typestr, typestr))
+ ret.append(delim+"r.thisptr = retp")
+ ret.append(delim+"return r")
+ return ret
+
+ def get_Python_pass_argument(self):
+ """ Returns argument to be passed to C function, to be called with .format(arg_name)"""
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "{}"
+ if typestr[-1] == "*":
+ return "{}.thisptr"
+ return "deref( {}.thisptr )"
+
+class CythonWrapper:
+ def __init__(self, gn):
+ self.global_namespace = gn
+ self.delim = ' '
+
+ def wrap_constructor(self, constructor, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in constructor.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+ pxd_function_str = constructor.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in constructor.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format("__init__")+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in constructor.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "new "+constructor.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = "self.thisptr = {}".format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_operator(self, operator, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in operator.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+ boost_wrapped = ["operator{}=".format(i) for i in ["+", "-", "*", "/", "|", "&"]]
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name+pxd_argument_str
+
+ if not (operator.name in boost_wrapped):
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ else:
+ pxd_lines.append(self.delim*2+"#"+pxd_function_str)
+
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name[:-1]+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+
+ special_methods = {
+ "+" : "__add__",
+ "-" : "__sub__",
+ "*" : "__mul__",
+ "/" : "__div__",
+ "|" : "__or__",
+ "&" : "__and__",
+ "[]": "__getitem__",
+ "()": "__call__",
+ "==": "__richcmp__",
+ "!=": "__richcmp__"
+ }
+
+ if operator.name in boost_wrapped:
+ operation = operator.name[-2]
+ else:
+ operation = operator.name[ len("operator"): ]
+
+ if operation in special_methods:
+ python_name = special_methods[operation]
+ else:
+ python_name = operator.name
+
+ arguments_python = ["self"]
+ for argument in operator.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(python_name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in operator.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "deref( self.thisptr ) "+operation+" {}".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[operator.return_type.decl_string].get_Python_return().format(function_call)
+
+ if (operator.arguments == []) and (operation == '-'):
+ declaration = "def __neg__(self):"
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_method(self, method, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in method.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[method.return_type.decl_string].get_Cython_type()+\
+ " "+method.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in method.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(method.name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in method.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "self.thisptr."+method.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[method.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_free_function(self, function, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+
+ pxd_arguments = []
+ for argument_type in function.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[function.return_type.decl_string].get_Cython_type()+\
+ " "+function.name+pxd_argument_str
+ pxd_lines.append(self.delim+pxd_function_str)
+
+ #python function
+ arguments_python = []
+ for argument in function.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def cy_{}".format(function.name)+"({}):".format(", ".join(arguments_python))
+
+ decl_lines.append("from _cy_??? import cy_{0} as {0}".format(function.name))
+
+ arguments_cython = []
+ for argument in function.arguments:
+
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = function.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[function.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(declaration)
+ pyx_lines.append(self.delim + return_statement)
+
+ return (pxd_lines, pyx_lines, decl_lines)
+
+
+ def wrap_class(self, class_name, class_file):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+ other_lines = []
+
+ pxd_lines.append("cdef extern from \"2geom/{}\" namespace \"Geom\":".format(class_file))
+ pxd_lines.append(self.delim+"cdef cppclass {}:".format(class_name))
+ pyx_lines.append("cdef class cy_{}:".format(class_name))
+ pyx_lines.append(self.delim+"cdef {}* thisptr".format(class_name))
+ #get_types
+ types_dict = self.collect_types(class_name)
+ #parse
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+
+ for member in c_class.get_members():
+ if isinstance(member, declarations.calldef.constructor_t):
+ pxd, pyx = self.wrap_constructor(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_operator_t):
+ pxd, pyx = self.wrap_operator(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_function_t):
+ pxd, pyx = self.wrap_method(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+
+ pyx_lines.append("#free functions:")
+ for ff in self.global_namespace.namespace(name = "Geom").free_funs():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx, decl = self.wrap_free_function(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+ decl_lines += decl
+
+ for ff in self.global_namespace.namespace(name = "Geom").free_operators():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx = self.wrap_operator(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+
+ pyx_lines.append("")
+ for ctype in types_dict:
+ wtype = types_dict[ctype]
+ for line in wtype.get_wrap_method(self.delim):
+ pyx_lines.append(line)
+ print "------"
+ for i in pxd_lines:
+ print i
+ print "------"
+ for i in pyx_lines:
+ print i
+ print "------"
+ for i in decl_lines:
+ print i
+
+
+ def collect_types(self, class_name):
+ types_set = set()
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+ for member in c_class.get_members():
+ if any([isinstance(member, declarations.calldef.constructor_t),
+ isinstance(member, declarations.calldef.member_operator_t),
+ isinstance(member, declarations.calldef.member_function_t)]):
+ for a_type in member.argument_types:
+ types_set.add(a_type.decl_string)
+ if isinstance(member, declarations.calldef.member_operator_t) or isinstance(member, declarations.calldef.member_function_t):
+ types_set.add(member.return_type.decl_string)
+ for ff in list(self.global_namespace.namespace(name = "Geom").free_funs()) + list(self.global_namespace.namespace(name = "Geom").free_operators()):
+ for a_type in ff.argument_types:
+ types_set.add(a_type.decl_string)
+ types_set.add(ff.return_type.decl_string)
+ types_dict = dict()
+
+ for f_type in types_set:
+
+ types_dict[f_type] = wType(f_type)
+ return types_dict
+
+def main():
+
+ #set up argument parser
+ cmd_parser = argparse.ArgumentParser()
+ cmd_parser.add_argument('--lib2geom_dir', action = 'store')
+ cmd_parser.add_argument('--file_name', action = 'store')
+ cmd_parser.add_argument('--class_name', action = 'store')
+ cmd_args = cmd_parser.parse_args()
+
+ #set up pygccxml
+ includes = [cmd_args.lib2geom_dir,
+ os.path.join(cmd_args.lib2geom_dir, 'src'),
+ "/usr/include/boost"]
+
+ config = parser.config_t(compiler='gcc',
+ include_paths=includes,
+ cflags="")
+
+ file_to_parse = [os.path.join(cmd_args.lib2geom_dir, "src", "2geom", cmd_args.file_name)]
+
+ decls = parser.parse( file_to_parse, config )
+ global_namespace = declarations.get_global_namespace( decls )
+ wrapper = CythonWrapper(global_namespace)
+ wrapper.wrap_class(cmd_args.class_name, cmd_args.file_name)
+
+main()
+
+#run with
+#python2 wrapper.py --lib2geom_dir ../../.. --file_name int-point.h --class_name IntPoint
+#from cython-bindings directory
diff --git a/src/performance-tests/CMakeLists.txt b/src/performance-tests/CMakeLists.txt
new file mode 100644
index 0000000..60ceaa3
--- /dev/null
+++ b/src/performance-tests/CMakeLists.txt
@@ -0,0 +1,27 @@
+SET(2GEOM_PERFORMANCE_TESTS_SRC
+example-performance-test
+boolops-performance-test
+bendpath-test
+bezier-utils-test
+parse-svg-test
+path-operations-test
+)
+
+add_custom_target(perf)
+
+OPTION(2GEOM_PERFORMANCE_TESTS
+ "Build the performance tests"
+ ON)
+IF(2GEOM_PERFORMANCE_TESTS)
+ FOREACH(source ${2GEOM_PERFORMANCE_TESTS_SRC})
+ ADD_EXECUTABLE(${source} ${source}.cpp)
+ target_link_libraries(${source} 2Geom::2geom)
+ add_dependencies(perf ${source})
+ add_custom_command(TARGET perf COMMAND ${source})
+ ENDFOREACH(source)
+ENDIF()
+
+IF(WIN32 AND 2GEOM_BUILD_SHARED)
+ ADD_CUSTOM_TARGET(copy-perf ALL COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/src/2geom/lib2geom.dll ${CMAKE_BINARY_DIR}/src/performance-tests/lib2geom.dll)
+ ADD_DEPENDENCIES(copy-perf 2geom)
+ENDIF()
diff --git a/src/performance-tests/bendpath-test.cpp b/src/performance-tests/bendpath-test.cpp
new file mode 100644
index 0000000..30fc500
--- /dev/null
+++ b/src/performance-tests/bendpath-test.cpp
@@ -0,0 +1,128 @@
+/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479";
+
+static char const *bend_str =
+ "M -489.19983,473.24818 C -478.58599,460.43092 -465.31914,447.40133 -450.91614,434.95746 -436.51315,422.51359 "
+ "-420.97403,410.65543 -405.81565,400.18102 -390.65727,389.70661 -375.87963,380.61595 -362.99962,373.70708 "
+ "-350.11961,366.79821 -339.13723,362.07112 -331.56936,360.32386 -323.49864,358.4605 -311.32246,357.96153 "
+ "-296.07049,358.53595 -280.81852,359.11038 -262.49077,360.7582 -242.11692,363.18844 -221.74308,365.61867 "
+ "-199.32313,368.83132 -175.88677,372.53539 -152.45042,376.23947 -127.99765,380.43497 -103.55815,384.83091 "
+ "-90.798706,387.12596 -78.042879,389.47564 -65.437198,391.83855 -52.831517,394.20146 -40.375984,396.57759 "
+ "-28.217127,398.92554 -16.058269,401.27349 -4.1960885,403.59326 7.2228872,405.84343 18.641863,408.09361 "
+ "29.617633,410.27419 40.00367,412.34378 50.09967,414.35557 59.638414,416.26247 68.485308,418.02645 "
+ "77.332201,419.79043 85.487246,421.41149 92.815847,422.85159 100.14445,424.29169 106.64661,425.55083 "
+ "112.18773,426.59098 117.72885,427.63113 122.30894,428.45228 125.7934,429.01641 134.20502,430.37823 "
+ "142.47705,429.25391 150.64115,426.35391 158.80525,423.45391 166.86142,418.77822 174.84134,413.03729 "
+ "182.82125,407.29637 190.72491,400.49022 198.58398,393.32929 206.44305,386.16836 214.25752,378.65265 "
+ "222.05908,371.49262 227.06421,366.89906 232.06402,362.4519 237.06688,358.33874 242.06974,354.22557 "
+ "247.07564,350.44641 252.09294,347.18884 257.11025,343.93127 262.13896,341.1953 267.18744,339.16853 "
+ "272.23593,337.14176 277.30418,335.82419 282.40056,335.40342 293.00641,334.52778 302.22965,341.02775 "
+ "310.80162,351.57579 319.37358,362.12384 327.29427,376.71996 335.29503,392.03663 343.29579,407.3533 "
+ "351.37662,423.39051 360.26886,436.82075 369.1611,450.25099 378.86474,461.07425 390.11114,465.96301 "
+ "401.98566,471.1248 423.45174,470.72496 449.88663,467.14319 476.32151,463.56143 507.72518,456.79775 "
+ "539.47487,449.23188 571.22455,441.66601 603.32025,433.29795 631.13918,426.50743 658.9581,419.7169 "
+ "688.21455,391.64677 702.85715,390.39104";
+
+
+// adapted from Inkscape's BendPath LPE
+Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in,
+ Piecewise<D2<SBasis> > const &bend_path)
+{
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(bend_path, 2, .1);
+ uskeleton = remove_short_cuts(uskeleton, .01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n, .1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pwd2_in);
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[1]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[0]);
+
+ Geom::OptRect bbox = bounds_exact(pwd2_in);
+ Interval bboxHorizontal = (*bbox)[Geom::X];
+ Interval bboxVertical = (*bbox)[Geom::Y];
+
+ x -= bboxHorizontal.min();
+ y -= bboxVertical.middle();
+
+ double scaling = uskeleton.cuts.back() / bboxHorizontal.extent();
+
+ if (scaling != 1.0) {
+ x *= scaling;
+ y *= scaling;
+ }
+
+ Piecewise<D2<SBasis> > output = compose(uskeleton, x) + y * compose(n, x);
+ return output;
+}
+
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ const int num_repeats = 100;
+ PathVector path1 = parse_svg_path(path_str);
+ PathVector path2 = parse_svg_path(bend_str);
+ Piecewise<D2<SBasis> > pwd2_path = path1[0].toPwSb();
+ Piecewise<D2<SBasis> > pwd2_bend = path2[0].toPwSb();
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > result = doEffect_pwd2(pwd2_path, pwd2_bend);
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "Bend paths (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+// \ No newline at end of file
diff --git a/src/performance-tests/bezier-utils-test.cpp b/src/performance-tests/bezier-utils-test.cpp
new file mode 100644
index 0000000..111d939
--- /dev/null
+++ b/src/performance-tests/bezier-utils-test.cpp
@@ -0,0 +1,133 @@
+/*
+ * Test performance of bezier-util fitting code
+ *
+ * Copyright (C) authors 2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/bezier-utils.h"
+#include "2geom/path.h"
+using namespace Geom;
+
+//static const Point data[] = { Point(0, 0), Point(1, 0), Point( 2, 0 ), Point( 1, 1 )};
+static const Point data[] = { Point( 0, 0 ), Point( 1, 0 ), Point( 2, 0 ), Point( 1, 1 ),
+ Point( 1, 2 ), Point( 1, 3 ), Point( 3, 0 ), Point( 4, 0 ),
+ Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ), Point( 2, 0 ),
+ Point( 1, 0 ), Point( 2, 0 ), Point( 1, 3 ), Point( 1, 4 ),
+ Point( 1, 2 ), Point( 1, 1 ), Point( 0, 0 ), Point( 1, 0 ),
+ Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ), Point( 1, 3 ),
+ Point( 4, 0 ), Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ),
+ Point( 2, 0 ), Point( 1, 0 ), Point( 2, 0 ), Point( 1, 3 ),
+ Point( 1, 4 ), Point( 1, 2 ), Point( 1, 1 ), Point( 2, 1 ) };
+
+static const unsigned int data_len = sizeof(data)/sizeof(Point);
+
+
+
+// code test with 2geom types
+Path interpolateToPath(std::vector<Point> const &points, double tolerance_sq, unsigned max_beziers)
+{
+ Geom::Point *b = new Geom::Point[max_beziers*4];
+ Geom::Point *points_array = new Geom::Point[4 * points.size()]; // for safety, do a copy into a vector. I think it
+ // is possible to simply pass &points[0] as a
+ // Point[], but I am not sure
+ for (size_t i = 0; i < points.size(); ++i) {
+ points_array[i] = points.at(i);
+ }
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, points.size(), tolerance_sq, max_beziers);
+
+ Geom::Path fit;
+ if (n_segs > 0) {
+ fit.start(b[0]);
+ for (int c = 0; c < n_segs; c++) {
+ fit.appendNew<Geom::CubicBezier>(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
+ }
+ }
+
+ delete[] b;
+ delete[] points_array;
+
+ return fit;
+};
+
+
+Path interpolateToPath2(std::vector<Point> const &points, double tolerance_sq, unsigned max_beziers)
+{
+ std::vector<Point> b(max_beziers * 4);
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b.data(), points.data(), points.size(), tolerance_sq, max_beziers);
+
+ Geom::Path fit;
+ if (n_segs > 0) {
+ fit.start(b[0]);
+ for (int c = 0; c < n_segs; c++) {
+ fit.appendNew<Geom::CubicBezier>(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
+ }
+ }
+
+ return fit;
+};
+
+
+int main()
+{
+ std::vector<Point> data_vector;
+ for (auto i : data) {
+ data_vector.push_back(i);
+ }
+
+ const int num_repeats = 2000;
+
+ unsigned max_beziers = data_len*2;
+ double tolerance_sq = 0.01;
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Point *bezier = new Point[max_beziers*4]; // large array on stack = not good, so allocate on heap
+ int n_segs = bezier_fit_cubic_r(bezier, data, data_len, tolerance_sq, max_beziers);
+ (void) n_segs;
+ delete[] bezier;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r C-array (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Path path = interpolateToPath(data_vector, tolerance_sq, max_beziers);
+ int n_segs = path.size();
+ (void) n_segs;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r 2Geom interoperability (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Path path = interpolateToPath2(data_vector, tolerance_sq, max_beziers);
+ int n_segs = path.size();
+ (void) n_segs;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r 2Geom interoperability 2nd version (" << num_repeats
+ << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms " << std::endl;
+ }
+
+}
+
+
+
+// \ No newline at end of file
diff --git a/src/performance-tests/boolops-performance-test.cpp b/src/performance-tests/boolops-performance-test.cpp
new file mode 100644
index 0000000..49d87aa
--- /dev/null
+++ b/src/performance-tests/boolops-performance-test.cpp
@@ -0,0 +1,101 @@
+/**
+ * \file
+ * \brief Performance test for Boolops
+ *//*
+ * 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/intersection-graph.h>
+#include <2geom/svg-path-parser.h>
+#include <iostream>
+#include <glib.h>
+
+using namespace Geom;
+
+int main(int argc, char **argv)
+{
+ if (argc != 4) {
+ std::cout << "boolops: wrong number of arguments; no tests run!" << std::endl;
+ exit(0); // TODO: add suitable arguments in CMake target / actually do some tests here
+ }
+
+ PathVector a = read_svgd(argv[2]);
+ PathVector b = read_svgd(argv[3]);
+ unsigned const ops = atoi(argv[1]);
+
+ OptRect abox = a.boundsExact();
+ OptRect bbox = a.boundsExact();
+ if (!abox) {
+ std::cout << argv[1] << " contains an empty path" << std::endl;
+ std::exit(1);
+ }
+ if (!bbox) {
+ std::cout << argv[2] << " contains an empty path" << std::endl;
+ std::exit(1);
+ }
+
+ a *= Translate(-abox->corner(0));
+ b *= Translate(-bbox->corner(0));
+
+ long num_intersections = 0;
+ long num_outcv = 0;
+
+ // for reproducibility.
+ g_random_set_seed(1234);
+
+ for (unsigned i = 0; i < ops; ++i) {
+ Point delta;
+ delta[X] = g_random_double_range(-bbox->width(), abox->width());
+ delta[Y] = g_random_double_range(-bbox->height(), abox->height());
+
+ PathVector bt = b * Translate(delta);
+
+ PathIntersectionGraph pig(a, bt);
+ PathVector x = pig.getIntersection();
+ num_intersections += pig.intersectionPoints().size();
+ num_outcv += x.curveCount();
+ }
+
+ std::cout << "Completed " << ops << " operations.\n"
+ << "Total intersections: " << num_intersections << "\n"
+ << "Total output curves: " << num_outcv << std::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/src/performance-tests/example-performance-test.cpp b/src/performance-tests/example-performance-test.cpp
new file mode 100644
index 0000000..0ca706d
--- /dev/null
+++ b/src/performance-tests/example-performance-test.cpp
@@ -0,0 +1,217 @@
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+using namespace Geom;
+
+static char const *many_subpaths =
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "
+ "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 ";
+
+
+
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ const int num_repeats = 100;
+ PathVector path = parse_svg_path(many_subpaths);
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ PathVector path2 = path.reversed();
+ PathVector path3 = path2.reversed().reversed();
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "Reverse paths (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms " << std::endl;
+ }
+}
+
+
+
+//
diff --git a/src/performance-tests/parse-svg-test.cpp b/src/performance-tests/parse-svg-test.cpp
new file mode 100644
index 0000000..5147d75
--- /dev/null
+++ b/src/performance-tests/parse-svg-test.cpp
@@ -0,0 +1,88 @@
+/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479 C -478.58599,460.43092 -465.31914,447.40133 -450.91614,434.95746 -436.51315,422.51359 "
+ "-420.97403,410.65543 -405.81565,400.18102 -390.65727,389.70661 -375.87963,380.61595 -362.99962,373.70708 "
+ "-350.11961,366.79821 -339.13723,362.07112 -331.56936,360.32386 -323.49864,358.4605 -311.32246,357.96153 "
+ "-296.07049,358.53595 -280.81852,359.11038 -262.49077,360.7582 -242.11692,363.18844 -221.74308,365.61867 "
+ "-199.32313,368.83132 -175.88677,372.53539 -152.45042,376.23947 -127.99765,380.43497 -103.55815,384.83091 "
+ "-90.798706,387.12596 -78.042879,389.47564 -65.437198,391.83855 -52.831517,394.20146 -40.375984,396.57759 "
+ "-28.217127,398.92554 -16.058269,401.27349 -4.1960885,403.59326 7.2228872,405.84343 18.641863,408.09361 "
+ "29.617633,410.27419 40.00367,412.34378 50.09967,414.35557 59.638414,416.26247 68.485308,418.02645 "
+ "77.332201,419.79043 85.487246,421.41149 92.815847,422.85159 100.14445,424.29169 106.64661,425.55083 "
+ "112.18773,426.59098 117.72885,427.63113 122.30894,428.45228 125.7934,429.01641 134.20502,430.37823 "
+ "142.47705,429.25391 150.64115,426.35391 158.80525,423.45391 166.86142,418.77822 174.84134,413.03729 "
+ "182.82125,407.29637 190.72491,400.49022 198.58398,393.32929 206.44305,386.16836 214.25752,378.65265 "
+ "222.05908,371.49262 227.06421,366.89906 232.06402,362.4519 237.06688,358.33874 242.06974,354.22557 "
+ "247.07564,350.44641 252.09294,347.18884 257.11025,343.93127 262.13896,341.1953 267.18744,339.16853 "
+ "272.23593,337.14176 277.30418,335.82419 282.40056,335.40342 293.00641,334.52778 302.22965,341.02775 "
+ "310.80162,351.57579 319.37358,362.12384 327.29427,376.71996 335.29503,392.03663 343.29579,407.3533 "
+ "351.37662,423.39051 360.26886,436.82075 369.1611,450.25099 378.86474,461.07425 390.11114,465.96301 "
+ "401.98566,471.1248 423.45174,470.72496 449.88663,467.14319 476.32151,463.56143 507.72518,456.79775 "
+ "539.47487,449.23188 571.22455,441.66601 603.32025,433.29795 631.13918,426.50743 658.9581,419.7169 "
+ "688.21455,391.64677 702.85715,390.39104";
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ const int num_repeats = 100;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ PathVector path = parse_svg_path(path_str);
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "Parse SVG-d (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+// \ No newline at end of file
diff --git a/src/performance-tests/path-operations-test.cpp b/src/performance-tests/path-operations-test.cpp
new file mode 100644
index 0000000..c55ddda
--- /dev/null
+++ b/src/performance-tests/path-operations-test.cpp
@@ -0,0 +1,100 @@
+/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+#include "2geom/transforms.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479";
+
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ PathVector path1 = parse_svg_path(path_str);
+ const int num_repeats = 1000;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ path1 *= Translate(Point(3.,1.));
+ path1 *= Translate(Point(4.,1.));
+ path1 *= Translate(Point(5.,1.));
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "PathVector *= Translate (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ PathVector path1 = parse_svg_path(path_str);
+ const int num_repeats = 1000;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ path1 *= Affine(Translate(Point(3.,1.)));
+ path1 *= Affine(Translate(Point(4.,1.)));
+ path1 *= Affine(Translate(Point(5.,1.)));
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "PathVector *= Affine (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+// \ No newline at end of file
diff --git a/src/py2geom/CMakeLists.txt b/src/py2geom/CMakeLists.txt
new file mode 100644
index 0000000..8e21e74
--- /dev/null
+++ b/src/py2geom/CMakeLists.txt
@@ -0,0 +1,118 @@
+SET(2GEOM_BOOST_PYTHON_SRC
+etc.cpp
+point.cpp
+interval.cpp
+transforms.cpp
+rect.cpp
+line.cpp
+circle.cpp
+ellipse.cpp
+conic.cpp
+crossing.cpp
+sbasis.cpp
+bezier.cpp
+linear.cpp
+pw.cpp
+d2.cpp
+parser.cpp
+path.cpp
+ray.cpp
+#convexcover.cpp
+py2geom.cpp
+# curves
+#curve.cpp
+#bezier-curve.cpp
+)
+
+IF (WIN32)
+ SET(BUILD_BOOST_PYTHON_STATIC FALSE)
+ELSE (WIN32)
+ SET(BUILD_BOOST_PYTHON_STATIC FALSE)
+ENDIF (WIN32)
+IF (BUILD_BOOST_PYTHON_STATIC)
+ SET(BOOST_PYTHON_SRC "C:/boost_1_42_0/libs/python/src")
+ #define BOOST_PYTHON_STATIC_LIB
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_PYTHON_STATIC_LIB")
+ SET(2GEOM_BOOST_PYTHON_SRC
+ ${2GEOM_BOOST_PYTHON_SRC}
+ ${BOOST_PYTHON_SRC}/dict.cpp
+ ${BOOST_PYTHON_SRC}/errors.cpp
+ ${BOOST_PYTHON_SRC}/exec.cpp
+ ${BOOST_PYTHON_SRC}/import.cpp
+ ${BOOST_PYTHON_SRC}/list.cpp
+ ${BOOST_PYTHON_SRC}/long.cpp
+ ${BOOST_PYTHON_SRC}/module.cpp
+ ${BOOST_PYTHON_SRC}/numeric.cpp
+ ${BOOST_PYTHON_SRC}/object_operators.cpp
+ ${BOOST_PYTHON_SRC}/object_protocol.cpp
+ ${BOOST_PYTHON_SRC}/slice.cpp
+ ${BOOST_PYTHON_SRC}/str.cpp
+ ${BOOST_PYTHON_SRC}/tuple.cpp
+ ${BOOST_PYTHON_SRC}/wrapper.cpp
+
+ ${BOOST_PYTHON_SRC}/converter/arg_to_python_base.cpp
+ ${BOOST_PYTHON_SRC}/converter/builtin_converters.cpp
+ ${BOOST_PYTHON_SRC}/converter/from_python.cpp
+ ${BOOST_PYTHON_SRC}/converter/registry.cpp
+ ${BOOST_PYTHON_SRC}/converter/type_id.cpp
+
+ ${BOOST_PYTHON_SRC}/object/class.cpp
+ ${BOOST_PYTHON_SRC}/object/enum.cpp
+ ${BOOST_PYTHON_SRC}/object/function.cpp
+ ${BOOST_PYTHON_SRC}/object/function_doc_signature.cpp
+ ${BOOST_PYTHON_SRC}/object/inheritance.cpp
+ ${BOOST_PYTHON_SRC}/object/iterator.cpp
+ ${BOOST_PYTHON_SRC}/object/life_support.cpp
+ ${BOOST_PYTHON_SRC}/object/pickle_support.cpp
+ ${BOOST_PYTHON_SRC}/object/stl_iterator.cpp
+ )
+ENDIF (BUILD_BOOST_PYTHON_STATIC)
+
+IF(PYCAIRO_FOUND)
+ SET(2GEOM_BOOST_PYTHON_SRC
+ ${2GEOM_BOOST_PYTHON_SRC}
+ cairo-helpers.cpp
+ )
+ENDIF(PYCAIRO_FOUND)
+
+
+OPTION(2GEOM_BOOST_PYTHON
+ "Build a python binding with Boost.Python"
+ OFF)
+IF(2GEOM_BOOST_PYTHON)
+ FIND_PACKAGE(Python3 COMPONENTS Development Interpreter REQUIRED)
+
+ SET(Boost_DEBUG TRUE)
+ SET(Boost_REALPATH FALSE)
+ FIND_PACKAGE(Boost 1.42.0 REQUIRED)
+ FIND_PACKAGE(Boost REQUIRED COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR})
+
+ IF (WIN32)
+ SET_TARGET_PROPERTIES(py2geom PROPERTIES SUFFIX ".pyd")
+ ELSEIF (APPLE)
+ SET(CMAKE_MACOSX_RPATH FALSE)
+ SET(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
+ ENDIF(WIN32)
+
+ INCLUDE_DIRECTORIES( src/ ${Python3_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} )
+ ADD_LIBRARY(py2geom SHARED ${2GEOM_BOOST_PYTHON_SRC})
+ SET_TARGET_PROPERTIES(py2geom PROPERTIES PREFIX "_")
+
+ IF (BUILD_BOOST_PYTHON_STATIC)
+ TARGET_LINK_LIBRARIES(py2geom 2geom ${Python3_LIBRARIES})
+ ELSE (BUILD_BOOST_PYTHON_STATIC)
+ TARGET_LINK_LIBRARIES(py2geom 2geom ${Boost_LIBRARIES} ${Python3_LIBRARIES})
+ ENDIF (BUILD_BOOST_PYTHON_STATIC)
+
+ set_property(TARGET 2geom PROPERTY POSITION_INDEPENDENT_CODE ON) # we need -fPIC to link py2geom against 2geom
+
+ IF(PYCAIRO_FOUND)
+ TARGET_LINK_LIBRARIES(py2geom ${Cairo_LIBRARIES})
+ ENDIF(PYCAIRO_FOUND)
+
+ INSTALL(TARGETS py2geom DESTINATION "${Python3_SITEARCH}/py2geom")
+ INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/__init__.py" DESTINATION "${Python3_SITEARCH}/py2geom")
+ENDIF(2GEOM_BOOST_PYTHON)
+
+
+
diff --git a/src/py2geom/__init__.py b/src/py2geom/__init__.py
new file mode 100644
index 0000000..06faf3c
--- /dev/null
+++ b/src/py2geom/__init__.py
@@ -0,0 +1,26 @@
+# Copyright 2006, 2007 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.
+
+from py2geom._py2geom import *
diff --git a/src/py2geom/bezier.cpp b/src/py2geom/bezier.cpp
new file mode 100644
index 0000000..f0664c1
--- /dev/null
+++ b/src/py2geom/bezier.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/bezier.h"
+#include "2geom/point.h"
+
+using namespace boost::python;
+
+double bezier_getitem(Geom::Bezier const& p, int index)
+{
+ int D = p.size();
+ if (index < 0)
+ {
+ index = D + index;
+ }
+ if ((index < 0) || (index > (D - 1))) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ boost::python::throw_error_already_set();
+ }
+ return p[index];
+}
+
+void wrap_bezier() {
+ //bezier.h
+
+ class_<Geom::Bezier>("Bezier", init<double>())
+ .def(init<double, double>())
+ .def(init<double, double, double>())
+ .def(init<double, double, double, double>())
+ .def(self_ns::str(self))
+ //TODO: add important vector funcs
+ .def("__getitem__", &bezier_getitem)
+
+ .def("isZero", &Geom::Bezier::isZero)
+ .def("isFinite", &Geom::Bezier::isFinite)
+ .def("at0", (double (Geom::Bezier::*)() const) &Geom::Bezier::at0)
+ .def("at1", (double (Geom::Bezier::*)() const) &Geom::Bezier::at1)
+ .def("valueAt", &Geom::Bezier::valueAt)
+ .def("toSBasis", &Geom::Bezier::toSBasis)
+
+ .def(self + float())
+ .def(self - float())
+
+ .def(self * float())
+ ;
+};
+
+/*
+ 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/src/py2geom/cairo-helpers.cpp b/src/py2geom/cairo-helpers.cpp
new file mode 100644
index 0000000..c341439
--- /dev/null
+++ b/src/py2geom/cairo-helpers.cpp
@@ -0,0 +1,164 @@
+#include <boost/python.hpp>
+#include <cairo.h>
+#include <toys/path-cairo.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/utils.h>
+#include <sstream>
+#include <pycairo/pycairo.h>
+#include "cairo-helpers.h"
+
+using namespace Geom;
+
+
+void
+cairo_move_to (cairo_t *cr, Geom::Point p1) {
+ cairo_move_to(cr, p1[0], p1[1]);
+}
+
+void
+cairo_line_to (cairo_t *cr, Geom::Point p1) {
+ cairo_line_to(cr, p1[0], p1[1]);
+}
+
+void
+cairo_curve_to (cairo_t *cr, Geom::Point p1,
+ Geom::Point p2, Geom::Point p3) {
+ cairo_curve_to(cr, p1[0], p1[1],
+ p2[0], p2[1],
+ p3[0], p3[1]);
+}
+
+void cairo_rectangle(cairo_t *cr, Rect const& r) {
+ cairo_rectangle(cr, r.left(), r.top(), r.width(), r.height());
+}
+
+void cairo_convex_hull(cairo_t *cr, ConvexHull const& ch) {
+ if(ch.empty()) return;
+ cairo_move_to(cr, ch.back());
+ for(unsigned i = 0; i < ch.size(); i++) {
+ cairo_line_to(cr, ch[i]);
+ }
+}
+
+void cairo_curve(cairo_t *cr, Curve const& c) {
+ if(LineSegment const* line_segment = dynamic_cast<LineSegment const*>(&c)) {
+ cairo_line_to(cr, (*line_segment)[1][0], (*line_segment)[1][1]);
+ }
+ else if(QuadraticBezier const *quadratic_bezier = dynamic_cast<QuadraticBezier const*>(&c)) {
+ std::vector<Point> points = quadratic_bezier->controlPoints();
+ Point b1 = points[0] + (2./3) * (points[1] - points[0]);
+ Point b2 = b1 + (1./3) * (points[2] - points[0]);
+ cairo_curve_to(cr, b1[0], b1[1],
+ b2[0], b2[1],
+ points[2][0], points[2][1]);
+ }
+ else if(CubicBezier const *cubic_bezier = dynamic_cast<CubicBezier const*>(&c)) {
+ std::vector<Point> points = cubic_bezier->controlPoints();
+ cairo_curve_to(cr, points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]);
+ }
+// else if(EllipticalArc const *svg_elliptical_arc = dynamic_cast<EllipticalArc *>(c)) {
+// //TODO: get at the innards and spit them out to cairo
+// }
+ else {
+ //this case handles sbasis as well as all other curve types
+ Path sbasis_path = cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ //recurse to convert the new path resulting from the sbasis to svgd
+ for(Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
+ cairo_curve(cr, *iter);
+ }
+ }
+}
+
+void cairo_path(cairo_t *cr, Path const &p) {
+ cairo_move_to(cr, p.initialPoint()[0], p.initialPoint()[1]);
+ if(p.size() == 0) { // naked moveto
+ cairo_move_to(cr, p.finalPoint()+Point(8,0));
+ cairo_line_to(cr, p.finalPoint()+Point(-8,0));
+ cairo_move_to(cr, p.finalPoint()+Point(0,8));
+ cairo_line_to(cr, p.finalPoint()+Point(0,-8));
+ return;
+ }
+
+ for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) {
+ cairo_curve(cr, *iter);
+ }
+ if(p.closed())
+ cairo_close_path(cr);
+}
+
+void cairo_path_stitches(cairo_t *cr, Path const &p) {
+ Path::const_iterator iter;
+ for ( iter = p.begin() ; iter != p.end() ; ++iter ) {
+ Curve const &c=*iter;
+ if (dynamic_cast<Path::StitchSegment const *>(&c)) {
+ cairo_move_to(cr, c.initialPoint()[X], c.initialPoint()[Y]);
+ cairo_line_to(cr, c.finalPoint()[X], c.finalPoint()[Y]);
+
+ //std::stringstream s;
+ //s << L1(c.finalPoint() - c.initialPoint());
+ //std::string ss = s.str();
+ //draw_text(cr, c.initialPoint()+Point(5,5), ss.c_str(), false, "Serif 6");
+
+ //std::cout << c.finalPoint() - c.initialPoint() << std::endl;
+ }
+ }
+}
+
+void cairo_path(cairo_t *cr, PathVector const &p) {
+ PathVector::const_iterator it;
+ for(it = p.begin(); it != p.end(); ++it) {
+ cairo_path(cr, *it);
+ }
+}
+
+void cairo_path_stitches(cairo_t *cr, PathVector const &p) {
+ PathVector::const_iterator it;
+ for ( it = p.begin() ; it != p.end() ; ++it ) {
+ cairo_path_stitches(cr, *it);
+ }
+}
+
+
+void cairo_d2_sb(cairo_t *cr, D2<SBasis> const &B) {
+ cairo_path(cr, path_from_sbasis(B, 0.1));
+}
+
+void cairo_d2_pw_sb(cairo_t *cr, D2<Piecewise<SBasis> > const &p) {
+ cairo_pw_d2_sb(cr, sectionize(p));
+}
+
+void cairo_pw_d2_sb(cairo_t *cr, Piecewise<D2<SBasis> > const &p) {
+ for(unsigned i = 0; i < p.size(); i++)
+ cairo_d2_sb(cr, p[i]);
+}
+
+#if PY_MAJOR_VERSION < 3
+static Pycairo_CAPI_t *Pycairo_CAPI = 0;
+#endif
+
+cairo_t* cairo_t_from_object(boost::python::object cr) {
+#if PY_MAJOR_VERSION < 3
+ if(!Pycairo_CAPI)
+ Pycairo_IMPORT;
+#else
+ import_cairo();
+#endif
+ PycairoContext* pcc = (PycairoContext*)cr.ptr();
+ assert(PyObject_TypeCheck(pcc, &PycairoContext_Type));
+ return PycairoContext_GET(pcc);
+}
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
diff --git a/src/py2geom/cairo-helpers.h b/src/py2geom/cairo-helpers.h
new file mode 100644
index 0000000..daa2960
--- /dev/null
+++ b/src/py2geom/cairo-helpers.h
@@ -0,0 +1,27 @@
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+#include <2geom/path.h>
+#include <2geom/convex-hull.h>
+#include <vector>
+
+typedef struct _cairo cairo_t;
+
+void cairo_move_to(cairo_t *cr, Geom::Point p1);
+void cairo_line_to(cairo_t *cr, Geom::Point p1);
+void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3);
+
+void cairo_curve(cairo_t *cr, Geom::Curve const &c);
+void cairo_rectangle(cairo_t *cr, Geom::Rect const &r);
+void cairo_convex_hull(cairo_t *cr, Geom::ConvexHull const &r);
+void cairo_path(cairo_t *cr, Geom::Path const &p);
+void cairo_path(cairo_t *cr, Geom::PathVector const &p);
+void cairo_path_stitches(cairo_t *cr, Geom::Path const &p);
+void cairo_path_stitches(cairo_t *cr, Geom::PathVector const &p);
+
+void cairo_d2_sb(cairo_t *cr, Geom::D2<Geom::SBasis> const &p);
+void cairo_d2_pw_sb(cairo_t *cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p);
+void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p);
+
+cairo_t* cairo_t_from_object(boost::python::object cr);
diff --git a/src/py2geom/circle.cpp b/src/py2geom/circle.cpp
new file mode 100644
index 0000000..55e683b
--- /dev/null
+++ b/src/py2geom/circle.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 Ricardo Lafuente <r@sollec.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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/circle.h"
+#include "2geom/exception.h"
+
+// i can't get these to work
+//Geom::Point (Geom::Circle::*center_point)() = (Geom::Point (*)() const)&Geom::Circle::center;
+//Geom::Coord (Geom::Circle::*center_coord)(Geom::Dim2 const& d) = &Geom::Circle::center;
+
+using namespace boost::python;
+
+void wrap_circle() {
+
+ class_<Geom::Circle>("Circle", init<double, double, double>())
+ .def(init<double, double, double, double>())
+
+ .def("setCoefficients", &Geom::Circle::setCoefficients)
+ .def("fit", &Geom::Circle::fit)
+ .add_property("radius", &Geom::Circle::radius)
+
+ .add_property("center", (Geom::Point (Geom::Circle::*)() const )&Geom::Circle::center)
+ //.def("center", center)
+ // requires EllipticalArc
+ //.def("arc", &Geom::Circle::arc)
+ ;
+
+};
+
+/*
+ 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/src/py2geom/conic.cpp b/src/py2geom/conic.cpp
new file mode 100644
index 0000000..8e5360e
--- /dev/null
+++ b/src/py2geom/conic.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2009 Nathan Hurst <njh@njhurst.com>
+ *
+ * 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 <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <optional>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/line.h"
+#include "2geom/conicsec.h"
+
+using namespace boost::python;
+
+// helpers for point
+static tuple xAx_to_tuple(Geom::xAx const& a)
+{
+ return make_tuple(a.c[0], a.c[1], a.c[2], a.c[3], a.c[4], a.c[5]);
+}
+
+static Geom::xAx tuple_to_xAx(boost::python::tuple const& t)
+{
+ return Geom::xAx(extract<double>(t[0]),
+ extract<double>(t[1]),
+ extract<double>(t[2]),
+ extract<double>(t[3]),
+ extract<double>(t[4]),
+ extract<double>(t[5])
+ );
+}
+
+static std::vector<double> xax_roots1(Geom::xAx const & xax, Geom::Point const &a, Geom::Point const &b) { return xax.roots(a,b); }
+static std::vector<double> xax_roots2(Geom::xAx const & xax, Geom::Line const &l) { return xax.roots(l); }
+static Geom::SBasis homo_eval_at(Geom::xAx const & xax,
+ Geom::SBasis const & x,
+ Geom::SBasis const & y,
+ Geom::SBasis const & w
+ ) {
+ return xax.evaluate_at(x, y, w);
+}
+
+static Geom::SBasis xy_eval_at(Geom::xAx const & xax,
+ Geom::SBasis const & x,
+ Geom::SBasis const & y
+ ) {
+ return xax.evaluate_at(x, y);
+}
+
+static Geom::D2<Geom::SBasis> wrap_rq_to_cubic_sb(Geom::RatQuad const & rq) {
+ return rq.toCubic().toSBasis();
+}
+
+static Geom::D2<Geom::SBasis> wrap_rq_to_cubic_sb_l(Geom::RatQuad const & rq, double l) {
+ return rq.toCubic(l).toSBasis();
+}
+
+static std::vector<Geom::Point> wrap_rq_to_cubic_l(Geom::RatQuad const & rq, double l) {
+ return rq.toCubic(l).controlPoints();
+}
+
+static std::vector<Geom::Point> wrap_rq_to_cubic(Geom::RatQuad const & rq) {
+ return wrap_rq_to_cubic_l(rq, rq.lambda());
+}
+
+static tuple wrap_rq_split(Geom::RatQuad const & rq) {
+ Geom::RatQuad a, b;
+ rq.split(a, b);
+ return make_tuple(a, b);
+}
+
+static object wrap_xax_to_curve(Geom::xAx const & xax, Geom::Rect const & r) {
+ std::optional<Geom::RatQuad> oc = xax.toCurve(r);
+ return oc?object(*oc):object();
+}
+
+
+
+void wrap_conic() {
+ //conicsec.h
+ def("intersect", (std::vector<Geom::Point> (*)(Geom::xAx const &, Geom::xAx const &))Geom::intersect);
+
+ class_<Geom::xAx>("xAx", init<>())
+ .def(init<double, double, double, double, double, double>())
+ .def(init<Geom::xAx const&>())
+ .def_readonly("c", &Geom::xAx::c)
+ .def("tuple", xAx_to_tuple)
+
+ .def("from_tuple", tuple_to_xAx)
+ .staticmethod("from_tuple")
+ .def("fromPoint", Geom::xAx::fromPoint)
+ .staticmethod("fromPoint")
+ .def("fromPoints", Geom::xAx::fromPoints)
+ .staticmethod("fromPoints")
+ .def("fromLine", (Geom::xAx (*)(Geom::Line l))Geom::xAx::fromLine)
+ .staticmethod("fromLine")
+ .def(self_ns::str(self))
+ .def("valueAt", &Geom::xAx::valueAt)
+
+ .def("implicit_form_coefficients", &Geom::xAx::implicit_form_coefficients)
+
+ .def("isDegenerate", &Geom::xAx::isDegenerate)
+ .def("roots", &xax_roots1)
+ .def("roots", &xax_roots2)
+ .def("extrema", &Geom::xAx::extrema)
+ .def("gradient", &Geom::xAx::gradient)
+ .def("crossings", &Geom::xAx::crossings)
+ .def("evaluate_at", &xy_eval_at)
+ .def("evaluate_at", &homo_eval_at)
+ .def("toCurve", &wrap_xax_to_curve)
+ .def(self - self)
+ .def(self + float())
+ .def(self * float())
+ ;
+
+ class_<Geom::RatQuad>("RatQuad", init<>())
+ .def(init<Geom::Point, Geom::Point, Geom::Point, double>())
+ .def_readonly("P", &Geom::RatQuad::P)
+ .def_readonly("w", &Geom::RatQuad::w)
+ .def_readonly("lam", &Geom::RatQuad::lambda)
+ //.def(self_ns::str(self))
+ .def("at0", &Geom::RatQuad::at0)
+ .def("at1", &Geom::RatQuad::at1)
+ .def("pointAt", &Geom::RatQuad::pointAt)
+
+ .def("toCubic", &wrap_rq_to_cubic)
+ .def("toCubic", &wrap_rq_to_cubic_l)
+ .def("toCubicSBasis", &wrap_rq_to_cubic_sb)
+ .def("toCubicSBasis", &wrap_rq_to_cubic_sb_l)
+
+ .def("split", &wrap_rq_split)
+ .def("hermite", &Geom::RatQuad::hermite)
+ .def("homogeneous", &Geom::RatQuad::homogeneous)
+ .def("fromPointsTangents", &Geom::RatQuad::fromPointsTangents)
+ .staticmethod("fromPointsTangents")
+ ;
+ implicitly_convertible<Geom::Point,tuple>();
+};
+
+/*
+ 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/src/py2geom/convexcover.cpp b/src/py2geom/convexcover.cpp
new file mode 100644
index 0000000..3f0b270
--- /dev/null
+++ b/src/py2geom/convexcover.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2009 Nathan Hurst <njh@njhurst.com>
+ *
+ * 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 <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/exception.h"
+#include "2geom/convex-cover.h"
+
+
+
+using namespace boost::python;
+
+PointVec ch_boundary(Geom::ConvexHull const &ch) {
+ return ch.boundary;
+}
+
+int furthest_index(Geom::ConvexHull const &ch, Geom::Point const &p) {
+ return (int)(ch.furthest(p) - &ch.boundary[0]);
+}
+
+void wrap_convex_cover() {
+ class_<Geom::ConvexHull>("ConvexHull", init<>())
+ .def(init<PointVec>())
+
+ .def("merge", &Geom::ConvexHull::merge)
+ .def("contains_point", &Geom::ConvexHull::contains_point)
+ .def("strict_contains_point", &Geom::ConvexHull::strict_contains_point)
+
+ .add_property("boundary", &ch_boundary)
+ .add_property("is_clockwise", &Geom::ConvexHull::is_clockwise)
+ .add_property("top_point_first", &Geom::ConvexHull::top_point_first)
+ .add_property("meets_invariants", &Geom::ConvexHull::meets_invariants)
+ .add_property("empty", &Geom::ConvexHull::empty)
+
+ .add_property("singular", &Geom::ConvexHull::singular)
+
+ .add_property("linear", &Geom::ConvexHull::linear)
+ .add_property("is_degenerate", &Geom::ConvexHull::is_degenerate)
+
+ .def("centroid_and_area", &Geom::ConvexHull::centroid_and_area)
+ .def("area", &Geom::ConvexHull::area)
+ .def("furthest", &furthest_index)
+
+ .def("is_left", &Geom::ConvexHull::is_left)
+ .def("is_strict_left", &Geom::ConvexHull::is_strict_left)
+ .def("find_left", &Geom::ConvexHull::find_left)
+ .def("find_strict_left", &Geom::ConvexHull::find_strict_left)
+ .def("narrowest_diameter", &Geom::ConvexHull::narrowest_diameter)
+ ;
+
+ };
+
+/*
+ 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/src/py2geom/crossing.cpp b/src/py2geom/crossing.cpp
new file mode 100644
index 0000000..6b4f050
--- /dev/null
+++ b/src/py2geom/crossing.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2009 Nathan Hurst <njh@njhurst.com>
+ *
+ * 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 <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/crossing.h"
+#include "2geom/point.h"
+
+using namespace boost::python;
+
+void wrap_crossing() {
+ //line.h
+
+ class_<Geom::Crossing>("Crossing", init<>())
+ .def(init<double, double, bool>())
+ .def(init<double, double, unsigned, unsigned, bool>())
+ .def_readonly("ta", &Geom::Crossing::ta)
+ .def_readonly("tb", &Geom::Crossing::tb)
+ .def_readonly("a", &Geom::Crossing::a)
+ .def_readonly("b", &Geom::Crossing::b)
+ .def_readonly("dir", &Geom::Crossing::dir)
+ //.def(self_ns::str(self))
+ .def("getOther", &Geom::Crossing::getOther)
+ .def("getTime", &Geom::Crossing::getTime)
+ .def("getOtherTime", &Geom::Crossing::getOtherTime)
+ .def("onIx", &Geom::Crossing::onIx)
+ ;
+};
+
+/*
+ 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/src/py2geom/d2.cpp b/src/py2geom/d2.cpp
new file mode 100644
index 0000000..d646ea5
--- /dev/null
+++ b/src/py2geom/d2.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+#include <2geom/point.h>
+#include <2geom/sbasis.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+
+using namespace boost::python;
+
+void wrap_d2() {
+ class_<Geom::D2<Geom::SBasis> >("D2SBasis", init<>())
+ .def(init<Geom::SBasis,Geom::SBasis>())
+ .def("__getitem__", python_getitem<Geom::D2<Geom::SBasis>,Geom::SBasis,2>)
+
+ .def("isZero", &Geom::D2<Geom::SBasis>::isZero)
+ .def("isFinite", &Geom::D2<Geom::SBasis>::isFinite)
+ .def("at0", &Geom::D2<Geom::SBasis>::at0)
+ .def("at1", &Geom::D2<Geom::SBasis>::at1)
+ .def("pointAt", &Geom::D2<Geom::SBasis>::valueAt)
+ .def("valueAndDerivatives", &Geom::D2<Geom::SBasis>::valueAndDerivatives)
+ .def("toSBasis", &Geom::D2<Geom::SBasis>::toSBasis)
+
+ .def(-self)
+ .def(self + self)
+ .def(self - self)
+ .def(self += self)
+ .def(self -= self)
+ .def(self + Geom::Point())
+ .def(self - Geom::Point())
+ .def(self += Geom::Point())
+ .def(self -= Geom::Point())
+ .def(self * Geom::Point())
+ .def(self / Geom::Point())
+ .def(self *= Geom::Point())
+ .def(self /= Geom::Point())
+ .def(self * float())
+ .def(self / float())
+ .def(self *= float())
+ .def(self /= float())
+ ;
+ def("reverse", ((Geom::D2<Geom::SBasis> (*)(Geom::D2<Geom::SBasis> const &b))&Geom::reverse));
+ def("portion", ((Geom::D2<Geom::SBasis> (*)(Geom::D2<Geom::SBasis> const &a, Geom::Coord f, Geom::Coord t))&Geom::portion));
+ //TODO: dot, rot90, cross, compose, composeEach, eval ops, derivative, integral, L2, portion, multiply ops,
+
+ class_<Geom::D2<Geom::Piecewise<Geom::SBasis> > >("D2PiecewiseSBasis")
+ .def("__getitem__", python_getitem<Geom::D2<Geom::Piecewise<Geom::SBasis> >,Geom::Piecewise<Geom::SBasis>,2>)
+
+ //.def("isZero", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::isZero)
+ //.def("isFinite", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::isFinite)
+ //.def("at0", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::at0)
+ //.def("at1", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::at1)
+ //.def("pointAt", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::valueAt)
+ //.def("valueAndDerivatives", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::valueAndDerivatives)
+ //.def("toSBasis", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::toSBasis)
+
+ ;
+};
+
+/*
+ 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/src/py2geom/ellipse.cpp b/src/py2geom/ellipse.cpp
new file mode 100644
index 0000000..7592bd4
--- /dev/null
+++ b/src/py2geom/ellipse.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2009 Ricardo Lafuente <r@sollec.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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/ellipse.h"
+#include "2geom/circle.h"
+#include "2geom/exception.h"
+#include "2geom/d2.h"
+
+
+void (Geom::Ellipse::*ellipse_set1)(Geom::Point const &, Geom::Point const &, double) = &Geom::Ellipse::set;
+void (Geom::Ellipse::*ellipse_set2)(double, double, double, double, double) = &Geom::Ellipse::set;
+std::vector<Geom::Coord> (Geom::Ellipse::*ellipse_coefficients)() const = &Geom::Ellipse::coefficients;
+
+// i can't get these to work
+//Geom::Point (Geom::Ellipse::*center_point)() = (Geom::Point (*)() const)&Geom::Ellipse::center;
+// Geom::Coord (Geom::Ellipse::*center_coord)(Geom::Dim2 const& d) = &Geom::Ellipse::center;
+
+using namespace boost::python;
+
+void wrap_ellipse() {
+ class_<Geom::Ellipse>("Ellipse", init<double, double, double, double, double>())
+ .def(init<double, double, double, double, double, double>())
+ // needs to be mapped to PointVec, but i can't figure out how
+ .def(init<Geom::Circle>())
+
+ .def("set", ellipse_set1)
+ .def("set", ellipse_set2)
+ .def("setCoefficients", &Geom::Ellipse::setCoefficients)
+ .def("fit", &Geom::Ellipse::fit)
+
+ .def("center", (Geom::Point (Geom::Ellipse::*)() const) &Geom::Ellipse::center)
+ // .def("center", center_coord)
+
+ .def("ray", &Geom::Ellipse::ray)
+ .def("rotationAngle", &Geom::Ellipse::rotationAngle)
+ .def("coefficients", ellipse_coefficients)
+ .def(self * Geom::Affine())
+ .def(self *= Geom::Affine())
+ // requires EllipticalArc
+ //.def("arc", &Geom::Ellipse::arc)
+
+ ;
+
+};
+
+/*
+ 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/src/py2geom/etc.cpp b/src/py2geom/etc.cpp
new file mode 100644
index 0000000..57fdf4c
--- /dev/null
+++ b/src/py2geom/etc.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 Ricardo Lafuente <r@sollec.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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/sbasis.h"
+#include "2geom/exception.h"
+
+using namespace boost::python;
+
+void wrap_etc() {
+ // needed for roots
+ class_<DoubleVec >("DoubleVec")
+ .def(vector_indexing_suite<std::vector<double> >())
+ ;
+ class_<PointVec>("PointVec")
+ .def(vector_indexing_suite<std::vector<Geom::Point> >())
+ ;
+ // sbasis is a subclass of
+ class_<LinearVec >("LinearVec")
+ .def(vector_indexing_suite<std::vector<Geom::Linear> >())
+ ;
+
+};
+
+/*
+ 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/src/py2geom/helpers.h b/src/py2geom/helpers.h
new file mode 100644
index 0000000..4502fba
--- /dev/null
+++ b/src/py2geom/helpers.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#ifndef SEEN_PY2GEOM_HELPERS_H
+#define SEEN_PY2GEOM_HELPERS_H
+
+#include <boost/python.hpp>
+
+template <typename T, typename R, unsigned D>
+R python_getitem(T const& p, int index)
+{
+ unsigned i = index;
+ if (index < 0)
+ {
+ i = index = D + index;
+ }
+ if ((index < 0) || (i > (D - 1))) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ boost::python::throw_error_already_set();
+ }
+ return p[i];
+}
+
+#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/src/py2geom/interval.cpp b/src/py2geom/interval.cpp
new file mode 100644
index 0000000..1ac2d60
--- /dev/null
+++ b/src/py2geom/interval.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/interval.h"
+
+using namespace boost::python;
+
+
+// helpers for interval
+static tuple interval_to_tuple(Geom::Interval const& p)
+{
+ return make_tuple(p.min(), p.max());
+}
+
+static Geom::Interval tuple_to_interval(boost::python::tuple const& t)
+{
+ return Geom::Interval(extract<double>(t[0]), extract<double>(t[1]));
+}
+
+static str interval_repr(Geom::Interval const& p)
+{
+ return str("(" + str(p.min()) + ", " + str(p.max()) + ")");
+}
+
+static Geom::Interval from_optinterval(Geom::OptInterval const & ivl)
+{
+ return *ivl;
+}
+
+
+static bool wrap_contains_coord(Geom::Interval const &x, Geom::Coord val) {
+ return x.contains(val);
+}
+
+static bool wrap_contains_ivl(Geom::Interval const &x, Geom::Interval val) {
+ return x.contains(val);
+}
+
+static bool wrap_interiorContains_coord(Geom::Interval const &x, Geom::Coord val) {
+ return x.interiorContains(val);
+}
+
+static bool wrap_interiorContains_ivl(Geom::Interval const &x, Geom::Interval val) {
+ return x.interiorContains(val);
+}
+
+void wrap_interval() {
+ def("interval_to_tuple", interval_to_tuple);
+ def("tuple_to_interval", tuple_to_interval);
+
+ //def("unify", Geom::unify(Geom::Interval const &, Geom::Interval const &));
+ //def("intersect", Geom::intersect(Geom::Interval const &, Geom::Interval const &));
+
+ //TODO: add overloaded constructors
+ class_<Geom::Interval>("Interval", init<double, double>())
+ .def("__str__", interval_repr)
+ .def("__repr__", interval_repr)
+ .def("tuple", interval_to_tuple)
+
+ .def("from_tuple", tuple_to_interval)
+ .staticmethod("from_tuple")
+
+ .def("min", &Geom::Interval::min)
+ .def("max", &Geom::Interval::max)
+ .def("middle", &Geom::Interval::middle)
+ .def("extent", &Geom::Interval::extent)
+ .def("isSingular", &Geom::Interval::isSingular)
+ //TODO: fix for overloading
+ .def("contains", wrap_contains_coord)
+ .def("contains", wrap_contains_ivl)
+ .def("interiorContains", wrap_interiorContains_coord)
+ .def("interiorContains", wrap_interiorContains_ivl)
+ .def("intersects", &Geom::Interval::intersects)
+
+ .def("setMin", &Geom::Interval::setMin)
+ .def("setMax", &Geom::Interval::setMax)
+ .def("expandTo", &Geom::Interval::expandTo)
+ .def("from_array", &Geom::Interval::from_array)
+ .def("expandBy", &Geom::Interval::expandBy)
+ .def("unionWith", &Geom::Interval::unionWith)
+
+ .def(self == self)
+ .def(self != self)
+
+ .def(self + float())
+ .def(self - float())
+ .def(self += float())
+ .def(self -= float())
+
+ .def(-self)
+
+ .def(self * float())
+ .def(self / float())
+ .def(self *= float())
+ .def(self /= float())
+
+ .def(self + self)
+ .def(self - self)
+ .def(self += self)
+ .def(self -= self)
+ .def(self * self)
+ .def(self *= self)
+ ;
+ class_<Geom::OptInterval>("OptInterval", init<double, double>())
+ .def(init<Geom::Interval>())
+ .def("unionWith", &Geom::OptInterval::unionWith)
+ .def("empty", &Geom::OptInterval::empty)
+ .def("toInterval", from_optinterval)
+
+ .def(self == self)
+ .def(self != self)
+ ;
+ implicitly_convertible<Geom::Interval,tuple>();
+// TODO: is this possible?
+// implicitly_convertible<tuple,Geom::Interval>();
+
+};
+
+/*
+ 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/src/py2geom/line.cpp b/src/py2geom/line.cpp
new file mode 100644
index 0000000..0df4360
--- /dev/null
+++ b/src/py2geom/line.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2009 Nathan Hurst <njh@njhurst.com>
+ *
+ * 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 <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/line.h"
+//#include "2geom/bezier-curve.h"
+#include "2geom/point.h"
+
+using namespace boost::python;
+
+template <typename S, typename T>
+object wrap_intersection(S const& a, T const& b) {
+ Geom::OptCrossing oc = intersection(a, b);
+ return oc?object(*oc):object();
+}
+
+std::vector<Geom::Coord> (Geom::Line::*coefficients_vec)() const = &Geom::Line::coefficients;
+
+void wrap_line() {
+ //line.h
+
+ def("intersection", wrap_intersection<Geom::Line, Geom::Line>);
+ def("intersection", wrap_intersection<Geom::Line, Geom::Ray>);
+ //def("intersection", wrap_intersection<Geom::Line, Geom::LineSegment>);
+ def("intersection", wrap_intersection<Geom::Ray, Geom::Line>);
+ def("intersection", wrap_intersection<Geom::Ray, Geom::Ray>);
+ //def("intersection", wrap_intersection<Geom::Ray, Geom::LineSegment>);
+ //def("intersection", wrap_intersection<Geom::LineSegement, Geom::Line>);
+ //def("intersection", wrap_intersection<Geom::LineSegement, Geom::Ray>);
+ //def("intersection", wrap_intersection<Geom::LineSegement, Geom::LineSegment>);
+ class_<Geom::Line>("Line", init<>())
+ .def(init<Geom::Point const&, Geom::Coord>())
+ .def(init<Geom::Point const&, Geom::Point const&>())
+ .def(init<double, double, double>())
+ //.def(self_ns::str(self))
+ .def("valueAt", &Geom::Line::valueAt)
+
+ .def("coefficients", coefficients_vec)
+ .def("isDegenerate", &Geom::Line::isDegenerate)
+ .def("pointAt", &Geom::Line::pointAt)
+ .def("roots", &Geom::Line::roots)
+ .def("nearestTime", &Geom::Line::nearestTime)
+ .def("reverse", &Geom::Line::reverse)
+ //.def("portion", &Geom::Line::portion)
+ //.def("segment", &Geom::Line::segment)
+ .def("derivative", &Geom::Line::derivative)
+ .def("transformed", &Geom::Line::transformed)
+ .def("normal", &Geom::Line::normal)
+ .def("normalAndDist", &Geom::Line::normalAndDist)
+ .def("setPoints", &Geom::Line::setPoints)
+ .def("setCoefficients", &Geom::Line::setCoefficients)
+ ;
+
+};
+
+/*
+ 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/src/py2geom/linear.cpp b/src/py2geom/linear.cpp
new file mode 100644
index 0000000..419af64
--- /dev/null
+++ b/src/py2geom/linear.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/linear.h"
+#include "2geom/point.h"
+#include "2geom/sbasis.h"
+
+using namespace boost::python;
+
+// helpers for bezord
+tuple bezord_to_tuple(Geom::Linear const& b)
+{
+ return make_tuple(b[0], b[1]);
+}
+
+Geom::Linear tuple_to_bezord(boost::python::tuple const& t)
+{
+ return Geom::Linear(extract<double>(t[0]), extract<double>(t[1]));
+}
+
+str bezord_repr(Geom::Linear const& b)
+{
+ return str("<" + str(b[0]) + ", " + str(b[1]) + ">");
+}
+
+void wrap_linear() {
+ def("lerp", (double (*)(double, double, double))&Geom::lerp);
+ def("reverse", (Geom::Linear (*)(Geom::Linear const &))&Geom::reverse);
+ def("bounds_fast", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_fast);
+ def("bounds_exact", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_exact);
+ def("bounds_local", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_local);
+
+ class_<Geom::Linear>("Linear", init<double, double>())
+ .def("__str__", bezord_repr)
+ .def("__repr__", bezord_repr)
+ .def("__getitem__", python_getitem<Geom::Linear,double,2>)
+ .def("tuple", bezord_to_tuple)
+
+ .def("from_tuple", tuple_to_bezord)
+ .staticmethod("from_tuple")
+
+ .def("isZero", &Geom::Linear::isZero)
+ .def("isFinite", &Geom::Linear::isFinite)
+ .def("at0", (double (Geom::Linear::*)() const) &Geom::Linear::at0)
+ .def("at1", (double (Geom::Linear::*)() const) &Geom::Linear::at1)
+ .def("valueAt", &Geom::Linear::valueAt)
+ .def("toSBasis", &Geom::Linear::toSBasis)
+
+ .def(-self)
+ .def(self + self)
+ .def(self - self)
+ .def(self += self)
+ .def(self -= self)
+ .def(self + float())
+ .def(self - float())
+ .def(self += float())
+ .def(self -= float())
+ .def(self == self)
+ .def(self != self)
+ .def(self * float())
+ .def(self / float())
+ .def(self *= float())
+ .def(self /= float())
+ ;
+ def("reverse", ((Geom::Linear (*)(Geom::Linear const &b))&Geom::reverse));
+ //TODO: reinstate
+ //implicitly_convertible<Geom::Linear,tuple>();
+};
+
+/*
+ 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/src/py2geom/parser.cpp b/src/py2geom/parser.cpp
new file mode 100644
index 0000000..8f729a1
--- /dev/null
+++ b/src/py2geom/parser.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ *
+ */
+
+#include <boost/python.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/path-sink.h"
+#include "2geom/svg-path-parser.h"
+
+
+using namespace boost::python;
+
+void (*parse_svg_path_str_sink) (char const *, Geom::PathSink &) = &Geom::parse_svg_path;
+Geom::PathVector (*parse_svg_path_str) (char const *) = &Geom::parse_svg_path;
+
+void (Geom::PathSink::*feed_path)(Geom::Path const &) = &Geom::PathSink::feed;
+void (Geom::PathSink::*feed_pathvector)(Geom::PathVector const &) = &Geom::PathSink::feed;
+
+class PathSinkWrap: public Geom::PathSink, public wrapper<Geom::PathSink> {
+ void moveTo(Geom::Point const &p) {this->get_override("moveTo")(p);}
+ void lineTo(Geom::Point const &p) {this->get_override("lineTo")(p);}
+ void curveTo(Geom::Point const &c0, Geom::Point const &c1, Geom::Point const &p) {this->get_override("curveTo")(c0, c1, p);}
+ void quadTo(Geom::Point const &c, Geom::Point const &p) {this->get_override("quadTo")(c, p);}
+ void arcTo(double rx, double ry, double angle, bool large_arc, bool sweep, Geom::Point const &p) {this->get_override("arcTo")(rx, ry, angle, large_arc, sweep, p);}
+ bool backspace() {return this->get_override("backspace")();}
+ void closePath() {this->get_override("closePath")();}
+ void flush() {this->get_override("flush")();}
+};
+
+void wrap_parser() {
+ def("parse_svg_path", parse_svg_path_str_sink);
+ def("parse_svg_path", parse_svg_path_str);
+ def("read_svgd", Geom::read_svgd);
+
+ class_<PathSinkWrap, boost::noncopyable>("PathSink")
+ .def("moveTo", pure_virtual(&Geom::PathSink::moveTo))
+ .def("lineTo", pure_virtual(&Geom::PathSink::lineTo))
+ .def("curveTo", pure_virtual(&Geom::PathSink::curveTo))
+ .def("quadTo", pure_virtual(&Geom::PathSink::quadTo))
+ .def("arcTo", pure_virtual(&Geom::PathSink::arcTo))
+ .def("backspace", pure_virtual(&Geom::PathSink::backspace))
+ .def("closePath", pure_virtual(&Geom::PathSink::closePath))
+ .def("flush", pure_virtual(&Geom::PathSink::flush))
+ .def("feed", feed_path)
+ .def("feed", feed_pathvector)
+ ;
+};
+
+/*
+ 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/src/py2geom/path.cpp b/src/py2geom/path.cpp
new file mode 100644
index 0000000..68757a6
--- /dev/null
+++ b/src/py2geom/path.cpp
@@ -0,0 +1,265 @@
+/*
+ * Python bindings for lib2geom
+ *
+ * Copyright 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "2geom/curve.h"
+#include "2geom/bezier-curve.h"
+#include "2geom/path.h"
+#include "2geom/pathvector.h"
+#include "2geom/sbasis-to-bezier.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/rect.h"
+#include "2geom/d2.h"
+
+using namespace boost::python;
+
+Geom::Curve const &path_getitem(Geom::Path const& p, int index)
+{
+ unsigned size = p.size_default();
+ unsigned i = index;
+ if (index < 0)
+ {
+ i = index = size + index;
+ }
+ if ((index < 0) || (i > (size - 1))) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ boost::python::throw_error_already_set();
+ }
+ return p[i];
+}
+
+struct CurveWrap : Geom::Curve, wrapper<Geom::Curve>
+{
+ Geom::Point initialPoint() const {return this->get_override("initialPoint")();}
+ Geom::Point finalPoint() const {return this->get_override("finalPoint")();}
+ bool isDegenerate() const {return this->get_override("isDegenerate")();}
+ CurveWrap *duplicate() const {return this->get_override("duplicate")();}
+ Geom::Rect boundsFast() const {return this->get_override("boundsFast")();}
+ Geom::Rect boundsExact() const {return this->get_override("boundsExact")();}
+ virtual Geom::OptRect boundsLocal(Geom::OptInterval const &i, unsigned deg) const {return this->get_override("boundsLocal")(i,deg);}
+ std::vector<double> roots(double v, Geom::Dim2 d) const {return this->get_override("roots")(v,d);}
+
+ int winding(Geom::Point const &p) const {
+ if (override f = this->get_override("winding")) {
+ return f(p);
+ }
+ return Geom::Curve::winding(p);
+ }
+ int default_winding(Geom::Point p) const { return this->Geom::Curve::winding(p); }
+
+ Geom::Curve *portion(double f, double t) const { return this->get_override("portion")(f,t); }
+ Geom::Curve *reverse() const {
+ if (override f = this->get_override("reverse")) {
+ return f();
+ }
+ return Geom::Curve::reverse();
+ }
+ Geom::Curve *default_reverse() const { return this->Geom::Curve::reverse(); }
+
+ Geom::Curve *derivative() const { return this->get_override("derivative")(); }
+
+
+ Geom::Curve *transformed(Geom::Affine const &m) const { return this->get_override("transformed")(m); }
+
+ Geom::Point pointAt(Geom::Coord t) const {
+ if (override f = this->get_override("pointAt")) {
+ return f(t);
+ }
+ return Geom::Curve::pointAt(t);
+ }
+ Geom::Point default_pointAt(Geom::Coord t) { return this->Geom::Curve::pointAt(t); }
+ std::vector<Geom::Point> pointAndDerivatives(Geom::Coord t, unsigned n) const {
+ return this->get_override("pointAndDerivatives")(t, n);
+ }
+
+ Geom::D2<Geom::SBasis> toSBasis() const {return this->get_override("sbasis")();}
+};
+
+
+/* pycairo stuff: */
+#ifdef HAVE_PYCAIRO
+
+#include "cairo-helpers.h"
+
+void py_cairo_curve(object cr, Geom::Curve const &c) {
+ cairo_curve(cairo_t_from_object(cr), c);
+}
+void py_cairo_rectangle(object cr, Geom::Rect const &r) {
+ cairo_rectangle(cairo_t_from_object(cr), r);
+}
+
+void py_cairo_convex_hull(object cr, Geom::ConvexHull const &r) {
+ cairo_convex_hull(cairo_t_from_object(cr), r);
+}
+/*void py_cairo_path(object cr, Geom::Path const &p) {
+ cairo_path(cairo_t_from_object(cr), p);
+ }*/
+
+void py_cairo_path(object cr, Geom::Path const &p) {
+ cairo_path(cairo_t_from_object(cr), p);
+}
+
+void py_cairo_path(object cr, Geom::PathVector const &p) {
+ cairo_path(cairo_t_from_object(cr), p);
+}
+void py_cairo_path_stitches(object cr, Geom::Path const &p) {
+ cairo_path_stitches(cairo_t_from_object(cr), p);
+}
+void py_cairo_path_stitches(object cr, Geom::PathVector const &p) {
+ cairo_path_stitches(cairo_t_from_object(cr), p);
+}
+void (*cp_1)(object, Geom::Path const &) = &py_cairo_path;
+void (*cp_2)(object, Geom::PathVector const &) = &py_cairo_path;
+
+void (*cps_1)(object, Geom::Path const &) = &py_cairo_path_stitches;
+void (*cps_2)(object, Geom::PathVector const &) = &py_cairo_path_stitches;
+
+
+void py_cairo_d2_sb(object cr, Geom::D2<Geom::SBasis> const &p) {
+ cairo_d2_sb(cairo_t_from_object(cr), p);
+}
+
+void py_cairo_d2_pw_sb(object cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p) {
+ cairo_d2_pw_sb(cairo_t_from_object(cr), p);
+}
+
+void py_cairo_pw_d2_sb(object cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p) {
+ cairo_pw_d2_sb(cairo_t_from_object(cr), p);
+}
+
+#endif // HAVE_PYCAIRO
+
+Geom::Point (Geom::Path::*path_pointAt_time)(Geom::Coord) const = &Geom::Path::pointAt;
+Geom::Coord (Geom::Path::*path_valueAt_time)(Geom::Coord, Geom::Dim2) const = &Geom::Path::valueAt;
+void (Geom::Path::*appendPortionTo_time)(Geom::Path &, Geom::Coord, Geom::Coord) const = &Geom::Path::appendPortionTo;
+//void (Geom::Path::*appendPortionTo_pos)(Geom::Path &, Geom::PathPosition const &, Geom::PathPosition const &, bool) const = &Geom::Path::appendPortionTo;
+
+void wrap_path()
+{
+/* class_<CurveWrap, boost::noncopyable>("Curve")
+ .def("initalPoint", pure_virtual(&Geom::Curve::initialPoint))
+ .def("finalPoint", pure_virtual(&Geom::Curve::finalPoint))
+ .def("duplicate", pure_virtual(&Geom::Curve::duplicate), return_value_policy<manage_new_object>())
+ .def("boundsFast", pure_virtual(&Geom::Curve::boundsFast))
+ .def("boundsExact", pure_virtual(&Geom::Curve::boundsExact))
+ //.def("pointAt", &Geom::Curve::pointAt, &CurveWrap::default_pointAt)
+ //.def("winding", &Geom::Curve::winding, &CurveWrap::default_winding)
+ .def("pointAndDerivatives", pure_virtual(&Geom::Curve::pointAndDerivatives))
+ .def("toSBasis", pure_virtual(&Geom::Curve::toSBasis))
+ ;*/
+/* class_<Geom::LineSegment, bases<CurveWrap> >("LineSegment")
+ .def("points", &Geom::LineSegment::points)
+ ;
+ class_<Geom::QuadraticBezier, bases<CurveWrap> >("QuadraticBezier")
+ .def("points", &Geom::QuadraticBezier::points)
+ ;
+ class_<Geom::CubicBezier, bases<CurveWrap> >("CubicBezier")
+ .def("points", &Geom::CubicBezier::points)
+ ;*/
+ class_<Geom::Path>("Path")
+ .def("__getitem__", path_getitem, return_value_policy<copy_const_reference>()) //or return_internal_reference see http://www.boost.org/doc/libs/1_36_0/libs/python/doc/v2/faq.html#question1
+ .def("empty", &Geom::Path::empty)
+ .def("closed", &Geom::Path::closed)
+ .def("close", &Geom::Path::close)
+ .def("boundsFast", &Geom::Path::boundsFast)
+ .def("boundsExact", &Geom::Path::boundsExact)
+ .def("toPwSb", &Geom::Path::toPwSb)
+ .def(self * Geom::Affine())
+ .def(self *= Geom::Affine())
+ .def("pointAt", path_pointAt_time)
+ .def("valueAt", path_valueAt_time)
+ .def("__call__", path_pointAt_time)
+ .def("roots", &Geom::Path::roots)
+ //.def("allNearestTimes", &Geom::Path::allNearestTimes)
+ //.def("nearestTime", &Geom::Path::nearestTime)
+ .def("appendPortionTo", appendPortionTo_time)
+ //.def("portion", &Geom::Path::portion)
+ .def("reversed", &Geom::Path::reversed)
+ //.def("insert", &Geom::Path::insert)
+ .def("clear", &Geom::Path::clear)
+ //.def("erase", &Geom::Path::erase)
+ .def("erase_last", &Geom::Path::erase_last)
+ //.def("replace", &Geom::Path::replace)
+ .def("start", &Geom::Path::start)
+ .def("initialPoint", &Geom::Path::initialPoint)
+ .def("finalPoint", &Geom::Path::finalPoint)
+ //.def("append", &Geom::Path::append)
+ //.def("appendNew", &Geom::Path::appendNew)
+ ;
+ def("paths_to_pw",Geom::paths_to_pw);
+ class_<Geom::PathVector >("PathVector")
+ .def(vector_indexing_suite<Geom::PathVector >())
+ .def(self * Geom::Affine())
+ .def(self *= Geom::Affine())
+ .def("reversed", &Geom::PathVector::reversed)
+ .def("reverse", &Geom::PathVector::reverse)
+ .def("boundsFast", &Geom::PathVector::boundsFast)
+ .def("boundsExact", &Geom::PathVector::boundsExact)
+ ;
+ def("path_from_piecewise", Geom::path_from_piecewise);
+ def("path_from_sbasis", Geom::path_from_sbasis);
+ def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis);
+
+#ifdef HAVE_PYCAIRO
+void cairo_move_to(cairo_t *cr, Geom::Point p1);
+ def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis);
+void cairo_line_to(cairo_t *cr, Geom::Point p1);
+ def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis);
+void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3);
+ def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis);
+
+ //def("cairo_curve", cairo_curve);
+ def("cairo_convex_hull", py_cairo_convex_hull);
+ def("cairo_path", cp_1);
+ def("cairo_path", cp_2);
+ def("cairo_path_stitches", cps_1);
+ def("cairo_path_stitches", cps_2);
+
+ def("cairo_d2_sb", py_cairo_d2_sb);
+ def("cairo_d2_pw_sb", py_cairo_d2_pw_sb);
+ def("cairo_pw_d2_sb", py_cairo_pw_d2_sb);
+#endif // HAVE_PYCAIRO
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
diff --git a/src/py2geom/point.cpp b/src/py2geom/point.cpp
new file mode 100644
index 0000000..869240d
--- /dev/null
+++ b/src/py2geom/point.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+
+using namespace boost::python;
+
+
+// helpers for point
+tuple point_to_tuple(Geom::Point const& p)
+{
+ return make_tuple(p[0], p[1]);
+}
+
+Geom::Point tuple_to_point(boost::python::tuple const& t)
+{
+ return Geom::Point(extract<double>(t[0]), extract<double>(t[1]));
+}
+
+str point_repr(Geom::Point const& p)
+{
+ return str("(" + str(p[0]) + ", " + str(p[1]) + ")");
+}
+
+//Specifications of overloads
+Geom::Coord (*L2_point) (Geom::Point const &) = &Geom::L2;
+Geom::Point (*rot90_point)(Geom::Point const &) = &Geom::rot90;
+Geom::Coord (*dot_point) (Geom::Point const &, Geom::Point const &) = &Geom::dot;
+Geom::Coord (*cross_point)(Geom::Point const &, Geom::Point const &) = &Geom::cross;
+Geom::Point (*lerp_point)(Geom::Coord, Geom::Point const &, Geom::Point const &) = &Geom::lerp;
+
+bool near_point1(Geom::Point const &a, Geom::Point const &b) { return are_near(a,b); }
+bool near_point2(Geom::Point const &a, Geom::Point const &b, double eps) { return are_near(a,b,eps); }
+
+void wrap_point() {
+ def("point_to_tuple", point_to_tuple);
+ def("tuple_to_point", tuple_to_point);
+
+ def("L1", Geom::L1);
+ def("L2", L2_point);
+ def("L2sq", Geom::L2sq);
+ def("LInfty", Geom::LInfty);
+
+ def("unit_vector", Geom::unit_vector);
+ def("is_zero", Geom::is_zero);
+ def("is_unit_vector", Geom::is_unit_vector);
+
+ def("dot", dot_point);
+ def("cross", cross_point);
+ def("distance", Geom::distance);
+ def("distanceSq", Geom::distanceSq);
+ def("lerp", lerp_point);
+
+ def("atan2", Geom::atan2);
+ def("angle_between", Geom::angle_between);
+
+ def("near", near_point1);
+ def("near", near_point2);
+
+ def("rot90", rot90_point);
+ def("abs", (Geom::Point (*)(Geom::Point const&))&Geom::abs);
+
+ class_<Geom::Point>("Point", init<double, double>())
+ .def(init<>())
+
+ .def("__str__", point_repr)
+ .def("__repr__", point_repr)
+ .def("__getitem__", python_getitem<Geom::Point,double,2>)
+ .def("tuple", point_to_tuple)
+
+ .def("from_tuple", tuple_to_point)
+ .staticmethod("from_tuple")
+
+ //point.h
+ //.def("polar", &Geom::Point::polar)
+ //.staticmethod("polar")
+
+ .def("ccw", &Geom::Point::ccw)
+ .def("cw", &Geom::Point::cw)
+ .def("round", &Geom::Point::round)
+ .def("normalize", &Geom::Point::normalize)
+
+ .def("length", &Geom::Point::length)
+
+ .def(self + self)
+ .def(self - self)
+ .def(self += self)
+ .def(self -= self)
+
+ .def(-self)
+ .def(self * float()).def(float() * self)
+ .def(self / float())
+ .def(self *= float())
+
+ .def(self == self)
+ .def(self != self)
+
+ .def(self <= self)
+ ;
+ implicitly_convertible<Geom::Point,tuple>();
+// TODO: explain why this gives a compile time error
+// implicitly_convertible<tuple,Geom::Point>();
+
+};
+
+/*
+ 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/src/py2geom/pw.cpp b/src/py2geom/pw.cpp
new file mode 100644
index 0000000..bbd8f2c
--- /dev/null
+++ b/src/py2geom/pw.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "2geom/sbasis.h"
+#include "2geom/piecewise.h"
+#include "2geom/d2.h"
+#include "2geom/sbasis-math.h"
+#include "2geom/sbasis-geometric.h"
+
+#include "py2geom.h"
+#include "helpers.h"
+
+using namespace boost::python;
+
+// helpers for point
+tuple pwd2sb_centroid(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pw)
+{
+ Geom::Point p;
+ double a;
+ Geom::centroid(pw, p, a);
+ return boost::python::make_tuple(p, a);
+}
+
+
+void (Geom::Piecewise<Geom::SBasis>::*push_pwsb)(Geom::SBasis const &, double) = &Geom::Piecewise<Geom::SBasis>::push;
+void (Geom::Piecewise<Geom::SBasis>::*push_seg_pwsb)(Geom::SBasis const &) = &Geom::Piecewise<Geom::SBasis>::push_seg;
+Geom::Piecewise<Geom::SBasis> (*portion_pwsb)(const Geom::Piecewise<Geom::SBasis> &, double, double) = &Geom::portion;
+void (Geom::Piecewise<Geom::D2<Geom::SBasis>>::*push_pwd2sb)(Geom::D2<Geom::SBasis> const &, double) = &Geom::Piecewise<Geom::D2<Geom::SBasis>>::push;
+void (Geom::Piecewise<Geom::D2<Geom::SBasis>>::*push_seg_pwd2sb)(Geom::D2<Geom::SBasis> const &) = &Geom::Piecewise<Geom::D2<Geom::SBasis>>::push_seg;
+Geom::Piecewise<Geom::D2<Geom::SBasis>> (*portion_pwd2sb)(const Geom::Piecewise<Geom::D2<Geom::SBasis> > &, double, double) = &Geom::portion;
+std::vector<double> (*roots_pwsb)(const Geom::Piecewise<Geom::SBasis> &) = &Geom::roots;
+//Geom::Piecewise<Geom::SBasis> (*multiply_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::multiply;
+Geom::Piecewise<Geom::SBasis> (*divide_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &, unsigned) = &Geom::divide;
+Geom::Piecewise<Geom::SBasis> (*compose_pwsb_sb)(Geom::Piecewise<Geom::SBasis> const &, Geom::SBasis const &) = &Geom::compose;
+Geom::Piecewise<Geom::SBasis> (*compose_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::compose;
+
+Geom::Piecewise<Geom::SBasis> (*abs_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::abs;
+
+Geom::Piecewise<Geom::SBasis> (*min_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::min;
+Geom::Piecewise<Geom::SBasis> (*max_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::max;
+Geom::Piecewise<Geom::SBasis> (*signSb_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::signSb;
+
+Geom::Piecewise<Geom::SBasis> (*sqrt_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::sqrt;
+
+Geom::Piecewise<Geom::SBasis> (*sin_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::sin;
+Geom::Piecewise<Geom::SBasis> (*cos_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::cos;
+
+//Geom::Piecewise<Geom::SBasis> (*log_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::log;
+Geom::Piecewise<Geom::SBasis> (*reciprocal_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::reciprocal;
+
+Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_fast_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::bounds_fast;
+Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_exact_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::bounds_exact;
+Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_local_pwsb)(Geom::Piecewise<Geom::SBasis> const &, const Geom::OptInterval &) = &Geom::bounds_local;
+
+Geom::SBasis getitem_pwsb(Geom::Piecewise<Geom::SBasis> const &p, int index) {
+ unsigned D = p.size();
+ unsigned i = index;
+ if (index < 0)
+ {
+ i = index = D + index;
+ }
+ if (index < 0 || i > (D - 1)) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ boost::python::throw_error_already_set();
+ }
+ return p[i];
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> > (*unitVector_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &, double, unsigned int) = &Geom::unitVector;
+
+Geom::Piecewise<Geom::SBasis> (*arcLengthSb_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &, double) = &Geom::arcLengthSb;
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> > (*rot90_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &) = &Geom::rot90;
+
+void wrap_pw() {
+ class_<std::vector<Geom::SBasis> >("SBasisVec")
+ .def(vector_indexing_suite<std::vector<Geom::SBasis> >())
+ ;
+ class_<std::vector<Geom::D2<Geom::SBasis> > >("D2SBasisVec")
+ .def(vector_indexing_suite<std::vector<Geom::D2<Geom::SBasis> > >())
+ ;
+
+ def("portion", portion_pwsb);
+ def("portion", portion_pwd2sb);
+ //def("partition", &partition);
+ def("roots", roots_pwsb);
+ //def("multiply", multiply_pwsb);
+ def("divide", divide_pwsb);
+ def("compose", compose_pwsb_sb);
+ def("compose", compose_pwsb);
+ def("abs", abs_pwsb);
+ def("min", min_pwsb);
+ def("max", max_pwsb);
+ def("signSb", signSb_pwsb);
+ def("sqrt", sqrt_pwsb);
+ def("cos", cos_pwsb);
+ def("sin", sin_pwsb);
+ //def("log", log_pwsb);
+ def("reciprocal", reciprocal_pwsb);
+ def("bounds_fast", bounds_fast_pwsb);
+ def("bounds_exact", bounds_exact_pwsb);
+ def("bounds_local", bounds_local_pwsb);
+
+ def("derivative", (Geom::Piecewise<Geom::SBasis> (*)(Geom::Piecewise<Geom::SBasis> const & ))&Geom::derivative);
+ def("integral", (Geom::Piecewise<Geom::SBasis> (*)(Geom::Piecewise<Geom::SBasis> const & ))&Geom::integral);
+ def("derivative", (Geom::Piecewise<Geom::D2<Geom::SBasis> > (*)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &)) &Geom::derivative);
+ def("rot90", rot90_pwd2sb);
+ def("unit_vector", unitVector_pwd2sb);
+ def("arcLengthSb", arcLengthSb_pwd2sb);
+
+ class_<Geom::Piecewise<Geom::SBasis> >("PiecewiseSBasis", init<>())
+ .def(init<double>())
+ .def(init<Geom::SBasis>())
+ .def("__getitem__", getitem_pwsb)
+ .def("__call__", &Geom::Piecewise<Geom::SBasis>::valueAt)
+ .def_readonly("cuts", &Geom::Piecewise<Geom::SBasis>::cuts)
+ .def_readonly("segs", &Geom::Piecewise<Geom::SBasis>::segs)
+ .def("at0", &Geom::Piecewise<Geom::SBasis>::firstValue)
+ .def("at1", &Geom::Piecewise<Geom::SBasis>::lastValue)
+ .def("valueAt", &Geom::Piecewise<Geom::SBasis>::valueAt)
+ .def("size", &Geom::Piecewise<Geom::SBasis>::size)
+ .def("empty", &Geom::Piecewise<Geom::SBasis>::empty)
+ .def("push", push_pwsb)
+ .def("push_cut", &Geom::Piecewise<Geom::SBasis>::push_cut)
+ .def("push_seg", push_seg_pwsb)
+
+ .def("segN", &Geom::Piecewise<Geom::SBasis>::segN)
+ .def("segT", &Geom::Piecewise<Geom::SBasis>::segT)
+ .def("offsetDomain", &Geom::Piecewise<Geom::SBasis>::offsetDomain)
+ .def("scaleDomain", &Geom::Piecewise<Geom::SBasis>::scaleDomain)
+ .def("setDomain", &Geom::Piecewise<Geom::SBasis>::setDomain)
+ .def("concat", &Geom::Piecewise<Geom::SBasis>::concat)
+ .def("continuousConcat", &Geom::Piecewise<Geom::SBasis>::continuousConcat)
+ .def("invariants", &Geom::Piecewise<Geom::SBasis>::invariants)
+
+ .def(self + double())
+ .def(-self)
+ .def(self += double())
+ .def(self -= double())
+ .def(self /= double())
+ .def(self * double())
+ .def(self *= double())
+ .def(self + self)
+ .def(self - self)
+ .def(self * self)
+ .def(self *= self)
+
+ ;
+
+ class_<Geom::Piecewise<Geom::D2<Geom::SBasis> > >("PiecewiseD2SBasis", init<>())
+ .def(init<Geom::D2<Geom::SBasis> >())
+ .def("__getitem__", getitem_pwsb)
+ .def("__call__", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::valueAt)
+ .def_readonly("cuts", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::cuts)
+ .def_readonly("segs", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segs)
+ .def("valueAt", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::valueAt)
+ .def("size", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::size)
+ .def("empty", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::empty)
+ .def("push", push_pwd2sb)
+ .def("push_cut", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::push_cut)
+ .def("push_seg", push_seg_pwd2sb)
+
+ .def("segN", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segN)
+ .def("segT", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segT)
+ .def("offsetDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::offsetDomain)
+ .def("scaleDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::scaleDomain)
+ .def("setDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::setDomain)
+ .def("concat", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::concat)
+ .def("continuousConcat", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::continuousConcat)
+ .def("invariants", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::invariants)
+
+ //.def(self + double())
+ .def(-self)
+ //.def(self += double())
+ //.def(self -= double())
+ //.def(self /= double())
+ .def(self * double())
+ .def(Geom::Piecewise<Geom::SBasis>() * self)
+ .def(self *= double())
+ .def(self + self)
+ .def(self - self)
+ //.def(self * self)
+ //.def(self *= self)
+
+ ;
+ def("centroid", pwd2sb_centroid);
+ def("make_cuts_independent", Geom::make_cuts_independent);
+
+};
+
+/*
+ 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/src/py2geom/py2geom.cpp b/src/py2geom/py2geom.cpp
new file mode 100644
index 0000000..b1ef493
--- /dev/null
+++ b/src/py2geom/py2geom.cpp
@@ -0,0 +1,85 @@
+/*
+ * Python bindings for lib2geom
+ *
+ * Copyright 2006, 2007 Aaron Spike <aaron@ekips.org>
+ * Copyright 2007 Alex Mac <ajm@cs.nott.ac.uk>
+ *
+ * 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 <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include <2geom/geom.h>
+
+#include "py2geom.h"
+
+using namespace boost::python;
+
+BOOST_PYTHON_MODULE(_py2geom)
+{
+
+ /*enum_<IntersectorKind>("IntersectorKind")
+ .value("intersects", intersects)
+ .value("parallel", parallel)
+ .value("coincident", coincident)
+ .value("no_intersection", no_intersection)
+ ;
+ def("segment_intersect", segment_intersect);*/
+
+ wrap_point();
+ wrap_etc();
+ wrap_interval();
+ wrap_transforms();
+ wrap_rect();
+ wrap_circle();
+ wrap_ellipse();
+ wrap_sbasis();
+ wrap_bezier();
+ wrap_linear();
+ wrap_line();
+ wrap_conic();
+ wrap_pw();
+ wrap_d2();
+ wrap_parser();
+ wrap_path();
+ wrap_ray();
+ // wrap_shape();
+ wrap_crossing();
+ // wrap_convex_cover();
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
diff --git a/src/py2geom/py2geom.h b/src/py2geom/py2geom.h
new file mode 100644
index 0000000..ba9fb13
--- /dev/null
+++ b/src/py2geom/py2geom.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#ifndef SEEN_PY2GEOM_H
+#define SEEN_PY2GEOM_H
+
+void wrap_point();
+void wrap_etc();
+void wrap_interval();
+void wrap_transforms();
+void wrap_rect();
+void wrap_circle();
+void wrap_ellipse();
+void wrap_sbasis();
+void wrap_bezier();
+void wrap_linear();
+void wrap_pw();
+void wrap_d2();
+void wrap_path();
+void wrap_parser();
+void wrap_ray();
+// void wrap_shape();
+void wrap_line();
+void wrap_conic();
+void wrap_crossing();
+// void wrap_convex_cover();
+namespace Geom{
+class Point;
+class Linear;
+};
+#include <vector>
+typedef std::vector<Geom::Point > PointVec;
+typedef std::vector<double > DoubleVec;
+typedef std::vector<Geom::Linear> LinearVec;
+
+#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/src/py2geom/ray.cpp b/src/py2geom/ray.cpp
new file mode 100644
index 0000000..9fda595
--- /dev/null
+++ b/src/py2geom/ray.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2009 Ricardo Lafuente <r@sollec.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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/point.h"
+#include "2geom/ray.h"
+// #include "2geom/bezier_curve.h"
+#include "2geom/exception.h"
+
+
+using namespace boost::python;
+
+bool (*are_near_ray)(Geom::Point const& _point, Geom::Ray const& _ray, double eps) = &Geom::are_near;
+double (*angle_between_ray)(Geom::Ray const& r1, Geom::Ray const& r2, bool cw) = &Geom::angle_between;
+
+
+double angle_between_ray_def(Geom::Ray const& r1, Geom::Ray const& r2) {
+ return Geom::angle_between(r1, r2);
+}
+double (*distance_ray)(Geom::Point const& _point, Geom::Ray const& _ray) = &Geom::distance;
+
+// why don't these compile?
+//Geom::Point (*get_ray_origin)(Geom::Ray const) = &Geom::Ray::origin;
+//void (*set_ray_origin)(Geom::Ray const, Geom::Point const& _point) = &Geom::Ray::origin;
+
+void wrap_ray() {
+ def("distance", distance_ray);
+ def("are_near", are_near_ray);
+ def("are_same", Geom::are_same);
+ def("angle_between", angle_between_ray);
+ def("angle_between", angle_between_ray_def);
+ def("make_angle_bisector_ray", Geom::make_angle_bisector_ray);
+
+ class_<Geom::Ray>("Ray", init<Geom::Point, Geom::Coord>())
+ .def(init<Geom::Point,Geom::Point>())
+ .def(init<>())
+
+ // TODO: overloaded
+ //.add_property("origin", get_ray_origin, set_ray_origin)
+ // .add_property("versor", &Geom::Ray::versor, &Geom::Ray::versor)
+ // .add_property("angle", &Geom::Ray::angle, &Geom::Ray::angle)
+
+ .def("isDegenerate", &Geom::Ray::isDegenerate)
+ .def("nearestTime", &Geom::Ray::nearestTime)
+ .def("setBy2Points", &Geom::Ray::setPoints)
+ .def("valueAt", &Geom::Ray::valueAt)
+ .def("pointAt", &Geom::Ray::pointAt)
+ .def("nearestTime", &Geom::Ray::nearestTime)
+ .def("reverse", &Geom::Ray::reverse)
+ .def("roots", &Geom::Ray::roots)
+ .def("transformed", &Geom::Ray::transformed)
+ // requires Curve
+ // .def("portion", &Geom::Ray::portion)
+ .def("segment", &Geom::Ray::segment)
+ ;
+
+};
+
+/*
+ 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/src/py2geom/rect.cpp b/src/py2geom/rect.cpp
new file mode 100644
index 0000000..3b75625
--- /dev/null
+++ b/src/py2geom/rect.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/affine.h"
+#include "2geom/d2.h"
+#include "2geom/interval.h"
+
+using namespace boost::python;
+
+static bool wrap_contains_coord(Geom::Rect const &x, Geom::Point val) {
+ return x.contains(val);
+}
+
+static bool wrap_contains_ivl(Geom::Rect const &x, Geom::Rect val) {
+ return x.contains(val);
+}
+
+static bool wrap_interiorContains_coord(Geom::Rect const &x, Geom::Point val) {
+ return x.interiorContains(val);
+}
+
+static bool wrap_interiorContains_ivl(Geom::Rect const &x, Geom::Rect val) {
+ return x.interiorContains(val);
+}
+
+static void wrap_expandBy_pt(Geom::Rect &x, Geom::Point val) {
+ x.expandBy(val);
+}
+
+static void wrap_expandBy(Geom::Rect &x, double val) {
+ x.expandBy(val);
+}
+
+static void wrap_unionWith(Geom::Rect &x, Geom::Rect const &y) {
+ x.unionWith(y);
+}
+static bool wrap_intersects(Geom::Rect const &x, Geom::Rect const &y) {
+ return x.intersects(y);
+}
+
+void wrap_rect() {
+ //TODO: fix overloads
+ //def("unify", Geom::unify);
+ def("union_list", Geom::union_list);
+ //def("intersect", Geom::intersect);
+ def("distanceSq", (double (*)( Geom::Point const&, Geom::Rect const& ))Geom::distanceSq);
+ def("distance", (double (*)( Geom::Point const&, Geom::Rect const& ))Geom::distance);
+
+ class_<Geom::Rect>("Rect", init<Geom::Interval, Geom::Interval>())
+ .def(init<Geom::Point,Geom::Point>())
+ .def(init<>())
+ .def(init<Geom::Rect const &>())
+
+ .def("__getitem__", python_getitem<Geom::Rect,Geom::Interval,2>)
+
+ .def("min", &Geom::Rect::min)
+ .def("max", &Geom::Rect::max)
+ .def("corner", &Geom::Rect::corner)
+ .def("top", &Geom::Rect::top)
+ .def("bottom", &Geom::Rect::bottom)
+ .def("left", &Geom::Rect::left)
+ .def("right", &Geom::Rect::right)
+ .def("width", &Geom::Rect::width)
+ .def("height", &Geom::Rect::height)
+ .def("dimensions", &Geom::Rect::dimensions)
+ .def("midpoint", &Geom::Rect::midpoint)
+ .def("area", &Geom::Rect::area)
+ .def("maxExtent", &Geom::Rect::maxExtent)
+ .def("contains", wrap_contains_coord)
+ .def("contains", wrap_contains_ivl)
+ .def("interiorContains", wrap_interiorContains_coord)
+ .def("interiorContains", wrap_interiorContains_ivl)
+ .def("intersects", wrap_intersects)
+ .def("expandTo", &Geom::Rect::expandTo)
+ .def("unionWith", &wrap_unionWith)
+ // TODO: overloaded
+ .def("expandBy", wrap_expandBy)
+ .def("expandBy", wrap_expandBy_pt)
+
+ .def(self * Geom::Affine())
+ ;
+
+};
+
+/*
+ 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/src/py2geom/sbasis.cpp b/src/py2geom/sbasis.cpp
new file mode 100644
index 0000000..be547ca
--- /dev/null
+++ b/src/py2geom/sbasis.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#include "py2geom.h"
+#include "helpers.h"
+
+#include "2geom/sbasis.h"
+#include "2geom/sbasis-math.h"
+#include "2geom/point.h"
+
+using namespace boost::python;
+
+Geom::SBasis (*truncate_sbasis)(Geom::SBasis const &, unsigned) = &Geom::truncate;
+Geom::SBasis (*multiply_sbasis)(Geom::SBasis const &, Geom::SBasis const &) = &Geom::multiply;
+Geom::SBasis (*integral_sbasis)(Geom::SBasis const &) = &Geom::integral;
+Geom::SBasis (*derivative_sbasis)(Geom::SBasis const &) = &Geom::derivative;
+
+Geom::Linear sbasis_getitem(Geom::SBasis const& p, int index)
+{
+ int D = p.size();
+ if (index < 0)
+ {
+ index = D + index;
+ }
+ if ((index < 0) || (index > (D - 1))) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ boost::python::throw_error_already_set();
+ }
+ return p[index];
+}
+
+int sbasis_len(Geom::SBasis const& p)
+{
+ return p.size();
+}
+
+#include "2geom/sbasis-to-bezier.h"
+#include "2geom/bezier.h"
+
+Geom::Bezier sbasis_to_returned_bezier (Geom::SBasis const& sb, size_t sz = 0) {
+ Geom::Bezier res;
+ Geom::sbasis_to_bezier(res, sb, sz);
+ return res;
+}
+
+/*object wrap_bounds_fast(Geom::SBasis const& sb) {
+ Geom::OptInterval oi = bounds_fast(sb);
+ return oi?object(*oi):object();
+ }*/
+
+template <typename T, typename target_type>
+object wrap_bounds_fast(T const& sb) {
+ target_type oi = bounds_fast(sb);
+ return oi?object(*oi):object();
+}
+
+
+object wrap_bounds_exact(Geom::SBasis const& sb) {
+ Geom::OptInterval oi = bounds_exact(sb);
+ return oi?object(*oi):object();
+}
+
+object wrap_bounds_local(Geom::SBasis const& sb, Geom::Interval const & iv) {
+ Geom::OptInterval oi = bounds_local(sb, iv);
+ return oi?object(*oi):object();
+}
+void wrap_sbasis() {
+ //sbasis.h
+
+ def("shift", (Geom::SBasis (*)(Geom::SBasis const &a, int sh))&Geom::shift);
+ def("truncate", truncate_sbasis);
+ def("multiply", multiply_sbasis);
+ def("compose", (Geom::SBasis (*) (Geom::SBasis const &, Geom::SBasis const &))&Geom::compose);
+ def("integral", integral_sbasis);
+ def("derivative", derivative_sbasis);
+ def("min", (Geom::Piecewise<Geom::SBasis> (*)(Geom::SBasis const &, Geom::SBasis const & ))&Geom::min);
+ def("sqrt", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::sqrt);
+ def("reciprocal", (Geom::SBasis (*)(Geom::Linear const &, int ))&Geom::reciprocal);
+ def("divide",(Geom::SBasis (*)(Geom::SBasis const &, Geom::SBasis const &, int )) &Geom::divide);
+ def("inverse", (Geom::SBasis (*)(Geom::SBasis, int ))&Geom::inverse);
+ //def("sin", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::sin);
+ //def("cos", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::cos);
+ def("reverse", (Geom::SBasis (*)(Geom::SBasis const &))&Geom::reverse);
+ def("roots", (std::vector<double> (*)(Geom::SBasis const &))&Geom::roots);
+ def("bounds_fast", &wrap_bounds_fast<Geom::SBasis, Geom::OptInterval>);
+ def("bounds_exact", &wrap_bounds_exact);
+ def("bounds_local", &wrap_bounds_local);
+ def("sbasis_to_bezier", &::sbasis_to_returned_bezier);
+
+ class_<Geom::SBasis>("SBasis", init<double>())
+ .def(init<double, double>())
+ .def(init<Geom::Linear>())
+ .def(self_ns::str(self))
+ //TODO: add important vector funcs
+ .def("__getitem__", &sbasis_getitem)
+ .def("__len__", &sbasis_len)
+
+ .def("isZero", &Geom::SBasis::isZero)
+ .def("isFinite", &Geom::SBasis::isFinite)
+ .def("at0", (double (Geom::SBasis::*)() const) &Geom::SBasis::at0)
+ .def("at1", (double (Geom::SBasis::*)() const) &Geom::SBasis::at1)
+ .def("valueAt", &Geom::SBasis::valueAt)
+ .def("toSBasis", &Geom::SBasis::toSBasis)
+
+ .def("normalize", &Geom::SBasis::normalize)
+ .def("tailError", &Geom::SBasis::tailError)
+ .def("truncate", &Geom::SBasis::truncate)
+
+ .def(self + self)
+ .def(self - self)
+ .def(self += self)
+ .def(self -= self)
+
+ .def(self + Geom::Linear())
+ .def(self - Geom::Linear())
+ .def(self += Geom::Linear())
+ .def(self -= Geom::Linear())
+
+ .def(self + float())
+ .def(self - float())
+ .def(self += float())
+ .def(self -= float())
+
+ .def(-self)
+ .def(self * self)
+ .def(self *= self)
+ .def(self * float())
+ .def(float() * self)
+ .def(self / float())
+ .def(self *= float())
+ .def(self /= float())
+ ;
+};
+
+/*
+ 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/src/py2geom/transforms.cpp b/src/py2geom/transforms.cpp
new file mode 100644
index 0000000..ea050da
--- /dev/null
+++ b/src/py2geom/transforms.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2006, 2007 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.
+ *
+ */
+
+#include <boost/python.hpp>
+
+#include "py2geom.h"
+
+#include "2geom/transforms.h"
+
+using namespace boost::python;
+
+//TODO: properly wrap other transforms
+
+void wrap_transforms() {
+ class_<Geom::Affine>("Affine", init<double, double, double, double, double, double>())
+ .def(init<>())
+ .def(init<Geom::Rotate>())
+ .def(init<Geom::Scale>())
+ .def(init<Geom::Translate>())
+ .def(self_ns::str(self))
+ .add_property("xAxis",&Geom::Affine::xAxis,&Geom::Affine::setXAxis)
+ .add_property("yAxis",&Geom::Affine::yAxis,&Geom::Affine::setYAxis)
+ .add_property("translation",&Geom::Affine::translation,&Geom::Affine::setTranslation)
+ .def("isTranslation", &Geom::Affine::isTranslation)
+ .def("isRotation", &Geom::Affine::isRotation)
+ .def("isScale", &Geom::Affine::isScale)
+ .def("isUniformScale", &Geom::Affine::isUniformScale)
+ .def("setIdentity", &Geom::Affine::setIdentity)
+ .def("inverse", &Geom::Affine::inverse)
+ .def("det", &Geom::Affine::det)
+ .def("descrim2", &Geom::Affine::descrim2)
+ .def("descrim", &Geom::Affine::descrim)
+ .def("expansionX", &Geom::Affine::expansionX)
+ .def("expansionY", &Geom::Affine::expansionY)
+ .def(self * self)
+ .def(self * other<Geom::Translate>())
+ .def(self * other<Geom::Scale>())
+ .def(self * other<Geom::Rotate>())
+ ;
+
+ class_<Geom::Scale>("Scale", init<double, double>())
+ .def(self == self)
+ .def(self != self)
+ .def("inverse", &Geom::Scale::inverse)
+ .def(Geom::Point() * self)
+ .def(self * self)
+ .def(self * Geom::Affine())
+ ;
+
+ class_<Geom::Translate>("Translate", init<double, double>())
+ .def(init<Geom::Point>())
+ .def(self == self)
+ .def(self != self)
+ .def("inverse", &Geom::Translate::inverse)
+ .def(Geom::Point() * self)
+ .def(self * self)
+ .def(self * other<Geom::Rotate>())
+ .def(self * other<Geom::Scale>())
+ ;
+
+ class_<Geom::Rotate>("Rotate", init<double>())
+ .def(self == self)
+ .def(self != self)
+ .def("inverse", &Geom::Rotate::inverse)
+ .def("from_degrees", &Geom::Rotate::from_degrees)
+ .staticmethod("from_degrees")
+ .def(Geom::Point() * self)
+ .def(self * self)
+ .def(Geom::Affine() * self)
+ ;
+};
+
+/*
+ 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/src/python/cy2geom_example.py b/src/python/cy2geom_example.py
new file mode 100644
index 0000000..aca7dca
--- /dev/null
+++ b/src/python/cy2geom_example.py
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+import cy2geom
+from cy2geom import *
+
+a = Point(1,2)
+b = Point(31,2)
+print a, b
+print Point.dot(a,b)
+print Point.unit_vector(a)
diff --git a/src/python/elip.py b/src/python/elip.py
new file mode 100644
index 0000000..9809db8
--- /dev/null
+++ b/src/python/elip.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+
+import gtk,gtk.gdk,math,pango #Numeric,
+
+templayout = None
+
+def draw_spot(w, h):
+ x,y = h.x, h.y
+ g = gtk.gdk.GC(w)
+ w.draw_line(g, int(x), int(y), int(x), int(y))
+
+def draw_handle(w, h, name = ""):
+ x,y = h.x, h.y
+ g = gtk.gdk.GC(w)
+ w.draw_line(g, int(x-3), int(y), int(x+3), int(y))
+ w.draw_line(g, int(x), int(y-3), int(x), int(y+3))
+ templayout.set_text(name)
+ w.draw_layout(g, x, y, templayout)
+
+def draw_ray(w, h, d):
+ x,y = h.x, h.y
+ g = gtk.gdk.GC(w)
+ w.draw_line(g, int(h.x), int(h.y), int(3*d.x-2*h.x), int(3*d.y-2*h.y))
+
+def intersect(n0, d0, n1, d1):
+ denominator = n0.x*n1.y - n0.y*n1.x
+ X = n1.y * d0 - n0.y * d1
+ if denominator == 0:
+ return None;
+
+ Y = n0.x * d1 - n1.x * d0
+
+ return handle(X / denominator, Y / denominator)
+
+def seg(a0, a1, b0, b1):
+ n0 = handle(a1.y - a0.y, -a1.x + a0.x)
+ d0 = n0.x*a0.x + n0.y *a0.y
+ n1 = handle(b1.y - b0.y, -b1.x + b0.x)
+ d1 = n1.x*b0.x + n1.y *b0.y
+
+ return intersect(n0, d0, n1, d1)
+
+def draw_elip(w, h):
+ g = gtk.gdk.GC(w)
+ w.draw_line(g, h[0].x, h[0].y, h[1].x, h[1].y)
+ w.draw_line(g, h[3].x, h[3].y, h[4].x, h[4].y)
+ w.draw_line(g, h[3].x, h[3].y, h[2].x, h[2].y)
+ w.draw_line(g, h[2].x, h[2].y, h[1].x, h[1].y)
+
+ c = seg(h[0], h[1], h[3], h[4])
+ draw_handle(w, c)
+
+ if 0:
+ for i in range(6):
+ w.draw_line(g, h[i].x, h[i].y, h[(i+1)%6].x, h[(i+1)%6].y)
+
+
+ cx,cy = c.x, c.y
+
+ ox, oy = None, None
+ for i in range(0, 101):
+ t = i/100.0
+
+
+ nx = (1-t)*h[0].x + t*h[3].x
+ ny = (1-t)*h[0].y + t*h[3].y
+ #w.draw_line(g, 2*cx-nx, 2*cy-ny, nx, ny)
+ c1 = seg(handle(2*cx-nx, 2*cy-ny), handle(nx, ny), h[0], h[2])
+ #draw_handle(w, c1)
+ c2 = seg(handle(2*cx-nx, 2*cy-ny), handle(nx, ny), h[4], h[2])
+ #draw_handle(w, c2)
+ #draw_ray(w, h[3], c1)
+ #draw_ray(w, h[1], c2)
+ six = seg(c1, h[3], c2, h[1])
+ #draw_spot(w, six)
+ if ox:
+ w.draw_line(g, ox, oy, six.x, six.y)
+ ox, oy = six.x, six.y
+ return
+
+ r = math.hypot(h[0].x - cx, h[0].y - cy)
+ s = math.atan2(h[0].y - h[3].y, h[0].x - h[3].x)
+ e = math.atan2(h[1].y - h[4].y, h[1].x - h[4].x)
+ for i in range(0, 101):
+ t = (e-s)*i/100.0 + s
+ nx, ny = r*math.cos(t)+cx, r*math.sin(t)+cy
+ sx, sy = r*math.cos(t+math.pi)+cx, r*math.sin(t+math.pi)+cy
+ w.draw_line(g, sx, sy, nx, ny)
+
+
+class handle:
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+ def __repr__(self):
+ return "handle(%f, %f)" % (self.x, self.y)
+
+handles = [handle(145.000000, 50.000000), handle(43.000000, 69.000000), handle(26.000000, 135.000000), handle(48.000000, 189.000000), handle(248.000000, 188.000000)]
+
+selected_handle = None
+
+def display(da, ev):
+ g = gtk.gdk.GC(da.window)
+ i = 0
+
+
+
+ for h in handles:
+ draw_handle(da.window, h, str(i))
+ i += 1
+ draw_elip(da.window, handles)
+
+def mouse_event(w, e):
+ global selected_handle
+ if e.button == 1:
+ for h in handles:
+ if math.hypot(e.x-h.x, e.y - h.y) < 5:
+ selected_handle = (h, (e.x-h.x, e.y-h.y))
+
+def mouse_release_event(w, e):
+ global selected_handle
+ selected_handle = None
+
+def mouse_motion_event(w, e):
+ global selected_handle
+ if selected_handle:
+ h, (ox, oy) = selected_handle
+ if(e.state & gtk.gdk.BUTTON1_MASK):
+ h.x = e.x - ox
+ h.y = e.y - oy
+ w.queue_draw()
+
+
+win = gtk.Window()
+win.set_default_size(400,400)
+vb = gtk.VBox(False)
+
+da = gtk.DrawingArea()
+templayout = da.create_pango_layout("")
+da.connect("expose_event", display)
+da.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.KEY_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK)
+da.connect("button-press-event", mouse_event)
+da.connect("button-release-event", mouse_release_event)
+da.connect("motion-notify-event", mouse_motion_event)
+#da.connect("key_press_event", key_event)
+win.add(vb)
+vb.pack_start(da)
+win.connect("destroy", gtk.main_quit)
+
+
+win.show_all()
+
+gtk.main()
+
+print handles
diff --git a/src/python/exact-arc-length-quad-bez.py b/src/python/exact-arc-length-quad-bez.py
new file mode 100644
index 0000000..fd2f4b3
--- /dev/null
+++ b/src/python/exact-arc-length-quad-bez.py
@@ -0,0 +1,16 @@
+import math
+
+def q(x, a, b, c):
+ sa = math.sqrt(a)
+ y = math.sqrt(a*x*x+b*x+c)
+ dp = 2*a*x + b
+ r = dp*y/(4*a)
+ t = abs(dp + 2*y*sa)
+ s = (4*a*c-b*b)/(a*sa*8)
+ return r+s*math.log(t)
+
+a = 1160390
+b = -922658
+c = 249477
+print q(1, a,b,c) - q(0,a,b,c)
+
diff --git a/src/python/test_py2geom.py b/src/python/test_py2geom.py
new file mode 100644
index 0000000..2ad6e66
--- /dev/null
+++ b/src/python/test_py2geom.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+
+import py2geom as g
+
+a = g.Point(1,2)
+b = g.Point(31,2)
+print a, b
+
+point_fns_1 = ["L1", "L2", "L2sq", "LInfty", "is_zero", "is_unit_vector",
+ "atan2", "rot90",
+ "unit_vector", "abs"]
+point_fns_2 = ["dot", "angle_between", "distance", "distanceSq", "cross"]
+
+for i in point_fns_1:
+ print "%s:" % i, g.__dict__[i](a)
+for i in point_fns_2:
+ print "%s:" % i, g.__dict__[i](a,b)
+print "a == b", a == b
+print "Lerp:", g.lerp(0.3, a,b)
+
+bo = g.BezOrd(2,3)
+print bo
+print bo.point_at(0.3)
+
+print bo.reverse()
+
+sn = g.sin(g.BezOrd(0.0,8.0),5)
+print sn
+print g.inverse(sn,10)
+print list(sn)
+
+r_sn = g.roots(sn)
+print len(r_sn)
+print list(r_sn)
+
+bo = g.BezOrd(-1,1)
+sb = g.SBasis()
+print sb
+print list(g.roots(sb))
+sb.append(bo)
+print list(g.roots(sb))
diff --git a/src/toys/2dsb2d.cpp b/src/toys/2dsb2d.cpp
new file mode 100644
index 0000000..3effd1f
--- /dev/null
+++ b/src/toys/2dsb2d.cpp
@@ -0,0 +1,128 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/transforms.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+class Sb2d2: public Toy {
+ Path path_a;
+ D2<SBasis2d> sb2;
+ Piecewise<D2<SBasis> > path_a_pw;
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ Geom::Point dir(1,-2);
+ for(unsigned dim = 0; dim < 2; dim++) {
+ Geom::Point dir(0,0);
+ dir[dim] = 1;
+ for(unsigned vi = 0; vi < sb2[dim].vs; vi++)
+ for(unsigned ui = 0; ui < sb2[dim].us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++) {
+ unsigned corner = iu + 2*iv;
+ unsigned i = ui + vi*sb2[dim].us;
+ Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+ if(vi == 0 && ui == 0) {
+ base = Geom::Point(width/4., width/4.);
+ }
+ double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir);
+ sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi);
+ }
+ }
+ cairo_d2_sb2d(cr, sb2, dir*0.1, width);
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.5);
+ cairo_stroke(cr);
+ for(unsigned i = 0; i < path_a_pw.size(); i++) {
+ D2<SBasis> B = path_a_pw[i];
+ //const int depth = sb2[0].us*sb2[0].vs;
+ //const int surface_hand.pts = 4*depth;
+ //D2<SBasis> B = hand.pts_to_sbasis<3>(hand.pts.begin() + surface_hand.pts);
+ cairo_d2_sb(cr, B);
+ for(unsigned dim = 0; dim < 2; dim++) {
+ std::vector<double> r = roots(B[dim]);
+ for(double i : r)
+ draw_cross(cr, B(i));
+ r = roots(Linear(width/4) - B[dim]);
+ for(double i : r)
+ draw_cross(cr, B(i));
+ }
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+ B *= (4./width);
+ D2<SBasis> tB = compose_each(sb2, B);
+ B = B*(width/2) + Geom::Point(width/4, width/4);
+ tB = tB*(width/2) + Geom::Point(width/4, width/4);
+
+ cairo_d2_sb(cr, tB);
+ }
+
+ //*notify << "bo = " << sb2.index(0,0);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="star.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ PathVector paths_a = read_svgd(path_a_name);
+ assert(!paths_a.empty());
+ path_a = paths_a[0];
+ Rect bounds = path_a[0].boundsFast();
+ std::cout << bounds.min() <<std::endl;
+ path_a = path_a * Affine(Translate(-bounds.min()));
+ double extreme = std::max(bounds.width(), bounds.height());
+ path_a = path_a * Scale(40./extreme);
+
+ path_a_pw = path_a.toPwSb();
+ for(unsigned dim = 0; dim < 2; dim++) {
+ sb2[dim].us = 2;
+ sb2[dim].vs = 2;
+ const int depth = sb2[dim].us*sb2[dim].vs;
+ sb2[dim].resize(depth, Linear2d(0));
+ }
+
+ hand.pts.resize(sb2[0].vs*sb2[0].us*4);
+ handles.push_back(&hand);
+
+ }
+ void resize_canvas(Geom::Rect const & s) override {
+ double width = s[0].extent();
+ unsigned ii = 0;
+ for(unsigned vi = 0; vi < sb2[0].vs; vi++)
+ for(unsigned ui = 0; ui < sb2[0].us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++)
+ hand.pts[ii++] = Geom::Point((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2d2);
+ 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/src/toys/CMakeLists.txt b/src/toys/CMakeLists.txt
new file mode 100644
index 0000000..c4929b7
--- /dev/null
+++ b/src/toys/CMakeLists.txt
@@ -0,0 +1,172 @@
+SET(2GEOM_TOY-FRAMEWORK-2_SRC
+toy-framework-2.cpp
+${2GEOM_INCLUDE_DIR}/toys/toy-framework-2.h
+path-cairo.cpp
+${2GEOM_INCLUDE_DIR}/toys/path-cairo.h
+)
+SET(2GEOM_LPE_TOY_FRAMEWORK_SRC
+${2GEOM_TOY-FRAMEWORK-2_SRC}
+lpe-framework.cpp
+${2GEOM_INCLUDE_DIR}/toys/lpe-framework.h
+)
+
+SET(2GEOM_TOYS-2_SRC
+2dsb2d
+aa
+arc-bez
+arc-length-param
+auto-cross
+boolops-toy
+bound-path
+bounds-test
+box3d
+center-warp
+circle-fitting
+circle-intersect
+circle-line-intersect
+circle-tangent-fitting
+collinear-normal
+conic-3
+conic-4
+conic-5
+conic-6
+conic-section-toy
+convole
+curvature-curve
+curvature-test
+curve-curve-distance
+curve-curve-nearest-time
+curve-intersection-by-bezier-clipping
+curve-intersection-by-implicitization
+cylinder3d
+d2sbasis-fitting
+d2sbasis-fitting-with-np
+draw-toy
+ellipse-area-minimizer
+ellipse-bezier-intersect-toy
+ellipse-fitting
+ellipse-intersect-toy
+ellipse-line-intersect-toy
+elliptiarc-3point-center-fitting
+elliptiarc-curve-fitting
+elliptical-arc-toy
+evolute
+filet-minion
+find-derivative
+gear
+#hatches
+implicit-toy
+ineaa
+inner-product-clip
+intersect-data
+inverse-test
+kinematic_templates
+levelsets-test
+line-toy
+load-svgd
+match-curve
+mesh-grad
+metro
+minsb2d-solver
+#normal-bundle
+offset-toy
+pair-intersect
+paptest
+parametrics
+parser
+path-along-path
+path-effects
+pencil
+pencil-2
+plane3d
+point-curve-nearest-time
+portion-test
+precise-flat
+pw-compose-test
+pw-funcs
+pw-toy
+rdm-area
+rect_01
+rect_02
+rect_03
+rect-toy
+root-finder-comparer
+#rtree-toy
+sanitize
+#sb1d
+sb2d
+sb2d-solver
+sbasisdim
+sbasis-fitting
+sb-math-test
+sb-of-interval
+sb-of-sb
+sb-to-bez
+sb-zeros
+scribble
+self-intersect
+sketch-fitter
+smash-intersector
+squiggles
+sweep
+sweeper-toy
+# these ones have only had a trivial rewrite to toy-2
+#uncross
+winding-test
+worms
+)
+
+SET(2GEOM_LPE_TOYS_SRC
+lpe-test
+)
+
+OPTION(2GEOM_TOYS_LPE
+ "Build Inkscape Live Path Effect (LPE) Toy files"
+ ON)
+IF(2GEOM_TOYS_LPE)
+ # make lib for lpetoy
+ add_library(lpetoy ${LIB_TYPE} ${2GEOM_LPE_TOY_FRAMEWORK_SRC})
+ target_include_directories(lpetoy PUBLIC ${GTK3_INCLUDE_DIRS})
+ target_link_libraries(lpetoy 2Geom::2geom ${GTK3_LIBRARIES})
+ if(NOT WIN32 AND NOT APPLE)
+ target_link_libraries(lpetoy -lrt)
+ endif()
+
+ FOREACH(source ${2GEOM_LPE_TOYS_SRC})
+ add_executable(${source} ${source}.cpp)
+ target_link_libraries(${source} lpetoy 2Geom::2geom)
+ ENDFOREACH(source)
+
+ENDIF(2GEOM_TOYS_LPE)
+
+OPTION(2GEOM_TOYS
+ "Build the projects Toy files"
+ ON)
+IF(2GEOM_TOYS)
+ # make lib for toy
+ ADD_LIBRARY(toy-2 ${LIB_TYPE} ${2GEOM_TOY-FRAMEWORK-2_SRC})
+ target_include_directories(toy-2 PUBLIC ${GTK3_INCLUDE_DIRS})
+ TARGET_LINK_LIBRARIES(toy-2 2Geom::2geom ${GTK3_LIBRARIES})
+ if(NOT WIN32 AND NOT APPLE)
+ target_link_libraries(toy-2 -lrt)
+ endif()
+
+ FOREACH(source ${2GEOM_TOYS-2_SRC})
+ IF(${source} STREQUAL aa)
+ ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp)
+ TARGET_LINK_LIBRARIES(${source} affa)
+ ELSEIF(${source} STREQUAL ineaa)
+ ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp)
+ TARGET_LINK_LIBRARIES(${source} affa)
+ ELSEIF(${source} STREQUAL implicit-toy)
+ ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp)
+ TARGET_LINK_LIBRARIES(${source} affa)
+ ELSEIF(${source} STREQUAL boolops-cgal)
+
+ ELSE(${source} STREQUAL aa)
+ ADD_EXECUTABLE(${source} ${source}.cpp)
+ ENDIF(${source} STREQUAL aa)
+ TARGET_LINK_LIBRARIES(${source} toy-2 2Geom::2geom ${GTK3_LIBRARIES} )
+ ENDFOREACH(source)
+ENDIF(2GEOM_TOYS)
+
diff --git a/src/toys/aa.cpp b/src/toys/aa.cpp
new file mode 100644
index 0000000..8b852a2
--- /dev/null
+++ b/src/toys/aa.cpp
@@ -0,0 +1,520 @@
+#include <2geom/convex-hull.h>
+#include <2geom/d2.h>
+#include <2geom/geom.h>
+#include <2geom/numeric/linear_system.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <aa.h>
+#include <complex>
+#include <algorithm>
+#include <optional>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+//Geom::Rect zoom(Geom::Rect r, Geom::Point p, double s) {
+// return p + (r - p)*s;
+//}
+
+typedef std::complex<AAF> CAAF;
+
+struct PtLexCmp{
+ bool operator()(const Point &a, const Point &b) {
+ return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1]));
+ }
+};
+
+void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) {
+ std::optional<Geom::LineSegment> ls =
+ rect_line_intersect(r, Line::fromNormalDistance(n, c));
+
+ if(ls) {
+ cairo_move_to(cr, (*ls)[0]);
+ cairo_line_to(cr, (*ls)[1]);
+ cairo_stroke(cr);
+
+ }
+}
+
+OptRect tighten(Rect &r, Point n, Interval lu) {
+ vector<Geom::Point> result;
+ Point resultp;
+ for(int i = 0; i < 4; i++) {
+ Point cnr = r.corner(i);
+ double z = dot(cnr, n);
+ if((z > lu[0]) and (z < lu[1]))
+ result.push_back(cnr);
+ }
+ for(int i = 0; i < 2; i++) {
+ double c = lu[i];
+
+ std::optional<Geom::LineSegment> ls =
+ rect_line_intersect(r, Line::fromNormalDistance(n, c));
+
+ if(ls) {
+ result.push_back((*ls)[0]);
+ result.push_back((*ls)[1]);
+ }
+ }
+ if(result.size() < 2)
+ return OptRect();
+ Rect nr(result[0], result[1]);
+ for(size_t i = 2; i < result.size(); i++) {
+ nr.expandTo(result[i]);
+ }
+ return intersect(nr, r);
+}
+
+AAF ls_sample_based(AAF x, vector<Point> pts) {
+ NL::Matrix m(pts.size(), 2);
+ NL::Vector v(pts.size());
+ NL::LinearSystem ls(m, v);
+
+ m.set_all(0);
+ v.set_all(0);
+ for (unsigned int k = 0; k < pts.size(); ++k)
+ {
+ m(k,0) += pts[k][0];
+ m(k,1) += 1;
+ //std::cout << pts[k] << " ";
+
+ v[k] += pts[k][1];
+ //v[1] += pts[k][1];
+ //v[2] += y2;
+ }
+
+ ls.SV_solve();
+
+ double A = ls.solution()[0];
+ double B = ls.solution()[1];
+ // Ax + B = y
+ Interval bnd(0,0);
+ for (unsigned int k = 0; k < pts.size(); ++k)
+ {
+ bnd.extendTo(A*pts[k][0]+B - pts[k][1]);
+ }
+ //std::cout << A << "," << B << std::endl;
+ return AAF(x, A, B, bnd.extent(),
+ x.special);
+}
+
+AAF md_sample_based(AAF x, vector<Point> pts) {
+ Geom::ConvexHull ch1(pts);
+ Point a, b, c;
+ double dia = ch1.narrowest_diameter(a, b, c);
+ Point db = c-b;
+ double A = db[1]/db[0];
+ Point aa = db*(dot(db, a-b)/dot(db,db))+b;
+ Point mid = (a+aa)/2;
+ double B = mid[1] - A*mid[0];
+ double dB = (a[1] - A*a[0]) - B;
+ // Ax + B = y
+ std::cout << A << "," << B << std::endl;
+ return AAF(x, A, B, dB,
+ x.special);
+}
+
+AAF atan_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+
+ const double ea = atan(a);
+ const double eb = atan(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ double xs = sqrt(1/alpha-1);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,atan(xs)));
+ xs = -xs;
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,atan(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+AAF log_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+ AAF_TYPE type;
+ if(a > 0)
+ type = AAF_TYPE_AFFINE;
+ else if(b < 0) { // no point in continuing
+ type = AAF_TYPE_NAN;
+ return AAF(type);
+ }
+ else if(a <= 0) { // undefined, can we do better?
+ type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN);
+ return AAF(type);
+ // perhaps we should make a = 0+eps and try to continue?
+ }
+
+ const double ea = log(a);
+ const double eb = log(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // dlog(xs) = alpha
+ double xs = 1/(alpha);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,log(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+AAF exp_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+
+ const double ea = exp(a);
+ const double eb = exp(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // dexp(xs) = alpha
+ double xs = log(alpha);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,exp(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+AAF pow_sample_based(AAF x, double p) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+ AAF_TYPE type;
+ if(a >= 0)
+ type = AAF_TYPE_AFFINE;
+ else if(b < 0) { // no point in continuing
+ type = AAF_TYPE_NAN;
+ return AAF(type);
+ }
+ else if(a <= 0) { // undefined, can we do better?
+ type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN);
+ return AAF(type);
+ // perhaps we should make a = 0+eps and try to continue?
+ }
+
+ const double ea = pow(a, p);
+ const double eb = pow(b, p);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // d(xs^p) = alpha
+ // p xs^(p-1) = alpha
+ // xs = (alpha/p)^(1-p)
+ double xs = pow(alpha/p, 1./(p-1));
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,pow(xs, p)));
+ xs = -xs;
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,pow(xs, p)));
+
+ return md_sample_based(x, pts);
+}
+
+Point origin;
+double scale=100;
+
+AAF trial_eval(AAF x, AAF y) {
+ x = x-origin[0];
+ y = y-origin[1];
+
+ x = x/scale;
+ y = y/scale;
+
+ return x*x -y*y + -6*x +10*y-16;
+ return -y + log(sqrt(x))/log(x);
+ return y*y - x*(x-1)*(x+1);
+
+ //return x*x - 1;
+ //return y - pow(x,3);
+ //return y - pow_sample_based(x,2.5);
+ //return y - log_sample_based(x);
+ //return y - log(x);
+ //return y - exp_sample_based(x*log(x));
+ //return y - sqrt(sin(x));
+ //return sqrt(y)*x - sqrt(x) - y - 1;
+ //return y-1/x;
+ //return exp(x)-y;
+ //return sin(x)-y;
+ //return exp_sample_based(x)-y;
+ //return atan(x)-y;
+ //return atan_sample_based(x)-y;
+ //return atanh(x)-y;
+ //return x*y;
+ //return 4*x+3*y-1;
+ //return x*x + y*y - 1;
+ //return sin(x*y) + cos(pow(x, 3)) - atan(x);
+ //return pow((x*x + y*y), 2) - (x*x-y*y);
+ return 4*(2*y-4*x)*(2*y+4*x-16)-16*y*y;
+ return pow((x*x + y*y), 2) - (x*x-y*y);
+ //return pow(x,3) - 3*x*x - 3*y*y;
+ return (x*x + y*y-1)*((x-1)*(x-1)+y*y-1);
+ //return x*x-y;
+ //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5;
+}
+
+AAF xaxis(AAF x, AAF y) {
+ y = y-origin[1];
+ y = y/scale;
+ return y;
+}
+
+AAF xaxis2(AAF x, AAF y) {
+ y = y-origin[1];
+ y = y/scale;
+ return y-4;
+}
+
+AAF yaxis(AAF x, AAF y) {
+ x = x-origin[0];
+ x = x/scale;
+ return x;
+}
+
+class ConvexTest: public Toy {
+public:
+ PointSetHandle test_window;
+ PointSetHandle samples;
+ PointHandle orig_handle;
+ ConvexTest () {
+ toggles.push_back(Toggle("Show trials", false));
+ handles.push_back(&test_window);
+ handles.push_back(&samples);
+ handles.push_back(&orig_handle);
+ orig_handle.pos = Point(300,300);
+ test_window.push_back(Point(100,100));
+ test_window.push_back(Point(200,200));
+ for(int i = 0; i < 0; i++) {
+ samples.push_back(Point(i*100, i*100+25));
+ }
+ }
+ int iters;
+ int splits[4];
+ bool show_splits;
+ std::vector<Toggle> toggles;
+ AAF (*eval)(AAF, AAF);
+ Geom::Rect view;
+ void recursive_implicit(Rect r, cairo_t*cr, double w) {
+ if(show_splits) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ /*if(f.is_partial())
+ cairo_set_source_rgba(cr, 1, 0, 1, 0.25);
+ else*/
+ cairo_set_source_rgba(cr, 0, 1, 0, 0.25);
+ cairo_rectangle(cr, r);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ iters++;
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ //assert(x.rad() > 0);
+ //assert(y.rad() > 0);
+ AAF f = (*eval)(x, y);
+ // pivot
+ double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if(ivl.extent() < 0.5*L2(n)) {
+ draw_line_in_rect(cr, r, n, ivl.middle());
+ return;
+ }
+ if(!f.is_partial() and f.is_indeterminate()) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ if(f.is_infinite()) {
+ cairo_set_source_rgb(cr, 1, 0.5, 0.5);
+ } else if(f.is_nan()) {
+ cairo_set_source_rgb(cr, 1, 1, 0);
+ } else {
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ }
+ cairo_rectangle(cr, r);
+ if(show_splits) {
+ cairo_stroke(cr);
+ } else {
+ cairo_fill(cr);
+ }
+ cairo_restore(cr);
+ return;
+ }
+
+ if((r.width() > w) or (r.height()>w)) {
+ if(f.straddles_zero()) {
+ // Three possibilities:
+ // 1) the trim operation buys us enough that we should just iterate
+ Point c = r.midpoint();
+ Rect oldr = r;
+ if(out)
+ r = *out;
+ if(1 && out && (r.area() < oldr.area()*0.25)) {
+ splits[0] ++;
+ recursive_implicit(r, cr, w);
+ // 2) one dimension is significantly smaller
+ } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) {
+ splits[1]++;
+ recursive_implicit(Rect(Interval(r.left(), r.right()),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(r.left(), r.right()),
+ Interval(c[1], r.bottom())), cr,w);
+ } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) {
+ splits[2]++;
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), r.bottom())), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(r.top(), r.bottom())), cr,w);
+ // 3) to ensure progress we must do a four way split
+ } else {
+ splits[3]++;
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(c[1], r.bottom())), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(c[1], r.bottom())), cr,w);
+ }
+ }
+ } else {
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 'w') toggles[0].toggle(); else
+ if(e->keyval == 'a') toggles[1].toggle(); else
+ if(e->keyval == 'q') toggles[2].toggle(); else
+ if(e->keyval == 's') toggles[3].toggle();
+ redraw();
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+ void scroll(GdkEventScroll* e) override {
+ if (e->direction == GDK_SCROLL_UP) {
+ scale /= 1.2;
+ } else if (e->direction == GDK_SCROLL_DOWN) {
+ scale *= 1.2;
+ }
+ redraw();
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ origin = orig_handle.pos;
+ if(1) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0.5, 0.5, 1);
+ eval = xaxis;
+ recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ eval = xaxis2;
+ recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ eval = yaxis;
+ recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ cairo_restore(cr);
+ iters = 0;
+ for(int & split : splits)
+ split = 0;
+ show_splits = toggles[0].on;
+ eval = trial_eval;
+ recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ for(int split : splits)
+ *notify << split << " + ";
+ *notify << " = " << iters;
+ }
+ if(1) {
+ Rect r(test_window.pts[0], test_window.pts[1]);
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ //AAF f = md_sample_based(x, samples.pts)-y;
+ if(0) {
+ x = x-500;
+ y = y-300;
+ x = x/200;
+ y = y/200;
+ AAF f = atan_sample_based(x)-y;
+ cout << f << endl;
+ }
+ AAF f = (*eval)(x, y);
+ double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ //cout << d << endl;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if(out)
+ cairo_rectangle(cr, *out);
+ cairo_rectangle(cr, r);
+ draw_line_in_rect(cr, r, n, ivl.min());
+ cairo_stroke(cr);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0.5, 0.5, 0);
+ draw_line_in_rect(cr, r, n, ivl.middle());
+ cairo_restore(cr);
+ draw_line_in_rect(cr, r, n, ivl.max());
+ cairo_stroke(cr);
+ }
+ if(0) {
+ Geom::ConvexHull gm(samples.pts);
+ cairo_convex_hull(cr, gm);
+ cairo_stroke(cr);
+ Point a, b, c;
+ double dia = gm.narrowest_diameter(a, b, c);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 2);
+ cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+ cairo_move_to(cr, b);
+ cairo_line_to(cr, c);
+ cairo_move_to(cr, a);
+ cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b);
+ cairo_stroke(cr);
+ //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n";
+ cairo_restore(cr);
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ Point d(25,25);
+ toggles[0].bounds = Rect(Point(10, height-80)+d,
+ Point(10+120, height-80+d[1])+d);
+
+ draw_toggles(cr, toggles);
+ }
+
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ConvexTest());
+
+ 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/src/toys/arc-bez.cpp b/src/toys/arc-bez.cpp
new file mode 100644
index 0000000..4e9dace
--- /dev/null
+++ b/src/toys/arc-bez.cpp
@@ -0,0 +1,129 @@
+#include <2geom/d2.h>
+
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+Piecewise<SBasis>
+arcLengthSb2(Piecewise<D2<SBasis> > const &M, double /*tol*/){
+ Piecewise<D2<SBasis> > dM = derivative(M);
+ Piecewise<SBasis> length = integral(dot(dM, unitVector(dM)));
+ length-=length.segs.front().at0();
+ return length;
+}
+
+
+
+class ArcBez: public Toy {
+ PointSetHandle bez_handle;
+public:
+ ArcBez() {
+ for(int i = 0; i < 6; i++)
+ bez_handle.push_back(uniform()*400, uniform()*400);
+ handles.push_back(&bez_handle);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timing_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ D2<SBasis> B = bez_handle.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0.25, 0.5, 0, 0.8);
+
+ double tol = 0.01;
+ bool time_operations = true;
+ if(time_operations) {
+ std::string units_string("us");
+ Timer tm;
+ tm.ask_for_timeslice();
+ tm.start();
+ Piecewise<SBasis> als = arcLengthSb(B, tol);
+ Timer::Time als_time = tm.lap();
+ *timing_stream << "arcLengthSb based "
+ << ", time = " << als_time
+ << units_string << std::endl;
+
+ tm.start();
+ Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01);
+ Timer::Time als2_time = tm.lap();
+
+ *timing_stream << "arcLengthSb2 based "
+ << ", time = " << als2_time
+ << units_string << std::endl;
+ double abs_error = 0;
+ double integrating_arc_length = 0;
+ tm.start();
+ length_integrating(B, integrating_arc_length, abs_error, 1e-10);
+ Timer::Time li_time = tm.lap();
+
+ *timing_stream << "gsl integrating "
+ << ", time = " << li_time
+ << units_string << std::endl;
+ }
+ Piecewise<SBasis> als = arcLengthSb(B, tol);
+ Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01);
+
+ cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0, width))) , Piecewise<SBasis>(Linear(height-5)) - Piecewise<SBasis>(als)) );
+
+ double abs_error = 0;
+ double integrating_arc_length = 0;
+ length_integrating(B, integrating_arc_length, abs_error, 1e-10);
+ *notify << "arc length = " << integrating_arc_length << "; abs error = " << abs_error << std::endl;
+ double als_arc_length = als.segs.back().at1();
+ *notify << "arc length = " << als_arc_length << "; error = " << als_arc_length - integrating_arc_length << std::endl;
+ double als_arc_length2 = als2.segs.back().at1();
+ *notify << "arc length2 = " << als_arc_length2 << "; error = " << als_arc_length2 - integrating_arc_length << std::endl;
+
+ {
+ double err = fabs(als_arc_length - integrating_arc_length);
+ double scale = 10./err;
+ Piecewise<D2<SBasis> > dM = derivative(Piecewise<D2<SBasis> >(B));
+ Piecewise<SBasis> ddM = dot(dM,dM);
+ Piecewise<SBasis> dMlength = sqrt(ddM,tol,3);
+ double plot_width = (width - 200);
+
+ Point org(100,height - 200);
+ cairo_move_to(cr, org);
+ for(double t = 0; t < 1; t += 0.01) {
+ cairo_line_to(cr, org + Point(t*plot_width, scale*(sqrt(ddM.valueAt(t)) - dMlength.valueAt(t))));
+ }
+ cairo_move_to(cr, org);
+ cairo_line_to(cr, org+Point(plot_width, 0));
+ cairo_stroke(cr);
+
+ draw_number(cr, org, scale);
+
+ }
+
+
+ Toy::draw(cr, notify, width, height, save,timing_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ArcBez());
+
+ 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/src/toys/arc-length-param.cpp b/src/toys/arc-length-param.cpp
new file mode 100644
index 0000000..2c7f3c9
--- /dev/null
+++ b/src/toys/arc-length-param.cpp
@@ -0,0 +1,101 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2;
+ for( double t = M.cuts.front(); t < M.cuts.back(); t += space) {
+ Point pos = M(t), perp = Mperp(t);
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_pw_d2_sb(cr, M);
+ cairo_stroke(cr);
+}
+
+#define SIZE 4
+
+class LengthTester: public Toy {
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.concat(Piecewise<D2<SBasis> >(B2));
+
+// testing fuse_nearby_ends
+ std::vector< Piecewise<D2<SBasis> > > pieces;
+ pieces = fuse_nearby_ends(split_at_discontinuities(B),9);
+ Piecewise<D2<SBasis> > C;
+ for (auto & piece : pieces){
+ C.concat(piece);
+ }
+// testing fuse_nearby_ends
+
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ //cairo_d2_sb(cr, B1);
+ cairo_pw_d2_sb(cr, C);
+ //cairo_pw_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ Timer tm;
+ tm.ask_for_timeslice();
+ tm.start();
+
+ Piecewise<D2<SBasis> > uniform_B = arc_length_parametrization(B);
+ Timer::Time als_time = tm.lap();
+ *timer_stream << "arc_length_parametrization, time = " << als_time << std::endl;
+
+ cairo_set_source_rgba (cr, 0., 0., 0.9, 1);
+ dot_plot(cr,uniform_B);
+ cairo_stroke(cr);
+ *notify << "pieces = " << uniform_B.size() << ";\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ LengthTester(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(150,150);
+ b1_handle.pts[1] = Geom::Point(150,150);
+ b1_handle.pts[2] = Geom::Point(150,450);
+ b1_handle.pts[3] = Geom::Point(450,150);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new LengthTester);
+ 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/src/toys/auto-cross.cpp b/src/toys/auto-cross.cpp
new file mode 100644
index 0000000..00b5b25
--- /dev/null
+++ b/src/toys/auto-cross.cpp
@@ -0,0 +1,321 @@
+/* @brief
+ * A toy for playing around with Path::intersectSelf().
+ *
+ * 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 <toys/toy-framework-2.h>
+#include <2geom/path.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/svg-path-parser.h>
+
+using namespace Geom;
+using Color = uint32_t;
+
+Color const RED = 0x80000000;
+Color const GREEN = 0x00800000;
+Color const BROWN = 0x90500000;
+Color const BLUE = 0x0000ff00;
+Color const BLACK = 0x00000000;
+
+static void set_cairo_rgb(cairo_t *c, Color rgb)
+{
+ cairo_set_source_rgba(c, (double)((rgb & 0xFF000000) >> 24) / 255.0,
+ (double)((rgb & 0x00FF0000) >> 16) / 255.0,
+ (double)((rgb & 0x0000FF00) >> 8) / 255.0,
+ 1.0);
+}
+
+static void write_text(cairo_t *c, const char *text, Point const &position, Color color)
+{
+ cairo_move_to(c, position);
+ cairo_set_font_size(c, 12);
+ set_cairo_rgb(c, color);
+ cairo_show_text(c, text);
+}
+
+static std::string format_point(Point const &pt)
+{
+ std::ostringstream ss;
+ ss.precision(4);
+ ss << pt;
+ return ss.str();
+}
+
+static EllipticalArc random_arc(Point from, Point to)
+{
+ double const dist = distance(from, to);
+ auto angle = atan2(to - from);
+ bool sweep = std::abs(angle) > M_PI_2;
+ angle *= 2;
+ angle = std::fmod(angle, 2.0 * M_PI);
+ return EllipticalArc(from, Point(0.5 * dist, 2.0 * dist), angle, false, sweep, to);
+}
+
+class Item
+{
+private:
+ Path _path;
+ Color _color;
+ std::string _d;
+
+public:
+ Item(Color color)
+ : _color{color}
+ {}
+
+ void setPath(Path &&new_path)
+ {
+ _path = std::forward<Path>(new_path);
+ std::ostringstream oss;
+ oss << _path;
+ _d = oss.str();
+ }
+
+ void draw(cairo_t *cr) const
+ {
+ cairo_set_line_width(cr, 2);
+ set_cairo_rgb(cr, _color);
+ cairo_path(cr, _path);
+ cairo_stroke(cr);
+ _drawBezierTangents(cr);
+ _drawSelfIntersections(cr);
+ }
+
+ void write(cairo_t *cr, Point const &pos) const
+ {
+ write_text(cr, _d.c_str(), pos, _color);
+ }
+
+ std::string const& getSVGD() const { return _d; }
+
+private:
+ void _drawBezierTangents(cairo_t *c) const
+ {
+ cairo_set_line_width(c, 1);
+ set_cairo_rgb(c, 0x0000b000);
+ // Draw tangents for Beziers:
+ for (auto const &curve : _path) {
+ if (auto const *bezier = dynamic_cast<BezierCurve const *>(&curve)) {
+ if (bezier->order() > 1) {
+ auto points = bezier->controlPoints();
+ cairo_move_to(c, points[0]);
+ cairo_line_to(c, points[1]);
+ cairo_stroke(c);
+ cairo_move_to(c, points.back());
+ cairo_line_to(c, points[points.size() - 2]);
+ cairo_stroke(c);
+ }
+ }
+ }
+ }
+
+ void _drawSelfIntersections(cairo_t *cr) const
+ {
+ set_cairo_rgb(cr, BLACK);
+ for (auto const &xing : _path.intersectSelf()) {
+ draw_cross(cr, xing.point());
+ auto const coords = format_point(xing.point());
+ write_text(cr, coords.c_str(), xing.point() + Point(8, -8), BLACK);
+ }
+ }
+};
+
+class AutoCross : public Toy
+{
+public:
+ AutoCross()
+ : items{Item(RED), Item(GREEN), Item(BROWN)}
+ {
+ bezier_handles.pts = { {200, 400}, {100, 300}, {300, 400}, {300, 300}, {450, 300}, {500, 500}, {400, 400} };
+ elliptical_handles.pts = { {500, 200}, {700, 400}, {600, 500} };
+ mixed_handles.pts = { {100, 600}, {120, 690}, {300, 650}, {330, 600}, {500, 800} };
+ handles.push_back(&bezier_handles);
+ handles.push_back(&elliptical_handles);
+ handles.push_back(&mixed_handles);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save,
+ std::ostringstream *timer_stream) override
+ {
+ if (crashed) {
+ draw_error(cr, width, height);
+ } else {
+ try {
+ draw_impl(cr, width, height);
+ } catch (Exception &e) {
+ error = e.what();
+ handles.clear();
+ crashed = true;
+ }
+ }
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ void key_hit(GdkEventKey *ev) override
+ {
+ if (ev->keyval == GDK_KEY_space) {
+ print_path_d();
+ } else if ((ev->keyval == GDK_KEY_V || ev->keyval == GDK_KEY_v) && (ev->state & GDK_CONTROL_MASK)) {
+ paste_d();
+ }
+
+ }
+
+private:
+ std::string error;
+ std::vector<Item> items;
+ PointSetHandle bezier_handles, elliptical_handles, mixed_handles;
+ bool crashed = false;
+
+ void paste_d()
+ {
+ auto *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ auto *text = gtk_clipboard_wait_for_text(clipboard);
+ if (!text) {
+ return;
+ }
+ PathVector pv;
+ try {
+ pv = parse_svg_path(text);
+ } catch (SVGPathParseError &error) {
+ std::cerr << "Error pasting path d: " << error.what() << std::endl;
+ return;
+ }
+ if (pv.empty()) {
+ return;
+ }
+ Item paste_item{RED}; // TODO: cycle through a color palette.
+ paste_item.setPath(std::move(pv[0]));
+ items.push_back(paste_item);
+ redraw();
+ }
+
+ void print_path_d()
+ {
+ std::cout << "Path snapshots:\n";
+ for (auto it = items.rbegin(); it != items.rend(); ++it) {
+ std::cout << it->getSVGD() << '\n';
+ }
+ }
+
+ void refresh_geometry()
+ {
+ // Construct the 2-segment Bézier path
+ auto const &cp = bezier_handles.pts;
+ Path bezier;
+ bezier.append(BezierCurveN<3>(cp[0].round(), cp[1].round(), cp[2].round(), cp[3].round()));
+ bezier.append(BezierCurveN<3>(cp[3].round(), cp[4].round(), cp[5].round(), cp[6].round()));
+ items[0].setPath(std::move(bezier));
+
+ // Construct the elliptical arcs
+ auto const &ae = elliptical_handles.pts;
+ Path elliptical;
+ elliptical.append(random_arc(ae[0], ae[1]));
+ elliptical.append(random_arc(ae[1], ae[2]));
+ items[1].setPath(std::move(elliptical));
+
+ // Construct a mixed path
+ auto const &mh = mixed_handles.pts;
+ Path mixed;
+ mixed.append(BezierCurveN<3>(mh[0], mh[1], mh[2], mh[3]));
+ mixed.append(random_arc(mh[3], mh[4]));
+ mixed.close();
+ items[2].setPath(std::move(mixed));
+ }
+
+ void draw_impl(cairo_t *cr, int width, int height)
+ {
+ refresh_geometry();
+ write_title(cr);
+
+ auto text_pos = Point(20, height - 20);
+ for (auto const &item : items) {
+ item.draw(cr);
+ item.write(cr, text_pos);
+ text_pos -= Point(0, 20);
+ }
+ }
+
+ void write_title(cairo_t *c)
+ {
+ cairo_move_to(c, 10, 40);
+ cairo_set_font_size(c, 30);
+ set_cairo_rgb(c, 0x0);
+ cairo_show_text(c, "Self-intersection of paths in lib2geom!");
+ cairo_set_font_size(c, 14);
+ cairo_move_to(c, 10, 60);
+ cairo_show_text(c, "[Space]: Print SVG 'd' attributes to stdout");
+ cairo_move_to(c, 10, 80);
+ cairo_show_text(c, "[Ctrl-V]: Paste a 'd' attribute from clipboard");
+ }
+
+ void draw_error(cairo_t *cr, int width, int height)
+ {
+ auto center = Point(0.5 * (double)width, 0.5 * (double)height);
+ cairo_move_to(cr, center + Point(-90, -100));
+ cairo_line_to(cr, center + Point(100, 90));
+ cairo_line_to(cr, center + Point(90, 100));
+ cairo_line_to(cr, center + Point(-100, -90));
+ cairo_close_path(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ cairo_fill(cr);
+
+ cairo_move_to(cr, center + Point(90, -100));
+ cairo_line_to(cr, center + Point(100, -90));
+ cairo_line_to(cr, center + Point(-90, 100));
+ cairo_line_to(cr, center + Point(-100, 90));
+ cairo_close_path(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ cairo_fill(cr);
+
+ cairo_move_to(cr, center + Point(-90, 120));
+ cairo_show_text(cr, "Sorry, your toy has just broken :-/");
+ cairo_move_to(cr, Point(10, center[Y] + 150));
+ cairo_show_text(cr, error.c_str());
+ }
+};
+
+int main(int argc, char **argv)
+{
+ auto toy = AutoCross();
+ init(argc, argv, &toy, 800, 800);
+ 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 : \ No newline at end of file
diff --git a/src/toys/boolops-toy.cpp b/src/toys/boolops-toy.cpp
new file mode 100644
index 0000000..389fdc3
--- /dev/null
+++ b/src/toys/boolops-toy.cpp
@@ -0,0 +1,242 @@
+#include <2geom/d2.h>
+#include <2geom/intersection-graph.h>
+#include <2geom/path.h>
+#include <2geom/sbasis.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/transforms.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <algorithm>
+#include <cstdlib>
+
+using namespace Geom;
+
+class BoolOps : public Toy {
+ PathVector as, bs;
+ Line ah, bh;
+ PointHandle path_handles[4];
+ std::vector<Toggle> togs;
+ bool path_handles_inited;
+
+public:
+ BoolOps()
+ : path_handles_inited(false)
+ {}
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ if (!path_handles_inited) {
+ Rect vp(Point(10,10), Point(width-10, height-10));
+ setup_path_handles(vp);
+ }
+
+ Line aht(path_handles[0].pos, path_handles[1].pos);
+ Line bht(path_handles[2].pos, path_handles[3].pos);
+
+ PathVector ast = as * ah.transformTo(aht);
+ PathVector bst = bs * bh.transformTo(bht);
+
+ Timer tm;
+ tm.start();
+
+ PathIntersectionGraph pig(ast, bst);
+ std::vector<Point> dix, ix, wpoints;
+ ix = pig.intersectionPoints();
+ dix = pig.intersectionPoints(true);
+ wpoints = pig.windingPoints();
+ PathVector result, f_in, f_out;
+
+ if (pig.valid()) {
+ if (togs[0].on && !togs[1].on && !togs[2].on) {
+ result = pig.getAminusB();
+ }
+ if (!togs[0].on && togs[1].on && !togs[2].on) {
+ result = pig.getIntersection();
+ }
+ if (!togs[0].on && !togs[1].on && togs[2].on) {
+ result = pig.getBminusA();
+ }
+ if (togs[0].on && togs[1].on && !togs[2].on) {
+ result = ast;
+ }
+ if (togs[0].on && !togs[1].on && togs[2].on) {
+ result = pig.getXOR();
+ }
+ if (!togs[0].on && togs[1].on && togs[2].on) {
+ result = bst;
+ }
+ if (togs[0].on && togs[1].on && togs[2].on) {
+ result = pig.getUnion();
+ }
+ }
+
+ if (togs[5].on || togs[6].on) {
+ pig.fragments(f_in, f_out);
+ }
+ Timer::Time boolop_time = tm.lap();
+
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+
+ cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
+ cairo_path(cr, result);
+ cairo_fill(cr);
+
+ cairo_set_line_width(cr, 1);
+ cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
+ cairo_path(cr, ast);
+
+ cairo_stroke(cr);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_path(cr, bst);
+ cairo_stroke(cr);
+
+ if (togs[5].on) {
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ cairo_path(cr, f_in);
+ cairo_stroke(cr);
+ }
+ if (togs[6].on) {
+ cairo_set_source_rgb(cr, 0, 0, 1);
+ cairo_path(cr, f_out);
+ cairo_stroke(cr);
+ }
+
+ //cairo_set_line_width(cr, 1);
+
+ if (togs[7].on) {
+ cairo_set_source_rgb(cr, 0, 1, 1);
+ for (auto & wpoint : wpoints) {
+ draw_handle(cr, wpoint);
+ }
+ cairo_stroke(cr);
+ }
+
+ if (togs[3].on) {
+ cairo_set_source_rgb(cr, 0, 1, 0);
+ for (auto & i : ix) {
+ draw_handle(cr, i);
+ }
+ cairo_stroke(cr);
+ }
+
+ if (togs[4].on) {
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : dix) {
+ draw_handle(cr, i);
+ }
+ cairo_stroke(cr);
+ }
+
+
+ double x = width - 90, y = height - 40, y2 = height - 80;
+ Point p(x, y), p2(x, y2), dpoint(25,25), xo(25,0);
+ togs[0].bounds = Rect(p, p + dpoint);
+ togs[1].bounds = Rect(p + xo, p + xo + dpoint);
+ togs[2].bounds = Rect(p + 2*xo, p + 2*xo + dpoint);
+
+ togs[3].bounds = Rect(p2 - 2*xo, p2 - 2*xo + dpoint);
+ togs[4].bounds = Rect(p2 - xo, p2 - xo + dpoint);
+ togs[5].bounds = Rect(p2, p2 + dpoint);
+ togs[6].bounds = Rect(p2 + xo, p2 + xo + dpoint);
+ togs[7].bounds = Rect(p2 + 2*xo, p2 + 2*xo + dpoint);
+ draw_toggles(cr, togs);
+
+ *notify << ix.size() << " intersections";
+ if (dix.size() != 0) {
+ *notify << " + " << dix.size() << " defective";
+ }
+ if (pig.valid()) {
+ *notify << "\nboolop time: " << boolop_time << std::endl;
+ } else {
+ *notify << "\nboolop failed, time: " << boolop_time << std::endl;
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(togs, e);
+ Toy::mouse_pressed(e);
+ }
+
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="svgd/winding.svgd";
+ const char *path_b_name="svgd/star.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ if(argc > 2)
+ path_b_name = argv[2];
+
+ as = read_svgd(path_a_name);
+ bs = read_svgd(path_b_name);
+
+ OptRect abox = as.boundsExact();
+ OptRect bbox = bs.boundsExact();
+
+ if (!abox) {
+ std::clog << "Error: path A is empty" << std::endl;
+ }
+ if (!bbox) {
+ std::clog << "Error: path B is empty" << std::endl;
+ }
+ if (!abox || !bbox) {
+ std::exit(1);
+ }
+
+ std::vector<Point> anodes = as.nodes();
+ std::vector<Point> bnodes = bs.nodes();
+
+ typedef std::vector<Point>::iterator Iter;
+ std::pair<Iter, Iter> apts =
+ std::minmax_element(anodes.begin(), anodes.end(), Point::LexLess<Y>());
+ std::pair<Iter, Iter> bpts =
+ std::minmax_element(bnodes.begin(), bnodes.end(), Point::LexLess<Y>());
+
+ ah = Line(*apts.first, *apts.second);
+ bh = Line(*bpts.first, *bpts.second);
+
+ togs.emplace_back("R", true);
+ togs.emplace_back("&", false);
+ togs.emplace_back("B", false);
+
+ togs.emplace_back("X", true);
+ togs.emplace_back("D", true);
+ togs.emplace_back("I", false);
+ togs.emplace_back("O", false);
+ togs.emplace_back("W", false);
+ }
+
+ void setup_path_handles(Rect const &viewport) {
+ Line aht = ah * as.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID));
+ Line bht = bh * bs.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID));
+
+ path_handles[0] = PointHandle(aht.initialPoint());
+ path_handles[1] = PointHandle(aht.finalPoint());
+ path_handles[2] = PointHandle(bht.initialPoint());
+ path_handles[3] = PointHandle(bht.finalPoint());
+
+ for (auto & path_handle : path_handles) {
+ handles.push_back(&path_handle);
+ }
+ path_handles_inited = true;
+ }
+ //virtual bool should_draw_numbers() {return false;}
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new BoolOps());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/bound-path.cpp b/src/toys/bound-path.cpp
new file mode 100644
index 0000000..39ce2a1
--- /dev/null
+++ b/src/toys/bound-path.cpp
@@ -0,0 +1,289 @@
+/*
+ * Bounds Path and PathVector
+ *
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+std::string option_formatter(double x)
+{
+ if (x == 0.0)
+ return std::string("CURVE");
+ if (x == 1.0)
+ return std::string("PATH");
+ if (x == 2.0)
+ return std::string("PATHVECTOR");
+
+ return std::string("");
+}
+
+class BoundsPath : public Toy
+{
+ enum { CURVE = 0, PATH, PATHVECTOR };
+ enum { FAST = 0, EXACT = 1 };
+
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.3);
+ m_selection_kind = (unsigned int) (sliders[0].value());
+
+ for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i)
+ {
+ cairo_set_source_rgba(cr, 0.0, 0.4*(i+1), 0.8/(i+1), 1.0);
+ for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j)
+ {
+ m_pathvector_coll[i][j].clear();
+ for (unsigned int k = 0; k < m_curves_per_path; ++k)
+ {
+ PointSetHandle psh;
+ psh.pts.resize(m_handles_per_curve);
+ for (unsigned int h = 0; h < m_handles_per_curve; ++h)
+ {
+ unsigned int kk = k * (m_handles_per_curve-1) + h;
+ psh.pts[h] = m_pathvector_coll_handles[i][j].pts[kk];
+ }
+ m_pathvector_coll[i][j].append(psh.asBezier());
+ }
+ cairo_path(cr, m_pathvector_coll[i][j]);
+ }
+ cairo_stroke(cr);
+ }
+
+
+ Rect bound;
+ if ( (m_selection_kind == CURVE) && (m_selected_curve != -1) )
+ {
+ const Curve & curve = m_pathvector_coll[m_selected_pathvector][m_selected_path][m_selected_curve];
+ bound = toggles[0].on ? curve.boundsExact()
+ : curve.boundsFast();
+ }
+ else if ( (m_selection_kind == PATH) && (m_selected_path != -1) )
+ {
+ const Path & path = m_pathvector_coll[m_selected_pathvector][m_selected_path];
+ bound = toggles[0].on ? *path.boundsExact()
+ : *path.boundsFast();
+ }
+ else if ( (m_selection_kind == PATHVECTOR) && (m_selected_pathvector != -1) )
+ {
+ const PathVector & pathvector = m_pathvector_coll[m_selected_pathvector];
+ bound = toggles[0].on ? *bounds_exact(pathvector)
+ : *bounds_fast(pathvector);
+ }
+
+ cairo_set_source_rgba(cr, 0.5, 0.0, 0.0, 1.0);
+ cairo_set_line_width (cr, 0.4);
+ cairo_rectangle(cr, bound.left(), bound.top(), bound.width(), bound.height());
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+ void mouse_pressed(GdkEventButton* e) override
+ {
+ Point pos(e->x, e->y);
+ double d, t;
+ double dist = 1e10;
+ Rect bound;
+ m_selected_pathvector = -1;
+ m_selected_path = -1;
+ m_selected_curve = -1;
+ if (m_selection_kind == CURVE)
+ {
+ for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i)
+ {
+ for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j)
+ {
+ for ( unsigned int k = 0; k < m_pathvector_coll[i][j].size(); ++k)
+ {
+ const Curve & curve = m_pathvector_coll[i][j][k];
+ bound = toggles[0].on ? curve.boundsExact()
+ : curve.boundsFast();
+ d = distanceSq(pos, bound);
+ if ( are_near(d, 0) )
+ {
+ t = curve.nearestTime(pos);
+ d = distanceSq(pos, curve.pointAt(t));
+ if (d < dist)
+ {
+ dist = d;
+ m_selected_pathvector = i;
+ m_selected_path = j;
+ m_selected_curve = k;
+ }
+ }
+ }
+ }
+ }
+ //std::cerr << "m_selected_path = " << m_selected_path << std::endl;
+ //std::cerr << "m_selected_curve = " << m_selected_curve << std::endl;
+ }
+ else if (m_selection_kind == PATH)
+ {
+ for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i)
+ {
+ for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j)
+ {
+ const Path & path = m_pathvector_coll[i][j];
+ bound = toggles[0].on ? *path.boundsExact()
+ : *path.boundsFast();
+ d = distanceSq(pos, bound);
+ if ( are_near(d, 0) )
+ {
+ t = path.nearestTime(pos).asFlatTime();
+ d = distanceSq(pos, path.pointAt(t));
+ if (d < dist)
+ {
+ dist = d;
+ m_selected_pathvector = i;
+ m_selected_path = j;
+ }
+ }
+ }
+ }
+ }
+ else if (m_selection_kind == PATHVECTOR)
+ {
+ for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i)
+ {
+ const PathVector & pathvector = m_pathvector_coll[i];
+ bound = toggles[0].on ? *bounds_exact(pathvector)
+ : *bounds_fast(pathvector);
+ d = distanceSq(pos, bound);
+ if ( are_near(d, 0) )
+ {
+ for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j)
+ {
+ const Path & path = m_pathvector_coll[i][j];
+ t = path.nearestTime(pos).asFlatTime();
+ d = distanceSq(pos, path.pointAt(t));
+ if (d < dist)
+ {
+ dist = d;
+ m_selected_pathvector = i;
+ }
+ }
+ }
+ }
+ }
+
+ Toy::mouse_pressed(e);
+ }
+
+ public:
+ BoundsPath()
+ {
+ m_total_pathvectors = 2;
+ m_paths_per_vector = 2;
+ m_curves_per_path = 3;
+ m_handles_per_curve = 4;
+
+ m_selection_kind = CURVE;
+ m_selected_pathvector = -1;
+ m_selected_path = -1;
+ m_selected_curve = -1;
+ m_handles_per_path = m_curves_per_path * (m_handles_per_curve-1) + 1;
+
+ m_pathvector_coll_handles.resize(m_total_pathvectors);
+ m_pathvector_coll.resize(m_total_pathvectors);
+ for (unsigned int k = 0; k < m_total_pathvectors; ++k)
+ {
+ m_pathvector_coll_handles[k].resize(m_paths_per_vector);
+ m_pathvector_coll[k].resize(m_paths_per_vector);
+ for (unsigned int j = 0; j < m_paths_per_vector; ++j)
+ {
+ m_pathvector_coll_handles[k][j].pts.resize(m_handles_per_path);
+ handles.push_back(&(m_pathvector_coll_handles[k][j]));
+ for (unsigned int i = 0; i < m_handles_per_path; ++i)
+ {
+ m_pathvector_coll_handles[k][j].pts[i]
+ = Point(500*uniform() + 300*k, 300*uniform() + 80 + 200*j);
+ }
+ }
+ }
+
+ sliders.emplace_back(0, 2, 1, 0, "selection type");
+ sliders[0].geometry(Point(10, 20), 50, X);
+ sliders[0].formatter(&option_formatter);
+
+ Rect toggle_bound(Point(300,20), Point(390, 45));
+ toggles.emplace_back(toggle_bound, "fast/exact", EXACT);
+
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(toggles[0]));
+ }
+
+
+ private:
+ unsigned int m_total_pathvectors;
+ unsigned int m_paths_per_vector;
+ unsigned int m_curves_per_path;
+ unsigned int m_handles_per_curve;
+ unsigned int m_handles_per_path;
+ std::vector<PathVector> m_pathvector_coll;
+ std::vector< std::vector<PointSetHandle> > m_pathvector_coll_handles;
+// PathVector m_pathvector;
+// std::vector<PointSetHandle> m_pathvector_handles;
+ int m_selected_curve;
+ int m_selected_path;
+ int m_selected_pathvector;
+ unsigned int m_selection_kind;
+ std::vector<Slider> sliders;
+ std::vector<Toggle> toggles;
+};
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new BoundsPath(), 800, 600 );
+ 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/src/toys/bounds-test.cpp b/src/toys/bounds-test.cpp
new file mode 100644
index 0000000..f600162
--- /dev/null
+++ b/src/toys/bounds-test.cpp
@@ -0,0 +1,171 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*(-vscale);
+ plot[1]+=300;
+ cairo_d2_sb(cr, plot);
+ cairo_stroke(cr);
+}
+static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){
+ cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300));
+ cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300));
+ cairo_stroke(cr);
+}
+
+class BoundsTester: public Toy {
+ unsigned size;
+ PointSetHandle hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ for (unsigned i=0;i<size;i++){
+ hand.pts[i ][0]=150+15*(i-size);
+ hand.pts[i+size][0]=450+15*(i+1);
+ cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450));
+ cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450));
+ }
+ cairo_move_to(cr, Geom::Point(0,300));
+ cairo_line_to(cr, Geom::Point(600,300));
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ SBasis B(size, Linear());
+ for (unsigned i=0;i<size;i++){
+ B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i),
+ -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) );
+ }
+ B.normalize();
+ plot(cr,B,1);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ cairo_stroke(cr);
+
+ Interval bnds = *bounds_local(B,Interval(0.,.5));
+ plot_bar(cr,bnds.min(),1,.0,.5);
+ plot_bar(cr,bnds.max(),1,.0,.5);
+ cairo_set_source_rgba (cr, 0.4, 0., 0., 1);
+ cairo_stroke(cr);
+ bnds = *bounds_exact(B);
+ plot_bar(cr,bnds.min());
+ plot_bar(cr,bnds.max());
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1);
+ cairo_stroke(cr);
+
+/*
+This is a multi-root test...
+*/
+ hand.pts[2*size ][0]=150;
+ hand.pts[2*size+1][0]=150;
+ hand.pts[2*size+2][0]=150;
+ hand.pts[2*size ][1]=std::max(hand.pts[2*size ][1],hand.pts[2*size+1][1]);
+ hand.pts[2*size+1][1]=std::max(hand.pts[2*size+1][1],hand.pts[2*size+2][1]);
+ vector<double> levels;
+ levels.push_back((300-hand.pts[2*size ][1]));
+ levels.push_back((300-hand.pts[2*size+1][1]));
+ levels.push_back((300-hand.pts[2*size+2][1]));
+ for (double level : levels) plot_bar(cr,level);
+
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+
+ *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl;
+
+ vector<double>my_roots;
+
+// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1);
+// for (unsigned i=0;i<levels.size();i++){
+// my_roots.clear();
+// my_roots=roots(B-Linear(levels[i]));
+// for(unsigned j=0;j<my_roots.size();j++){
+// draw_cross(cr,Point(150+300*my_roots[j],300-levels[i]));
+// }
+// }
+
+// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1);
+
+ vector<vector<double> > sols=multi_roots(B,levels,.001,.001);
+ //map<double,unsigned> sols=multi_roots(B,levels);
+ //for(map<double,unsigned>::iterator sol=sols.begin();sol!=sols.end();sol++){
+ // draw_handle(cr,Point(150+300*(*sol).first,300-levels[(*sol).second]));
+ //}
+
+ for (unsigned i=0;i<sols.size();i++){
+ for (unsigned j=0;j<sols[i].size();j++){
+ draw_handle(cr,Point(150+300*sols[i][j],300-levels[i]));
+ }
+ }
+ cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1);
+
+/*
+
+ clock_t end_t;
+ unsigned iterations = 0;
+
+ my_roots.clear();
+ end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC);
+ iterations = 0;
+ while(end_t > clock()) {
+ my_roots.clear();
+ for (unsigned i=0;i<levels.size();i++){
+ my_roots=roots(B-Linear(levels[i]));
+ }
+ iterations++;
+ }
+ *notify << 1000*0.1/iterations <<" ms = roots time"<< std::endl;
+
+ sols.clear();
+ end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC);
+ iterations = 0;
+ while(end_t > clock()) {
+ sols.clear();
+ sols=multi_roots(B,levels);
+ iterations++;
+ }
+ *notify << 1000*0.1/iterations <<" ms = multi roots time"<< std::endl;
+*/
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ BoundsTester(){
+ size=5;
+ if(hand.pts.empty()) {
+ for(unsigned i = 0; i < 2*size; i++)
+ hand.pts.emplace_back(0,150+150+uniform()*300*0);
+ }
+ hand.pts.emplace_back(150,300+ 50+uniform()*100);
+ hand.pts.emplace_back(150,300- 50+uniform()*100);
+ hand.pts.emplace_back(150,300-150+uniform()*100);
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new BoundsTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/box3d.cpp b/src/toys/box3d.cpp
new file mode 100644
index 0000000..06d2e6a
--- /dev/null
+++ b/src/toys/box3d.cpp
@@ -0,0 +1,153 @@
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+class Box3d: public Toy {
+ Point orig;
+
+ double tmat[3][4];
+ double c[8][4];
+ Point corners[8];
+
+ PointHandle origin_handle;
+ PointSetHandle vanishing_points_handles, axes_handles;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ Geom::Point dir(1,-2);
+
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+
+ // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions
+ draw_slider_lines (cr);
+ axes_handles.pts[0][0] = 30;
+ axes_handles.pts[1][0] = 45;
+ axes_handles.pts[2][0] = 60;
+
+ /* create the transformation matrix for the map P^3 --> P^2 that has the following effect:
+ (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0)
+ (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1)
+ (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2)
+ (0 : 0 : 0 : 1) --> origin (= handle #3)
+ */
+ for (int j = 0; j < 4; ++j) {
+ tmat[0][j] = vanishing_points_handles.pts[j][0];
+ tmat[1][j] = vanishing_points_handles.pts[j][1];
+ tmat[2][j] = 1;
+ }
+
+ *notify << "Projection matrix:" << endl;
+ for (auto & i : tmat) {
+ for (double j : i) {
+ *notify << j << " ";
+ }
+ *notify << endl;
+ }
+
+ // draw the projective images of the box's corners
+ for (int i = 0; i < 8; ++i) {
+ corners[i] = proj_image (cr, c[i]);
+ }
+ draw_box(cr, corners);
+ cairo_set_line_width (cr, 2);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void first_time(int /*argc*/, char** /*argv*/) override {
+ // Finite images of the three vanishing points and the origin
+ handles.push_back(&origin_handle);
+ handles.push_back(&vanishing_points_handles);
+ handles.push_back(&axes_handles);
+ vanishing_points_handles.push_back(550,350);
+ vanishing_points_handles.push_back(150,300);
+ vanishing_points_handles.push_back(380,40);
+ vanishing_points_handles.push_back(340,450);
+ // plane origin
+ origin_handle.pos = Point(180,65);
+
+ // Handles for moving in axes directions
+ axes_handles.push_back(30,300);
+ axes_handles.push_back(45,300);
+ axes_handles.push_back(60,300);
+
+ // Box corners
+ for (int i = 0; i < 8; ++i) {
+ c[i][0] = ((i & 1) ? 1 : 0);
+ c[i][1] = ((i & 2) ? 1 : 0);
+ c[i][2] = ((i & 4) ? 1 : 0);
+ c[i][3] = 1;
+ }
+
+ orig = origin_handle.pos;
+ }
+ Geom::Point proj_image (cairo_t *cr, const double pt[4]) {
+ double res[3];
+ for (int j = 0; j < 3; ++j) {
+ res[j] =
+ tmat[j][0] * (pt[0] - (axes_handles.pts[0][1]-300)/100)
+ + tmat[j][1] * (pt[1] - (axes_handles.pts[1][1]-300)/100)
+ + tmat[j][2] * (pt[2] - (axes_handles.pts[2][1]-300)/100)
+ + tmat[j][3] * pt[3];
+ }
+ if (fabs (res[2]) > 0.000001) {
+ Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]);
+ draw_handle(cr, result);
+ return result;
+ }
+ assert(0); // unclipped point
+ return Geom::Point(0,0);
+ }
+
+ void draw_box (cairo_t *cr, Geom::Point corners[8]) {
+ cairo_move_to(cr,corners[0]);
+ cairo_line_to(cr,corners[1]);
+ cairo_line_to(cr,corners[3]);
+ cairo_line_to(cr,corners[2]);
+ cairo_close_path(cr);
+
+ cairo_move_to(cr,corners[4]);
+ cairo_line_to(cr,corners[5]);
+ cairo_line_to(cr,corners[7]);
+ cairo_line_to(cr,corners[6]);
+ cairo_close_path(cr);
+
+ for(int i = 0 ; i < 4; i++) {
+ cairo_move_to(cr,corners[i]);
+ cairo_line_to(cr,corners[i+4]);
+ }
+ }
+
+ void draw_slider_lines (cairo_t *cr) {
+ cairo_move_to(cr, Geom::Point(20,300));
+ cairo_line_to(cr, Geom::Point(70,300));
+
+ for(int i = 0; i < 3; i++) {
+ cairo_move_to(cr, Geom::Point(30 + 15*i,00));
+ cairo_line_to(cr, Geom::Point(30 + 15*i,450));
+ }
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Box3d);
+ 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/src/toys/center-warp.cpp b/src/toys/center-warp.cpp
new file mode 100644
index 0000000..4c0f750
--- /dev/null
+++ b/src/toys/center-warp.cpp
@@ -0,0 +1,113 @@
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/d2.h>
+#include <2geom/path.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/transforms.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+class CentreWarp: public Toy {
+ Path path_a;
+ D2<SBasis2d> sb2;
+ Piecewise<D2<SBasis> > path_a_pw;
+ PointHandle brush_handle;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ Geom::Point dir(1,-2);
+
+ D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw);
+
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+
+ if(0) {
+ D2<Piecewise<SBasis> > tB(cos(B[0]*0.1)*(brush_handle.pos[0]/100) + B[0],
+ cos(B[1]*0.1)*(brush_handle.pos[1]/100) + B[1]);
+
+ cairo_d2_pw_sb(cr, tB);
+ } else {
+ Piecewise<SBasis> r2 = (dot(path_a_pw - brush_handle.pos, path_a_pw - brush_handle.pos));
+ Piecewise<SBasis> rc;
+ rc.push_cut(0);
+ rc.push(SBasis(Linear(1, 1)), 2);
+ rc.push(SBasis(Linear(1, 0)), 4);
+ rc.push(SBasis(Linear(0, 0)), 30);
+ rc *= 10;
+ rc.scaleDomain(1000);
+ Piecewise<SBasis> swr;
+ swr.push_cut(0);
+ swr.push(SBasis(Linear(0, 1)), 2);
+ swr.push(SBasis(Linear(1, 0)), 4);
+ swr.push(SBasis(Linear(0, 0)), 30);
+ swr *= 10;
+ swr.scaleDomain(1000);
+ cairo_pw(cr, swr);// + (height - 100));
+ D2<Piecewise<SBasis> > uB = make_cuts_independent(unitVector(path_a_pw - brush_handle.pos));
+
+ D2<Piecewise<SBasis> > tB(compose(rc, (r2))*uB[0] + B[0],
+ compose(rc, (r2))*uB[1] + B[1]);
+ cairo_d2_pw_sb(cr, tB);
+ //path_a_pw = sectionize(tB);
+ }
+ cairo_stroke(cr);
+
+ *notify << path_a_pw.size();
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="star.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ PathVector paths_a = read_svgd(path_a_name);
+ assert(!paths_a.empty());
+ path_a = paths_a[0];
+
+ path_a.close(true);
+ path_a_pw = path_a.toPwSb();
+ for(unsigned dim = 0; dim < 2; dim++) {
+ sb2[dim].us = 2;
+ sb2[dim].vs = 2;
+ const int depth = sb2[dim].us*sb2[dim].vs;
+ sb2[dim].resize(depth, Linear2d(0));
+ }
+
+ handles.push_back(&brush_handle);
+ brush_handle.pos = Point(100,100);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CentreWarp);
+ 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/src/toys/circle-fitting.cpp b/src/toys/circle-fitting.cpp
new file mode 100644
index 0000000..d51b1cf
--- /dev/null
+++ b/src/toys/circle-fitting.cpp
@@ -0,0 +1,164 @@
+/*
+ * Circle and Elliptical Arc Fitting Example
+ *
+ * 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 <memory>
+#include <2geom/circle.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+class CircleFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ if (first_time)
+ {
+ first_time = false;
+ Point toggle_sp( 300, height - 50);
+ toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) );
+ sliders[0].geometry(Point(50, height - 50), 100);
+ }
+
+ size_t n = (size_t)(sliders[0].value()) + 3;
+ if (n < psh.pts.size())
+ {
+ psh.pts.resize(n);
+ }
+ else if (n > psh.pts.size())
+ {
+ psh.push_back(400*uniform()+50, 300*uniform()+50);
+ }
+
+ try
+ {
+ c.fit(psh.pts);
+ }
+ catch(RangeError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+
+ if (toggles[0].on)
+ {
+ try
+ {
+ std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[2]) );
+ ea = *eap;
+ }
+ catch(RangeError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ }
+
+ std::cerr << "center = " << c.center() << " ray = " << c.radius() << std::endl;
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ if (!toggles[0].on)
+ {
+ cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI);
+ }
+ else
+ {
+ draw_text(cr, psh.pts[0], "initial");
+ draw_text(cr, psh.pts[1], "inner");
+ draw_text(cr, psh.pts[2], "final");
+
+ D2<SBasis> easb = ea.toSBasis();
+ cairo_d2_sb(cr, easb);
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleFitting()
+ {
+ first_time = true;
+
+ psh.pts.resize(3);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(250, 100);
+ psh.pts[2] = Point(250, 400);
+
+
+ toggles.emplace_back(" arc / circle ", false);
+ sliders.emplace_back(0, 5, 1, 0, "more handles");
+
+ handles.push_back(&psh);
+ handles.push_back(&(toggles[0]));
+ handles.push_back(&(sliders[0]));
+ }
+
+ private:
+ Circle c;
+ EllipticalArc ea;
+ bool first_time;
+ PointSetHandle psh;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new CircleFitting(), 600, 600 );
+ 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/src/toys/circle-intersect.cpp b/src/toys/circle-intersect.cpp
new file mode 100644
index 0000000..d327a24
--- /dev/null
+++ b/src/toys/circle-intersect.cpp
@@ -0,0 +1,70 @@
+#include <toys/toy-framework-2.h>
+#include <2geom/circle.h>
+
+using namespace Geom;
+
+class CircleIntersect : public Toy {
+ PointSetHandle psh[2];
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ double r1 = Geom::distance(psh[0].pts[0], psh[0].pts[1]);
+ double r2 = Geom::distance(psh[1].pts[0], psh[1].pts[1]);
+
+ Circle c1(psh[0].pts[0], r1);
+ Circle c2(psh[1].pts[0], r2);
+
+ std::vector<ShapeIntersection> result = c1.intersect(c2);
+
+ cairo_set_line_width(cr, 1.0);
+
+ // draw the circles
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_arc(cr, c1.center(X), c1.center(Y), c1.radius(), 0, 2*M_PI);
+ cairo_stroke(cr);
+ cairo_arc(cr, c2.center(X), c2.center(Y), c2.radius(), 0, 2*M_PI);
+ cairo_stroke(cr);
+
+ // draw intersection points
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : result) {
+ draw_handle(cr, i.point());
+ }
+ cairo_stroke(cr);
+
+ // show message
+ if (c1.contains(c2)) {
+ *notify << "Containment";
+ } else if (!result.empty()) {
+ for (auto & i : result) {
+ *notify << i.point() << " ";
+ }
+ } else {
+ *notify << "No intersection";
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleIntersect(){
+ psh[0].push_back(200,200); psh[0].push_back(250,200);
+ psh[1].push_back(150,150); psh[1].push_back(250,150);
+ handles.push_back(&psh[0]);
+ handles.push_back(&psh[1]);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CircleIntersect());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/circle-line-intersect.cpp b/src/toys/circle-line-intersect.cpp
new file mode 100644
index 0000000..3946ca1
--- /dev/null
+++ b/src/toys/circle-line-intersect.cpp
@@ -0,0 +1,67 @@
+#include <2geom/circle.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class CircleIntersect : public Toy {
+ PointSetHandle psh[2];
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ Rect all(Point(0, 0), Point(width, height));
+ double r = Geom::distance(psh[0].pts[0], psh[0].pts[1]);
+
+ Circle circ(psh[0].pts[0], r);
+ Line line(psh[1].pts[0], psh[1].pts[1]);
+
+ std::vector<ShapeIntersection> result = circ.intersect(line);
+
+ cairo_set_line_width(cr, 1.0);
+
+ // draw the shapes
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_arc(cr, circ.center(X), circ.center(Y), circ.radius(), 0, 2*M_PI);
+ draw_line(cr, line, all);
+ cairo_stroke(cr);
+
+ // draw intersection points
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : result) {
+ draw_handle(cr, i.point());
+ }
+ cairo_stroke(cr);
+
+ // show message
+ if (!result.empty()) {
+ for (auto & i : result) {
+ *notify << i.point() << ", ";
+ }
+ } else {
+ *notify << "No intersection";
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleIntersect(){
+ psh[0].push_back(200,200); psh[0].push_back(250,200);
+ psh[1].push_back(150,150); psh[1].push_back(250,150);
+ handles.push_back(&psh[0]);
+ handles.push_back(&psh[1]);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CircleIntersect());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/circle-tangent-fitting.cpp b/src/toys/circle-tangent-fitting.cpp
new file mode 100644
index 0000000..f0d74ee
--- /dev/null
+++ b/src/toys/circle-tangent-fitting.cpp
@@ -0,0 +1,224 @@
+/*
+ * Circle and Elliptical Arc Fitting Example
+ *
+ * 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 <memory>
+#include <2geom/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/circle.h>
+#include <2geom/elliptical-arc.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+
+class LFMCircleTangentEquation
+ : public NL::LinearFittingModelWithFixedTerms<Point, double, Circle>
+{
+ public:
+ mutable unsigned count; // sigh
+ mutable Point pb;
+ vector<Point> bases;
+ void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const
+ {
+ if (count >= bases.size()) {
+ coeff[0] = p[X];
+ coeff[1] = p[Y];
+ coeff[2] = 1;
+ fixed_term = p[X] * p[X] + p[Y] * p[Y];
+ pb = p;
+ } else {
+ Point pb = bases[count];
+ coeff[0] = p[X];
+ coeff[1] = p[Y];
+ coeff[2] = 0;
+ fixed_term = 2*p[X]*pb[X] + 2*p[Y]*pb[Y];
+ count ++;
+ }
+ }
+
+ size_t size() const
+ {
+ return 3;
+ }
+ void instance(Circle & c, NL::Vector const& coeff) const
+ {
+ c.setCoefficients(1, coeff[0], coeff[1], coeff[2]);
+ }
+};
+
+
+class CircleFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ if (first_time)
+ {
+ first_time = false;
+ Point toggle_sp( 300, height - 50);
+ toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) );
+ sliders[0].geometry(Point(50, height - 50), 100);
+ }
+
+ try
+ {
+ vector<Point> points;
+ points.push_back(psh.pts[1]-psh.pts[0]); // tangents
+ //points.push_back(psh.pts[2]-psh.pts[3]);
+ points.push_back(psh.pts[3]);
+ points.push_back(psh.pts[0]);
+ size_t sz = points.size();
+ if (sz < 3)
+ {
+ THROW_RANGEERROR("fitting error: too few points passed");
+ }
+ LFMCircleTangentEquation model;
+ model.count = 0;
+ model.bases.push_back(psh.pts[0]);
+ //model.bases.push_back(psh.pts[3]);
+ NL::least_squeares_fitter<LFMCircleTangentEquation> fitter(model, sz);
+
+ for (size_t i = 0; i < sz; ++i)
+ {
+ fitter.append(points[i]);
+ }
+ fitter.update();
+
+ NL::Vector z(sz, 0.0);
+ model.instance(c, fitter.result(z));
+ //c.set(psh.pts);
+ }
+ catch(RangeError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+
+ if (toggles[0].on)
+ {
+ try
+ {
+ std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[3]) );
+ ea = *eap;
+ }
+ catch(RangeError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ }
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ cairo_move_to(cr, psh.pts[1]);
+ cairo_line_to(cr, 2*psh.pts[0] - psh.pts[1]);
+ cairo_move_to(cr, psh.pts[2]);
+ cairo_line_to(cr, 2*psh.pts[3] - psh.pts[2]);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ if (!toggles[0].on)
+ {
+ cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI);
+ }
+ else
+ {
+ draw_text(cr, psh.pts[0], "initial");
+ draw_text(cr, psh.pts[1], "inner");
+ draw_text(cr, psh.pts[2], "inner");
+ draw_text(cr, psh.pts[3], "final");
+
+ D2<SBasis> easb = ea.toSBasis();
+ cairo_d2_sb(cr, easb);
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleFitting()
+ {
+ first_time = true;
+
+ psh.pts.resize(4);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(450+100, 250+100);
+ psh.pts[2] = Point(250+100, 400+100);
+ psh.pts[3] = Point(250, 400);
+
+
+
+ toggles.emplace_back(" arc / circle ", false);
+ sliders.emplace_back(0, 5, 1, 0, "more handles");
+
+ handles.push_back(&psh);
+ handles.push_back(&(toggles[0]));
+ handles.push_back(&(sliders[0]));
+ }
+
+ private:
+ Circle c;
+ EllipticalArc ea;
+ bool first_time;
+ PointSetHandle psh;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new CircleFitting(), 600, 600 );
+ 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/src/toys/collinear-normal.cpp b/src/toys/collinear-normal.cpp
new file mode 100644
index 0000000..a5e1af5
--- /dev/null
+++ b/src/toys/collinear-normal.cpp
@@ -0,0 +1,204 @@
+/*
+ * Show off collinear normals between two Bezier curves.
+ * The intersection points are found by using Bezier clipping.
+ *
+ * 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/d2.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/ray.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+
+
+class CurveIntersect : public Toy
+{
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.8, 0., 0, 1);
+ D2<SBasis> A = pshA.asBezier();
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 0.0, 0., 0, 1);
+ D2<SBasis> B = pshB.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+ draw_text(cr, A.at0(), "A");
+ draw_text(cr, B.at0(), "B");
+
+ Timer tm;
+ tm.ask_for_timeslice();
+ tm.start();
+
+ find_collinear_normal(xs, pshA.pts, pshB.pts, m_precision);
+ Timer::Time als_time = tm.lap();
+ *timer_stream << "find_collinear_normal " << als_time << std::endl;
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1);
+ for (auto & x : xs)
+ {
+ Point At = A(x.first);
+ Point Bu = B(x.second);
+ draw_axis(cr, At, Bu);
+ draw_handle(cr, At);
+ draw_handle(cr, Bu);
+
+ }
+ cairo_stroke(cr);
+
+ double h_a_t = 0, h_b_t = 0;
+
+ double h_dist = hausdorfl( A, B, m_precision, &h_a_t, &h_b_t);
+ {
+ Point At = A(h_a_t);
+ Point Bu = B(h_b_t);
+ cairo_move_to(cr, At);
+ cairo_line_to(cr, Bu);
+ draw_handle(cr, At);
+ draw_handle(cr, Bu);
+ cairo_save(cr);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ /*h_dist = hausdorf( A, B, m_precision, &h_a_t, &h_b_t);
+ {
+ Point At = A(h_a_t);
+ Point Bu = B(h_b_t);
+ draw_axis(cr, At, Bu);
+ draw_handle(cr, At);
+ draw_handle(cr, Bu);
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.0, 0.7, 0.0, 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }*/
+ *notify << "Hausdorf distance = " << h_dist
+ << " occurring at " << h_a_t
+ << " B=" << h_b_t << std::endl;
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void draw_segment(cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+ }
+
+ void draw_segment(cairo_t* cr, Point const& p1, double angle, double length)
+ {
+ Point p2;
+ p2[X] = length * std::cos(angle);
+ p2[Y] = length * std::sin(angle);
+ p2 += p1;
+ draw_segment(cr, p1, p2);
+ }
+
+ void draw_ray(cairo_t* cr, Ray const& r)
+ {
+ double angle = r.angle();
+ draw_segment(cr, r.origin(), angle, m_length);
+ }
+
+ void draw_axis(cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ double d = Geom::distance(p1, p2);
+ d = d + d/4;
+ Point q1 = Ray(p1, p2).pointAt(d);
+ Point q2 = Ray(p2, p1).pointAt(d);
+ draw_segment(cr, q1, q2);
+ }
+
+public:
+ CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ handles.push_back(&pshA);
+ for (unsigned int i = 0; i <= A_bez_ord; ++i)
+ pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200));
+ handles.push_back(&pshB);
+ for (unsigned int i = 0; i <= B_bez_ord; ++i)
+ pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200));
+
+ m_precision = 1e-6;
+ }
+
+private:
+ unsigned int A_bez_ord, B_bez_ord;
+ PointSetHandle pshA, pshB, pshC;
+ std::vector< std::pair<double, double> > xs;
+ double m_precision;
+ double m_width, m_height, m_length;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord = 4;
+ unsigned int B_bez_ord = 6;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+
+
+ init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800);
+ 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/src/toys/conic-3.cpp b/src/toys/conic-3.cpp
new file mode 100644
index 0000000..64b2c27
--- /dev/null
+++ b/src/toys/conic-3.cpp
@@ -0,0 +1,96 @@
+/**
+ * elliptics via C-curves. (njh)
+ * Limited to 180 degrees (by end point and tangent matching criteria)
+ * Also represents cycloids
+ */
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+Linear z0(0.5,1.);
+
+unsigned total_pieces;
+
+double sinC(double t) { return t - sin(t);}
+double cosC(double t) { return 1 - cos(t);}
+double tanC(double t) { return sinC(t) / cosC(t);}
+
+class Conic3: public Toy {
+ PointSetHandle psh;
+ public:
+ Conic3 () {
+ psh.push_back(100, 500);
+ psh.push_back(100, 500 - 200*M_PI/2);
+ psh.push_back(500, 500 - 200*M_PI/2);
+ psh.push_back(500, 500);
+ handles.push_back(&psh);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
+ cairo_set_line_width (cr, 0.5);
+ cairo_stroke(cr);
+
+ Geom::Point a[2] = {psh.pts[0] - psh.pts[1],
+ psh.pts[2] - psh.pts[1]};
+ double angle = Geom::angle_between(a[0], a[1]);
+ double len = std::max(Geom::L2(a[0]),
+ Geom::L2(a[1]));
+ for(auto & i : a)
+ i = len*unit_vector(i);
+ *notify << "angle = " << angle;
+ *notify << " sinC = " << sinC(angle);
+ *notify << " cosC = " << cosC(angle);
+ *notify << " tanC = " << tanC(angle);
+ vector<Geom::Point> e_a_h = psh.pts;
+
+ double alpha = M_PI;
+ Piecewise<SBasis> pw(Linear(0, alpha));
+ Piecewise<SBasis> C = cos(pw);
+ Piecewise<SBasis> S = sin(pw);
+ Piecewise<SBasis> sinC = pw - S;
+ Piecewise<SBasis> cosC = Piecewise<SBasis>(1) - C;
+ Piecewise<SBasis> Z3 = sinC/sinC(1);
+ Piecewise<SBasis> Z0 = reverse(Z3);
+ Piecewise<SBasis> Z2 = cosC/cosC(1) - Z3;
+ Piecewise<SBasis> Z1 = reverse(Z2);
+
+ Piecewise<SBasis> Z[4] = {Z0, Z1, Z2, Z3};
+
+ D2<Piecewise<SBasis> > B;
+ for(unsigned dim = 0; dim < 2; dim++) {
+ B[dim] = Piecewise<SBasis>(0);
+ for(unsigned i = 0; i < 4; i++) {
+ B[dim] += Z[i]*e_a_h[i][dim];
+ }
+ }
+ cairo_d2_pw_sb(cr, B);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Conic3());
+
+ 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/src/toys/conic-4.cpp b/src/toys/conic-4.cpp
new file mode 100644
index 0000000..0dbecd6
--- /dev/null
+++ b/src/toys/conic-4.cpp
@@ -0,0 +1,129 @@
+/**
+ * elliptics via 5 point w-pi basis. (njh)
+ * Affine, endpoint, tangent, exact circle
+ * full circle. Convex containment implies small circle.
+ * Also represents lumpy polar type curves
+ */
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+const double w = 1./3;
+const double cwp = cos(w*M_PI);
+const double swp = sin(w*M_PI);
+/*double phi(double t, double w) { return sin(w*t) - w*sin(t); }
+double phih(double t, double w) { return sin(w*t) + w*sin(t); }
+double b4(double t, double w) {return phi(t/2,w)*phih(t/2,w)/(swp*swp);}
+double b3(double t, double w) {return cwp*phi(t,w)/(2*swp) - cwp*cwp*b4(t,w); }
+double b2(double t, double w) {return 2*w*w*sin(t/2)*sin(t/2);}
+double b1(double t, double w) {return b3(2*M_PI - t, w);}
+double b0(double t, double w) {return b4(2*M_PI - t, w);}*/
+
+class arc_basis{
+public:
+ Piecewise<SBasis> basis[5];
+ double w;
+
+ Piecewise<SBasis> phi(Piecewise<SBasis> const &d, double w) {
+ return sin(d*w) - sin(d)*w;
+ }
+ Piecewise<SBasis> phih(Piecewise<SBasis> const &d, double w) {
+ return sin(d*w) + sin(d)*w;
+ }
+ Piecewise<SBasis> b4(Piecewise<SBasis> const &d, double w) {
+ return phi(d*.5,w)/(swp*swp)*phih(d*.5,w);
+ }
+ Piecewise<SBasis> b3(Piecewise<SBasis> const &d, double w) {
+ return phi(d,w)*(cwp/(2*swp)) - b4(d,w)*(cwp*cwp);
+ }
+
+ Piecewise<SBasis> b2(Piecewise<SBasis> const &d, double w) {
+ return sin(d*.5)*(2*w*w)*sin(d*.5);
+ }
+ Piecewise<SBasis> b1(Piecewise<SBasis> const &d, double w) {
+ return b3(reverse(d), w);
+ }
+ Piecewise<SBasis> b0(Piecewise<SBasis> const &d, double w) {
+ return b4(reverse(d), w);
+ }
+
+
+ arc_basis(double w) {
+ Piecewise<SBasis> dom(Linear(0, 2*M_PI));
+ basis[0] = b4(dom, w);
+ basis[1] = b3(dom, w);
+ basis[2] = b2(dom, w);
+ basis[3] = b1(dom, w);
+ basis[4] = b0(dom, w);
+ }
+
+};
+
+class Conic4: public Toy {
+ PointSetHandle psh;
+ public:
+ Conic4 () {
+ double sc = 30;
+ Geom::Point c(6*sc, 6*sc);
+ psh.push_back(sc*Geom::Point(0,0)+c);
+ psh.push_back(sc*Geom::Point(tan(w*M_PI)/w, 0)+c);
+ psh.push_back(sc*Geom::Point(0, 1/(w*w))+c);
+ psh.push_back(sc*Geom::Point(-tan(w*M_PI)/w, 0)+c);
+ psh.push_back(sc*Geom::Point(0,0)+c);
+ handles.push_back(&psh);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ std::vector<Geom::Point> e_h = psh.pts;
+ for(int i = 0; i < 5; i++) {
+ Geom::Point p = e_h[i];
+
+ if(i)
+ cairo_line_to(cr, p);
+ else
+ cairo_move_to(cr, p);
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+
+ arc_basis ab(1./3);
+ D2<Piecewise<SBasis> > B;
+
+ for(unsigned dim = 0; dim < 2; dim++)
+ for(unsigned i = 0; i < 5; i++)
+ B[dim] += ab.basis[i]*e_h[i][dim];
+
+ cairo_d2_pw_sb(cr, B);
+ cairo_set_source_rgba (cr, 1., 0.5, 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Conic4());
+
+ 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/src/toys/conic-5.cpp b/src/toys/conic-5.cpp
new file mode 100644
index 0000000..51140c1
--- /dev/null
+++ b/src/toys/conic-5.cpp
@@ -0,0 +1,356 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/path-intersection.h>
+#include <2geom/nearest-time.h>
+#include <2geom/line.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+
+#include <cstdlib>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <optional>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/ord.h>
+
+
+
+#include <2geom/conicsec.h>
+
+using namespace Geom;
+using namespace std;
+
+
+// File: convert.h
+#include <sstream>
+#include <stdexcept>
+
+class BadConversion : public std::runtime_error {
+public:
+ BadConversion(const std::string& s)
+ : std::runtime_error(s)
+ { }
+};
+
+template <typename T>
+inline std::string stringify(T x)
+{
+ std::ostringstream o;
+ if (!(o << x))
+ throw BadConversion("stringify(T)");
+ return o.str();
+}
+
+void draw_hull(cairo_t*cr, RatQuad rq) {
+ cairo_move_to(cr, rq.P[0]);
+ cairo_line_to(cr, rq.P[1]);
+ cairo_line_to(cr, rq.P[2]);
+ cairo_stroke(cr);
+}
+
+
+
+void draw(cairo_t* cr, xAx C, Rect bnd) {
+ if(bnd[1].extent() < 5) return;
+ vector<double> prev_rts;
+ double py = bnd[Y].min();
+ for(int i = 0; i < 100; i++) {
+ double t = i/100.;
+ double y = bnd[Y].valueAt(t);
+ vector<double> rts = C.roots(Point(1, 0), Point(0, y));
+ int top = 0;
+ for(unsigned j = 0; j < rts.size(); j++) {
+ if(bnd[0].contains(rts[j])) {
+ rts[top] = rts[j];
+ top++;
+ }
+ }
+ rts.erase(rts.begin()+top, rts.end());
+
+ if(rts.size() == prev_rts.size()) {
+ for(unsigned j = 0; j < rts.size(); j++) {
+ cairo_move_to(cr, prev_rts[j], py);
+ cairo_line_to(cr, rts[j], y);
+ cairo_stroke(cr);
+ }
+ } else {
+ draw(cr, C, Rect(bnd[X], Interval(py, y)));
+ }
+ prev_rts = rts;
+ py = y;
+ }
+}
+
+template <typename T>
+static T det(T a, T b, T c, T d) {
+ return a*d - b*c;
+}
+
+template <typename T>
+static T det(T M[2][2]) {
+ return M[0][0]*M[1][1] - M[1][0]*M[0][1];
+}
+
+template <typename T>
+static T det3(T M[3][3]) {
+ return ( M[0][0] * det(M[1][1], M[1][2],
+ M[2][1], M[2][2])
+ -M[1][0] * det(M[0][1], M[0][2],
+ M[2][1], M[2][2])
+ +M[2][0] * det(M[0][1], M[0][2],
+ M[1][1], M[1][2]));
+}
+
+double xAx_descr(xAx const & C) {
+ double mC[3][3] = {{C.c[0], (C.c[1])/2, (C.c[3])/2},
+ {(C.c[1])/2, C.c[2], (C.c[4])/2},
+ {(C.c[3])/2, (C.c[4])/2, C.c[5]}};
+
+ return det3(mC);
+}
+
+void draw_ratquad(cairo_t*cr, RatQuad rq, double tol=1e-1) {
+ CubicBezier cb = rq.toCubic();
+ // I tried using the nearest point to 0.5 for the error, but the improvement was negligible
+ if(L2(cb.pointAt(0.5) - rq.pointAt(0.5)) > tol) {
+ RatQuad a, b;
+ rq.split(a, b);
+ draw_ratquad(cr, a, tol);
+ draw_ratquad(cr, b, tol);
+ } else {
+ cairo_curve(cr, cb);
+ //draw_cross(cr, cb.pointAt(0));
+ //draw_cross(cr, cb.pointAt(1));
+ }
+}
+
+
+class Conic5: public Toy {
+ PointSetHandle path_handles;
+ PointHandle oncurve;
+ PointSetHandle cutting_plane;
+ std::vector<Slider> sliders;
+ RectHandle rh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ if(0) {
+ Path path;
+ path = Path(path_handles.pts[0]);
+ D2<SBasis> c = handles_to_sbasis(path_handles.pts.begin(), 2);
+ path.append(c);
+
+ cairo_save(cr);
+ cairo_path(cr, path);
+ cairo_set_source_rgba (cr, 0., 1., 0, 0.3);
+ cairo_set_line_width (cr, 3);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ //double w = exp(sliders[0].value());
+ }
+ Point A = path_handles.pts[0];
+ Point B = path_handles.pts[1];
+ Point C = path_handles.pts[2];
+
+ if(1) {
+ QuadraticBezier qb(A, B, C);
+ //double abt = qb.nearestTime(oncurve.pos);
+ //oncurve.pos = qb.pointAt(abt);
+
+ RatQuad rq = RatQuad::fromPointsTangents(A, B-A, oncurve.pos, C, B -C); //( A, B, C, w);
+
+ cairo_save(cr);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ draw_ratquad(cr, rq);
+ //cairo_d2_sb(cr, rq.hermite());
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+
+ if(0) {
+ RatQuad rq = RatQuad::circularArc(A, B, C);
+
+ cairo_save(cr);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ RatQuad a, b;
+ rq.split(a,b);
+ cairo_curve(cr, a.toCubic());
+ cairo_curve(cr, b.toCubic());
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+
+ Rect screen_rect(Interval(10, width-10), Interval(10, height-10));
+ Line cutLine(cutting_plane.pts[0], cutting_plane.pts[1]);
+ //double dist;
+ //Point norm = cutLine.normalAndDist(dist);
+
+ const unsigned N = 3;
+ xAx sources[N] = {
+ xAx::fromPoint(A)*(exp(-sliders[0].value())),
+ xAx::fromPoint(B)*(exp(-sliders[1].value())),
+ xAx::fromPoint(C)*(exp(-sliders[2].value()))
+ //xAx::fromLine(Line(A, oncurve.pos))
+ };
+ for(unsigned i = 0; i < N; i++) {
+ //*notify << sources[i] << "\n";
+ }
+ for(unsigned i = 0; i < N; i++) {
+ for(unsigned j = i+1; j < N; j++) {
+ xAx Q = sources[i]-sources[j];
+ *notify << Q << " is a " << Q.categorise() << "\n";
+ }
+ }
+ {
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0, 0, 1, 0.5);
+
+ ::draw(cr, (sources[0]-sources[1]), screen_rect);
+ ::draw(cr, (sources[0]-sources[2]), screen_rect);
+ ::draw(cr, (sources[1]-sources[2]), screen_rect);
+ cairo_restore(cr);
+ }
+ {
+ string os;
+ for(unsigned i = 0; i < N; i++) {
+ for(unsigned j = i+1; j < N; j++) {
+ xAx Q = sources[i]-sources[j];
+ Interval iQ = Q.extrema(rh.pos);
+ if(iQ.contains(0)) {
+ os += stringify(iQ) + "\n";
+
+ Q.toCurve(rh.pos);
+ vector<Point> crs = Q.crossings(rh.pos);
+ for(auto & ei : crs) {
+ draw_cross(cr, ei);
+ }
+
+ }
+ }
+ }
+
+ draw_text(cr, rh.pos.midpoint(),
+ os);
+ }
+ if(1) {
+ xAx oxo=sources[0] - sources[2];
+ Timer tm;
+
+ tm.ask_for_timeslice();
+ tm.start();
+
+ std::vector<Point> intrs = intersect(oxo, sources[0] - sources[1]);
+ Timer::Time als_time = tm.lap();
+ *notify << "intersect time = " << als_time << std::endl;
+ for(auto & intr : intrs) {
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1, 0,0);
+ draw_cross(cr, intr);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+
+ std::optional<RatQuad> orq = oxo.toCurve(rh.pos);
+ if(orq) {
+ RatQuad rq = *orq;
+ draw_hull(cr, rq);
+ vector<SBasis> hrq = rq.homogeneous();
+ SBasis vertex_poly = (sources[0] - sources[1]).evaluate_at(hrq[0], hrq[1], hrq[2]);
+ //*notify << "\n0: " << hrq[0];
+ //*notify << "\n1: " << hrq[1];
+ //*notify << "\n2: " << hrq[2];
+ vector<double> rts = roots(vertex_poly);
+ //*notify << "\nvertex poly:" << vertex_poly << '\n';
+ for(unsigned i = 0; i < rts.size(); i++) {
+ draw_circ(cr, Point(rq.pointAt(rts[i])));
+ *notify << "\nrq" << i << ":" << rts[i];
+ }
+
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ RatQuad a, b;
+ rq.split(a,b);
+ cairo_curve(cr, a.toCubic());
+ cairo_curve(cr, b.toCubic());
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ }
+ if(0) {
+ RatQuad a, b;
+ //rq.split(a,b);
+ //cairo_move_to(cr, rq.toCubic().pointAt(0.5));
+ cairo_line_to(cr, a.P[2]);
+ cairo_stroke(cr);
+
+ cairo_curve(cr, a.toCubic());
+ cairo_curve(cr, b.toCubic());
+
+ }
+ cairo_stroke(cr);
+
+ //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n";
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+public:
+ Conic5() {
+ handles.push_back(&path_handles);
+ handles.push_back(&rh);
+ rh.pos = Rect(Point(100,100), Point(200,200));
+ rh.show_center_handle = true;
+ handles.push_back(&oncurve);
+ for(int j = 0; j < 3; j++){
+ path_handles.push_back(uniform()*400, 100+ uniform()*300);
+ }
+ oncurve.pos = ((path_handles.pts[0]+path_handles.pts[1]+path_handles.pts[2])/3);
+ handles.push_back(&cutting_plane);
+ for(int j = 0; j < 2; j++){
+ cutting_plane.push_back(uniform()*400, 100+ uniform()*300);
+ }
+ sliders.emplace_back(0.0, 5.0, 0, 0.0, "a");
+ sliders.emplace_back(0.0, 5.0, 0, 0.0, "b");
+ sliders.emplace_back(0.0, 5.0, 0, 0.0, "c");
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(sliders[1]));
+ handles.push_back(&(sliders[2]));
+ sliders[0].geometry(Point(50, 20), 250);
+ sliders[1].geometry(Point(50, 50), 250);
+ sliders[2].geometry(Point(50, 80), 250);
+ }
+
+ void first_time(int /*argc*/, char**/* argv*/) override {
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Conic5());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/conic-6.cpp b/src/toys/conic-6.cpp
new file mode 100644
index 0000000..90da671
--- /dev/null
+++ b/src/toys/conic-6.cpp
@@ -0,0 +1,304 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/path-intersection.h>
+#include <2geom/nearest-time.h>
+#include <2geom/line.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+
+#include <cstdlib>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <optional>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/ord.h>
+
+#include <2geom/conicsec.h>
+
+std::vector<Geom::RatQuad> xAx_to_RatQuads(Geom::xAx const &/*C*/, Geom::Rect const &/*bnd*/) {
+ // find points on boundary
+ // if there are exactly 0 points return
+ // if there are exactly 2 points fit ratquad and return
+ // if there are an odd number, split bnd on the point with the smallest dot(unit_vector(grad), rect_edge)
+ // sort into clockwise order ABCD
+ // compute corresponding tangents
+ // test boundary points against the line through A
+ // if all on one side
+ //
+ // if A,X and Y,Z
+ // ratquad from A,X and Y,Z
+ return std::vector<Geom::RatQuad>();
+}
+
+
+
+using namespace Geom;
+using namespace std;
+
+
+// File: convert.h
+#include <sstream>
+#include <stdexcept>
+
+class BadConversion : public std::runtime_error {
+public:
+ BadConversion(const std::string& s)
+ : std::runtime_error(s)
+ { }
+};
+
+template <typename T>
+inline std::string stringify(T x)
+{
+ std::ostringstream o;
+ if (!(o << x))
+ throw BadConversion("stringify(T)");
+ return o.str();
+}
+
+namespace Geom{
+xAx degen;
+};
+
+void draw_hull(cairo_t*cr, RatQuad rq) {
+ cairo_move_to(cr, rq.P[0]);
+ cairo_line_to(cr, rq.P[1]);
+ cairo_line_to(cr, rq.P[2]);
+ cairo_stroke(cr);
+}
+
+
+
+void draw(cairo_t* cr, xAx C, Rect bnd) {
+ if(bnd[1].extent() < 5) return;
+ vector<double> prev_rts;
+ double py = bnd[Y].min();
+ for(int i = 0; i < 100; i++) {
+ double t = i/100.;
+ double y = bnd[Y].valueAt(t);
+ vector<double> rts = C.roots(Point(1, 0), Point(0, y));
+ int top = 0;
+ for(unsigned j = 0; j < rts.size(); j++) {
+ if(bnd[0].contains(rts[j])) {
+ rts[top] = rts[j];
+ top++;
+ }
+ }
+ rts.erase(rts.begin()+top, rts.end());
+
+ if(rts.size() == prev_rts.size()) {
+ for(unsigned j = 0; j < rts.size(); j++) {
+ cairo_move_to(cr, prev_rts[j], py);
+ cairo_line_to(cr, rts[j], y);
+ cairo_stroke(cr);
+ }
+/* } else if(prev_rts.size() == 1) {
+ for(unsigned j = 0; j < rts.size(); j++) {
+ cairo_move_to(cr, prev_rts[0], py);
+ cairo_line_to(cr, rts[j], y);
+ cairo_stroke(cr);
+ }
+ } else if(rts.size() == 1) {
+ for(unsigned j = 0; j < prev_rts.size(); j++) {
+ cairo_move_to(cr, prev_rts[j], py);
+ cairo_line_to(cr, rts[0], y);
+ cairo_stroke(cr);
+ }*/
+ } else {
+ draw(cr, C, Rect(bnd[0], Interval(py, y)));
+ /*for(unsigned j = 0; j < rts.size(); j++) {
+ cairo_move_to(cr, rts[j], y);
+ cairo_rel_line_to(cr, 1,1);
+ }*/
+ }
+ prev_rts = rts;
+ py = y;
+ }
+}
+
+template <typename T>
+static T det(T a, T b, T c, T d) {
+ return a*d - b*c;
+}
+
+template <typename T>
+static T det(T M[2][2]) {
+ return M[0][0]*M[1][1] - M[1][0]*M[0][1];
+}
+
+template <typename T>
+static T det3(T M[3][3]) {
+ return ( M[0][0] * det(M[1][1], M[1][2],
+ M[2][1], M[2][2])
+ -M[1][0] * det(M[0][1], M[0][2],
+ M[2][1], M[2][2])
+ +M[2][0] * det(M[0][1], M[0][2],
+ M[1][1], M[1][2]));
+}
+
+class Conic6: public Toy {
+ PointSetHandle C1H, C2H;
+ std::vector<Slider> sliders;
+ Point mouse_sampler;
+
+ void mouse_moved(GdkEventMotion* e) override {
+ mouse_sampler = Point(e->x, e->y);
+ Toy::mouse_moved(e);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ Rect screen_rect(Interval(10, width-10), Interval(10, height-10));
+
+ Geom::xAx C1 = xAx::fromPoints(C1H.pts);
+ ::draw(cr, C1, screen_rect);
+ *notify << C1;
+
+ Geom::xAx C2 = xAx::fromPoints(C2H.pts);
+ ::draw(cr, C2, screen_rect);
+ *notify << C2;
+
+
+ SBasis T(Linear(-1,1));
+ SBasis S(Linear(1,1));
+ SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
+ {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
+ {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
+
+ SBasis D = det3(C);
+ std::vector<double> rts = Geom::roots(D);
+ if(rts.empty()) {
+ T = Linear(1,1);
+ S = Linear(-1,1);
+ SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
+ {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
+ {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
+
+ D = det3(C);
+ rts = Geom::roots(D);
+ }
+ // at this point we have a T and S and perhaps some roots that represent our degenerate conic
+ // Let's just pick one randomly (can we do better?)
+ //for(unsigned i = 0; i < rts.size(); i++) {
+ if(!rts.empty()) {
+ cairo_save(cr);
+
+ unsigned i = 0;
+ double t = T.valueAt(rts[i]);
+ double s = S.valueAt(rts[i]);
+ *notify << t << "; " << s << std::endl;
+ /*double C0[3][3] = {{t*C1.c[0]+s*C2.c[0], (t*C1.c[1]+s*C2.c[1])/2, (t*C1.c[3]+s*C2.c[3])/2},
+ {(t*C1.c[1]+s*C2.c[1])/2, t*C1.c[2]+s*C2.c[2], (t*C1.c[4]+s*C2.c[4])/2},
+ {(t*C1.c[3]+s*C2.c[3])/2, (t*C1.c[4]+s*C2.c[4])/2, t*C1.c[5]+s*C2.c[5]}};*/
+ xAx xC0 = C1*t + C2*s;
+ //::draw(cr, xC0, screen_rect); // degen
+
+ std::optional<Point> oB0 = xC0.bottom();
+
+ Point B0 = *oB0;
+ //*notify << B0 << " = " << C1.gradient(B0);
+ draw_circ(cr, B0);
+
+ Point n0, n1;
+ // Are these just the eigenvectors of A11?
+ if(fabs(xC0.c[0]) > fabs(xC0.c[2])) {
+ double b = 0.5*xC0.c[1]/xC0.c[0];
+ double c = xC0.c[2]/xC0.c[0];
+ double d = std::sqrt(b*b-c);
+ n0 = Point(1, b+d);
+ n1 = Point(1, b-d);
+ } else {
+
+ double b = 0.5*xC0.c[1]/xC0.c[2];
+ double c = xC0.c[0]/xC0.c[2];
+ double d = std::sqrt(b*b-c);
+ n0 = Point(b+d, 1);
+ n1 = Point(b-d, 1);
+ }
+ cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
+
+ Line L0 = Line::from_origin_and_vector(B0, rot90(n0));
+ draw_line(cr, L0, screen_rect);
+ Line L1 = Line::from_origin_and_vector(B0, rot90(n1));
+ draw_line(cr, L1, screen_rect);
+
+ cairo_set_source_rgb(cr, 1, 0., 0.);
+ rts = C1.roots(L0);
+ for(double rt : rts) {
+ Point P = L0.pointAt(rt);
+ draw_cross(cr, P);
+ *notify << C1.valueAt(P) << "; " << C2.valueAt(P) << "\n";
+ }
+ rts = C1.roots(L1);
+ for(double rt : rts) {
+ Point P = L1.pointAt(rt);
+ draw_cross(cr, P);
+ *notify << C1.valueAt(P) << "; "<< C2.valueAt(P) << "\n";
+ }
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+
+ ::draw(cr, C1*sliders[0].value() + C2*sliders[1].value(), screen_rect);
+
+ std::vector<Point> res = intersect(C1, C2);
+ for(auto & re : res) {
+ draw_circ(cr, re);
+ }
+
+ cairo_stroke(cr);
+
+ //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n";
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+public:
+ Conic6() {
+ for(int j = 0; j < 5; j++){
+ C1H.push_back(uniform()*400, 100+ uniform()*300);
+ C2H.push_back(uniform()*400, 100+ uniform()*300);
+ }
+ handles.push_back(&C1H);
+ handles.push_back(&C2H);
+ sliders.emplace_back(-1.0, 1.0, 0, 0.0, "a");
+ sliders.emplace_back(-1.0, 1.0, 0, 0.0, "b");
+ sliders.emplace_back(0.0, 5.0, 0, 0.0, "c");
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(sliders[1]));
+ handles.push_back(&(sliders[2]));
+ sliders[0].geometry(Point(50, 20), 250);
+ sliders[1].geometry(Point(50, 50), 250);
+ sliders[2].geometry(Point(50, 80), 250);
+ }
+
+ void first_time(int /*argc*/, char**/* argv*/) override {
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Conic6());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/conic-section-toy.cpp b/src/toys/conic-section-toy.cpp
new file mode 100644
index 0000000..31695be
--- /dev/null
+++ b/src/toys/conic-section-toy.cpp
@@ -0,0 +1,813 @@
+/*
+ * Conic Section Toy
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2009 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 <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+//#define CLIP_WITH_CAIRO_SUPPORT
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ #include <2geom/conic_section_clipper_cr.h>
+#endif
+
+
+#include <2geom/conicsec.h>
+#include <2geom/line.h>
+
+//#include <iomanip>
+#include <optional>
+
+
+using namespace Geom;
+
+
+class ConicSectionToy : public Toy
+{
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_VERTEX_FOCI,
+ TEST_FITTING,
+ TEST_ECCENTRICITY,
+ TEST_DEGENERATE,
+ TEST_ROOTS,
+ TEST_NEAREST_POINT,
+ TEST_BOUND,
+ TEST_CLIP,
+ TEST_TANGENT,
+ TEST_DUAL,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &ConicSectionToy::draw_menu;
+ }
+
+
+ void init_common()
+ {
+ init_vertex_foci();
+ set_control_geometry = true;
+ }
+
+ void draw_common (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_vertex_foci(cr, notify, width, height, save, timer_stream);
+ }
+
+
+/*
+ * TEST VERTEX FOCI
+ */
+ void init_vertex_foci()
+ {
+ set_common_control_geometry = true;
+ handles.clear();
+
+ focus1.pos = Point(300, 300);
+ focus2.pos = Point(500, 300);
+ vertex.pos = Point(200, 300);
+
+ parabola_toggle = Toggle("set/unset F2 to infinity", false);
+
+ handles.push_back (&vertex);
+ handles.push_back (&focus1);
+ handles.push_back (&focus2);
+ handles.push_back (&parabola_toggle);
+ }
+
+ void draw_vertex_foci (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/,
+ std::ostringstream * /*timer_stream*/)
+ {
+ init_vertex_foci_ctrl_geom(cr, notify, width, height);
+
+
+ if (!parabola_toggle.on )
+ {
+ if (focus2.pos == Point(infinity(),infinity()))
+ focus2.pos = Point(m_width/2, m_height/2);
+ double df1f2 = distance (focus1.pos, focus2.pos);
+ if (df1f2 > 20 )
+ {
+ Line axis(focus1.pos,focus2.pos);
+ vertex.pos = projection (vertex.pos, axis);
+ }
+ else if (df1f2 > 1)
+ {
+ Line axis(focus1.pos,vertex.pos);
+ focus2.pos = projection (focus2.pos, axis);
+ }
+ else
+ {
+ Line axis(focus1.pos,vertex.pos);
+ focus2.pos = focus1.pos;
+ }
+ }
+ else
+ {
+ focus2.pos = Point(infinity(),infinity());
+ }
+
+ cs.set (vertex.pos, focus1.pos, focus2.pos);
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ draw (cr, cs, m_window);
+ cairo_stroke(cr);
+
+ draw_label(cr, focus1, "F1");
+ if (!parabola_toggle.on) draw_label(cr, focus2, "F2");
+ draw_label(cr, vertex, "V");
+ cairo_stroke(cr);
+
+ *notify << cs.categorise() << ": " << cs << std::endl;
+ }
+
+ void init_vertex_foci_ctrl_geom (cairo_t* /*cr*/,
+ std::ostringstream* /*notify*/,
+ int width, int /*height*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+
+ Point toggle_sp( width - 200, 10);
+ parabola_toggle.bounds = Rect (toggle_sp, toggle_sp + Point(190,30));
+ }
+ }
+
+
+/*
+ * TEST FITTING
+ */
+ void init_fitting()
+ {
+ set_common_control_geometry = true;
+ handles.clear();
+
+ psh.pts.resize(5);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(250, 100);
+ psh.pts[2] = Point(250, 400);
+ psh.pts[3] = Point(400, 320);
+ psh.pts[4] = Point(50, 250);
+
+ fitting_slider.set (0, 5, 1, 0, "more handles");
+
+ handles.push_back(&psh);
+ handles.push_back(&fitting_slider);
+ }
+
+ void draw_fitting (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/,
+ std::ostringstream * /*timer_stream*/)
+ {
+ init_fitting_ctrl_geom(cr, notify, width, height);
+
+ size_t n = (size_t)(fitting_slider.value()) + 5;
+ if (n < psh.pts.size())
+ {
+ psh.pts.resize(n);
+ }
+ else if (n > psh.pts.size())
+ {
+ psh.push_back (std::fabs (width - 100) * uniform() + 50,
+ std::fabs (height - 100) * uniform() + 50);
+ }
+
+ cs.set (psh.pts);
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ draw (cr, cs, m_window);
+ cairo_stroke(cr);
+ *notify << cs.categorise() << ": " << cs << std::endl;
+ }
+
+ void init_fitting_ctrl_geom (cairo_t* /*cr*/,
+ std::ostringstream* /*notify*/,
+ int /*width*/, int height)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ fitting_slider.geometry (Point(50, height - 50), 100);
+ }
+ }
+
+
+/*
+ * TEST ECCENTRICITY
+ */
+ void init_eccentricity()
+ {
+ set_common_control_geometry = true;
+ handles.clear();
+
+ p1 = Point (100, 100);
+ p2 = Point (100, 400);
+ focus1 = Point (300, 250);
+
+ eccentricity_slider.set (0, 3, 0, 1, "eccentricity");
+
+ handles.push_back (&p1);
+ handles.push_back (&p2);
+ handles.push_back (&focus1);
+ handles.push_back (&eccentricity_slider);
+ }
+
+ void draw_eccentricity (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/,
+ std::ostringstream * /*timer_stream*/)
+ {
+ init_eccentricity_ctrl_geom(cr, notify, width, height);
+
+ Line directrix (p1.pos, p2.pos);
+
+ cs.set (focus1.pos, directrix, eccentricity_slider.value());
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ draw (cr, cs, m_window);
+ cairo_stroke(cr);
+
+ draw_label (cr, focus1, "F");
+ draw_line (cr, directrix, m_window);
+ draw_label(cr, p1, "directrix");
+ cairo_stroke(cr);
+
+ *notify << cs.categorise() << ": " << cs << std::endl;
+ }
+
+ void init_eccentricity_ctrl_geom (cairo_t* /*cr*/,
+ std::ostringstream* /*notify*/,
+ int /*width*/, int height)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ eccentricity_slider.geometry (Point (10, height - 50), 300);
+ }
+ }
+
+
+
+/*
+ * TEST DEGENERATE
+ */
+ void init_degenerate()
+ {
+ set_common_control_geometry = true;
+ handles.clear();
+
+ psh.pts.resize(4);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(250, 100);
+ psh.pts[2] = Point(250, 400);
+ psh.pts[3] = Point(400, 320);
+
+
+
+ handles.push_back(&psh);
+ }
+
+ void draw_degenerate (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/,
+ std::ostringstream * /*timer_stream*/)
+ {
+ init_degenerate_ctrl_geom(cr, notify, width, height);
+
+ Line l1 (psh.pts[0], psh.pts[1]);
+ Line l2 (psh.pts[2], psh.pts[3]);
+ cs.set (l1, l2);
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ draw (cr, cs, m_window);
+ cairo_stroke(cr);
+
+ *notify << cs.categorise() << ": " << cs << std::endl;
+ }
+
+ void init_degenerate_ctrl_geom (cairo_t* /*cr*/,
+ std::ostringstream* /*notify*/,
+ int /*width*/, int /*height*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ }
+ }
+
+
+/*
+ * TEST ROOTS
+ */
+ void init_roots()
+ {
+ init_common();
+
+ p1.pos = Point(180, 50);
+
+ x_y_toggle = Toggle("X/Y roots", true);
+
+ handles.push_back(&p1);
+ handles.push_back(&x_y_toggle);
+ }
+
+ void draw_roots (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_roots_ctrl_geom(cr, notify, width, height);
+
+
+ Dim2 DIM = x_y_toggle.on ? X : Y;
+ Line l(p1.pos, DIM * (-M_PI/2) + M_PI/2);
+
+ cairo_set_line_width(cr, 0.2);
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ draw_line(cr, l, m_window);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ std::vector<double> values;
+ try
+ {
+ cs.roots(values, p1.pos[DIM], DIM);
+ }
+ catch(Geom::Exception e)
+ {
+ std::cerr << e.what() << std::endl;
+ }
+ for (double value : values)
+ {
+ Point p(value, value);
+ p[DIM] = p1.pos[DIM];
+ draw_handle(cr, p);
+ }
+ cairo_stroke(cr);
+
+ *notify << " ";
+ for ( unsigned int i = 0; i < values.size(); ++i )
+ {
+ *notify << "v" << i << " = " << values[i] << " ";
+ }
+ }
+
+ void init_roots_ctrl_geom (cairo_t* /*cr*/, std::ostringstream* /*notify*/,
+ int width, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ Point T(width - 120, height - 60);
+ x_y_toggle.bounds = Rect( T, T + Point(100,25) );
+ }
+ }
+
+/*
+ * TEST NEAREST POINT
+ */
+
+ void init_nearest_time()
+ {
+ init_common();
+ p1.pos = Point(180, 50);
+ handles.push_back(&p1);
+ }
+
+ void draw_nearest_time (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+
+ Point P;
+ try
+ {
+ P = cs.nearestTime (p1.pos);
+ }
+ catch (LogicalError e)
+ {
+ std::cerr << e.what() << std::endl;
+ }
+
+
+ cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0);
+ draw_line_seg(cr, p1.pos, P);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.9, 1.0);
+ draw_handle(cr, P);
+ cairo_stroke(cr);
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0);
+ draw_label(cr, p1, "Q");
+ draw_text(cr, P + Point(5, 5), "P");
+ cairo_stroke(cr);
+ }
+
+/*
+ * TEST BOUND
+ */
+ void init_bound()
+ {
+ init_common();
+ p1.pos = Point(50, 200);
+ p2.pos = Point(50, 400);
+ p3.pos = Point(50, 500);
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ }
+
+ void draw_bound (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+
+ try
+ {
+ p1.pos = cs.nearestTime (p1.pos);
+ p2.pos = cs.nearestTime (p2.pos);
+ p3.pos = cs.nearestTime (p3.pos);
+ }
+ catch (LogicalError e)
+ {
+ std::cerr << e.what() << std::endl;
+ }
+
+ Rect bound = cs.arc_bound (p1.pos, p2.pos, p3.pos);
+ cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ cairo_rectangle (cr, bound);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0);
+ draw_label (cr, p1, "initial");
+ draw_label (cr, p2, "inner");
+ draw_label (cr, p3, "final");
+ cairo_stroke(cr);
+
+ }
+
+/*
+ * TEST CLIP
+ */
+ void init_clip()
+ {
+ init_common();
+ }
+
+ void draw_clip (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ //init_clip_ctrl_geom(cr, notify, width, height);
+
+
+ Rect R(Point (100, 100),Point (width-100, height-100));
+ std::vector<RatQuad> rq;
+#ifdef CLIP_WITH_CAIRO_SUPPORT
+ clipper_cr aclipper(cr, cs, R);
+ aclipper.clip (rq);
+#else
+ clip (rq, cs, R);
+#endif
+ cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ cairo_rectangle (cr, Rect (Point (100, 100),Point (width-100, height-100)));
+ for (auto & i : rq)
+ {
+ cairo_d2_sb (cr, i.toCubic().toSBasis());
+ }
+ cairo_stroke(cr);
+ }
+
+/*
+ * TEST TANGENT
+ */
+ void init_tangent()
+ {
+ init_common();
+
+ p1.pos = Point(180, 50);
+
+ handles.push_back(&p1);
+ }
+
+ void draw_tangent (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+
+ p1.pos = cs.nearestTime (p1.pos);
+ Line l = cs.tangent(p1.pos);
+
+ draw_label (cr, p1, "P");
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ draw_line(cr, l, m_window);
+ cairo_stroke(cr);
+ }
+
+/*
+ * TEST DUAL
+ */
+ void init_dual()
+ {
+ init_common();
+ }
+
+ void draw_dual (cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream * timer_stream)
+ {
+ draw_common(cr, notify, width, height, save, timer_stream);
+
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ xAx dc = cs.dual();
+ // we need some trick to make the dual visible in the window
+ std::string dckind = dc.categorise();
+ std::optional<Point> T = dc.centre();
+ if (T) dc = dc.translate (-*T);
+ dc = dc.scale (1e-5, 1e-5);
+ dc = dc.translate (Point(width/2, height/2));
+ draw (cr, dc, m_window);
+ cairo_stroke(cr);
+ *notify << "\n dual: " << dckind << ": " << dc;
+ }
+
+
+ void draw_segment (cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+ }
+
+ void draw (cairo_t* cr, xAx const& cs, const Rect& _window)
+ {
+
+ // offset
+ //Point O(400, 300);
+ Point O(0, 0);
+ std::vector<double> r1, r2;
+
+ size_t N = (size_t)(2 *_window.width());
+ double dx = 0.5;//width / N;
+ //std::cout << "dx = " << dx << std::endl;
+ double x = _window.left() - O[X];
+ for (size_t i = 0; i < N; ++i, x += dx)
+ {
+ if (r1.empty())
+ {
+ cs.roots(r1, x, X);
+ if (r1.size() == 1)
+ {
+ r1.push_back(r1.front());
+ }
+ if (i != 0 && r1.size() == 2)
+ {
+ Point p1(x-dx, r1[0]);
+ Point p2(x-dx, r1[1]);
+ p1 += O; p2 += O;
+ if (_window.contains(p1) && _window.contains(p2))
+ draw_segment(cr, p1, p2);
+ }
+ continue;
+ }
+ cs.roots(r2, x, X);
+ if (r2.empty())
+ {
+ Point p1(x-dx, r1[0]);
+ Point p2(x-dx, r1[1]);
+ p1 += O; p2 += O;
+ if (_window.contains(p1) && _window.contains(p2))
+ draw_segment(cr, p1, p2);
+ r1.clear();
+ continue;
+ }
+ if (r2.size() == 1)
+ {
+ r2.push_back(r2.front());
+ }
+
+ Point p1(x-dx, r1[0]);
+ Point p2(x, r2[0]);
+ p1 += O; p2 += O;
+ if (_window.contains(p1) && _window.contains(p2))
+ draw_segment(cr, p1, p2);
+
+ p1 = Point(x-dx, r1[1]) + O;
+ p2 = Point(x, r2[1]) + O;
+ if (_window.contains(p1) && _window.contains(p2))
+ draw_segment(cr, p1, p2);
+
+ using std::swap;
+ swap(r1, r2);
+ }
+ }
+
+ void draw_label(cairo_t* cr, PointHandle const& ph, const char* label)
+ {
+ draw_text(cr, ph.pos+op, label);
+ }
+
+// void draw_label(cairo_t* cr, Line const& l, const char* label)
+// {
+// draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label);
+// }
+
+ void init_menu()
+ {
+ handles.clear();
+ }
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/ )
+ {
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ }
+
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &ConicSectionToy::draw_menu;
+ break;
+ case 'B':
+ init_common();
+ draw_f = &ConicSectionToy::draw_vertex_foci;
+ break;
+ case 'C':
+ init_fitting();
+ draw_f = &ConicSectionToy::draw_fitting;
+ break;
+ case 'D':
+ init_eccentricity();
+ draw_f = &ConicSectionToy::draw_eccentricity;
+ break;
+ case 'E':
+ init_degenerate();
+ draw_f = &ConicSectionToy::draw_degenerate;
+ break;
+ case 'F':
+ init_roots();
+ draw_f = &ConicSectionToy::draw_roots;
+ break;
+ case 'G':
+ init_nearest_time();
+ draw_f = &ConicSectionToy::draw_nearest_time;
+ break;
+ case 'H':
+ init_bound();
+ draw_f = &ConicSectionToy::draw_bound;
+ break;
+ case 'K':
+ init_clip();
+ draw_f = &ConicSectionToy::draw_clip;
+ break;
+ case 'J':
+ init_tangent();
+ draw_f = &ConicSectionToy::draw_tangent;
+ break;
+ case 'I':
+ init_dual();
+ draw_f = &ConicSectionToy::draw_dual;
+ break;
+
+ }
+ redraw();
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream *timer_stream ) override
+ {
+ if (timer_stream == 0) timer_stream = notify;
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+ m_window = Rect (Point (0, 0), Point (m_width, m_height));
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+public:
+ ConicSectionToy()
+ {
+ op = Point(5,5);
+ }
+
+private:
+ typedef void (ConicSectionToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ Point op;
+ double m_width, m_height, m_length;
+ Rect m_window;
+
+ xAx cs;
+ PointHandle vertex, focus1, focus2;
+ Toggle parabola_toggle;
+
+ PointSetHandle psh;
+ Slider fitting_slider;
+
+ PointHandle p1, p2, p3;
+ Toggle x_y_toggle;
+
+ Slider eccentricity_slider;
+};
+
+
+const char* ConicSectionToy::menu_items[] =
+{
+ "show this menu",
+ "vertex and foci",
+ "fitting",
+ "eccentricity",
+ "degenerate",
+ "roots",
+ "nearest point",
+ "bound",
+ "clip",
+ "tangent",
+ "dual"
+};
+
+const char ConicSectionToy::keys[] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'K', 'J', 'I'
+};
+
+
+int main(int argc, char **argv)
+{
+ //std::cout.precision(20);
+ init( argc, argv, new ConicSectionToy(), 800, 600 );
+ 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/src/toys/convole.cpp b/src/toys/convole.cpp
new file mode 100644
index 0000000..5f76cbc
--- /dev/null
+++ b/src/toys/convole.cpp
@@ -0,0 +1,352 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+
+SBasis integral(SBasis const &c) {
+ SBasis a;
+ a.resize(c.size() + 1, Linear(0,0));
+ a[0] = Linear(0,0);
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ double ahat = -c[k-1].tri()/(2*k);
+ a[k][0] = a[k][1] = ahat;
+ }
+ double aTri = 0;
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (c[k].hat() + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+template<typename T>
+SBasisOf<T> integraaal(SBasisOf<T> const &c){
+ SBasisOf<T> a;
+ a.resize(c.size() + 1, LinearOf<T>(T(0.),T(0.)));
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ T ahat = (c[k-1][0]-c[k-1][1])/(2*k);
+ a[k] = LinearOf<T>(ahat);
+ }
+
+ T aTri = T(0.);
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (HatOf<T>(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ //a.normalize();
+ return a;
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integraaal(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ //result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ result.push_back(LinearOf<SBasisOf<double> >( integraaal(f[i][0]),integraaal(f[i][1])));
+ }
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u1 = g;
+ SBasisOf<T> u0 = -g + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g,
+ bool f_cst_ends = false){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ Piecewise<SBasis> result;
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ SBasisOf<SBasisOf<double> > v_u = (v - u)*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(-dom_g.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,v_u);
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ //double rho = dom_f.extent()/dom_g.extent();
+ double t0 = dom_g.min()/dom_f.extent();
+ double t1 = dom_g.max()/dom_f.extent();
+ double t2 = t0+1;
+ double t3 = t1+1;
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0,0));
+ b = SBasisOf<double>(LinearOf<double>(0,t1-t0));
+ t = SBasisOf<double>(LinearOf<double>(t0,t1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0,t2-t1));
+ b = SBasisOf<double>(LinearOf<double>(t1-t0,1));
+ t = SBasisOf<double>(LinearOf<double>(t1,t2));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(t2-t1,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(t2,t3));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ result*=dom_f.extent();
+
+ //--constant ends correction--------------
+ if (f_cst_ends){
+ SBasis ig = toSBasis(integraaal(g))*dom_g.extent();
+ ig -= ig.at0();
+ Piecewise<SBasis> cor;
+ cor.cuts.push_back(dom_f.min()+dom_g.min());
+ cor.push(reverse(ig)*f.at0(),dom_f.min()+dom_g.max());
+ cor.push(Linear(0),dom_f.max()+dom_g.min());
+ cor.push(ig*f.at1(),dom_f.max()+dom_g.max());
+ result+=cor;
+ }
+ //----------------------------------------
+ return result;
+}
+
+/*static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2;
+ for( double t = M.cuts.front(); t < M.cuts.back(); t += space) {
+ Point pos = M(t), perp = Mperp(t);
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_pw_d2_sb(cr, M);
+ cairo_stroke(cr);
+ }*/
+
+static void plot_graph(cairo_t *cr, Piecewise<SBasis> const &f,
+ double x_scale=300,
+ double y_scale=100){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ D2<Piecewise<SBasis> > g;
+ g[X] = Piecewise<SBasis>( SBasis(Linear(100+f.cuts.front()*x_scale,
+ 100+f.cuts.back()*x_scale)));
+ g[X].setDomain(f.domain());
+ g[Y] = -f*y_scale+400;
+ cairo_d2_pw_sb(cr, g);
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ SBasisOf<double> var = LinearOf<double>(0,1);
+ SBasisOf<double> cst = LinearOf<double>(i*1./NbRays,i*1./NbRays);
+ SBasis f_on_seg = toSBasis(compose(f,var,cst));
+ plot3d(cr,Linear(0,1),Linear(i*1./NbRays),f_on_seg,frame);
+ f_on_seg = toSBasis(compose(f,cst,var));
+ plot3d(cr,Linear(i*1./NbRays),Linear(0,1),f_on_seg,frame);
+ }
+}
+
+
+#define SIZE 2
+
+class ConvolutionToy: public Toy {
+
+ PointHandle adjuster, adjuster2, adjuster3;
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+ D2<Piecewise<SBasis> >B;
+ B[X].concat(Piecewise<SBasis>(B1[X]));
+ B[X].concat(Piecewise<SBasis>(B2[X]));
+ B[Y].concat(Piecewise<SBasis>(B1[Y]));
+ B[Y].concat(Piecewise<SBasis>(B2[Y]));
+
+//-----------------------------------------------------
+#if 0
+ Frame frame;
+ frame.O = Point(50,400);
+ frame.x = Point(300,0);
+ frame.y = Point(120,-75);
+ frame.z = Point(0,-300);
+ SBasisOf<SBasisOf<double> > u,v,cst;
+ cst.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1))));
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ plot3d(cr, integral(v,1) ,frame);
+ plot3d(cr, v ,frame);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_stroke(cr);
+ plot3d(cr, Linear(0,1), Linear(0), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0,1), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0), Linear(0,1), frame);
+ plot3d(cr, Linear(0,1), Linear(1), Linear(0), frame);
+ plot3d(cr, Linear(1), Linear(0,1), Linear(0), frame);
+ cairo_set_source_rgba (cr, 0., 0.0, 0.9, 1);
+ cairo_stroke(cr);
+#endif
+//-----------------------------------------------------
+
+ SBasisOf<double> smoother;
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(30.));//could be less degree hungry!
+
+ adjuster.pos[X] = 400;
+ if(adjuster.pos[Y]>400) adjuster.pos[Y] = 400;
+ if(adjuster.pos[Y]<100) adjuster.pos[Y] = 100;
+ double scale=(400.-adjuster.pos[Y])/300+.01;
+ D2<Piecewise<SBasis> > smoothB1,smoothB2;
+ smoothB1[X] = convole(toSBasisOfDouble(B1[X]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[Y] = convole(toSBasisOfDouble(B1[Y]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[X].push(Linear(0.), 8+scale/2);
+ smoothB1[Y].push(Linear(0.), 8+scale/2);
+ smoothB2[X] = Piecewise<SBasis>(Linear(0));
+ smoothB2[X].setDomain(Interval(-scale/2,4-scale/2));
+ smoothB2[Y] = smoothB2[X];
+ smoothB2[X].concat(convole(toSBasisOfDouble(B2[X]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+ smoothB2[Y].concat(convole(toSBasisOfDouble(B2[Y]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+
+ Piecewise<D2<SBasis> > smoothB;
+ smoothB = sectionize(smoothB1)+sectionize(smoothB2);
+ //cairo_d2_sb(cr, B1);
+ //cairo_d2_sb(cr, B2);
+ cairo_pw_d2_sb(cr, smoothB);
+
+ cairo_move_to(cr,100,400);
+ cairo_line_to(cr,500,400);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Piecewise<SBasis>bx = Piecewise<SBasis>(B1[X]);
+ bx.setDomain(Interval(0,4));
+ Piecewise<SBasis>smth = Piecewise<SBasis>(toSBasis(smoother));
+ smth.setDomain(Interval(-scale/2,scale/2));
+ cairo_d2_sb(cr, B1);
+ plot_graph(cr, bx, 100, 1);
+ plot_graph(cr, smth/scale, 100, 100);
+ plot_graph(cr, smoothB1[X],100,1);
+
+ //cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(B1) );
+ //cairo_pw_d2_sb(cr, sectionize(smoothB1));
+
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ ConvolutionToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ adjuster.pos = Geom::Point(400,100+300*uniform());
+ handles.push_back(&adjuster);
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ConvolutionToy);
+ 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/src/toys/curvature-curve.cpp b/src/toys/curvature-curve.cpp
new file mode 100644
index 0000000..8f8f7bc
--- /dev/null
+++ b/src/toys/curvature-curve.cpp
@@ -0,0 +1,142 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+//-----------------------------------------------
+
+class CurvatureTester: public Toy {
+ PointSetHandle curve_handle;
+ Path current_curve;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_line_width (cr, 1);
+ current_curve = Path();
+
+ for(int base_i = 0; base_i < int(curve_handle.pts.size()/2) - 1; base_i++) {
+ for(int i = 0; i < 2; i++) {
+ Geom::Point center=curve_handle.pts[1+2*i+base_i*2];
+ Geom::Point normal=center- curve_handle.pts[2*i+base_i*2];
+ double radius = Geom::L2(normal);
+ *notify<<"K="<<radius<<std::endl;
+ if (fabs(radius)>1e-4){
+
+ double ang = atan2(-normal);
+ cairo_arc(cr, center[0], center[1],fabs(radius), ang-0.3, ang+0.3);
+ cairo_set_source_rgba (cr, 0.75, 0.89, 1., 1);
+ cairo_stroke(cr);
+
+
+ //draw_handle(cr, center);
+ }else{
+ }
+ {
+ cairo_save(cr);
+ double dashes[2] = {10, 10};
+ cairo_set_dash(cr, dashes, 2, 0);
+ cairo_move_to(cr, center);
+ cairo_line_to(cr, center-normal);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ }
+ cairo_set_source_rgba (cr, 0., 0, 0., 1);
+ Geom::Point A = curve_handle.pts[0+base_i*2];
+ Geom::Point B = curve_handle.pts[2+base_i*2];
+ D2<SBasis> best_c = D2<SBasis>(SBasis(Linear(A[X],B[X])),SBasis(Linear(A[Y],B[Y])));
+ double error = -1;
+ for(int i = 0; i < 16; i++) {
+ Geom::Point dA = curve_handle.pts[1+base_i*2]-A;
+ Geom::Point dB = curve_handle.pts[3+base_i*2]-B;
+ std::vector<D2<SBasis> > candidates =
+ cubics_fitting_curvature(curve_handle.pts[0+base_i*2],curve_handle.pts[2+base_i*2],
+ (i&2)?rot90(dA):-rot90(dA),
+ (i&1)?rot90(dB):-rot90(dB),
+ ((i&4)?-1:1)*L2sq(dA), ((i&8)?-1:1)*L2sq(dB));
+
+ if (candidates.empty()) {
+ } else {
+ //TODO: I'm sure std algorithm could do that for me...
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ Piecewise<SBasis> K = arcLengthSb(candidates[i]);
+
+ double l = Geom::length(candidates[i]);
+ //double l = K.segs.back().at1();//Geom::length(candidates[i]);
+ //printf("l = %g\n", l);
+ if ( l < error || error < 0 ){
+ error = l;
+ best = i;
+ best_c = candidates[best];
+ }
+ }
+ }
+ }
+ if(error >= 0) {
+ //cairo_d2_sb(cr, best_c);
+ current_curve.append(best_c);
+ }
+ }
+
+ cairo_path(cr, current_curve);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void canvas_click(Geom::Point at, int button) override {
+ std::cout << "clicked at:" << at << " with button " << button << std::endl;
+ if(button == 1) {
+ double dist;
+ double t = current_curve.nearestTime(at, &dist).asFlatTime();
+ if(dist > 5) {
+ curve_handle.push_back(at);
+ curve_handle.push_back(at+Point(100,100));
+ } else {
+ // split around t
+ Piecewise<D2<SBasis> > pw = current_curve.toPwSb();
+ std::vector<Point > vnd = pw.valueAndDerivatives(t, 2);
+ Point on_curve = current_curve(t);
+ Point normal = rot90(vnd[1]);
+ Piecewise<SBasis > K = curvature(pw);
+ Point ps[2] = {on_curve, on_curve+unit_vector(normal)/K(t)};
+ curve_handle.pts.insert(curve_handle.pts.begin()+2*(int(t)+1), ps, ps+2);
+ }
+ }
+ }
+
+public:
+ CurvatureTester(){
+ if(handles.empty()) {
+ handles.push_back(&curve_handle);
+ for(unsigned i = 0; i < 4; i++)
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl;
+ init(argc, argv, new CurvatureTester);
+ 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/curvature-test.cpp b/src/toys/curvature-test.cpp
new file mode 100644
index 0000000..c209fe3
--- /dev/null
+++ b/src/toys/curvature-test.cpp
@@ -0,0 +1,110 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// TODO:
+// use path2
+// replace Ray stuff with path2 line segments.
+
+//-----------------------------------------------
+
+class CurvatureTester: public Toy {
+ PointSetHandle curve_handle;
+ PointHandle sample_point;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ D2<SBasis> B = curve_handle.asBezier();
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ sample_point.pos[1]=400;
+ sample_point.pos[0]=std::max(150.,sample_point.pos[0]);
+ sample_point.pos[0]=std::min(450.,sample_point.pos[0]);
+ cairo_move_to(cr, Geom::Point(150,400));
+ cairo_line_to(cr, Geom::Point(450,400));
+ cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8);
+ cairo_stroke(cr);
+
+ double t=std::max(0.,std::min(1.,(sample_point.pos[0]-150)/300.));
+ Timer tm;
+ tm.ask_for_timeslice();
+ tm.start();
+
+ Piecewise<SBasis> K = curvature(B);
+ Timer::Time als_time = tm.lap();
+ *timer_stream << "curvature " << als_time << std::endl;
+
+ for(unsigned ix = 0; ix < K.segs.size(); ix++) {
+ D2<SBasis> Kxy;
+ Kxy[1] = Linear(400) - K.segs[ix]*300;
+ Kxy[0] = Linear(300*K.cuts[ix] + 150, 300*K.cuts[ix+1] + 150);
+ cairo_d2_sb(cr, Kxy);
+ cairo_stroke(cr);
+ }
+
+ double radius = K(t);
+ *notify<<"K="<<radius<<std::endl;
+ if (fabs(radius)>1e-4){
+ radius=1./radius;
+ Geom::Point normal=unit_vector(derivative(B)(t));
+ normal=rot90(normal);
+ Geom::Point center=B(t)-radius*normal;
+
+ cairo_arc(cr, center[0], center[1],fabs(radius), 0, M_PI*2);
+ draw_handle(cr, center);
+ draw_handle(cr, B(t));
+ }else{
+ Geom::Point p=B(t);
+ Geom::Point v=derivative(B)(t);
+ draw_handle(cr, p);
+ cairo_move_to(cr, p-100*v);
+ cairo_line_to(cr, p+100*v);
+ }
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ CurvatureTester(){
+ if(handles.empty()) {
+ handles.push_back(&curve_handle);
+ handles.push_back(&sample_point);
+ for(unsigned i = 0; i < 4; i++)
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ sample_point.pos = Geom::Point(250,300);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl;
+ init(argc, argv, new CurvatureTester);
+ 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/curve-curve-distance.cpp b/src/toys/curve-curve-distance.cpp
new file mode 100644
index 0000000..bca0f27
--- /dev/null
+++ b/src/toys/curve-curve-distance.cpp
@@ -0,0 +1,1000 @@
+/*
+ * curve-curve distance
+ *
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/angle.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/piecewise.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/nearest-time.h>
+#include <2geom/numeric/linear_system.h>
+
+#include <algorithm>
+
+
+
+namespace Geom
+{
+
+namespace detail
+{
+
+// this wrapper class is an helper to make up a curve portion and access it
+// in an homogeneous way
+template< typename Curve01T >
+class CurvePortion
+{
+ public:
+ CurvePortion(const Curve & curve, double from, double to)
+ : m_curve_ptr(curve.portion(from, to))
+ {
+ }
+
+ Curve01T & get_curve()
+ {
+ return *( static_cast<Curve01T*>(m_curve_ptr) );
+ }
+
+ ~CurvePortion()
+ {
+ if (m_curve_ptr != NULL)
+ delete m_curve_ptr;
+ }
+
+ private:
+ Curve* m_curve_ptr;
+};
+
+template<>
+class CurvePortion< D2<SBasis> >
+{
+ public:
+ CurvePortion< D2<SBasis> >(const D2<SBasis> & curve, double from, double to)
+ : m_curve(portion(curve, from, to))
+ {
+ }
+
+ D2<SBasis> & get_curve()
+ {
+ return m_curve;
+ }
+
+ private:
+ D2<SBasis> m_curve;
+};
+
+
+template< typename Curve01T, typename CurveT >
+class distance_impl
+{
+ typedef Curve01T curveA_type;
+ typedef CurveT curveB_type;
+ // determine how near a distance sample and the value computed through
+ // the interpolated function have to be
+ double accuracy;
+ // determine the recursion limit
+ double adaptive_limit;
+ // pieces of the initial subdivision
+ unsigned int piecees;
+ // degree of the polynomial used to interpolate a piece
+ unsigned int piece_degree;
+ // number of coefficients = piece_degree + 1
+ unsigned int piece_size;
+ unsigned int samples_per_piece;
+ // total initial samples
+ unsigned int N;
+ // a junction is a part of the previous or of the next piece
+ unsigned int samples_per_junction;
+ unsigned int samples_per_2junctions;
+ // number of distance samples used in the interpolation (in the general case)
+ unsigned int samples_per_interpolation;
+
+ // distance between two consecutive parameters at which samples are evaluated
+ double step;
+ double half_step;
+ // length of the initial domain interval of a piece
+ double piece_step;
+ // length of the interval related to a junction
+ double junction_step;
+ // index of the first sample related to a piece
+ unsigned int interval_si;
+ // index of the last sample related to a piece
+ unsigned int interval_ei;
+ // index of the first sample to be evaluated for the current piece
+ unsigned int evaluation_si;
+ // index of the last sample to be evaluated for the current piece
+ unsigned int evaluation_ei;
+ // index of the first sample to be used for interpolating the current piece
+ unsigned int interpolation_si;
+ // index of the last sample to be used for interpolating the current piece
+ unsigned int interpolation_ei;
+ // number of total samples to be used for interpolating the current piece
+ // this is equal to samples_per_interpolation except for the first and last
+ // piece
+ unsigned int interpolation_samples;
+ // parameter value for the first sample related to the current piece
+ double interval_st;
+ // interval_st + piece_step
+ double interval_et;
+ // curve piece start t
+ double portion_st;
+ // curve piece end t
+ double portion_et;
+
+ unsigned int rec_pieces;
+ unsigned int rec_N;
+ unsigned int shared_si;
+ unsigned int shared_ei;
+ double rec_step;
+ double rec_half_step;
+ double rec_piece_step;
+ double rec_piece_2steps;
+ unsigned int rec_total_samples;
+
+
+ void init()
+ {
+ piece_degree = 3;
+ piece_size = piece_degree + 1;
+ samples_per_piece = 4;
+ N = piecees * samples_per_piece;
+ samples_per_junction = 2;
+ samples_per_2junctions = 2*samples_per_junction;
+ samples_per_interpolation
+ = samples_per_piece + samples_per_2junctions;
+ step = 1.0 / N;
+ half_step = step / 2;
+ piece_step = samples_per_piece * step;
+ junction_step = samples_per_junction * step;
+ interval_si = samples_per_junction;
+ interval_ei = interval_si + samples_per_piece;
+ portion_st = (double)(samples_per_junction) / samples_per_interpolation;
+ portion_et = portion_st
+ + (double)(samples_per_piece) / samples_per_interpolation;
+
+ // recursive routine parameters
+ rec_pieces = 2;
+ rec_N = rec_pieces * samples_per_piece;
+ rec_total_samples = 2 * samples_per_piece + 1;
+ shared_si = samples_per_piece - samples_per_junction;
+ shared_ei = samples_per_piece + samples_per_junction;
+ rec_step = 1.0 / rec_N;
+ rec_half_step = rec_step / 2;
+ rec_piece_step = samples_per_piece * rec_step;
+ rec_piece_2steps = 2 * rec_piece_step;
+ }
+
+ bool check_accuracy( SBasis const& piece,
+ NL::Vector const& sample_distances,
+ double step )
+ {
+ double t = 0;
+ for (unsigned int i = 0; i < sample_distances.size(); ++i)
+ {
+ if ( !are_near(piece(t), sample_distances[i], accuracy) )
+ {
+ return false;
+ }
+ t += step;
+ }
+ return true;
+ }
+
+
+ void append( Piecewise<SBasis> & pwc,
+ Piecewise<SBasis> const& spwc,
+ double interval_st,
+ double interval_length )
+ {
+ double cut;
+ for (unsigned int i = 0; i < spwc.size(); ++i)
+ {
+ cut = interval_st + spwc.cuts[i+1] * interval_length;
+ pwc.push(spwc.segs[i], cut);
+ }
+ }
+
+ void init_power_matrix(NL::Matrix & power_matrix)
+ {
+ double t = 0;
+ double u0, u1, s;
+ unsigned int half_rows = power_matrix.rows() / 2;
+ unsigned int n = power_matrix.rows() - 1;
+ for (unsigned int i0 = 0, i1 = n; i0 < half_rows; ++i0, --i1)
+ {
+ u0 = 1-t;
+ u1 = t;
+ s = u0 * u1;
+ for (unsigned int j = 0; j < piece_size; j+=2)
+ {
+ power_matrix(i0, j) = u0;
+ power_matrix(i0, j+1) = u1;
+ power_matrix(i1, j) = u1;
+ power_matrix(i1, j+1) = u0;
+ u0 *= s;
+ u1 *= s;
+ }
+ t += rec_step;
+ }
+ // t = 1/2
+ assert( are_near(t, 0.5) );
+ u1 = 1/2.0;
+ s = 1/4.0;
+ for (unsigned int j = 0; j < piece_size; j+=2)
+ {
+ power_matrix(half_rows, j) = u1;
+ power_matrix(half_rows, j+1) = u1;
+ u1 *= s;
+ }
+ }
+
+ void interpolate( SBasis & piece,
+ NL::Matrix & psdinv_matrix,
+ NL::Vector & sample_distances,
+ double interpolation_si, double interpolation_samples,
+ double _portion_st, double _portion_et )
+ {
+ piece.resize(2);
+
+ NL::VectorView v( sample_distances,
+ interpolation_samples,
+ interpolation_si );
+ NL::Vector coeff = psdinv_matrix * v;
+ for (unsigned int i = 0, k = 0; i < piece_size; i+=2, ++k)
+ {
+ piece[k][0] = coeff[i];
+ piece[k][1] = coeff[i+1];
+ }
+ piece = portion(piece, _portion_st, _portion_et);
+ }
+
+ void evaluate_samples( curveA_type const& A,
+ curveB_type const& B,
+ NL::Vector & sample_distances,
+ double& t )
+ {
+ Point At;
+ double nptime;
+ for (unsigned int i = evaluation_si; i < evaluation_ei; ++i)
+ {
+ At = A(t);
+ nptime = nearest_time(At, B);
+ sample_distances[i] = distance(At, B(nptime));
+ t += step;
+ }
+ }
+
+ void evaluate_piece_rec( Piecewise<SBasis> & pwc,
+ curveA_type const& A,
+ curveB_type const& B,
+ NL::Matrix & psdinv_matrix,
+ NL::Matrix & fpi_matrix,
+ NL::Matrix & lpi_matrix,
+ NL::Vector & curr_vector,
+ NL::Vector & sample_distances,
+ bool adaptive,
+ double _interpolation_si,
+ double _interpolation_ei,
+ double _interval_st,
+ double _interval_et,
+ double half_real_step )
+ {
+ SBasis piece;
+ double _interpolation_samples = _interpolation_ei - _interpolation_si;
+ interpolate( piece, psdinv_matrix, curr_vector,
+ _interpolation_si, _interpolation_samples,
+ _interval_st, _interval_et );
+ if (adaptive)
+ {
+ bool good
+ = check_accuracy( piece, sample_distances, rec_step );
+ if (!good)
+ {
+ Piecewise<SBasis> spwc;
+ CurvePortion<curveA_type> cp(A, _interval_st, _interval_et);
+ evaluate_rec( spwc,
+ cp.get_curve(),
+ B,
+ fpi_matrix,
+ lpi_matrix,
+ sample_distances,
+ half_real_step );
+ append(pwc, spwc, _interval_st, rec_piece_step);
+ }
+ else
+ {
+ pwc.push(piece, _interval_et);
+ }
+ }
+ else
+ {
+ pwc.push(piece, _interval_et);
+ }
+ }
+
+
+ // recursive routine: if the interpolated piece is accurate enough
+ // it's returned in the out-parameter pwc, otherwise the computation of
+ // two new piecees is performed using half of the current step so the
+ // number of samples per piece is always the same, while the interpolation
+ // of one piece is split into the computation of two new piecees when
+ // needed.
+ void evaluate_rec( Piecewise<SBasis> & pwc,
+ curveA_type const& A,
+ curveB_type const& B,
+ NL::Matrix & fpi_matrix,
+ NL::Matrix & lpi_matrix,
+ NL::Vector & sample_distances,
+ double real_step )
+ {
+ const double half_real_step = real_step / 2;
+ const bool adaptive = !(real_step < adaptive_limit);
+ static const unsigned int middle_sample_index = samples_per_piece + 1;
+
+ pwc.clear();
+ pwc.push_cut(0);
+ // sample_distances used to check accuracy and for the interpolation
+ // of the two sub-pieces when needed
+ NL::Vector sample_distances_1(rec_total_samples);
+ NL::Vector sample_distances_2(rec_total_samples);
+
+ // view of even indexes of sample_distances_1
+ NL::VectorView
+ sd1_view_0(sample_distances_1, middle_sample_index, 0, 2);
+ // view of even indexes of sample_distances_2
+ NL::VectorView
+ sd2_view_0(sample_distances_2, middle_sample_index, 0, 2);
+ // view of first half (+ 1) of sample_distances
+ NL::VectorView
+ sd_view_1(sample_distances, middle_sample_index, 0);
+ // view of second half of sample_distances
+ NL::VectorView
+ sd_view_2(sample_distances, middle_sample_index, samples_per_piece);
+
+ sd1_view_0 = sd_view_1;
+ sd2_view_0 = sd_view_2;
+
+ // if we have to check accuracy and go on with recursion
+ // we need to compute the distance samples of middle points
+ // of all current samples, because the new step is half of
+ // the current one
+ if (adaptive)
+ {
+ Point At;
+ double nptime;
+ double t = rec_half_step;
+ for (unsigned int i = 1; i < sample_distances.size(); i+=2)
+ {
+ At = A(t);
+ nptime = nearest_time(At, B);
+ sample_distances_1[i] = distance(At, B(nptime));
+ At = A(t + rec_piece_step);
+ nptime = nearest_time(At, B);
+ sample_distances_2[i] = distance(At, B(nptime));
+ t += rec_step;
+ }
+ }
+
+ // first piece
+ evaluate_piece_rec( pwc, A, B,
+ fpi_matrix,
+ fpi_matrix,
+ lpi_matrix,
+ sample_distances,
+ sample_distances_1,
+ adaptive,
+ 0, // interpolation_si
+ shared_ei, // interpolation_ei
+ 0, // portion_st
+ rec_piece_step, // portion_et
+ half_real_step );
+
+ // copy back junction parts because
+ // the interpolate routine modifies them
+ for ( unsigned int i = 0, j = samples_per_piece - 1;
+ i < samples_per_junction;
+ ++i, --j )
+ {
+ sd_view_1[j] = sd1_view_0[j];
+ sd_view_2[i] = sd2_view_0[i];
+ }
+
+
+ // last piece
+ evaluate_piece_rec( pwc, A, B,
+ lpi_matrix,
+ fpi_matrix,
+ lpi_matrix,
+ sample_distances,
+ sample_distances_2,
+ adaptive,
+ shared_si, // interpolation_si
+ rec_total_samples, // interpolation_ei
+ rec_piece_step, // portion_st
+ rec_piece_2steps, // portion_et
+ half_real_step );
+ }
+
+
+ void evaluate_piece( Piecewise<SBasis> & pwc,
+ curveA_type const& A,
+ curveB_type const& B,
+ NL::Matrix & psdinv_matrix,
+ NL::Matrix & fpi_matrix,
+ NL::Matrix & lpi_matrix,
+ NL::Vector & curr_vector,
+ NL::Vector & sample_distances,
+ NL::Vector & end_junction,
+ NL::VectorView & start_junction_view,
+ NL::VectorView & end_junction_view,
+ double & t )
+ {
+ //static size_t index = 0;
+ //std::cerr << "index = " << index++ << std::endl;
+ bool good;
+ SBasis piece;
+ Piecewise<SBasis> spwc;
+ interval_et += piece_step;
+ //std::cerr << "interval: " << interval_st << ", " << interval_et << std::endl;
+ //std::cerr << "interpolation range: " << interpolation_si << ", " << interpolation_ei << std::endl;
+ //std::cerr << "interpolation samples = " << interpolation_samples << std::endl;
+ evaluate_samples( A, B, curr_vector, t );
+ //std::cerr << "current vector: " << curr_vector << std::endl;
+ for ( unsigned int i = 0, k = interval_si;
+ i < sample_distances.size();
+ i+=2, ++k )
+ {
+ sample_distances[i] = curr_vector[k];
+ }
+ Point At;
+ double nptime;
+ double tt = interval_st + half_step;
+ for (unsigned int i = 1; i < sample_distances.size(); i+=2)
+ {
+ At = A(tt);
+ nptime = nearest_time(At, B);
+ sample_distances[i] = distance(At, B(nptime));
+ tt += step;
+ }
+ //std::cerr << "sample_distances: " << sample_distances << std::endl;
+ end_junction = end_junction_view;
+ interpolate( piece, psdinv_matrix, curr_vector,
+ interpolation_si, interpolation_samples,
+ portion_st, portion_et );
+ good = check_accuracy( piece, sample_distances, rec_step );
+ //std::cerr << "good: " << good << std::endl;
+ //good = true;
+ if (!good)
+ {
+ CurvePortion<curveA_type> cp(A, interval_st, interval_et);
+ evaluate_rec( spwc,
+ cp.get_curve(),
+ B,
+ fpi_matrix,
+ lpi_matrix,
+ sample_distances,
+ half_step );
+ append(pwc, spwc, interval_st, piece_step);
+ }
+ else
+ {
+ pwc.push(piece, interval_et);
+ }
+ interval_st = interval_et;
+ for (unsigned int i = 0; i < samples_per_junction; ++i)
+ {
+ curr_vector[i] = start_junction_view[i];
+ curr_vector[samples_per_junction + i] = end_junction[i];
+ }
+ }
+
+ public:
+ void evaluate( Piecewise<SBasis> & pwc,
+ curveA_type const& A,
+ curveB_type const& B,
+ unsigned int _piecees )
+ {
+ piecees = _piecees;
+ init();
+ assert( !(piecees & 1) );
+ assert( !(piece_size & 1) );
+ assert( rec_total_samples & 1);
+ pwc.clear();
+ pwc.push_cut(0);
+ NL::Matrix power_matrix(rec_total_samples, piece_size);
+ init_power_matrix(power_matrix);
+
+ NL::MatrixView rec_fmv( power_matrix,
+ 0, 0,
+ shared_ei, piece_size );
+ NL::Matrix rec_fpim = NL::pseudo_inverse(rec_fmv);
+ NL::MatrixView rec_lmv( power_matrix,
+ shared_si, 0,
+ rec_total_samples - shared_si, piece_size );
+ NL::Matrix rec_lpim = NL::pseudo_inverse(rec_lmv);
+
+
+
+ NL::Vector curr_vector(samples_per_interpolation);
+ NL::Vector sample_distances(rec_total_samples);
+ NL::Vector end_junction(samples_per_junction);
+ NL::VectorView start_junction_view(
+ sample_distances,
+ samples_per_junction,
+ rec_total_samples - 1 - samples_per_2junctions,
+ 2 );
+ NL::VectorView end_junction_view(
+ curr_vector,
+ samples_per_junction,
+ samples_per_junction + samples_per_piece );
+
+ double t = 0;
+
+ // first piece
+ evaluation_si = interval_si;
+ evaluation_ei = samples_per_interpolation;
+ interpolation_si = evaluation_si;
+ interpolation_ei = evaluation_ei;
+ interpolation_samples = interpolation_ei - interpolation_si;
+ interval_st = 0;
+ interval_et = 0;
+ NL::MatrixView fmv( power_matrix,
+ interpolation_si, 0,
+ interpolation_samples, piece_size );
+ NL::Matrix fpim = NL::pseudo_inverse(fmv);
+
+ evaluate_piece( pwc, A, B, fpim,
+ rec_fpim, rec_lpim,
+ curr_vector, sample_distances, end_junction,
+ start_junction_view, end_junction_view,
+ t );
+
+ // general case
+ evaluation_si = interval_si + samples_per_junction;
+ evaluation_ei = samples_per_interpolation;
+ interpolation_si = 0;
+ interpolation_ei = evaluation_ei;
+ interpolation_samples = interpolation_ei - interpolation_si;
+ NL::MatrixView gmv( power_matrix,
+ interpolation_si, 0,
+ interpolation_samples, piece_size );
+ NL::Matrix gpim = NL::pseudo_inverse(gmv);
+
+ for ( unsigned int piece_index = 1;
+ piece_index < piecees - 1;
+ ++piece_index )
+ {
+ evaluate_piece( pwc, A, B, gpim,
+ rec_fpim, rec_lpim,
+ curr_vector, sample_distances, end_junction,
+ start_junction_view, end_junction_view,
+ t );
+ }
+
+ // last piece
+ evaluation_si = interval_si + samples_per_junction;
+ evaluation_ei = interval_ei + 1;
+ interpolation_si = 0;
+ interpolation_ei = evaluation_ei;
+ interpolation_samples = interpolation_ei -interpolation_si;
+ NL::MatrixView lmv( power_matrix,
+ interpolation_si, 0,
+ interpolation_samples, piece_size );
+ NL::Matrix lpim = NL::pseudo_inverse(lmv);
+
+ evaluate_piece( pwc, A, B, lpim,
+ rec_fpim, rec_lpim,
+ curr_vector, sample_distances, end_junction,
+ start_junction_view, end_junction_view,
+ t );
+ }
+
+ distance_impl()
+ : accuracy(1e-3),
+ adaptive_limit(1e-5)
+ {}
+
+ void set_accuracy(double _accuracy)
+ {
+ accuracy = _accuracy;
+ }
+
+ void set_adaptive_limit(double _adaptive_limit)
+ {
+ adaptive_limit = _adaptive_limit;
+ }
+
+}; // end class distance_impl
+
+} // end namespace detail
+
+template < typename Curve01T, typename CurveT >
+inline
+Piecewise<SBasis>
+distance( Curve01T const& A,
+ CurveT const& B,
+ unsigned int pieces = 40,
+ double adaptive_limit = 1e-5,
+ double accuracy = 1e-3 )
+{
+
+ detail::distance_impl<Curve01T, CurveT> dist;
+ dist.set_accuracy(accuracy);
+ dist.set_adaptive_limit(adaptive_limit);
+ Piecewise<SBasis> pwc;
+ dist.evaluate(pwc, A, B, pieces);
+ return pwc;
+}
+
+template < typename CurveT >
+inline
+Piecewise<SBasis>
+distance( Piecewise< D2<SBasis> > const& A,
+ CurveT const& B,
+ unsigned int pieces = 40,
+ double adaptive_limit = 1e-5,
+ double accuracy = 1e-3 )
+{
+ Piecewise<SBasis> result;
+ Piecewise<SBasis> pwc;
+ for (unsigned int i = 0; i < A.size(); ++i)
+ {
+ pwc = distance(A[i], B, pieces, adaptive_limit, accuracy);
+ pwc.scaleDomain(A.cuts[i+1] - A.cuts[i]);
+ pwc.offsetDomain(A.cuts[i]);
+ result.concat(pwc);
+ }
+ return result;
+}
+
+template < typename CurveT >
+inline
+Piecewise<SBasis>
+distance( Path const& A,
+ CurveT const& B,
+ unsigned int pieces = 40,
+ double adaptive_limit = 1e-5,
+ double accuracy = 1e-3 )
+{
+ Piecewise<SBasis> result;
+ Piecewise<SBasis> pwc;
+ unsigned int sz = A.size();
+ if (A.closed()) ++sz;
+ for (unsigned int i = 0; i < sz; ++i)
+ {
+ pwc = distance(A[i], B, pieces, adaptive_limit, accuracy);
+ pwc.offsetDomain(i);
+ result.concat(pwc);
+ }
+ return result;
+}
+
+
+template < typename Curve01T, typename CurveT >
+unsigned int dist_test( Piecewise<SBasis> const& pwc,
+ Curve01T const& A,
+ CurveT const& B,
+ double step )
+{
+ std::cerr << "======= inside dist test =======" << std::endl;
+ unsigned int total_checked_values = 0;
+ unsigned int total_error = 0;
+ double nptime, sample_distance;
+ Point At;
+ for (double t = 0; t <= 1; t += step)
+ {
+ At = A(t);
+ nptime = nearest_time(At, B);
+ sample_distance = distance(At, B(nptime));
+ if ( !are_near(pwc(t), sample_distance, 0.001) )
+ {
+ ++total_error;
+ std::cerr << "error at t: " << t << std::endl;
+ }
+ ++total_checked_values;
+ }
+ std::cerr << " total checked values : " << total_checked_values << std::endl;
+ std::cerr << " total error : " << total_error << std::endl;
+ return total_error;
+}
+
+} // end namespace Geom
+
+
+using namespace Geom;
+
+class DCCToy : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ Point ulc(width - 300, height - 60 );
+ toggles[0].bounds = Rect(ulc, ulc + Point(160,25) );
+ toggles[1].bounds = Rect(ulc + Point(0,30), ulc + Point(160,55) );
+ sliders[0].geometry(ulc - Point(450,0), 400);
+ if (toggle0_status != toggles[0].on)
+ {
+ toggle0_status = toggles[0].on;
+ using std::swap;
+ swap(sliders[0], sliders[1]);
+ }
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+
+ if (choice == 0)
+ {
+ A = single_curve_psh.asBezier();
+ cairo_d2_sb(cr, A);
+ }
+ else if (choice == 1)
+ {
+ pA.clear();
+ for (unsigned int k = 0; k < path_curves; ++k)
+ {
+ PointSetHandle psh;
+ psh.pts.resize(path_handles_per_curve);
+ for (unsigned int h = 0; h < path_handles_per_curve; ++h)
+ {
+ unsigned int kk = k * (path_handles_per_curve-1) + h;
+ psh.pts[h] = path_psh.pts[kk];
+ }
+ pA.append(psh.asBezier());
+ }
+ cairo_path(cr, pA);
+ }
+ else if (choice == 2)
+ {
+ for (unsigned int i = 0; i < pwc_curves; ++i)
+ {
+ pwA.segs[i] = pwc_psh[i].asBezier();
+ }
+ cairo_pw_d2_sb(cr, pwA);
+ }
+
+ D2<SBasis> B = B_psh.asBezier();
+ cairo_d2_sb(cr, B);
+
+ double t = sliders[0].value();
+ Piecewise<SBasis> d;
+ unsigned int total_error = 0;
+ Point cursor;
+
+ if (!toggles[0].on)
+ {
+ if (choice == 0)
+ {
+ cursor = A(t);
+ d = distance(A, B, 40);
+ // uncomment following lines to view errors in computing the distance
+ //total_error = dist_test(d, A, B, 0.0004);
+ }
+ else if (choice == 1)
+ {
+ cursor = pA(t);
+ d = distance(pA, B, 40);
+ // uncomment following lines to view errors in computing the distance
+ //total_error = dist_test(d, pA, B, 0.0004);
+ }
+ else if (choice == 2)
+ {
+ cursor = pwA(t);
+ d = distance(pwA, B, 40);
+ // uncomment following lines to view errors in computing the distance
+ //total_error = dist_test(d, pwA, B, 0.0004);
+ }
+
+ double nptB = nearest_time(cursor, B);
+ draw_circ(cr, cursor);
+ cairo_move_to(cr, cursor);
+ cairo_line_to(cr, B(nptB));
+ cairo_stroke(cr);
+ }
+ else
+ {
+ Point np(0,0);
+ cursor = B(t);
+ if (choice == 0)
+ {
+ double nptA = nearest_time(cursor, A);
+ np = A(nptA);
+ d = distance(B, A, 40);
+ // uncomment following lines to view errors in computing the distance
+ //total_error = dist_test(d, B, A, 0.0004);
+ }
+ else if (choice == 1)
+ {
+ double nptA = nearest_time(cursor, pA);
+ np = pA(nptA);
+ d = distance(B, pA, 40);
+ // uncomment following lines to view errors in computing the distance
+ //total_error = dist_test(d, B, pA, 0.0004);
+ }
+ draw_circ(cr, cursor);
+ cairo_move_to(cr, cursor);
+ cairo_line_to(cr, np);
+ cairo_stroke(cr);
+ }
+
+ if (total_error != 0)
+ *notify << "total error: " << total_error << " ";
+
+
+ // draw distance function
+ Piecewise< D2<SBasis> > pwc;
+ pwc.cuts = d.cuts;
+ pwc.segs.resize(d.size());
+ D2<SBasis> piece;
+ double domain_length = 800 / d.domain().extent();
+ for ( unsigned int i = 0; i < d.size(); ++i )
+ {
+ piece[X] = SBasis(Linear(20,20)
+ + domain_length * Linear(d.cuts[i], d.cuts[i+1]));
+ piece[Y] = 3 * d.segs[i];
+ pwc.segs[i] = piece;
+ }
+ cairo_set_source_rgb(cr, 0.7,0,0);
+ cairo_pw_d2_sb(cr, pwc);
+ *notify << "total cuts: " << pwc.cuts.size();
+ if (toggles[1].on)
+ {
+ for (unsigned int i = 0; i < pwc.cuts.size(); ++i)
+ {
+ draw_handle(cr, pwc(pwc.cuts[i]));
+ }
+ }
+ else
+ {
+ draw_handle(cr, pwc(0.0));
+ draw_handle(cr, pwc(0.25));
+ draw_handle(cr, pwc(0.5));
+ draw_handle(cr, pwc(0.75));
+ draw_handle(cr, pwc(1));
+ }
+ draw_circ(cr, pwc(t));
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+ public:
+ DCCToy()
+ {
+ toggle0_status = false;
+ choice = 0;
+
+ single_curve_handles = 6;
+ path_curves = 3;
+ path_handles_per_curve = 4;
+ path_total_handles = path_curves * (path_handles_per_curve - 1) + 1;
+ pwc_curves = 3;
+ pwc_handles_per_curve = 4;
+ pwc_total_handles = pwc_curves * pwc_handles_per_curve;
+ B_handles = 4;
+
+ if (choice == 0)
+ {
+ for (unsigned int i = 0; i < single_curve_handles; ++i)
+ {
+ single_curve_psh.push_back(700*uniform(), 500*uniform());
+ }
+ handles.push_back(&single_curve_psh);
+ sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t");
+ }
+ else if (choice == 1)
+ {
+ for (unsigned int i = 0; i < path_total_handles; ++i)
+ {
+ path_psh.push_back(700*uniform(), 500*uniform());
+ }
+ handles.push_back(&path_psh);
+ sliders.emplace_back(0.0, path_curves, 0.0, 0.0, "t");
+ }
+ else if (choice == 2)
+ {
+ pwc_psh.resize(pwc_curves);
+ pwA.segs.resize(pwc_curves);
+ pwA.cuts.resize(pwc_curves+1);
+ pwA.cuts[0] = 0;
+ double length = 1.0 / pwc_curves;
+ for (unsigned int i = 0; i < pwc_curves; ++i)
+ {
+ for (unsigned int j = 0; j < pwc_handles_per_curve; ++j)
+ {
+ pwc_psh[i].push_back(700*uniform(), 500*uniform());
+ }
+ handles.push_back(&(pwc_psh[i]));
+ pwA.cuts[i+1] = pwA.cuts[i] + length;
+ }
+ sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t");
+ }
+
+ for (unsigned int i = 0; i < B_handles; ++i)
+ {
+ B_psh.push_back(700*uniform(), 500*uniform());
+ }
+ handles.push_back(&B_psh);
+ sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t");
+
+ toggles.emplace_back("d(A,B) <-> d(B,A)", false);
+ toggles.emplace_back("Show/Hide cuts", false);
+
+ handles.push_back(&(toggles[0]));
+ handles.push_back(&(toggles[1]));
+ handles.push_back(&(sliders[0]));
+
+ }
+
+ private:
+ bool toggle0_status;
+ unsigned int choice;
+ unsigned int single_curve_handles, B_handles;
+ unsigned int path_curves, path_handles_per_curve, path_total_handles;
+ unsigned int pwc_curves, pwc_handles_per_curve, pwc_total_handles;
+ PointSetHandle single_curve_psh;
+ PointSetHandle path_psh;
+ std::vector<PointSetHandle> pwc_psh;
+ PointSetHandle B_psh;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ D2<SBasis> A;
+ Path pA;
+ Piecewise< D2<SBasis> > pwA;
+};
+
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new DCCToy(), 840, 600 );
+ 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/src/toys/curve-curve-nearest-time.cpp b/src/toys/curve-curve-nearest-time.cpp
new file mode 100644
index 0000000..30fb327
--- /dev/null
+++ b/src/toys/curve-curve-nearest-time.cpp
@@ -0,0 +1,609 @@
+/*
+ * Nearest Points Toy 3
+ *
+ * Authors:
+ * Nathan Hurst <njh at njhurst.com>
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/piecewise.h>
+#include <2geom/path-intersection.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <algorithm>
+
+
+using namespace Geom;
+
+
+class np_finder
+{
+public:
+ np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2)
+ : cr(_cr), cc1(_c1), cc2(_c2), c1(_c1), c2(_c2)
+ {
+
+ dc1 = derivative(_c1);
+ dc2 = derivative(_c2);
+ cd1 = dot(_c1,dc1);
+ cd2 = dot(_c2,dc2);
+ dsq = 10e30;
+
+ Piecewise< D2<SBasis> > uv1 = unitVector(dc1, EPSILON);
+ Piecewise< D2<SBasis> > uv2 = unitVector(dc2, EPSILON);
+
+ dcn1 = dot(Piecewise< D2<SBasis> >(dc1), uv1);
+ dcn2 = dot(Piecewise< D2<SBasis> >(dc2), uv2);
+
+ r_dcn1 = cross(derivative(uv1), uv1);
+ r_dcn2 = cross(derivative(uv2), uv2);
+
+ k1 = Geom::divide(r_dcn1, dcn1, EPSILON, 3);
+ k2 = Geom::divide(r_dcn2, dcn2, EPSILON, 3);
+
+
+ n1 = divide(rot90(uv1), k1, EPSILON, 3);
+ n2 = divide(rot90(uv2), k2, EPSILON, 3);
+
+ std::vector<double> cuts1, cuts2;
+
+ // add cuts at points where the curvature is discontinuos
+ for ( unsigned int i = 1; i < k1.size(); ++i )
+ {
+ if( !are_near(k1[i-1].at1(), k1[i].at0()) )
+ {
+ cuts1.push_back(k1.cuts[i]);
+ }
+ }
+ for ( unsigned int i = 1; i < k2.size(); ++i )
+ {
+ if( !are_near(k2[i-1].at1(), k2[i].at0()) )
+ {
+ cuts2.push_back(k2.cuts[i]);
+ }
+ }
+
+ c1 = partition(c1, cuts1);
+ c2 = partition(c2, cuts2);
+
+// std::cerr << "# k1 discontinuitis" << std::endl;
+// for( unsigned int i = 0; i < cuts1.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << cuts1[i] << std::endl;
+// }
+// std::cerr << "# k2 discontinuitis" << std::endl;
+// for( unsigned int i = 0; i < cuts2.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << cuts2[i] << std::endl;
+// }
+
+ // add cuts at points were the curvature is zero
+ std::vector<double> k1_roots = roots(k1);
+ std::vector<double> k2_roots = roots(k2);
+ std::sort(k1_roots.begin(), k1_roots.end());
+ std::sort(k2_roots.begin(), k2_roots.end());
+ c1 = partition(c1, k1_roots);
+ c2 = partition(c2, k2_roots);
+
+// std::cerr << "# k1 zeros" << std::endl;
+// for( unsigned int i = 0; i < k1_roots.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << k1_roots[i] << std::endl;
+// }
+// std::cerr << "# k2 zeros" << std::endl;
+// for( unsigned int i = 0; i < k2_roots.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << k2_roots[i] << std::endl;
+// }
+
+
+ cairo_set_line_width(cr, 0.2);
+// cairo_set_source_rgba(cr, 0.0, 0.5, 0.0, 1.0);
+// for( unsigned int i = 1; i < c1.size(); ++i )
+// {
+// draw_circ(cr, c1[i].at0() );
+// }
+// for( unsigned int i = 1; i < c2.size(); ++i )
+// {
+// draw_circ(cr, c2[i].at0() );
+// }
+
+
+ // add cuts at nearest points to the other curve cuts points
+ cuts1.clear();
+ cuts1.reserve(c1.size()+1);
+ for ( unsigned int i = 0; i < c1.size(); ++i )
+ {
+ cuts1.push_back( nearest_time(c1[i].at0(), _c2, dc2, cd2) );
+ }
+ cuts1.push_back( nearest_time(c1[c1.size()-1].at1(), _c2, dc2, cd2) );
+
+// for ( unsigned int i = 0; i < c1.size(); ++i )
+// {
+// cairo_move_to( cr, c1[i].at0() );
+// cairo_line_to(cr, c2(cuts1[i]) );
+// }
+// cairo_move_to( cr, c1[c1.size()-1].at1() );
+// cairo_line_to(cr, c2(cuts1[c1.size()]));
+
+ std::sort(cuts1.begin(), cuts1.end());
+
+ cuts2.clear();
+ cuts2.reserve(c2.size()+1);
+ for ( unsigned int i = 0; i < c2.size(); ++i )
+ {
+ cuts2.push_back( nearest_time(c2[i].at0(), _c1, dc1, cd1) );
+ }
+ cuts2.push_back( nearest_time(c2[c2.size()-1].at1(), _c1, dc1, cd1) );
+
+// for ( unsigned int i = 0; i < c2.size(); ++i )
+// {
+// cairo_move_to( cr, c2[i].at0() );
+// cairo_line_to(cr, c1(cuts2[i]) );
+// }
+// cairo_move_to( cr, c2[c2.size()-1].at1() );
+// cairo_line_to(cr, c1(cuts2[c2.size()]));
+// cairo_stroke(cr);
+
+ std::sort(cuts2.begin(), cuts2.end());
+
+ c1 = partition(c1, cuts2);
+ c2 = partition(c2, cuts1);
+
+
+ // copy curve to preserve cuts status
+ Piecewise< D2<SBasis> > pwc1 = c1;
+ n1 = partition(n1, pwc1.cuts);
+ pwc1 = partition(pwc1, n1.cuts);
+ r_dcn1 = partition(r_dcn1, n1.cuts);
+ Piecewise< D2<SBasis> > pwc2 = c2;
+ n2 = partition(n2, pwc2.cuts);
+ pwc2 = partition(pwc2, n2.cuts);
+
+ assert( pwc1.size() == n1.size() );
+ assert( pwc2.size() == n2.size() );
+ assert( r_dcn1.size() == n1.size() );
+
+ // add cuts at curvature max and min points
+ Piecewise<SBasis> dk1 = derivative(k1);
+ Piecewise<SBasis> dk2 = derivative(k2);
+ std::vector<double> dk1_roots = roots(dk1);
+ std::vector<double> dk2_roots = roots(dk2);
+ std::sort(dk1_roots.begin(), dk1_roots.end());
+ std::sort(dk2_roots.begin(), dk2_roots.end());
+
+ c1 = partition(c1, dk1_roots);
+ c2 = partition(c2, dk2_roots);
+
+// std::cerr << "# k1 min/max" << std::endl;
+// for( unsigned int i = 0; i < dk1_roots.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << dk1_roots[i] << std::endl;
+// }
+// std::cerr << "# k2 min/max" << std::endl;
+// for( unsigned int i = 0; i < dk2_roots.size(); ++i )
+// {
+// std::cerr << "[" << i << "]= " << dk2_roots[i] << std::endl;
+// }
+
+// cairo_set_source_rgba(cr, 0.0, 0.0, 0.6, 1.0);
+// for( unsigned int i = 0; i < dk1_roots.size(); ++i )
+// {
+// draw_handle(cr, c1(dk1_roots[i]));
+// }
+// for( unsigned int i = 0; i < dk2_roots.size(); ++i )
+// {
+// draw_handle(cr, c2(dk2_roots[i]));
+// }
+
+
+ // add cuts at nearest points to max and min curvature points
+ // of the other curve
+ cuts1.clear();
+ cuts1.reserve(dk2_roots.size());
+ for (double dk2_root : dk2_roots)
+ {
+ cuts1.push_back(nearest_time(_c2(dk2_root), _c1, dc1, cd1));
+ }
+
+// for( unsigned int i = 0; i < dk2_roots.size(); ++i )
+// {
+// cairo_move_to(cr, c2(dk2_roots[i]));
+// cairo_line_to(cr, c1(cuts1[i]));
+// }
+// cairo_stroke(cr);
+
+ std::sort(cuts1.begin(), cuts1.end());
+ c1 = partition(c1, cuts1);
+
+
+ // swap normal vector direction and fill the skip list
+ skip_list.clear();
+ skip_list.resize(c1.size(), false);
+ double npt;
+ Point p, nv;
+ unsigned int si;
+ for ( unsigned int i = 0; i < pwc1.size(); ++i )
+ {
+ p = pwc1[i](0.5);
+ nv = n1[i](0.5);
+ npt = nearest_time(p, _c2, dc2, cd2);
+ if( dot( _c2(npt) - p, nv ) > 0 )
+ {
+ if ( dot( nv, n2(npt) ) > 0 )
+ {
+ n1[i] = -n1[i];
+ r_dcn1[i] = -r_dcn1[i];
+ }
+ else
+ {
+ si = c1.segN( n1.mapToDomain(0.5, i) );
+ skip_list[si] = true;
+ }
+ }
+ }
+
+
+ for ( unsigned int i = 0; i < pwc2.size(); ++i )
+ {
+ p = pwc2[i](0.5);
+ nv = n2[i](0.5);
+ npt = nearest_time(p, _c1, dc1, cd1);
+ if( dot( _c1(npt) - p, nv ) > 0 )
+ {
+ if ( dot( nv, n1(npt) ) > 0 )
+ {
+ n2[i] = -n2[i];
+ }
+ }
+ }
+
+
+ evl1 = c1 + n1;
+ evl2 = c2 + n2;
+
+// cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+// for ( unsigned int i = 0; i < c1.size(); ++i )
+// {
+// double t = c1.mapToDomain(0.5, i);
+// cairo_move_to(cr, c1(t));
+// cairo_line_to(cr, c1(t) + 30*unit_vector(n1(t)));
+// }
+//
+// for ( unsigned int i = 0; i < c2.size(); ++i )
+// {
+// double t = c2.mapToDomain(0.5, i);
+// cairo_move_to(cr, c2(t));
+// cairo_line_to(cr, c2(t) + 30*unit_vector(n2(t)));
+// }
+// cairo_stroke(cr);
+
+ std::cerr << "# skip list: ";
+ for( unsigned int i = 0; i < c1.cuts.size(); ++i )
+ {
+ if ( skip_list[i] )
+ std::cerr << i << " ";
+ }
+ std::cerr << std::endl;
+
+ cairo_set_line_width(cr, 0.4);
+ cairo_set_source_rgba(cr, 0.6, 0.0, 0.0, 1.0);
+ for( unsigned int i = 0; i < c1.size(); ++i )
+ {
+ if ( skip_list[i] )
+ {
+ cairo_move_to(cr, c1[i].at0());
+ cairo_line_to(cr, c1[i].at1());
+ }
+ }
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
+ for( unsigned int i = 1; i < c1.size(); ++i )
+ {
+ draw_circ(cr, c1[i].at0() );
+ }
+ cairo_stroke(cr);
+
+ std::cerr << "# c1 cuts: " << std::endl;
+ for( unsigned int i = 0; i < c1.cuts.size(); ++i )
+ {
+ std::cerr << "c1.cuts[" << i << "]= " << c1.cuts[i] << std::endl;
+ }
+
+ }
+
+ void operator() ()
+ {
+ nearest_times_impl();
+ d = sqrt(dsq);
+ }
+
+ Point firstPoint() const
+ {
+ return p1;
+ }
+
+ Point secondPoint() const
+ {
+ return p2;
+ }
+
+ double firstValue() const
+ {
+ return t1;
+ }
+
+ double secondValue() const
+ {
+ return t2;
+ }
+
+ double distance() const
+ {
+ return d;
+ }
+
+private:
+ void nearest_times_impl()
+ {
+ double t;
+ for ( unsigned int i = 0; i < c1.size(); ++i )
+ {
+ if ( skip_list[i] ) continue;
+ std::cerr << i << " ";
+ t = c1.mapToDomain(0.5, i);
+ std::pair<double, double> npc = loc_nearest_times(t, c1.cuts[i], c1.cuts[i+1]);
+ if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) )
+ {
+ t1 = npc.first;
+ t2 = npc.second;
+ p1 = c1(t1);
+ p2 = c2(t2);
+ dsq = L2sq(p1 - p2);
+ }
+ }
+ }
+
+ std::pair<double, double>
+ loc_nearest_times( double t, double from = 0, double to = 1 )
+ {
+ std::cerr << "[" << from << "," << to << "] t: " << t << std::endl;
+ unsigned int iter = 0, iter1 = 0, iter2 = 0;
+ std::pair<double, double> np(-1,-1);
+ std::pair<double, double> npf(from, -1);
+ std::pair<double, double> npt(to, -1);
+ double ct = t;
+ double pt = -1;
+ double s = nearest_time(c1(t), cc2, dc2, cd2);
+ cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0);
+ cairo_move_to(cr, c1(t));
+ while( !are_near(ct, pt) && iter < 1000 )
+ {
+ pt = ct;
+ double angle = angle_between( n1(ct), evl2(s) - evl1(ct) );
+ assert( !std::isnan(angle) );
+ angle = (angle > 0) ? angle - M_PI : angle + M_PI;
+ if ( std::fabs(angle) < M_PI/12 )
+ {
+ ++iter2;
+// cairo_move_to(cr, c1(ct));
+// cairo_line_to(cr, evl1(ct));
+// cairo_line_to(cr, evl2(s));
+ //std::cerr << "s: " << s << std::endl;
+ //std::cerr << "t: " << ct << std::endl;
+
+ ct = ct + angle / r_dcn1(ct);
+ s = nearest_time(c1(ct), cc2, dc2, cd2);
+// angle = angle_between( n2(s), evl1(ct) - evl2(s) );
+// assert( !std::isnan(angle) );
+// angle = (angle > 0) ? angle - M_PI : angle + M_PI;
+// s = s + angle / (dcn2(s) * k2(s));
+ }
+ else
+ {
+ ++iter1;
+ ct = nearest_time(c2(s), cc1, dc1, cd1, from, to);
+ s = nearest_time(c1(ct), cc2, dc2, cd2);
+ }
+ iter = iter1 + iter2;
+ //std::cerr << "s: " << s << std::endl;
+ //std::cerr << "t: " << ct << std::endl;
+ //cairo_line_to(cr, c2(s));
+ //cairo_line_to(cr, c1(ct));
+ //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl;
+ if ( ct < from )
+ {
+ std::cerr << "break left" << std::endl;
+ np = npf;
+ break;
+ }
+ if ( ct > to )
+ {
+ std::cerr << "break right" << std::endl;
+ np =npt;
+ break;
+ }
+ }
+ //std::cerr << "\n \n";
+ std::cerr << "iterations: " << iter1 << " + " << iter2 << " = "<< iter << std::endl;
+ assert(iter < 3000);
+ //cairo_move_to(cr, c1(ct));
+ //cairo_line_to(cr, c2(s));
+ cairo_stroke(cr);
+ np.first = ct;
+ np.second = s;
+ return np;
+ }
+
+ double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 )
+ {
+ D2<SBasis> sbc = c - p;
+ SBasis dd = cd - dotp(p, dc);
+ std::vector<double> zeros = roots(dd);
+ double closest = from;
+ double distsq = L2sq(sbc(from));
+ for (double zero : zeros)
+ {
+ if ( distsq > L2sq(sbc(zero)) )
+ {
+ closest = zero;
+ distsq = L2sq(sbc(closest));
+ }
+ }
+ if ( distsq > L2sq(sbc(to)) )
+ closest = to;
+ return closest;
+ }
+
+ SBasis dotp(Point const& p, D2<SBasis> const& c)
+ {
+ SBasis d;
+ d.resize(c[X].size());
+ for ( unsigned int i = 0; i < c[0].size(); ++i )
+ {
+ for( unsigned int j = 0; j < 2; ++j )
+ d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j];
+ }
+ return d;
+ }
+
+ Piecewise< D2<SBasis> >
+ divide( Piecewise< D2<SBasis> > const& a, Piecewise<SBasis> const& b, double tol, unsigned int k, double zero=1.e-3)
+ {
+ D2< Piecewise<SBasis> > aa = make_cuts_independent(a);
+ D2< Piecewise<SBasis> > q(Geom::divide(aa[0], b, tol, k, zero), Geom::divide(aa[1], b, tol, k, zero));
+ return sectionize(q);
+ }
+
+ struct are_near_
+ {
+ bool operator() (double x, double y, double eps = Geom::EPSILON )
+ {
+ return are_near(x, y, eps);
+ }
+ };
+
+private:
+ cairo_t* cr;
+ D2<SBasis> const& cc1, cc2;
+ Piecewise< D2<SBasis> > c1, c2;
+ D2<SBasis> dc1, dc2;
+ SBasis cd1, cd2;
+ Piecewise< D2<SBasis> > n1, n2, evl1, evl2;
+ Piecewise<SBasis> k1, k2, dcn1, dcn2, r_dcn1, r_dcn2;
+ double t1, t2, d, dsq;
+ Point p1, p2;
+ std::vector<bool> skip_list;
+};
+
+
+
+
+class NearestPoints : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.3);
+ D2<SBasis> A = pshA.asBezier();
+ cairo_d2_sb(cr, A);
+ D2<SBasis> B = pshB.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ np_finder np(cr, A, B);
+ Path AP, BP;
+ AP.append(A); BP.append(B);
+ Crossings ip_list = curve_sweep<SimpleCrosser>(AP, BP);
+ if( ip_list.empty() )
+ {
+ np();
+ cairo_set_line_width (cr, 0.4);
+ cairo_set_source_rgba(cr, 0.7, 0.0, 0.7, 1.0);
+ cairo_move_to(cr, np.firstPoint());
+ cairo_line_to(cr, np.secondPoint());
+ cairo_stroke(cr);
+ //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl;
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ handles.push_back(&pshA);
+ handles.push_back(&pshB);
+ for ( unsigned int i = 0; i < A_bez_ord; ++i )
+ pshA.push_back(Geom::Point(uniform()*400, uniform()*400));
+ for ( unsigned int i = 0; i < B_bez_ord; ++i )
+ pshB.push_back(Geom::Point(uniform()*400, uniform()*400));
+
+ }
+
+ private:
+ PointSetHandle pshA, pshB;
+ unsigned int A_bez_ord;
+ unsigned int B_bez_ord;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord=8;
+ unsigned int B_bez_ord=5;
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+
+ init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord));
+ 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/src/toys/curve-intersection-by-bezier-clipping.cpp b/src/toys/curve-intersection-by-bezier-clipping.cpp
new file mode 100644
index 0000000..dfd0a56
--- /dev/null
+++ b/src/toys/curve-intersection-by-bezier-clipping.cpp
@@ -0,0 +1,127 @@
+/*
+ * Show off crossings between two Bezier curves.
+ * The intersection points are found by using Bezier clipping.
+ *
+ * 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 <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/d2.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/sbasis-to-bezier.h>
+
+
+
+
+using namespace Geom;
+
+
+class CurveIntersect : public Toy
+{
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.8, 0., 0, 1);
+ //pshA.pts.back() = pshB.pts[0];
+ D2<SBasis> A = pshA.asBezier();
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 0.0, 0., 0, 1);
+ D2<SBasis> B = pshB.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ find_intersections_bezier_clipping(xs, pshA.pts, pshB.pts, m_precision);
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1);
+ for (auto & x : xs)
+ {
+ draw_handle(cr, A(x.first));
+ draw_handle(cr, B(x.second));
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+public:
+ CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ handles.push_back(&pshA);
+ for (unsigned int i = 0; i <= A_bez_ord; ++i)
+ pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200));
+ handles.push_back(&pshB);
+ for (unsigned int i = 0; i <= B_bez_ord; ++i)
+ pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200));
+
+ m_precision = 1e-6;
+ }
+
+private:
+ unsigned int A_bez_ord, B_bez_ord;
+ PointSetHandle pshA, pshB, pshC;
+ std::vector< std::pair<double, double> > xs;
+ double m_precision;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord = 6;
+ unsigned int B_bez_ord = 8;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+
+
+ init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800);
+ 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/src/toys/curve-intersection-by-implicitization.cpp b/src/toys/curve-intersection-by-implicitization.cpp
new file mode 100644
index 0000000..e071911
--- /dev/null
+++ b/src/toys/curve-intersection-by-implicitization.cpp
@@ -0,0 +1,300 @@
+/*
+ * Show off crossings between two D2<SBasis> curves.
+ * The intersection points are found by using implicitization tecnique.
+ *
+ * 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 <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis-poly.h>
+#include <2geom/numeric/linear_system.h>
+#include <2geom/symbolic/implicit.h>
+
+
+using namespace Geom;
+
+/*
+ * helper routines
+ */
+void poly_to_mvpoly1(SL::MVPoly1 & p, Geom::Poly const& q)
+{
+ for (size_t i = 0; i < q.size(); ++i)
+ {
+ p.coefficient(i, q[i]);
+ }
+ p.normalize();
+}
+
+void mvpoly1_to_poly(Geom::Poly & p, SL::MVPoly1 const& q)
+{
+ p.resize(q.get_poly().size());
+ for (size_t i = 0; i < q.get_poly().size(); ++i)
+ {
+ p[i] = q[i];
+ }
+}
+
+
+/*
+ * intersection_info
+ * structure utilized to store intersection info
+ *
+ * p - the intersection point
+ * t0 - the parameter t value at which the first curve pass through p
+ * t1 - the parameter t value at which the first curve pass through p
+ */
+struct intersection_info
+{
+ intersection_info()
+ {}
+
+ intersection_info(Point const& _p, Coord _t0, Coord _t1)
+ : p(_p), t0(_t0), t1(_t1)
+ {}
+
+ Point p;
+ Coord t0, t1;
+};
+
+typedef std::vector<intersection_info> intersections_info;
+
+
+
+/*
+ * intersection algorithm
+ */
+void intersect(intersections_info& xs, D2<SBasis> const& A, D2<SBasis> const& B)
+{
+ using std::swap;
+
+ // supposing implicitization the most expensive step
+ // we perform a call to intersect with curve arguments swapped
+ if (A[0].size() > B[0].size())
+ {
+ intersect(xs, B, A);
+ for (auto & x : xs)
+ swap(x.t0, x.t1);
+
+ return;
+ }
+
+ // convert A from symmetric power basis to power basis
+ Geom::Poly A0 = sbasis_to_poly(A[0]);
+ Geom::Poly A1 = sbasis_to_poly(A[1]);
+
+ // convert to MultiPoly type
+ SL::MVPoly1 Af, Ag;
+ poly_to_mvpoly1(Af, A0);
+ poly_to_mvpoly1(Ag, A1);
+
+ // compute a basis of the ideal related to the curve A
+ // in vector form
+ Geom::SL::basis_type b;
+ // if we compute the micro-basis the bezout matrix is made up
+ // by one only entry so we can't do the inversion step.
+ if (A0.size() == 3)
+ {
+ make_initial_basis(b, Af, Ag);
+ }
+ else
+ {
+ microbasis(b, Af, Ag);
+ }
+
+ // we put the basis in of the form of two independent moving line
+ Geom::SL::MVPoly3 p, q;
+ basis_to_poly(p, b[0]);
+ basis_to_poly(q, b[1]);
+
+ // compute the Bezout matrix and the implicit equation of the curve A
+ Geom::SL::Matrix<Geom::SL::MVPoly2> BZ = make_bezout_matrix(p, q);
+ SL::MVPoly2 ic = determinant_minor(BZ);
+ ic.normalize();
+
+
+ // convert B from symmetric power basis to power basis
+ Geom::Poly B0 = sbasis_to_poly(B[0]);
+ Geom::Poly B1 = sbasis_to_poly(B[1]);
+
+ // convert to MultiPoly type
+ SL::MVPoly1 Bf, Bg;
+ poly_to_mvpoly1(Bf, B0);
+ poly_to_mvpoly1(Bg, B1);
+
+ // evaluate the implicit equation of A on B
+ // so we get an s(t) polynomial that give us
+ // the t values for B at which intersection happens
+ SL::MVPoly1 s = ic(Bf, Bg);
+
+ // convert s(t) to Poly type, in order to use the real_solve function
+ Geom::Poly z;
+ mvpoly1_to_poly(z, s);
+
+ // compute t values for the curve B at which intersection happens
+ std::vector<double> sol = solve_reals(z);
+
+ // filter the found solutions wrt the domain interval [0,1] of B
+ // and compute the related point coordinates
+ std::vector<double> pt;
+ pt.reserve(sol.size());
+ std::vector<Point> points;
+ points.reserve(sol.size());
+ for (double & i : sol)
+ {
+ if (i >= 0 && i <= 1)
+ {
+ pt.push_back(i);
+ points.push_back(B(pt.back()));
+ }
+ }
+
+ // case: A is parametrized by polynomial of degree 1
+ // we compute the t values of A at the intersection points
+ // and filter the results wrt the domain interval [0,1]
+ double t;
+ xs.clear();
+ xs.reserve(pt.size());
+ if (A0.size() == 2)
+ {
+ for (size_t i = 0; i < points.size(); ++i)
+ {
+ t = (points[i][X] - A0[0]) / A0[1];
+ if (t >= 0 && t <= 1)
+ {
+ xs.push_back(intersection_info(points[i], t, pt[i]));
+ }
+ }
+ return;
+ }
+
+ // general case
+ // we compute the value of the parameter t of A at each intersection point
+ // and we filter the final result wrt the domain interval [0,1]
+ // the computation is performed by using the inversion formula for each point
+ // As reference see:
+ // Sederberger - Computer Aided Geometric Design
+ // par 16.5 - Implicitization and Inversion
+ size_t n = BZ.rows();
+ Geom::NL::Matrix BZN(n, n);
+ Geom::NL::MatrixView BZV(BZN, 0, 0, n-1, n-1);
+ Geom::NL::VectorView cv = BZN.column_view(n-1);
+ Geom::NL::VectorView bv(cv, n-1);
+ Geom::NL::LinearSystem ls(BZV, bv);
+ for (size_t i = 0; i < points.size(); ++i)
+ {
+ // evaluate the first main minor of order n-1 at each intersection point
+ polynomial_matrix_evaluate(BZN, BZ, points[i]);
+ // solve the linear system with the powers of t as unknowns
+ ls.SV_solve();
+ // the last element contains the t value
+ t = -ls.solution()[n-2];
+ // filter with respect to the domain of A
+ if (t >= 0 && t <= 1)
+ {
+ xs.push_back(intersection_info(points[i], t, pt[i]));
+ }
+ }
+}
+
+
+
+class IntersectImplicit : public Toy
+{
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.8, 0., 0, 1);
+ D2<SBasis> A = pshA.asBezier();
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 0.0, 0., 0, 1);
+ D2<SBasis> B = pshB.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ intersect(xs, A, B);
+ for (auto & x : xs)
+ {
+ draw_handle(cr, x.p);
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+public:
+ IntersectImplicit(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ handles.push_back(&pshA);
+ for (unsigned int i = 0; i <= A_bez_ord; ++i)
+ pshA.push_back(Geom::Point(uniform()*400, uniform()*400));
+ handles.push_back(&pshB);
+ for (unsigned int i = 0; i <= B_bez_ord; ++i)
+ pshB.push_back(Geom::Point(uniform()*400, uniform()*400));
+
+ }
+
+private:
+ unsigned int A_bez_ord, B_bez_ord;
+ PointSetHandle pshA, pshB;
+ intersections_info xs;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord = 4;
+ unsigned int B_bez_ord = 6;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+
+
+ init( argc, argv, new IntersectImplicit(A_bez_ord, B_bez_ord));
+ 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/src/toys/cylinder3d.cpp b/src/toys/cylinder3d.cpp
new file mode 100644
index 0000000..19f3440
--- /dev/null
+++ b/src/toys/cylinder3d.cpp
@@ -0,0 +1,253 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+
+#include <gsl/gsl_matrix.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+Geom::Point orig;
+
+static void draw_box (cairo_t *cr, Geom::Point corners[8]);
+static void draw_slider_lines (cairo_t *cr);
+static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles);
+
+double tmat[3][4];
+double c[8][4];
+Geom::Point corners[8];
+
+class Box3d: public Toy {
+ std::vector<Toggle> togs;
+ Path path_a;
+ Piecewise<D2<SBasis> > path_a_pw;
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ orig = hand.pts[7];
+
+ Geom::Point dir(1,-2);
+
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+
+ // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions
+ draw_slider_lines (cr);
+ hand.pts[4][0] = 30;
+ hand.pts[5][0] = 45;
+ hand.pts[6][0] = 60;
+
+ // draw the curve that is supposed to be projected on the box's front face
+ vector<Geom::Point>::iterator it = hand.pts.begin();
+ for (int j = 0; j < 7; ++j) ++it;
+
+ /* create the transformation matrix for the map P^3 --> P^2 that has the following effect:
+ (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0)
+ (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1)
+ (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2)
+ (0 : 0 : 0 : 1) --> origin (= handle #3)
+ */
+ for (int j = 0; j < 4; ++j) {
+ tmat[0][j] = hand.pts[j][0];
+ tmat[1][j] = hand.pts[j][1];
+ tmat[2][j] = 1;
+ }
+
+ *notify << "Projection matrix:" << endl;
+ for (auto & i : tmat) {
+ for (double j : i) {
+ *notify << j << " ";
+ }
+ *notify << endl;
+ }
+
+ // draw the projective images of the box's corners
+ for (int i = 0; i < 8; ++i) {
+ corners[i] = proj_image (cr, c[i], hand.pts);
+ }
+ draw_box(cr, corners);
+ cairo_set_line_width (cr, 2);
+ cairo_stroke(cr);
+
+ {
+ D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw);
+ Piecewise<SBasis> preimage[4];
+
+ if(togs[0].on) {
+ preimage[0] = sin((B[0] - orig[0]) / 100);
+ preimage[1] = -(B[1] - orig[1]) / 100;
+ preimage[2] = cos((B[0] - orig[0]) / 100);
+ } else { //if(togs[1].state) {
+ Piecewise<SBasis> sphi = sin((B[0] - orig[0]) / 200);
+ Piecewise<SBasis> cphi = cos((B[0] - orig[0]) / 200);
+
+ preimage[0] = -sphi*sin((B[1] - orig[1]) / 200);
+ preimage[1] = -sphi*cos((B[1] - orig[1]) / 200);
+ preimage[2] = -cphi;
+ }
+ Piecewise<SBasis> res[3];
+ for (int j = 0; j < 3; ++j) {
+ res[j] =
+ (preimage[0]) * tmat[j][0]
+ + (preimage[1] - ((hand.pts[5][1]-300)/100)) * tmat[j][1]
+ + (preimage[2] - ((hand.pts[6][1]-00)/100)) * tmat[j][2]
+ +( - (hand.pts[4][1]-300)/100) * tmat[j][0] + tmat[j][3];
+ }
+ //if (fabs (res[2]) > 0.000001) {
+ D2<Piecewise<SBasis> > result(divide(res[0],res[2], 4),
+ divide(res[1],res[2], 4));
+
+ cairo_d2_pw_sb(cr, result);
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+ }
+ draw_toggles(cr, togs);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="star.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ PathVector paths_a = read_svgd(path_a_name);
+ assert(!paths_a.empty());
+ path_a = paths_a[0];
+
+ path_a.close(true);
+ path_a_pw = path_a.toPwSb();
+
+ // Finite images of the three vanishing points and the origin
+ hand.pts.emplace_back(150,300);
+ hand.pts.emplace_back(380,40);
+ hand.pts.emplace_back(550,350);
+ hand.pts.emplace_back(340,450);
+
+ // Hand.Pts for moving in axes directions
+ hand.pts.emplace_back(30,300);
+ hand.pts.emplace_back(45,300);
+ hand.pts.emplace_back(60,300);
+
+ // Box corners
+ for (int i = 0; i < 8; ++i) {
+ c[i][0] = ((i & 1) ? 1 : 0);
+ c[i][1] = ((i & 2) ? 1 : 0);
+ c[i][2] = ((i & 4) ? 1 : 0);
+ c[i][3] = 1;
+ }
+
+ // Origin handle
+ hand.pts.emplace_back(180,70);
+ togs.emplace_back("S", true);
+ handles.push_back(&hand);
+ }
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 'c') togs[0].set(1); else
+ if(e->keyval == 's') togs[0].set(0);
+ redraw();
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(togs, e);
+ Toy::mouse_pressed(e);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Box3d);
+ return 0;
+}
+
+void draw_box (cairo_t *cr, Geom::Point corners[8]) {
+ cairo_move_to(cr,corners[0]);
+ cairo_line_to(cr,corners[1]);
+ cairo_line_to(cr,corners[3]);
+ cairo_line_to(cr,corners[2]);
+ cairo_close_path(cr);
+
+ cairo_move_to(cr,corners[4]);
+ cairo_line_to(cr,corners[5]);
+ cairo_line_to(cr,corners[7]);
+ cairo_line_to(cr,corners[6]);
+ cairo_close_path(cr);
+
+ cairo_move_to(cr,corners[0]);
+ cairo_line_to(cr,corners[4]);
+
+ cairo_move_to(cr,corners[1]);
+ cairo_line_to(cr,corners[5]);
+
+ cairo_move_to(cr,corners[2]);
+ cairo_line_to(cr,corners[6]);
+
+ cairo_move_to(cr,corners[3]);
+ cairo_line_to(cr,corners[7]);
+}
+
+void draw_slider_lines (cairo_t *cr) {
+ cairo_move_to(cr, Geom::Point(20,300));
+ cairo_line_to(cr, Geom::Point(70,300));
+
+ cairo_move_to(cr, Geom::Point(30,00));
+ cairo_line_to(cr, Geom::Point(30,450));
+
+ cairo_move_to(cr, Geom::Point(45,00));
+ cairo_line_to(cr, Geom::Point(45,450));
+
+ cairo_move_to(cr, Geom::Point(60,00));
+ cairo_line_to(cr, Geom::Point(60,450));
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+}
+
+static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles)
+{
+ double res[3];
+ for (int j = 0; j < 3; ++j) {
+ res[j] =
+ tmat[j][0] * (pt[0] - (handles[4][1]-300)/100)
+ + tmat[j][1] * (pt[1] - (handles[5][1]-300)/100)
+ + tmat[j][2] * (pt[2] - (handles[6][1]-300)/100)
+ + tmat[j][3] * pt[3];
+ }
+ if (fabs (res[2]) > 0.000001) {
+ Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]);
+ draw_handle(cr, result);
+ return result;
+ }
+ assert(0); // unclipped point
+ return Geom::Point(0,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/src/toys/d2sbasis-fitting-with-np.cpp b/src/toys/d2sbasis-fitting-with-np.cpp
new file mode 100644
index 0000000..05bcc6c
--- /dev/null
+++ b/src/toys/d2sbasis-fitting-with-np.cpp
@@ -0,0 +1,147 @@
+/*
+ * D2<SBasis> Fitting Example
+ *
+ * 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/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+class D2SBasisFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ bool changed = false;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ if (psh.pts[i] != prev_pts[i])
+ {
+ changed = true;
+ break;
+ }
+ }
+ if (changed)
+ {
+ for (size_t k = 0; k < 200; ++k)
+ {
+ lsf_2dsb.clear();
+ lsf_2dsb.append(0);
+ lsf_2dsb.append(0.33);
+ double t = 0;
+ for (size_t i = 2; i < total_handles-2; ++i)
+ {
+ t = nearest_time(psh.pts[i], sb_curve);
+ lsf_2dsb.append(t);
+ }
+ lsf_2dsb.append(0.66);
+ lsf_2dsb.append(1);
+ lsf_2dsb.update();
+ fmd2sb.instance(sb_curve, lsf_2dsb.result(psh.pts));
+ }
+ prev_pts = psh.pts;
+ }
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ cairo_d2_sb(cr, sb_curve);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ D2SBasisFitting()
+ : sb_curve(),
+ total_handles(8),
+ order(total_handles / 2 - 1),
+ fmd2sb(order),
+ lsf_2dsb(fmd2sb, total_handles),
+ prev_pts()
+ {
+ step = 1.0 / (total_handles - 1);
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ psh.push_back(400*uniform() + 50, 300*uniform() + 50);
+ }
+ handles.push_back(&psh);
+
+ double t = 0;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ lsf_2dsb.append(t);
+ t += step;
+ }
+ prev_pts = psh.pts;
+ lsf_2dsb.update();
+ fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts));
+ }
+
+ private:
+ D2<SBasis> sb_curve;
+ unsigned int total_handles;
+ unsigned int order;
+ NL::LFMD2SBasis fmd2sb;
+ NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb;
+ std::vector<Point> prev_pts;
+ double step;
+ PointSetHandle psh;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new D2SBasisFitting(), 600, 600 );
+ 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/src/toys/d2sbasis-fitting.cpp b/src/toys/d2sbasis-fitting.cpp
new file mode 100644
index 0000000..7e9b233
--- /dev/null
+++ b/src/toys/d2sbasis-fitting.cpp
@@ -0,0 +1,120 @@
+/*
+ * D2<SBasis> Fitting Example
+ *
+ * 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/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+class D2SBasisFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts, psh.pts));
+ prev_pts = psh.pts;
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ cairo_d2_sb(cr, sb_curve);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ D2SBasisFitting()
+ : sb_curve(),
+ total_handles(6),
+ order(total_handles / 2 - 1),
+ fmd2sb(order),
+ lsf_2dsb(fmd2sb, total_handles),
+ prev_pts()
+ {
+ step = 1.0 / (total_handles - 1);
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ psh.push_back(400*uniform() + 50, 300*uniform() + 50);
+ }
+ handles.push_back(&psh);
+
+ double t = 0;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ lsf_2dsb.append(t);
+ t += step;
+ }
+ prev_pts = psh.pts;
+ lsf_2dsb.update();
+ fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts));
+ }
+
+ private:
+ D2<SBasis> sb_curve;
+ unsigned int total_handles;
+ unsigned int order;
+ NL::LFMD2SBasis fmd2sb;
+ NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb;
+ std::vector<Point> prev_pts;
+ double step;
+ PointSetHandle psh;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new D2SBasisFitting(), 600, 600 );
+ 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/src/toys/data/london-locations.csv b/src/toys/data/london-locations.csv
new file mode 100644
index 0000000..e7299c9
--- /dev/null
+++ b/src/toys/data/london-locations.csv
@@ -0,0 +1,298 @@
+Harrow & Wealdstone,75.2,163.5
+Kenton,86.1,153.6
+South Kenton,91,143.5
+North Wembley,94.2,136.8
+Wembley Central,99.1,128
+Stonebridge Park,110.7,122.8
+Harlesden,120.1,116.7
+Willesden Junction,128.3,112.4
+Kensal Green,137.7,111.6
+Queen's Park,148.6,113.5
+Kilburn Park,155.2,115.1
+Maida Vale,158.7,110
+Warwick Avenue,161.1,104.9
+Paddington,164.1,98.5
+Edgware Road,168.2,103.7
+Marylebone,171,105.1
+Baker Street,175.3,105.1
+Regent's Park,181.7,103.9
+Oxford Circus,184.3,97.5
+Piccadilly Circus,188.3,92.5
+Charing Cross,192.9,91.3
+Embankment,195.2,91
+Waterloo,199.6,87.6
+Lambeth North,201.3,84.9
+Elephant & Castle,208.3,81.1
+West Ruislip,20.7,139.9
+Ruislip Gardens,35.6,133.5
+South Ruislip,42.8,130.5
+Northolt,58.8,123.9
+Greenford,72.4,119.4
+Perivale,84.3,115.6
+Hanger Lane,103,109.7
+North Acton,121.2,104.4
+Ealing Broadway,98.3,93.7
+West Acton,108.6,98.8
+East Acton,127.1,99
+White City,138.5,95.1
+Shepherd's Bush,141.7,87.7
+Holland Park,148,90.3
+Notting Hill Gate,152.6,91.8
+Queensway,158.4,92.9
+Lancaster Gate,165,94
+Marble Arch,174.8,96
+Bond Street,179.8,96.8
+Tottenham Court Road,189.9,98.4
+Holborn,195.7,99.3
+Chancery Lane,199.9,100.1
+St Paul's,207.8,97.8
+Bank,213,96.1
+Liverpool Street,216.9,101
+Bethnal Green,234.3,109.1
+Mile End,243.4,107.7
+Stratford,259.5,121.2
+Leyton,259.1,136.9
+Leytonstone,266.9,145.2
+Snaresbrook,272.5,153.7
+South Woodford,276,164.6
+Woodford,279.7,180.2
+Wanstead,277.9,150.9
+Redbridge,286.9,152.9
+Gants Hill,296.3,152.4
+Newbury Park,310.1,152
+Barkingside,309.3,160.9
+Fairlop,310.4,170.7
+Hainault,311.4,175.2
+Grange Hill,310.4,185.8
+Chigwell,299.8,190.2
+Roding Valley,285.7,190.8
+Buckhurst Hill,285.6,195.5
+Loughton,290.1,213.7
+Debden,306.5,214.6
+Theydon Bois,315.1,241.9
+Epping,320.3,264.7
+Tower Hill,220.7,94.3
+Aldgate,220.4,98.8
+Moorgate,212.5,102.2
+Barbican,208.1,102.9
+Farringdon,203.2,105
+King's Cross St Pancras,193.1,112.1
+Euston Square,188.1,107.1
+Great Portland Street,183.3,105.4
+Edgware Road,169.6,102.2
+Bayswater,158,95.3
+High Street Kensington,155.2,84.6
+Gloucester Road,161.2,78.8
+South Kensington,166.5,78.5
+Sloane Square,176.5,77.4
+Victoria,183,79.3
+St James's Park,187.9,83.1
+Westminster,193.7,85.4
+Temple,199.2,94.2
+Blackfriars,205.3,94.7
+Mansion House,210.3,94.3
+Cannon Street,213,93.8
+Monument,215.5,93.1
+Ealing Common,105.3,91.1
+Acton Town,109.2,85.1
+Chiswick Park,114.9,80.3
+Turnham Green,123.5,79.5
+Richmond,99.7,49.4
+Kew Gardens,107.8,61.9
+Gunnersbury,111.3,75.6
+Stamford Brook,128.1,79.3
+Ravenscourt Park,133.3,79.2
+Hammersmith,139.1,77.3
+Barons Court,144.1,75.1
+West Kensington,148.9,75.7
+Earl's Court,154.8,76.8
+Kensington (Olympia),146.3,82
+Wimbledon,152,15.1
+Wimbledon Park,155.2,25.7
+Southfields,150.2,36.6
+East Putney,147.5,46.4
+Putney Bridge,149.1,54.9
+Parsons Green,152.3,62.1
+Fulham Broadway,155.2,67.3
+West Brompton,154.4,72.7
+Aldgate East,222.9,99.8
+Whitechapel,228.6,102.8
+Stepney Green,236.3,105.3
+Bow Road,249.5,107.1
+Bromley-by-Bow,256.1,106.2
+West Ham,264.2,110.7
+Plaistow,271.2,113.4
+Upton Park,280.7,116
+East Ham,291.9,120.2
+Barking,305.8,121.1
+Upney,316.3,118.7
+Becontree,330.3,121.8
+Dagenham Heathway,340.3,122.7
+Dagenham East,350.3,125.4
+Elm Park,369.8,130.4
+Hornchurch,378.4,133.8
+Upminster Bridge,386.5,137.2
+Upminster,397.5,138.6
+Shoreditch,223,103.3
+Shadwell,231.3,96
+Wapping,231.8,89.2
+Rotherhithe,233.1,85.5
+Canada Water,234.3,83.5
+Surrey Quays,237.1,78
+New Cross Gate,241.3,63.5
+New Cross,245,64.3
+Hammersmith,137.7,80.1
+Goldhawk Road,136.8,85.7
+Shepherd's Bush,137,89.2
+Latimer Road,142.5,95.9
+Ladbroke Grove,145.5,99.1
+Westbourne Park,150,101.9
+Royal Oak,158.1,100.2
+Stanmore,93.9,186.2
+Canons Park,97.2,177.9
+Queensbury,101.1,165.5
+Kingsbury,106.7,156.6
+Wembley Park,106.4,139.6
+Neasden,123.8,131.6
+Dollis Hill,129.7,129.9
+Willesden Green,139.1,128.9
+Kilburn,147.4,128.2
+West Hampstead,157.2,127.2
+Finchley Road,162.4,126.9
+Swiss Cottage,164.7,123.3
+St John's Wood,164.8,115.1
+Green Park,184.1,89.6
+Southwark,204.4,88.5
+London Bridge,214.4,89.7
+Bermondsey,225,84.5
+Canary Wharf,254,88.1
+North Greenwich,269.3,88
+Canning Town,267.7,96.2
+Watford,29.9,219.2
+Croxley,15.2,210.9
+Moor Park,22,194.9
+Northwood,27.7,177.9
+Northwood Hills,35.4,169.1
+Pinner,51.6,163.4
+North Harrow,60.4,157.3
+Harrow-on-the-Hill,76,149.6
+Uxbridge,2,121.7
+Hillingdon,14,126.4
+Ickenham,17.6,133.5
+Ruislip,28.9,142.2
+Ruislip Manor,34.2,144.6
+Eastcote,43.2,147.3
+Rayners Lane,56.3,146.9
+West Harrow,66.4,150
+Northwick Park,84.8,149.9
+Preston Road,97.8,146.4
+Edgware,107.3,182.3
+Burnt Oak,113.7,173.6
+Colindale,122.1,167.5
+Hendon Central,135.2,157.6
+Bent Cross,141.9,151.7
+Golders Green,154.3,146.9
+Hampstead,164.8,135.3
+Belsize Park,171.2,129.1
+Chalk Farm,176.2,124.3
+Camden Town,182,121.2
+High Barnet,150.8,219.3
+Totteridge & Whetstone,159.7,200.8
+Woodside Park,156.8,188.9
+West Finchley,154.5,181.9
+Finchley Central,154.5,172.7
+Mill Hill East,142.9,180.2
+East Finchley,168.9,161.8
+Highgate,180.5,152.8
+Archway,186.2,144.1
+Tufnell Park,186,136.6
+Kentish Town,184.4,129.9
+Mornington Crescent,184.6,114.9
+Euston,187.9,110.1
+Angel,203.9,113.6
+Old Street,212.2,108.4
+Borough,210.5,85.8
+Kennington,204.3,74.8
+Warren Street,185.4,104.9
+Goodge Street,187.9,101.9
+Leicester Square,191.4,94.2
+Oval,200,69.1
+Stockwell,195.3,60.4
+Clapham North,191.2,52.6
+Clapham Common,186,49.2
+Clapham South,181.6,41.9
+Balham,178.6,34.2
+Tooting Bec,176.2,28.2
+Tooting Broadway,171.2,17.6
+Colliers Wood,166.8,11.2
+South Wimbledon,159.4,7.9
+Morden,155.1,1.5
+Heathrow Terminals 1-2-3,20.5,53.7
+Heathrow Terminal 4,24.4,46.8
+Hatton Cross,35,51.4
+Hounslow West,57,57
+Hounslow Central,65.2,55.4
+Hounslow East,70.8,57.8
+Osterley,74,65
+Boston Manor,87.1,76.2
+Northfields,91.8,80.2
+South Ealing,95.6,81.7
+South Harrow,67.8,137.1
+Sudbury Hill,75.6,131.2
+Sudbury Town,88.1,127
+Alperton,97.1,119
+Park Royal,107,106
+North Ealing,105.2,98.1
+Knightsbridge,173.7,85.1
+Hyde Park Corner,178.1,86.5
+Covent Garden,193.7,95.6
+Russell Square,192.9,104.7
+Caledonian Road,196,128.8
+Holloway Road,200.2,132.5
+Arsenal,202.7,137.9
+Finsbury Park,203.8,143.9
+Manor House,208.6,149.6
+Turnpike Lane,204.2,165.8
+Wood Green,200.5,172.4
+Bounds Green,190.4,181.2
+Arnos Grove,186.3,189
+Southgate,188.2,201.3
+Oakwood,186.3,216.8
+Cockfosters,177.6,220.3
+Brixton,201.7,52.2
+Vauxhall,194.7,72.4
+Pimlico,189.8,75.5
+Highbury & Islington,204.1,126
+Seven Sisters,219.2,160.5
+Tottenham Hale,228.2,164.8
+Blackhorse Road,238.5,162.7
+Walthamstow Central,253.4,161.1
+Limehouse,239.2,97.1
+Westferry,246.8,95.3
+Poplar,254.6,93.8
+All Saints,256.2,96.4
+Devons Road,253.3,103.8
+Bow Church,250.9,108.9
+Pudding Mill Lane,255.8,115.7
+West India Quay,252.2,91.3
+Heron Quay,252,86.8
+South Quay,254.1,85.5
+Crossharbour and London Arena,256.1,80.3
+Mudchute,257.1,76.5
+Island Gardens,259,74.3
+Cutty Sark,259.2,69.3
+Greenwich,256.1,65.7
+Deptford Bridge,253.9,63.7
+Elverston Road,254.6,58.3
+Lewisham,256.5,54.5
+Blackwall,258.7,93.7
+East India,263.3,94.3
+Royal Victoria,272.5,93
+Custom House,277.9,93.2
+Prince Regent,281.4,93.3
+Royal Albert,288.9,93.2
+Beckton Park,292.7,93.3
+Cyprus,296.6,93.3
+Gallions Reach,301.5,94.2
+Beckton,296,99.3
+Tower Gateway,222.7,94.7
diff --git a/src/toys/data/london.txt b/src/toys/data/london.txt
new file mode 100644
index 0000000..49e6f9c
--- /dev/null
+++ b/src/toys/data/london.txt
@@ -0,0 +1,86 @@
+Bakerloo Line:
+Harrow & Wealdstone,Kenton,South Kenton,North Wembley,Wembley Central,Stonebridge Park,Harlesden,Willesden Junction,Kensal Green,Queen's Park,Kilburn Park,Maida Vale,Warwick Avenue,Paddington,Edgware Road,Marylebone,Baker Street,Regent's Park,Oxford Circus,Piccadilly Circus,Charing Cross,Embankment,Waterloo,Lambeth North,Elephant & Castle
+
+Central Line 1:
+West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping
+
+Central Line 2:
+West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford
+
+Central Line 3:
+Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping
+
+Central Line 4:
+Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford
+
+Circle Line 1:
+Westminster,St James's Park,Victoria,Sloane Square,South Kensington,Gloucester Road,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate
+
+Circle Line 2:
+Tower Hill,Monument,Cannon Street,Mansion House,Blackfriars,Temple,Embankment,Westminster
+
+Hammersmith & City Line:
+Hammersmith,Goldhawk Road,Shepherd's Bush,Latimer Road,Ladbroke Grove,Westbourne Park,Royal Oak,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking
+
+District Line 1:
+Ealing Broadway,Ealing Common,Acton Town,Chiswick Park,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster
+
+District Line 2:
+Richmond,Kew Gardens,Gunnersbury,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster
+
+District Line 3:
+Wimbledon,Wimbledon Park,Southfields,East Putney,Putney Bridge,Parsons Green,Fulham Broadway,West Brompton,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster
+
+District Line 4:
+Kensington (Olympia),Earl's Court,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road
+
+East London Line 1:
+Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross
+
+East London Line 2:
+Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross Gate
+
+Jubilee Line:
+Stratford,West Ham,Canning Town,North Greenwich,Canary Wharf,Canada Water,Bermondsey,London Bridge,Southwark,Waterloo,Westminster,Green Park,Bond Street,Baker Street,St John's Wood,Swiss Cottage,Finchley Road,West Hampstead,Kilburn,Willesden Green,Dollis Hill,Neasden,Wembley Park,Kingsbury,Queensbury,Canons Park,Stanmore
+
+Metropolitan Line 1:
+Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate
+
+Metropolitan Line 2:
+Watford,Croxley,Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate
+
+Metropolitan Line 3:
+Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,West Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate
+
+Northern Line 1:
+High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Northern Line 2:
+High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Northern Line 3:
+Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Northern Line 4:
+Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Northern Line 5:
+Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Northern Line 6:
+Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden
+
+Piccadilly Line 1:
+Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,South Harrow,Sudbury Hill,Sudbury Town,Alperton,Park Royal,North Ealing,Ealing Common,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters
+
+Piccadilly Line 2:
+Heathrow Terminal 4,Hatton Cross,Hounslow West,Hounslow Central,Hounslow East,Osterley,Boston Manor,Northfields,South Ealing,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters
+
+Piccadilly Line 3:
+Hatton Cross,Heathrow Terminals 1-2-3,Heathrow Terminal 4
+
+Victoria Line:
+Walthamstow Central,Blackhorse Road,Tottenham Hale,Seven Sisters,Finsbury Park,Highbury & Islington,King's Cross St Pancras,Euston,Warren Street,Oxford Circus,Green Park,Victoria,Pimlico,Vauxhall,Stockwell,Brixton
+
+Waterloo & City Line:
+Waterloo,Bank
diff --git a/src/toys/data/nsw-centre.txt b/src/toys/data/nsw-centre.txt
new file mode 100644
index 0000000..73b6817
--- /dev/null
+++ b/src/toys/data/nsw-centre.txt
@@ -0,0 +1,56 @@
+Olympic Park I:
+Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park
+
+Olympic Park II:
+Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park
+
+Eastern Suburbs & Illawarra 1:
+Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+Eastern Suburbs & Illawarra 2:
+Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+Bankstown Line a:
+Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe
+
+Bankstown Line b:
+Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool
+
+Inner West 1a:
+Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Inner West 1b:
+Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood
+
+Inner West 2a:
+Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Inner West 2b:
+Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood
+
+Airport & East Hills 1:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall
+
+Airport & East Hills 2:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall
+
+South:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Cumberland:
+Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys
+
+Carlingford Line:
+Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde
+
+Western 1:
+Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+Western 2:
+Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+North Shore:
+Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta
+
+Northern:
+Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
diff --git a/src/toys/data/nsw-locations.csv b/src/toys/data/nsw-locations.csv
new file mode 100644
index 0000000..2cb6d13
--- /dev/null
+++ b/src/toys/data/nsw-locations.csv
@@ -0,0 +1,805 @@
+Aberdare Junction,151.513199,-32.773602
+Aberdeen,150.892593,-32.165401
+Adamstown,151.718307,-32.933899
+Albion Park,150.798203,-34.562698
+Albury,146.922302,-36.0863
+Alectown West,148.171906,-32.934101
+Allandale,151.416306,-32.720699
+Allawah,151.113098,-33.9692
+Alleena,147.134903,-34.072399
+Antiene,150.984695,-32.335999
+Ariah Park,147.2164,-34.344398
+Armidale,151.652206,-30.514
+Arncliffe,151.147095,-33.935299
+Artarmon,151.185593,-33.8069
+Ashfield,151.125504,-33.886799
+Ashley,149.800201,-29.297001
+Asquith,151.1082,-33.688202
+Athol,149.303604,-33.5452
+Auburn,151.032501,-33.8489
+Austinmer,150.928207,-34.305901
+Awaba,151.540604,-33.012501
+Baan Baa,149.951096,-30.599501
+Balldale,146.515106,-35.846199
+Ballina,153.554993,-28.860001
+Balmoral,150.521194,-34.305901
+Bangaroo,148.632599,-33.627602
+Banksia,151.138702,-33.944099
+Bankstown,151.034897,-33.917
+Barairmol,152.995102,-28.719601
+Barbigal,148.839905,-32.223202
+Bardwell Park,151.124405,-33.930901
+Bargo,150.579102,-34.291
+Bathurst,149.582199,-33.425999
+Beanbri,148.405701,-29.9536
+Beecroft,151.066803,-33.749199
+Beelbangera,146.099792,-34.254002
+Belford,151.274902,-32.6544
+Bell,150.278107,-33.506001
+Bellambi,150.909195,-34.362099
+Bellarwi,147.194702,-34.095001
+Bellata,149.786407,-29.918501
+Belmore,151.089203,-33.9161
+Bengerang,149.519608,-29.0714
+Beni,148.750595,-32.195099
+Berala,151.031906,-33.870998
+Beresfield,151.6586,-32.798901
+Berowra,151.153397,-33.623699
+Berry,150.695694,-34.780701
+Bethungra,147.860306,-34.763699
+Beverly Hills,151.082108,-33.9478
+Bexhill,153.346893,-28.761101
+Bexley North,151.111404,-33.937099
+Birrong,151.024399,-33.892601
+Black Springs,150.629395,-30.442801
+Blackheath,150.281998,-33.633301
+Blacktown,150.908707,-33.7668
+Blandford,150.898605,-31.788799
+Blaxland,150.608902,-33.742001
+Blayney,149.253799,-33.5261
+Bloomfield,149.105606,-33.319401
+Blue Cow,148.389801,-36.383701
+Boambee,153.1073,-30.348801
+Bogan Gate,147.801498,-33.108299
+Boggabri,150.038803,-30.702999
+Bomaderry,150.609695,-34.853298
+Bombo,150.852707,-34.658001
+Bomera,149.798798,-31.548
+Bon Accord,147.190903,-35.226501
+Bonalbo,152.623154,-28.73671
+Bondi Junction,151.246506,-33.893101
+Bonville,153.061996,-30.379
+Boomley,149.1035,-32.026798
+Booragul,151.604797,-32.970798
+Boothenba,148.689606,-32.198101
+Borenore,148.975693,-33.253101
+Bourke,145.933701,-30.094601
+Bowenfels,150.135406,-33.474998
+Bowral,150.416107,-34.4781
+Box Tank,142.2276,-32.201099
+Braemar,150.498596,-34.412498
+Branxton,151.345703,-32.6614
+Braunstone,152.961807,-29.8027
+Brightling,148.404999,-31.2185
+Broadmeadow,151.734406,-32.921902
+Broken Hill,141.466003,-31.9604
+Brolgan,148.063507,-33.142502
+Buddigower,147.008698,-34.0438
+Bullaburra,150.412796,-33.722301
+Bulli,150.9142,-34.333401
+Bulliac,152.035599,-31.914499
+Bullocks Flat,148.443207,-36.444698
+Bundanoon,150.300705,-34.655399
+Bundook,152.1259,-31.891199
+Bungabbee,153.146393,-28.780199
+Bungendore,149.444901,-35.254601
+Bunyan,149.158997,-36.1712
+Buralyang,146.760498,-33.974098
+Burgooney,146.574097,-33.385399
+Burradoo,150.397293,-34.493999
+Burrawang,150.529999,-34.581402
+Burren,148.966705,-30.1061
+Burringbar,153.471497,-28.4331
+Burwood,151.104706,-33.876301
+Buxton,150.532501,-34.2547
+Bygalorie,146.781693,-33.469002
+Byron,151.093994,-29.736401
+Cabramatta,150.938995,-33.8927
+Calleen,147.105804,-33.787701
+Calwalla,150.474304,-34.5625
+Camden,150.696899,-34.050701
+Camellia,151.025604,-33.817001
+Camira Creek,152.964203,-29.250401
+Campbelltown,150.812607,-34.063801
+Campsie,151.101303,-33.909901
+Canberra,149.150497,-35.3167
+Canberra (Civic),149.132904,-35.262299
+Canley Vale,150.944199,-33.886501
+Canterbury,151.117493,-33.91
+Capertee,149.986496,-33.149899
+Caragabal,147.740707,-33.839802
+Cardiff,151.662292,-32.941299
+Caringbah,151.121094,-34.0415
+Carlingford,151.046204,-33.780102
+Carlton,151.123795,-33.967701
+Carramar,150.960907,-33.884201
+Casino,153.037903,-28.860201
+Casula,150.910797,-33.9487
+Central,151.205704,-33.882999
+Cessnock,151.361694,-32.8419
+Chakola,149.184296,-36.099602
+Charity Creek,152.231003,-31.9016
+Chatswood,151.181,-33.796799
+Cheltenham,151.078796,-33.754601
+Chester Hill,150.996597,-33.883202
+Circular Quay,151.210907,-33.861198
+Civic,151.772903,-32.9249
+Clarendon,150.790497,-33.610001
+Clifton,150.967804,-34.2584
+Clyde,151.017197,-33.8349
+Coalcliff,150.976501,-34.241001
+Cobar,145.845398,-31.4939
+Cockle Creek,151.622406,-32.9426
+Coffs Harbour,153.138596,-30.3027
+Coledale,150.942596,-34.288799
+Collombatti,152.825699,-30.9755
+Colo Vale,150.4869,-34.3993
+Combara,148.370193,-31.1229
+Como,151.067902,-34.004799
+Compton Downs,146.571304,-30.4224
+Concord West,151.0858,-33.847599
+Condobolin,147.147995,-33.083302
+Coniston,150.884598,-34.437
+Conoble,144.707703,-32.9739
+Cooerwull,150.142105,-33.4818
+Cookamidgera,148.302902,-33.202801
+Coolalie,148.983704,-34.7854
+Coolamon,147.202194,-34.8167
+Cooma,149.133804,-36.2355
+Coombell,152.971893,-29.0159
+Coonong,146.122101,-35.1297
+Coopernook,152.600098,-31.8057
+Cootamundra,148.031006,-34.6399
+Coramba,153.019608,-30.2251
+Coreinbob,147.630905,-35.210999
+Corowa,146.384399,-35.996399
+Corrimal,150.904099,-34.374901
+Corringle,147.347397,-33.632801
+Couridjah,150.547897,-34.232601
+Cowan,151.170593,-33.591999
+Cowra,148.699005,-33.834702
+Cowra West,148.68248,-33.833015
+Crabbes Creek,153.501007,-28.457001
+Craboon,149.460297,-32.0294
+Craven,151.941498,-32.153999
+Cringila,150.877106,-34.465302
+Cronulla,151.150497,-34.056198
+Crooble,150.253998,-29.266199
+Croppa Creek,150.304703,-29.1269
+Crowther,148.502502,-34.0979
+Croydon,151.115799,-33.8829
+Cubbaroo,149.125198,-30.171499
+Culcairn,147.035797,-35.669899
+Culgoora,149.5849,-30.2798
+Cullen Bullen,150.006699,-33.3009
+Cullerin,149.407104,-34.783901
+Cullivel,146.379593,-35.259701
+Cullya,149.109695,-33.199699
+Curlewis,150.270401,-31.116699
+Currabubula,150.7341,-31.2626
+Dapto,150.790695,-34.492599
+Darnick,143.637894,-32.849602
+Daroobalgie,148.052002,-33.329899
+Deepwater,151.851593,-29.4387
+Denistone,151.087799,-33.798401
+Deringulla,149.326797,-31.3883
+Dingadee,151.7901,-32.368
+Domestic,151.179596,-33.930401
+Doonside,150.866196,-33.7631
+Dora Creek,151.500107,-33.080502
+Dorrigo,152.707199,-30.3319
+Douglas Park,150.709305,-34.182598
+Dripstone,148.990494,-32.644001
+Dubbo,148.605392,-32.245602
+Dulwich Hill,151.139801,-33.909302
+Dumaresq,151.580795,-30.459801
+Dunbible,153.401703,-28.380199
+Dundas,151.033096,-33.804001
+Dungog,151.759003,-32.398602
+Dunmore,150.839493,-34.605301
+East Hills,150.9841,-33.960602
+East Maitland,151.587494,-32.744499
+East Richmond,150.758194,-33.6017
+Eastwood,151.082794,-33.789501
+Edgecliff,151.239105,-33.879902
+Edgeroi,149.797394,-30.1171
+Elderslie,150.704895,-34.053299
+Eltham,153.3974,-28.753799
+Emerald Hill,150.104706,-30.8857
+Emu Plains,150.671799,-33.744202
+Engadine,151.013702,-34.067299
+Epping,151.082397,-33.770901
+Erigolia,146.356293,-33.8568
+Erskineville,151.184799,-33.9006
+Ettamogah,146.978806,-36.022499
+Euabalong West,146.391296,-33.054401
+Eulomogo,148.692795,-32.2887
+Eungai,152.900101,-30.848801
+Eurabba,147.8255,-34.055199
+Euratha,146.548401,-33.878799
+Eurie Eurie,148.252899,-29.964899
+Euston,142.764008,-34.569801
+Excelsior,149.974899,-33.0681
+Exeter,150.317307,-34.613499
+Fairfield,150.957703,-33.871101
+Fairview,148.159698,-32.378201
+Fairy Hill,153.008804,-28.770901
+Fairy Meadow,150.895203,-34.394699
+Farley,151.519501,-32.726398
+Fassifern,151.580902,-32.984798
+Faulconbridge,150.536194,-33.696499
+Finley,145.572998,-35.649799
+Fish River,149.3125,-34.7589
+Flemington,151.069901,-33.864101
+Forbes,148.010193,-33.374802
+Frampton,147.922501,-34.695301
+Gadara,148.158798,-35.320999
+Garah,149.634598,-29.0818
+Garema,147.937805,-33.551498
+Garoolgan,146.441101,-34.25
+Gerringong,150.8172,-34.744801
+Geurie,148.828995,-32.397301
+Gidginbung,147.4711,-34.3246
+Gidley,150.846893,-31.0065
+Girral,147.071106,-33.707699
+Glen Innes,151.726898,-29.7372
+Glenbrook,150.617905,-33.767601
+Glencoe,151.723694,-29.9231
+Glenfield,150.892105,-33.970699
+Glenlogan,148.653,-33.7691
+Glenroy,147.944107,-35.7495
+Gloucester,151.965897,-32.0042
+Gobondery,147.594193,-32.691002
+Gooloogong,148.449097,-33.567402
+Goondah,148.730499,-34.733601
+Goonumbla,148.1353,-32.9809
+Gooramma,148.622101,-34.493
+Gordon,151.155701,-33.754501
+Gosford,151.341003,-33.422699
+Gosford Racecourse,151.327194,-33.422401
+Goulburn,149.718002,-34.757801
+Grafton,152.925797,-29.683701
+Grafton City,152.941803,-29.7027
+Grahams Hill,150.730301,-34.040798
+Granville,151.012604,-33.831799
+Grasstree,150.965607,-32.285702
+Grawlin Plains,148.035995,-33.467701
+Green Square,151.1996,-33.9053
+Greenethorpe,148.401398,-33.997898
+Greenwood,151.008896,-29.731701
+Gresham,149.405197,-33.569199
+Greta,151.384201,-32.685799
+Griffith,146.045197,-34.285801
+Gubbata,146.5513,-33.637299
+Guildford,150.983795,-33.853699
+Gum Lake,143.177399,-32.679699
+Gunebang,146.682602,-33.014
+Gungal,150.500397,-32.266998
+Gunnedah,150.253098,-30.9821
+Gunning,149.262802,-34.776798
+Gunningbland,147.915207,-33.132801
+Gurley,149.799393,-29.7342
+Gurranang,152.996506,-29.459299
+Gwabegar,148.969101,-30.611
+Gymea,151.084305,-34.035
+Hadleigh,150.453598,-29.5877
+Hamilton,151.747696,-32.916901
+Hannan,146.417999,-33.6222
+Harden,148.370193,-34.5541
+Harris Park,151.007095,-33.822498
+Hartley Vale,150.261993,-33.5392
+Hawkesbury River,151.226196,-33.545399
+Hay,144.842102,-34.498299
+Hazelbrook,150.452606,-33.7229
+Heathcote,151.007706,-34.087502
+Helensburgh,150.993607,-34.1759
+Henty,147.032501,-35.516602
+Hermidale,146.723694,-31.548401
+Herons Creek,152.727295,-31.5891
+Hexham,151.684006,-32.8302
+High Street,151.561905,-32.740601
+Hilldale,151.648499,-32.504002
+Hillston,145.535507,-33.479099
+Hill Top,150.4936,-34.3540
+Holsworthy,150.955704,-33.961601
+Homebush,151.086594,-33.8661
+Hopefield,146.435806,-35.8918
+Hornsby,151.098495,-33.701302
+Horse Lake,142.065903,-32.115002
+Hurlstone Park,151.131897,-33.909698
+Hurstville,151.100601,-33.966099
+Illabo,147.741699,-34.814098
+Illalong Creek,148.656204,-34.715599
+Ingleburn,150.865494,-33.995701
+International,151.166107,-33.935001
+Ivanhoe,144.304092,-32.913502
+Jannali,151.064194,-34.015598
+Jaspers Brush,150.667007,-34.805401
+Jerilderie,145.723206,-35.361198
+Jindalee,148.088806,-34.577301
+Junee,147.582596,-34.870098
+Kadungle,147.621902,-32.758701
+Kahibah,151.718201,-32.9618
+Kamarah,146.7827,-34.324299
+Kamber,148.608795,-31.6383
+Kandos,149.968704,-32.859299
+Kankool,150.772705,-31.6975
+Kapooka,147.298492,-35.159302
+Karaak Flat,152.285004,-31.9237
+Katoomba,150.310593,-33.710701
+Kellys Plains,151.646393,-30.5667
+Kelso,149.604401,-33.422199
+Kembla Grange Racecourse,150.817795,-34.468899
+Kempsey,152.832993,-31.0765
+Kendall,152.706696,-31.6341
+Kenebri,149.0224,-30.7768
+Kentucky,151.449799,-30.7621
+Kiama,150.854095,-34.671902
+Kikoira,146.659698,-33.653198
+Kilgra,152.975906,-28.5497
+Killara,151.162598,-33.763901
+Killawarra,152.311707,-31.889999
+Kinalung,141.959396,-32.058701
+Kings Cross,151.219193,-33.8717
+Kingsgrove,151.098801,-33.940498
+Kingswood,150.719894,-33.756001
+Kirkham,150.715302,-34.046001
+Kirrawee,151.070801,-34.034599
+Kogarah,151.132095,-33.961498
+Kolodong,152.429092,-31.888399
+Komungla,149.645599,-34.864799
+Koolbury,150.902893,-32.216599
+Koolewong,151.315994,-33.464401
+Koolkhan,152.935303,-29.619699
+Koonadan,146.365097,-34.479801
+Koorakee,142.9245,-34.454498
+Kootingal,151.058502,-31.055799
+Korangi,153.045105,-30.256901
+Kotara,151.703094,-32.938599
+Kundabung,152.835495,-31.2082
+Kungala,153.008194,-29.948099
+Kurrajong,150.665604,-33.555
+Kyogle,153.002502,-28.6206
+Lake Cowal,147.357407,-33.688499
+Lakemba,151.076202,-33.919102
+Langtree,145.564499,-33.661598
+Langunyah,145.572205,-35.729198
+Lanitza,153.000305,-29.881399
+Lapstone,150.642807,-33.773499
+Larras Lee,148.859406,-33.000801
+Laureldale,153.414597,-28.7514
+Lawrance Road,153.009399,-29.3965
+Lawson,150.428101,-33.718102
+Leeton,146.395096,-34.553001
+Leeville,152.994202,-28.943399
+Leightonfield,150.9841,-33.881599
+Leniston,145.681503,-35.6506
+Lette,143.170593,-34.354198
+Leumeah,150.828903,-34.051399
+Leura,150.328201,-33.711201
+Lewisham,151.148193,-33.893101
+Leycester,153.196899,-28.7803
+Liamena,149.336304,-31.9685
+Lidcombe,151.044907,-33.862099
+Lilyvale,151.006195,-34.191002
+Limbri,151.156296,-31.033701
+Linden,150.504501,-33.713001
+Lindfield,151.169907,-33.773201
+Lisarow,151.369598,-33.3806
+Lithgow,150.156204,-33.479301
+Liverpool,150.9263,-33.923599
+Lochinvar,151.449707,-32.720299
+Loftus,151.0504,-34.044701
+Lucan,148.988998,-33.719002
+Lysaghts,150.875305,-34.453999
+Macarthur,150.795898,-34.071201
+Macdonaldtown,151.186203,-33.896301
+Macksville,152.912796,-30.7082
+Macquarie Fields,150.878098,-33.983799
+Maitland,151.550995,-32.737202
+Maldon,150.633301,-34.191299
+Marayong,150.8992,-33.7458
+Marrangaroo,150.112701,-33.449699
+Marrickville,151.155502,-33.9132
+Martin Place,151.211807,-33.8666
+Martins Creek,151.617599,-32.557899
+Marulan,150.006104,-34.7113
+Mary Vale,148.906799,-32.473999
+Mascot,151.1866,-33.921501
+Matakana,145.903595,-32.990002
+Meadowbank,151.090103,-33.815399
+Medlow Bath,150.281494,-33.674301
+Melinga,152.519104,-31.808901
+Menangle,150.743301,-34.124401
+Menangle Park,150.743805,-34.1021
+Mendooran,149.128693,-31.833401
+Menindee,142.425003,-32.3895
+Merah North,149.293793,-30.183901
+Merrylands,150.992203,-33.835602
+Merrywinebone,148.815399,-29.686001
+Metford,151.614807,-32.761501
+Michelago,149.165298,-35.710499
+Mickibri,148.194702,-32.8606
+Middlefield,147.575302,-32.4356
+Milguy,150.202805,-29.350901
+Milsons Point,151.211395,-33.8456
+Milsons Point (1st),151.209396,-33.850498
+Milsons Point (2nd),151.210297,-33.8475
+Mindaribba,151.5844,-32.6667
+Minemoorong,147.450195,-32.282299
+Minnamurra,150.8517,-34.625401
+Minto,150.8414,-34.026901
+Miowera,147.333298,-31.636499
+Miranda,151.102005,-34.036499
+Mittagong,150.447006,-34.452099
+Moleton,152.903503,-30.1607
+Molong,148.869904,-33.094299
+Molonglo,149.173996,-35.330601
+Mooball,153.492996,-28.440399
+Moombooldool,146.672897,-34.299999
+Mooren,149.336502,-31.6875
+Moppin,149.747299,-29.204901
+Moree,149.846405,-29.4736
+Morisset,151.486298,-33.110298
+Morpeth,151.626907,-32.724602
+Morpeth (1st),151.626877,-32.72459
+Morrisons Hill,148.109497,-34.533901
+Mortdale,151.0811,-33.9702
+Mortuary,151.202393,-33.885601
+Moss Vale,150.372803,-34.5452
+Moulamein,144.038895,-35.0882
+Mt Colah,151.115204,-33.6712
+Mt Druitt,150.818893,-33.768799
+Mount Gipps,141.598007,-31.898899
+Mt Kuring-gai,151.137497,-33.652302
+Mount Lion,152.9552,-28.4338
+Mount Rae,149.718201,-34.446301
+Mt Victoria,150.257599,-33.588299
+Mudgee,149.586594,-32.6012
+Mugincoble,148.225403,-33.190201
+Mulgrave,150.830597,-33.625198
+Mullion Creek,149.1185,-33.137699
+Mulwala,146.001495,-35.9809
+Mumbil,149.046295,-32.723
+Mungindi,148.973297,-28.968901
+Muronbung,148.960403,-32.174301
+Murrawal,149.336502,-31.4646
+Murrays Flats,149.787903,-34.723701
+Murrulla,150.919495,-31.8349
+Murrurundi,150.839294,-31.766399
+Museum,151.211304,-33.8755
+Muswellbrook,150.891006,-32.265202
+Myambat,150.617203,-32.3745
+Myocum,153.510803,-28.578199
+Myrtle Creek,152.945908,-29.110001
+Nambucca,152.977707,-30.628799
+Nammoona,153.022293,-28.8269
+Nana Glen,153.018097,-30.135599
+Nanardine,148.113297,-33.060799
+Napier,146.766998,-35.250401
+Narara,151.344193,-33.394001
+Narellan,150.735992,-34.039398
+Nargong,148.9366,-33.727699
+Narrabri,149.792206,-30.326
+Narrabri West,149.751205,-30.339399
+Narrandera,146.555496,-34.739799
+Narriah,146.725998,-33.948101
+Narwee,151.069595,-33.946602
+Narwonah,148.191498,-32.306599
+Neeworra,149.082504,-29.0266
+Neible,149.637207,-31.642401
+Nemingah,150.988098,-31.119101
+Nevertire,147.714005,-31.838301
+Newbridge,149.365799,-33.581799
+Newcastle,151.783905,-32.9249
+Newnes,150.237701,-33.176998
+Newtown,151.179703,-33.898201
+Niagara Park,151.352997,-33.382099
+No 1 Mortuary Station,151.047195,-33.868401
+No 2 Mortuary Station,151.048996,-33.875599
+No 3 Mortuary Station,151.054199,-33.877899
+No 4 Mortuary Station,151.058701,-33.872601
+Normanhurst,151.097504,-33.719898
+North Menangle,150.742401,-34.115601
+North Richmond,150.715805,-33.579899
+North Star,150.388702,-28.9307
+North Strathfield,151.087997,-33.858002
+North Sydney,151.205399,-33.841099
+North Wollongong,150.890594,-34.410999
+North Yathong,145.901001,-35.244301
+Norwood,149.731995,-34.680302
+Nubba,148.220398,-34.523399
+Nyngan,147.195007,-31.562901
+Nyrang Creek,148.556,-33.5467
+Oak Flats,150.815598,-34.57
+Oakey Creek,149.699493,-31.625799
+Oaklands,146.166,-35.5495
+Oatley,151.078705,-33.9799
+Oberon,149.852493,-33.701199
+Old Burren,148.905899,-29.9447
+Old Casino,153.046707,-28.854799
+Olympic Park,151.069,-33.847099
+Oolong,149.172501,-34.777
+Orange,149.1035,-33.286499
+Otford,151.005295,-34.210701
+Otford,151.005295,-34.210701
+Ourimbah,151.370102,-33.353901
+Padstow,151.031998,-33.950699
+Panania,150.996201,-33.953701
+Pangela,150.793503,-31.755199
+Parkes,148.174896,-33.141899
+Parkville,150.868393,-31.988701
+Parramatta,151.006195,-33.8167
+Paterson,151.612701,-32.602798
+Pendle Hill,150.953506,-33.800999
+Pennant Hills,151.073303,-33.735298
+Penrith,150.6922,-33.748199
+Penrose,150.210693,-34.673801
+Penshurst,151.089005,-33.965199
+Perisher,148.407501,-36.4025
+Petersham,151.155106,-33.893101
+Piambra,149.3741,-31.6329
+Picton,150.610992,-34.1782
+Pipers Flat,149.996704,-33.371201
+Pippita,151.063202,-33.8564
+Point Clare,151.328903,-33.445099
+Polona,149.205002,-33.489899
+Port Kembla,150.899597,-34.476898
+Port Kembla North,150.886505,-34.4715
+Portland,149.993393,-33.360298
+Premer,149.899597,-31.459
+Pucawan,147.349396,-34.396599
+Puggoon,149.470901,-32.250301
+Punchbowl,151.057098,-33.9245
+Pymble,151.144104,-33.7425
+Quakers Hill,150.886795,-33.726799
+Quandary,147.312607,-34.3857
+Quandialla,147.796906,-34.0117
+Quandong,148.176498,-33.9221
+Queanbeyan,149.225403,-35.3419
+Queens Wharf,151.620895,-32.7244
+Quipolly,150.655594,-31.4242
+Quirindi,150.680695,-31.504299
+Raglan,149.6510,-33.4344
+Raleigh,153.017807,-30.4541
+Ralvona,147.191101,-35.691101
+Rappville,152.953293,-29.086
+Ravensworth,151.057999,-32.439701
+Raworth,151.613998,-32.725101
+Red Bend,148.014404,-33.384102
+Redfern,151.1978,-33.8913
+Regents Park,151.025299,-33.881001
+Remep,149.845596,-31.495399
+Revesby,151.013794,-33.951401
+Rhodes,151.085403,-33.830101
+Richmond,150.753998,-33.5998
+Riverstone,150.857498,-33.678299
+Riverwood,151.050507,-33.951199
+Robertson,150.592194,-34.590599
+Rockdale,151.135406,-33.9519
+Rocky Ponds,148.503799,-34.583401
+Rookwood,151.055099,-33.863453
+Rooty Hill,150.843903,-33.770401
+Rosehill,151.022903,-33.822201
+Roseville,151.177795,-33.7817
+Rosewood,147.866196,-35.676399
+Roto,145.462402,-33.050804
+Rowena,148.908203,-29.814899
+Roxburgh,150.759003,-32.312698
+Royalla,149.149399,-35.510101
+Rutherford Racecourse,151.503006,-32.712101
+Ryan,146.910507,-35.5172
+Rydal,150.031006,-33.4846
+Rydalmere,151.0289,-33.8102
+Rylstone,149.973404,-32.7962
+Sandgate,151.702103,-32.870499
+Sandy Hollow,150.570007,-32.3382
+Sawtell,153.098007,-30.358101
+Sayers Lake,143.275299,-32.7225
+Scarborough,150.964905,-34.2635
+Schofields,150.867996,-33.6973
+Scone,150.866898,-32.045898
+Sefton,151.010895,-33.8848
+Seven Hills,150.936996,-33.7738
+Singleton,151.165695,-32.570499
+Somerset,152.164902,-31.884199
+South Gundagai,148.0979,-35.077202
+South Wyalong,147.2341,-33.934502
+Spring Ridge,150.253494,-31.396601
+Springdale,147.724701,-34.469299
+Springwood,150.563904,-33.7001
+Sproules Lagoon,147.500397,-34.387199
+St Helena,153.578903,-28.6658
+St James,151.211304,-33.870899
+St Leonards,151.195297,-33.823601
+St Marys,150.774902,-33.7612
+St Peters,151.178299,-33.906799
+Stanmore,151.163498,-33.893501
+Stanwell Park,150.978806,-34.2271
+Stokers,153.404404,-28.394501
+Stratford,151.9375,-32.118801
+Strathaird,149.746399,-34.423901
+Strathfield,151.093994,-33.871201
+Stuart Town,149.077393,-32.8008
+Summer Hill,151.139404,-33.889599
+Summervale,147.058899,-31.428499
+Sutherland,151.056396,-34.031502
+Sydenham,151.168503,-33.9133
+Tabbita,145.844803,-34.1022
+Table Top,147.001694,-35.9706
+Tahmoor,150.588501,-34.223999
+Taleeban,146.462097,-33.8699
+Tallawang,149.453293,-32.204201
+Tallimba,146.881897,-33.992298
+Tallong,150.085693,-34.7197
+Tamboolba,147.559692,-35.231499
+Tamworth,150.930603,-31.0839
+Tarago,149.649002,-35.069801
+Tarana,149.906494,-33.525101
+Tarcutta,147.710999,-35.2953
+Taree,152.458603,-31.905001
+Tarro,151.669998,-32.808201
+Tascott,151.316895,-33.448898
+Telarah,151.5392,-32.723202
+Telegraph Point,152.800507,-31.3214
+Telopea,151.042496,-33.793598
+Temora,147.5271,-34.444401
+Tempe,151.156906,-33.922901
+Tenterfield,152.005493,-29.053301
+Teralba,151.602997,-32.962299
+Tharbogang,145.992599,-34.254002
+The Gorge,141.675095,-31.9126
+The Risk,152.939407,-28.48
+The Rock,147.113907,-35.271999
+The Royal National Park,151.055603,-34.062199
+The Troffs,147.653702,-32.827599
+Thirlmere,150.570801,-34.204201
+Thirroul,150.917496,-34.318298
+Thornleigh,151.078003,-33.730598
+Thornton,151.640793,-32.783901
+Thulloo,146.768005,-33.659
+Tichborne,148.120605,-33.222099
+Tomingley West,148.152405,-32.5728
+Toolijooa,150.780807,-34.764702
+Toongabbie,150.949402,-33.786999
+Tootool,146.996994,-35.262001
+Torbane,149.989197,-33.112301
+Toronto,151.598007,-33.0154
+Tottenham,147.354797,-32.240501
+Towealgra,149.327698,-31.9305
+Town Hall,151.207108,-33.872398
+Towradgi,150.900406,-34.383801
+Trajere,148.387802,-33.492901
+Trangie,147.985001,-32.0299
+Trewilga,148.213303,-32.7883
+Trida,145.004501,-33.0182
+Trundle,147.708893,-32.916599
+Tubbul Road,147.936005,-34.255299
+Tuggerah,151.420898,-33.307201
+Tullamore,147.561996,-32.631401
+Tumblong,148.008194,-35.138802
+Tumulla,149.480606,-33.516899
+Turramurra,151.128403,-33.730099
+Turrawan,149.883804,-30.457399
+Turrella,151.139694,-33.929401
+Tweed Heads,153.5401,-28.1735
+Ulamambri,149.383194,-31.3298
+Ulinda,149.481705,-31.582199
+Umbango Creek,147.766205,-35.382702
+Unanderra,150.845795,-34.453701
+Upper Manilla,150.664001,-30.636101
+Uralla,151.505295,-30.6437
+Urana,146.273407,-35.328999
+Uranagong,146.235397,-35.4007
+Uranquinty,147.243698,-35.191399
+Urunga,153.017593,-30.4979
+Valley Heights,150.581497,-33.703602
+Victoria Street,151.592102,-32.7491
+Villawood,150.973907,-33.880402
+Vineyard,150.849792,-33.6497
+Wagga Wagga,147.366501,-35.119801
+Wahroonga,151.117401,-33.715698
+Waitara,151.104401,-33.707901
+Walcha Road,151.400497,-30.941799
+Wallangarra,151.931595,-28.9226
+Wallarobba,151.6946,-32.4951
+Wallerawang,150.0681,-33.4090
+Wallsend,151.667206,-32.902802
+Wambool,149.7789,-33.5158
+Wamboyne,147.279602,-33.5695
+Warabrook,151.709793,-32.886299
+Waratah,151.730896,-32.901001
+Wards River,151.938599,-32.217098
+Wargambeal,146.492294,-33.3606
+Wargin,147.253601,-34.115299
+Warnecliffe,149.084595,-32.987202
+Warnervale,151.447205,-33.247601
+Warragai Creek,152.988495,-29.547701
+Warral,150.860992,-31.1595
+Warrawee,151.121994,-33.722698
+Warrell Creek,152.892502,-30.7694
+Warren,147.826096,-31.697599
+Warrimoo,150.601196,-33.720001
+Warwick Farm,150.933807,-33.9147
+Waterfall,150.993393,-34.133999
+Wauchope,152.736694,-31.455099
+Waverton,151.197998,-33.8377
+Wee Elwah,145.197906,-33.045399
+Weedallion,147.936707,-34.193802
+Weemelah,149.255905,-29.0165
+Weetaliba,149.582504,-31.638901
+Weethalle,146.619904,-33.876499
+Weja,146.8228,-33.533401
+Wellington,148.945007,-32.5522
+Wentworth Falls,150.376801,-33.709099
+Wentworthville,150.971497,-33.806099
+Werai,150.345505,-34.590599
+Werrington,150.759705,-33.7589
+Werris Creek,150.646194,-31.3487
+West Ryde,151.091095,-33.805
+West Tamworth,150.912994,-31.0905
+West Wyalong,147.200195,-33.928398
+Westmead,150.986801,-33.8069
+Wickham,151.758194,-32.923
+Wiley Park,151.065994,-33.923
+Willie Ploma,148.075897,-35.093899
+Willow Tree,150.726593,-31.6476
+Wimborne,150.643494,-30.670799
+Windsor,150.809402,-33.612801
+Wingello,150.157593,-34.693298
+Wingen,150.880997,-31.8934
+Wingham,152.367004,-31.8659
+Winnunga,146.863907,-33.5965
+Wiragulla,151.742203,-32.4613
+Wirrinya,147.802994,-33.670898
+Wittenbra,149.085403,-31.044201
+Wolli Creek,151.154999,-33.925999
+Wollongong,150.887405,-34.425701
+Wollstonecraft,151.191498,-33.831299
+Wombarra,150.9524,-34.2752
+Wondabyne,151.255005,-33.491299
+Wongabinda,150.072098,-29.385401
+Wongarbon,148.759293,-32.336399
+Wongoni,149.278503,-31.871099
+Woodford,150.480698,-33.735802
+Woodlawn,153.319199,-28.7757
+Woodstock,148.8452,-33.743301
+Woolbrook,151.351898,-30.965099
+Woollahra,151.242401,-33.8848
+Woolooware,151.143494,-34.0476
+Woonona,150.913803,-34.3493
+Woy Woy,151.321899,-33.482601
+Wubbera,150.145004,-29.546301
+Wumbulgal,146.209198,-34.358601
+Wyanga,148.131104,-32.458302
+Wyangaree,152.963806,-28.5117
+Wybalena,148.163498,-35.4944
+Wyee,151.485001,-33.180401
+Wynyard,151.205795,-33.865101
+Wyong,151.426895,-33.284
+Wyrra,147.268005,-33.823002
+Wyuna Downs,146.488495,-30.5005
+Yagoona,151.023804,-33.904202
+Yallah,150.784195,-34.5364
+Yarra,149.630096,-34.786301
+Yass Junction,148.912903,-34.8088
+Yass Town,148.9086,-34.844002
+Yearinan,149.179199,-31.1819
+Yennora,150.969803,-33.8638
+Yerrinbool,150.542603,-34.3713
+Yethera,147.574799,-32.525398
+Yiddah,147.317795,-34.0364
+Yoogali,146.081497,-34.300499
+Youngareen,146.852203,-33.650398
+Yullundry,148.713898,-32.843201
+Zig Zag,150.2001,-33.4717
diff --git a/src/toys/data/nsw-london.zip b/src/toys/data/nsw-london.zip
new file mode 100644
index 0000000..d0c0050
--- /dev/null
+++ b/src/toys/data/nsw-london.zip
Binary files differ
diff --git a/src/toys/data/nsw.txt b/src/toys/data/nsw.txt
new file mode 100644
index 0000000..6f33691
--- /dev/null
+++ b/src/toys/data/nsw.txt
@@ -0,0 +1,86 @@
+Olympic Park I:
+Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park
+
+Olympic Park II:
+Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park
+
+Eastern Suburbs & Illawarra 1:
+Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+Eastern Suburbs & Illawarra 2:
+Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+South Coast 1:
+Bomaderry,Berry,Gerringong,Kiama,Bombo,Minnamurra,Dunmore,Oak Flats,Albion Park,Dapto,Kembla Grange Racecourse,Unanderra,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+South Coast 2:
+Port Kembla,Port Kembla North,Cringila,Lysaghts,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction
+
+Bankstown Line a:
+Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe
+
+Bankstown Line b:
+Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool
+
+Inner West 1a:
+Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Inner West 1b:
+Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood
+
+Inner West 2a:
+Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Inner West 2b:
+Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood
+
+Airport & East Hills 1:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall
+
+Airport & East Hills 2:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall
+
+South:
+Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum
+
+Southern Highlands:
+Goulburn,Marulan,Tallong,Wingello,Penrose,Bundanoon,Exeter,Moss Vale,Burradoo,Bowral,Mittagong,Yerrinbool,Bargo,Tahmoor,Picton,Douglas Park,Menangle,Menangle Park,Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown
+
+Southern Highlands ROAD COACH:
+Bowral,Mittagong,Colo Vale,Hill Top,Balmoral,Buxton,Couridjah,Thirlmere,Picton
+
+Cumberland:
+Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys
+
+Carlingford Line:
+Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde
+
+Western 1:
+Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+Western 2:
+Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+Blue Mountains:
+Lithgow,Zig Zag,Bell,Mt Victoria,Blackheath,Medlow Bath,Katoomba,Leura,Wentworth Falls,Bullaburra,Lawson,Hazelbrook,Woodford,Linden,Faulconbridge,Springwood,Valley Heights,Warrimoo,Blaxland,Glenbrook,Lapstone,Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+North Shore:
+Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta
+
+Northern:
+Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney
+
+Newcastle & Central Coast 1:
+Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central
+
+Newcastle & Central Coast 2:
+Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central
+
+Wyong Line:
+Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys
+
+Hunter 1:
+Scone,Aberdeen,Muswellbrook,Singleton,Belford,Branxton,Greta,Allandale,Lochinvar,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle
+
+Hunter 2:
+Dungog,Wiragulla,Wallarobba,Hilldale,Martins Creek,Paterson,Mindaribba,Telarah,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle
diff --git a/src/toys/data/parser.cpp b/src/toys/data/parser.cpp
new file mode 100644
index 0000000..cc5c6f0
--- /dev/null
+++ b/src/toys/data/parser.cpp
@@ -0,0 +1,47 @@
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <vector>
+#include <map>
+using namespace std;
+typedef pair<double,double> Point;
+int main(void)
+{
+ ifstream location_file("london-locations.csv"), path_file("london.txt");
+ string id,sx,sy;
+ map<string,Point> idlookup;
+ while (getline(location_file,id,','))
+ {
+ getline(location_file,sx,',');
+ getline(location_file,sy,'\n');
+ char *e;
+ double x = strtod(sx.c_str(),&e), y = strtod(sy.c_str(),&e);
+ cout << id << " (" << x << "," << y << ")"<< endl;
+ idlookup[id]=make_pair(x,y);
+ }
+ string l;
+ vector<vector<Point> > paths;
+ while (getline(path_file,l,'\n')) {
+ vector<Point> ps;
+ if(l.size() < 2) continue; // skip blank lines
+ if(l.find(":",0)!=string::npos) continue; // skip labels
+ string::size_type p=0,q;
+ while((q=l.find(",",p))!=string::npos || p < l.size() && (q = l.size()-1)) {
+ id = l.substr(p,q-p);
+ cout << id << ",";
+ ps.push_back(idlookup[id]);
+ p=q+1;
+ }
+ paths.push_back(ps);
+ cout << "*******************************************" << endl;
+ }
+ for(unsigned i=0;i<paths.size();i++) {
+ vector<Point> ps=paths[i];
+ for(unsigned j=0;j<ps.size();j++) {
+ double x=ps[j].first, y=ps[j].second;
+ cout << "(" << x << "," << y << ")" << ",";
+ }
+ cout << endl;
+ }
+ return(0);
+}
diff --git a/src/toys/differential-constraint.cpp b/src/toys/differential-constraint.cpp
new file mode 100644
index 0000000..501eb76
--- /dev/null
+++ b/src/toys/differential-constraint.cpp
@@ -0,0 +1,132 @@
+/** Differential constraint solver hack
+ * based on idea from Michael Glissner
+ * (njh)
+ */
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework.h>
+
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+#include <stdio.h>
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_odeiv.h>
+
+vector<Geom::Point> *handlesptr = NULL;
+
+const unsigned order = 6;
+
+class SBez: public Toy {
+ static int
+ func (double /*t*/, const double y[], double f[],
+ void */*params*/)
+ {
+ //double mu = *(double *)params;
+ D2<SBasis> B = handles_to_sbasis(handlesptr->begin(), order);
+ D2<SBasis> dB = derivative(B);
+ Geom::Point tan = dB(y[0]);//Geom::unit_vector();
+ tan /= dot(tan,tan);
+ Geom::Point yp = B(y[0]);
+ double dtau = -dot(tan, yp - (*handlesptr)[order+1]);
+ f[0] = dtau;
+
+ return GSL_SUCCESS;
+ }
+
+ static int
+ jac (double /*t*/, const double y[], double *dfdy,
+ double dfdt[], void *params)
+ {
+ double mu = *(double *)params;
+ gsl_matrix_view dfdy_mat
+ = gsl_matrix_view_array (dfdy, 2, 2);
+ gsl_matrix * m = &dfdy_mat.matrix;
+ gsl_matrix_set (m, 0, 0, 0.0);
+ gsl_matrix_set (m, 0, 1, 1.0);
+ gsl_matrix_set (m, 1, 0, -2.0*mu*y[0]*y[1] - 1.0);
+ gsl_matrix_set (m, 1, 1, -mu*(y[0]*y[0] - 1.0));
+ dfdt[0] = 0.0;
+ dfdt[1] = 0.0;
+ return GSL_SUCCESS;
+ }
+
+ double y[2];
+
+ virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ handlesptr = &handles;
+ cairo_set_line_width (cr, 0.5);
+
+ D2<SBasis> B = handles_to_sbasis(handles.begin(), order);
+ cairo_d2_sb(cr, B);
+
+ const gsl_odeiv_step_type * T
+ = gsl_odeiv_step_rk8pd;
+
+ gsl_odeiv_step * s
+ = gsl_odeiv_step_alloc (T, 1);
+ gsl_odeiv_control * c
+ = gsl_odeiv_control_y_new (1e-6, 0.0);
+ gsl_odeiv_evolve * e
+ = gsl_odeiv_evolve_alloc (1);
+
+ double mu = 10;
+ gsl_odeiv_system sys = {func, jac, 1, &mu};
+
+ static double t = 0.0;
+ double t1 = t + 1;
+ double h = 1e-6;
+
+ while (t < t1)
+ {
+ int status = gsl_odeiv_evolve_apply (e, c, s,
+ &sys,
+ &t, t1,
+ &h, y);
+
+ if (status != GSL_SUCCESS)
+ break;
+
+ //printf ("%.5e %.5e %.5e\n", t, y[0], y[1]);
+ }
+
+ draw_cross(cr, B(y[0]));
+
+ gsl_odeiv_evolve_free (e);
+ gsl_odeiv_control_free (c);
+ gsl_odeiv_step_free (s);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+public:
+ SBez() {
+ y[0] = 0;
+ for(unsigned i = 0; i <= order+1; i++) {
+ handles.push_back(Geom::Point(uniform()*400, uniform()*400));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBez());
+
+ 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/src/toys/draw-toy.cpp b/src/toys/draw-toy.cpp
new file mode 100644
index 0000000..f5bd315
--- /dev/null
+++ b/src/toys/draw-toy.cpp
@@ -0,0 +1,116 @@
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+class DrawToy: public Toy {
+ PointSetHandle hand;
+ //Knot : Och : Och : Knot : Och : Och : Knot : Och : Och : ...
+ void draw(cairo_t *cr, std::ostringstream */*notify*/, int /*width*/, int /*height*/, bool save, std::ostringstream*) override {
+ if(!save) {
+ cairo_set_source_rgba (cr, 0, 0.5, 0, 1);
+ cairo_set_line_width (cr, 1);
+ for(unsigned i = 0; i < hand.pts.size(); i+=3) {
+ draw_circ(cr, hand.pts[i]);
+ draw_number(cr, hand.pts[i], i/3);
+ }
+ cairo_set_source_rgba (cr, 0, 0, 0.5, 1);
+ for(unsigned i = 2; i < hand.pts.size(); i+=3) {
+ draw_circ(cr, hand.pts[i]);
+ draw_circ(cr, hand.pts[i-1]);
+ }
+
+ cairo_set_source_rgba (cr, 0.5, 0, 0, 1);
+ for(unsigned i = 3; i < hand.pts.size(); i+=3) {
+ draw_line_seg(cr, hand.pts[i-2], hand.pts[i-3]);
+ draw_line_seg(cr, hand.pts[i], hand.pts[i-1]);
+ }
+ }
+ cairo_set_source_rgba (cr, 0, 0, 0, 1);
+ Geom::Path pb;
+ if(hand.pts.size() > 3) {
+ pb.start(hand.pts[0]);
+ for(unsigned i = 1; i < hand.pts.size() - 3; i+=3) {
+ pb.appendNew<Geom::CubicBezier>(hand.pts[i], hand.pts[i+1], hand.pts[i+2]);
+ }
+ }
+ cairo_path(cr, pb);
+ cairo_stroke(cr);
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ selected = NULL;
+ Geom::Point mouse(e->x, e->y);
+ int close_i = 0;
+ float close_d = 1000;
+ for(unsigned i = 0; i < hand.pts.size(); i+=1) {
+ if(Geom::distance(mouse, hand.pts[i]) < close_d) {
+ close_d = Geom::distance(mouse, hand.pts[i]);
+ close_i = i;
+ }
+ }
+ if(close_d < 5) {
+ if(e->button==3)
+ hand.pts.erase(hand.pts.begin() + close_i);
+ else {
+ selected = &hand;
+ hit_data = (void*)(intptr_t)close_i;
+ }
+ } else {
+ if(e->button==1) {
+ if(hand.pts.size() > 0) {
+ if(hand.pts.size() == 1) {
+ hand.pts.push_back((hand.pts[0] * 2 + mouse) / 3);
+ hand.pts.push_back((hand.pts[0] + mouse * 2) / 3);
+ } else {
+ Geom::Point prev = hand.pts[hand.pts.size() - 1];
+ Geom::Point curve = prev - hand.pts[hand.pts.size() - 2];
+ hand.pts.push_back(prev + curve);
+ hand.pts.push_back(mouse + curve);
+ }
+ }
+ hand.pts.push_back(mouse);
+ } else {
+ selected = &hand;
+ hit_data = (void*)(intptr_t)close_i;
+ }
+ }
+ }
+
+ void mouse_moved(GdkEventMotion* e) override {
+ Geom::Point mouse(e->x, e->y);
+
+ if(e->state & (GDK_BUTTON1_MASK) && selected != NULL) {
+ // NOTE this is completely broken.
+ int hd = 0;
+ if (hd % 3 == 0) {
+ Geom::Point diff = mouse - hand.pts[hd];
+ if(int(hand.pts.size() - 1) > hd) hand.pts[hd + 1] += diff;
+ if(hd != 0) hand.pts[hd - 1] += diff;
+ }
+ Toy::mouse_moved(e);
+ }
+ }
+
+ bool should_draw_numbers() override { return false; }
+public:
+ DrawToy() {
+
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new DrawToy());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/ellipse-area-minimizer.cpp b/src/toys/ellipse-area-minimizer.cpp
new file mode 100644
index 0000000..48df598
--- /dev/null
+++ b/src/toys/ellipse-area-minimizer.cpp
@@ -0,0 +1,352 @@
+/*
+ * Ellipse and Elliptical Arc Fitting Example
+ *
+ * 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/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+#include <2geom/utils.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <stdio.h>
+
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_min.h>
+
+
+
+
+using namespace Geom;
+
+
+class LFMEllipseArea
+ : public NL::LinearFittingModelWithFixedTerms<Point, double, Ellipse>
+{
+ public:
+ LFMEllipseArea(double coeff)
+ : m_coeff(coeff*coeff)
+ {
+ }
+ void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const
+ {
+ coeff[0] = p[X] * p[Y];
+ coeff[1] = p[X];
+ coeff[2] = p[Y];
+ coeff[3] = 1;
+ fixed_term = p[X] * p[X] + m_coeff * p[Y] * p[Y];
+ }
+
+ size_t size() const
+ {
+ return 4;
+ }
+
+ void instance(Ellipse & e, NL::ConstVectorView const& coeff) const
+ {
+// std::cerr << " B = " << coeff[0]
+// << " C = " << decimal_round(m_coeff,10)
+// << " D = " << coeff[1]
+// << " E = " << coeff[2]
+// << " F = " << coeff[3]
+// << std::endl;
+ e.setCoefficients(1, coeff[0], m_coeff, coeff[1], coeff[2], coeff[3]);
+ }
+
+ private:
+ double m_coeff;
+};
+
+inline
+Ellipse fitting(std::vector<Point> const& points, double coeff)
+{
+ size_t sz = points.size();
+ if (sz != 4)
+ {
+ THROW_RANGEERROR("fitting error: too few points passed");
+ }
+ LFMEllipseArea model(coeff);
+ NL::least_squeares_fitter<LFMEllipseArea> fitter(model, sz);
+
+ for (size_t i = 0; i < sz; ++i)
+ {
+ fitter.append(points[i]);
+ }
+ fitter.update();
+
+ NL::Vector z(sz, 0.0);
+ Ellipse e;
+ model.instance(e, fitter.result(z));
+ return e;
+}
+
+
+inline
+double area_goal(double coeff, void* params)
+{
+ typedef std::vector<Point> point_set_t;
+ const point_set_t & points = *static_cast<point_set_t*>(params);
+ Ellipse e;
+ try
+ {
+ e = fitting(points, coeff);
+ }
+ catch(LogicalError exc)
+ {
+ //std::cerr << exc.what() << std::endl;
+ return 1e18;
+ }
+ return e.ray(X) * e.ray(Y);
+}
+
+
+inline
+double perimeter_goal(double coeff, void* params)
+{
+ typedef std::vector<Point> point_set_t;
+ const point_set_t & points = *static_cast<point_set_t*>(params);
+ Ellipse e;
+ try
+ {
+ e = fitting(points, coeff);
+ }
+ catch(LogicalError exc)
+ {
+ //std::cerr << exc.what() << std::endl;
+ return 1e18;
+ }
+ return e.ray(X) + e.ray(Y);
+}
+
+void no_minimum_error_handler (const char * reason,
+ const char * file,
+ int line,
+ int gsl_errno)
+{
+ if (gsl_errno == GSL_EINVAL)
+ {
+ std::cerr << "gsl: " << file << ":" << line << " ERROR: " << reason << std::endl;
+ }
+ else
+ {
+ gsl_error(reason, file, line, gsl_errno);
+ }
+}
+
+typedef double goal_function_type(double coeff, void* params);
+
+double minimizer (std::vector<Point> & points, goal_function_type* gf)
+{
+ int status;
+ int iter = 0, max_iter = 1000;
+ const gsl_min_fminimizer_type *T;
+ gsl_min_fminimizer *s;
+ double m = 1.0;
+ double a = 1e-2, b = 1e2;
+ gsl_function F;
+
+ F.function = gf;
+ F.params = static_cast<void*>(&points);
+
+ //T = gsl_min_fminimizer_goldensection;
+ T = gsl_min_fminimizer_brent;
+ s = gsl_min_fminimizer_alloc (T);
+ gsl_min_fminimizer_set (s, &F, m, a, b);
+
+// printf ("using %s method\n",
+// gsl_min_fminimizer_name (s));
+//
+// printf ("%5s [%9s, %9s] %9s %10s %9s\n",
+// "iter", "lower", "upper", "min",
+// "err", "err(est)");
+//
+// printf ("%5d [%.7f, %.7f] %.7f %+.7f %.7f\n",
+// iter, a, b,
+// m, m - m_expected, b - a);
+
+ do
+ {
+ iter++;
+ status = gsl_min_fminimizer_iterate (s);
+
+ m = gsl_min_fminimizer_x_minimum (s);
+ a = gsl_min_fminimizer_x_lower (s);
+ b = gsl_min_fminimizer_x_upper (s);
+
+ status
+ = gsl_min_test_interval (a, b, 1e-3, 0.0);
+
+// if (status == GSL_SUCCESS)
+// printf ("Converged:\n");
+//
+// printf ("%5d [%.7f, %.7f] "
+// "%.7f %+.7f %.7f\n",
+// iter, a, b,
+// m, m - m_expected, b - a);
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+
+ gsl_min_fminimizer_free (s);
+
+ if (status != GSL_SUCCESS) return 0;
+
+ return m;
+}
+
+
+
+class EllipseAreaMinimizer : public Toy
+{
+ public:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ Point toggle_sp( 300, height - 50);
+ toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(135,25) );
+ ConvexHull ch(psh.pts);
+ bool non_convex = false;
+ for(auto & pt : psh.pts) {
+ if (ch.contains(pt))
+ non_convex = true;
+ }
+
+ if(non_convex) {
+ Circle circ;
+ std::vector<Point> boundary(ch.begin(), ch.end());
+ circ.fit(boundary);
+ e = Ellipse(circ);
+ } else {
+ goal_function_type* gf = &area_goal;
+ if (!toggles[0].on) gf = &perimeter_goal;
+ double coeff = minimizer(psh.pts, gf);
+
+ try
+ {
+ e = fitting(psh.pts, coeff);
+ }
+ catch(LogicalError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ }
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ draw_elliptical_arc_with_cairo( cr,
+ e.center(X), e.center(Y),
+ e.ray(X), e.ray(Y),
+ 0, 2*M_PI,
+ e.rotationAngle() );
+ if (toggles[0].on)
+ *notify << "Area: " << e.ray(X)*e.ray(Y);
+ else
+ *notify << "Perimeter: " << 2* (e.ray(X) + e.ray(Y));
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void draw_elliptical_arc_with_cairo(
+ cairo_t *cr,
+ double _cx, double _cy,
+ double _rx, double _ry,
+ double _sa, double _ea,
+ double _ra = 0
+ ) const
+ {
+ double cos_rot_angle = std::cos(_ra);
+ double sin_rot_angle = std::sin(_ra);
+ cairo_matrix_t transform_matrix;
+ cairo_matrix_init( &transform_matrix,
+ _rx * cos_rot_angle, _rx * sin_rot_angle,
+ -_ry * sin_rot_angle, _ry * cos_rot_angle,
+ _cx, _cy
+ );
+ cairo_save(cr);
+ cairo_transform(cr, &transform_matrix);
+ cairo_arc(cr, 0, 0, 1, _sa, _ea);
+ cairo_restore(cr);
+ }
+
+ public:
+ EllipseAreaMinimizer()
+ {
+ gsl_set_error_handler(&no_minimum_error_handler);
+
+ first_time = true;
+
+ psh.pts.resize(4);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(250, 100);
+ psh.pts[2] = Point(250, 400);
+ psh.pts[3] = Point(400, 320);
+
+ handles.push_back(&psh);
+
+ toggles.emplace_back("Area/Perimeter", true);
+ handles.push_back(&(toggles[0]));
+ }
+
+ public:
+ Ellipse e;
+ bool first_time;
+ PointSetHandle psh;
+ std::vector<Toggle> toggles;
+};
+
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new EllipseAreaMinimizer(), 600, 600 );
+ 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/src/toys/ellipse-bezier-intersect-toy.cpp b/src/toys/ellipse-bezier-intersect-toy.cpp
new file mode 100644
index 0000000..5468d97
--- /dev/null
+++ b/src/toys/ellipse-bezier-intersect-toy.cpp
@@ -0,0 +1,74 @@
+#include <2geom/cairo-path-sink.h>
+#include <2geom/ellipse.h>
+#include <2geom/line.h>
+#include <2geom/polynomial.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class CircleIntersect : public Toy {
+ PointSetHandle eh, bh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ Rect all(Point(0,0), Point(width, height));
+
+ double rx = Geom::distance(eh.pts[0], eh.pts[1]);
+ double ry = Geom::distance(eh.pts[0], eh.pts[2]);
+ double rot = Geom::atan2(eh.pts[1] - eh.pts[0]);
+
+ Ellipse e(eh.pts[0], Point(rx, ry), rot);
+ D2<Bezier> b(bh.pts);
+
+ cairo_set_line_width(cr, 1.0);
+ Geom::CairoPathSink cps(cr);
+
+ // draw Bezier control polygon
+ cairo_set_source_rgba(cr, 0, 0, 1, 0.3);
+ cps.moveTo(bh.pts[0]);
+ for (unsigned i = 1; i < bh.pts.size(); ++i) {
+ cps.lineTo(bh.pts[i]);
+ }
+ cairo_stroke(cr);
+
+ // draw Bezier curve and ellipse
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cps.feed(BezierCurve(b), true);
+ cps.feed(e);
+ cairo_stroke(cr);
+
+ std::vector<ShapeIntersection> result = e.intersect(b);
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : result) {
+ draw_handle(cr, i.point());
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleIntersect(){
+ eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350);
+ bh.push_back(100,100); bh.push_back(500,100); bh.push_back(100,500); bh.push_back(500,500);
+ handles.push_back(&eh);
+ handles.push_back(&bh);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CircleIntersect());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/ellipse-fitting.cpp b/src/toys/ellipse-fitting.cpp
new file mode 100644
index 0000000..de8c81a
--- /dev/null
+++ b/src/toys/ellipse-fitting.cpp
@@ -0,0 +1,191 @@
+/*
+ * Ellipse and Elliptical Arc Fitting Example
+ *
+ * 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/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+class EllipseFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ if (first_time)
+ {
+ first_time = false;
+ Point toggle_sp( 300, height - 50);
+ toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) );
+ sliders[0].geometry(Point(50, height - 50), 100);
+ }
+
+ size_t n = (size_t)(sliders[0].value()) + 5;
+ if (n < psh.pts.size())
+ {
+ psh.pts.resize(n);
+ }
+ else if (n > psh.pts.size())
+ {
+ psh.push_back(400*uniform()+50, 300*uniform()+50);
+ }
+
+ try
+ {
+ e.fit(psh.pts);
+ }
+ catch(LogicalError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+
+ if (toggles[0].on)
+ {
+ try
+ {
+ std::unique_ptr<EllipticalArc> eap( e.arc(psh.pts[0], psh.pts[2], psh.pts[4]) );
+ ea = *eap;
+ }
+ catch(RangeError exc)
+ {
+ std::cerr << exc.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ }
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ if (!toggles[0].on)
+ {
+ draw_elliptical_arc_with_cairo( cr,
+ e.center(X), e.center(Y),
+ e.ray(X), e.ray(Y),
+ 0, 2*M_PI,
+ e.rotationAngle() );
+ }
+ else
+ {
+ draw_text(cr, psh.pts[0], "initial");
+ draw_text(cr, psh.pts[2], "inner");
+ draw_text(cr, psh.pts[4], "final");
+
+ D2<SBasis> easb = ea.toSBasis();
+ cairo_d2_sb(cr, easb);
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void draw_elliptical_arc_with_cairo(
+ cairo_t *cr,
+ double _cx, double _cy,
+ double _rx, double _ry,
+ double _sa, double _ea,
+ double _ra = 0
+ ) const
+ {
+ double cos_rot_angle = std::cos(_ra);
+ double sin_rot_angle = std::sin(_ra);
+ cairo_matrix_t transform_matrix;
+ cairo_matrix_init( &transform_matrix,
+ _rx * cos_rot_angle, _rx * sin_rot_angle,
+ -_ry * sin_rot_angle, _ry * cos_rot_angle,
+ _cx, _cy
+ );
+ cairo_save(cr);
+ cairo_transform(cr, &transform_matrix);
+ cairo_arc(cr, 0, 0, 1, _sa, _ea);
+ cairo_restore(cr);
+ }
+
+ public:
+ EllipseFitting()
+ {
+ first_time = true;
+
+ psh.pts.resize(5);
+ psh.pts[0] = Point(450, 250);
+ psh.pts[1] = Point(250, 100);
+ psh.pts[2] = Point(250, 400);
+ psh.pts[3] = Point(400, 320);
+ psh.pts[4] = Point(50, 250);
+
+
+ toggles.emplace_back(" arc / ellipse ", false);
+ sliders.emplace_back(0, 5, 1, 0, "more handles");
+
+ handles.push_back(&psh);
+ handles.push_back(&(toggles[0]));
+ handles.push_back(&(sliders[0]));
+ }
+
+ private:
+ Ellipse e;
+ EllipticalArc ea;
+ bool first_time;
+ PointSetHandle psh;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new EllipseFitting(), 600, 600 );
+ 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/src/toys/ellipse-intersect-toy.cpp b/src/toys/ellipse-intersect-toy.cpp
new file mode 100644
index 0000000..3d65baa
--- /dev/null
+++ b/src/toys/ellipse-intersect-toy.cpp
@@ -0,0 +1,158 @@
+#include <2geom/cairo-path-sink.h>
+#include <2geom/ellipse.h>
+#include <2geom/line.h>
+#include <2geom/polynomial.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class CircleIntersect : public Toy {
+ PointSetHandle psh[2];
+ Ellipse ea, eb;
+ Line l[6];
+
+ void intersect() {
+ // This is code is almost the same as in ellipse.cpp.
+ // We use it here to get the lines.
+ double A, B, C, D, E, F;
+ double a, b, c, d, e, f;
+
+ ea.coefficients(A, E, B, C, D, F);
+ eb.coefficients(a, e, b, c, d, f);
+
+ double I, J, K, L;
+ I = (-E*E*F + 4*A*B*F + C*D*E - A*D*D - B*C*C) / 4;
+ J = -((E*E - 4*A*B) * f + (2*E*F - C*D) * e + (2*A*D - C*E) * d +
+ (2*B*C - D*E) * c + (C*C - 4*A*F) * b + (D*D - 4*B*F) * a) / 4;
+ K = -((e*e - 4*a*b) * F + (2*e*f - c*d) * E + (2*a*d - c*e) * D +
+ (2*b*c - d*e) * C + (c*c - 4*a*f) * B + (d*d - 4*b*f) * A) / 4;
+ L = (-e*e*f + 4*a*b*f + c*d*e - a*d*d - b*c*c) / 4;
+
+ std::vector<double> mu = solve_cubic(I, J, K, L);
+
+ for (unsigned i = 0; i < mu.size(); ++i) {
+ double aa = mu[i] * A + a;
+ double bb = mu[i] * B + b;
+ double cc = mu[i] * C + c;
+ double dd = mu[i] * D + d;
+ double ee = mu[i] * E + e;
+ double ff = mu[i] * F + f;
+ double delta = ee*ee - 4*aa*bb;
+ if (delta < 0) {
+ continue;
+ }
+
+ if (aa != 0) {
+ bb /= aa; cc /= aa; dd /= aa; ee /= aa; ff /= aa;
+ double s = (ee + std::sqrt(ee*ee - 4*bb)) / 2;
+ double q = ee - s;
+ double alpha = (dd - cc*q) / (s - q);
+ double beta = cc - alpha;
+
+ l[i*2] = Line(1, q, alpha);
+ l[i*2+1] = Line(1, s, beta);
+ } else if (bb != 0) {
+ cc /= bb; dd /= bb; ee /= bb; ff /= bb;
+ double s = ee;
+ double q = 0;
+ double alpha = cc / ee;
+ double beta = ff * ee / cc;
+
+ l[i*2] = Line(q, 1, alpha);
+ l[i*2+1] = Line(s, 1, beta);
+ } else {
+ // both aa and bb are zero
+ l[i*2] = Line(ee, 1, dd);
+ l[i*2+1] = Line(0, 1, cc/ee);
+ }
+ }
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ Rect all(Point(0,0), Point(width, height));
+
+ double r1x = Geom::distance(psh[0].pts[0], psh[0].pts[1]);
+ double r1y = Geom::distance(psh[0].pts[0], psh[0].pts[2]);
+ double rot1 = Geom::atan2(psh[0].pts[1] - psh[0].pts[0]);
+
+ double r2x = Geom::distance(psh[1].pts[0], psh[1].pts[1]);
+ double r2y = Geom::distance(psh[1].pts[0], psh[1].pts[2]);
+ double rot2 = Geom::atan2(psh[1].pts[1] - psh[1].pts[0]);
+
+ ea = Ellipse(psh[0].pts[0], Point(r1x, r1y), rot1);
+ eb = Ellipse(psh[1].pts[0], Point(r2x, r2y), rot2);
+
+ for (auto & i : l) {
+ i = Line(0, 0, 0);
+ }
+
+ cairo_set_line_width(cr, 1.0);
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ Geom::CairoPathSink cps(cr);
+ cps.feed(ea);
+ cps.feed(eb);
+ cairo_stroke(cr);
+
+ try {
+ intersect();
+ std::vector<ShapeIntersection> result = ea.intersect(eb);
+
+ if (!l[0].isDegenerate() && !l[1].isDegenerate()) {
+ cairo_set_source_rgba(cr, 1, 0, 0, 0.2);
+ draw_line(cr, l[0], all);
+ draw_line(cr, l[1], all);
+ cairo_stroke(cr);
+ }
+ if (!l[2].isDegenerate() && !l[3].isDegenerate()) {
+ cairo_set_source_rgba(cr, 0, 1, 0, 0.2);
+ draw_line(cr, l[2], all);
+ draw_line(cr, l[3], all);
+ cairo_stroke(cr);
+ }
+ if (!l[4].isDegenerate() && !l[5].isDegenerate()) {
+ cairo_set_source_rgba(cr, 0, 0, 1, 0.2);
+ draw_line(cr, l[4], all);
+ draw_line(cr, l[5], all);
+ cairo_stroke(cr);
+ }
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : result) {
+ draw_handle(cr, i.point());
+ }
+ cairo_stroke(cr);
+ } catch(...) {
+ *notify << "Exception";
+ }
+
+ // TODO: draw_handle at intersections
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleIntersect(){
+ psh[0].push_back(300,300); psh[0].push_back(450,150); psh[0].push_back(250, 250);
+ psh[1].push_back(350,300); psh[1].push_back(500,500); psh[1].push_back(300, 350);
+ handles.push_back(&psh[0]);
+ handles.push_back(&psh[1]);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CircleIntersect());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/ellipse-line-intersect-toy.cpp b/src/toys/ellipse-line-intersect-toy.cpp
new file mode 100644
index 0000000..4172852
--- /dev/null
+++ b/src/toys/ellipse-line-intersect-toy.cpp
@@ -0,0 +1,67 @@
+#include <2geom/cairo-path-sink.h>
+#include <2geom/ellipse.h>
+#include <2geom/line.h>
+#include <2geom/polynomial.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class CircleIntersect : public Toy {
+ PointSetHandle eh, lh;
+ Ellipse e;
+ Line l;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ Rect all(Point(0,0), Point(width, height));
+
+ double rx = Geom::distance(eh.pts[0], eh.pts[1]);
+ double ry = Geom::distance(eh.pts[0], eh.pts[2]);
+ double rot = Geom::atan2(eh.pts[1] - eh.pts[0]);
+
+ Ellipse e(eh.pts[0], Point(rx, ry), rot);
+ LineSegment l(lh.pts[0], lh.pts[1]);
+
+ cairo_set_line_width(cr, 1.0);
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ draw_line_segment(cr, l, all);
+ Geom::CairoPathSink cps(cr);
+ cps.feed(e);
+ cairo_stroke(cr);
+
+ std::vector<ShapeIntersection> result = e.intersect(l);
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for (auto & i : result) {
+ draw_handle(cr, i.point());
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ CircleIntersect(){
+ eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350);
+ lh.push_back(280, 50); lh.push_back(320,550);
+ handles.push_back(&eh);
+ handles.push_back(&lh);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new CircleIntersect());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/elliptiarc-3point-center-fitting.cpp b/src/toys/elliptiarc-3point-center-fitting.cpp
new file mode 100644
index 0000000..d2330a1
--- /dev/null
+++ b/src/toys/elliptiarc-3point-center-fitting.cpp
@@ -0,0 +1,266 @@
+/*
+ * make up an elliptical arc knowing 3 points lying on the arc
+ * and the ellipse centre
+ *
+ * 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 <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/elliptical-arc.h>
+#include <2geom/numeric/linear_system.h>
+
+namespace Geom
+{
+
+bool make_elliptical_arc( EllipticalArc & ea,
+ Point const& centre,
+ Point const& initial,
+ Point const& final,
+ Point const& inner )
+{
+
+ Point p[3] = { initial, inner, final };
+ double x1, x2, x3, x4;
+ double y1, y2, y3, y4;
+ double x1y1, x2y2, x3y1, x1y3;
+ NL::Matrix m(3,3);
+ NL::Vector v(3);
+ NL::LinearSystem ls(m, v);
+
+ m.set_all(0);
+ v.set_all(0);
+ for (auto & k : p)
+ {
+ // init_x_y
+ x1 = k[X] - centre[X]; x2 = x1 * x1; x3 = x2 * x1; x4 = x3 * x1;
+ y1 = k[Y] - centre[Y]; y2 = y1 * y1; y3 = y2 * y1; y4 = y3 * y1;
+ x1y1 = x1 * y1;
+ x2y2 = x2 * y2;
+ x3y1 = x3 * y1; x1y3 = x1 * y3;
+
+ // init linear system
+ m(0,0) += x4;
+ m(0,1) += x3y1;
+ m(0,2) += x2y2;
+
+ m(1,0) += x3y1;
+ m(1,1) += x2y2;
+ m(1,2) += x1y3;
+
+ m(2,0) += x2y2;
+ m(2,1) += x1y3;
+ m(2,2) += y4;
+
+ v[0] += x2;
+ v[1] += x1y1;
+ v[2] += y2;
+ }
+
+ ls.SV_solve();
+
+ double A = ls.solution()[0];
+ double B = ls.solution()[1];
+ double C = ls.solution()[2];
+
+
+ //evaluate ellipse rotation angle
+ double rot = std::atan2( -B, -(A - C) )/2;
+ std::cerr << "rot = " << rot << std::endl;
+ bool swap_axes = false;
+ if ( are_near(rot, 0) ) rot = 0;
+ if ( are_near(rot, M_PI/2) || rot < 0 )
+ {
+ swap_axes = true;
+ }
+
+ // evaluate the length of the ellipse rays
+ double cosrot = std::cos(rot);
+ double sinrot = std::sin(rot);
+ double cos2 = cosrot * cosrot;
+ double sin2 = sinrot * sinrot;
+ double cossin = cosrot * sinrot;
+
+ double den = A * cos2 + B * cossin + C * sin2;
+ if ( den <= 0 )
+ {
+ std::cerr << "!(den > 0) error" << std::endl;
+ std::cerr << "evaluating rx" << std::endl;
+ return false;
+ }
+ double rx = std::sqrt(1/den);
+
+ den = C * cos2 - B * cossin + A * sin2;
+ if ( den <= 0 )
+ {
+ std::cerr << "!(den > 0) error" << std::endl;
+ std::cerr << "evaluating ry" << std::endl;
+ return false;
+ }
+ double ry = std::sqrt(1/den);
+
+
+ // the solution is not unique so we choose always the ellipse
+ // with a rotation angle between 0 and PI/2
+ if ( swap_axes ) std::swap(rx, ry);
+ if ( are_near(rot, M_PI/2)
+ || are_near(rot, -M_PI/2)
+ || are_near(rx, ry) )
+ {
+ rot = 0;
+ }
+ else if ( rot < 0 )
+ {
+ rot += M_PI/2;
+ }
+
+ std::cerr << "swap axes: " << swap_axes << std::endl;
+ std::cerr << "rx = " << rx << " ry = " << ry << std::endl;
+ std::cerr << "rot = " << deg_from_rad(rot) << std::endl;
+ std::cerr << "centre: " << centre << std::endl;
+
+
+ // find out how we should set the large_arc_flag and sweep_flag
+ bool large_arc_flag = true;
+ bool sweep_flag = true;
+
+ Point sp_cp = initial - centre;
+ Point ep_cp = final - centre;
+ Point ip_cp = inner - centre;
+
+ double angle1 = angle_between(sp_cp, ep_cp);
+ double angle2 = angle_between(sp_cp, ip_cp);
+ double angle3 = angle_between(ip_cp, ep_cp);
+
+ if ( angle1 > 0 )
+ {
+ if ( angle2 > 0 && angle3 > 0 )
+ {
+ large_arc_flag = false;
+ sweep_flag = true;
+ }
+ else
+ {
+ large_arc_flag = true;
+ sweep_flag = false;
+ }
+ }
+ else
+ {
+ if ( angle2 < 0 && angle3 < 0 )
+ {
+ large_arc_flag = false;
+ sweep_flag = false;
+ }
+ else
+ {
+ large_arc_flag = true;
+ sweep_flag = true;
+ }
+ }
+
+ // finally we're going to create the elliptical arc!
+ try
+ {
+ ea.set( initial, rx, ry, rot,
+ large_arc_flag, sweep_flag, final );
+ }
+ catch( RangeError e )
+ {
+ std::cerr << e.what() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+
+}
+
+
+
+using namespace Geom;
+
+class ElliptiArcMaker : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgb(cr, 0,0,0.3);
+ draw_text(cr, O.pos, "centre");
+ draw_text(cr, A.pos, "initial");
+ draw_text(cr, B.pos, "final");
+ draw_text(cr, C.pos, "inner");
+ cairo_stroke(cr);
+ cairo_set_source_rgb(cr, 0.7,0,0);
+ bool status
+ = make_elliptical_arc(ea, O.pos, A.pos, B.pos, C.pos);
+ if (status)
+ {
+ D2<Geom::SBasis> easb = ea.toSBasis();
+ cairo_d2_sb(cr, easb);
+ }
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ ElliptiArcMaker()
+ : O(443, 441),
+ A(516, 275),
+ B(222, 455),
+ C(190, 234)
+ {
+ handles.push_back(&O);
+ handles.push_back(&A);
+ handles.push_back(&B);
+ handles.push_back(&C);
+ }
+
+ private:
+ PointHandle O, A, B, C;
+ EllipticalArc ea;
+};
+
+
+
+
+
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new ElliptiArcMaker() );
+ return 0;
+}
+
diff --git a/src/toys/elliptiarc-curve-fitting.cpp b/src/toys/elliptiarc-curve-fitting.cpp
new file mode 100644
index 0000000..6c47f8d
--- /dev/null
+++ b/src/toys/elliptiarc-curve-fitting.cpp
@@ -0,0 +1,127 @@
+/*
+ * Elliptical Arc Fitting Toy
+ *
+ * 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 <gsl/gsl_linalg.h>
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/elliptical-arc.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+
+using namespace Geom;
+
+
+
+class EAFittingToy : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ cairo_set_line_width (cr, 0.2);
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.);
+ //D2<SBasis> SB = handles_to_sbasis(handles.begin(), total_handles - 1);
+ D2<SBasis> SB = psh.asBezier();
+ cairo_d2_sb(cr, SB);
+ cairo_stroke(cr);
+
+ cairo_set_line_width (cr, 0.4);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.7, 1.0);
+ try
+ {
+ EllipticalArc EA;
+ if (!arc_from_sbasis(EA, SB, tolerance, 10)) {
+// *notify << "distance error: " << convert.get_error()
+// << " ( " << convert.get_bound() << " )" << std::endl
+// << "angle error: " << convert.get_angle_error()
+// << " ( " << convert.get_angle_tolerance() << " )";
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ D2<SBasis> easb = EA.toSBasis();
+ cairo_d2_sb(cr, easb);
+ cairo_stroke(cr);
+ }
+ catch( RangeError e )
+ {
+ std::cerr << e.what() << std::endl;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ EAFittingToy( double _tolerance )
+ : tolerance(_tolerance)
+ {
+ handles.push_back(&psh);
+ total_handles = 6;
+ for ( unsigned int i = 0; i < total_handles; ++i )
+ {
+ psh.push_back(uniform()*400, uniform()*400);
+ }
+ }
+
+ PointSetHandle psh;
+ unsigned int total_handles;
+ double tolerance;
+};
+
+
+
+int main(int argc, char **argv)
+{
+ double tolerance = 8;
+ if(argc > 1)
+ sscanf(argv[1], "%lf", &tolerance);
+ init( argc, argv, new EAFittingToy(tolerance) );
+ 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:
+*/
diff --git a/src/toys/elliptical-arc-toy.cpp b/src/toys/elliptical-arc-toy.cpp
new file mode 100644
index 0000000..e2518f0
--- /dev/null
+++ b/src/toys/elliptical-arc-toy.cpp
@@ -0,0 +1,903 @@
+/** @file
+ * @brief Demonstration of elliptical arc functions
+ *//*
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Copyright 2008-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/elliptical-arc.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/cairo-path-sink.h>
+
+#include <vector>
+#include <string>
+
+
+using namespace Geom;
+
+
+
+std::string angle_formatter(double angle)
+{
+ return default_formatter(decimal_round(deg_from_rad(angle),2));
+}
+
+
+
+class EllipticalArcToy: public Toy
+{
+
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_BASIC,
+ TEST_COMPARISON,
+ TEST_PORTION,
+ TEST_REVERSE,
+ TEST_NEAREST_POINTS,
+ TEST_DERIVATIVE,
+ TEST_ROOTS,
+ TEST_BOUNDS,
+ TEST_FITTING,
+ TEST_TRANSFORM,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ enum handle_label_t
+ {
+ START_POINT = 0,
+ END_POINT,
+ POINT
+ };
+
+ enum toggle_label_t
+ {
+ LARGE_ARC_FLAG = 0,
+ SWEEP_FLAG,
+ X_Y_TOGGLE
+ };
+
+ enum slider_label_t
+ {
+ RX_SLIDER = 0,
+ RY_SLIDER,
+ ROT_ANGLE_SLIDER,
+ T_SLIDER,
+ FROM_SLIDER = T_SLIDER,
+ TO_SLIDER,
+ TM0_SLIDER = T_SLIDER,
+ TM1_SLIDER,
+ TM2_SLIDER,
+ TM3_SLIDER
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &EllipticalArcToy::draw_menu;
+ }
+
+ void init_common()
+ {
+ set_common_control_geometry = true;
+ set_control_geometry = true;
+
+ double start_angle = (10.0/6.0) * M_PI;
+ double sweep_angle = (4.0/6.0) * M_PI;
+ double end_angle = start_angle + sweep_angle;
+ double rot_angle = (0.0/6.0) * M_PI;
+ double rx = 200;
+ double ry = 150;
+ double cx = 300;
+ double cy = 300;
+
+ Point start_point( cx + rx * std::cos(start_angle),
+ cy + ry * std::sin(start_angle) );
+ Point end_point( cx + rx * std::cos(end_angle),
+ cy + ry * std::sin(end_angle) );
+
+ bool large_arc = false;
+ bool sweep = true;
+
+
+ initial_point.pos = start_point;
+ final_point.pos = end_point;
+
+ try
+ {
+ ea.set (initial_point.pos,
+ rx, ry, rot_angle,
+ large_arc, sweep,
+ final_point.pos);
+ }
+ catch (RangeError const &e)
+ {
+ no_solution = true;
+ std::cerr << e.what() << std::endl;
+ }
+
+ sliders.clear();
+ sliders.reserve(50);
+ sliders.emplace_back(0, 500, 0, ea.ray(X), "ray X");
+ sliders.emplace_back(0, 500, 0, ea.ray(Y), "ray Y");
+ sliders.emplace_back(0, 2*M_PI, 0, ea.rotationAngle(), "rot angle");
+ sliders[ROT_ANGLE_SLIDER].formatter(&angle_formatter);
+
+ toggles.clear();
+ toggles.reserve(50);
+ toggles.emplace_back("Large Arc Flag", ea.largeArc());
+ toggles.emplace_back("Sweep Flag", ea.sweep());
+
+ handles.clear();
+ handles.push_back(&initial_point);
+ handles.push_back(&final_point);
+ handles.push_back(&(sliders[RX_SLIDER]));
+ handles.push_back(&(sliders[RY_SLIDER]));
+ handles.push_back(&(sliders[ROT_ANGLE_SLIDER]));
+ handles.push_back(&(toggles[LARGE_ARC_FLAG]));
+ handles.push_back(&(toggles[SWEEP_FLAG]));
+ }
+
+ virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/,
+ std::ostringstream *timer_stream=0)
+ {
+ if(timer_stream == 0)
+ timer_stream = notify;
+ init_common_ctrl_geom(cr, width, height, notify);
+
+ no_solution = false;
+ try
+ {
+ ea.set( initial_point.pos,
+ sliders[0].value(),
+ sliders[1].value(),
+ sliders[2].value(),
+ toggles[0].on,
+ toggles[1].on,
+ final_point.pos );
+ }
+ catch (RangeError const &e)
+ {
+ no_solution = true;
+ std::cerr << e.what() << std::endl;
+ return;
+ }
+
+ degenerate = ea.isDegenerate();
+
+ point_overlap = false;
+ if ( are_near(ea.initialPoint(), ea.finalPoint()) )
+ {
+ point_overlap = true;
+ }
+
+ // calculate the center of the two possible ellipse supporting the arc
+ std::pair<Point,Point> centers
+ = calculate_ellipse_centers( ea.initialPoint(), ea.finalPoint(),
+ ea.ray(X), ea.ray(Y), ea.rotationAngle(),
+ ea.largeArc(), ea.sweep() );
+
+
+ // draw axes passing through the center of the ellipse supporting the arc
+ cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.5);
+ draw_axes(cr);
+
+ // draw the 2 ellipse with rays rx, ry passing through
+ // the 2 given point and with the x-axis inclined of rot_angle
+ if ( !(are_near(ea.ray(X), 0) || are_near(ea.ray(Y), 0)) )
+ {
+ cairo_elliptiarc( cr,
+ centers.first[X], centers.first[Y],
+ ea.ray(X), ea.ray(Y),
+ 0, 2*M_PI,
+ ea.rotationAngle() );
+ cairo_stroke(cr);
+ cairo_elliptiarc( cr,
+ centers.second[X], centers.second[Y],
+ ea.ray(X), ea.ray(Y),
+ 0, 2*M_PI,
+ ea.rotationAngle() );
+ cairo_stroke(cr);
+ }
+
+ // convert the elliptical arc to a sbasis path and draw it
+ D2<SBasis> easb = ea.toSBasis();
+ cairo_set_line_width(cr, 0.5);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
+ cairo_d2_sb(cr, easb);
+ cairo_stroke(cr);
+
+ // draw initial and final point labels
+ draw_text(cr, ea.initialPoint() + Point(5, -15), "initial");
+ draw_text(cr, ea.finalPoint() + Point(5, 0), "final");
+ cairo_stroke(cr);
+
+ // TODO re-enable this
+ //*notify << ea;
+ }
+
+
+ void draw_comparison(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ if ( no_solution || point_overlap ) return;
+
+ // draw the arc with cairo in order to make a visual comparison
+ cairo_set_line_width(cr, 1);
+ cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0);
+
+ if (ea.isDegenerate())
+ {
+ cairo_move_to(cr, ea.initialPoint());
+ cairo_line_to(cr, ea.finalPoint());
+ }
+ else
+ {
+ if ( ea.sweep() )
+ {
+ cairo_elliptiarc( cr,
+ ea.center(X), ea.center(Y),
+ ea.ray(X), ea.ray(Y),
+ ea.initialAngle(), ea.finalAngle(),
+ ea.rotationAngle() );
+ }
+ else
+ {
+ cairo_elliptiarc( cr,
+ ea.center(X), ea.center(Y),
+ ea.ray(X), ea.ray(Y),
+ ea.finalAngle(), ea.initialAngle(),
+ ea.rotationAngle() );
+ }
+ }
+ cairo_stroke(cr);
+ }
+
+
+ void init_portion()
+ {
+ init_common();
+
+ from_t = 0;
+ to_t = 1;
+
+ sliders.emplace_back(0, 1, 0, from_t, "from");
+ sliders.emplace_back(0, 1, 0, to_t, "to");
+
+ handles.push_back(&(sliders[FROM_SLIDER]));
+ handles.push_back(&(sliders[TO_SLIDER]));
+ }
+
+ void draw_portion(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_portion_ctrl_geom(cr, notify, width, height);
+ if ( no_solution || point_overlap ) return;
+
+ from_t = sliders[FROM_SLIDER].value();
+ to_t = sliders[TO_SLIDER].value();
+
+ EllipticalArc* eapp
+ = static_cast<EllipticalArc*>(ea.portion(from_t, to_t));
+ EllipticalArc& eap = *eapp;
+
+ cairo_set_line_width(cr, 0.8);
+ cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0);
+ cairo_move_to(cr, eap.center(X), eap.center(Y));
+ cairo_line_to(cr, eap.initialPoint()[X], eap.initialPoint()[Y]);
+ cairo_move_to(cr, eap.center(X), eap.center(Y));
+ cairo_line_to(cr, eap.finalPoint()[X], eap.finalPoint()[Y]);
+ cairo_stroke(cr);
+ D2<SBasis> sub_arc = eap.toSBasis();
+ cairo_d2_sb(cr, sub_arc);
+ cairo_stroke(cr);
+
+ delete eapp;
+
+ }
+
+
+ void init_reverse()
+ {
+ init_common();
+ time = 0;
+
+ sliders.emplace_back(0, 1, 0, time, "t");
+ handles.push_back(&(sliders[T_SLIDER]));
+ }
+
+ void draw_reverse(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_reverse_ctrl_geom(cr, notify, width, height);
+ if ( no_solution || point_overlap ) return;
+
+
+ time = sliders[T_SLIDER].value();
+
+ EllipticalArc* eapp = static_cast<EllipticalArc*>(ea.reverse());
+ EllipticalArc& eap = *eapp;
+
+ cairo_set_line_width(cr, 0.8);
+ cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
+
+ cairo_move_to(cr, eap.center(X), eap.center(Y));
+ cairo_line_to(cr, eap.valueAt(time,X), eap.valueAt(time,Y));
+ draw_circ(cr, eap.pointAt(time));
+ cairo_stroke(cr);
+ cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0);
+ D2<SBasis> sub_arc = eap.toSBasis();
+ cairo_d2_sb(cr, sub_arc);
+ cairo_stroke(cr);
+
+ delete eapp;
+
+ }
+
+
+ void init_np()
+ {
+ init_common();
+ nph.pos = Point(10,10);
+ handles.push_back(&nph);
+ }
+
+ void draw_np(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ if ( no_solution || point_overlap ) return;
+
+ std::vector<double> times = ea.allNearestTimes( nph.pos );
+ for (double time : times)
+ {
+ cairo_move_to(cr,nph.pos);
+ cairo_line_to( cr, ea.pointAt(time) );
+ }
+ cairo_stroke(cr);
+ }
+
+
+ void init_derivative()
+ {
+ init_common();
+ time = 0;
+
+ sliders.emplace_back(0, 1, 0, time, "t");
+ handles.push_back(&(sliders[T_SLIDER]));
+ }
+
+ void draw_derivative(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_reverse_ctrl_geom(cr, notify, width, height);
+ if ( no_solution || point_overlap ) return;
+
+ time = sliders[T_SLIDER].value();
+
+ Curve* der = ea.derivative();
+ Point p = ea.pointAt(time);
+ Point v = der->pointAt(time) + p;
+ delete der;
+// std::vector<Point> points = ea.pointAndDerivatives(time, 8);
+// Point p = points[0];
+// Point v = points[1] + p;
+ cairo_move_to(cr, p);
+ cairo_line_to(cr, v);
+ cairo_stroke(cr);
+ }
+
+
+ void init_roots()
+ {
+ init_common();
+ ph.pos = Point(10,10);
+ toggles.emplace_back("X/Y roots", true );
+
+ handles.push_back(&ph);
+ handles.push_back(&(toggles[X_Y_TOGGLE]));
+ }
+
+ void draw_roots(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_roots_ctrl_geom(cr, notify, width, height);
+ if ( no_solution || point_overlap ) return;
+
+ Dim2 DIM = toggles[X_Y_TOGGLE].on ? X : Y;
+
+ Point p1[2] = { Point(ph.pos[X], -1000),
+ Point(-1000, ph.pos[Y]) };
+ Point p2[2] = { Point(ph.pos[X], 1000),
+ Point(1000, ph.pos[Y]) };
+ cairo_set_line_width(cr, 0.5);
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_move_to(cr, p1[DIM]);
+ cairo_line_to(cr, p2[DIM]);
+
+ std::vector<double> times;
+ try
+ {
+ times = ea.roots(ph.pos[DIM], DIM);
+ *notify << "winding: " << ea.winding(ph.pos);
+ }
+ catch(Geom::Exception e)
+ {
+ std::cerr << e.what() << std::endl;
+ }
+ for (double time : times)
+ {
+ draw_handle(cr, ea.pointAt(time));
+ }
+ cairo_stroke(cr);
+ }
+
+
+ void init_bounds()
+ {
+ init_common();
+ }
+
+ void draw_bounds(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ if ( no_solution || point_overlap ) return;
+
+// const char* msg[] = { "xmax", "xmin", "ymax", "ymin" };
+
+ Rect bb = ea.boundsFast();
+
+// for ( unsigned int i = 0; i < limits.size(); ++i )
+// {
+// std::cerr << "angle[" << i << "] = " << deg_from_rad(limits[i]) << std::endl;
+// Point extreme = ea.pointAtAngle(limits[i]);
+// draw_handle(cr, extreme );
+// draw_text(cr, extreme, msg[i]);
+// }
+ cairo_rectangle( cr, bb.left(), bb.top(), bb.width(), bb.height() );
+ cairo_stroke(cr);
+ }
+
+
+ void init_fitting()
+ {
+ init_common();
+ }
+
+ void draw_fitting(cairo_t * cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ if ( no_solution || point_overlap ) return;
+
+ D2<SBasis> easb = ea.toSBasis();
+ try
+ {
+ EllipticalArc earc;
+ if (!arc_from_sbasis(earc, easb, 0.1, 5)) return;
+
+ D2<SBasis> arc = earc.toSBasis();
+ arc[0] += Linear(50, 50);
+ cairo_d2_sb(cr, arc);
+ cairo_stroke(cr);
+ }
+ catch (RangeError const &e)
+ {
+ std::cerr << "conversion failure" << std::endl;
+ std::cerr << e.what() << std::endl;
+ return;
+ }
+ }
+
+ void init_transform()
+ {
+ init_common();
+
+ double max = 4;
+ double min = -max;
+
+ sliders.emplace_back(min, max, 0, 1, "TM0");
+ sliders.emplace_back(min, max, 0, 0, "TM1");
+ sliders.emplace_back(min, max, 0, 0, "TM2");
+ sliders.emplace_back(min, max, 0, 1, "TM3");
+
+ handles.push_back(&(sliders[TM0_SLIDER]));
+ handles.push_back(&(sliders[TM1_SLIDER]));
+ handles.push_back(&(sliders[TM2_SLIDER]));
+ handles.push_back(&(sliders[TM3_SLIDER]));
+ }
+
+ void draw_transform(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_transform_ctrl_geom(cr, notify, width, height);
+ if ( no_solution || point_overlap ) return;
+
+ Affine TM(sliders[TM0_SLIDER].value(), sliders[TM1_SLIDER].value(),
+ sliders[TM2_SLIDER].value(), sliders[TM3_SLIDER].value(),
+ ea.center(X), ea.center(Y));
+
+ Affine tm( 1, 0,
+ 0, 1,
+ -ea.center(X), -ea.center(Y) );
+
+
+ EllipticalArc* tea = static_cast<EllipticalArc*>(ea.transformed(tm));
+ EllipticalArc* eat = NULL;
+ eat = static_cast<EllipticalArc*>(tea->transformed(TM));
+ delete tea;
+ if (eat == NULL)
+ {
+ std::cerr << "elliptiarc transformation failed" << std::endl;
+ return;
+ }
+
+ CairoPathSink ps(cr);
+
+ //D2<SBasis> sb = eat->toSBasis();
+ cairo_set_line_width(cr, 0.8);
+ cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0);
+ //cairo_d2_sb(cr, sb);
+ ps.feed(*eat);
+ cairo_stroke(cr);
+ delete eat;
+ }
+
+ void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int height, std::ostringstream* /*notify*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+
+ sliders[RX_SLIDER].geometry(Point(50, height-120), 250);
+ sliders[RY_SLIDER].geometry(Point(50, height-85), 250);
+ sliders[ROT_ANGLE_SLIDER].geometry(Point(50, height-50), 180);
+
+ toggles[LARGE_ARC_FLAG].bounds = Rect(Point(400, height-120), Point(540, height-95));
+ toggles[SWEEP_FLAG].bounds = Rect(Point(400, height-70), Point(520, height-45));
+ }
+ }
+
+ void init_portion_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ Point from_sp = Point(600, height - 120);
+ Point to_sp = from_sp + Point(0,45);
+ double from_to_len = 100;
+
+ sliders[FROM_SLIDER].geometry(from_sp, from_to_len);
+ sliders[TO_SLIDER].geometry(to_sp, from_to_len);
+ }
+ }
+
+ void init_reverse_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ Point t_sp = Point(600, height - 120);
+ double t_len = 200;
+
+ sliders[T_SLIDER].geometry(t_sp, t_len);
+ }
+ }
+
+ void init_roots_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+ Point T(600, height - 120);
+ toggles[X_Y_TOGGLE].bounds = Rect( T, T + Point(100,25) );
+ }
+ }
+
+ void init_transform_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ Point sp = Point(600, height - 140);
+ Point op = Point(0, 30);
+ double len = 200;
+
+ sliders[TM0_SLIDER].geometry(sp, len);
+ sliders[TM1_SLIDER].geometry(sp += op, len);
+ sliders[TM2_SLIDER].geometry(sp += op, len);
+ sliders[TM3_SLIDER].geometry(sp += op, len);
+ }
+ }
+
+ void init_menu()
+ {
+ handles.clear();
+ sliders.clear();
+ toggles.clear();
+ }
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/)
+ {
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &EllipticalArcToy::draw_menu;
+ break;
+ case 'B':
+ init_common();
+ draw_f = &EllipticalArcToy::draw_common;
+ break;
+ case 'C':
+ init_common();
+ draw_f = &EllipticalArcToy::draw_comparison;
+ break;
+ case 'D':
+ draw_f = &EllipticalArcToy::draw_menu;
+ init_portion();
+ draw_f = &EllipticalArcToy::draw_portion;
+ break;
+ case 'E':
+ init_reverse();
+ draw_f = &EllipticalArcToy::draw_reverse;
+ break;
+ case 'F':
+ init_np();
+ draw_f = &EllipticalArcToy::draw_np;
+ break;
+ case 'G':
+ init_derivative();
+ draw_f = &EllipticalArcToy::draw_derivative;
+ break;
+ case 'H':
+ init_roots();
+ draw_f = &EllipticalArcToy::draw_roots;
+ break;
+ case 'I':
+ init_bounds();
+ draw_f = &EllipticalArcToy::draw_bounds;
+ break;
+ case 'J':
+ init_fitting();
+ draw_f = &EllipticalArcToy::draw_fitting;
+ break;
+ case 'K':
+ init_transform();
+ draw_f = &EllipticalArcToy::draw_transform;
+ break;
+ }
+ redraw();
+ }
+
+
+ void draw_axes(cairo_t* cr) const
+ {
+ Point D(std::cos(ea.rotationAngle()), std::sin(ea.rotationAngle()));
+ Point Dx = (ea.ray(X) + 20) * D;
+ Point Dy = (ea.ray(Y) + 20) * D.cw();
+ Point C(ea.center(X),ea.center(Y));
+ Point LP = C - Dx;
+ Point RP = C + Dx;
+ Point UP = C - Dy;
+ Point DP = C + Dy;
+
+ cairo_move_to(cr, LP[X], LP[Y]);
+ cairo_line_to(cr, RP[X], RP[Y]);
+ cairo_move_to(cr, UP[X], UP[Y]);
+ cairo_line_to(cr, DP[X], DP[Y]);
+ cairo_move_to(cr, 0, 0);
+ cairo_stroke(cr);
+ }
+
+ void cairo_elliptiarc( cairo_t *cr,
+ double _cx, double _cy,
+ double _rx, double _ry,
+ double _sa, double _ea,
+ double _ra = 0
+ ) const
+ {
+ double cos_rot_angle = std::cos(_ra);
+ double sin_rot_angle = std::sin(_ra);
+ cairo_matrix_t transform_matrix;
+ cairo_matrix_init( &transform_matrix,
+ _rx * cos_rot_angle, _rx * sin_rot_angle,
+ -_ry * sin_rot_angle, _ry * cos_rot_angle,
+ _cx, _cy
+ );
+ cairo_save(cr);
+ cairo_transform(cr, &transform_matrix);
+ cairo_arc(cr, 0, 0, 1, _sa, _ea);
+ cairo_restore(cr);
+ }
+
+
+ std::pair<Point,Point>
+ calculate_ellipse_centers( Point _initial_point, Point _final_point,
+ double m_rx, double m_ry,
+ double m_rot_angle,
+ bool m_large_arc, bool m_sweep
+ )
+ {
+ std::pair<Point,Point> result;
+ if ( _initial_point == _final_point )
+ {
+ result.first = result.second = _initial_point;
+ return result;
+ }
+
+ m_rx = std::fabs(m_rx);
+ m_ry = std::fabs(m_ry);
+
+ Point d = _initial_point - _final_point;
+
+ if ( are_near(m_rx, 0) || are_near(m_ry, 0) )
+ {
+ result.first = result.second
+ = middle_point(_initial_point, _final_point);
+ return result;
+ }
+
+ double sin_rot_angle = std::sin(m_rot_angle);
+ double cos_rot_angle = std::cos(m_rot_angle);
+
+
+ Affine m( cos_rot_angle, -sin_rot_angle,
+ sin_rot_angle, cos_rot_angle,
+ 0, 0 );
+
+ Point p = (d / 2) * m;
+ double rx2 = m_rx * m_rx;
+ double ry2 = m_ry * m_ry;
+ double rxpy = m_rx * p[Y];
+ double rypx = m_ry * p[X];
+ double rx2py2 = rxpy * rxpy;
+ double ry2px2 = rypx * rypx;
+ double num = rx2 * ry2;
+ double den = rx2py2 + ry2px2;
+ assert(den != 0);
+ double rad = num / den;
+ Point c(0,0);
+ if (rad > 1)
+ {
+ rad -= 1;
+ rad = std::sqrt(rad);
+
+ if (m_large_arc == m_sweep) rad = -rad;
+ c = rad * Point(rxpy / m_ry, -rypx / m_rx);
+
+ m[1] = -m[1];
+ m[2] = -m[2];
+
+ c = c * m;
+ }
+
+ d = middle_point(_initial_point, _final_point);
+
+ result.first = c + d;
+ result.second = -c + d;
+ return result;
+
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ EllipticalArcToy() {}
+
+ private:
+ typedef void (EllipticalArcToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ bool no_solution, point_overlap;
+ bool degenerate;
+ PointHandle initial_point, final_point;
+ PointHandle nph, ph;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ EllipticalArc ea;
+
+ double from_t;
+ double to_t;
+ double time;
+
+};
+
+
+const char* EllipticalArcToy::menu_items[] =
+{
+ "show this menu",
+ "basic",
+ "comparison",
+ "portion, pointAt",
+ "reverse, valueAt",
+ "nearest points",
+ "derivative",
+ "roots",
+ "bounding box",
+ "fitting",
+ "transformation"
+};
+
+const char EllipticalArcToy::keys[] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'
+};
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new EllipticalArcToy(), 850, 780 );
+ 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/src/toys/evolute.cpp b/src/toys/evolute.cpp
new file mode 100644
index 0000000..a5deb08
--- /dev/null
+++ b/src/toys/evolute.cpp
@@ -0,0 +1,93 @@
+#include <2geom/basic-intersection.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+/*
+jfb: I think the evolute goes to infinity at inflection points, in which case you cannot "join" the pieces by hand.
+jfb: for the evolute toy, you could not only cut at inflection points, but event remove the domains where cross(dda,da)<c*|da|^3, where c is a small constant, as these points will be off screen anyway.
+*/
+
+class Evolution: public Toy {
+ PointSetHandle psh;
+void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_line_width (cr, 0.5);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+
+ D2<SBasis> A(psh.asBezier());
+
+ D2<SBasis> dA = derivative(A);
+ D2<SBasis> ddA = derivative(dA);
+ SBasis crs = cross(ddA, dA);
+ cairo_d2_sb(cr, D2<SBasis>(Linear(0,1000), crs*(500./bounds_exact(crs)->extent())));
+ vector<double> rts = roots(crs);
+ for(double rt : rts) {
+ draw_handle(cr, A(rt));
+ }
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+ Interval r(0, 1);
+ if(!rts.empty())
+ r.setMax(rts[0]);
+ //if(rts[0] == 0)
+ //rts.erase(rts.begin(), rts.begin()+1);
+ A = portion(A, r.min(), r.max());
+ dA = portion(dA, r.min(), r.max());
+ ddA = portion(ddA, r.min(), r.max());
+ crs = portion(crs, r.min(), r.max());
+ cairo_stroke(cr);
+ Piecewise<SBasis> s = divide(dA[0]*dot(dA,dA), crs, 100, 1);
+ D2<Piecewise<SBasis> > ev4(Piecewise<SBasis>(A[0]) + divide(-dA[1]*dot(dA,dA), crs, 100, 1),
+ Piecewise<SBasis>(A[1]) + divide(dA[0]*dot(dA,dA), crs, 100, 1));
+ cairo_d2_pw_sb(cr, ev4);
+ cairo_stroke(cr);
+ if(1) {
+ std::cout << "bnd" << *bounds_exact(dot(ev4, ev4)) << std::endl;
+ cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0,1000))), dot(ev4, ev4)*1000));
+ cairo_stroke(cr);
+ vector<double> rts = roots(dot(ev4, ev4)-1);
+ for(double rt : rts) {
+ std::cout << rt << std::endl;
+ draw_handle(cr, ev4(rt));
+ }
+ }
+ cairo_set_source_rgba (cr, 1., 0., 1, 1);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+}
+public:
+Evolution (unsigned bez_ord) {
+ handles.push_back(&psh);
+ for(unsigned i = 0; i < bez_ord; i++)
+ psh.push_back(uniform()*400, uniform()*400);
+}
+};
+
+int main(int argc, char **argv) {
+ unsigned bez_ord=5;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &bez_ord);
+ init(argc, argv, new Evolution(bez_ord));
+
+ 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/src/toys/filet-minion.cpp b/src/toys/filet-minion.cpp
new file mode 100644
index 0000000..e94e354
--- /dev/null
+++ b/src/toys/filet-minion.cpp
@@ -0,0 +1,159 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/path-intersection.h>
+#include <2geom/nearest-time.h>
+#include <2geom/circle.h>
+
+#include <cstdlib>
+#include <map>
+#include <vector>
+#include <algorithm>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/ord.h>
+using namespace Geom;
+using namespace std;
+
+class IntersectDataTester: public Toy {
+ int nb_paths;
+ int nb_curves_per_path;
+ int degree;
+
+ std::vector<PointSetHandle> paths_handles;
+ std::vector<Slider> sliders;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ std::vector<D2<SBasis> > pieces;
+ PathVector paths;
+ for (int i = 0; i < nb_paths; i++){
+ paths.push_back(Path(paths_handles[i].pts[0]));
+ for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){
+ D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree);
+ paths[i].append(c);
+ pieces.push_back(c);
+ }
+ }
+
+ cairo_path(cr, paths);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+ double r = sliders[0].value();
+
+ D2<SBasis> B = pieces[0];
+ Piecewise<D2<SBasis> > offset_curve0 = Piecewise<D2<SBasis> >(pieces[0])+rot90(unitVector(derivative(pieces[0])))*(-r);
+ Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(pieces[1])+rot90(unitVector(derivative(pieces[1])))*(-r);
+
+ //cairo_pw_d2_sb(cr, offset_curve0);
+ //cairo_pw_d2_sb(cr, offset_curve1);
+ //cairo_stroke(cr);
+
+
+ Path p0 = path_from_piecewise(offset_curve0, 0.1)[0];
+ Path p1 = path_from_piecewise(offset_curve1, 0.1)[0];
+ Crossings cs = crossings(p0, p1);
+
+
+ for(auto & c : cs) {
+ *notify << c.ta << ", " << c.tb << '\n';
+ Point cp =p0(c.ta);
+ //draw_circ(cr, cp);
+ //cairo_stroke(cr);
+ double p0pt = nearest_time(cp, pieces[0]);
+ double p1pt = nearest_time(cp, pieces[1]);
+ Circle circ(cp[0], cp[1], r);
+ //cairo_arc(cr, circ.center(X), circ.center(Y), circ.ray(), 0, 2*M_PI);
+
+ std::unique_ptr<EllipticalArc> eap(
+ circ.arc(pieces[0](p0pt), pieces[0](1), pieces[1](p1pt)) );
+ D2<SBasis> easb = eap->toSBasis();
+ cairo_d2_sb(cr, easb);
+ cairo_stroke(cr);
+ }
+
+ Point ends[2];
+ if (0)
+ for(int endi = 0; endi < 2; endi++) {
+ D2<SBasis> dist = pieces[endi]-pieces[0].at1();
+ *notify << dist << "\n";
+ vector<double> locs = roots(dot(dist,dist) - SBasis(r*r));
+ for(double loc : locs) {
+ //draw_circ(cr, pieces[endi](locs[i]));
+ *notify << loc << ' ';
+ }
+ if(locs.size()) {
+ std::sort(locs.begin(), locs.end());
+ if (endi)
+ ends[endi] = pieces[endi](locs[0]);
+ else
+ ends[endi] = pieces[endi](locs.back());
+ draw_circ(cr, ends[endi]);
+ }
+ }
+
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ IntersectDataTester(int paths, int curves_in_path, int degree) :
+ nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) {
+ for (int i = 0; i < nb_paths; i++){
+ paths_handles.emplace_back();
+ }
+ for(int i = 0; i < nb_paths; i++){
+ for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){
+ paths_handles[i].push_back(uniform()*400, 100+ uniform()*300);
+ }
+ handles.push_back(&paths_handles[i]);
+ }
+ sliders.emplace_back(0.0, 100.0, 1, 30.0, "min radius");
+ sliders.emplace_back(0.0, 100.0, 1, 0.0, "ray chooser");
+ sliders.emplace_back(0.0, 100.0, 1, 0.0, "area chooser");
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(sliders[1]));
+ handles.push_back(&(sliders[2]));
+ sliders[0].geometry(Point(50, 20), 250);
+ sliders[1].geometry(Point(50, 50), 250);
+ sliders[2].geometry(Point(50, 80), 250);
+ }
+
+ void first_time(int /*argc*/, char** /*argv*/) override {
+
+ }
+};
+
+int main(int argc, char **argv) {
+ unsigned paths=1;
+ unsigned curves_in_path=2;
+ unsigned degree=3;
+ if(argc > 3)
+ sscanf(argv[3], "%d", &degree);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &curves_in_path);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &paths);
+ init(argc, argv, new IntersectDataTester(paths, curves_in_path, degree));
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/find-derivative.cpp b/src/toys/find-derivative.cpp
new file mode 100644
index 0000000..e4c64f5
--- /dev/null
+++ b/src/toys/find-derivative.cpp
@@ -0,0 +1,500 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/angle.h>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// Author: Johan Engelen, 2009
+//
+// Shows how to find the locations on a path where the derivative is parallel to a certain vector.
+//-----------------------------------------------
+
+
+std::string angle_formatter(double angle)
+{
+ return default_formatter(decimal_round(deg_from_rad(angle),2));
+}
+
+
+
+class FindDerivatives : public Toy
+{
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_CREATE,
+ TEST_PROJECTION,
+ TEST_ORTHO,
+ TEST_DISTANCE,
+ TEST_POSITION,
+ TEST_SEG_BISEC,
+ TEST_ANGLE_BISEC,
+ TEST_COLLINEAR,
+ TEST_INTERSECTIONS,
+ TEST_COEFFICIENTS,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ enum handle_label_t
+ {
+ };
+
+ enum toggle_label_t
+ {
+ };
+
+ enum slider_label_t
+ {
+ END_SHARED_SLIDERS = 0,
+ ANGLE_SLIDER = END_SHARED_SLIDERS,
+ A_COEFF_SLIDER = END_SHARED_SLIDERS,
+ B_COEFF_SLIDER,
+ C_COEFF_SLIDER
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ PointSetHandle curve_handle;
+ PointHandle sample_point;
+
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &FindDerivatives::draw_menu;
+ }
+
+ void init_common()
+ {
+ set_common_control_geometry = true;
+ set_control_geometry = true;
+
+ sliders.clear();
+ toggles.clear();
+ handles.clear();
+ }
+
+
+ virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/ )
+ {
+ init_common_ctrl_geom(cr, width, height, notify);
+ }
+
+
+ void init_create()
+ {
+ init_common();
+
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ O.pos = Point(50, 400);
+
+ sliders.emplace_back(0, 2*M_PI, 0, 0, "angle");
+ sliders[ANGLE_SLIDER].formatter(&angle_formatter);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&O);
+ handles.push_back(&(sliders[ANGLE_SLIDER]));
+ }
+
+ void draw_create(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_create_ctrl_geom(cr, notify, width, height);
+
+ Line l1(p1.pos, p2.pos);
+ Line l2(O.pos, sliders[ANGLE_SLIDER].value());
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_line(cr, l2);
+ cairo_stroke(cr);
+
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, O, "O");
+ draw_label(cr, l1, "L(P1,P2)");
+ draw_label(cr, l2, "L(O,angle)");
+ }
+
+
+ void init_projection()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 250);
+ p4.pos = Point(200, 450);
+ O.pos = Point(50, 150);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ handles.push_back(&O);
+ }
+
+ void draw_projection(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ LineSegment ls(p3.pos, p4.pos);
+
+ Point np = projection(O.pos, l1);
+ LineSegment lsp = projection(ls, l1);
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width(cr, 0.2);
+ draw_line(cr, l1);
+ draw_segment(cr, ls);
+ cairo_stroke(cr);
+
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
+ draw_segment(cr, lsp);
+ draw_handle(cr, lsp[0]);
+ draw_handle(cr, lsp[1]);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ draw_circ(cr, np);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, ls, "S");
+ draw_label(cr, lsp, "prj(S)");
+ draw_label(cr, O, "P");
+ draw_text(cr, np, "prj(P)");
+
+ cairo_stroke(cr);
+ }
+
+ void init_derivative() {
+ init_common();
+ handles.push_back(&curve_handle);
+ handles.push_back(&sample_point);
+ for(unsigned i = 0; i < 4; i++)
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ sample_point.pos = Geom::Point(250,300);
+ }
+
+ void draw_derivative(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+
+ D2<SBasis> B = curve_handle.asBezier();
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ Point vector = sample_point.pos - Geom::Point(400,400);
+ cairo_move_to(cr, Geom::Point(400,400));
+ cairo_line_to(cr, sample_point.pos);
+ cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8);
+ cairo_stroke(cr);
+
+ // How to find location of points with certain derivative along a path:
+ D2<SBasis> deriv = derivative(B);
+ SBasis dotp = dot(deriv, rot90(vector));
+ std::vector<double> sol = roots(dotp);
+ for (double i : sol) {
+ draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol'
+ }
+
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void init_find_tangents() {
+ init_common();
+ handles.push_back(&curve_handle);
+ handles.push_back(&sample_point);
+
+ toggles.emplace_back(" tangent / normal ", false);
+ handles.push_back(&(toggles[0]));
+ for(unsigned i = 0; i < 4; i++)
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ sample_point.pos = Geom::Point(250,300);
+ Point toggle_sp( 30, 30);
+ toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(200,25) );
+ }
+
+ void draw_find_tangents(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+
+ D2<SBasis> B = curve_handle.asBezier();
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ std::vector<double> sol = toggles[0].on ?
+ find_tangents(sample_point.pos, B)
+ : find_normals(sample_point.pos, B);
+ for (double i : sol) {
+ draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol'
+ draw_segment(cr, B.valueAt(i), sample_point.pos);
+ }
+
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void init_ortho()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 50);
+ p4.pos = Point(150, 450);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ }
+
+ void draw_ortho(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ Line l2 = make_orthogonal_line(p3.pos, l1);
+ Line l3 = make_parallel_line(p4.pos, l1);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_line(cr, l2);
+ draw_line(cr, l3);
+ cairo_stroke(cr);
+
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, p3, "O1");
+ draw_label(cr, p4, "O2");
+
+ draw_label(cr, l1, "L");
+ draw_label(cr, l2, "L1 _|_ L");
+ draw_label(cr, l3, "L2 // L");
+
+ }
+
+
+
+
+ void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ }
+ }
+
+ void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180);
+ }
+ }
+
+ void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400);
+ sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400);
+ sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400);
+ }
+ }
+
+
+ void draw_segment(cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+ }
+
+ void draw_segment(cairo_t* cr, Point const& p1, double angle, double length)
+ {
+ Point p2;
+ p2[X] = length * std::cos(angle);
+ p2[Y] = length * std::sin(angle);
+ p2 += p1;
+ draw_segment(cr, p1, p2);
+ }
+
+ void draw_segment(cairo_t* cr, LineSegment const& ls)
+ {
+ draw_segment(cr, ls[0], ls[1]);
+ }
+
+ void draw_ray(cairo_t* cr, Ray const& r)
+ {
+ double angle = r.angle();
+ draw_segment(cr, r.origin(), angle, m_length);
+ }
+
+ void draw_line(cairo_t* cr, Line const& l)
+ {
+ double angle = l.angle();
+ draw_segment(cr, l.origin(), angle, m_length);
+ draw_segment(cr, l.origin(), angle, -m_length);
+ }
+
+ void draw_label(cairo_t* cr, PointHandle const& ph, const char* label)
+ {
+ draw_text(cr, ph.pos+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Line const& l, const char* label)
+ {
+ draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label);
+ }
+
+ void draw_label(cairo_t* cr, LineSegment const& ls, const char* label)
+ {
+ draw_text(cr, middle_point(ls[0], ls[1])+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Ray const& r, const char* label)
+ {
+ Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30)));
+ if (L2(r.origin() - prj) < 100)
+ {
+ prj = r.origin() + 100*r.vector();
+ }
+ draw_text(cr, prj+op, label);
+ }
+
+ void init_menu()
+ {
+ handles.clear();
+ sliders.clear();
+ toggles.clear();
+ }
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/ )
+ {
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &FindDerivatives::draw_menu;
+ break;
+ case 'B':
+ init_derivative();
+ draw_f = &FindDerivatives::draw_derivative;
+ break;
+ case 'C':
+ init_find_tangents();
+ draw_f = &FindDerivatives::draw_find_tangents;
+ break;
+ case 'D':
+ init_ortho();
+ draw_f = &FindDerivatives::draw_ortho;
+ break;
+ }
+ redraw();
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ FindDerivatives()
+ {
+ op = Point(5,5);
+ }
+
+ private:
+ typedef void (FindDerivatives::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ PointHandle p1, p2, p3, p4, p5, p6, O;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ Point op;
+ double m_width, m_height, m_length;
+
+}; // end class FindDerivatives
+
+
+const char* FindDerivatives::menu_items[] =
+{
+ "show this menu",
+ "derivative matching on curve",
+ "find normals",
+ "find tangents"
+};
+
+const char FindDerivatives::keys[] =
+{
+ 'A', 'B', 'C', 'D'
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new FindDerivatives());
+ 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/gear.cpp b/src/toys/gear.cpp
new file mode 100644
index 0000000..d2f2de2
--- /dev/null
+++ b/src/toys/gear.cpp
@@ -0,0 +1,317 @@
+/*
+ * GearToy - displays involute gears
+ *
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ * Copyright 2006 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.
+ *
+ */
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+class Gear {
+public:
+ // pitch circles touch on two properly meshed gears
+ // all measurements are taken from the pitch circle
+ double pitch_diameter() {return (_number_of_teeth * _module) / M_PI;}
+ double pitch_radius() {return pitch_diameter() / 2.0;}
+ void pitch_radius(double R) {_module = (2 * M_PI * R) / _number_of_teeth;}
+
+ // base circle serves as the basis for the involute toothe profile
+ double base_diameter() {return pitch_diameter() * cos(_pressure_angle);}
+ double base_radius() {return base_diameter() / 2.0;}
+
+ // diametrical pitch
+ double diametrical_pitch() {return _number_of_teeth / pitch_diameter();}
+
+ // height of the tooth above the pitch circle
+ double addendum() {return 1.0 / diametrical_pitch();}
+ // depth of the tooth below the pitch circle
+ double dedendum() {return addendum() + _clearance;}
+
+ // root circle specifies the bottom of the fillet between teeth
+ double root_radius() {return pitch_radius() - dedendum();}
+ double root_diameter() {return root_radius() * 2.0;}
+
+ // outer circle is the outside diameter of the gear
+ double outer_radius() {return pitch_radius() + addendum();}
+ double outer_diameter() {return outer_radius() * 2.0;}
+
+ // angle covered by the tooth on the pitch circle
+ double tooth_thickness_angle() {return M_PI / _number_of_teeth;}
+
+ Geom::Point centre() {return _centre;}
+ void centre(Geom::Point c) {_centre = c;}
+
+ double angle() {return _angle;}
+ void angle(double a) {_angle = a;}
+
+ int number_of_teeth() {return _number_of_teeth;}
+
+ Geom::Path path();
+ Gear spawn(int N, double a);
+
+ Gear(int n, double m, double phi) {
+ _number_of_teeth = n;
+ _module = m;
+ _pressure_angle = phi;
+ _clearance = 0.0;
+ _angle = 0.0;
+ _centre = Geom::Point(0.0,0.0);
+ }
+private:
+ int _number_of_teeth;
+ double _pressure_angle;
+ double _module;
+ double _clearance;
+ double _angle;
+ Geom::Point _centre;
+ D2<SBasis> _involute(double start, double stop) {
+ D2<SBasis> B;
+ D2<SBasis> I;
+ Linear bo = Linear(start,stop);
+
+ B[0] = cos(bo,2);
+ B[1] = sin(bo,2);
+
+ I = B - Linear(0,1) * derivative(B);
+ I = I*base_radius() + _centre;
+ return I;
+ }
+ D2<SBasis> _arc(double start, double stop, double R) {
+ D2<SBasis> B;
+ Linear bo = Linear(start,stop);
+
+ B[0] = cos(bo,2);
+ B[1] = sin(bo,2);
+
+ B = B*R + _centre;
+ return B;
+ }
+ // angle of the base circle used to create the involute to a certain radius
+ double involute_swath_angle(double R) {
+ if (R <= base_radius()) return 0.0;
+ return sqrt(R*R - base_radius()*base_radius())/base_radius();
+ }
+
+ // angle of the base circle between the origin of the involute and the intersection on another radius
+ double involute_intersect_angle(double R) {
+ if (R <= base_radius()) return 0.0;
+ return (sqrt(R*R - base_radius()*base_radius())/base_radius()) - acos(base_radius()/R);
+ }
+};
+
+void makeContinuous(D2<SBasis> &a, Point const b) {
+ for(unsigned d=0;d<2;d++)
+ a[d][0][0] = b[d];
+}
+
+Geom::Path Gear::path() {
+ Geom::Path pb;
+
+ // angle covered by a full tooth and fillet
+ double tooth_rotation = 2.0 * tooth_thickness_angle();
+ // angle covered by an involute
+ double involute_advance = involute_intersect_angle(outer_radius()) - involute_intersect_angle(root_radius());
+ // angle covered by the tooth tip
+ double tip_advance = tooth_thickness_angle() - (2 * (involute_intersect_angle(outer_radius()) - involute_intersect_angle(pitch_radius())));
+ // angle covered by the toothe root
+ double root_advance = (tooth_rotation - tip_advance) - (2.0 * involute_advance);
+ // begin drawing the involute at t if the root circle is larger than the base circle
+ double involute_t = involute_swath_angle(root_radius())/involute_swath_angle(outer_radius());
+
+ //rewind angle to start drawing from the leading edge of the tooth
+ double first_tooth_angle = _angle - ((0.5 * tip_advance) + involute_advance);
+
+ Geom::Point prev;
+ for (int i=0; i < _number_of_teeth; i++)
+ {
+ double cursor = first_tooth_angle + (i * tooth_rotation);
+
+ D2<SBasis> leading_I = compose(_involute(cursor, cursor + involute_swath_angle(outer_radius())), Linear(involute_t,1));
+ if(i != 0) makeContinuous(leading_I, prev);
+ pb.append(SBasisCurve(leading_I));
+ cursor += involute_advance;
+ prev = leading_I.at1();
+
+ D2<SBasis> tip = _arc(cursor, cursor+tip_advance, outer_radius());
+ makeContinuous(tip, prev);
+ pb.append(SBasisCurve(tip));
+ cursor += tip_advance;
+ prev = tip.at1();
+
+ cursor += involute_advance;
+ D2<SBasis> trailing_I = compose(_involute(cursor, cursor - involute_swath_angle(outer_radius())), Linear(1,involute_t));
+ makeContinuous(trailing_I, prev);
+ pb.append(SBasisCurve(trailing_I));
+ prev = trailing_I.at1();
+
+ if (base_radius() > root_radius()) {
+ Geom::Point leading_start = trailing_I.at1();
+ Geom::Point leading_end = (root_radius() * unit_vector(leading_start - _centre)) + _centre;
+ prev = leading_end;
+ pb.appendNew<LineSegment>(leading_end);
+ }
+
+ D2<SBasis> root = _arc(cursor, cursor+root_advance, root_radius());
+ makeContinuous(root, prev);
+ pb.append(SBasisCurve(root));
+ cursor += root_advance;
+ prev = root.at1();
+
+ if (base_radius() > root_radius()) {
+ Geom::Point trailing_start = root.at1();
+ Geom::Point trailing_end = (base_radius() * unit_vector(trailing_start - _centre)) + _centre;
+ pb.appendNew<LineSegment>(trailing_end);
+ prev = trailing_end;
+ }
+ }
+
+ return pb;
+}
+Gear Gear::spawn(int N, double a) {
+ Gear gear(N, _module, _pressure_angle);
+ double dist = gear.pitch_radius() + pitch_radius();
+ gear.centre(Geom::Point::polar(a, dist) + _centre);
+ double new_angle = 0.0;
+ if (gear.number_of_teeth() % 2 == 0)
+ new_angle -= gear.tooth_thickness_angle();
+ new_angle -= (_angle) * (pitch_radius() / gear.pitch_radius());
+ new_angle += (a) * (pitch_radius() / gear.pitch_radius());
+ gear.angle(new_angle + a);
+ return gear;
+}
+
+class GearToy: public Toy {
+ public:
+ PointSetHandle hand;
+ GearToy () {
+ for(unsigned i = 0; i < 4; i++)
+ hand.pts.emplace_back(uniform()*400, uniform()*400);
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
+ cairo_set_line_width (cr, 0.5);
+
+ //Geom::Point centre = Geom::Point(width/2,height/2);
+ /* draw cross hairs
+ double dominant_dim = std::max(width,height);
+ double minor_dim = std::min(width,height);
+ for(unsigned i = 1; i < 2; i++) {
+ cairo_move_to(cr, centre[0]-minor_dim/4, centre[1]);
+ cairo_line_to(cr, centre[0]+minor_dim/4, centre[1]);
+ cairo_move_to(cr, centre[0], centre[1]-minor_dim/4);
+ cairo_line_to(cr, centre[0], centre[1]+minor_dim/4);
+ }
+ cairo_stroke(cr);*/
+
+ double pressure_angle = (hand.pts[3][0] / 10) * M_PI / 180;
+ Gear gear(int(hand.pts[2][0] / 10),200.0,pressure_angle);
+ Geom::Point gear_centre = hand.pts[1];
+ gear.pitch_radius(Geom::distance(gear_centre, hand.pts[0]));
+ gear.angle(atan2(hand.pts[0] - gear_centre));
+ gear.centre(gear_centre);
+
+ // draw radii
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, gear_centre[0], gear_centre[1], gear.base_radius(), 0, M_PI*2);
+ cairo_set_source_rgba (cr, 0., 0., 0.5, 1);
+ cairo_stroke(cr);
+
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, gear_centre[0], gear_centre[1], gear.pitch_radius(), 0, M_PI*2);
+ cairo_set_source_rgba (cr, 0.5, 0., 0., 1);
+ cairo_stroke(cr);
+
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, gear_centre[0], gear_centre[1], gear.outer_radius(), 0, M_PI*2);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_stroke(cr);
+
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, gear_centre[0], gear_centre[1], gear.root_radius(), 0, M_PI*2);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_stroke(cr);
+
+ //draw gear
+ Geom::Path p = gear.path();
+ cairo_path(cr, p);
+ cairo_set_source_rgba (cr, 0., 0., 0., 0.5);
+ cairo_set_line_width (cr, 2.0);
+ cairo_stroke(cr);
+
+ Gear gear2 = gear.spawn(5, -2.0 * M_PI / 8.0);
+ Geom::Path p2 = gear2.path();
+ cairo_path(cr, p2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 0.5);
+ cairo_set_line_width (cr, 2.0);
+ cairo_stroke(cr);
+
+ Gear gear3 = gear2.spawn(8, 0.0 * M_PI / 8.0);
+ Geom::Path p3 = gear3.path();
+ cairo_path(cr, p3);
+ cairo_set_source_rgba (cr, 0., 0., 0., 0.5);
+ cairo_set_line_width (cr, 2.0);
+ cairo_stroke(cr);
+
+ Gear gear4 = gear.spawn(6, 3.0 * M_PI / 4.0);
+ Geom::Path p4 = gear4.path();
+ cairo_path(cr, p4);
+ cairo_set_source_rgba (cr, 0., 0., 0., 0.5);
+ cairo_set_line_width (cr, 2.0);
+ cairo_stroke(cr);
+
+ *notify << "angle = " << gear.angle();
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new GearToy());
+
+ 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/src/toys/hatches.cpp b/src/toys/hatches.cpp
new file mode 100644
index 0000000..28fe440
--- /dev/null
+++ b/src/toys/hatches.cpp
@@ -0,0 +1,386 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 8
+
+//------------------------------------------------
+// Some goodies to navigate through curve's levels.
+//------------------------------------------------
+struct LevelCrossing{
+ Point pt;
+ double t;
+ bool sign;
+ bool used;
+};
+struct LevelCrossingOrder {
+ bool operator()(LevelCrossing a, LevelCrossing b) {
+ return a.pt[Y] < b.pt[Y];
+ }
+};
+
+typedef std::vector<LevelCrossing> LevelCrossings;
+
+class LevelsCrossings: public std::vector<LevelCrossings>{
+public:
+ LevelsCrossings():std::vector<LevelCrossings>(){};
+ LevelsCrossings(std::vector<std::vector<double> > const &times,
+ Piecewise<D2<SBasis> > const &f,
+ Piecewise<SBasis> const &dx){
+ for (const auto & time : times){
+ LevelCrossings lcs;
+ for (double j : time){
+ LevelCrossing lc;
+ lc.pt = f.valueAt(j);
+ lc.t = j;
+ lc.sign = ( dx.valueAt(j)>0 );
+ lc.used = false;
+ lcs.push_back(lc);
+ }
+ std::sort(lcs.begin(), lcs.end(), LevelCrossingOrder());
+ //TODO: reverse all "in" flag if we had the wrong orientation!
+ push_back(lcs);
+ }
+ }
+ void flipInOut(){
+ for (unsigned i=0; i<size(); i++){
+ for (auto & j : (*this)){
+ j.sign = !j.sign;
+ }
+ }
+ }
+ void findFirstUnused(unsigned &level, unsigned &idx){
+ level = size();
+ idx = 0;
+ for (unsigned i=0; i<size(); i++){
+ for (unsigned j=0; j<(*this)[i].size(); j++){
+ if (!(*this)[i][j].used){
+ level = i;
+ idx = j;
+ return;
+ }
+ }
+ }
+ }
+ //set indexes to point to the next point in the "snake walk"
+ //follow_level's meaning:
+ // 0=yes upward
+ // 1=no, last move was upward,
+ // 2=yes downward
+ // 3=no, last move was downward.
+ void step(unsigned &level, unsigned &idx, int &direction){
+ std::cout << "Entering step: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+
+ if ( direction % 2 == 0 ){
+ if (direction == 0) {
+ if ( idx >= (*this)[level].size()-1 || (*this)[level][idx+1].used ) {
+ level = size();
+ std::cout << "max end of level reached...\n";
+ return;
+ }
+ idx += 1;
+ }else{
+ if ( idx <= 0 || (*this)[level][idx-1].used ) {
+ level = size();
+ std::cout << "min end of level reached...\n";
+ return;
+ }
+ idx -= 1;
+ }
+ direction += 1;
+ std::cout << "exit with: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+ return;
+ }
+ double t = (*this)[level][idx].t;
+ double sign = ((*this)[level][idx].sign ? 1 : -1);
+ double next_t = t;
+ level += 1;
+ direction = (direction + 1)%4;
+ if (level == size()){
+ std::cout << "max level reached\n";
+ return;
+ }
+ for (unsigned j=0; j<(*this)[level].size(); j++){
+ double tj = (*this)[level][j].t;
+ if ( sign*(tj-t) > 0 ){
+ if( next_t == t || sign*(tj-next_t)<0 ){
+ next_t = tj;
+ idx = j;
+ }
+ }
+ }
+ if ( next_t == t ){//not found.
+ level = size();
+ std::cout << "no next time found\n";
+ return;
+ }
+ //TODO: time is periodic!!!
+ //TODO: allow several components.
+ if ( (*this)[level][idx].used ) {
+ level = size();
+ std::cout << " reached a point already used\n";
+ return;
+ }
+ std::cout << "exit with: "<<level<<","<<idx<<"\n";
+ return;
+ }
+};
+
+
+//------------------------------------------------
+// Generate the levels with random, growth...
+//------------------------------------------------
+std::vector<double>generateLevels(Interval const &domain,
+ double const width,
+ double const growth,
+ double randomness){
+ std::vector<double> result;
+ std::srand(0);
+ double x = domain.min() + width/2;
+ double step = width;
+ while (x<domain.max()){
+ result.push_back(x);
+ double rdm = 1+ ( (rand() % 100) - 50) /100.*randomness;
+ x+= step*growth*rdm;
+ step*=growth;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------------
+// Walk through the intersections to create linear hatches
+//-------------------------------------------------------
+std::vector<Point> linearSnake(Piecewise<D2<SBasis> > const &f, double dy,double growth, double rdmness){
+
+ std::vector<Point> result;
+
+ Piecewise<SBasis> x = make_cuts_independent(f)[X];
+ //Rque: derivative is computed twice in the 2 lines below!!
+ Piecewise<SBasis> dx = derivative(x);
+ OptInterval range = bounds_exact(x);
+ //TODO: test range non emptyness!!
+ std::vector<double> levels = generateLevels((*range), dy, growth, rdmness);
+ std::vector<std::vector<double> > times;
+ times = multi_roots(x,levels);
+
+//TODO: fix multi_roots!!!*****************************************
+//remove doubles :-(
+ std::vector<std::vector<double> > cleaned_times(levels.size(),std::vector<double>());
+ for (unsigned i=0; i<times.size(); i++){
+ if ( times[i].size()>0 ){
+ double last_t = times[i][0]-1;//ugly hack!!
+ for (unsigned j=0; j<times[i].size(); j++){
+ if (times[i][j]-last_t >0.000001){
+ last_t = times[i][j];
+ cleaned_times[i].push_back(last_t);
+ }
+ }
+ }
+ }
+ times = cleaned_times;
+ for (unsigned i=0; i<times.size(); i++){
+ std::cout << "roots on level "<<i<<": ";
+ for (double j : times){
+ std::cout << j <<" ";
+ }
+ std::cout <<"\n";
+ }
+//*******************************************************************
+ LevelsCrossings lscs(times,f,dx);
+ unsigned i,j;
+ lscs.findFirstUnused(i,j);
+ while ( i < lscs.size() ){
+ int dir = 0;
+ while ( i < lscs.size() ){
+ result.push_back(lscs[i][j].pt);
+ lscs[i][j].used = true;
+ lscs.step(i,j, dir);
+ }
+ //TODO: handle "non convex cases" where hatches have to be restarted at some point.
+ //This needs some care in linearSnake->smoothSnake.
+ //
+ lscs.findFirstUnused(i,j);
+ }
+ return result;
+}
+
+//-------------------------------------------------------
+// Smooth the linear hatches according to params...
+//-------------------------------------------------------
+Piecewise<D2<SBasis> > smoothSnake(std::vector<Point> const &linearSnake,
+ double scale_bf = 1, double scale_bb = 1,
+ double scale_tf = 1, double scale_tb = 1){
+
+ if (linearSnake.size()<2) return Piecewise<D2<SBasis> >();
+ bool is_top = true;
+ Point last_pt = linearSnake[0];
+ Point last_hdle = linearSnake[0];
+ Path result(last_pt);
+ unsigned i=1;
+ while( i+1<linearSnake.size() ){
+ Point pt0 = linearSnake[i];
+ Point pt1 = linearSnake[i+1];
+ Point new_pt = (pt0+pt1)/2;
+ double scale = (is_top ? scale_tf : scale_bf );
+ Point new_hdle = new_pt+(pt0-new_pt)*scale;
+
+ result.appendNew<CubicBezier>(last_hdle,new_hdle,new_pt);
+
+ last_pt = new_pt;
+ scale = (is_top ? scale_tb : scale_bb );
+ last_hdle = new_pt+(pt1-new_pt)*scale;
+ i+=2;
+ is_top = !is_top;
+ }
+ if ( i<linearSnake.size() )
+ result.appendNew<CubicBezier>(last_hdle,linearSnake[i],linearSnake[i]);
+ return result.toPwSb();
+}
+
+//-------------------------------------------------------
+// Bend a path...
+//-------------------------------------------------------
+
+Piecewise<D2<SBasis> > bend(Piecewise<D2<SBasis> > const &f, Piecewise<SBasis> bending){
+ D2<Piecewise<SBasis> > ff = make_cuts_independent(f);
+ ff[X] += compose(bending, ff[Y]);
+ return sectionize(ff);
+}
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class HatchesToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double hatch_width = (400-adjuster[0].pos[Y])/300.*50;
+ double scale_topfront = (250-adjuster[1].pos[Y])/150.*5;
+ double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+ double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+ double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+ double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+ double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+ double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_restore(cr);
+ }
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> bending = Piecewise<SBasis>(shift(Linear(bend_amount),1));
+ //TODO: test optrect non empty!!
+ bending.setDomain((*bounds_exact(B))[Y]);
+ Piecewise<D2<SBasis> >bentB = bend(B, bending);
+
+ std::vector<Point> snakePoints;
+ snakePoints = linearSnake(bentB, hatch_width, growth, rdmness);
+ Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints,
+ scale_topfront,
+ scale_topback,
+ scale_botfront,
+ scale_botback);
+
+ smthSnake = bend(smthSnake, -bending);
+ cairo_pw_d2_sb(cr, smthSnake);
+ cairo_set_line_width (cr, 1.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ if ( snakePoints.size() > 0 ){
+ Path snake(snakePoints.front());
+ for (unsigned i=1; i<snakePoints.size(); i++){
+ snake.appendNew<LineSegment>(snakePoints[i]);
+ }
+ //cairo_pw_d2_sb(cr, snake.toPwSb() );
+ }
+
+ //cairo_pw_d2_sb(cr, B);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ HatchesToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new HatchesToy);
+ 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/src/toys/implicit-toy.cpp b/src/toys/implicit-toy.cpp
new file mode 100644
index 0000000..c90c082
--- /dev/null
+++ b/src/toys/implicit-toy.cpp
@@ -0,0 +1,510 @@
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/geom.h>
+#include <2geom/d2.h>
+#include <2geom/polynomial.h>
+#include <2geom/sbasis-poly.h>
+#include <2geom/transforms.h>
+
+#include <2geom/symbolic/implicit.h>
+
+#include <aa.h>
+
+#include <algorithm>
+#include <ctime>
+#include <functional>
+
+
+using namespace Geom;
+
+
+
+struct PtLexCmp{
+ bool operator()(const Point &a, const Point &b) {
+ return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1]));
+ }
+};
+
+//typedef AAF (*implicit_curve_t)(AAF, AAF);
+typedef std::function<AAF (AAF const&, AAF const&)> implicit_curve_t;
+
+// draw ax + by + c = 0
+void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c)
+{
+ vector<Geom::Point> result;
+ Point resultp;
+ if(intersects == line_intersection(Point(1, 0), r.left(),
+ n, c,
+ resultp) && r[1].contains(resultp[1]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(1, 0), r.right(),
+ n, c,
+ resultp) && r[1].contains(resultp[1]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(0, 1), r.top(),
+ n, c,
+ resultp) && r[0].contains(resultp[0]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(0, 1), r.bottom(),
+ n, c,
+ resultp) && r[0].contains(resultp[0]))
+ result.push_back(resultp);
+ if(result.size() > 2) {
+ std::sort(result.begin(), result.end(), PtLexCmp());
+ vector<Geom::Point>::iterator new_end = std::unique(result.begin(), result.end());
+ result.resize(new_end-result.begin());
+ }
+ if(result.size() == 2)
+ {
+ cairo_move_to(cr, result[0]);
+ cairo_line_to(cr, result[1]);
+ cairo_stroke(cr);
+ }
+}
+
+OptRect tighten(Rect const&r, Point n, Interval lu)
+{
+ vector<Geom::Point> result;
+ Point resultp;
+ for(int i = 0; i < 4; i++)
+ {
+ Point cnr = r.corner(i);
+ double z = dot(cnr, n);
+ if ((z > lu[0]) && (z < lu[1]))
+ result.push_back(cnr);
+ }
+ for(int i = 0; i < 2; i++)
+ {
+ double c = lu[i];
+ if(intersects == line_intersection(Point(1, 0), r.left(),
+ n, c,
+ resultp) && r[1].contains(resultp[1]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(1, 0), r.right(),
+ n, c,
+ resultp) && r[1].contains(resultp[1]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(0, 1), r.top(),
+ n, c,
+ resultp) && r[0].contains(resultp[0]))
+ result.push_back(resultp);
+ if(intersects == line_intersection(Point(0, 1), r.bottom(),
+ n, c,
+ resultp) && r[0].contains(resultp[0]))
+ result.push_back(resultp);
+ }
+ if(result.size() < 2)
+ return OptRect;
+ Rect nr(result[0], result[1]);
+ for(size_t i = 2; i < result.size(); i++)
+ {
+ nr.expandTo(result[i]);
+ }
+ return intersect(nr, r);
+}
+
+static const unsigned int DEG = 5;
+double bvp[DEG+1][DEG+1]
+ = {{-1, 0.00945115, -4.11799e-05, 1.01365e-07, -1.35037e-10, 7.7868e-14},
+ {0.00837569, -6.24676e-05, 1.96093e-07, -3.09683e-10, 1.95681e-13, 0},
+ {-2.39448e-05, 1.3331e-07, -2.65787e-10, 1.96698e-13, 0, 0},
+ {2.76173e-08, -1.01069e-10, 9.88596e-14, 0, 0, 0},
+ {-1.43584e-11, 2.48433e-14, 0, 0, 0, 0}, {2.49723e-15, 0, 0, 0, 0, 0}};
+
+
+AAF trial_eval(AAF const& x, AAF const& y) {
+// AAF x = _x/100;
+// AAF y = _y/100;
+ //return x*x - 1;
+ //return y - pow(x,3);
+ //return y - pow_sample_based(x,2.5);
+ //return y - log_sample_based(x);
+ //return y - log(x);
+ //return y - exp_sample_based(x*log(x));
+ //return y - sqrt(sin(x));
+ //return sqrt(y)*x - sqrt(x) - y - 1;
+ //return y-1/x;
+ //return exp(x)-y;
+ //return sin(x)-y;
+ //return exp_sample_based(x)-y;
+ //return atan(x)-y;
+ //return atan_sample_based(x)-y;
+ //return atanh(x)-y;
+ //return x*y;
+ //return 4*x+3*y-1;
+ //return x*x + y*y - 1;
+ //return sin(x*y) + cos(pow(x, 3)) - atan(x);
+ //return pow((x*x + y*y), 2) - (x*x-y*y);
+ //return x*x-y;
+ //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5;
+// return -120.75 +(-64.4688 +(-16.6875 +(0.53125 -0.00390625*y)*y)*y)*y
+// + (-15.9375 + ( 1.5 +( 4.375 -0.0625*y)*y)*y
+// + (17 +( 9.5 -0.375*y)*y + (2 + -1*y -1*x)*x)*x)*x;
+
+// AAF v(0);
+// for (size_t i = DEG; i > 0; --i)
+// {
+// AAF vy(0);
+// for (size_t j = DEG - i; j > 0; --j)
+// {
+// vy += bvp[i][j];
+// vy *= y;
+// }
+// vy += bvp[i][0];
+// v += vy;
+// v *= x;
+// }
+// AAF vy(0);
+// for (size_t j = DEG; j > 0; --j)
+// {
+// vy += bvp[0][j];
+// vy *= y;
+// }
+// vy += bvp[0][0];
+// v += vy;
+// return v;
+
+ int i = DEG;
+ int j = DEG - i;
+ AAF vy(bvp[i][j]);
+ --j;
+ for (; j >= 0; --j)
+ {
+ vy *= y;
+ vy += bvp[DEG][j];
+ }
+ AAF v(vy);
+ --i;
+ for (; i >= 0; --i)
+ {
+ int j = DEG - i;
+ AAF vy(bvp[i][j]);
+ --j;
+ for (; j >= 0; --j)
+ {
+ vy *= y;
+ vy += bvp[i][j];
+ }
+ v *= x;
+ v += vy;
+ }
+ return v;
+
+// return
+// -1 +( 0.00945115 +( -4.11799e-05 +( 1.01365e-07 +( -1.35037e-10 + 7.7868e-14*y)*y)*y)*y)*y
+// + (0.00837569 +( -6.24676e-05 +( 1.96093e-07 +( -3.09683e-10 + 1.95681e-13*y)*y)*y)*y
+// + (-2.39448e-05 +( 1.3331e-07 +( -2.65787e-10 + 1.96698e-13*y)*y)*y
+// + (2.76173e-08 +( -1.01069e-10 + 9.88596e-14*y)*y
+// + (-1.43584e-11 + 2.48433e-14*y + 2.49723e-15*x)*x)*x)*x)*x;
+}
+
+
+
+double max_modulus (SL::MVPoly2 const& p)
+{
+ double a, m = 1;
+
+ for (size_t i = 0; i < p.get_poly().size(); ++i)
+ for (double j : p)
+ {
+ a = std::abs(j);
+ if (m < a) m = a;
+ }
+ return m;
+}
+
+void poly_to_mvpoly1(SL::MVPoly1& p, Geom::Poly const& q)
+{
+ for (size_t i = 0; i < q.size(); ++i)
+ {
+ p.coefficient(i, q[i]);
+ }
+ p.normalize();
+}
+
+void make_implicit_curve (SL::MVPoly2& ic, D2<SBasis> const& pc)
+{
+ Geom::Poly pc0 = sbasis_to_poly(pc[0]);
+ Geom::Poly pc1 = sbasis_to_poly(pc[1]);
+
+// std::cerr << "parametrization: \n";
+// std::cerr << "pc0 = " << pc0 << std::endl;
+// std::cerr << "pc1 = " << pc1 << "\n\n";
+
+ SL::MVPoly1 f, g;
+ poly_to_mvpoly1(f, pc0);
+ poly_to_mvpoly1(g, pc1);
+
+// std::cerr << "parametrization: \n";
+// std::cerr << "f = " << f << std::endl;
+// std::cerr << "g = " << g << "\n\n";
+
+ Geom::SL::basis_type b;
+ microbasis(b, f, g);
+
+ Geom::SL::MVPoly3 p, q;
+ basis_to_poly(p, b[0]);
+ basis_to_poly(q, b[1]);
+
+// std::cerr << "generators as polynomial in R[t,x,y] : \n";
+// std::cerr << "p = " << p << std::endl;
+// std::cerr << "q = " << q << "\n\n";
+
+
+ Geom::SL::Matrix<Geom::SL::MVPoly2> B = make_bezout_matrix(p, q);
+ ic = determinant_minor(B);
+ ic.normalize();
+ double m = max_modulus(ic);
+ ic /= m;
+
+// std::cerr << "Bezout matrix: (entries are bivariate polynomials) \n";
+// std::cerr << "B = " << B << "\n\n";
+// std::cerr << "determinant: \n";
+// std::cerr << "r(x, y) = " << ic << "\n\n";
+
+}
+
+//namespace Geom{ namespace SL{
+//
+//template<>
+//struct zero<AAF, false>
+//{
+// AAF operator() () const
+// {
+// return AAF(0);
+// }
+//};
+//
+//} }
+
+class ImplicitToy : public Toy
+{
+ bool contains_zero (implicit_curve_t const& eval,
+ Rect r, double w=1e-5)
+ {
+ ++iters;
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ AAF f = eval(x, y);
+ double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if (f.straddles_zero())
+ {
+ if ((r.width() > w) || (r.height() > w))
+ {
+ Point c = r.midpoint();
+ Rect oldr = r;
+ if (out) r = *out;
+ // Three possibilities:
+ // 1) the trim operation buys us enough that we should just iterate
+ if (1 && (r.area() < oldr.area()*0.25))
+ {
+ return contains_zero(eval, r, w);
+ }
+ // 2) one dimension is significantly smaller
+ else if (1 && (r[1].extent() < oldr[1].extent()*0.5))
+ {
+ return contains_zero (eval,
+ Rect(Interval(r.left(), r.right()),
+ Interval(r.top(), c[1])), w)
+ || contains_zero (eval,
+ Rect(Interval(r.left(), r.right()),
+ Interval(c[1], r.bottom())), w);
+ }
+ else if (1 && (r[0].extent() < oldr[0].extent()*0.5))
+ {
+ return contains_zero (eval,
+ Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), r.bottom())), w)
+ || contains_zero (eval,
+ Rect(Interval(c[0], r.right()),
+ Interval(r.top(), r.bottom())), w);
+ }
+ // 3) to ensure progress we must do a four way split
+ else
+ {
+ return contains_zero (eval,
+ Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), c[1])), w)
+ || contains_zero (eval,
+ Rect(Interval(c[0], r.right()),
+ Interval(r.top(), c[1])), w)
+ || contains_zero (eval,
+ Rect(Interval(r.left(), c[0]),
+ Interval(c[1], r.bottom())), w)
+ || contains_zero (eval,
+ Rect(Interval(c[0], r.right()),
+ Interval(c[1], r.bottom())), w);
+ }
+ }
+ //std::cout << w << " < " << r.width() << " , " << r.height() << std::endl;
+ //std::cout << r.min() << " - " << r.max() << std::endl;
+ return true;
+ }
+ return false;
+ } // end recursive_implicit
+
+
+ void draw_implicit_curve (cairo_t*cr, implicit_curve_t const& eval,
+ Point const& origin, Rect r, double w)
+ {
+ ++iters;
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ //assert(x.rad() > 0);
+ //assert(y.rad() > 0);
+// time(&t0);
+ AAF f = eval(x-origin[X], y-origin[Y]);
+// time(&t1);
+// d1 += std::difftime(t1, t0);
+ // pivot
+// time(&t2);
+ double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if (ivl.extent() < 0.5*L2(n))
+ {
+ draw_line_in_rect(cr, r, n, ivl.middle());
+ return;
+ }
+// time(&t3);
+// d2 += std::difftime(t3, t2);
+ if ((r.width() > w) || (r.height() > w))
+ {
+ if (f.straddles_zero())
+ {
+ Point c = r.midpoint();
+ Rect oldr = r;
+ if (out) r = *out;
+ // Three possibilities:
+ // 1) the trim operation buys us enough that we should just iterate
+ if (1 && (r.area() < oldr.area()*0.25))
+ {
+ draw_implicit_curve(cr, eval, origin, r, w);
+ }
+ // 2) one dimension is significantly smaller
+ else if (1 && (r[1].extent() < oldr[1].extent()*0.5))
+ {
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(r.left(), r.right()),
+ Interval(r.top(), c[1])), w);
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(r.left(), r.right()),
+ Interval(c[1], r.bottom())), w);
+ }
+ else if (1 && (r[0].extent() < oldr[0].extent()*0.5))
+ {
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), r.bottom())), w);
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(c[0], r.right()),
+ Interval(r.top(), r.bottom())), w);
+ }
+ // 3) to ensure progress we must do a four way split
+ else
+ {
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), c[1])), w);
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(c[0], r.right()),
+ Interval(r.top(), c[1])), w);
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(r.left(), c[0]),
+ Interval(c[1], r.bottom())), w);
+ draw_implicit_curve (cr, eval, origin,
+ Rect(Interval(c[0], r.right()),
+ Interval(c[1], r.bottom())), w);
+ }
+ }
+ } else {
+ if(contains_zero(eval, r*Geom::Translate(-origin))) {
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 0,0.5,0);
+ cairo_rectangle(cr, r);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ }
+ }
+ } // end recursive_implicit
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ iters = 0;
+ d1 = d2 = 0;
+ cairo_set_line_width (cr, 0.3);
+ D2<SBasis> A = pshA.asBezier();
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+
+ SL::MVPoly2 ic;
+ make_implicit_curve(ic, A);
+
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 0.8);
+ draw_implicit_curve (cr, ic, orig_handle.pos,
+ Rect(Interval(0,width), Interval(0, height)), 1);
+ cairo_stroke(cr);
+
+// std::cerr << "D1 = " << d1 << std::endl;
+// std::cerr << "D2 = " << d2 << std::endl;
+
+ *notify << "iter: " << iters;
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+public:
+ ImplicitToy(unsigned int _A_bez_ord)
+ : A_bez_ord(_A_bez_ord)
+ {
+ handles.push_back(&orig_handle);
+ orig_handle.pos = Point(0,0); //Point(300,300);
+
+ handles.push_back(&pshA);
+ for (unsigned int i = 0; i < A_bez_ord; ++i)
+ pshA.push_back(Geom::Point(uniform()*400, uniform()*400));
+ }
+
+private:
+ unsigned int A_bez_ord;
+ PointHandle orig_handle;
+ PointSetHandle pshA;
+ time_t t0, t1, t2, t3;
+ double d1, d2;
+ unsigned int iters;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord=5;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+
+
+ init( argc, argv, new ImplicitToy(A_bez_ord));
+ 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/src/toys/ineaa.cpp b/src/toys/ineaa.cpp
new file mode 100644
index 0000000..75b9d41
--- /dev/null
+++ b/src/toys/ineaa.cpp
@@ -0,0 +1,655 @@
+#include <2geom/convex-hull.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/d2.h>
+#include <2geom/geom.h>
+#include <2geom/numeric/linear_system.h>
+
+#include <aa.h>
+#include <complex>
+#include <algorithm>
+#include <optional>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+typedef std::complex<AAF> CAAF;
+
+struct PtLexCmp{
+ bool operator()(const Point &a, const Point &b) {
+ return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1]));
+ }
+};
+
+// draw ax + by + c = 0
+void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) {
+ std::optional<Geom::LineSegment> ls =
+ rect_line_intersect(r, Line::fromNormalDistance(n, c));
+
+ if(ls) {
+ cairo_move_to(cr, (*ls)[0]);
+ cairo_line_to(cr, (*ls)[1]);
+ cairo_stroke(cr);
+
+ }
+}
+
+void fill_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) {
+ ConvexHull ch;
+
+ std::optional<Geom::LineSegment> ls =
+ rect_line_intersect(r, Line::fromNormalDistance(n, c));
+
+ if(ls) {
+ ch.boundary.push_back((*ls)[0]);
+ ch.boundary.push_back((*ls)[1]);
+ }
+ for(int i = 0; i < 4; i++) {
+ Point p = r.corner(i);
+ if(dot(n,p) < c) {
+ ch.boundary.push_back(p);
+ }
+ }
+ ch.graham();
+ cairo_convex_hull(cr, ch.boundary);
+}
+
+OptRect tighten(Rect &r, Point n, Interval lu) {
+ vector<Geom::Point> result;
+ Point resultp;
+ for(int i = 0; i < 4; i++) {
+ Point cnr = r.corner(i);
+ double z = dot(cnr, n);
+ if((z > lu[0]) and (z < lu[1]))
+ result.push_back(cnr);
+ }
+ for(int i = 0; i < 2; i++) {
+ double c = lu[i];
+
+ std::optional<Geom::LineSegment> ls =
+ rect_line_intersect(r, Line::fromNormalDistance(n, c));
+
+ if(ls) {
+ result.push_back((*ls)[0]);
+ result.push_back((*ls)[1]);
+ }
+ }
+ if(result.size() < 2)
+ return OptRect();
+ Rect nr(result[0], result[1]);
+ for(size_t i = 2; i < result.size(); i++) {
+ nr.expandTo(result[i]);
+ }
+ return intersect(nr, r);
+}
+
+AAF ls_sample_based(AAF x, vector<Point> pts) {
+ NL::Matrix m(pts.size(), 2);
+ NL::Vector v(pts.size());
+ NL::LinearSystem ls(m, v);
+
+ m.set_all(0);
+ v.set_all(0);
+ for (unsigned int k = 0; k < pts.size(); ++k)
+ {
+ m(k,0) += pts[k][0];
+ m(k,1) += 1;
+ //std::cout << pts[k] << " ";
+
+ v[k] += pts[k][1];
+ //v[1] += pts[k][1];
+ //v[2] += y2;
+ }
+
+ ls.SV_solve();
+
+ double A = ls.solution()[0];
+ double B = ls.solution()[1];
+ // Ax + B = y
+ Interval bnd(0,0);
+ for (unsigned int k = 0; k < pts.size(); ++k)
+ {
+ bnd.extendTo(A*pts[k][0]+B - pts[k][1]);
+ }
+ //std::cout << A << "," << B << std::endl;
+ return AAF(x, A, B, bnd.extent(),
+ x.special);
+}
+
+AAF md_sample_based(AAF x, vector<Point> pts) {
+ Geom::ConvexHull ch1(pts);
+ Point a, b, c;
+ ch1.narrowest_diameter(a, b, c);
+ Point db = c-b;
+ double A = db[1]/db[0];
+ Point aa = db*(dot(db, a-b)/dot(db,db))+b;
+ Point mid = (a+aa)/2;
+ double B = mid[1] - A*mid[0];
+ double dB = (a[1] - A*a[0]) - B;
+ // Ax + B = y
+ //std::cout << A << "," << B << std::endl;
+ return AAF(x, A, B, dB,
+ x.special);
+}
+
+AAF atan_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+
+ const double ea = atan(a);
+ const double eb = atan(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ double xs = sqrt(1/alpha-1);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,atan(xs)));
+ xs = -xs;
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,atan(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+AAF log_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+ AAF_TYPE type;
+ if(a > 0)
+ type = AAF_TYPE_AFFINE;
+ else if(b < 0) { // no point in continuing
+ type = AAF_TYPE_NAN;
+ return AAF(type);
+ }
+ else if(a <= 0) { // undefined, can we do better?
+ type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN);
+ return AAF(type);
+ // perhaps we should make a = 0+eps and try to continue?
+ }
+
+ const double ea = log(a);
+ const double eb = log(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // dlog(xs) = alpha
+ double xs = 1/(alpha);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,log(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+AAF exp_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+
+ const double ea = exp(a);
+ const double eb = exp(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // dexp(xs) = alpha
+ double xs = log(alpha);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,exp(xs)));
+
+ return md_sample_based(x, pts);
+}
+
+double
+desy_lambert_W(double x) {
+ if (x <= 500.0) {
+ double lx1 = log(x + 1.0);
+ return 0.665 * (1 + 0.0195 * lx1) * lx1 + 0.04;
+ }
+ return log(x - 4.0) - (1.0 - 1.0/log(x)) * log(log(x));
+}
+
+double lambertW(double x, double prec = 1E-12, int maxiters = 100) {
+ double w = desy_lambert_W(x);
+ const double e = exp(1);
+ for(int i = 0; i < maxiters; i++) {
+ double we = w * pow(e,w);
+ double w1e = (w + 1) * pow(e,w);
+ if( prec > abs((x - we) / w1e))
+ return w;
+ w -= (we - x) / (w1e - (w+2) * (we-x) / (2*w+2));
+ }
+ //raise ValueError("W doesn't converge fast enough for abs(z) = %f" % abs(x))
+ return 0./0.;
+}
+
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_min.h>
+
+typedef struct{
+ double a, b;
+} param_W;
+
+double fn1 (double x, void * params)
+{
+ param_W *pw = (param_W*)params;
+ return (pw->a*x+pw->b) - lambertW(x);
+}
+
+double optimise(void * params, double a, double b) {
+ int status;
+ //param_W *pw = (param_W*)params;
+ int iter = 0, max_iter = 100;
+ double m = (a+b)/2;
+ gsl_function F;
+
+ F.function = &fn1;
+ F.params = params;
+
+ const gsl_min_fminimizer_type *T = gsl_min_fminimizer_brent;
+ gsl_min_fminimizer *s = gsl_min_fminimizer_alloc (T);
+ if(a+1e-10 >= b) return m;
+#if 0
+ cout << a << " " << b << " " << m << endl;
+ cout << "fn:" << fn1(a, params) << " " << fn1(b, params) << " " << fn1(m, params) << endl;
+ cout << "fn:" << (pw->a*a+pw->b) << " " << (pw->a*b+pw->b) << " " << (pw->a*m+pw->b) << endl;
+#endif
+ gsl_min_fminimizer_set (s, &F, m, a, b);
+ do
+ {
+ iter++;
+ status = gsl_min_fminimizer_iterate (s);
+
+ m = gsl_min_fminimizer_x_minimum (s);
+ a = gsl_min_fminimizer_x_lower (s);
+ b = gsl_min_fminimizer_x_upper (s);
+
+ status
+ = gsl_min_test_interval (a, b, 0.001, 0.0);
+
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+
+ gsl_min_fminimizer_free (s);
+
+ return m;
+}
+
+
+AAF W_sample_based(AAF x) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+ const double e = exp(1);
+ AAF_TYPE type;
+ if(a >= -1./e)
+ type = AAF_TYPE_AFFINE;
+ else if(b < 0) { // no point in continuing
+ type = AAF_TYPE_NAN;
+ return AAF(type);
+ }
+ else if(a <= 0) { // undefined, can we do better?
+ type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN);
+ return AAF(type);
+ // perhaps we should make a = 0+eps and try to continue?
+ }
+ const double ea = lambertW(a);
+ const double eb = lambertW(b);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // d(W(xs)) = alpha
+ // W =
+ param_W pw;
+ pw.a = alpha;
+ pw.b = ea - alpha*a;
+ if(a < b) {
+ double xs = optimise(&pw, a, b);
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,lambertW(xs)));
+ }
+ return md_sample_based(x, pts);
+}
+
+AAF pow_sample_based(AAF x, double p) {
+ interval ab(x);
+ const double a = ab.min(); // [a,b] is our interval
+ const double b = ab.max();
+ AAF_TYPE type;
+ if(floor(p) != p) {
+ if(a >= 0)
+ type = AAF_TYPE_AFFINE;
+ else if(b < 0) { // no point in continuing
+ type = AAF_TYPE_NAN;
+ return AAF(type);
+ }
+ else if(a <= 0) { // undefined, can we do better?
+ type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN);
+ return AAF(type);
+ // perhaps we should make a = 0+eps and try to continue?
+ }
+ }
+ const double ea = pow(a, p);
+ const double eb = pow(b, p);
+ vector<Point> pts;
+ pts.push_back(Point(a,ea));
+ pts.push_back(Point(b,eb));
+ const double alpha = (eb-ea)/(b-a);
+ // d(xs^p) = alpha
+ // p xs^(p-1) = alpha
+ // xs = (alpha/p)^(1-p)
+ double xs = pow(alpha/p, 1./(p-1));
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,pow(xs, p)));
+ xs = -xs;
+ if((a < xs) and (xs < b))
+ pts.push_back(Point(xs,pow(xs, p)));
+
+ return md_sample_based(x, pts);
+}
+
+Point origin;
+AAF trial_eval(AAF x, AAF y) {
+ x = x-origin[0];
+ y = y-origin[1];
+ x = x/200;
+ y = y/200;
+ AAF x2 = pow_sample_based(x,2);
+ AAF y2 = pow_sample_based(y,2);
+ //return x2 + y2 - 1;
+ //return y - pow(x,3);
+ return y + W_sample_based(x);
+ //return y - pow_sample_based(x,2.5);
+ //return y - log_sample_based(x);
+ //return y - log(x);
+ //return -y + -exp_sample_based(x*log(x));
+ return -x + -exp_sample_based(y*log(y));
+ //return y - sqrt(sin(x));
+ //return sqrt(y)*x - sqrt(x) - y - 1;
+ //return y-1/x;
+ //return exp(x)-y;
+ //return sin(x)-y;
+ //return exp_sample_based(x)-y;
+ //return atan(x)-y;
+ //return atan_sample_based(x)-y;
+ //return atanh(x)-y;
+ //return x*y;
+ //return 4*x+3*y-1;
+ //return x*x + y*y - 1;
+ return pow_sample_based((x2 + y2), 2) - (x2-y2);
+ //return x*x-y;
+ //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5;
+}
+
+AAF xaxis(AAF x, AAF y) {
+ (void)x;
+ y = y-origin[1];
+ y = y/200;
+ return y;
+}
+
+AAF yaxis(AAF x, AAF y) {
+ (void)y;
+ x = x-origin[0];
+ x = x/200;
+ return x;
+}
+
+class ConvexTest: public Toy {
+public:
+ PointSetHandle test_window;
+ PointSetHandle samples;
+ PointHandle orig_handle;
+ ConvexTest () {
+ toggles.push_back(Toggle("Show trials", false));
+ handles.push_back(&test_window);
+ handles.push_back(&samples);
+ handles.push_back(&orig_handle);
+ orig_handle.pos = Point(00,300);
+ test_window.push_back(Point(100,100));
+ test_window.push_back(Point(200,200));
+ for(int i = 0; i < 0; i++) {
+ samples.push_back(Point(i*100, i*100+25));
+ }
+ }
+ int iters;
+ int splits[4];
+ bool show_splits;
+ std::vector<Toggle> toggles;
+ AAF (*eval)(AAF, AAF);
+ void recursive_implicit(Rect r, cairo_t*cr, double w) {
+ if(show_splits) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ /*if(f.is_partial())
+ cairo_set_source_rgba(cr, 1, 0, 1, 0.25);
+ else*/
+ cairo_set_source_rgba(cr, 0, 1, 0, 0.25);
+ cairo_rectangle(cr, r);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ iters++;
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ //assert(x.rad() > 0);
+ //assert(y.rad() > 0);
+ AAF f = (*eval)(x, y);
+ double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if(ivl.extent() < 0.5*L2(n)) {
+
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0,1,0,0.125);
+ fill_line_in_rect(cr, r, n, ivl.middle());
+ cairo_fill(cr);
+ cairo_restore(cr);
+ draw_line_in_rect(cr, r, n, ivl.middle());
+ return;
+ }
+ if(f.strictly_neg()) {
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0,1,0,0.125);
+ cairo_rectangle(cr, r);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ return;
+ }
+ if(!f.is_partial() and f.is_indeterminate()) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ if(f.is_infinite()) {
+ cairo_set_source_rgb(cr, 1, 0.5, 0.5);
+ } else if(f.is_nan()) {
+ cairo_set_source_rgb(cr, 1, 1, 0);
+ } else {
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ }
+ cairo_rectangle(cr, r);
+ if(show_splits) {
+ cairo_stroke(cr);
+ } else {
+ cairo_fill(cr);
+ }
+ cairo_restore(cr);
+ return;
+ }
+
+ if((r.width() > w) or (r.height()>w)) {
+ if(f.strictly_neg() or f.straddles_zero()) {
+ // Three possibilities:
+ // 1) the trim operation buys us enough that we should just iterate
+ Point c = r.midpoint();
+ Rect oldr = r;
+ if(1 && out) {
+ r = *out;
+ for(int i = 0; i < 4; i++) {
+ Point p = oldr.corner(i);
+ if(dot(n,p) < ivl.middle()) {
+ r.expandTo(p);
+ }
+ }
+ }
+ if(1 && out && (r.area() < oldr.area()*0.25)) {
+ splits[0] ++;
+ recursive_implicit(r, cr, w);
+ // 2) one dimension is significantly smaller
+ } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) {
+ splits[1]++;
+ recursive_implicit(Rect(Interval(r.left(), r.right()),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(r.left(), r.right()),
+ Interval(c[1], r.bottom())), cr,w);
+ } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) {
+ splits[2]++;
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), r.bottom())), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(r.top(), r.bottom())), cr,w);
+ // 3) to ensure progress we must do a four way split
+ } else {
+ splits[3]++;
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(r.top(), c[1])), cr,w);
+ recursive_implicit(Rect(Interval(r.left(), c[0]),
+ Interval(c[1], r.bottom())), cr,w);
+ recursive_implicit(Rect(Interval(c[0], r.right()),
+ Interval(c[1], r.bottom())), cr,w);
+ }
+ }
+ } else {
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 'w') toggles[0].toggle(); else
+ if(e->keyval == 'a') toggles[1].toggle(); else
+ if(e->keyval == 'q') toggles[2].toggle(); else
+ if(e->keyval == 's') toggles[3].toggle();
+ redraw();
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ origin = orig_handle.pos;
+ if(1) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0.5, 0.5, 1);
+ eval = xaxis;
+ //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ eval = yaxis;
+ //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ cairo_restore(cr);
+ iters = 0;
+ for(int & split : splits)
+ split = 0;
+ show_splits = toggles[0].on;
+ eval = trial_eval;
+ recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3);
+ for(int split : splits)
+ *notify << split << " + ";
+ *notify << " = " << iters;
+ }
+ if(1) {
+ Rect r(test_window.pts[0], test_window.pts[1]);
+ AAF x(interval(r.left(), r.right()));
+ AAF y(interval(r.top(), r.bottom()));
+ //AAF f = md_sample_based(x, samples.pts)-y;
+ if(0) {
+ x = x-500;
+ y = y-300;
+ x = x/200;
+ y = y/200;
+ AAF f = atan_sample_based(x)-y;
+ cout << f << endl;
+ }
+ AAF f = (*eval)(x, y);
+ double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0));
+ double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0));
+ AAF d = a*x + b*y - f;
+ //cout << d << endl;
+ interval ivl(d);
+ Point n(a,b);
+ OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max()));
+ if(out)
+ cairo_rectangle(cr, *out);
+ cairo_rectangle(cr, r);
+ draw_line_in_rect(cr, r, n, ivl.min());
+ if(f.strictly_neg()) {
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, .5, 0.5, 0, 0.125);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ } else
+ cairo_stroke(cr);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0.5, 0.5, 0);
+ draw_line_in_rect(cr, r, n, ivl.middle());
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ //fill_line_in_rect(cr, r, n, c);
+ draw_line_in_rect(cr, r, n, ivl.max());
+ cairo_stroke(cr);
+ }
+ if(0) {
+ Geom::ConvexHull gm(samples.pts);
+ cairo_convex_hull(cr, gm);
+ cairo_stroke(cr);
+ Point a, b, c;
+ gm.narrowest_diameter(a, b, c);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 2);
+ cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+ cairo_move_to(cr, b);
+ cairo_line_to(cr, c);
+ cairo_move_to(cr, a);
+ cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b);
+ cairo_stroke(cr);
+ //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n";
+ cairo_restore(cr);
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ Point d(25,25);
+ toggles[0].bounds = Rect(Point(10, height-80)+d,
+ Point(10+120, height-80+d[1])+d);
+
+ draw_toggles(cr, toggles);
+ }
+
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ConvexTest());
+
+ 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/src/toys/inner-product-clip.cpp b/src/toys/inner-product-clip.cpp
new file mode 100644
index 0000000..9ada56b
--- /dev/null
+++ b/src/toys/inner-product-clip.cpp
@@ -0,0 +1,174 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+
+#include <gsl/gsl_matrix.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void draw_line(cairo_t* cr, Geom::Point n, double d) {
+ cairo_move_to(cr, d*n + rot90(n)*1000);
+ cairo_line_to(cr, d*n - rot90(n)*1000);
+ cairo_move_to(cr, d*n);
+ cairo_line_to(cr, (d+10)*n);
+}
+
+class InnerProductClip: public Toy {
+ Path path_a;
+ Piecewise<D2<SBasis> > path_a_pw;
+ std::vector<Toggle> togs;
+ PointHandle start_handle, end_handle;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+
+ D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw);
+
+ Point n;
+ double d;
+ {
+ double x = width - 60, y = height - 60;
+ Point p(x, y), dpoint(25,25), xo(25,0), yo(0,25);
+ togs[0].bounds = Rect(p, p + dpoint);
+ togs[1].bounds = Rect(p + xo, p + xo + dpoint);
+ draw_toggles(cr, togs);
+ }
+ if(togs[0].on) {
+ d = L2(end_handle.pos - start_handle.pos);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, start_handle.pos[0], start_handle.pos[1], d, 0, M_PI*2);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ } else {
+ n = unit_vector(rot90(end_handle.pos - start_handle.pos));
+ d = dot(n, start_handle.pos);
+ draw_line(cr, n, d);
+ }
+ //printf("%g\n", d);
+
+ vector<double> all_roots;
+ for(unsigned i = 0; i <= path_a.size(); i++) {
+ //deriv = p[i].derivative();
+ D2<SBasis> curpw = path_a[i].toSBasis();
+ SBasis inner;
+ if(togs[0].on) {
+ D2<SBasis> test = curpw - start_handle.pos;
+ inner = test[0]*test[0] + test[0]*test[1] + 2*test[1]*test[1] - d*d;
+ } else {
+ inner = n[0]*curpw[0] + n[1]*curpw[1] - d;
+ }
+ vector<double> lr = roots(inner);
+ all_roots.insert(all_roots.end(), lr.begin(), lr.end());
+ for(double i : lr)
+ draw_handle(cr, curpw(i));
+ sort(lr.begin(), lr.end());
+ lr.insert(lr.begin(), 0);
+ lr.insert(lr.end(), 1);
+ Path out;
+ for(unsigned j = 0; j < lr.size()-1; j++) {
+ //Point s = curpw(lr[j]);
+ Point m = curpw((lr[j] + lr[j+1])/2);
+ if(togs[0].on)
+ m -= start_handle.pos;
+ //Point e = curpw(lr[j+1]);
+ double dd;
+ if(togs[0].on)
+ //dd = dot(m, m) - d*d;
+ dd = m[0]*m[0] + m[0]*m[1] + 2*m[1]*m[1] - d*d;
+ else
+ dd = dot(n, m) - d;
+ if(togs[1].on)
+ dd = -dd;
+ //printf("%d [%g, %g] %g (%g, %g) (%g, %g)\n",
+ // i, lr[j], lr[j+1], dd, s[0], s[1], e[0], e[1]);
+ if(0 > dd) {
+ //Curve * cv = path_a[i].portion(lr[j], lr[j+1]);
+ cairo_d2_sb(cr, portion(curpw, lr[j], lr[j+1]));
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+ /*cairo_curve(cr, path_a[i]);
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);*/
+ }
+
+ }
+ }
+
+ //cairo_pw_d2_sb(cr, path_a_pw);
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 's') togs[1].toggle(); else
+ if(e->keyval == 'c') togs[0].toggle();
+ redraw();
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(togs, e);
+ Toy::mouse_pressed(e);
+ }
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="star.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ PathVector paths_a = read_svgd(path_a_name);
+ assert(paths_a.size() > 0);
+ path_a = paths_a[0];
+
+ path_a.close(true);
+ path_a_pw = path_a.toPwSb();
+
+ // Finite images of the three vanishing points and the origin
+ handles.push_back(&start_handle);
+ handles.push_back(&end_handle);
+ togs.emplace_back("C", true);
+ togs.emplace_back("S", true);
+ }
+public:
+ InnerProductClip() : start_handle(150,300),
+ end_handle(380,40) {}
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new InnerProductClip);
+ 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/src/toys/intersect-data.cpp b/src/toys/intersect-data.cpp
new file mode 100644
index 0000000..d262684
--- /dev/null
+++ b/src/toys/intersect-data.cpp
@@ -0,0 +1,436 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+
+#include <cstdlib>
+#include <cstdio>
+#include <set>
+#include <vector>
+#include <algorithm>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/ord.h>
+
+#include "topology.cpp"
+
+
+static double exp_rescale(double x){ return pow(10, x);}
+std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));}
+
+
+class IntersectDataTester: public Toy {
+ unsigned nb_paths;
+ unsigned nb_curves_per_path;
+ unsigned degree;
+ double tol;
+
+ PathVector cmd_line_paths;
+
+ std::vector<PointSetHandle> paths_handles;
+ std::vector<Slider> sliders;
+ int nb_steps;
+
+ Topology topo;
+
+ //TODO conversions to path should be owned by the relevant classes.
+ Path edgeToPath(Topology::OrientedEdge o_edge){
+ Topology::Edge e = topo.edges[o_edge.edge];
+ D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ p = portion(p, dom);
+ if ( o_edge.reversed ){
+ p = compose( p, Linear(1.,0.) );
+ }
+ Path ret;
+ ret.setStitching(true);
+ Point center;
+ unsigned c_idx = topo.source(o_edge, true);
+ if ( c_idx == NULL_IDX ){
+ ret.append(p);
+ }else{
+ center = topo.vertices[c_idx].bounds.midpoint();
+ ret = Path(center);
+ ret.append(p);
+ }
+ c_idx = topo.target(o_edge, true);
+ if ( c_idx == NULL_IDX ){
+ return ret;
+ }else{
+ center = topo.vertices[c_idx].bounds.midpoint();
+ if ( center != p.at1() ) ret.appendNew<LineSegment>(center);
+ return ret;
+ }
+ }
+
+ Path boundaryToPath(Topology::Boundary b){
+ Point pt;
+ Path bndary;
+ bndary.setStitching(true);
+
+ if (b.size()==0){ return Path(); }
+
+ Topology::OrientedEdge o_edge = b.front();
+ unsigned first_v = topo.source(o_edge, true);
+ if ( first_v != NULL_IDX ){
+ pt = topo.vertices[first_v].bounds.midpoint();
+ bndary = Path(pt);
+ }
+
+ for (unsigned i = 0; i < b.size(); i++){
+ bndary.append( edgeToPath(b[i]));
+ }
+ bndary.close();
+ return bndary;
+ }
+
+ //TODO:this should return a path vector, but we glue the components for easy drawing in the toy.
+ Path areaToPath(unsigned a){
+ Path bndary;
+ bndary.setStitching(true);
+ if ( topo.areas[a].boundary.size()==0 ){//this is the unbounded component...
+ OptRect bbox = bounds_fast( topo.input_paths );
+ if (!bbox ){return Path();}//???
+ bbox->expandBy(50);
+ bndary = Path(bbox->corner(0));
+ bndary.appendNew<LineSegment>(bbox->corner(1));
+ bndary.appendNew<LineSegment>(bbox->corner(2));
+ bndary.appendNew<LineSegment>(bbox->corner(3));
+ bndary.appendNew<LineSegment>(bbox->corner(0));
+ }else{
+ bndary = boundaryToPath(topo.areas[a].boundary);
+ }
+ for (auto & inner_boundarie : topo.areas[a].inner_boundaries){
+ bndary.append( boundaryToPath(inner_boundarie));
+ bndary.appendNew<LineSegment>( bndary.initialPoint() );
+ }
+ bndary.close();
+ return bndary;
+ }
+ void drawAreas( cairo_t *cr, Topology const &topo, bool fill=true ){
+ //don't draw the first one...
+ for (unsigned a=0; a<topo.areas.size(); a++){
+ drawArea(cr, topo, a, fill);
+ }
+ }
+ void drawArea( cairo_t *cr, Topology const &topo, unsigned a, bool fill=true ){
+ if (a>=topo.areas.size()) return;
+ Path bndary = areaToPath(a);
+ cairo_path(cr, bndary);
+ double r,g,b;
+
+ int winding = 0;
+ for (int k : topo.areas[a].windings){
+ winding += k;
+ }
+
+ //convertHSVtoRGB(0, 1., .5 + winding/10, r,g,b);
+ //convertHSVtoRGB(360*a/topo.areas.size(), 1., .5, r,g,b);
+ convertHSVtoRGB(180+30*winding, 1., .5, r,g,b);
+ cairo_set_source_rgba (cr, r, g, b, 1);
+ //cairo_set_source_rgba (cr, 1., 0., 1., .3);
+
+ if (fill){
+ cairo_fill(cr);
+ }else{
+ cairo_set_line_width (cr, 5);
+ cairo_stroke(cr);
+ }
+ }
+
+ void highlightRay( cairo_t *cr, Topology &topo, unsigned b, unsigned r ){
+ if (b>=topo.vertices.size()) return;
+ if (r>=topo.vertices[b].boundary.size()) return;
+ Rect box = topo.vertices[b].bounds;
+ //box.expandBy(2);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, 1.0);
+ cairo_set_line_width (cr, 1);
+ cairo_fill(cr);
+ unsigned eidx = topo.vertices[b].boundary[r].edge;
+ Topology::Edge e = topo.edges[eidx];
+ D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ if (topo.vertices[b].boundary[r].reversed){
+ //dom[0] += e.portion.extent()*2./3;
+ cairo_set_source_rgba (cr, 0., 1., 0., 1.0);
+ }else{
+ //dom[1] -= e.portion.extent()*2./3;
+ cairo_set_source_rgba (cr, 0., 0., 1., 1.0);
+ }
+ p = portion(p, dom);
+ cairo_d2_sb(cr, p);
+ cairo_set_source_rgba (cr, 1., 0., 0, 1.0);
+ cairo_set_line_width (cr, 5);
+ cairo_stroke(cr);
+ }
+
+ void drawEdge( cairo_t *cr, Topology const &topo, unsigned eidx ){
+ if (eidx>=topo.edges.size()) return;
+ Topology::Edge e = topo.edges[eidx];
+ D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ p = portion(p, dom);
+ cairo_d2_sb(cr, p);
+ if (e.start == NULL_IDX || e.end == NULL_IDX )
+ cairo_set_source_rgba (cr, 0., 1., 0, 1.0);
+ else
+ cairo_set_source_rgba (cr, 0., 0., 0, 1.0);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+ }
+ void drawEdges( cairo_t *cr, Topology const &topo ){
+ for (unsigned e=0; e<topo.edges.size(); e++){
+ drawEdge(cr, topo, e);
+ }
+ }
+ void drawKnownEdges( cairo_t *cr, Topology const &topo ){
+ for (unsigned v=0; v<topo.vertices.size(); v++){
+ for (unsigned e=0; e<topo.vertices[v].boundary.size(); e++){
+ drawEdge(cr, topo, topo.vertices[v].boundary[e].edge);
+ }
+ }
+ }
+
+
+ void drawBox( cairo_t *cr, Topology const &topo, unsigned b ){
+ if (b>=topo.vertices.size()) return;
+ Rect box = topo.vertices[b].bounds;
+ //box.expandBy(5);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, .5);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, .2);
+ cairo_fill(cr);
+
+// //std::cout<<"\nintersection boundary:\n";
+// for (unsigned i = 0; i < topo.vertices[b].boundary.size(); i++){
+// unsigned eidx = topo.vertices[b].boundary[i].edge;
+// Topology::Edge e = topo.edges[eidx];
+// D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis();
+// Interval dom = e.portion;
+// if (topo.vertices[b].boundary[i].reversed){
+// dom[0] += e.portion.extent()*2./3;
+// cairo_set_source_rgba (cr, 0., 1., .5, 1);
+// }else{
+// dom[1] -= e.portion.extent()*2./3;
+// cairo_set_source_rgba (cr, 0., .5, 1., 1);
+// }
+// p = portion(p, dom);
+// cairo_d2_sb(cr, p);
+// cairo_set_line_width (cr, 2);
+// cairo_stroke(cr);
+// }
+ }
+ void drawBoxes( cairo_t *cr, Topology const &topo ){
+ for (unsigned b=0; b<topo.vertices.size(); b++){
+ drawBox(cr, topo, b);
+ }
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ *notify<<"line command args: svgd file or (nb paths, nb curves/path, degree of curves).\n";
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ PathVector paths;
+ if (!cmd_line_paths.empty()){
+ paths = cmd_line_paths;
+ for (unsigned i = 0; i < paths.size(); i++){
+ paths[i] *= Translate( paths_handles[i].pts[0] - paths[i].initialPoint() );
+ }
+ }else{
+ for (unsigned i = 0; i < nb_paths; i++){
+ paths_handles[i].pts.back()=paths_handles[i].pts.front();
+ paths.push_back(Path(paths_handles[i].pts[0]));
+ for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){
+ D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree);
+ if ( j + degree == paths_handles[i].size()-1 ){
+ c[X].at(0)[1] = paths_handles[i].pts.front()[X];
+ c[Y].at(0)[1] = paths_handles[i].pts.front()[Y];
+ }
+ paths[i].append(c);
+ }
+ paths[i].close();
+ }
+ }
+ *notify<<"Use '<' and '>' keys to move backward/forward in the sweep: (currently doing "<<nb_steps<<" steps)\n";
+ *notify<<"nb_steps: "<<nb_steps<<"\n";
+
+
+#if 0
+ cairo_path(cr, paths);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+#endif
+
+ tol = exp_rescale( sliders[3].value() );
+ topo = Topology(paths, cr, tol, nb_steps );
+
+#if 1
+ unsigned v = (unsigned)(sliders[0].value()*(double(topo.vertices.size())));
+ unsigned r = (unsigned)(sliders[1].value()*(double(topo.vertices[v].boundary.size())));
+ unsigned a = (unsigned)(sliders[2].value()*(double(topo.areas.size())));
+ if( v == topo.vertices.size() ) v--;
+ if( r == topo.vertices[v].boundary.size()) r--;
+ if( a == topo.areas.size()) a--;
+ drawAreas(cr, topo);
+ drawKnownEdges(cr, topo);
+ //drawArea(cr, topo, a, false);
+ //highlightRay(cr, topo, v, r );
+ //*notify<<"highlighted edge: "<< topo.vertices[v].boundary[r].edge<<"\n";
+
+ //drawBox(cr,topo, unsigned(sliders[0].value()));
+ drawBoxes(cr,topo);
+#endif
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+
+ void initSliders(){
+ sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser");
+ sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser");
+ sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser");
+ sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser");
+
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(sliders[1]));
+ handles.push_back(&(sliders[2]));
+ handles.push_back(&(sliders[3]));
+
+ sliders[0].geometry(Point(50, 20), 250);
+ sliders[1].geometry(Point(50, 50), 250);
+ sliders[2].geometry(Point(50, 80), 250);
+ sliders[3].geometry(Point(50, 110), 250);
+ sliders[3].formatter( &exp_formatter );
+
+ }
+
+ public:
+ IntersectDataTester(PathVector input_paths){
+ cmd_line_paths = input_paths;
+ //nb_paths=0; nb_curves_per_path = 0; degree = 0;//meaningless
+ paths_handles = std::vector<PointSetHandle>( cmd_line_paths.size(), PointSetHandle() );
+ for(unsigned i = 0; i < cmd_line_paths.size(); i++){
+ //TODO: use path iterators to deal with closed/open paths!!!
+ //cmd_line_paths[i].close();
+ if ( cmd_line_paths[i].closed() ){
+ cmd_line_paths[i].appendNew<LineSegment>(cmd_line_paths[i].initialPoint() );
+ }
+ Point p = cmd_line_paths[i].initialPoint();
+ paths_handles.emplace_back();
+ paths_handles[i].push_back(p);
+ handles.push_back( &paths_handles[i] );
+ }
+ initSliders();
+ }
+
+ IntersectDataTester(unsigned paths, unsigned curves_in_path, unsigned degree) :
+ nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) {
+
+ paths_handles = std::vector<PointSetHandle>( nb_paths, PointSetHandle() );
+ for(unsigned i = 0; i < nb_paths; i++){
+ for(unsigned j = 0; j < (nb_curves_per_path*degree)+1; j++){
+ paths_handles[i].push_back(uniform()*400, 100+ uniform()*300);
+ }
+ handles.push_back(&paths_handles[i]);
+ }
+ initSliders();
+ }
+
+ IntersectDataTester(){
+ nb_paths=3; nb_curves_per_path = 5; degree = 1;
+
+ paths_handles.emplace_back();
+ paths_handles[0].push_back(100,100);
+ paths_handles[0].push_back(100,200);
+ paths_handles[0].push_back(300,200);
+ paths_handles[0].push_back(300,100);
+ paths_handles[0].push_back(100,100);
+
+ paths_handles.emplace_back();
+ paths_handles[1].push_back(120,190);
+ paths_handles[1].push_back(200,210);
+ paths_handles[1].push_back(280,190);
+ paths_handles[1].push_back(200,300);
+ paths_handles[1].push_back(120,190);
+
+ paths_handles.emplace_back();
+ paths_handles[2].push_back(180,150);
+ paths_handles[2].push_back(200,140);
+ paths_handles[2].push_back(220,150);
+ paths_handles[2].push_back(300,160);
+ paths_handles[2].push_back(180,150);
+
+ handles.push_back(&paths_handles[0]);
+ handles.push_back(&paths_handles[1]);
+ handles.push_back(&paths_handles[2]);
+
+ initSliders();
+ }
+
+
+ void first_time(int /*argc*/, char** /*argv*/) override {
+ nb_steps = -1;
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case '>':
+ nb_steps++;
+ break;
+ case '<':
+ if ( nb_steps > -1 ) nb_steps--;
+ break;
+ }
+ redraw();
+ }
+
+};
+
+int main(int argc, char **argv) {
+ if(argc == 2){
+ const char *path_name = argv[1];
+ PathVector cmd_line_paths = read_svgd(path_name); //* Scale(3);
+ OptRect bounds = bounds_exact(cmd_line_paths);
+ if (bounds) {
+ cmd_line_paths *= Translate(Point(10,10) - bounds->min());
+ }
+ init(argc, argv, new IntersectDataTester(cmd_line_paths));
+ }else{
+ unsigned nb_paths=3, nb_curves_per_path = 5, degree = 1;
+ if(argc > 3)
+ sscanf(argv[3], "%d", &degree);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &nb_curves_per_path);
+ if(argc > 1){
+ sscanf(argv[1], "%d", &nb_paths);
+ init(argc, argv, new IntersectDataTester( nb_paths, nb_curves_per_path, degree ) );
+ }else{
+ init(argc, argv, new IntersectDataTester());
+ }
+ }
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/inverse-test.cpp b/src/toys/inverse-test.cpp
new file mode 100644
index 0000000..c339419
--- /dev/null
+++ b/src/toys/inverse-test.cpp
@@ -0,0 +1,174 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*-vscale;
+ plot[1]+=450;
+ cairo_d2_sb(cr, plot);
+ cairo_stroke(cr);
+}
+static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){
+ for (unsigned i=0;i<f.size();i++){
+ plot(cr,f.segs[i],vscale,f.cuts[i],f.cuts[i+1]);
+ draw_cross(cr,Geom::Point(f.cuts[i]*300 + 150, f.segs[i][0][0]*(-vscale) + 450));
+ }
+}
+
+static SBasis my_inverse(SBasis f, int order){
+ double a0 = f[0][0];
+ if(a0 != 0) {
+ f -= a0;
+ }
+ double a1 = f[0][1];
+ if(a1 == 0)
+ THROW_NOTINVERTIBLE();
+ //assert(a1 != 0);// not invertible.
+ if(a1 != 1) {
+ f /= a1;
+ }
+
+ SBasis g=SBasis(order, Linear());
+ g[0] = Linear(0,1);
+ double df0=derivative(f)(0);
+ double df1=derivative(f)(1);
+ SBasis r = Linear(0,1)-g(f);
+
+ for(int i=1; i<order; i++){
+ //std::cout<<"i: "<<i<<std::endl;
+ r=Linear(0,1)-g(f);
+ //std::cout<<"t-gof="<<r<<std::endl;
+ r.normalize();
+ if (r.size()==0) return(g);
+ double a=r[i][0]/std::pow(df0,i);
+ double b=r[i][1]/std::pow(df1,i);
+ g[i] = Linear(a,b);
+ }
+
+ return(g);
+}
+
+static Piecewise<SBasis> pw_inverse(SBasis const &f, int order,double tol=.1,int depth=0){
+ SBasis g=SBasis(Linear(0,1)),r;
+ Piecewise<SBasis> res,res1,res2;
+
+ //std::cout<<"depth: "<<depth<<std::endl;
+ g=my_inverse(f,order);
+ r=g(f);
+ //std::cout<<"error: "<<g.tail_error(1)<<std::endl;
+ if (g.tailError(1)<tol){
+ res.segs.push_back(g);
+ res.cuts.push_back(f[0][0]);
+ res.cuts.push_back(f[0][1]);
+ }else if (depth<200){
+ SBasis ff;
+ ff=f(Linear(0,.5));
+ res=pw_inverse(ff,order,tol,depth+1);
+ for (unsigned i=0;i<res.size();i++){
+ res.segs[i]*=.5;
+ }
+ ff=f(Linear(.5,1));
+ res1=pw_inverse(ff,order,tol,depth+1);
+ for (unsigned i=0;i<res1.size();i++){
+ res1.segs[i]*=.5;
+ res1.segs[i]+=.5;
+ }
+ res.concat(res1);
+ }
+ return(res);
+}
+
+
+
+class InverseTester: public Toy {
+ int size;
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ for (int i=0;i<size;i++){
+ hand.pts[i ][0]=150+15*(i-size);
+ hand.pts[i+size][0]=450+15*(i+1);
+ cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450));
+ cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450));
+ }
+ cairo_move_to(cr, Geom::Point(0,300));
+ cairo_line_to(cr, Geom::Point(600,300));
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ SBasis f(size, Linear());//=SBasis(Linear(0,.5));
+ for (int i=0;i<size;i++){
+ f[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,i)/150,
+ -(hand.pts[i+size][1]-300)*std::pow(4.,i)/150 );
+ }
+ plot(cr,Linear(0,1),300);
+
+ f.normalize();
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ plot(cr,f,300);
+
+ *notify<<"Use hand.pts to set the coefficients of the (blue) s-basis."<<std::endl;
+ *notify<<" (keep it monotonic!)"<<std::endl;
+ *notify<<"red=flipped inverse; should be the same as the blue one."<<std::endl;
+
+ try {
+ Piecewise<SBasis> g=pw_inverse(f,3);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.8, 0., 0., 1);
+ plot(cr,g,300);
+ Piecewise<SBasis> h=compose(g,f);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.8, 0., 1);
+ plot(cr,h,300);
+ *notify<<g.size()<<" segments.";
+ } catch(NotInvertible) {
+ *notify << "function not invertible!" << std::endl;
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ InverseTester() {
+ size=4;
+ if(hand.pts.empty()) {
+ for(int i = 0; i < 2*size; i++)
+ hand.pts.emplace_back(0,150+150+uniform()*300*0);
+ }
+ hand.pts[0][1]=300;
+ hand.pts[size][1]=150;
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new InverseTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/kinematic_templates.cpp b/src/toys/kinematic_templates.cpp
new file mode 100644
index 0000000..0089a1a
--- /dev/null
+++ b/src/toys/kinematic_templates.cpp
@@ -0,0 +1,365 @@
+#include <toys/toy-framework-2.h>
+
+/*
+ * Copyright cilix42
+ * Kinematic template toy. The aim is to manipulate the cursor movement
+ * so that it stays closer to a given shape (e.g., a circle or a line).
+ * For details, see http://hci.uwaterloo.ca/Publications/Papers/uist222-fung.pdf
+ *
+ * Each kinematic template has a radius of action outside of which it
+ * has no effect (this is indicated by a red circle).
+ */
+
+#include <vector>
+#include <2geom/point.h>
+#include <2geom/transforms.h>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// I feel a little uneasy using a Point for polar coords.
+Point cartesian_to_polar(Point const &pt, Point const &center = Point(0,0)) {
+ Point rvec = pt - center;
+ // use atan2 unless you want to measure between two vectors
+ return Point(L2(rvec), atan2(rvec));
+}
+
+Point polar_to_cartesian(Point const &pt, Point const &center = Point(0,0)) {
+ return center + Point(pt[0],0) * Rotate(pt[1]);
+}
+
+class KinematicTemplate {
+public:
+ KinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0);
+ ~KinematicTemplate();
+
+ /*
+ * To facilitate the creation of templates, we can use different coordinates at each point
+ * (e.g., radial coordinates around a fixed center)
+ */
+ virtual std::pair<Point, Point> local_coordinate_system(Point const &/*at*/) {
+ // Return standard cartesian coordinates
+ return std::make_pair(Point(1,0), Point(0,1));
+ }
+
+ virtual Point next_point(Point const &at, Point const &delta);// { return at; }
+ virtual void draw_visual_cue(cairo_t *cr);
+
+ Point const get_center() { return center; }
+ void set_center(Point const &pos) { center = pos; }
+
+ double get_radius_of_action() { return radius; }
+ void set_radius_of_action(double const r) { radius = r; }
+ void enlarge_radius_of_action(double const by) {
+ if (radius > -by)
+ radius += by;
+ else
+ radius = 0;
+ }
+
+
+protected:
+ double sx, sy, cx, cy;
+ Point center;
+ double radius;
+};
+
+KinematicTemplate::KinematicTemplate(double const sx, double const sy, double const cx, double const cy)
+ : sx(sx),
+ sy(sy),
+ cx(cx),
+ cy(cy),
+ center(300,300),
+ radius(100)
+{
+}
+
+KinematicTemplate::~KinematicTemplate()
+{
+}
+
+Point
+KinematicTemplate::next_point(Point const &last_pt, Point const &delta)
+{
+// Point new_pt = last_pushed + kinematic_delta(last_pushed, delta, 0);
+
+ /* Compute the "relative" coordinates w.r.t. the "local coordinate system" at the current point */
+ Point v = local_coordinate_system(last_pt).first;
+ Point w = local_coordinate_system(last_pt).second;
+ double dotv = dot(v, delta);
+ double dotw = dot(w, delta);
+
+ Point new_delta;
+ if (L2(last_pt + delta - center) < radius) {
+ /*
+ * We are within the radius of action of the kinematic template.
+ * Compute displacement w.r.t. the v/w-coordinate system.
+ */
+ new_delta = (dotv*sx + cx)*v + (dotw*sy + cy)*w;
+ } else {
+ new_delta = delta;
+ }
+
+ return last_pt + new_delta;
+}
+
+void
+KinematicTemplate::draw_visual_cue(cairo_t *cr) {
+ cairo_set_source_rgba (cr, 1, 0, 0, 1);
+ cairo_set_line_width (cr, 0.5);
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, center[X], center[Y], radius, 0, M_PI*2);
+ cairo_stroke(cr);
+}
+
+class RadialKinematicTemplate : public KinematicTemplate {
+public:
+ RadialKinematicTemplate(Point const &center, double const sx, double const sy, double const cx, double const cy);
+
+ std::pair<Point, Point> local_coordinate_system(Point const &at) override {
+ /* Return 'radial' coordinates around polar_center */
+ Point v = unit_vector(at - center);
+ return std::make_pair(v, rot90(v));
+ }
+
+private:
+ Point radial_center;
+};
+
+RadialKinematicTemplate::RadialKinematicTemplate(Point const &center, double const sx, double const sy,
+ double const cx = 0.0, double const cy = 0.0)
+ : KinematicTemplate(sx, sy, cx, cy)
+{
+ radial_center = center;
+}
+
+class GridKinematicTemplate : public KinematicTemplate {
+public:
+ GridKinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0)
+ : KinematicTemplate(sx, sy, cx, cy) {};
+ Point next_point(Point const &at, Point const &delta) override;// { return at; }
+};
+
+Point
+GridKinematicTemplate::next_point(Point const &at, Point const &delta) {
+ if (L2(at + delta - center) < radius) {
+ // we are within the radius of action
+ Point new_delta = delta;
+
+ if (fabs(delta[0]) > fabs(delta[1]))
+ new_delta[1] *= sy;
+ else
+ new_delta[0] *= sx;
+
+ return at + new_delta;
+ } else {
+ return at + delta;
+ }
+}
+
+
+// My idea was to compute the gradient of an arbitrary potential function as the transform. Probably the right way to do this is to use the hessian as the integrand -- njh
+class ImplicitKinematicTemplate : public KinematicTemplate {
+public:
+ ImplicitKinematicTemplate() {}
+
+ Point next_point(Point const &at, Point const &delta) override {
+ if (L2(at + delta - center) < radius) {
+ // we are within the radius of action
+
+ // the 0.7dx+1 includes a weakened version of the constraining force
+ // I can't help but think this is really a form of differential constraint solver, let's discuss.
+ return at + delta*Scale(0.7*sin(at[0]/10.0)+1, 0.7*cos(at[1]/10.0)+1);
+ } else {
+ return at + delta;
+ }
+ }
+};
+
+
+
+vector<KinematicTemplate*> kin;
+KinematicTemplate *cur_kin;
+std::string cur_choice = "A";
+
+class KinematicTemplatesToy : public Toy {
+
+ enum menu_item_t
+ {
+ KT_HORIZONTAL = 0,
+ KT_VERTICAL,
+ KT_GRID,
+ KT_CIRCLE,
+ KT_RADIAL,
+ KT_CONVEYOR,
+ KT_IMP,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ Point cur, last_pushed;
+ vector<vector<Point>*> pts;
+
+ bool dragging_center; // to prevent drawing while dragging the center
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/ )
+ {
+ *notify << std::endl;
+ for (int i = KT_HORIZONTAL; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ *notify << "+/- - enlarge/shrink radius of action" << endl << endl << endl << endl;
+ *notify << "Current choice: " << cur_choice << endl;
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ //draw_handle(cr, cur_kin->get_center());
+ draw_menu(cr, notify, width, height, save, timer_stream);
+
+ // draw all points accumulated so far
+ for (auto & pt : pts) {
+ if (pt->size() > 0) {
+ cairo_move_to(cr, (*pt)[0]);
+ }
+ for (auto & j : *pt) {
+ //cout << " --> drawing line to point #" << j << endl;
+ cairo_line_to(cr, j);
+ }
+ }
+
+ cairo_stroke(cr);
+
+ cur_kin->draw_visual_cue(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void first_time(int /*argc*/, char** /*argv*/) override {
+ p1.pos = Point(200, 200);
+ handles.push_back(&p1);
+
+ pts.clear();
+ kin.push_back(new KinematicTemplate(1.0, 0.1)); // horizontal lines
+ kin.push_back(new KinematicTemplate(0.1, 1.0)); // horizontal lines
+ kin.push_back(new GridKinematicTemplate(0.1, 0.1));
+ kin.push_back(new RadialKinematicTemplate(p1.pos, 0.1, 1.0));
+ kin.push_back(new RadialKinematicTemplate(p1.pos, 1.0, 0.1));
+ kin.push_back(new KinematicTemplate(1.0, 0.1, 1, 0)); // horiz conveyor
+ kin.push_back(new ImplicitKinematicTemplate());
+ cur_kin = kin[0];
+ cur_kin->set_center(p1.pos);
+
+ dragging_center = false;
+ }
+
+ void mouse_pressed(GdkEventButton *e) override {
+ Point at(e->x, e->y);
+
+ if(L2(at - p1.pos) < 5) {
+ dragging_center = true;
+ } else {
+ if(e->button == 1) {
+ vector<Point> *vec = new vector<Point>;
+ vec->clear();
+ vec->push_back(at);
+ last_pushed = at;
+ pts.push_back(vec);
+ }
+ }
+
+ Toy::mouse_pressed(e);
+ }
+
+ void mouse_released(GdkEventButton */*e*/) override {
+ dragging_center = false;
+ }
+
+ void mouse_moved(GdkEventMotion* e) override {
+ if (!dragging_center) {
+ Point at(e->x, e->y);
+
+ Point delta = at - cur;
+ //cout << "Mouse moved to: " << at << " (difference: " << delta << ")" << endl;
+ if(e->state & GDK_BUTTON1_MASK) {
+ Point new_pt = cur_kin->next_point(last_pushed, delta);
+
+ pts.back()->push_back(new_pt);
+ last_pushed = new_pt;
+ }
+ cur = at;
+ } else {
+ cur_kin->set_center(p1.pos);
+ }
+
+ Toy::mouse_moved(e);
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ // No need to copy and paste code
+ if(choice >= 'A' and choice < 'A' + TOTAL_ITEMS) {
+ cur_kin = kin[choice - 'A'];
+ cur_choice = choice;
+
+ } else
+ switch (choice)
+ {
+ case '+':
+ cur_kin->enlarge_radius_of_action(5);
+ break;
+ case '-':
+ cur_kin->enlarge_radius_of_action(-5);
+ break;
+ default:
+ break;
+ }
+ p1.pos = cur_kin->get_center();
+ redraw();
+ }
+
+private:
+ PointHandle p1;
+};
+
+const char* KinematicTemplatesToy::menu_items[] =
+{
+ "horizontal",
+ "vertical",
+ "grid",
+ "circular",
+ "radial",
+ "conveyor",
+ "implicit"
+};
+
+const char KinematicTemplatesToy::keys[] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G'
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new KinematicTemplatesToy);
+ 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/src/toys/levelsets-test.cpp b/src/toys/levelsets-test.cpp
new file mode 100644
index 0000000..cd5874e
--- /dev/null
+++ b/src/toys/levelsets-test.cpp
@@ -0,0 +1,155 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace std;
+using namespace Geom;
+
+static double exp_rescale(double x){ return std::pow(10, x);}
+std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));}
+
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*(-vscale);
+ plot[1]+=300;
+ cairo_d2_sb(cr, plot);
+ cairo_stroke(cr);
+}
+static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){
+ cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300));
+ cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300));
+}
+static void plot_bar(cairo_t* cr, Interval height, double vscale=1,double a=0,double b=1){
+ Point A(150+300*a,-height.max()*vscale+300);
+ Point B(150+300*b,-height.min()*vscale+300);
+ cairo_rectangle(cr, Rect(A,B) );
+}
+
+class BoundsTester: public Toy {
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ for (unsigned i=0;i<size;i++){
+ hand.pts[i ][0]=150+15*(i-size);
+ hand.pts[i+size][0]=450+15*(i+1);
+ cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450));
+ cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150));
+ cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450));
+ }
+ cairo_move_to(cr, Geom::Point(0,300));
+ cairo_line_to(cr, Geom::Point(600,300));
+
+ cairo_set_line_width (cr, .3);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ SBasis B(size, Linear());
+ for (unsigned i=0;i<size;i++){
+ B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i),
+ -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) );
+ }
+ B.normalize();
+ plot(cr,B,1);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ cairo_stroke(cr);
+
+ double vtol = exp_rescale(slider.value());
+ if (vtol<1e-4) vtol=0;
+
+ hand.pts[2*size ][X]=150;
+ hand.pts[2*size+1][X]=150;
+ hand.pts[2*size+2][X]=150;
+ hand.pts[2*size+1][Y]=std::max(hand.pts[2*size+1][Y],hand.pts[2*size+2][Y]+2*vtol);
+ hand.pts[2*size ][Y]=std::max(hand.pts[2*size ][Y],hand.pts[2*size+1][Y]+2*vtol);
+
+ vector<Interval> levels;
+ levels.emplace_back(300-(hand.pts[2*size ][Y]-vtol), 300-(hand.pts[2*size ][Y]+vtol) );
+ levels.emplace_back(300-(hand.pts[2*size+1][Y]-vtol), 300-(hand.pts[2*size+1][Y]+vtol) );
+ levels.emplace_back(300-(hand.pts[2*size+2][Y]-vtol), 300-(hand.pts[2*size+2][Y]+vtol) );
+
+ for (auto & level : levels) plot_bar(cr,level.middle());
+ cairo_set_source_rgba( cr, 1., 0., 0., 1);
+ cairo_stroke(cr);
+ for (auto & level : levels) plot_bar(cr,level);
+ cairo_set_source_rgba( cr, 1., 0., 0., .2);
+ cairo_fill(cr);
+
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+
+ *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl;
+
+ vector<vector<Interval> > sols=level_sets(B,levels,0,1);
+ for (unsigned i=0;i<sols.size();i++){
+ for (unsigned j=0;j<sols[i].size();j++){
+ Interval ys = levels[i];
+ ys.expandTo(0.);
+ cairo_set_line_width (cr, .3);
+ plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max());
+ cairo_set_source_rgba( cr, 0., 0., 1., .3);
+ cairo_fill(cr);
+ plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max());
+ cairo_set_source_rgba( cr, 0., 0., 1., .3);
+ cairo_stroke(cr);
+ cairo_set_line_width (cr, 1.6);
+ cairo_set_source_rgba( cr, 0., 0., 1, 1);
+ plot_bar(cr,0., 1., sols[i][j].min(), sols[i][j].max());
+ Point sol ( 150 + 300 * sols[i][j].middle(), 300);
+ draw_cross(cr, sol);
+ cairo_stroke(cr);
+ }
+ }
+
+ cairo_set_source_rgba( cr, 0., 0., 1., .5);
+ cairo_fill(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+
+public:
+ BoundsTester(){
+ size=5;
+ if(hand.pts.empty()) {
+ for(unsigned i = 0; i < 2*size; i++)
+ hand.pts.emplace_back(0,150+150+uniform()*300*0);
+ }
+ hand.pts.emplace_back(150,300+ 50+uniform()*100);
+ hand.pts.emplace_back(150,300- 50+uniform()*100);
+ hand.pts.emplace_back(150,300-150+uniform()*100);
+ handles.push_back(&hand);
+ slider = Slider(-5, 2, 0, 0.5, "tolerance");
+ slider.geometry(Point(50, 20), 250);
+ slider.formatter(&exp_formatter);
+ handles.push_back(&slider);
+ }
+
+
+private:
+ unsigned size;
+ PointSetHandle hand;
+ Slider slider;
+
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new BoundsTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/line-toy.cpp b/src/toys/line-toy.cpp
new file mode 100644
index 0000000..0090d3a
--- /dev/null
+++ b/src/toys/line-toy.cpp
@@ -0,0 +1,916 @@
+/*
+ * Line Toy
+ *
+ * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * 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/line.h>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/angle.h>
+
+#include <vector>
+#include <string>
+#include <optional>
+
+using namespace Geom;
+
+
+
+std::string angle_formatter(double angle)
+{
+ return default_formatter(decimal_round(deg_from_rad(angle),2));
+}
+
+
+
+class LineToy : public Toy
+{
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_CREATE,
+ TEST_PROJECTION,
+ TEST_ORTHO,
+ TEST_DISTANCE,
+ TEST_POSITION,
+ TEST_SEG_BISEC,
+ TEST_ANGLE_BISEC,
+ TEST_COLLINEAR,
+ TEST_INTERSECTIONS,
+ TEST_COEFFICIENTS,
+ TEST_SEGMENT_INSIDE,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ enum handle_label_t
+ {
+ };
+
+ enum toggle_label_t
+ {
+ };
+
+ enum slider_label_t
+ {
+ END_SHARED_SLIDERS = 0,
+ ANGLE_SLIDER = END_SHARED_SLIDERS,
+ A_COEFF_SLIDER = END_SHARED_SLIDERS,
+ B_COEFF_SLIDER,
+ C_COEFF_SLIDER
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &LineToy::draw_menu;
+ }
+
+ void init_common()
+ {
+ set_common_control_geometry = true;
+ set_control_geometry = true;
+
+ sliders.clear();
+ toggles.clear();
+ handles.clear();
+ }
+
+
+ virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/ )
+ {
+ init_common_ctrl_geom(cr, width, height, notify);
+ }
+
+
+ void init_create()
+ {
+ init_common();
+
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ O.pos = Point(50, 400);
+
+ sliders.emplace_back(0, 2*M_PI, 0, 0, "angle");
+ sliders[ANGLE_SLIDER].formatter(&angle_formatter);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&O);
+ handles.push_back(&(sliders[ANGLE_SLIDER]));
+ }
+
+ void draw_create(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_create_ctrl_geom(cr, notify, width, height);
+
+ Line l1(p1.pos, p2.pos);
+ Line l2(O.pos, sliders[ANGLE_SLIDER].value());
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_line(cr, l2);
+ cairo_stroke(cr);
+
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, O, "O");
+ draw_label(cr, l1, "L(P1,P2)");
+ draw_label(cr, l2, "L(O,angle)");
+ }
+
+
+ void init_projection()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 250);
+ p4.pos = Point(200, 450);
+ O.pos = Point(50, 150);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ handles.push_back(&O);
+ }
+
+ void draw_projection(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ LineSegment ls(p3.pos, p4.pos);
+
+ Point np = projection(O.pos, l1);
+ LineSegment lsp = projection(ls, l1);
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width(cr, 0.2);
+ draw_line(cr, l1);
+ draw_segment(cr, ls);
+ cairo_stroke(cr);
+
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
+ draw_segment(cr, lsp);
+ draw_handle(cr, lsp[0]);
+ draw_handle(cr, lsp[1]);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ draw_circ(cr, np);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, ls, "S");
+ draw_label(cr, lsp, "prj(S)");
+ draw_label(cr, O, "P");
+ draw_text(cr, np, "prj(P)");
+
+ cairo_stroke(cr);
+ }
+
+
+ void init_ortho()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 50);
+ p4.pos = Point(150, 450);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ }
+
+ void draw_ortho(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ Line l2 = make_orthogonal_line(p3.pos, l1);
+ Line l3 = make_parallel_line(p4.pos, l1);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_line(cr, l2);
+ draw_line(cr, l3);
+ cairo_stroke(cr);
+
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ draw_label(cr, p3, "O1");
+ draw_label(cr, p4, "O2");
+
+ draw_label(cr, l1, "L");
+ draw_label(cr, l2, "L1 _|_ L");
+ draw_label(cr, l3, "L2 // L");
+
+ }
+
+
+ void init_distance()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 250);
+ p4.pos = Point(200, 450);
+ p5.pos = Point(50, 150);
+ p6.pos = Point(480, 60);
+ O.pos = Point(300, 300);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ handles.push_back(&p5);
+ handles.push_back(&p6);
+ handles.push_back(&O);
+ }
+
+ void draw_distance(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ LineSegment ls(p3.pos, p4.pos);
+ Ray r1(p5.pos, p6.pos);
+
+ Point q1 = l1.pointAt(l1.nearestTime(O.pos));
+ Point q2 = ls.pointAt(ls.nearestTime(O.pos));
+ Point q3 = r1.pointAt(r1.nearestTime(O.pos));
+
+ double d1 = distance(O.pos, l1);
+ double d2 = distance(O.pos, ls);
+ double d3 = distance(O.pos, r1);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_segment(cr, ls);
+ draw_ray(cr, r1);
+ cairo_stroke(cr);
+
+
+ draw_label(cr, l1, "L");
+ draw_label(cr, ls, "S");
+ draw_label(cr, r1, "R");
+ draw_label(cr, O, "P");
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0);
+ cairo_set_line_width(cr, 0.2);
+ draw_segment(cr, O.pos, q1);
+ draw_segment(cr, O.pos, q2);
+ draw_segment(cr, O.pos, q3);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_handle(cr, q1);
+ draw_handle(cr, q2);
+ draw_handle(cr, q3);
+ cairo_stroke(cr);
+
+ *notify << " distance(P,L) = " << d1 << std::endl;
+ *notify << " distance(P,S) = " << d2 << std::endl;
+ *notify << " distance(P,R) = " << d3 << std::endl;
+ }
+
+
+ void init_position()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 50);
+ p4.pos = Point(150, 450);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ }
+
+ void draw_position(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ Line l2(p3.pos, p4.pos);
+
+ bool b1 = are_same(l1, l2, 0.01);
+ bool b2 = are_parallel(l1, l2, 0.01);
+ bool b3 = are_orthogonal(l1, l2, 0.01);
+
+ double a = angle_between(l1,l2);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_line(cr, l2);
+ cairo_stroke(cr);
+
+ draw_label(cr, l1, "A");
+ draw_label(cr, l2, "B");
+ cairo_stroke(cr);
+
+ if (b1)
+ {
+ *notify << " A is coincident with B" << std::endl;
+ }
+ else if (b2)
+ {
+ *notify << " A is parallel to B" << std::endl;
+ }
+ else if (b3)
+ {
+ *notify << " A is orthogonal to B" << std::endl;
+ }
+ else
+ {
+ *notify << " A is incident with B: angle(A,B) = " << angle_formatter(a) << std::endl;
+ }
+ }
+
+
+ void init_seg_bisec()
+ {
+ init_common();
+ p1.pos = Point(100, 50);
+ p2.pos = Point(150, 450);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ }
+
+ void draw_seg_bisec(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ LineSegment ls(p1.pos, p2.pos);
+ Line l = make_bisector_line(ls);
+ Point M = middle_point(p1.pos, p2.pos);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_label(cr, p1, "P");
+ draw_label(cr, p2, "Q");
+ draw_label(cr, M, "M");
+ draw_segment(cr, ls);
+ draw_line(cr, l);
+ cairo_stroke(cr);
+
+ draw_label(cr, l, "axis");
+ cairo_stroke(cr);
+ }
+
+
+ void init_angle_bisec()
+ {
+ init_common();
+ p1.pos = Point(100, 50);
+ p2.pos = Point(150, 450);
+ O.pos = Point(50, 200);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&O);
+ }
+
+ void draw_angle_bisec(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Ray r1(O.pos, p1.pos);
+ Ray r2(O.pos, p2.pos);
+ Ray rb = make_angle_bisector_ray(r1, r2);
+
+ double a1 = angle_between(r1,rb);
+ double a2 = angle_between(rb,r2);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_label(cr, O, "O");
+ draw_ray(cr, r1);
+ draw_ray(cr, r2);
+ draw_ray(cr, rb);
+ cairo_stroke(cr);
+
+ draw_label(cr, r1, "R1");
+ draw_label(cr, r2, "R2");
+ draw_label(cr, rb, "bisector ray");
+
+ *notify << " angle(R1, bisector ray) = " << angle_formatter(a1)
+ << " angle(bisector ray, R2) = " << angle_formatter(a2)
+ << std::endl;
+ }
+
+
+ void init_collinear()
+ {
+ init_common();
+ p1.pos = Point(100, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(400, 50);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ }
+
+ void draw_collinear(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Point A = p1.pos;
+ Point B = p2.pos;
+ Point C = p3.pos;
+
+ LineSegment AB(A, B);
+ LineSegment BC(B, C);
+ Line l(A, C);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_label(cr, p1, "A");
+ draw_label(cr, p2, "B");
+ draw_label(cr, p3, "C");
+ cairo_stroke(cr);
+
+
+ double turn = cross(C, B) - cross(C, A) + cross(B, A);
+ //*notify << " turn: " << turn << std::endl;
+
+ bool collinear = are_collinear(A, B, C, 200);
+ if (collinear)
+ {
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l);
+ cairo_stroke(cr);
+ *notify << " A,B,C are collinear!" << std::endl;
+ }
+ else
+ {
+ cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0);
+ cairo_set_line_width(cr, 0.2);
+ draw_segment(cr, AB);
+ draw_segment(cr, BC);
+ cairo_stroke(cr);
+ if (turn < 0)
+ *notify << " A,B,C is a left turn: " << turn << std::endl;
+ else
+ *notify << " A,B,C is a right turn: " << turn << std::endl;
+ }
+
+ }
+
+
+ void init_intersections()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 250);
+ p4.pos = Point(200, 450);
+ p5.pos = Point(50, 150);
+ p6.pos = Point(480, 60);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ handles.push_back(&p5);
+ handles.push_back(&p6);
+ }
+
+ void draw_intersections(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Line l1(p1.pos, p2.pos);
+ Ray r1(p3.pos, p4.pos);
+ LineSegment s1(p5.pos, p6.pos);
+
+ std::vector<ShapeIntersection> cl1r1 = l1.intersect(r1);
+ std::vector<ShapeIntersection> cl1s1 = l1.intersect(s1);
+ std::vector<ShapeIntersection> cr1s1 = Line(r1).intersect(s1);
+ filter_ray_intersections(cr1s1, true, false);
+
+ std::vector<Point> ip;
+
+ if (!cl1r1.empty()) {
+ ip.push_back(l1.pointAt(cl1r1[0].first));
+ }
+ if (!cl1s1.empty()) {
+ ip.push_back(l1.pointAt(cl1s1[0].first));
+ }
+ if (!cr1s1.empty()) {
+ ip.push_back(r1.pointAt(cr1s1[0].first));
+ }
+
+ cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ draw_ray(cr, r1);
+ draw_segment(cr, s1);
+ cairo_stroke(cr);
+
+
+ draw_label(cr, l1, "L");
+ draw_label(cr, r1, "R");
+ draw_label(cr, s1, "S");
+ cairo_stroke(cr);
+
+ std::string label("A");
+ cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.5);
+ for (auto & i : ip)
+ {
+ draw_handle(cr, i);
+ draw_text(cr, i+op, label.c_str());
+ label[0] += 1;
+ }
+ cairo_stroke(cr);
+
+ }
+
+
+ void init_coefficients()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+
+ Line l(p1.pos, p2.pos);
+ std::vector<double> coeff = l.coefficients();
+ sliders.emplace_back(-1, 1, 0, coeff[0], "A");
+ sliders.emplace_back(-1, 1, 0, coeff[1], "B");
+ sliders.emplace_back(-500, 500, 0, coeff[2], "C");
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&(sliders[A_COEFF_SLIDER]));
+ handles.push_back(&(sliders[B_COEFF_SLIDER]));
+ handles.push_back(&(sliders[C_COEFF_SLIDER]));
+ }
+
+ void draw_coefficients(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_coefficients_ctrl_geom(cr, notify, width, height);
+
+ Line l1(p1.pos, p2.pos);
+ std::vector<double> coeff1 = l1.coefficients();
+ Line l2(sliders[A_COEFF_SLIDER].value(),
+ sliders[B_COEFF_SLIDER].value(),
+ sliders[C_COEFF_SLIDER].value());
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.8);
+ draw_line(cr, l1);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.4);
+ draw_line(cr, l2);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ draw_label(cr, p1, "P");
+ draw_label(cr, p2, "Q");
+ //draw_label(cr, l1, "L(P,Q)");
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0);
+ draw_label(cr, l2, "L(A, B, C)");
+ cairo_stroke(cr);
+
+ *notify << " L(P,Q): a = " << coeff1[0]
+ << ", b = " << coeff1[1]
+ << ", c = " << coeff1[2] << std::endl;
+
+ }
+
+ void init_segment_inside()
+ {
+ init_common();
+ p1.pos = Point(200, 300);
+ p2.pos = Point(400, 200);
+ p3.pos = Point(250, 200);
+ p4.pos = Point(350, 300);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ }
+
+ void draw_segment_inside(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ Line l(p1.pos, p2.pos);
+ Rect r(p3.pos, p4.pos);
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_set_line_width(cr, 0.5);
+ draw_line(cr, l);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0, 0, 1, 1);
+ cairo_rectangle(cr, r);
+ cairo_stroke(cr);
+
+ std::optional<LineSegment> seg = l.clip(r);
+ if (seg) {
+ cairo_set_source_rgba(cr, 1, 0, 0, 1);
+ draw_line_seg(cr, seg->initialPoint(), seg->finalPoint());
+ cairo_stroke(cr);
+ }
+ }
+
+
+ void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ }
+ }
+
+ void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180);
+ }
+ }
+
+ void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400);
+ sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400);
+ sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400);
+ }
+ }
+
+
+ void draw_segment(cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+ }
+
+ void draw_segment(cairo_t* cr, Point const& p1, double angle, double length)
+ {
+ Point p2;
+ p2[X] = length * std::cos(angle);
+ p2[Y] = length * std::sin(angle);
+ p2 += p1;
+ draw_segment(cr, p1, p2);
+ }
+
+ void draw_segment(cairo_t* cr, LineSegment const& ls)
+ {
+ draw_segment(cr, ls[0], ls[1]);
+ }
+
+ void draw_ray(cairo_t* cr, Ray const& r)
+ {
+ double angle = r.angle();
+ draw_segment(cr, r.origin(), angle, m_length);
+ }
+
+ void draw_line(cairo_t* cr, Line const& l)
+ {
+ double angle = l.angle();
+ draw_segment(cr, l.origin(), angle, m_length);
+ draw_segment(cr, l.origin(), angle, -m_length);
+ }
+
+ void draw_label(cairo_t* cr, PointHandle const& ph, const char* label)
+ {
+ draw_text(cr, ph.pos+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Line const& l, const char* label)
+ {
+ draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label);
+ }
+
+ void draw_label(cairo_t* cr, LineSegment const& ls, const char* label)
+ {
+ draw_text(cr, middle_point(ls[0], ls[1])+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Ray const& r, const char* label)
+ {
+ Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30)));
+ if (L2(r.origin() - prj) < 100)
+ {
+ prj = r.origin() + 100*r.vector();
+ }
+ draw_text(cr, prj+op, label);
+ }
+
+ void init_menu()
+ {
+ handles.clear();
+ sliders.clear();
+ toggles.clear();
+ }
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/ )
+ {
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &LineToy::draw_menu;
+ break;
+ case 'B':
+ init_create();
+ draw_f = &LineToy::draw_create;
+ break;
+ case 'C':
+ init_projection();
+ draw_f = &LineToy::draw_projection;
+ break;
+ case 'D':
+ init_ortho();
+ draw_f = &LineToy::draw_ortho;
+ break;
+ case 'E':
+ init_distance();
+ draw_f = &LineToy::draw_distance;
+ break;
+ case 'F':
+ init_position();
+ draw_f = &LineToy::draw_position;
+ break;
+ case 'G':
+ init_seg_bisec();
+ draw_f = &LineToy::draw_seg_bisec;
+ break;
+ case 'H':
+ init_angle_bisec();
+ draw_f = &LineToy::draw_angle_bisec;
+ break;
+ case 'I':
+ init_collinear();
+ draw_f = &LineToy::draw_collinear;
+ break;
+ case 'J':
+ init_intersections();
+ draw_f = &LineToy::draw_intersections;
+ break;
+ case 'K':
+ init_coefficients();
+ draw_f = &LineToy::draw_coefficients;
+ break;
+ case 'L':
+ init_segment_inside();
+ draw_f = &LineToy::draw_segment_inside;
+ }
+ redraw();
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ LineToy()
+ {
+ op = Point(5,5);
+ }
+
+ private:
+ typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ PointHandle p1, p2, p3, p4, p5, p6, O;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ Point op;
+ double m_width, m_height, m_length;
+
+}; // end class LineToy
+
+
+const char* LineToy::menu_items[] =
+{
+ "show this menu",
+ "line generation",
+ "projection on a line",
+ "make orthogonal/parallel",
+ "distance",
+ "position",
+ "segment bisector",
+ "angle bisector",
+ "collinear",
+ "intersection",
+ "coefficients",
+ "segment inside"
+};
+
+const char LineToy::keys[] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new LineToy());
+ 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/src/toys/load-svgd.cpp b/src/toys/load-svgd.cpp
new file mode 100644
index 0000000..68b1495
--- /dev/null
+++ b/src/toys/load-svgd.cpp
@@ -0,0 +1,76 @@
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/cairo-path-sink.h>
+#include <2geom/svg-path-writer.h>
+
+#include <cstdlib>
+
+using namespace Geom;
+
+/**
+ * @brief SVG path data loading toy.
+ *
+ * A very simple toy that loads a file containing raw SVG path data
+ * and displays it scaled so that it fits inside the window.
+ *
+ * Use this toy to see what the path data looks like without
+ * pasting it into the d= attribute of a path in Inkscape.
+ */
+class LoadSVGD: public Toy {
+ PathVector pv;
+ OptRect bounds;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ Rect viewport(Point(10, 10), Point(width-10, height-10));
+ PathVector res = pv * bounds->transformTo(viewport, Aspect(ALIGN_XMID_YMID));
+
+ CairoPathSink sink(cr);
+ sink.feed(res);
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ cairo_fill_preserve(cr);
+ cairo_set_line_width(cr, 1);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_set_source_rgb(cr, 0,0,0);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ public:
+ LoadSVGD() {}
+
+ void first_time(int argc, char** argv) override {
+ const char *path_b_name="star.svgd";
+ if (argc > 1)
+ path_b_name = argv[1];
+ pv = read_svgd(path_b_name);
+ bounds = pv.boundsExact();
+ if (!bounds) {
+ std::cerr << "Empty path, aborting" << std::endl;
+ std::exit(1);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ LoadSVGD x;
+ init(argc, argv, &x);
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/load-svgd.py b/src/toys/load-svgd.py
new file mode 100644
index 0000000..26bcef3
--- /dev/null
+++ b/src/toys/load-svgd.py
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+
+import py2geom
+import toyframework
+import random,gtk
+from py2geom_glue import *
+
+def cairo_region(cr, r):
+ cr.save()
+ cr.set_source_rgba(0, 0, 0, 1);
+ if not r.isFill():
+ pass#cr.set_dash([]1, 0)
+ cairo_path(cr, r)
+ cr.fill()
+ cr.restore()
+
+def cairo_regions(cr, p):
+ for j in p:
+ cairo_region(cr, j)
+
+def cairo_shape(cr, s):
+ cairo_regions(cr, s.getContent())
+
+
+
+def cleanup(ps):
+ pw = py2geom.paths_to_pw(ps)
+ centre, area = py2geom.centroid(pw)
+ if(area > 1):
+ return py2geom.sanitize(ps) * py2geom.Translate(-centre)
+ else:
+ return py2geom.sanitize(ps)
+
+class LoadSVGD(toyframework.Toy):
+ def __init__(self):
+ toyframework.Toy.__init__(self)
+ self.bs = []
+ self.offset_handle = toyframework.PointHandle(0,0)
+ self.handles.append(self.offset_handle)
+ def draw(self, cr, pos, save):
+ t = py2geom.Translate(*self.offset_handle.pos)
+ #self.paths_b[0] * t
+ m = py2geom.Matrix()
+ m.setIdentity()
+ bst = self.bs * (m * t)
+ #bt = Region(b * t, b.isFill())
+
+ cr.set_line_width(1)
+
+ cairo_shape(cr, bst)
+
+ toyframework.Toy.draw(self, cr, pos, save)
+
+ def first_time(self, argv):
+ path_b_name="star.svgd"
+ if len(argv) > 1:
+ path_b_name = argv[1]
+ self.paths_b = py2geom.read_svgd(path_b_name)
+
+ bounds = py2geom.bounds_exact(self.paths_b)
+ self.offset_handle.pos = bounds.midpoint() - bounds.corner(0)
+
+ self.bs = cleanup(self.paths_b)
+t = LoadSVGD()
+import sys
+
+toyframework.init(sys.argv, t, 500, 500)
+
diff --git a/src/toys/lpe-framework.cpp b/src/toys/lpe-framework.cpp
new file mode 100644
index 0000000..19f7267
--- /dev/null
+++ b/src/toys/lpe-framework.cpp
@@ -0,0 +1,128 @@
+/*
+ * A framework for writing an Inkscape Live Path Effect (LPE) toy.
+ *
+ * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * 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 <string.h>
+#include <stdint.h>
+#include <toys/lpe-framework.h>
+
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/affine.h>
+#include <2geom/pathvector.h>
+
+#define LPE_CONVERSION_TOLERANCE 0.01
+
+/**
+ * When the input path handles are moved, this method is called to re-execute the LPE and draw the result.
+ */
+void LPEToy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream)
+{
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > pwd2(curve_handle.asBezier());
+ PathVector A = Geom::path_from_piecewise( pwd2, LPE_CONVERSION_TOLERANCE);
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 1., 0.0, 0., 1);
+ cairo_path(cr, A);
+ cairo_stroke(cr);
+
+ // perform the effect:
+ PathVector B = doEffect_path(A);
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.0, 0., 1);
+ cairo_path(cr, B);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+}
+
+/**
+ * Initializes the LPE toy, and sets a simple default input path.
+ */
+LPEToy::LPEToy(){
+ if(handles.empty()) {
+ handles.push_back(&curve_handle);
+ for(unsigned i = 0; i < 4; i++) {
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ }
+}
+
+/*
+ * Here be the doEffect function chain: (this is copied code from Inkscape)
+ */
+Geom::PathVector
+LPEToy::doEffect_path (Geom::PathVector const &path_in)
+{
+ Geom::PathVector path_out;
+
+ if ( !concatenate_before_pwd2 ) {
+ // default behavior
+ for (const auto & i : path_in) {
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = i.toPwSb();
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
+ Geom::PathVector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
+ // add the output path vector to the already accumulated vector:
+ for (const auto & j : path) {
+ path_out.push_back(j);
+ }
+ }
+ } else {
+ // concatenate the path into possibly discontinuous pwd2
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
+ for (const auto & i : path_in) {
+ pwd2_in.concat( i.toPwSb() );
+ }
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
+ path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
+ }
+
+ return path_out;
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+LPEToy::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
+{
+ // default effect does nothing
+ return pwd2_in;
+}
+
+
+
+/*
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/lpe-test.cpp b/src/toys/lpe-test.cpp
new file mode 100644
index 0000000..ff397c0
--- /dev/null
+++ b/src/toys/lpe-test.cpp
@@ -0,0 +1,93 @@
+/**
+ * \file lpe-test.cpp
+ * \brief Example file showing how to write an Inkscape Live Path Effect toy.
+ */
+/*
+ * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * 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 <toys/lpe-framework.h>
+
+// This is usually very bad practice: don't do it in your LPE
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+class LPETest: public LPEToy {
+public:
+ LPETest() {
+ concatenate_before_pwd2 = false;
+ }
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> >
+ doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) override
+ {
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > pwd2_out = pwd2_in;
+
+ Point vector(50,100);
+ // generate extrusion bottom: (just a copy of original path, displaced a bit)
+ pwd2_out.concat( pwd2_in + vector );
+
+ // generate connecting lines (the 'sides' of the extrusion)
+ Path path(Point(0.,0.));
+ path.appendNew<Geom::LineSegment>( vector );
+ Piecewise<D2<SBasis> > connector = path.toPwSb();
+ // connecting lines should be put at cusps
+ Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
+ std::vector<double> cusps; // = roots(deriv);
+ for (double cusp : cusps) {
+ pwd2_out.concat( connector + pwd2_in.valueAt(cusp) );
+ }
+ // connecting lines should be put where the tangent of the path equals the extrude_vector in direction
+ std::vector<double> rts = roots(dot(deriv, rot90(vector)));
+ for (double rt : rts) {
+ pwd2_out.concat( connector + pwd2_in.valueAt(rt) );
+ }
+
+ return pwd2_out;
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new LPETest);
+ 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/match-curve.cpp b/src/toys/match-curve.cpp
new file mode 100644
index 0000000..60e81a5
--- /dev/null
+++ b/src/toys/match-curve.cpp
@@ -0,0 +1,162 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define ZROOTS_TEST 0
+#if ZROOTS_TEST
+#include <2geom/zroots.c>
+#endif
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+//#define HAVE_GSL
+
+template <typename T>
+void shift(T &a, T &b, T const &c) {
+ a = b;
+ b = c;
+}
+template <typename T>
+void shift(T &a, T &b, T &c, T const &d) {
+ a = b;
+ b = c;
+ c = d;
+}
+
+extern unsigned total_steps, total_subs;
+
+class MatchCurve: public Toy {
+public:
+ double timer_precision;
+ double units;
+ PointSetHandle psh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgb(cr, 0,0,0);
+ std::vector<Geom::Point> trans;
+ trans.resize(psh.size());
+ for(unsigned i = 0; i < psh.size(); i++) {
+ trans[i] = psh.pts[i] - Geom::Point(0, 3*width/4);
+ }
+
+ std::vector<double> solutions;
+
+ D2<SBasis> test_sb = psh.asBezier();
+
+
+ D2<SBasis> B = psh.asBezier();
+ Geom::Path pb;
+ pb.append(B);
+ pb.close(false);
+ cairo_path(cr, pb);
+ cairo_stroke(cr);
+
+ D2<SBasis> m;
+ D2<SBasis> dB = derivative(B);
+ D2<SBasis> ddB = derivative(dB);
+ D2<SBasis> dddB = derivative(ddB);
+
+ Geom::Point pt = B(0);
+ Geom::Point tang = dB(0);
+ Geom::Point dtang = ddB(0);
+ Geom::Point ddtang = dddB(0);
+ for(int dim = 0; dim < 2; dim++) {
+ m[dim] = Linear(pt[dim],pt[dim]+tang[dim]);
+ m[dim] += Linear(0, 1)*Linear(0, 1*dtang[dim])/2;
+ m[dim] += Linear(0, 1)*Linear(0, 1)*Linear(0, ddtang[dim])/6;
+ }
+
+ double lo = 0, hi = 1;
+ double eps = 5;
+ while(hi - lo > 0.0001) {
+ double mid = (hi + lo)/2;
+ //double Bmid = (Bhi + Blo)/2;
+
+ m = truncate(compose(B, Linear(0, mid)), 2);
+ // perform golden section search
+ double best_f = 0, best_x = 1;
+ for(int n = 2; n < 4; n++) {
+ Geom::Point m_pt = m(double(n)/6);
+ double x0 = 0, x3 = 1.; // just a guess!
+ const double R = 0.61803399;
+ const double C = 1 - R;
+ double x1 = C*x0 + R*x3;
+ double x2 = C*x1 + R*x3;
+ double f1 = Geom::distance(m_pt, B(x1));
+ double f2 = Geom::distance(m_pt, B(x2));
+ while(fabs(x3 - x0) > 1e-3*(fabs(x1) + fabs(x2))) {
+ if(f2 < f1) {
+ shift(x0, x1, x2, R*x1 + C*x3);
+ shift(f1, f2, Geom::distance(m_pt, B(x2)));
+ } else {
+ shift(x3, x2, x1, R*x2 + C*x0);
+ shift(f2, f1, Geom::distance(m_pt, B(x2)));
+ }
+ std::cout << x0 << ","
+ << x1 << ","
+ << x2 << ","
+ << x3 << ","
+ << std::endl;
+ }
+ if(f2 < f1) {
+ f1 = f2;
+ x1 = x2;
+ }
+ if(f1 > best_f) {
+ best_f = f1;
+ best_x = x1;
+ }
+ }
+ std::cout << mid << ":" << best_x << "->" << best_f << std::endl;
+ //draw_cross(cr, point_at(B, x1));
+
+ if(best_f > eps) {
+ hi = mid;
+ } else {
+ lo = mid;
+ }
+ }
+ std::cout << std::endl;
+ //draw_cross(cr, point_at(B, hi));
+ draw_circ(cr, m(hi));
+ {
+ Geom::Path pb;
+ pb.append(m);
+ pb.close(false);
+ cairo_path(cr, pb);
+ }
+
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ MatchCurve() : timer_precision(0.1), units(1e6) // microseconds
+ {
+ handles.push_back(&psh);
+ for(int i = 0; i < 6; i++)
+ psh.push_back(uniform()*400, uniform()*400);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new MatchCurve());
+
+ 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/src/toys/mesh-grad.cpp b/src/toys/mesh-grad.cpp
new file mode 100644
index 0000000..c71f5f0
--- /dev/null
+++ b/src/toys/mesh-grad.cpp
@@ -0,0 +1,134 @@
+/**
+ * Generate approximate mesh gradients for blurring technique suggested by bbyak.
+ * (njh)
+ */
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+const double u_subs = 5,
+ v_subs = 5,
+ fudge = .01;
+
+const double inv_u_subs = 1 / u_subs,
+ inv_v_subs = 1 / v_subs;
+
+class Sb2d2: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2d2() {
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ D2<SBasis2d> sb2;
+ for(unsigned dim = 0; dim < 2; dim++) {
+ sb2[dim].us = 2;
+ sb2[dim].vs = 2;
+ const int depth = sb2[dim].us*sb2[dim].vs;
+ sb2[dim].resize(depth, Linear2d(0));
+ }
+ Geom::Point dir(1,-2);
+ if(hand.pts.empty()) {
+ for(unsigned vi = 0; vi < sb2[0].vs; vi++)
+ for(unsigned ui = 0; ui < sb2[0].us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++)
+ hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+
+ }
+
+ for(int dim = 0; dim < 2; dim++) {
+ Geom::Point dir(0,0);
+ dir[dim] = 1;
+ for(unsigned vi = 0; vi < sb2[dim].vs; vi++)
+ for(unsigned ui = 0; ui < sb2[dim].us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++) {
+ unsigned corner = iu + 2*iv;
+ unsigned i = ui + vi*sb2[dim].us;
+ Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+ if(vi == 0 && ui == 0) {
+ base = Geom::Point(width/4., width/4.);
+ }
+ double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir);
+ sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi);
+ }
+ }
+ cairo_d2_sb2d(cr, sb2, dir*0.1, width);
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.5);
+ cairo_stroke(cr);
+ for(unsigned vi = 0; vi < v_subs; vi++) {
+ double tv = vi * inv_v_subs;
+ for(unsigned ui = 0; ui < u_subs; ui++) {
+ double tu = ui * inv_u_subs;
+
+ Geom::Path pb;
+ D2<SBasis> B;
+ D2<SBasis> tB;
+
+ B[0] = Linear(tu-fudge, tu+fudge + inv_u_subs );
+ B[1] = Linear(tv-fudge, tv-fudge);
+ tB = compose_each(sb2, B);
+ tB = tB*(width/2) + Geom::Point(width/4, width/4);
+ pb.append(tB);
+
+ B[0] = Linear(tu+fudge + inv_u_subs , tu+fudge + inv_u_subs);
+ B[1] = Linear(tv-fudge, tv+fudge + inv_v_subs);
+ tB = compose_each(sb2, B);
+ tB = tB*(width/2) + Geom::Point(width/4, width/4);
+ pb.append(tB);
+
+ B[0] = Linear(tu+fudge + inv_u_subs, tu-fudge);
+ B[1] = Linear(tv+fudge + inv_v_subs, tv+fudge + inv_v_subs);
+ tB = compose_each(sb2, B);
+ tB = tB*(width/2) + Geom::Point(width/4, width/4);
+ pb.append(tB);
+
+ B[0] = Linear(tu-fudge, tu-fudge);
+ B[1] = Linear(tv+fudge + inv_v_subs, tv-fudge);
+ tB = compose_each(sb2, B);
+ tB = tB*(width/2) + Geom::Point(width/4, width/4);
+ pb.append(tB);
+
+ cairo_path(cr, pb);
+
+ //std::cout << pb.peek().end() - pb.peek().begin() << std::endl;
+ cairo_set_source_rgba (cr, tu, tv, 0, 1);
+ cairo_fill(cr);
+ }
+ }
+ //*notify << "bo = " << sb2.index(0,0);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2d2);
+ 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/src/toys/metro.cpp b/src/toys/metro.cpp
new file mode 100644
index 0000000..5d2804c
--- /dev/null
+++ b/src/toys/metro.cpp
@@ -0,0 +1,922 @@
+/** Generates approximate metromap lines
+ * Copyright njh
+ * Copyright Tim Dwyer
+ * Published in ISVC 2008, Las Vegas, Nevada, USA
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+
+#include <gtk/gtk.h>
+#include <cassert>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <string>
+#include <map>
+#include <cairo-pdf.h>
+#include <2geom/point.h>
+#include <2geom/geom.h>
+#include <toys/toy-framework-2.h>
+
+using std::string;
+using std::vector;
+using std::pair;
+using std::make_pair;
+using std::ifstream;
+using std::map;
+using std::cout;
+using std::endl;
+using namespace Geom;
+
+vector<vector<Point> > paths;
+
+class sufficient_stats{
+public:
+ double Sx, Sy, Sxx, Syy, Sxy;
+ double n;
+
+ sufficient_stats() : Sx(0), Sy(0), Sxx(0), Syy(0), Sxy(0), n(0) {}
+ void
+ operator+=(Point p) {
+ Sx += p[0];
+ Sy += p[1];
+ Sxx += p[0]*p[0];
+ Syy += p[1]*p[1];
+ Sxy += p[0]*p[1];
+ n += 1.0;
+ }
+ void
+ operator-=(Point p) {
+ Sx -= p[0];
+ Sy -= p[1];
+ Sxx -= p[0]*p[0];
+ Syy -= p[1]*p[1];
+ Sxy -= p[0]*p[1];
+ n -= 1.0;
+ }
+ /*** What is the best regression we can do? . */
+ Point best_normal() {
+ return rot90(unit_vector(Point(n*Sxx - Sx*Sx,
+ n*Sxy - Sx*Sy)));
+ }
+ /*** Compute the best line for the points, given normal. */
+ double best_line(Point normal, const double dist,
+ double & mean) {
+ mean = (normal[0]*Sx + normal[1]*Sy);
+ return normal[0]*normal[0]*Sxx
+ + normal[1]*normal[1]*Syy
+ + 2*normal[0]*normal[1]*Sxy
+ - 2*dist*mean + n*dist*dist;
+ }
+ /*** Returns the index to the angle in angles that has the line of best fit
+ * passing through mean */
+ unsigned best_schematised_line(vector<Point>& angles, Point p,
+ double & /*mean*/, double & cost) {
+ cost = DBL_MAX;
+ unsigned bestAngle = 0;
+ for(unsigned i=0;i<angles.size();i++) {
+ Point n = unit_vector(rot90(angles[i]));
+ double dist = dot(n, p);
+ double mean;
+ double bl = best_line(n, dist, mean);
+ if(bl < cost) {
+ cost = bl;
+ bestAngle = i;
+ }
+ }
+ return bestAngle;
+ }
+ /*** Compute the best line for the points, given normal. */
+ double best_angled_line(Point normal,
+ double & mean) {
+ mean = (normal[0]*Sx + normal[1]*Sy);
+ double dist = mean/n;
+ return normal[0]*normal[0]*Sxx
+ + normal[1]*normal[1]*Syy
+ + 2*normal[0]*normal[1]*Sxy
+ - 2*dist*mean + n*dist*dist;
+ }
+};
+
+sufficient_stats
+operator+(sufficient_stats const & a, sufficient_stats const &b) {
+ sufficient_stats ss;
+ ss.Sx = a.Sx + b.Sx;
+ ss.Sy = a.Sy + b.Sy;
+ ss.Sxx = a.Sxx + b.Sxx;
+ ss.Sxy = a.Sxy + b.Sxy;
+ ss.Syy = a.Syy + b.Syy;
+ ss.n = a.n + b.n;
+ return ss;
+}
+
+sufficient_stats
+operator-(sufficient_stats const & a, sufficient_stats const &b) {
+ sufficient_stats ss;
+ ss.Sx = a.Sx - b.Sx;
+ ss.Sy = a.Sy - b.Sy;
+ ss.Sxx = a.Sxx - b.Sxx;
+ ss.Sxy = a.Sxy - b.Sxy;
+ ss.Syy = a.Syy - b.Syy;
+ ss.n = a.n - b.n;
+ return ss;
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const sufficient_stats &s) {
+ out_file << "Sx: " << s.Sx
+ << "Sy: " << s.Sy
+ << "Sxx: " << s.Sxx
+ << "Sxy: " << s.Sxy
+ << "Syy: " << s.Syy
+ << "n: " << s.n;
+ return out_file;
+}
+
+
+
+class fit{
+public:
+ vector<Point> input;
+ vector<Point> solution;
+
+ vector<pair<Point,Point> > lines;
+ vector<int> thickness;
+
+ void
+ draw(cairo_t* cr) {
+ /*
+ if(solution.size() > 1) {
+ //cairo_set_line_width (cr, 1);
+ cairo_move_to(cr, solution[0]);
+ for(unsigned i = 1; i < solution.size(); i++) {
+ cairo_line_to(cr, solution[i]);
+ }
+ }
+ */
+ //cairo_stroke(cr);
+ for(unsigned i = 0;i<lines.size();i++) {
+ if(thickness.size()>i) {
+ cairo_set_line_width (cr, thickness[i]);
+ }
+ cairo_move_to(cr, lines[i].first);
+ cairo_line_to(cr, lines[i].second);
+ cairo_stroke(cr);
+ }
+ }
+
+ void endpoints() {
+ solution.push_back(input[0]);
+ solution.push_back(input.back());
+ }
+
+ void arbitrary();
+ void linear_reg();
+
+ // sufficient stats from start to each point
+ vector<sufficient_stats> ac_ss;
+
+ /*** Compute the least squares error for a line between two points on the line. */
+ double measure(unsigned from, unsigned to, double & mean) {
+ sufficient_stats ss = ac_ss[to+1] - ac_ss[from];
+
+ Point n = unit_vector(rot90(input[to] - input[from]));
+ double dist = dot(n, input[from]);
+ return ss.best_line(n, dist, mean);
+ }
+
+ /*** Compute the best line for the points, given normal. */
+ double best_angled_line(unsigned from, unsigned to,
+ Point n,
+ double & mean) {
+ sufficient_stats ss = ac_ss[to+1] - ac_ss[from];
+ return ss.best_angled_line(n, mean);
+ }
+
+ double simple_measure(unsigned from, unsigned to, double & mean) {
+ Point n = unit_vector(rot90(input[to] - input[from]));
+ double dist = dot(n, input[from]); // distance from origin
+ double error = 0;
+ mean = 0;
+ for(unsigned l = from; l <= to; l++) {
+ double d = dot(input[l], n) - dist;
+ mean += dot(input[l], n);
+ error += d*d;
+ }
+ return error;
+ }
+
+ void simple_dp();
+ void C_simple_dp();
+
+ void C_endpoint();
+
+ vector<Geom::Point> angles;
+ fit(vector<Point> const & in)
+ :input(in) {
+ /*
+ Geom::Point as[] = {Point(0,1), Point(1,0), Point(1,1), Point(1,-1)};
+ for(unsigned c = 0; c < 4; c++) {
+ angles.push_back(unit_vector(as[c]));
+ }
+ */
+ sufficient_stats ss;
+ ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0;
+ ac_ss.push_back(ss);
+ for(auto & l : input) {
+ ss += l;
+ ac_ss.push_back(ss);
+ }
+ }
+
+ class block{
+ public:
+ unsigned next;
+ unsigned angle;
+ sufficient_stats ss;
+ double cost;
+ };
+ vector<block> blocks;
+ void test();
+ void merging_version();
+ void schematised_merging(unsigned number_of_directions);
+
+ double get_block_line(block& b, Point& d, Point& n, Point& c) {
+ n = unit_vector(rot90(d));
+ c = Point(b.ss.Sx/b.ss.n,b.ss.Sy/b.ss.n);
+ return 0;
+ }
+};
+
+class build_bounds{
+public:
+ int total_n;
+ Point starting[2];
+ Rect combined;
+ void add_point(Point const &P) {
+ if(total_n < 2) {
+ starting[total_n] = P;
+ total_n += 1;
+ if(total_n == 2)
+ combined = Rect(starting[0], starting[1]);
+ } else {
+ combined.expandTo(P);
+ total_n += 1;
+ }
+ }
+ OptRect result() const {
+ if(total_n > 1)
+ return combined;
+ return OptRect();
+ }
+ build_bounds() : total_n(0) {}
+};
+
+/**
+ * returns a point which is portionally between topleft and bottomright in the same way that p was
+ * in src.
+ */
+Point map_point(Point p, Rect src, Point topleft, Point bottomright) {
+ p -= src.min();
+ p[0] /= src[0].extent();
+ p[1] /= src[1].extent();
+ //cout << p << endl;
+ return Point(topleft[0]*(1-p[0]) + bottomright[0]*p[0],
+ topleft[1]*(1-p[1]) + bottomright[1]*p[1]);
+}
+
+void parse_data(vector<vector<Point> >& paths,
+ string location_file_name,
+ string path_file_name) {
+ ifstream location_file(location_file_name.c_str()),
+ path_file(path_file_name.c_str());
+ string id,sx,sy;
+ map<string,Point> idlookup;
+ build_bounds bld_bounds;
+ while (getline(location_file,id,','))
+ {
+ getline(location_file,sx,',');
+ getline(location_file,sy,'\n');
+ char *e;
+ // negative for coordinate system
+ Point p(strtod(sx.c_str(),&e), strtod(sy.c_str(),&e));
+ //cout << id << p << endl;
+ idlookup[id] = p;
+ }
+
+
+ string l;
+ while (getline(path_file,l,'\n')) {
+ vector<Point> ps;
+ if(l.size() < 2) continue; // skip blank lines
+ if(l.find(":",0)!=string::npos) continue; // skip labels
+ string::size_type p=0,q;
+ while((q=l.find(",",p))!=string::npos || (p < l.size() && (q = l.size()))) {
+ id = l.substr(p,q-p);
+ //cout << id << endl;
+ Point pt = 2*idlookup[id];
+ //cout << pt << endl;
+ bld_bounds.add_point(pt);
+ ps.push_back(pt);
+ p=q+1;
+ }
+ paths.push_back(ps);
+ //cout << "*******************************************" << endl;
+ }
+ Rect bounds = *bld_bounds.result();
+ //cout << bounds.min() << " - " << bounds.max() << endl;
+ for(auto & path : paths) {
+ for(auto & j : path) {
+ j = map_point(j, bounds,
+ Point(0,512), Point(512*bounds[0].extent()/bounds[1].extent(),0));
+ }
+ }
+ /*
+ for(map<string,Point>::iterator it = idlookup.begin();
+ it != idlookup.end(); it++) {
+ (*it).second = map_point((*it).second, bounds,
+ Point(0,0), Point(100,100));
+ //Point(0, 512), Point(512,0));
+ cout << (*it).first << ":" << (*it).second << endl;
+ }*/
+ /*
+ unsigned biggest = 0, biggest_i;
+ for(unsigned i=0;i<paths.size();i++) {
+ vector<Point> ps=paths[i];
+ if(ps.size()>biggest) {
+ biggest_i=i;
+ biggest = ps.size();
+ }
+ for(unsigned j=0;j<ps.size();j++) {
+ double x=ps[j][0], y=ps[j][1];
+ cout << "(" << x << "," << y << ")" << ",";
+ }
+ cout << endl;
+ }
+ */
+}
+
+void extremePoints(vector<Point> const & pts, Point const & dir,
+ Point & min, Point & max) {
+ double minProj = DBL_MAX, maxProj = -DBL_MAX;
+ for(auto pt : pts) {
+ double p = dot(pt,dir);
+ if(p < minProj) {
+ minProj = p;
+ min = pt;
+ }
+ if(p > maxProj) {
+ maxProj = p;
+ max = pt;
+ }
+ }
+}
+
+void fit::test() {
+ sufficient_stats ss;
+ const unsigned N = input.size();
+ for(unsigned i = 0; i < N; i++) {
+ ss += input[i];
+ }
+ double best_bl = DBL_MAX;
+ unsigned best = 0;
+ for(unsigned i=0;i<angles.size();i++) {
+ Point n = unit_vector(rot90(angles[i]));
+ double dist = dot(n, input[0]);
+ double mean;
+ double bl = ss.best_line(n, dist, mean);
+ if(bl < best_bl) {
+ best = i;
+ best_bl = bl;
+ }
+ mean/=N;
+ Point d = angles[i];
+ Point a = mean*n;
+ Point min, max;
+ extremePoints(input,d,min,max);
+ Point b = dot(min,d)*d;
+ Point c = dot(max,d)*d;
+ Point start = a+b;
+ Point end = a+c;
+ lines.emplace_back(start,end);
+ thickness.push_back(1);
+ }
+ thickness[best] = 4;
+}
+
+void fit::schematised_merging(unsigned number_of_directions) {
+ const double link_cost = 0;
+ const unsigned N = input.size()-1;
+ blocks.resize(N);
+ for(unsigned i = 0; i<number_of_directions ; i++) {
+ double t = M_PI*i/float(number_of_directions);
+ angles.emplace_back(cos(t),sin(t));
+ }
+ // pairs
+ for(unsigned i = 0; i < N; i++) {
+ block b;
+ sufficient_stats ss;
+ ss += input[i];
+ ss += input[i+1];
+ b.ss = ss;
+ double mean, newcost;
+ b.angle = ss.best_schematised_line(angles, input[i], mean, newcost);
+ b.cost = link_cost + newcost;
+ b.next = i+1;
+ blocks[i] = b;
+ //std::cout << ss
+ //<< std::endl;
+ }
+
+ // merge(a,b)
+ while(N>1)
+ {
+ block best_block;
+ unsigned best_idx = 0;
+ double best = 0;
+ unsigned beg = 0;
+ unsigned middle = blocks[beg].next;
+ unsigned end = blocks[middle].next;
+ while(middle < N) {
+ sufficient_stats ss = blocks[beg].ss + blocks[middle].ss;
+ //ss -= input[middle];
+ double mean, newcost;
+ unsigned bestAngle = ss.best_schematised_line(angles, input[beg], mean, newcost);
+ double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost
+ + newcost;
+ /*std::cout << beg << ", "
+ << middle << ", "
+ << end << ", "
+ << deltaCost <<"; "
+ << newcost <<"; "
+ << mean << ": "
+ << ss
+ << std::endl;*/
+ //if(deltaCost < best) {
+ if(blocks[beg].angle==blocks[middle].angle) {
+ best = deltaCost;
+ best = -1;
+ best_idx = beg;
+ best_block.ss = ss;
+ best_block.cost = newcost;
+ best_block.next = end;
+ best_block.angle = bestAngle;
+ }
+ beg = middle;
+ middle = end;
+ end = blocks[end].next;
+ }
+ if(best < 0)
+ blocks[best_idx] = best_block;
+ else // no improvement possible
+ break;
+ }
+ {
+ solution.resize(0); // empty list
+ unsigned beg = 0;
+ unsigned prev = 0;
+ while(beg < N) {
+ block b = blocks[beg];
+ {
+ Point n, c;
+ Point n1, c1;
+ Point d = angles[b.angle];
+ get_block_line(b,d,n,c);
+ Point start = c, end = c+10*angles[b.angle];
+ Line ln = Line::from_normal_distance(n, dot(c,n));
+ if(beg==0) {
+ //start = intersection of b.line and
+ // line through input[0] orthogonal to b.line
+ OptCrossing c = intersection(ln,
+ Line::from_normal_distance(d, dot(d,input[0])));
+ assert(c);
+ start = ln.pointAt(c->ta);
+ //line_intersection(n, dot(c,n), d, dot(d,input[0]), start);
+ } else {
+ //start = intersection of b.line and blocks[prev].line
+ block p = blocks[prev];
+ if(b.angle!=p.angle) {
+ get_block_line(p,angles[p.angle],n1,c1);
+ //line_intersection(n, dot(c,n), n1, dot(c1,n1), start);
+ OptCrossing c = intersection(ln,
+ Line::from_normal_distance(n1, dot(c1,n1)));
+ assert(c);
+ start = ln.pointAt(c->ta);
+ }
+ }
+
+ if (b.next < N) {
+ //end = intersection of b.line and blocks[b.next].line
+ block next = blocks[b.next];
+ if(b.angle!=next.angle) {
+ get_block_line(next,angles[next.angle],n1,c1);
+ //line_intersection(n, dot(c,n), n1, dot(c1,n1), end);
+ OptCrossing c = intersection(ln,
+ Line::from_normal_distance(n1, dot(c1,n1)));
+ assert(c);
+ end = ln.pointAt(c->ta);
+ }
+ } else {
+ //end = intersection of b.line and
+ // line through input[N-1] orthogonal to b.line
+ //line_intersection(n, dot(c,n), d, dot(d,input[N]), end);
+ OptCrossing c = intersection(ln,
+ Line::from_normal_distance(d, dot(d,input[N])));
+ assert(c);
+ end = ln.pointAt(c->ta);
+ }
+ lines.emplace_back(start,end);
+ }
+ prev = beg;
+ beg = b.next;
+ }
+ }
+}
+void fit::merging_version() {
+ const double link_cost = 100;
+ const unsigned N = input.size();
+ blocks.resize(N);
+ // pairs
+ for(unsigned i = 0; i < N; i++) {
+ block b;
+ sufficient_stats ss;
+ ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0;
+ ss.n = 0;
+ ss += input[i];
+ ss += input[i+1];
+ b.ss = ss;
+ b.cost = link_cost;
+ b.next = i+1;
+ blocks[i] = b;
+ //std::cout << ss
+ //<< std::endl;
+ }
+
+ // merge(a,b)
+ while(1)
+ {
+ block best_block;
+ unsigned best_idx = 0;
+ double best = 0;
+ unsigned beg = 0;
+ unsigned middle = blocks[beg].next;
+ unsigned end = blocks[middle].next;
+ while(end != N) {
+ sufficient_stats ss = blocks[beg].ss + blocks[middle].ss;
+ ss -= input[middle];
+ double mean;
+ Point normal = unit_vector(rot90(input[end] - input[beg]));
+ double dist = dot(normal, input[beg]);
+ double newcost = ss.best_line(normal, dist, mean);
+ double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost
+ + newcost;
+ /*std::cout << beg << ", "
+ << middle << ", "
+ << end << ", "
+ << deltaCost <<"; "
+ << newcost <<"; "
+ << mean << ": "
+ << ss
+ << std::endl;*/
+ if(deltaCost < best) {
+ best = deltaCost;
+ best_idx = beg;
+ best_block.ss = ss;
+ best_block.cost = newcost;
+ best_block.next = end;
+ }
+ beg = middle;
+ middle = end;
+ end = blocks[end].next;
+ }
+ if(best < 0)
+ blocks[best_idx] = best_block;
+ else // no improvement possible
+ break;
+ }
+ {
+ solution.resize(0); // empty list
+ unsigned beg = 0;
+ while(beg != N) {
+ solution.push_back(input[beg]);
+ beg = blocks[beg].next;
+ }
+ }
+}
+
+
+void fit::arbitrary() {
+ /*solution.resize(input.size());
+ copy(input.begin(), input.end(), solution.begin());*/
+ // normals
+
+ double best_error = INFINITY;
+ double best_mean = 0;
+ unsigned best_angle = 0;
+ for(unsigned i = 0; i < angles.size(); i++) {
+ Point angle = angles[i];
+ double mean = 0;
+ double error = 0;
+ for(unsigned l = 0; l < input.size(); l++) {
+ mean += dot(input[i], angle);
+ }
+ mean /= input.size();
+ for(unsigned l = 0; l < input.size(); l++) {
+ double d = dot(input[i], angle) - mean;
+ error += d*d;
+ }
+ if(error < best_error) {
+ best_mean = mean;
+ best_error = error;
+ best_angle = i;
+ }
+ }
+ Point angle = angles[best_angle];
+ solution.push_back(angle*best_mean + dot(input[0], rot90(angle))*rot90(angle));
+ solution.push_back(angle*best_mean + dot(input.back(), rot90(angle))*rot90(angle));
+}
+
+class reg_line{
+public:
+ Point parallel, centre, normal;
+ double Sr, Srr;
+ unsigned n;
+};
+
+template<class T>
+reg_line
+line_best_fit(T b, T e) {
+ double Sx = 0,
+ Sy = 0,
+ Sxx = 0,
+ Syy = 0,
+ Sxy = 0;
+ unsigned n = e - b;
+ reg_line rl;
+ rl.n = n;
+ for(T p = b; p != e; p++) {
+ Sx += (*p)[0];
+ Sy += (*p)[1];
+ Sxx += (*p)[0]*(*p)[0];
+ Syy += (*p)[1]*(*p)[1];
+ Sxy += (*p)[0]*(*p)[1];
+ }
+
+ rl.parallel = unit_vector(Point(n*Sxx - Sx*Sx,
+ n*Sxy - Sx*Sy));
+ rl.normal = rot90(rl.parallel);
+ rl.centre = Point(Sx/n, Sy/n);
+ rl.Sr = rl.Srr = 0;
+ for(T p = b; p != e; p++) {
+ double r = dot(rl.parallel, (*p) - rl.centre);
+ rl.Sr += fabs(r);
+ rl.Srr += r*r;
+ }
+ return rl;
+}
+
+void fit::linear_reg() {
+ reg_line rl = line_best_fit(input.begin(),
+ input.end());
+ solution.push_back(rl.centre + dot(rl.parallel, input[0] - rl.centre)*rl.parallel);
+ solution.push_back(rl.centre + dot(rl.parallel, input.back() - rl.centre)*rl.parallel);
+}
+
+void fit::simple_dp() {
+ const unsigned N = input.size();
+ vector<unsigned> prev(N);
+ vector<double> penalty(N);
+ const double bend_pen = 100;
+
+ for(unsigned i = 1; i < input.size(); i++) {
+ double mean;
+ double best = measure(0, i, mean);
+ unsigned best_prev = 0;
+ for(unsigned j = 1; j < i; j++) {
+ double err = penalty[j] + bend_pen + measure(j, i, mean);
+ if(err < best) {
+ best = err;
+ best_prev = j;
+ }
+ }
+ penalty[i] = best;
+ prev[i] = best_prev;
+ }
+
+ unsigned i = prev.size()-1;
+ while(i > 0) {
+ solution.push_back(input[i]);
+ i = prev[i];
+ }
+ solution.push_back(input[i]);
+ reverse(solution.begin(), solution.end());
+}
+
+void fit::C_endpoint() {
+ const unsigned N = input.size();
+
+ double best_mean;
+ double best = best_angled_line(0, N-1, angles[0], best_mean);
+ unsigned best_dir = 0;
+ for(unsigned c = 1; c < angles.size(); c++) {
+ double m;
+ double err = best_angled_line(0, N-1, angles[c], m);
+ if(err < best) {
+ best = err;
+ best_mean = m;
+ best_dir = c;
+ }
+
+ }
+ Point dir = angles[best_dir];
+ Point dirT = rot90(dir);
+ Point centre = dir*best_mean/N;
+
+ solution.push_back(centre + dot(dirT, input[0] - centre)*dirT);
+ solution.push_back(centre + dot(dirT, input.back() - centre)*dirT);
+}
+
+void fit::C_simple_dp() {
+ const unsigned N = input.size();
+
+ vector<int> prev(N);
+ vector<double> penalty(N);
+ vector<unsigned> dir(N);
+ vector<double> mean(N);
+ const double bend_pen = 0;
+
+ for(unsigned i = 1; i < input.size(); i++) {
+ double best_mean;
+ double best = best_angled_line(0, i, angles[0], best_mean);
+ unsigned best_prev = 0;
+ unsigned best_dir = 0;
+ for(unsigned c = 1; c < angles.size(); c++) {
+ double m;
+ double err = best_angled_line(0, i, angles[c], m);
+ if(err < best) {
+ best = err;
+ best_mean = m;
+ best_dir = c;
+ best_prev = 0;
+ }
+
+ }
+ for(unsigned j = 1; j < i; j++) {
+ for(unsigned c = 0; c < angles.size(); c++) {
+ double m;
+ if(c == dir[j])
+ continue;
+ double err = penalty[j] + bend_pen +
+ best_angled_line(j, i, angles[c], m);
+ if(err < best) {
+ best = err;
+ best_mean = m;
+ best_dir = c;
+ best_prev = j;
+ }
+
+ }
+ }
+ penalty[i] = best;
+ prev[i] = best_prev;
+ dir[i] = best_dir;
+ mean[i] = best_mean;
+ }
+
+ prev[0] = -1;
+ unsigned i = prev.size()-1;
+ unsigned pi = i;
+ while(i > 0) {
+ Point bdir = angles[dir[i]];
+ Point bdirT = rot90(bdir);
+ Point centre = bdir*mean[i]/N;
+ solution.push_back(centre + dot(bdirT, input[i] - centre)*bdirT);
+ solution.push_back(centre + dot(bdirT, input[pi] - centre)*bdirT);
+ pi = i;
+ i = prev[i];
+ }
+ /*Point a = angles[dir[i]];
+ Point aT = rot90(a);
+ solution.push_back(a*mean[i] +
+ dot(input[i], aT)*aT);*/
+ reverse(solution.begin(), solution.end());
+}
+
+
+
+
+
+
+class MetroMap: public Toy {
+public:
+ vector<PointSetHandle> metro_lines;
+ PointHandle directions;
+
+ bool should_draw_numbers() override { return false; }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ double slider_margin = 20;
+ double slider_top = 20;
+ double slider_bot = 200;
+ directions.pos[X] = slider_margin;
+ if (directions.pos[Y]<slider_top) directions.pos[Y] = slider_top;
+ if (directions.pos[Y]>slider_bot) directions.pos[Y] = slider_bot;
+
+ unsigned num_directions = 2 + 15*(slider_bot-directions.pos[Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0., 0.5, 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
+ cairo_set_line_width (cr, 1);
+
+ unsigned N= paths.size();
+ for(unsigned i=0;i<N;i++) {
+ double R,G,B;
+ convertHSVtoRGB(360.*double(i)/double(N),1,0.75,R,G,B);
+ metro_lines[i].rgb[0] = R;
+ metro_lines[i].rgb[1] = G;
+ metro_lines[i].rgb[2] = B;
+ cairo_set_source_rgba (cr, R, G, B, 0.8);
+ fit f(metro_lines[i].pts);
+ f.schematised_merging(num_directions);
+ f.draw(cr);
+ cairo_stroke(cr);
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ {
+ PangoLayout* layout = pango_cairo_create_layout (cr);
+ pango_layout_set_text(layout,
+ notify->str().c_str(), -1);
+
+ PangoFontDescription *font_desc = pango_font_description_new();
+ pango_font_description_set_family(font_desc, "Sans");
+ const unsigned size_px = 10;
+ pango_font_description_set_absolute_size(font_desc, size_px * 1024.0);
+ pango_layout_set_font_description(layout, font_desc);
+ PangoRectangle logical_extent;
+ pango_layout_get_pixel_extents(layout,
+ NULL,
+ &logical_extent);
+ cairo_move_to(cr, 0, height-logical_extent.height);
+ pango_cairo_show_layout(cr, layout);
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void first_time(int argc, char** argv) override {
+ string location_file_name("data/london-locations.csv");
+ string path_file_name("data/london.txt");
+ if(argc > 2) {
+ location_file_name = argv[1];
+ path_file_name = argv[2];
+ }
+ cout << location_file_name << ", " << path_file_name << endl;
+ parse_data(paths, location_file_name, path_file_name);
+ for(auto & path : paths) {
+ metro_lines.emplace_back();
+ for(auto & j : path) {
+ metro_lines.back().push_back(j);
+ }
+ }
+ for(auto & metro_line : metro_lines) {
+ handles.push_back(&metro_line);
+ }
+ handles.push_back(&directions);
+ }
+
+ MetroMap() {
+ }
+
+};
+
+
+
+
+
+int main(int argc, char **argv) {
+ init(argc, argv, new MetroMap());
+
+ 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/src/toys/minsb2d-solver.cpp b/src/toys/minsb2d-solver.cpp
new file mode 100644
index 0000000..f2df9d9
--- /dev/null
+++ b/src/toys/minsb2d-solver.cpp
@@ -0,0 +1,376 @@
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-4,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.size()==0||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+#include <gsl/gsl_multimin.h>
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+
+ class bits_n_bobs{
+ public:
+ SBasis2d * ff;
+ Point A, B;
+ Point dA, dB;
+ };
+ static double
+ my_f (const gsl_vector *v, void *params)
+ {
+ double x, y;
+ bits_n_bobs* bnb = (bits_n_bobs *)params;
+
+ x = gsl_vector_get(v, 0);
+ y = gsl_vector_get(v, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+
+ SBasis comp = compose((*bnb->ff),zeroset);
+ Interval bounds = *bounds_fast(comp);
+ double error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ //printf("error = %g %g %g\n", bounds.max(), bounds.min(), error);
+ return error*error;
+ }
+
+
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+/*
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+*/
+
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.141592/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.141592/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ //Geom::Point A(cos(tA*M_PI/2), sin(tA*M_PI/2));// = true_solution(tA);
+ //Geom::Point B(cos(tB*M_PI/2), sin(tB*M_PI/2));// = true_solution(tB);
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+ Point dA(-sin(tA*M_PI/2), cos(tA*M_PI/2));
+ Geom::Point dB(-sin(tB*M_PI/2), cos(tB*M_PI/2));
+ SBasis2d dfdu = partial_derivative(f, 0);
+ SBasis2d dfdv = partial_derivative(f, 1);
+ Geom::Point dfA(dfdu.apply(A[X],A[Y]),dfdv.apply(A[X],A[Y]));
+ Geom::Point dfB(dfdu.apply(B[X],B[Y]),dfdv.apply(B[X],B[Y]));
+ dA = rot90(dfA);
+ dB = rot90(dfB);
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 2; degree < 2; degree++) {
+ D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ if (1) {
+
+ bits_n_bobs par = {&f, A, B, dA, dB};
+ bits_n_bobs* bnb = &par;
+ std::cout << f[0] << "= initial f \n";
+ const gsl_multimin_fminimizer_type *T =
+ gsl_multimin_fminimizer_nmsimplex;
+ gsl_multimin_fminimizer *s = NULL;
+ gsl_vector *ss, *x;
+ gsl_multimin_function minex_func;
+
+ size_t iter = 0;
+ int status;
+ double size;
+
+ /* Starting point */
+ x = gsl_vector_alloc (2);
+ gsl_vector_set (x, 0, 0.552); // magic number for quarter circle
+ gsl_vector_set (x, 1, 0.552);
+
+ /* Set initial step sizes to 1 */
+ ss = gsl_vector_alloc (2);
+ gsl_vector_set_all (ss, 0.1);
+
+ /* Initialize method and iterate */
+ minex_func.n = 2;
+ minex_func.f = &my_f;
+ minex_func.params = (void *)&par;
+
+ s = gsl_multimin_fminimizer_alloc (T, 2);
+ gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
+
+ do
+ {
+ iter++;
+ status = gsl_multimin_fminimizer_iterate(s);
+
+ if (status)
+ break;
+
+ size = gsl_multimin_fminimizer_size (s);
+ status = gsl_multimin_test_size (size, 1e-7);
+
+ if (status == GSL_SUCCESS)
+ {
+ printf ("converged to minimum at\n");
+ }
+
+ }
+ while (status == GSL_CONTINUE && iter < 100);
+ printf ("%5lu %g %gf f() = %g size = %g\n",
+ iter,
+ gsl_vector_get (s->x, 0),
+ gsl_vector_get (s->x, 1),
+ s->fval, size);
+ {
+ double x = gsl_vector_get(s->x, 0);
+ double y = gsl_vector_get(s->x, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+
+ }
+
+ gsl_vector_free(x);
+ gsl_vector_free(ss);
+ gsl_multimin_fminimizer_free (s);
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ 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/src/toys/nasty.svg b/src/toys/nasty.svg
new file mode 100644
index 0000000..4457044
--- /dev/null
+++ b/src/toys/nasty.svg
@@ -0,0 +1,74 @@
+<?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://web.resource.org/cc/"
+ 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="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45"
+ sodipodi:modified="true">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="375"
+ inkscape:cy="481.62572"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="910"
+ inkscape:window-height="626"
+ inkscape:window-x="5"
+ inkscape:window-y="49" />
+ <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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z "
+ id="path2160"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z "
+ id="path2162"
+ sodipodi:nodetypes="csz" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z "
+ id="path2182"
+ sodipodi:nodetypes="czc" />
+ <path
+ id="path2186"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z "
+ sodipodi:nodetypes="czzsz" />
+ </g>
+</svg>
diff --git a/src/toys/nearest-times.cpp b/src/toys/nearest-times.cpp
new file mode 100644
index 0000000..66803d6
--- /dev/null
+++ b/src/toys/nearest-times.cpp
@@ -0,0 +1,258 @@
+/*
+ * Nearest Points Toy
+ *
+ * Authors:
+ * Nathan Hurst <njh at njhurst.com>
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework.h>
+
+
+using namespace Geom;
+
+
+class np_finder
+{
+public:
+ np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2)
+ : cr(_cr), c1(_c1), c2(_c2)
+ {
+ dc1 = derivative(c1);
+ dc2 = derivative(c2);
+ cd1 = dot(c1,dc1);
+ cd2 = dot(c2,dc2);
+ dsq = 10e30;
+ }
+
+ void operator() ()
+ {
+ nearest_times_impl(0.5, 0, 1);
+ d = sqrt(dsq);
+ }
+
+ Point firstPoint() const
+ {
+ return p1;
+ }
+
+ Point secondPoint() const
+ {
+ return p2;
+ }
+
+ double firstValue() const
+ {
+ return t1;
+ }
+
+ double secondValue() const
+ {
+ return t2;
+ }
+
+ double distance() const
+ {
+ return d;
+ }
+
+private:
+ void nearest_times_impl( double t, double from = 0, double to = 1 )
+ {
+ //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl;
+
+ double st = t, et = t;
+ std::pair<double, double> npc = loc_nearest_times(t, from, to);
+ //std::cerr << "(" << npc.first << "," << npc.second << ")" << std::endl;
+ if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) )
+ {
+ t1 = npc.first;
+ t2 = npc.second;
+ p1 = c1(t1);
+ p2 = c2(t2);
+ dsq = L2sq(p1 - p2);
+ }
+ if (npc.first < t)
+ st = npc.first;
+ else
+ et = npc.first;
+ //std::cerr << "[" << st << "," << et << "]" << std::endl;
+ double d1 = std::fabs(st - from);
+ double d2 = std::fabs(to - et);
+ if ( d1 > EPSILON )
+ nearest_times_impl(from + d1/2, from, st);
+ if ( d2 > EPSILON )
+ nearest_times_impl(et + d2/2, et, to);
+ }
+
+ std::pair<double, double>
+ loc_nearest_times( double t, double from = 0, double to = 1 )
+ {
+ unsigned int i = 0;
+ std::pair<double, double> np(-1,-1);
+ std::pair<double, double> npf(from, -1);
+ std::pair<double, double> npt(to, -1);
+ double ct = t;
+ double pt = -1;
+ double s;
+ //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0);
+ //cairo_move_to(cr, c1(t));
+ while( !are_near(ct, pt) )
+ {
+ ++i;
+ pt = ct;
+ s = nearest_time(c1(ct), c2, dc2, cd2);
+ //std::cerr << "s: " << s << std::endl;
+ //cairo_line_to(cr, c2(s));
+ ct = nearest_time(c2(s), c1, dc1, cd1, from, to);
+ //std::cerr << "t: " << t1 << std::endl;
+ //cairo_line_to(cr, c1(ct));
+ if ( ct < from ) return npf;
+ if ( ct > to ) return npt;
+ }
+ //std::cerr << "\n \n";
+ //std::cerr << "iterations: " << i << std::endl;
+ cairo_stroke(cr);
+ np.first = ct;
+ np.second = s;
+ return np;
+ }
+
+ double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 )
+ {
+ D2<SBasis> sbc = c - p;
+ SBasis dd = cd - dotp(p, dc);
+ std::vector<double> zeros = roots(dd);
+ double closest = from;
+ double distsq = L2sq(sbc(from));
+ for ( unsigned int i = 0; i < zeros.size(); ++i )
+ {
+ if ( distsq > L2sq(sbc(zeros[i])) )
+ {
+ closest = zeros[i];
+ distsq = L2sq(sbc(closest));
+ }
+ }
+ if ( distsq > L2sq(sbc(to)) )
+ closest = to;
+ return closest;
+ }
+
+ SBasis dotp(Point const& p, D2<SBasis> const& c)
+ {
+ SBasis d;
+ d.resize(c[X].size());
+ for ( unsigned int i = 0; i < c[0].size(); ++i )
+ {
+ for( unsigned int j = 0; j < 2; ++j )
+ d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j];
+ }
+ return d;
+ }
+
+private:
+ static const Coord EPSILON = 10e-3;
+ cairo_t* cr;
+ D2<SBasis> const& c1, c2;
+ D2<SBasis> dc1, dc2;
+ SBasis cd1, cd2;
+ double t1, t2, d, dsq;
+ Point p1, p2;
+};
+
+
+
+
+class NearestPoints : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream)
+ {
+ cairo_set_line_width (cr, 0.2);
+ D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1);
+ cairo_d2_sb(cr, A);
+ D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1);
+ cairo_d2_sb(cr, B);
+
+
+ np_finder np(cr, A, B);
+ np();
+ cairo_move_to(cr, np.firstPoint());
+ cairo_line_to(cr, np.secondPoint());
+ cairo_stroke(cr);
+ //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl;
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ unsigned int total_handles = A_bez_ord + B_bez_ord;
+ for ( unsigned int i = 0; i < total_handles; ++i )
+ handles.push_back(Geom::Point(uniform()*400, uniform()*400));
+ }
+
+ private:
+ unsigned int A_bez_ord;
+ unsigned int B_bez_ord;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord=8;
+ unsigned int B_bez_ord=5;
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+
+ init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord));
+ 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/src/toys/nearest-times2.cpp b/src/toys/nearest-times2.cpp
new file mode 100644
index 0000000..04cd9e6
--- /dev/null
+++ b/src/toys/nearest-times2.cpp
@@ -0,0 +1,313 @@
+/*
+ * Nearest Points Toy 2
+ *
+ * Authors:
+ * Nathan Hurst <njh at njhurst.com>
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework.h>
+
+#include <algorithm>
+
+
+using namespace Geom;
+
+
+class np_finder
+{
+public:
+ np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2)
+ : cr(_cr), c1(_c1), c2(_c2)
+ {
+ dc1 = derivative(c1);
+ dc2 = derivative(c2);
+ cd1 = dot(c1,dc1);
+ cd2 = dot(c2,dc2);
+ dsq = 10e30;
+
+ Piecewise<SBasis> k1 = curvature(c1, EPSILON);
+ Piecewise<SBasis> k2 = curvature(c2, EPSILON);
+ Piecewise<SBasis> dk1 = derivative(k1);
+ Piecewise<SBasis> dk2 = derivative(k2);
+ std::vector<double> k1_roots = roots(k1);
+ std::vector<double> k2_roots = roots(k2);
+ std::vector<double> dk1_roots = roots(dk1);
+ std::vector<double> dk2_roots = roots(dk2);
+ tlist.clear();
+ tlist.resize(k1_roots.size() + k2_roots.size() + dk1_roots.size() + dk2_roots.size() + 4);
+ tlist.push_back(0);
+ tlist.insert(tlist.end(), dk1_roots.begin(), dk1_roots.end());
+ tlist.insert(tlist.end(), k1_roots.begin(), k1_roots.end());
+// std::cerr << "dk1 roots: ";
+// for ( unsigned int i = 0; i < dk1_roots.size(); ++i )
+// {
+// std::cerr << dk1_roots[i] << " ";
+// }
+// std::cerr << "\n";
+ for ( unsigned int i = 0; i < k2_roots.size(); ++i )
+ {
+ tlist.push_back(nearest_time(c2(k2_roots[i]), c1, dc1, cd1));
+ }
+
+ for ( unsigned int i = 0; i < dk2_roots.size(); ++i )
+ {
+ tlist.push_back(nearest_time(c2(dk2_roots[i]), c1, dc1, cd1));
+ }
+ tlist.push_back(nearest_time(c2(0), c1, dc1, cd1));
+ tlist.push_back(nearest_time(c2(1), c1, dc1, cd1));
+ tlist.push_back(1);
+ std::sort(tlist.begin(), tlist.end());
+ std::vector<double>::iterator pos
+ = std::unique(tlist.begin(), tlist.end(), are_near_() );
+ if (pos != tlist.end())
+ {
+ tlist.erase(pos, tlist.end());
+ }
+ for( unsigned int i = 0; i < tlist.size(); ++i )
+ {
+ draw_circ(cr, c1(tlist[i]) );
+ }
+ }
+
+ void operator() ()
+ {
+ //nearest_times_impl( tlist.size() / 2, 0, tlist.size() - 1 );
+ nearest_times_impl();
+ d = sqrt(dsq);
+ }
+
+ Point firstPoint() const
+ {
+ return p1;
+ }
+
+ Point secondPoint() const
+ {
+ return p2;
+ }
+
+ double firstValue() const
+ {
+ return t1;
+ }
+
+ double secondValue() const
+ {
+ return t2;
+ }
+
+ double distance() const
+ {
+ return d;
+ }
+
+private:
+ void nearest_times_impl()
+ {
+ double t;
+ double from = tlist[0];
+ double to;
+ for ( unsigned int i = 1; i < tlist.size(); ++i )
+ {
+ to = tlist[i];
+ t = from + (to - from) / 2 ;
+ std::pair<double, double> npc = loc_nearest_times(t, from, to);
+ if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) )
+ {
+ t1 = npc.first;
+ t2 = npc.second;
+ p1 = c1(t1);
+ p2 = c2(t2);
+ dsq = L2sq(p1 - p2);
+ }
+ from = tlist[i];
+ }
+ }
+
+ std::pair<double, double>
+ loc_nearest_times( double t, double from = 0, double to = 1 )
+ {
+ //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl;
+ unsigned int i = 0;
+ std::pair<double, double> np(-1,-1);
+ std::pair<double, double> npf(from, -1);
+ std::pair<double, double> npt(to, -1);
+ double ct = t;
+ double pt = -1;
+ double s;
+ //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0);
+ cairo_move_to(cr, c1(t));
+ while( !are_near(ct, pt) )
+ {
+ ++i;
+ pt = ct;
+ s = nearest_time(c1(ct), c2, dc2, cd2);
+ //std::cerr << "s: " << s << std::endl;
+ //cairo_line_to(cr, c2(s));
+ ct = nearest_time(c2(s), c1, dc1, cd1, from, to);
+ //std::cerr << "t: " << ct << std::endl;
+ //cairo_line_to(cr, c1(ct));
+ //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl;
+ if ( ct < from ) return npf;
+ if ( ct > to ) return npt;
+ }
+ //std::cerr << "\n \n";
+ std::cerr << "iterations: " << i << std::endl;
+ //cairo_move_to(cr, c1(ct));
+ //cairo_line_to(cr, c2(s));
+ cairo_stroke(cr);
+ np.first = ct;
+ np.second = s;
+ return np;
+ }
+
+ double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 )
+ {
+ D2<SBasis> sbc = c - p;
+ SBasis dd = cd - dotp(p, dc);
+ std::vector<double> zeros = roots(dd);
+ double closest = from;
+ double distsq = L2sq(sbc(from));
+ for ( unsigned int i = 0; i < zeros.size(); ++i )
+ {
+ if ( distsq > L2sq(sbc(zeros[i])) )
+ {
+ closest = zeros[i];
+ distsq = L2sq(sbc(closest));
+ }
+ }
+ if ( distsq > L2sq(sbc(to)) )
+ closest = to;
+ return closest;
+ }
+
+ SBasis dotp(Point const& p, D2<SBasis> const& c)
+ {
+ SBasis d;
+ d.resize(c[X].size());
+ for ( unsigned int i = 0; i < c[0].size(); ++i )
+ {
+ for( unsigned int j = 0; j < 2; ++j )
+ d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j];
+ }
+ return d;
+ }
+
+ struct are_near_
+ {
+ bool operator() (double x, double y, double eps = Geom::EPSILON )
+ {
+ return are_near(x, y, eps);
+ }
+ };
+
+private:
+ static const Coord EPSILON = 10e-5;
+ cairo_t* cr;
+ D2<SBasis> const& c1, c2;
+ D2<SBasis> dc1, dc2;
+ SBasis cd1, cd2;
+ double t1, t2, d, dsq;
+ Point p1, p2;
+ std::vector<double> tlist;
+};
+
+
+
+
+class NearestPoints : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream)
+ {
+ cairo_set_line_width (cr, 0.2);
+ D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1);
+ cairo_d2_sb(cr, A);
+ D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1);
+ cairo_d2_sb(cr, B);
+
+
+ np_finder np(cr, A, B);
+ np();
+ cairo_move_to(cr, np.firstPoint());
+ cairo_line_to(cr, np.secondPoint());
+ cairo_stroke(cr);
+ //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl;
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ unsigned int total_handles = A_bez_ord + B_bez_ord;
+ for ( unsigned int i = 0; i < total_handles; ++i )
+ handles.push_back(Geom::Point(uniform()*400, uniform()*400));
+ }
+
+ private:
+ unsigned int A_bez_ord;
+ unsigned int B_bez_ord;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord=8;
+ unsigned int B_bez_ord=5;
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+
+ init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord));
+ 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/src/toys/normal-bundle.cpp b/src/toys/normal-bundle.cpp
new file mode 100644
index 0000000..efe7536
--- /dev/null
+++ b/src/toys/normal-bundle.cpp
@@ -0,0 +1,230 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+class NormalBundle : public std::vector<D2<SBasis2d> >{
+public:
+ vector<double> lengths;
+ NormalBundle(){lengths.push_back(0.);}
+ void setBase(D2<SBasis> const &B, double tol);
+ void draw(cairo_t* cr, unsigned NbSections =5,unsigned NbFibre=5);
+};
+vector<D2<SBasis> > compose(NormalBundle const &NB,
+ D2<SBasis> const &Binit,
+ Geom::Point Origin=Geom::Point(0,0));
+
+//--------------------------------------------
+
+void SBasis1d_to_2d(D2<SBasis> C0,
+ D2<SBasis> C1,
+ D2<SBasis2d> &S){
+ for(unsigned dim = 0; dim < 2; dim++) {
+//**** C0 and C1 should have the same size...
+ for (unsigned i=C0[dim].size();i<C1[dim].size(); i++)
+ C0[dim].push_back(Linear(0));
+ for (unsigned i=C1[dim].size();i<C0[dim].size(); i++)
+ C1[dim].push_back(Linear(0));
+ S[dim].clear();
+ S[dim].us = C0[dim].size();
+ S[dim].vs = 1;
+ for(unsigned v = 0; v < S[dim].vs; v++)
+ for(unsigned u = 0; u < S[dim].us; u++)
+ S[dim].push_back(Linear2d(C0[dim][u][0],C0[dim][u][1],
+ C1[dim][u][0],C1[dim][u][1]));
+ }
+}
+
+void NormalBundle::setBase(D2<SBasis> const &B, double tol=0.01) {
+
+ D2<SBasis> dB = derivative(B);
+ vector<double> cuts;
+ Piecewise<D2<SBasis> > unitV=unitVector(dB);
+
+ //TODO: clean this up, use arc_length_parametrization...
+ cuts=unitV.cuts;
+ double t0=0,t1,L=0;
+ for(unsigned i=1;i<cuts.size();i++){
+ t1=cuts[i];
+ D2<SBasis> subB=compose(B,Linear(t0,t1));
+ D2<SBasis2d> S;
+ SBasis1d_to_2d(subB, subB+rot90(unitV[i]), S);
+ push_back(S);
+
+ SBasis s=integral(dot(compose(dB,Linear(t0,t1)),unitV[i]));
+ L+=(s(1)-s(0))*(t1-t0);
+ lengths.push_back(L);
+
+ t0=t1;
+ }
+}
+
+void NormalBundle::draw(cairo_t *cr, unsigned NbLi, unsigned NbCol) {
+ D2<SBasis> B;
+ vector<D2<SBasis> > tB;
+ //Geom::Point Seg[2];
+ B[1]=Linear(-100,100);
+ double width=*(lengths.rbegin());
+ if (NbCol>0)
+ for(unsigned ui = 0; ui <= NbCol; ui++) {
+ B[0]=Linear(ui*width/NbCol);
+ tB = compose(*this,B);
+ if (tB.size()>0) cairo_d2_sb(cr, tB[0]);
+ }
+
+ B[0]=SBasis(Linear(0,1));
+ for(unsigned ui = 0; ui <= NbLi; ui++) {
+ B[1]=Linear(-100+ui*200/NbLi);
+ for(unsigned i = 0; i <size(); i++) {
+ D2<SBasis> section=composeEach((*this)[i],B);
+ cairo_d2_sb(cr, section);
+ }
+ }
+}
+
+
+vector<D2<SBasis> > compose(NormalBundle const &NB,
+ D2<SBasis> const &Binit,
+ Geom::Point Origin){
+ vector<D2<SBasis> > result;
+ D2<SBasis> B=Binit;
+ D2<SBasis> Bcut;
+ vector<double> Roots;
+ std::map<double,unsigned> Cuts;
+ unsigned idx;
+
+ B = B + (-Origin);
+
+ //--Find intersections with fibers over segment ends.
+ for(unsigned i=0; i<=NB.size();i++){
+ Roots=roots(B[0]);
+ for (vector<double>::iterator root=Roots.begin();
+ root!=Roots.end();root++)
+ Cuts[*root]=i;
+ if((Cuts.count(0.)==0) and
+ ((B[0].valueAt(0.)<=0) or i==NB.size()))
+ Cuts[0.]=i;
+ if((Cuts.count(1.)==0) and
+ ((B[0].valueAt(1.)<=0) or i==NB.size()))
+ Cuts[1.]=i;
+ if (i<NB.size())
+ B[0]-=(NB.lengths[i+1]-NB.lengths[i]);
+ }
+ B[0]+=*(NB.lengths.rbegin());
+
+ //-- Compose each piece with the relevant sbasis2d.
+ // TODO: use a uniform parametrization of the base.
+ std::map<double,unsigned>::iterator cut=Cuts.begin();
+ std::map<double,unsigned>::iterator next=cut; next++;
+ while(next!=Cuts.end()){
+ double t0=(*cut).first;
+ unsigned idx0=(*cut).second;
+ double t1=(*next).first;
+ unsigned idx1=(*next).second;
+ if (idx0 != idx1){
+ idx=std::min(idx0,idx1);
+ } else if(B[0]((t0+t1)/2) < NB.lengths[idx0]) { // we have a left 'bump',
+ idx=idx0-1;
+ } else if(B[0]((t0+t1)/2) == NB.lengths[idx0]) { //we have a vertical segment!...
+ idx= (idx0==NB.size())? idx0-1:idx0;
+ } else //we have a right 'bump'.
+ idx=idx0;
+
+ //--trim version...
+ if (idx>=0 and idx<NB.size()) {
+ for (unsigned dim=0;dim<2;dim++)
+ Bcut[dim]=compose(B[dim], Linear(t0,t1));
+ double width=NB.lengths[idx+1]-NB.lengths[idx];
+ Bcut[0]=compose(Linear(-NB.lengths[idx]/width,
+ (1-NB.lengths[idx])/width),Bcut[0]);
+ Bcut = composeEach(NB[idx], Bcut);
+ result.push_back(Bcut);
+ }
+ cut++;
+ next++;
+ }
+ return(result);
+}
+
+
+
+class NormalBundleToy: public Toy {
+ PointSetHandle B_handle;
+ PointSetHandle P_handle;
+ PointHandle O_handle;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ D2<SBasis> B = B_handle.asBezier();
+ D2<SBasis> P = P_handle.asBezier();
+ Geom::Point O = O_handle.pos;
+
+ NormalBundle NBdle;
+ NBdle.setBase(B);
+ Geom::Point Oo(O[0]+*(NBdle.lengths.rbegin()),O[1]);
+
+ vector<D2<SBasis> > Q=compose(NBdle,P,O);
+
+ cairo_set_line_width (cr, 0.5);
+ //Base lines
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1);
+ cairo_d2_sb(cr, B);
+ draw_line_seg(cr, O, Oo);
+ cairo_stroke(cr);
+
+ //Sections
+ cairo_set_source_rgba (cr, 0, 0, 0.9, 1);
+ cairo_d2_sb(cr, P);
+ for (unsigned i=0;i<Q.size();i++){
+ cairo_d2_sb(cr, Q[i]);
+ }
+ cairo_stroke(cr);
+
+ //Normal bundle
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ NBdle.draw(cr,3,5);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ NormalBundleToy(){
+ if(handles.empty()) {
+ handles.push_back(&B_handle);
+ handles.push_back(&P_handle);
+ handles.push_back(&O_handle);
+ for(unsigned i = 0; i < 4; i++)
+ B_handle.push_back(200+50*i,400);
+ for(unsigned i = 0; i < 4; i++)
+ P_handle.push_back(100+uniform()*400,
+ 150+uniform()*100);
+ O_handle.pos = Geom::Point(200,200);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new NormalBundleToy);
+ 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/src/toys/offset-toy.cpp b/src/toys/offset-toy.cpp
new file mode 100644
index 0000000..4b3e617
--- /dev/null
+++ b/src/toys/offset-toy.cpp
@@ -0,0 +1,156 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/path-intersection.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <sstream>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// TODO:
+// use path2
+// replace Ray stuff with path2 line segments.
+
+//-----------------------------------------------
+
+static void
+plot_offset(cairo_t* cr, D2<SBasis> const &M,
+ Coord offset = 10,
+ unsigned NbPts = 10){
+ D2<SBasis> dM = derivative(M);
+ for (unsigned i = 0;i < NbPts;i++){
+ double t = i*1./NbPts;
+ Point V = dM(t);
+ V = offset*rot90(unit_vector(V));
+ draw_handle(cr, M(t)+V);
+ }
+}
+
+static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){
+ D2<Piecewise<SBasis> > plot;
+ plot[1]=-f*vscale;
+ plot[1]+=450;
+
+ plot[0].cuts.push_back(f.cuts.front());
+ plot[0].cuts.push_back(f.cuts.back());
+ plot[0].segs.emplace_back(Linear(150,450));
+
+ for (unsigned i=1; i<f.size(); i++){
+ double t=f.cuts[i],ft=f.segs[i].at0();
+ cairo_move_to(cr, Point(150+t*300, 450));
+ cairo_line_to(cr, Point(150+t*300, 450-ft*vscale));
+ }
+ cairo_d2_pw_sb(cr, plot);
+}
+
+
+
+class OffsetTester: public Toy {
+ PointSetHandle psh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ D2<SBasis> B = psh.asBezier();
+ *notify << "Curve offset:" << endl;
+ *notify << " -blue: pointwise plotted offset," << endl;
+ *notify << " -red: rot90(unitVector(derivative(.)))+rays at cut" << endl;
+ *notify << " -gray: cos(atan2),sin(atan2)" << endl;
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ Coord offset = -100;
+ plot_offset(cr,B,offset,11);
+ cairo_set_source_rgba (cr, 0, 0, 1, 1);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8);
+ Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B)));
+ Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset;
+ PathVector offset_path = path_from_piecewise(offset_curve, 0.1);
+
+ cairo_path(cr, offset_path);
+ cairo_stroke(cr);
+ for(const auto & pi : offset_path) {
+ Crossings cs = self_crossings(pi);
+ for(auto & c : cs) {
+ draw_cross(cr, pi.pointAt(c.ta));
+ std::stringstream s;
+ Point Pa = pi.pointAt(c.ta);
+ Point Pb = pi.pointAt(c.tb);
+ s << L1(Pa - Pb) << std::endl;
+ std::string ss = s.str();
+ draw_text(cr, Pa+Point(3,3), ss.c_str(), false, "Serif 6");
+
+ }
+ }
+
+ for(unsigned i = 0; i < n.size()+1;i++){
+ Point ptA=B(n.cuts[i]), ptB;
+ if (i==n.size())
+ ptB=ptA+n.segs[i-1].at1()*offset;
+ else
+ ptB=ptA+n.segs[i].at0()*offset;
+ cairo_move_to(cr,ptA);
+ cairo_line_to(cr,ptB);
+ cairo_set_source_rgba (cr, 1, 0, 0, 1);
+ cairo_stroke(cr);
+ }
+
+ Piecewise<SBasis> alpha = atan2(derivative(B),1e-2,3);
+ plot(cr,alpha,75/M_PI);
+
+ Piecewise<D2<SBasis> >n2 = sectionize(D2<Piecewise<SBasis> >(sin(alpha),cos(alpha)));
+ cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+n2*offset*.9);
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8);
+ cairo_stroke(cr);
+
+ Piecewise<SBasis> k = curvature(B);
+ cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+k*n*100);
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8);
+ cairo_stroke(cr);
+
+ *notify << "Total length: " << length(B) << endl;
+ *notify << "(nb of cuts of unitVector: " << n.size()-1 << ")" << endl;
+ *notify << "(nb of cuts of cos,sin(atan2): " << n2.size()-1 << ")" << endl;
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ OffsetTester(int order) {
+ handles.push_back(&psh);
+ for(int i = 0; i < order; i++)
+ psh.push_back(200+50*i,300+70*uniform());
+ }
+};
+
+int main(int argc, char **argv) {
+ int A_bez_ord = 6;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+ init(argc, argv, new OffsetTester(A_bez_ord));
+ 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:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/pair-intersect.cpp b/src/toys/pair-intersect.cpp
new file mode 100644
index 0000000..9cc01ca
--- /dev/null
+++ b/src/toys/pair-intersect.cpp
@@ -0,0 +1,147 @@
+#include <2geom/basic-intersection.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/path-intersection.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+class PairIntersect: public Toy {
+ PointSetHandle A_handles;
+ PointSetHandle B_handles;
+ std::vector<Toggle> toggles;
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ draw_toggles(cr, toggles);
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ D2<SBasis> A = A_handles.asBezier();
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+ cairo_set_source_rgba(cr, 0.0, 0, 0.8, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ D2<SBasis> B = B_handles.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1.0);
+ cairo_set_line_width (cr, 0.5);
+ SBasis crs (cross(A - A(0), derivative(A)));
+ crs = shift(crs*Linear(-1, 0)*Linear(-1, 0), -2);
+ crs = crs * (300/(*bounds_exact(crs)).extent());
+ vector<double> rts = roots(crs);
+ for(double t : rts) {
+ cairo_move_to(cr, A(0));
+ cairo_line_to(cr, A(t));
+ cairo_stroke(cr);
+ }
+ cairo_restore(cr);
+ cairo_move_to(cr, 0, 300);
+ cairo_line_to(cr, width, 300);
+ crs += 300;
+ D2<SBasis > are_graph(SBasis(Linear(0, width)), crs );
+ cairo_save(cr);
+ cairo_d2_sb(cr, are_graph);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ Path PB;
+ PB.append(B);
+ Path PA;
+ PA.append(A);
+
+ if (toggles[0].on) {
+ PathVector ps;
+ ps.push_back(PA);
+ ps.push_back(PB);
+ CrossingSet cs = crossings_among(ps);
+ *notify << "total intersections: " << cs.size() << '\n';
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 1., 0., 0, 0.8);
+ for(unsigned i = 0; i < cs.size(); i++) {
+ Crossings section = cs[i];
+ *notify << "section " << i << ": " << section.size() << '\n';
+ for(auto & j : section) {
+ draw_handle(cr, A(j.ta));
+ *notify << Geom::distance(A(j.ta), B(j.tb))
+ << std::endl;
+ }
+ }
+
+ cairo_stroke(cr);
+ } else {
+ vector<Geom::Point> Ab = A_handles.pts, Bb = B_handles.pts;
+ std::vector<std::pair<double, double> > section;
+ find_intersections( section, A, B);
+ std::vector<std::pair<double, double> > polished_section = section;
+ *notify << "total intersections: " << section.size();
+ polish_intersections( polished_section, A, B);
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 1., 0., 0, 0.8);
+ for(unsigned i = 0; i < section.size(); i++) {
+ draw_handle(cr, A(section[i].first));
+ *notify << Geom::distance(A(section[i].first), B(section[i].second))
+ << " polished "
+ << Geom::distance(A(polished_section[i].first), B(polished_section[i].second))
+ << std::endl;
+ }
+
+ cairo_stroke(cr);
+ }
+ cairo_restore(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+}
+public:
+ PairIntersect (unsigned A_bez_ord, unsigned B_bez_ord) {
+ toggles.emplace_back("Path", true);
+ toggles[0].bounds = Rect(Point(10,100), Point(100, 130));
+ //toggles.push_back(Toggle("Curve", true));
+ //toggles[1].bounds = Rect(Point(10,130), Point(100, 160));
+ handles.push_back(&A_handles);
+ handles.push_back(&B_handles);
+ A_handles.name = "A";
+ B_handles.name = "B";
+ for(unsigned i = 0; i < A_bez_ord; i++)
+ A_handles.push_back(uniform()*400, uniform()*400);
+ for(unsigned i = 0; i < B_bez_ord; i++)
+ B_handles.push_back(uniform()*400, uniform()*400);
+}
+};
+
+int main(int argc, char **argv) {
+unsigned A_bez_ord=10;
+unsigned B_bez_ord=3;
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+ init(argc, argv, new PairIntersect(A_bez_ord, B_bez_ord));
+
+ 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/src/toys/paptest.cpp b/src/toys/paptest.cpp
new file mode 100644
index 0000000..4ea2cd2
--- /dev/null
+++ b/src/toys/paptest.cpp
@@ -0,0 +1,107 @@
+/*
+ * A simple toy to test the path along path
+ *
+ * Copyright 2007 Johan Engelen
+ *
+ * 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 <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > & pattern)
+{
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(pwd2_in, 2, .1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern);
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+ Interval pattBnds = *bounds_exact(x);
+ x -= pattBnds.min();
+ Interval pattBndsY = *bounds_exact(y);
+ y -= (pattBndsY.max()+pattBndsY.min())/2;
+
+ int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
+ double scaling = 1;
+
+ double pattWidth = pattBnds.extent() * scaling;
+
+ if (scaling != 1.0) {
+ x*=scaling;
+ }
+
+ double offs = 0;
+ Piecewise<D2<SBasis> > output;
+ for (int i=0; i<nbCopies; i++){
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ offs+=pattWidth;
+ }
+
+ return output;
+}
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ Geom::PathVector originald = Geom::parse_svg_path(&*argv[1]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2;
+ for (const auto & i : originald) {
+ originaldpwd2.concat( i.toPwSb() );
+ }
+
+ Geom::PathVector pattern = Geom::parse_svg_path(&*argv[2]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2;
+ for (const auto & i : pattern) {
+ patternpwd2.concat( i.toPwSb() );
+ }
+
+ doEffect_pwd2(originaldpwd2, patternpwd2);
+ }
+ 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/src/toys/parametrics.cpp b/src/toys/parametrics.cpp
new file mode 100644
index 0000000..2d2538c
--- /dev/null
+++ b/src/toys/parametrics.cpp
@@ -0,0 +1,229 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/affine.h>
+
+#include <glib.h>
+#include <vector>
+#include <iostream>
+using std::vector;
+using namespace Geom;
+
+int mode;
+
+static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double max, double space=10){
+ Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 3;
+ for( double t = M.cuts.front(); t < max; t += space) {
+ Point pos = M(t), perp = Mperp(t);
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_stroke(cr);
+}
+
+static void draw_axis(cairo_t *cr, Piecewise<D2<SBasis> > const &pw, unsigned d, Affine m) {
+ double mult;
+ if(abs(mode)==1) mult = 20;
+ if(abs(mode)==2) mult = 1;
+ if(abs(mode)==3) mult = 100;
+ if(abs(mode)==4) mult = 20;
+ if(abs(mode)==5) mult = 20;
+ if(abs(mode)==6) mult = 100;
+ for(unsigned i = 0; i < pw.size(); i++) {
+ cairo_d2_sb(cr, D2<SBasis>(SBasis(pw.cuts[i]-pw.cuts[0], pw.cuts[i+1]-pw.cuts[0])*mult, SBasis(pw[i][d]))*m);
+ }
+}
+/*
+void dump_latex(PathVector ps) {
+ for(unsigned d = 0; d < 2; d++) {
+ std::cout << "$$\n" << (d?"y":"x") << "(t) = \\left\\{\n\\begin{array}{ll}\n";
+ int seg = 0;
+ for(unsigned i = 0; i < ps.size(); i++)
+ for(unsigned j = 0; j < ps[i].size(); j++) {
+ Bezier<3> &b = dynamic_cast<Bezier<3>& >(const_cast<Curve&>(ps[i][j]));
+ std::cout << b[0][d] << "(" << seg+1 << "-t)^3 + "
+ << 3*b[1][d] << "t(" << seg+1 << "-t)^2 + "
+ << 3*b[2][d] << "t^2(" << seg+1 << "-t) + "
+ << b[3][d] << "t^3,& " << seg << "\\leq t < " << seg+1 << "\\\\\n";
+ seg++;
+ }
+ std::cout << "\\end{array}\n$$\n";
+ }
+}
+*/
+class Parametrics: public Toy {
+ Piecewise<D2<SBasis> > cat, alcat, box, arc, monk, traj;
+#ifdef USE_TIME
+ GTimer* time;
+ bool st;
+#endif
+ double waitt;
+ double t;
+ int count;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ //double t = handles[0][0] / 20.;
+
+#ifdef USE_TIME
+ gulong* foo = 0;
+ t = g_timer_elapsed(time, foo) * 100;
+#else
+ double inc;
+ if(mode==1) inc = .1;
+ if(mode==2) inc = 5;
+ if(mode==3) inc = .01;
+ if(mode==4) inc = .04;
+ if(mode==5) inc = .1;
+ if(mode==6) inc = .01;
+ if(mode<0) inc = .01*M_PI;
+ if(!save && !waitt) {
+ t += inc;
+ }
+ if(waitt) waitt += 1;
+ if(waitt>20) waitt = 0;
+#endif
+ Piecewise<D2<SBasis> > obj;
+ if(abs(mode)==1) obj = cat;
+ if(abs(mode)==2) obj = alcat;
+ if(abs(mode)==3) obj = arc;
+ if(abs(mode)==4) obj = box;
+ if(abs(mode)==5) obj = monk;
+ if(abs(mode)==6) obj = traj;
+ if(t==obj.cuts.back()) t += inc/2;
+ cairo_set_source_rgb(cr, 1,1,1);
+ if(save) {
+ cairo_rectangle(cr, 0, 0, width, height);
+ cairo_fill(cr);
+ }
+ Piecewise<D2<SBasis> > port, rport;
+ if(mode>0) {
+ port = portion(obj, 0, t);
+ rport = mode>0? portion(obj, t, obj.cuts[obj.size()]) : obj;
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ Point curpt = rport[0].at0();
+ if(t<obj.cuts.back()) {
+ draw_line_seg(cr, curpt, Point(curpt[0], 350));
+ draw_line_seg(cr, curpt, Point(350, curpt[1]));
+ cairo_stroke(cr);
+ }
+
+ char tlab[64];
+ sprintf(tlab, "t=%.02f", t);
+ draw_text(cr, curpt, tlab , true);
+
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_pw_d2_sb(cr, port);
+ cairo_stroke(cr);
+ }
+ if(mode>=0 && t>=obj.cuts.back()+inc) t = 0;
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1);
+ if(mode<0) {
+ draw_axis(cr, obj, 0, from_basis(Point(cos(t),sin(t)),Point(sin(t),-cos(t)),Point(0, 350)));
+ if(cos(t) <= 0) {
+ mode = -mode;
+ t = 0;
+ waitt = 1;
+ }
+ } else
+ draw_axis(cr, rport, 0, from_basis(Point(0,1),Point(1,0),Point(0, 350)));
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0., 0., 0.9, 1);
+ if(mode<0)
+ draw_axis(cr, obj, 1, from_basis(Point(1,0),Point(0,1),Point(350*t/M_PI*2, 0)));
+ else
+ draw_axis(cr, rport, 1, from_basis(Point(1,0),Point(0,1),Point(350, 0)));
+ cairo_stroke(cr);
+
+ if(mode==2 && t>0) {
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0., 0.9, 1);
+ dot_plot(cr, port, t);
+ cairo_stroke(cr);
+ }
+
+ if(!save) {
+ char file[100];
+ sprintf(file, "output/%04d.png", count);
+ //take_screenshot(file);
+ count++;
+ }
+ // *notify << "pieces = " << alcat.size() << ";\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ redraw();
+ }
+
+#ifdef USE_TIME
+ virtual void mouse_moved(GdkEventMotion* e) {
+ if(st) {
+ g_timer_start(time);
+ st = false;
+ }
+ Toy::mouse_moved(e);
+ }
+#endif
+
+ public:
+ Parametrics(){
+ mode = 2;
+ PathVector cp = read_svgd("cat.svgd");
+ //dump_latex(cp);
+ cat = paths_to_pw(cp);
+ cat *= .3;
+ cat += Point(50, 50);
+ alcat = arc_length_parametrization(cat);
+
+ monk = paths_to_pw(read_svgd("monkey.svgd"));
+ //monk *= .3;
+ monk += Point(50,50);
+
+ arc = sectionize(D2<Piecewise<SBasis> >(cos(Linear(0,M_PI))*120, sin(Linear(0,M_PI))*-120));
+ arc += Point(200, 200);
+
+ box = Piecewise<D2<SBasis> >();
+ box.push_cut(0);
+ box.push(D2<SBasis>(SBasis(100.,300.), SBasis(100.)), 1);
+ box.push(D2<SBasis>(SBasis(300.), SBasis(100.,300.)), 2);
+ box.push(D2<SBasis>(SBasis(300.,100.), SBasis(300.)), 3);
+ box.push(D2<SBasis>(SBasis(100.), SBasis(300.,100.)), 4);
+ //handles.push_back(Point(100, 100));
+ traj = Piecewise<D2<SBasis> >();
+ SBasis quad = Linear(0,1)*Linear(0,1)*256-Linear(0,256)+200;
+ traj.push_cut(0);
+ traj.push(D2<SBasis>(SBasis(100.,300.),SBasis(quad)), 1);
+#ifdef USE_TIME
+ time = g_timer_new();
+ g_timer_reset(time);
+ st = true;
+#endif
+ waitt = 0;
+ count = 0;
+ t = 0;
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Parametrics, 720, 480);
+ 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/src/toys/parser.cpp b/src/toys/parser.cpp
new file mode 100644
index 0000000..c7950e5
--- /dev/null
+++ b/src/toys/parser.cpp
@@ -0,0 +1,108 @@
+/*
+ * A simple toy to test the parser
+ *
+ * Copyright 2007 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.
+ *
+ */
+
+
+#include <iostream>
+#include <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+
+class SVGPathTestPrinter : public Geom::PathSink {
+public:
+ void moveTo(Geom::Point const &p) override {
+ std::cout << "M " << p << std::endl;
+ }
+
+ void hlineTo(Geom::Coord v) {
+ std::cout << "H " << v << std::endl;
+ }
+
+ void vlineTo(Geom::Coord v) {
+ std::cout << "V " << v << std::endl;
+ }
+
+ void lineTo(Geom::Point const &p) override {
+ std::cout << "L " << p << std::endl;
+ }
+
+ void curveTo(Geom::Point const &c0, Geom::Point const &c1, Geom::Point const &p) override {
+ std::cout << "C " << c0 << " " << c1 << " " << p << std::endl;
+ }
+
+ void quadTo(Geom::Point const &c, Geom::Point const &p) override {
+ std::cout << "Q " << c << " " << p << std::endl;
+ }
+
+ void arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Geom::Point const &p) override
+ {
+ std::cout << "A " << rx << " " << ry << " " << angle << " " << large_arc << " " << sweep << " " << p << std::endl;
+ }
+
+ bool backspace() override
+ {
+ //std::cout << "[remove last segment]" << std::endl;
+ return false;
+ }
+
+ void closePath() override {
+ std::cout << "Z" << std::endl;
+ }
+
+ void flush() override {
+ ;
+ }
+
+};
+
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ SVGPathTestPrinter sink;
+ Geom::parse_svg_path(&*argv[1], sink);
+ std::cout << "Try real pathsink:" << std::endl;
+ Geom::PathVector testpath = Geom::parse_svg_path(&*argv[1]);
+ std::cout << "Geom::PathVector length: " << testpath.size() << std::endl;
+ if ( !testpath.empty() )
+ std::cout << "Path curves: " << testpath.front().size() << std::endl;
+ std::cout << "success!" << std::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/src/toys/path-along-path.cpp b/src/toys/path-along-path.cpp
new file mode 100644
index 0000000..a2a6a1b
--- /dev/null
+++ b/src/toys/path-along-path.cpp
@@ -0,0 +1,112 @@
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <algorithm>
+using std::vector;
+using namespace Geom;
+
+class PathAlongPathToy: public Toy {
+ PointSetHandle skel_handles, pat_handles;
+ PointHandle origin_handle;
+ bool should_draw_numbers() override{return false;}
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ D2<SBasis> skeleton = skel_handles.asBezier();
+ D2<SBasis> pattern = pat_handles.asBezier();
+
+
+ cairo_set_line_width(cr,1.);
+ cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(skeleton));
+ cairo_set_source_rgba(cr,0.0,0.0,1.0,1.0);
+ cairo_stroke(cr);
+
+ cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(pattern));
+ cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0);
+ cairo_stroke(cr);
+
+ origin_handle.pos[0]=150;
+ Geom::Point origin = origin_handle.pos;
+
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(Piecewise<D2<SBasis> >(skeleton),2,.1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ Piecewise<SBasis> x=Piecewise<SBasis>(pattern[0]-origin[0]);
+ Piecewise<SBasis> y=Piecewise<SBasis>(pattern[1]-origin[1]);
+ Interval pattBnds = *bounds_exact(x);
+ int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
+
+ //double pattWidth = uskeleton.cuts.back()/nbCopies;
+ double pattWidth = pattBnds.extent();
+
+ double offs = 0;
+ x-=pattBnds.min();
+ //x*=pattWidth/pattBnds.extent();
+
+ Piecewise<D2<SBasis> >output;
+ for (int i=0; i<nbCopies; i++){
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ offs+=pattWidth;
+ }
+
+ //Perform cut for last segment
+ double tt = uskeleton.cuts.back() - offs;
+ if(tt > 0.) {
+ vector<double> rs = roots(x - tt);
+ rs.push_back(0); rs.push_back(1); //regard endpoints
+ std::sort(rs.begin(), rs.end());
+ std::unique(rs.begin(), rs.end());
+ //enumerate indices of sections to the left of the line
+ for(unsigned i = (x[0].at0()>tt ? 1 : 0); i < rs.size()-1; i+=2) {
+ Piecewise<SBasis> port = portion(x+offs, rs[i], rs[i+1]);
+ output.concat(compose(uskeleton,port)+portion(y, rs[i], rs[i+1])*compose(n,port));
+ }
+ }
+
+ cairo_pw_d2_sb(cr, output);
+ cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ PathAlongPathToy() : origin_handle(150,150) {
+ if(handles.empty()) {
+ handles.push_back(&skel_handles);
+ handles.push_back(&pat_handles);
+ for(int i = 0; i < 8; i++)
+ skel_handles.push_back(200+50*i,400);
+ for(int i = 0; i < 4; i++)
+ pat_handles.push_back(100+uniform()*400,
+ 150+uniform()*100);
+
+ handles.push_back(&origin_handle);
+ }
+ }
+};
+
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PathAlongPathToy);
+ 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/src/toys/path-cairo.cpp b/src/toys/path-cairo.cpp
new file mode 100644
index 0000000..73c2a59
--- /dev/null
+++ b/src/toys/path-cairo.cpp
@@ -0,0 +1,342 @@
+#include <cairo.h>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/utils.h>
+#include <sstream>
+#include <optional>
+
+using namespace Geom;
+
+void cairo_rectangle(cairo_t *cr, Rect const& r) {
+ cairo_rectangle(cr, r.left(), r.top(), r.width(), r.height());
+}
+
+void cairo_convex_hull(cairo_t *cr, ConvexHull const& ch) {
+ if(ch.empty()) return;
+ cairo_move_to(cr, ch[ch.size()-1]);
+ for(auto i : ch) {
+ cairo_line_to(cr, i);
+ }
+}
+
+void cairo_curve(cairo_t *cr, Curve const& c) {
+ if(!cairo_has_current_point(cr))
+ cairo_move_to(cr, c.initialPoint());
+
+ if(LineSegment const* line_segment = dynamic_cast<LineSegment const*>(&c)) {
+ cairo_line_to(cr, (*line_segment)[1][0], (*line_segment)[1][1]);
+ }
+ else if(QuadraticBezier const *quadratic_bezier = dynamic_cast<QuadraticBezier const*>(&c)) {
+ std::vector<Point> points = quadratic_bezier->controlPoints();
+ Point b1 = points[0] + (2./3) * (points[1] - points[0]);
+ Point b2 = b1 + (1./3) * (points[2] - points[0]);
+ cairo_curve_to(cr, b1[0], b1[1],
+ b2[0], b2[1],
+ points[2][0], points[2][1]);
+ }
+ else if(CubicBezier const *cubic_bezier = dynamic_cast<CubicBezier const*>(&c)) {
+ std::vector<Point> points = cubic_bezier->controlPoints();
+ cairo_curve_to(cr, points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]);
+ }
+// else if(EllipticalArc const *svg_elliptical_arc = dynamic_cast<EllipticalArc *>(c)) {
+// //TODO: get at the innards and spit them out to cairo
+// }
+ else {
+ //this case handles sbasis as well as all other curve types
+ Path sbasis_path = cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ //recurse to convert the new path resulting from the sbasis to svgd
+ for(const auto & iter : sbasis_path) {
+ cairo_curve(cr, iter);
+ }
+ }
+}
+
+void cairo_path(cairo_t *cr, Path const &p) {
+ cairo_move_to(cr, p.initialPoint()[0], p.initialPoint()[1]);
+ if(p.size() == 0) { // naked moveto
+ cairo_move_to(cr, p.finalPoint()+Point(8,0));
+ cairo_line_to(cr, p.finalPoint()+Point(-8,0));
+ cairo_move_to(cr, p.finalPoint()+Point(0,8));
+ cairo_line_to(cr, p.finalPoint()+Point(0,-8));
+ return;
+ }
+
+ for(const auto & iter : p) {
+ cairo_curve(cr, iter);
+ }
+ if(p.closed())
+ cairo_close_path(cr);
+}
+
+void cairo_path_stitches(cairo_t *cr, Path const &p) {
+ Path::const_iterator iter;
+ for ( iter = p.begin() ; iter != p.end() ; ++iter ) {
+ Curve const &c=*iter;
+ if (dynamic_cast<Path::StitchSegment const *>(&c)) {
+ cairo_move_to(cr, c.initialPoint()[X], c.initialPoint()[Y]);
+ cairo_line_to(cr, c.finalPoint()[X], c.finalPoint()[Y]);
+
+ std::stringstream s;
+ s << L1(c.finalPoint() - c.initialPoint());
+ std::string ss = s.str();
+ draw_text(cr, c.initialPoint()+Point(5,5), ss.c_str(), false, "Serif 6");
+
+ //std::cout << c.finalPoint() - c.initialPoint() << std::endl;
+ }
+ }
+}
+
+void cairo_path_handles(cairo_t */*cr*/, Path const &/*p*/) {
+ //TODO
+}
+
+void cairo_path(cairo_t *cr, PathVector const &p) {
+ PathVector::const_iterator it;
+ for(it = p.begin(); it != p.end(); ++it) {
+ cairo_path(cr, *it);
+ }
+}
+
+void cairo_path_stitches(cairo_t *cr, PathVector const &p) {
+ PathVector::const_iterator it;
+ for ( it = p.begin() ; it != p.end() ; ++it ) {
+ cairo_path_stitches(cr, *it);
+ }
+}
+
+void cairo_d2_sb(cairo_t *cr, D2<SBasis> const &B) {
+ cairo_path(cr, path_from_sbasis(B, 0.1));
+}
+
+void cairo_d2_sb2d(cairo_t* cr, D2<SBasis2d> const &sb2, Point /*dir*/, double width) {
+ D2<SBasis> B;
+ for(int ui = 0; ui <= 10; ui++) {
+ double u = ui/10.;
+ B[0] = extract_u(sb2[0], u);// + Linear(u);
+ B[1] = extract_u(sb2[1], u);
+ for(unsigned i = 0; i < 2; i ++) {
+ B[i] = B[i]*(width/2) + Linear(width/4);
+ }
+ cairo_d2_sb(cr, B);
+ }
+ for(int vi = 0; vi <= 10; vi++) {
+ double v = vi/10.;
+ B[1] = extract_v(sb2[1], v);// + Linear(v);
+ B[0] = extract_v(sb2[0], v);
+ for(unsigned i = 0; i < 2; i ++) {
+ B[i] = B[i]*(width/2) + Linear(width/4);
+ }
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void cairo_sb2d(cairo_t* cr, SBasis2d const &sb2, Point dir, double width) {
+ D2<SBasis> B;
+ for(int ui = 0; ui <= 10; ui++) {
+ double u = ui/10.;
+ B[0] = extract_u(sb2, u)*dir[0] + Linear(u);
+ B[1] = SBasis(Linear(0,1)) + extract_u(sb2, u)*dir[1];
+ for(unsigned i = 0; i < 2; i ++) {
+ B[i] = B[i]*(width/2) + Linear(width/4);
+ }
+ cairo_d2_sb(cr, B);
+ }
+ for(int vi = 0; vi <= 10; vi++) {
+ double v = vi/10.;
+ B[1] = extract_v(sb2, v)*dir[1] + Linear(v);
+ B[0] = SBasis(Linear(0,1)) + extract_v(sb2, v)*dir[0];
+ for(unsigned i = 0; i < 2; i ++) {
+ B[i] = B[i]*(width/2) + Linear(width/4);
+ }
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void cairo_d2_pw_sb(cairo_t *cr, D2<Piecewise<SBasis> > const &p) {
+ cairo_pw_d2_sb(cr, sectionize(p));
+}
+
+void cairo_pw_d2_sb(cairo_t *cr, Piecewise<D2<SBasis> > const &p) {
+ for(unsigned i = 0; i < p.size(); i++)
+ cairo_d2_sb(cr, p[i]);
+}
+
+
+void draw_line_seg(cairo_t *cr, Geom::Point a, Geom::Point b) {
+ cairo_move_to(cr, a[0], a[1]);
+ cairo_line_to(cr, b[0], b[1]);
+ cairo_stroke(cr);
+}
+
+void draw_spot(cairo_t *cr, Geom::Point h) {
+ draw_line_seg(cr, h, h);
+}
+
+void draw_handle(cairo_t *cr, Geom::Point h) {
+ double x = h[Geom::X];
+ double y = h[Geom::Y];
+ cairo_move_to(cr, x-3, y);
+ cairo_line_to(cr, x+3, y);
+ cairo_move_to(cr, x, y-3);
+ cairo_line_to(cr, x, y+3);
+}
+
+void draw_cross(cairo_t *cr, Geom::Point h) {
+ double x = h[Geom::X];
+ double y = h[Geom::Y];
+ cairo_move_to(cr, x-3, y-3);
+ cairo_line_to(cr, x+3, y+3);
+ cairo_move_to(cr, x+3, y-3);
+ cairo_line_to(cr, x-3, y+3);
+}
+
+void draw_circ(cairo_t *cr, Geom::Point h) {
+ int x = int(h[Geom::X]);
+ int y = int(h[Geom::Y]);
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, x, y, 3, 0, M_PI*2);
+ cairo_stroke(cr);
+}
+
+void draw_ray(cairo_t *cr, Geom::Point h, Geom::Point dir) {
+ draw_line_seg(cr, h, h+dir);
+ Point unit = 3*unit_vector(dir),
+ rot = rot90(unit);
+ draw_line_seg(cr, h+dir, h + dir - unit + rot);
+ draw_line_seg(cr, h+dir, h + dir - unit - rot);
+}
+
+
+void
+cairo_move_to (cairo_t *cr, Geom::Point p1) {
+ cairo_move_to(cr, p1[0], p1[1]);
+}
+
+void
+cairo_line_to (cairo_t *cr, Geom::Point p1) {
+ cairo_line_to(cr, p1[0], p1[1]);
+}
+
+void
+cairo_curve_to (cairo_t *cr, Geom::Point p1,
+ Geom::Point p2, Geom::Point p3) {
+ cairo_curve_to(cr, p1[0], p1[1],
+ p2[0], p2[1],
+ p3[0], p3[1]);
+}
+/*
+ void draw_string(GtkWidget *widget, string s, int x, int y) {
+ PangoLayout *layout = gtk_widget_create_pango_layout(widget, s.c_str());
+ cairo_t* cr = gdk_cairo_create (widget->window);
+ cairo_move_to(cr, x, y);
+ pango_cairo_show_layout(cr, layout);
+ cairo_destroy (cr);
+ }*/
+
+// H in [0,360)
+// S, V, R, G, B in [0,1]
+void convertHSVtoRGB(const double H, const double S, const double V,
+ double& R, double& G, double& B) {
+ int Hi = int(floor(H/60.)) % 6;
+ double f = H/60. - Hi;
+ double p = V*(1-S);
+ double q = V*(1-f*S);
+ double t = V*(1-(1-f)*S);
+ switch(Hi) {
+ case 0: R=V, G=t, B=p; break;
+ case 1: R=q, G=V, B=p; break;
+ case 2: R=p, G=V, B=t; break;
+ case 3: R=p, G=q, B=V; break;
+ case 4: R=t, G=p, B=V; break;
+ case 5: R=V, G=p, B=q; break;
+ }
+}
+
+void draw_line(cairo_t *cr, double a, double b, double c, const Geom::Rect& r) {
+ Geom::Line l(a, b, c);
+ std::optional<Geom::LineSegment> seg = l.clip(r);
+ if (seg) {
+ cairo_move_to(cr, seg->initialPoint());
+ cairo_line_to(cr, seg->finalPoint());
+ cairo_stroke(cr);
+ }
+}
+
+
+void draw_line(cairo_t* cr, const Geom::Line& l, const Geom::Rect& r)
+{
+ std::vector<double> coeff = l.coefficients();
+ draw_line (cr, coeff[0], coeff[1], coeff[2], r);
+}
+
+
+void draw_line(cairo_t *cr, Geom::Point n, double dist, Geom::Rect r) {
+ draw_line(cr, n[0], n[1], dist, r);
+}
+
+
+void draw_ray(cairo_t *cr, const Geom::Ray& ray, const Geom::Rect& r)
+{
+ LineSegment ls;
+
+ for (size_t i = 0; i < 4; ++i)
+ {
+ ls.setInitial (r.corner(i));
+ ls.setFinal (r.corner(i+1));
+ OptCrossing cx = intersection (ls, ray);
+ if (cx)
+ {
+ Point P = ray.pointAt ((*cx).tb);
+ draw_line_seg (cr, ray.origin(), P);
+ break;
+ }
+ }
+}
+
+void draw_line_segment(cairo_t *cr, const Geom::LineSegment& ls, const Geom::Rect& r)
+{
+ if(r.contains(ls[0])) {
+ if(r.contains(ls[1])) {
+ draw_line_seg(cr, ls[0], ls[1]);
+ } else {
+ draw_ray(cr, Geom::Ray(ls[0], ls[1]), r);
+ }
+
+ } else {
+ if(r.contains(ls[1])) {
+ draw_ray(cr, Geom::Ray(ls[1], ls[0]), r);
+ } else {
+ draw_line(cr, Geom::Line(ls[0], ls[1]), r);
+ }
+
+ }
+}
+
+void draw_line_seg_with_arrow(cairo_t *cr, Geom::Point a, Geom::Point b, double dangle, double radius) {
+ double angle = atan2(a-b);
+ cairo_move_to(cr, a);
+ cairo_line_to(cr, b);
+
+ cairo_move_to(cr, b);
+ cairo_line_to(cr, Point::polar(angle + dangle, radius) + b);
+ cairo_move_to(cr, b);
+ cairo_line_to(cr, Point::polar(angle - dangle, radius) + b);
+ cairo_stroke(cr);
+}
+
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
diff --git a/src/toys/path-effects.cpp b/src/toys/path-effects.cpp
new file mode 100644
index 0000000..fdd7ef0
--- /dev/null
+++ b/src/toys/path-effects.cpp
@@ -0,0 +1,140 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/sbasis-math.h>
+
+#include <cstdlib>
+
+using namespace Geom;
+
+Piecewise<SBasis > sore_tooth(Interval intv) {
+ Piecewise<SBasis > out;
+ double t = intv.min();
+ Point p(0,0);
+ out.push_cut(0);
+ double r = 20;
+ double dir = 0.5;
+ while(t < intv.max()) {
+ double nt = t + 10;
+ if(nt > intv.max())
+ nt = intv.max();
+ SBasis zag(r*Linear(dir,-dir));
+ out.push(zag, nt);
+ t = nt;
+ dir = -dir;
+ }
+ return out;
+}
+
+Piecewise< D2<SBasis> > zaggy(Interval intv, double dt, double radius) {
+ Piecewise<D2<SBasis> > out;
+ double t = intv.min();
+ Point p(0,0);
+ out.push_cut(0);
+ while(t < intv.max()) {
+ double nt = t + uniform()*dt;
+ if(nt > intv.max())
+ nt = intv.max();
+ Point np = Point((uniform()-0.5)*2*radius, (uniform()-0.5)*2*radius);
+ D2<SBasis> zag(SBasis(p[0],np[0]), SBasis(p[1],np[1]));
+ p = np;
+ //std::cout << t <<","<< nt << p << np << std::endl;
+ out.push(zag, nt);
+ t = nt;
+ }
+ return out;
+}
+
+
+class BoolOps: public Toy {
+ PathVector pv;
+ PointHandle offset_handle;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ Geom::Translate t(offset_handle.pos);
+
+ cairo_set_line_width(cr, 1);
+ cairo_set_source_rgb(cr, 0.75,0.75,1);
+
+ //cairo_shape(cr, bst);
+ cairo_path(cr, pv*t);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgb(cr, 0,0,0);
+ for(const auto & i : pv) {
+ Piecewise<D2<SBasis> > B = i.toPwSb();
+ Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B)));
+ Piecewise<SBasis > al = arcLengthSb(B);
+
+#if 0
+ Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset;
+ PathVector offset_path = path_from_piecewise(offset_curve, 0.1);
+
+ cairo_path(cr, offset_path*t);
+ cairo_stroke(cr);
+#endif
+ //Piecewise<D2<SBasis> > zz_curve = B+zaggy(B.domain(), 0.1, 20);//al*n;
+ //Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+
+ // compose(sore_tooth(Interval(al.firstValue(),al.lastValue())), al)*n;
+ Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+
+ sin(al*0.1)*10*n;
+ PathVector zz_path = path_from_piecewise(zz_curve, 0.1);
+
+ cairo_path(cr, zz_path*t);
+ cairo_stroke(cr);
+ }
+ for(const auto & i : pv) {
+ if(i.size() == 0) {
+ *notify << "naked moveto;";
+ } else
+ for(const auto & j : i) {
+ const Curve* c = &j;
+ *notify << typeid(*c).name() << ';' ;
+ }
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ public:
+ BoolOps () {}
+
+ void first_time(int argc, char** argv) override {
+ const char *path_b_name="star.svgd";
+ if(argc > 1)
+ path_b_name = argv[1];
+ pv = read_svgd(path_b_name);
+ std::cout << pv.size() << "\n";
+ std::cout << pv[0].size() << "\n";
+ pv *= Translate(-pv[0].initialPoint());
+
+ Rect bounds = *pv[0].boundsExact();
+ handles.push_back(&offset_handle);
+ offset_handle.pos = bounds.midpoint() - bounds.corner(0);
+
+ //bs = cleanup(pv);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new BoolOps());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/path-toy.py b/src/toys/path-toy.py
new file mode 100644
index 0000000..631482f
--- /dev/null
+++ b/src/toys/path-toy.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+
+import py2geom
+import toyframework
+import random,gtk
+from py2geom_glue import *
+
+class PathToy(toyframework.Toy):
+ def __init__(self):
+ toyframework.Toy.__init__(self)
+ self.handles.append(toyframework.PointHandle(200, 200))
+ self.path_b_name="star.svgd"
+ self.pv = py2geom.read_svgd(self.path_b_name);
+ centr = py2geom.Point()
+ for p in self.pv:
+ c,area = py2geom.centroid(p.toPwSb())
+ centr += c
+ self.pv = self.pv*py2geom.Matrix(py2geom.Translate(-centr))
+ def draw(self, cr, pos, save):
+ cr.set_source_rgba (0., 0., 0., 1)
+ cr.set_line_width (1)
+
+
+ B = (self.pv[0]*py2geom.Matrix(py2geom.Translate(*self.handles[0].pos))).toPwSb();
+ n = py2geom.rot90(py2geom.unit_vector(py2geom.derivative(B), 0.01, 3));
+ al = py2geom.arcLengthSb(B, 0.1);
+ offset = 10.
+
+ offset_curve = B+py2geom.sin(al*0.1, 0.01, 2)*n*10.
+ offset_path = py2geom.path_from_piecewise(offset_curve, 0.1, True)
+
+ py2geom.cairo_path(cr, offset_path)
+ cr.stroke()
+
+ self.notify = ''
+ toyframework.Toy.draw(self, cr, pos, save)
+
+t = PathToy()
+import sys
+
+toyframework.init(sys.argv, t, 500, 500)
diff --git a/src/toys/pencil-2.cpp b/src/toys/pencil-2.cpp
new file mode 100644
index 0000000..a312083
--- /dev/null
+++ b/src/toys/pencil-2.cpp
@@ -0,0 +1,1133 @@
+/*
+ * pencil-2 Toy - point fitting.
+ *
+ * 2009 njh
+ *
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/math-utils.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define SP_HUGE 1e5
+#define noBEZIER_DEBUG
+
+#ifdef HAVE_IEEEFP_H
+# include <ieeefp.h>
+#endif
+
+namespace Geom{
+
+namespace BezierFitter{
+
+typedef Point BezierCurve[];
+
+/* Forward declarations */
+static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2, double tolerance_sq);
+static void estimate_lengths(Point bezier[],
+ Point const data[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2);
+static void estimate_bi(Point b[4], unsigned ei,
+ Point const data[], double const u[], unsigned len);
+static void reparameterize(Point const d[], unsigned len, double u[], CubicBezier const & bezCurve);
+static double NewtonRaphsonRootFind(CubicBezier const & Q, Point const &P, double u);
+static Point darray_center_tangent(Point const d[], unsigned center, unsigned length);
+static Point darray_right_tangent(Point const d[], unsigned const len);
+static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]);
+static void chord_length_parameterize(Point const d[], double u[], unsigned len);
+static double compute_max_error_ratio(Point const d[], double const u[], unsigned len,
+ BezierCurve const bezCurve, double tolerance,
+ unsigned *splitPoint);
+static double compute_hook(Point const &a, Point const &b, double const u,
+ CubicBezier const & bezCurve,
+ double const tolerance);
+static double compute_hook(Point const &a, Point const &b, double const u,
+ BezierCurve const bezCurve,
+ double const tolerance) {
+ CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]);
+ return compute_hook(a, b, u, cb, tolerance);
+
+}
+
+
+static void reparameterize_pts(Point const d[], unsigned len, double u[], BezierCurve const bezCurve) {
+ CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]);
+ reparameterize(d, len, u, cb);
+}
+
+
+
+static Point const unconstrained_tangent(0, 0);
+
+
+/*
+ * B0, B1, B2, B3 : Bezier multipliers
+ */
+
+#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B2(u) ( 3 * u * u * ( 1.0 - u ) )
+#define B3(u) ( u * u * u )
+
+#ifdef BEZIER_DEBUG
+# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) )
+# define BEZIER_ASSERT(b) do { \
+ DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \
+ DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \
+ DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \
+ DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \
+ } while(0)
+#else
+# define DOUBLE_ASSERT(x) do { } while(0)
+# define BEZIER_ASSERT(b) do { } while(0)
+#endif
+
+
+Point
+bezier_pt(unsigned const degree, Point const V[], double const t)
+{
+ return bernstein_value_at(t, V, degree);
+
+}
+
+/*
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ * Approximate unit tangents at endpoints and "center" of digitized curve
+ */
+
+/**
+ * Estimate the (forward) tangent at point d[first + 0.5].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ * \pre (2 \<= len) and (d[0] != d[1]).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len)
+{
+ assert( len >= 2 );
+ assert( d[0] != d[1] );
+ return unit_vector( d[1] - d[0] );
+}
+
+/**
+ * Estimates the (backward) tangent at d[last - 0.5].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+static Point
+darray_right_tangent(Point const d[], unsigned const len)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ unsigned const prev = last - 1;
+ assert( d[last] != d[prev] );
+ return unit_vector( d[prev] - d[last] );
+}
+
+/**
+ * Estimate the (forward) tangent at point d[0].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ *
+ * \pre 2 \<= len.
+ * \pre d[0] != d[1].
+ * \pre all[p in d] in_svg_plane(p).
+ * \post is_unit_vector(ret).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ for (unsigned i = 1;;) {
+ Point const pi(d[i]);
+ Point const t(pi - d[0]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ ++i;
+ if (i == len) {
+ return ( distsq == 0
+ ? darray_left_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[last].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+Point
+darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ unsigned const last = len - 1;
+ for (unsigned i = last - 1;; i--) {
+ Point const pi(d[i]);
+ Point const t(pi - d[last]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ if (i == 0) {
+ return ( distsq == 0
+ ? darray_right_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[center], by averaging the two
+ * segments connected to d[center] (and then normalizing the result).
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre (0 \< center \< len - 1) and d is uniqued (at least in
+ * the immediate vicinity of \a center).
+ */
+static Point
+darray_center_tangent(Point const d[],
+ unsigned const center,
+ unsigned const len)
+{
+ assert( center != 0 );
+ assert( center < len - 1 );
+
+ Point ret;
+ if ( d[center + 1] == d[center - 1] ) {
+ /* Rotate 90 degrees in an arbitrary direction. */
+ Point const diff = d[center] - d[center - 1];
+ ret = rot90(diff);
+ } else {
+ ret = d[center - 1] - d[center + 1];
+ }
+ ret.normalize();
+ return ret;
+}
+
+
+int
+bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers);
+
+int
+bezier_fit_cubic_full(Point bezier[], int split_points[],
+ Point const data[], int const len,
+ Point const &tHat1, Point const &tHat2,
+ double const error, unsigned const max_beziers);
+
+
+/**
+ * Fit a single-segment Bezier curve to a set of digitized points.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic(Point *bezier, Point const *data, int len, double error)
+{
+ return bezier_fit_cubic_r(bezier, data, len, error, 1);
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, with
+ * possible weedout of identical points and NaNs.
+ *
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers)
+{
+ if(bezier == NULL ||
+ data == NULL ||
+ len <= 0 ||
+ max_beziers >= (1ul << (31 - 2 - 1 - 3)))
+ return -1;
+
+ Point *uniqued_data = new Point[len];
+ unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data);
+
+ if ( uniqued_len < 2 ) {
+ delete[] uniqued_data;
+ return 0;
+ }
+
+ /* Call fit-cubic function with recursion. */
+ int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len,
+ unconstrained_tangent, unconstrained_tangent,
+ error, max_beziers);
+ delete[] uniqued_data;
+ return ret;
+}
+
+
+
+/**
+ * Copy points from src to dest, filter out points containing NaN and
+ * adjacent points with equal x and y.
+ * \return length of dest
+ */
+static unsigned
+copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[])
+{
+ unsigned si = 0;
+ for (;;) {
+ if ( si == src_len ) {
+ return 0;
+ }
+ if (!std::isnan(src[si][X]) &&
+ !std::isnan(src[si][Y])) {
+ dest[0] = Point(src[si]);
+ ++si;
+ break;
+ }
+ si++;
+ }
+ unsigned di = 0;
+ for (; si < src_len; ++si) {
+ Point const src_pt = Point(src[si]);
+ if ( src_pt != dest[di]
+ && !std::isnan(src_pt[X])
+ && !std::isnan(src_pt[Y])) {
+ dest[++di] = src_pt;
+ }
+ }
+ unsigned dest_len = di + 1;
+ assert( dest_len <= src_len );
+ return dest_len;
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, without
+ * possible weedout of identical points and NaNs.
+ *
+ * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1].
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ */
+int
+bezier_fit_cubic_full(Point bezier[], int split_points[],
+ Point const data[], int const len,
+ Point const &tHat1, Point const &tHat2,
+ double const error, unsigned const max_beziers)
+{
+ int const maxIterations = 4; /* std::max times to try iterating */
+
+ if(!(bezier != NULL) ||
+ !(data != NULL) ||
+ !(len > 0) ||
+ !(max_beziers >= 1) ||
+ !(error >= 0.0))
+ return -1;
+
+ if ( len < 2 ) return 0;
+
+ if ( len == 2 ) {
+ /* We have 2 points, which can be fitted trivially. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+ double const dist = distance(bezier[0], bezier[3]) / 3.0;
+ if (std::isnan(dist)) {
+ /* Numerical problem, fall back to straight line segment. */
+ bezier[1] = bezier[0];
+ bezier[2] = bezier[3];
+ } else {
+ bezier[1] = ( is_zero(tHat1)
+ ? ( 2 * bezier[0] + bezier[3] ) / 3.
+ : bezier[0] + dist * tHat1 );
+ bezier[2] = ( is_zero(tHat2)
+ ? ( bezier[0] + 2 * bezier[3] ) / 3.
+ : bezier[3] + dist * tHat2 );
+ }
+ BEZIER_ASSERT(bezier);
+ return 1;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ unsigned splitPoint; /* Point to split point set at. */
+ bool is_corner;
+ {
+ double *u = new double[len];
+ chord_length_parameterize(data, u, len);
+ if ( u[len - 1] == 0.0 ) {
+ /* Zero-length path: every point in data[] is the same.
+ *
+ * (Clients aren't allowed to pass such data; handling the case is defensive
+ * programming.)
+ */
+ delete[] u;
+ return 0;
+ }
+
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize_pts(data, len, u, bezier);
+
+ /* Find max deviation of points to fitted curve. */
+ double const tolerance = std::sqrt(error + 1e-9);
+ double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+
+ /* If error not too large, then try some reparameterization and iteration. */
+ if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) {
+ for (int i = 0; i < maxIterations; i++) {
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize_pts(data, len, u, bezier);
+ maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+ }
+ }
+ delete[] u;
+ is_corner = (maxErrorRatio < 0);
+ }
+
+ if (is_corner) {
+ assert(splitPoint < unsigned(len));
+ if (splitPoint == 0) {
+ if (is_zero(tHat1)) {
+ /* Got spike even with unconstrained initial tangent. */
+ ++splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2,
+ error, max_beziers);
+ }
+ } else if (splitPoint == unsigned(len - 1)) {
+ if (is_zero(tHat2)) {
+ /* Got spike even with unconstrained final tangent. */
+ --splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent,
+ error, max_beziers);
+ }
+ }
+ }
+
+ if ( 1 < max_beziers ) {
+ /*
+ * Fitting failed -- split at max error point and fit recursively
+ */
+ unsigned const rec_max_beziers1 = max_beziers - 1;
+
+ Point recTHat2, recTHat1;
+ if (is_corner) {
+ if(!(0 < splitPoint && splitPoint < unsigned(len - 1)))
+ return -1;
+ recTHat1 = recTHat2 = unconstrained_tangent;
+ } else {
+ /* Unit tangent vector at splitPoint. */
+ recTHat2 = darray_center_tangent(data, splitPoint, len);
+ recTHat1 = -recTHat2;
+ }
+ int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1,
+ tHat1, recTHat2, error, rec_max_beziers1);
+ if ( nsegs1 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[1]: recursive call failed\n");
+#endif
+ return -1;
+ }
+ assert( nsegs1 != 0 );
+ if (split_points != NULL) {
+ split_points[nsegs1 - 1] = splitPoint;
+ }
+ unsigned const rec_max_beziers2 = max_beziers - nsegs1;
+ int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4,
+ ( split_points == NULL
+ ? NULL
+ : split_points + nsegs1 ),
+ data + splitPoint, len - splitPoint,
+ recTHat1, tHat2, error, rec_max_beziers2);
+ if ( nsegs2 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[2]: recursive call failed\n");
+#endif
+ return -1;
+ }
+
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n",
+ nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers);
+#endif
+ return nsegs1 + nsegs2;
+ } else {
+ return -1;
+ }
+}
+
+
+/**
+ * Fill in \a bezier[] based on the given data and tangent requirements, using
+ * a least-squares fit.
+ *
+ * Each of tHat1 and tHat2 should be either a zero vector or a unit vector.
+ * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise,
+ * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3].
+ *
+ * \param tolerance_sq Used only for an initial guess as to tangent directions
+ * when \a tHat1 or \a tHat2 is zero.
+ */
+static void
+generate_bezier(Point bezier[],
+ Point const data[], double const u[], unsigned const len,
+ Point const &tHat1, Point const &tHat2,
+ double const tolerance_sq)
+{
+ bool const est1 = is_zero(tHat1);
+ bool const est2 = is_zero(tHat2);
+ Point est_tHat1( est1
+ ? darray_left_tangent(data, len, tolerance_sq)
+ : tHat1 );
+ Point est_tHat2( est2
+ ? darray_right_tangent(data, len, tolerance_sq)
+ : tHat2 );
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ /* We find that darray_right_tangent tends to produce better results
+ for our current freehand tool than full estimation. */
+ if (est1) {
+ estimate_bi(bezier, 1, data, u, len);
+ if (bezier[1] != bezier[0]) {
+ est_tHat1 = unit_vector(bezier[1] - bezier[0]);
+ }
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ }
+}
+
+
+static void
+estimate_lengths(Point bezier[],
+ Point const data[], double const uPrime[], unsigned const len,
+ Point const &tHat1, Point const &tHat2)
+{
+ double C[2][2]; /* Matrix C. */
+ double X[2]; /* Matrix X. */
+
+ /* Create the C and X matrices. */
+ C[0][0] = 0.0;
+ C[0][1] = 0.0;
+ C[1][0] = 0.0;
+ C[1][1] = 0.0;
+ X[0] = 0.0;
+ X[1] = 0.0;
+
+ /* First and last control points of the Bezier curve are positioned exactly at the first and
+ last data points. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+
+ for (unsigned i = 0; i < len; i++) {
+ /* Bezier control point coefficients. */
+ double const b0 = B0(uPrime[i]);
+ double const b1 = B1(uPrime[i]);
+ double const b2 = B2(uPrime[i]);
+ double const b3 = B3(uPrime[i]);
+
+ /* rhs for eqn */
+ Point const a1 = b1 * tHat1;
+ Point const a2 = b2 * tHat2;
+
+ C[0][0] += dot(a1, a1);
+ C[0][1] += dot(a1, a2);
+ C[1][0] = C[0][1];
+ C[1][1] += dot(a2, a2);
+
+ /* Additional offset to the data point from the predicted point if we were to set bezier[1]
+ to bezier[0] and bezier[2] to bezier[3]. */
+ Point const shortfall
+ = ( data[i]
+ - ( ( b0 + b1 ) * bezier[0] )
+ - ( ( b2 + b3 ) * bezier[3] ) );
+ X[0] += dot(a1, shortfall);
+ X[1] += dot(a2, shortfall);
+ }
+
+ /* We've constructed a pair of equations in the form of a matrix product C * alpha = X.
+ Now solve for alpha. */
+ double alpha_l, alpha_r;
+
+ /* Compute the determinants of C and X. */
+ double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+ if ( det_C0_C1 != 0 ) {
+ /* Apparently Kramer's rule. */
+ double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+ double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+ alpha_l = det_X_C1 / det_C0_C1;
+ alpha_r = det_C0_X / det_C0_C1;
+ } else {
+ /* The matrix is under-determined. Try requiring alpha_l == alpha_r.
+ *
+ * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same
+ * variable in the equations. We can do this by adding the columns of C to form a single
+ * column, to be multiplied by alpha to give the column vector X.
+ *
+ * We try each row in turn.
+ */
+ double const c0 = C[0][0] + C[0][1];
+ if (c0 != 0) {
+ alpha_l = alpha_r = X[0] / c0;
+ } else {
+ double const c1 = C[1][0] + C[1][1];
+ if (c1 != 0) {
+ alpha_l = alpha_r = X[1] / c1;
+ } else {
+ /* Let the below code handle this. */
+ alpha_l = alpha_r = 0.;
+ }
+ }
+ }
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get
+ coincident control points that lead to divide by zero in any subsequent
+ NewtonRaphsonRootFind() call.) */
+ /// \todo Check whether this special-casing is necessary now that
+ /// NewtonRaphsonRootFind handles non-positive denominator.
+ if ( alpha_l < 1.0e-6 ||
+ alpha_r < 1.0e-6 )
+ {
+ alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0;
+ }
+
+ /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and
+ right, respectively. */
+ bezier[1] = alpha_l * tHat1 + bezier[0];
+ bezier[2] = alpha_r * tHat2 + bezier[3];
+
+ return;
+}
+
+static double lensq(Point const p) {
+ return dot(p, p);
+}
+
+static void
+estimate_bi(Point bezier[4], unsigned const ei,
+ Point const data[], double const u[], unsigned const len)
+{
+ if(!(1 <= ei && ei <= 2))
+ return;
+ unsigned const oi = 3 - ei;
+ double num[2] = {0., 0.};
+ double den = 0.;
+ for (unsigned i = 0; i < len; ++i) {
+ double const ui = u[i];
+ double const b[4] = {
+ B0(ui),
+ B1(ui),
+ B2(ui),
+ B3(ui)
+ };
+
+ for (unsigned d = 0; d < 2; ++d) {
+ num[d] += b[ei] * (b[0] * bezier[0][d] +
+ b[oi] * bezier[oi][d] +
+ b[3] * bezier[3][d] +
+ - data[i][d]);
+ }
+ den -= b[ei] * b[ei];
+ }
+
+ if (den != 0.) {
+ for (unsigned d = 0; d < 2; ++d) {
+ bezier[ei][d] = num[d] / den;
+ }
+ } else {
+ bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.;
+ }
+}
+
+/**
+ * Given set of points and their parameterization, try to find a better assignment of parameter
+ * values for the points.
+ *
+ * \param d Array of digitized points.
+ * \param u Current parameter values.
+ * \param bezCurve Current fitted curve.
+ * \param len Number of values in both d and u arrays.
+ * Also the size of the array that is allocated for return.
+ */
+static void
+reparameterize(Point const d[],
+ unsigned const len,
+ double u[],
+ CubicBezier const &bezCurve)
+{
+ assert( 2 <= len );
+
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* Otherwise, consider including 0 and last in the below loop. */
+
+ for (unsigned i = 1; i < last; i++) {
+ u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]);
+ }
+}
+
+/**
+ * Use Newton-Raphson iteration to find better root.
+ *
+ * \param Q Current fitted curve
+ * \param P Digitized point
+ * \param u Parameter value for "P"
+ *
+ * \return Improved u
+ */
+static double
+NewtonRaphsonRootFind(CubicBezier const &Q, Point const &P, double const u)
+{
+ assert( 0.0 <= u );
+ assert( u <= 1.0 );
+
+ std::vector<Point> Q_u = Q.pointAndDerivatives(u, 2);
+
+ /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the
+ distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the
+ distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum
+ distance from P to Q(u)). */
+ Point const diff = Q_u[0] - P;
+ double numerator = dot(diff, Q_u[1]);
+ double denominator = dot(Q_u[1], Q_u[1]) + dot(diff, Q_u[2]);
+
+ double improved_u;
+ if ( denominator > 0. ) {
+ /* One iteration of Newton-Raphson:
+ improved_u = u - f(u)/f'(u) */
+ improved_u = u - ( numerator / denominator );
+ } else {
+ /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather
+ than local minimum), so we move an arbitrary amount in the right direction. */
+ if ( numerator > 0. ) {
+ improved_u = u * .98 - .01;
+ } else if ( numerator < 0. ) {
+ /* Deliberately asymmetrical, to reduce the chance of cycling. */
+ improved_u = .031 + u * .98;
+ } else {
+ improved_u = u;
+ }
+ }
+
+ if (!std::isfinite(improved_u)) {
+ improved_u = u;
+ } else if ( improved_u < 0.0 ) {
+ improved_u = 0.0;
+ } else if ( improved_u > 1.0 ) {
+ improved_u = 1.0;
+ }
+
+ /* Ensure that improved_u isn't actually worse. */
+ {
+ double const diff_lensq = lensq(diff);
+ for (double proportion = .125; ; proportion += .125) {
+ if ( lensq( Q.pointAt(improved_u) - P ) > diff_lensq ) {
+ if ( proportion > 1.0 ) {
+ //g_warning("found proportion %g", proportion);
+ improved_u = u;
+ break;
+ }
+ improved_u = ( ( 1 - proportion ) * improved_u +
+ proportion * u );
+ } else {
+ break;
+ }
+ }
+ }
+
+ DOUBLE_ASSERT(improved_u);
+ return improved_u;
+}
+
+
+/**
+ * Assign parameter values to digitized points using relative distances between points.
+ *
+ * \pre Parameter array u must have space for \a len items.
+ */
+static void
+chord_length_parameterize(Point const d[], double u[], unsigned const len)
+{
+ if(!( 2 <= len ))
+ return;
+
+ /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */
+ u[0] = 0.0;
+ for (unsigned i = 1; i < len; i++) {
+ double const dist = distance(d[i], d[i-1]);
+ u[i] = u[i-1] + dist;
+ }
+
+ /* Then scale to [0.0 .. 1.0]. */
+ double tot_len = u[len - 1];
+ if(!( tot_len != 0 ))
+ return;
+ if (std::isfinite(tot_len)) {
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] /= tot_len;
+ }
+ } else {
+ /* We could do better, but this probably never happens anyway. */
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] = i / (double) ( len - 1 );
+ }
+ }
+
+ /** \todo
+ * It's been reported that u[len - 1] can differ from 1.0 on some
+ * systems (amd64), despite it having been calculated as x / x where x
+ * is isFinite and non-zero.
+ */
+ if (u[len - 1] != 1) {
+ double const diff = u[len - 1] - 1;
+ if (fabs(diff) > 1e-13) {
+ assert(0); // No warnings in 2geom
+ //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1",
+ // u[len - 1], diff);
+ }
+ u[len - 1] = 1;
+ }
+
+#ifdef BEZIER_DEBUG
+ assert( u[0] == 0.0 && u[len - 1] == 1.0 );
+ for (unsigned i = 1; i < len; i++) {
+ assert( u[i] >= u[i-1] );
+ }
+#endif
+}
+
+
+
+
+/**
+ * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum
+ * error is non-zero) set \a *splitPoint to the corresponding index.
+ *
+ * \pre 2 \<= len.
+ * \pre u[0] == 0.
+ * \pre u[len - 1] == 1.0.
+ * \post ((ret == 0.0)
+ * || ((*splitPoint \< len - 1)
+ * \&\& (*splitPoint != 0 || ret \< 0.0))).
+ */
+static double
+compute_max_error_ratio(Point const d[], double const u[], unsigned const len,
+ BezierCurve const bezCurve, double const tolerance,
+ unsigned *const splitPoint)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* I.e. assert that the error for the first & last points is zero.
+ * Otherwise we should include those points in the below loop.
+ * The assertion is also necessary to ensure 0 < splitPoint < last.
+ */
+
+ double maxDistsq = 0.0; /* Maximum error */
+ double max_hook_ratio = 0.0;
+ unsigned snap_end = 0;
+ Point prev = bezCurve[0];
+ for (unsigned i = 1; i <= last; i++) {
+ Point const curr = bezier_pt(3, bezCurve, u[i]);
+ double const distsq = lensq( curr - d[i] );
+ if ( distsq > maxDistsq ) {
+ maxDistsq = distsq;
+ *splitPoint = i;
+ }
+ double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance);
+ if (max_hook_ratio < hook_ratio) {
+ max_hook_ratio = hook_ratio;
+ snap_end = i;
+ }
+ prev = curr;
+ }
+
+ double const dist_ratio = std::sqrt(maxDistsq) / tolerance;
+ double ret;
+ if (max_hook_ratio <= dist_ratio) {
+ ret = dist_ratio;
+ } else {
+ assert(0 < snap_end);
+ ret = -max_hook_ratio;
+ *splitPoint = snap_end - 1;
+ }
+ assert( ret == 0.0
+ || ( ( *splitPoint < last )
+ && ( *splitPoint != 0 || ret < 0. ) ) );
+ return ret;
+}
+
+/**
+ * Whereas compute_max_error_ratio() checks for itself that each data point
+ * is near some point on the curve, this function checks that each point on
+ * the curve is near some data point (or near some point on the polyline
+ * defined by the data points, or something like that: we allow for a
+ * "reasonable curviness" from such a polyline). "Reasonable curviness"
+ * means we draw a circle centred at the midpoint of a..b, of radius
+ * proportional to the length |a - b|, and require that each point on the
+ * segment of bezCurve between the parameters of a and b be within that circle.
+ * If any point P on the bezCurve segment is outside of that allowable
+ * region (circle), then we return some metric that increases with the
+ * distance from P to the circle.
+ *
+ * Given that this is a fairly arbitrary criterion for finding appropriate
+ * places for sharp corners, we test only one point on bezCurve, namely
+ * the point on bezCurve with parameter halfway between our estimated
+ * parameters for a and b. (Alternatives are taking the farthest of a
+ * few parameters between those of a and b, or even using a variant of
+ * NewtonRaphsonFindRoot() for finding the maximum rather than minimum
+ * distance.)
+ */
+static double
+compute_hook(Point const &a, Point const &b, double const u, CubicBezier const & bezCurve,
+ double const tolerance)
+{
+ Point const P = bezCurve.pointAt(u);
+ double const dist = distance((a+b)*.5, P);
+ if (dist < tolerance) {
+ return 0;
+ }
+ double const allowed = distance(a, b) + tolerance;
+ return dist / allowed;
+ /** \todo
+ * effic: Hooks are very rare. We could start by comparing
+ * distsq, only resorting to the more expensive L2 in cases of
+ * uncertainty.
+ */
+}
+
+}
+
+}
+
+#include <2geom/bezier-utils.h>
+
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+class PointToBezierTester: public Toy {
+ //std::vector<Slider> sliders;
+ PointHandle adjuster, adjuster2, adjuster3;
+ std::vector<Toggle> toggles;
+ Piecewise<D2<SBasis > > stroke;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_save(cr);
+
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 0.5);
+ adjuster2.pos[0]=150;
+ adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.);
+ cairo_move_to(cr, 150, 150);
+ cairo_line_to(cr, 150, 450);
+ cairo_stroke(cr);
+ ostringstream val_s;
+ double scale0=(450-adjuster2.pos[1])/300;
+ double curve_precision = pow(10, scale0*5-2);
+ val_s << curve_precision;
+ draw_text(cr, adjuster2.pos, val_s.str().c_str());
+ cairo_restore(cr);
+
+ cairo_save(cr);
+
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 0.5);
+
+ if(!mouses.empty()) {
+ cairo_move_to(cr, mouses[0]);
+ for(auto & mouse : mouses) {
+ cairo_line_to(cr, mouse);
+ }
+ cairo_stroke(cr);
+ }
+
+ if(!mouses.empty()) {
+ Point bezier[1000];
+ int segsgenerated;
+ {
+ Timer tm;
+
+ tm.ask_for_timeslice();
+ tm.start();
+ segsgenerated = bezier_fit_cubic_r(bezier, &mouses[0],
+ mouses.size(), curve_precision, 240);
+
+ Timer::Time als_time = tm.lap();
+ *notify << "original time = " << als_time << std::endl;
+ }
+
+ if(1) {
+ cairo_save(cr);
+ cairo_set_source_rgba (cr, 0., 1., 0., 1);
+ cairo_move_to(cr, bezier[0]);
+ int bezi=1;
+ for(int i = 0; i < segsgenerated; i ++) {
+ cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]);
+ bezi += 4;
+ }
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ }
+ {
+ Timer tm;
+
+ tm.ask_for_timeslice();
+ tm.start();
+ segsgenerated = Geom::BezierFitter::bezier_fit_cubic_r(bezier, &mouses[0],
+ mouses.size(), curve_precision, 240);
+
+ Timer::Time als_time = tm.lap();
+ *notify << "experimental version time = " << als_time << std::endl;
+ }
+
+ if (1) {
+ cairo_save(cr);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_move_to(cr, bezier[0]);
+ int bezi=1;
+ for(int i = 0; i < segsgenerated; i ++) {
+ cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]);
+ bezi += 4;
+ }
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ *notify << "segments : "<< segsgenerated <<"\n";
+ }
+ cairo_restore(cr);
+
+ /*
+ Point p(25, height - 50), d(50,25);
+ toggles[0].bounds = Rect(p, p + d);
+ p+= Point(75, 0);
+ toggles[1].bounds = Rect(p, p + d);
+ draw_toggles(cr, toggles);
+ */
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 's') toggles[0].toggle();
+ redraw();
+ }
+ vector<Point> mouses;
+ int mouse_drag;
+
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ if(!selected) {
+ mouse_drag = 1;
+ mouses.clear();
+ }
+ }
+
+
+ void mouse_moved(GdkEventMotion* e) override {
+ if(mouse_drag) {
+ mouses.emplace_back(e->x, e->y);
+ redraw();
+ } else {
+ Toy::mouse_moved(e);
+ }
+ }
+
+ void mouse_released(GdkEventButton* e) override {
+ mouse_drag = 0;
+ stroke.clear();
+ stroke.push_cut(0);
+ Path pth;
+ for(unsigned i = 2; i < mouses.size(); i+=2) {
+ pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i]));
+ }
+ stroke = pth.toPwSb();
+ Toy::mouse_released(e);
+ }
+
+ PointToBezierTester() {
+ adjuster2.pos = Geom::Point(150,300);
+ handles.push_back(&adjuster2);
+ toggles.emplace_back("Seq", false);
+ toggles.emplace_back("Linfty", true);
+ //}
+ //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t"));
+ //handles.push_back(&(sliders[0]));
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PointToBezierTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/pencil.cpp b/src/toys/pencil.cpp
new file mode 100644
index 0000000..1ac3587
--- /dev/null
+++ b/src/toys/pencil.cpp
@@ -0,0 +1,374 @@
+/*
+ * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier.
+ *
+ * Copyright 2007 jf barraud.
+ * 2008 njh
+ *
+ * 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.
+ *
+ */
+
+// mainly experimental atm...
+// do not expect to find anything understandable here atm.
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/basic-intersection.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define ZERO 1e-7
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#include <stdio.h>
+#include <gsl/gsl_poly.h>
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale);
+ B[1] = Linear(450) - p[i]*vscale;
+ cairo_d2_sb(cr, B);
+ }
+}
+
+//===================================================================================
+
+D2<SBasis>
+naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){
+
+ Piecewise<D2<SBasis> > dM = derivative(M);
+ Point M0 = M(t0);
+ Point dM0 = dM(t0)*(t1-t0);
+ Point M1 = M(t1);
+ Point dM1 = dM(t1)*(t1-t0);
+ D2<SBasis> result;
+ for (unsigned dim=0; dim<2; dim++){
+ SBasis r(2, Linear());
+ r[0] = Linear(M0[dim],M1[dim]);
+ r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim]));
+ result[dim] = r;
+ }
+ return result;
+}
+
+D2<SBasis>
+sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){
+ Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1;
+ Piecewise<D2<SBasis> > dM,d2M;
+ dM=derivative(M);
+ d2M=derivative(dM);
+ M0 =M(t0);
+ M1 =M(t1);
+ dM0 =dM(t0);
+ dM1 =dM(t1);
+ d2M0=d2M(t0);
+ d2M1=d2M(t1);
+ A0=M(t0);
+ A1=M(t1);
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1);
+ if (candidates.size()==0){
+ return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ;
+ }
+ double maxlength = -1;
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ double l = length(candidates[i]);
+ if ( l < maxlength || maxlength < 0 ){
+ maxlength = l;
+ best = i;
+ }
+ }
+ return candidates[best];
+}
+#include <2geom/sbasis-to-bezier.h>
+
+int goal_function_type = 0;
+
+double goal_function(Piecewise<D2<SBasis> >const &A,
+ Piecewise<D2<SBasis> >const&B) {
+ if(goal_function_type) {
+ OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B))));
+ //double h_dist = bnds.dimensions().length();
+//0 is in the rect!, TODO:gain factor ~2 for free.
+// njh: not really, the benefit is actually rather small.
+ double h_dist = 0;
+ if(bnds)
+ h_dist = bnds->extent();
+ return h_dist ;
+ } else {
+ Rect bnds = *bounds_fast(A - B);
+ return max(bnds.min().length(), bnds.max().length());
+ }
+}
+
+int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) {
+ if (t0>=t1) return 0;//TODO: fix me...
+ if (t0+0.001>=t1) return 0;//TODO: fix me...
+
+ //TODO: don't re-compute derivative(f) at each try!!
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1);
+
+ if(k_bez[0].size() > 1 and k_bez[1].size() > 1) {
+ Piecewise<SBasis> s = arcLengthSb(k_bez);
+ s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1();
+ s += t0;
+ double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez));
+ if(h_dist < precision) {
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.93);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ draw_handle(cr, k_bez.at0());
+ cairo_d2_sb(cr, k_bez);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ return 1;
+ }
+ }
+ //TODO: find a better place where to cut (at the worst fit?).
+ return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) +
+ recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision);
+}
+
+double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) {
+ if (t0>=t1) return 0;//TODO: fix me...
+ if (t0+0.001>=t1) return 0;//TODO: fix me...
+
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1);
+
+ if(k_bez[0].size() > 1 and k_bez[1].size() > 1) {
+ Piecewise<SBasis> s = arcLengthSb(k_bez);
+ s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1();
+ s += t0;
+ return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez));
+ }
+ return 1e100;
+}
+
+struct quadratic_params
+{
+ Piecewise<D2<SBasis> > const *f;
+ double t0, precision;
+};
+
+
+double quadratic (double x, void *params) {
+ struct quadratic_params *p
+ = (struct quadratic_params *) params;
+
+ return single_curvature_fitter(*p->f, p->t0, x) - p->precision;
+}
+
+#include <stdio.h>
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_roots.h>
+
+
+int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) {
+ if(t0 >= t1) return 0;
+
+ double r = t1;
+ if(single_curvature_fitter(f, t0, t1) > precision) {
+ int status;
+ int iter = 0, max_iter = 100;
+ const gsl_root_fsolver_type *T;
+ gsl_root_fsolver *s;
+ gsl_function F;
+ struct quadratic_params params = {&f, t0, precision};
+
+ F.function = &quadratic;
+ F.params = &params;
+
+ T = gsl_root_fsolver_brent;
+ s = gsl_root_fsolver_alloc (T);
+ gsl_root_fsolver_set (s, &F, t0, t1);
+
+ do
+ {
+ iter++;
+ status = gsl_root_fsolver_iterate (s);
+ r = gsl_root_fsolver_root (s);
+ double x_lo = gsl_root_fsolver_x_lower (s);
+ double x_hi = gsl_root_fsolver_x_upper (s);
+ status = gsl_root_test_interval (x_lo, x_hi,
+ 0, 0.001);
+
+
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+
+ double x_lo = gsl_root_fsolver_x_lower (s);
+ double x_hi = gsl_root_fsolver_x_upper (s);
+ printf ("%5d [%.7f, %.7f] %.7f %.7f\n",
+ iter, x_lo, x_hi,
+ r,
+ x_hi - x_lo);
+ gsl_root_fsolver_free (s);
+ }
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r);
+
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.93);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ draw_handle(cr, k_bez.at0());
+ cairo_d2_sb(cr, k_bez);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ if(r < t1)
+ return sequential_curvature_fitter(cr, f, r, t1, precision) + 1;
+ return 1;
+}
+
+
+class SbToBezierTester: public Toy {
+ //std::vector<Slider> sliders;
+ PointHandle adjuster, adjuster2, adjuster3;
+ std::vector<Toggle> toggles;
+ Piecewise<D2<SBasis > > stroke;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_save(cr);
+
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 0.5);
+
+ if(!mouses.empty()) {
+ cairo_move_to(cr, mouses[0]);
+ for(auto & mouse : mouses) {
+ cairo_line_to(cr, mouse);
+ }
+ cairo_stroke(cr);
+ }
+ adjuster2.pos[0]=150;
+ adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.);
+ cairo_move_to(cr, 150, 150);
+ cairo_line_to(cr, 150, 450);
+ cairo_stroke(cr);
+ ostringstream val_s;
+ double scale0=(450-adjuster2.pos[1])/300;
+ double curve_precision = pow(10, scale0*5-2);
+ val_s << curve_precision;
+ draw_text(cr, adjuster2.pos, val_s.str().c_str());
+
+
+ Piecewise<D2<SBasis> > f_as_pw = stroke;
+ cairo_pw_d2_sb(cr, f_as_pw);
+ cairo_stroke(cr);
+ if(!stroke.empty()) {
+ f_as_pw = arc_length_parametrization(f_as_pw);
+ int segs = 0;
+ goal_function_type = toggles[1].on;
+ if(toggles[0].on)
+ segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision);
+ else {
+ segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision);
+ }
+ *notify << " total segments: "<< segs <<"\n";
+ }
+ cairo_restore(cr);
+ Point p(25, height - 50), d(50,25);
+ toggles[0].bounds = Rect(p, p + d);
+ p+= Point(75, 0);
+ toggles[1].bounds = Rect(p, p + d);
+ draw_toggles(cr, toggles);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 's') toggles[0].toggle();
+ redraw();
+ }
+ vector<Point> mouses;
+ int mouse_drag;
+
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ if(!selected) {
+ mouse_drag = 1;
+ mouses.clear();
+ }
+ }
+
+
+ void mouse_moved(GdkEventMotion* e) override {
+ if(mouse_drag) {
+ mouses.emplace_back(e->x, e->y);
+ redraw();
+ } else {
+ Toy::mouse_moved(e);
+ }
+ }
+
+ void mouse_released(GdkEventButton* e) override {
+ mouse_drag = 0;
+ stroke.clear();
+ stroke.push_cut(0);
+ Path pth;
+ for(unsigned i = 2; i < mouses.size(); i+=2) {
+ pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i]));
+ }
+ stroke = pth.toPwSb();
+ Toy::mouse_released(e);
+ }
+
+ SbToBezierTester() {
+ adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform());
+ handles.push_back(&adjuster);
+ adjuster2.pos = Geom::Point(150,300);
+ handles.push_back(&adjuster2);
+ adjuster3.pos = Geom::Point(450,300);
+ handles.push_back(&adjuster3);
+ toggles.emplace_back("Seq", false);
+ toggles.emplace_back("Linfty", true);
+ //}
+ //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t"));
+ //handles.push_back(&(sliders[0]));
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbToBezierTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/plane3d.cpp b/src/toys/plane3d.cpp
new file mode 100644
index 0000000..8710869
--- /dev/null
+++ b/src/toys/plane3d.cpp
@@ -0,0 +1,130 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+
+#include <gsl/gsl_matrix.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+class Box3d: public Toy {
+ double tmat[3][4];
+ PointHandle origin_handle;
+ PointSetHandle vanishing_points_handles;
+ PathVector paths_a;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ Geom::Point orig = origin_handle.pos;
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+
+ /* create the transformation matrix for the map P^3 --> P^2 that has the following effect:
+ (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0)
+ (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1)
+ (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2)
+ (0 : 0 : 0 : 1) --> origin (= handle #3)
+ */
+ for (int j = 0; j < 4; ++j) {
+ tmat[0][j] = vanishing_points_handles.pts[j][0];
+ tmat[1][j] = vanishing_points_handles.pts[j][1];
+ tmat[2][j] = 1;
+ }
+
+ *notify << "Projection matrix:" << endl;
+ for (auto & i : tmat) {
+ for (double j : i) {
+ *notify << j << " ";
+ }
+ *notify << endl;
+ }
+
+ for(const auto & i : paths_a) {
+ Piecewise<D2<SBasis> > path_a_pw = i.toPwSb();
+
+ D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw);
+ Piecewise<SBasis> preimage[4];
+
+ preimage[0] = (B[0] - orig[0]) / 100;
+ preimage[1] = -(B[1] - orig[1]) / 100;
+ Piecewise<SBasis> res[3];
+ for (int j = 0; j < 3; ++j) {
+ res[j] = preimage[0] * tmat[j][0]
+ + preimage[1] * tmat[j][1]
+ + tmat[j][3];
+ }
+
+ //if (fabs (res[2]) > 0.000001) {
+ D2<Piecewise<SBasis> > result(divide(res[0],res[2], 2),
+ divide(res[1],res[2], 2));
+
+ cairo_d2_pw_sb(cr, result);
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ void first_time(int argc, char** argv) override {
+ const char *path_a_name="ptitle.svgd";
+ if(argc > 1)
+ path_a_name = argv[1];
+ paths_a = read_svgd(path_a_name);
+ assert(paths_a.size() > 0);
+
+ // Finite images of the three vanishing points and the origin
+ handles.push_back(&origin_handle);
+ handles.push_back(&vanishing_points_handles);
+ vanishing_points_handles.push_back(550,350);
+ vanishing_points_handles.push_back(150,300);
+ vanishing_points_handles.push_back(380,40);
+ vanishing_points_handles.push_back(340,450);
+ // plane origin
+ origin_handle.pos = Point(180,65);
+ }
+ //int should_draw_bounds() {return 1;}
+
+ Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &/*handles*/)
+ {
+ double res[3];
+ for (int j = 0; j < 3; ++j) {
+ res[j] = 0;
+ for (int i = 0; i < 3; ++i)
+ res[j] += tmat[j][i] * pt[i];
+ }
+ if (fabs (res[2]) > 0.000001) {
+ Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]);
+ draw_handle(cr, result);
+ return result;
+ }
+ assert(0); // unclipped point
+ return Geom::Point(0,0);
+ }
+
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Box3d);
+ 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/src/toys/plane3d.py b/src/toys/plane3d.py
new file mode 100644
index 0000000..0ffcb51
--- /dev/null
+++ b/src/toys/plane3d.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+import py2geom
+import toyframework
+import random,gtk
+import numpy
+from py2geom_glue import *
+
+class Box3d(toyframework.Toy):
+ def __init__(self):
+ toyframework.Toy.__init__(self)
+ self.tmat = numpy.zeros([3,4])
+ # plane origin
+ self.origin_handle = toyframework.PointHandle(180,65)
+ self.handles.append(self.origin_handle)
+ self.vanishing_points_handles = toyframework.PointSetHandle()
+ path_a_name="ptitle.svgd"
+ import sys
+ if len(sys.argv) > 1:
+ path_a_name = sys.argv[1]
+ self.paths_a = py2geom.read_svgd(path_a_name)
+
+
+ # Finite images of the three vanishing points and the origin
+ self.handles.append(self.vanishing_points_handles)
+ self.vanishing_points_handles.append(550,350)
+ self.vanishing_points_handles.append(150,300)
+ self.vanishing_points_handles.append(380,40)
+ self.vanishing_points_handles.append(340,450)
+ def draw(self, cr, pos, save):
+ orig = self.origin_handle.pos;
+ cr.set_source_rgba (0., 0.125, 0, 1)
+
+ # create the transformation matrix for the map P^3 --> P^2 that has the following effect:
+ # (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0)
+ # (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1)
+ # (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2)
+ # (0 : 0 : 0 : 1) --> origin (= handle #3)
+
+ tmat = numpy.zeros([3,4])
+ for j in range(4):
+ tmat[0][j] = self.vanishing_points_handles.pts[j][0]
+ tmat[1][j] = self.vanishing_points_handles.pts[j][1]
+ tmat[2][j] = 1
+
+ self.notify = "Projection matrix:\n"
+ for i in range(3):
+ for j in range(4):
+ self.notify += str(tmat[i][j]) + " "
+ self.notify += '\n'
+
+ for p in self.paths_a:
+ B = py2geom.make_cuts_independant(p.toPwSb())
+ preimage = [None]*4
+
+ preimage[0] = (B[0] - orig[0]) / 100;
+ preimage[1] = -(B[1] - orig[1]) / 100;
+ Piecewise<SBasis> res[3];
+ for j in range(3):
+ res[j] = (preimage[0] * tmat[j][0] +
+ preimage[1] * tmat[j][1] +
+ + tmat[j][3])
+
+ result = D2PiecewiseSBasis(divide(res[0],res[2], 2),
+ divide(res[1],res[2], 2))
+
+ toyframework.cairo_d2_pw(cr, result)
+ cr.set_source_rgba (0., 0.125, 0, 1)
+ cr.stroke()
+
+ toyframework.Toy.draw(self, cr, pos, save)
+
+t = Box3d()
+import sys
+
+toyframework.init(sys.argv, t, 500, 500)
+
+
diff --git a/src/toys/point-curve-nearest-time.cpp b/src/toys/point-curve-nearest-time.cpp
new file mode 100644
index 0000000..0067920
--- /dev/null
+++ b/src/toys/point-curve-nearest-time.cpp
@@ -0,0 +1,397 @@
+/*
+ * point-curve nearest point routines testing
+ *
+ * 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/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/curves.h>
+#include <2geom/angle.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/piecewise.h>
+
+#include <2geom/svg-path-parser.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/transforms.h>
+#include <2geom/pathvector.h>
+
+
+#include <algorithm>
+
+using namespace Geom;
+
+std::ostream&
+operator<< (std::ostream &out, PathVectorTime const &pvp)
+{
+ return out << pvp.path_index << "." << pvp.curve_index << "." << pvp.t;
+}
+
+class NearestPoints : public Toy
+{
+ enum menu_item_t
+ {
+ FIRST_ITEM = 1,
+ LINE_SEGMENT = FIRST_ITEM,
+ ELLIPTICAL_ARC,
+ SBASIS_CURVE,
+ PIECEWISE,
+ PATH,
+ PATH_SVGD,
+ TOTAL_ITEMS
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+
+private:
+ PathVector paths_b;
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+
+ Point p = ph.pos;
+ Point np = p;
+ std::vector<Point> nps;
+
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgb(cr, 0,0,0);
+ switch ( choice )
+ {
+ case '1':
+ {
+ LineSegment seg(psh.pts[0], psh.pts[1]);
+ cairo_move_to(cr, psh.pts[0]);
+ cairo_curve(cr, seg);
+ double t = seg.nearestTime(p);
+ np = seg.pointAt(t);
+ if ( toggles[0].on )
+ {
+ nps.push_back(np);
+ }
+ break;
+ }
+ case '2':
+ {
+ EllipticalArc earc;
+ bool earc_constraints_satisfied = true;
+ try
+ {
+ earc.set(psh.pts[0], 200, 150, 0, true, true, psh.pts[1]);
+ }
+ catch( RangeError e )
+ {
+ earc_constraints_satisfied = false;
+ }
+ if ( earc_constraints_satisfied )
+ {
+ cairo_d2_sb(cr, earc.toSBasis());
+ if ( toggles[0].on )
+ {
+ std::vector<double> t = earc.allNearestTimes(p);
+ for (double i : t)
+ nps.push_back(earc.pointAt(i));
+ }
+ else
+ {
+ double t = earc.nearestTime(p);
+ np = earc.pointAt(t);
+ }
+ }
+ break;
+ }
+ case '3':
+ {
+ D2<SBasis> A = psh.asBezier();
+ cairo_d2_sb(cr, A);
+ if ( toggles[0].on )
+ {
+ std::vector<double> t = Geom::all_nearest_times(p, A);
+ for (double i : t)
+ nps.push_back(A(i));
+ }
+ else
+ {
+ double t = nearest_time(p, A);
+ np = A(t);
+ }
+ break;
+ }
+ case '4':
+ {
+ D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), 3);
+ D2<SBasis> B = handles_to_sbasis(psh.pts.begin() + 3, 3);
+ D2<SBasis> C = handles_to_sbasis(psh.pts.begin() + 6, 3);
+ D2<SBasis> D = handles_to_sbasis(psh.pts.begin() + 9, 3);
+ cairo_d2_sb(cr, A);
+ cairo_d2_sb(cr, B);
+ cairo_d2_sb(cr, C);
+ cairo_d2_sb(cr, D);
+ Piecewise< D2<SBasis> > pwc;
+ pwc.push_cut(0);
+ pwc.push_seg(A);
+ pwc.push_cut(0.25);
+ pwc.push_seg(B);
+ pwc.push_cut(0.50);
+ pwc.push_seg(C);
+ pwc.push_cut(0.75);
+ pwc.push_seg(D);
+ pwc.push_cut(1);
+ if ( toggles[0].on )
+ {
+ std::vector<double> t = Geom::all_nearest_times(p, pwc);
+ for (double i : t)
+ nps.push_back(pwc(i));
+ }
+ else
+ {
+ double t = Geom::nearest_time(p, pwc);
+ np = pwc(t);
+ }
+ break;
+ }
+ case '5':
+ {
+ closed_toggle = true;
+ BezierCurveN<3> A(psh.pts[0], psh.pts[1], psh.pts[2], psh.pts[3]);
+ BezierCurveN<2> B(psh.pts[3], psh.pts[4], psh.pts[5]);
+ BezierCurveN<3> C(psh.pts[5], psh.pts[6], psh.pts[7], psh.pts[8]);
+ Path path;
+ path.append(A);
+ path.append(B);
+ path.append(C);
+ EllipticalArc D;
+ bool earc_constraints_satisfied = true;
+ try
+ {
+ D.set(psh.pts[8], 160, 80, 0, true, true, psh.pts[9]);
+ }
+ catch( RangeError e )
+ {
+ earc_constraints_satisfied = false;
+ }
+ if ( earc_constraints_satisfied ) path.append(D);
+ if ( toggles[1].on ) path.close(true);
+
+ cairo_path(cr, path);
+
+ if ( toggles[0].on )
+ {
+ std::vector<double> t = path.allNearestTimes(p);
+ for (double i : t)
+ nps.push_back(path.pointAt(i));
+ }
+ else
+ {
+ PathTime pt = path.nearestTime(p);
+ np = path.pointAt(pt);
+ }
+ break;
+ }
+ case '6':
+ {
+ closed_toggle = true;
+ PathVector pathv = paths_b*Translate(psh.pts[0]-paths_b[0][0].initialPoint());
+ //std::cout << pathv.size() << std::endl;
+ OptRect optRect = bounds_fast(pathv);
+
+ cairo_rectangle(cr, *optRect);
+ cairo_stroke(cr);
+
+ cairo_path(cr, pathv);
+
+ if ( toggles[0].on )
+ {
+ std::vector<PathVectorTime> t = pathv.allNearestTimes(p);
+ for (auto & i : t)
+ nps.push_back(pathv.pointAt(i));
+ }
+ else
+ {
+ //std::optional<PathVectorTime>
+ double s = 0, e = 1;
+ draw_cross(cr, pathv[0].pointAt(s));
+ draw_cross(cr, pathv[0].pointAt(e));
+ double t = pathv[0][0].nearestTime(p, 0, 1);
+ if(t) {
+ *notify << p+psh.pts[0] << std::endl;
+ *notify << t << std::endl;
+ np = pathv[0].pointAt(t);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ *notify << std::endl;
+ for (int i = FIRST_ITEM; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << i << " - " << menu_items[i] << std::endl;
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ return;
+ }
+ }
+
+ if ( toggles[0].on )
+ {
+ for (auto & np : nps)
+ {
+ cairo_move_to(cr, p);
+ cairo_line_to(cr, np);
+ }
+ }
+ else
+ {
+ cairo_move_to(cr, p);
+ cairo_line_to(cr, np);
+ }
+ cairo_stroke(cr);
+
+ toggles[0].bounds = Rect( Point(10, height - 50), Point(10, height - 50) + Point(80,25) );
+ toggles[0].draw(cr);
+ if ( closed_toggle )
+ {
+ toggles[1].bounds = Rect( Point(100, height - 50), Point(100, height - 50) + Point(80,25) );
+ toggles[1].draw(cr);
+ closed_toggle = false;
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ choice = e->keyval;
+ switch ( choice )
+ {
+ case '1':
+ total_handles = 2;
+ break;
+ case '2':
+ total_handles = 2;
+ break;
+ case '3':
+ total_handles = 6;
+ break;
+ case '4':
+ total_handles = 13;
+ break;
+ case '5':
+ total_handles = 10;
+ break;
+ case '6':
+ total_handles = 1;
+ break;
+ default:
+ total_handles = 0;
+ }
+ psh.pts.clear();
+ psh.push_back( 262.6037,35.824151);
+ psh.pts[0] += Point(300,300);
+ psh.push_back(0,0);
+ psh.pts[1] += psh.pts[0];
+ psh.push_back(-92.64892,-187.405851);
+ psh.pts[2] += psh.pts[0];
+ psh.push_back(30,-149.999981);
+ psh.pts[3] += psh.pts[0];
+ for ( unsigned int i = 0; i < total_handles; ++i )
+ {
+ psh.push_back(uniform()*400, uniform()*400);
+ }
+ ph.pos = Point(uniform()*400, uniform()*400);
+ redraw();
+ }
+
+ void mouse_pressed(GdkEventButton* e) override
+ {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+
+public:
+ void first_time(int argc, char** argv) override {
+ const char *path_b_name="star.svgd";
+ if(argc > 1)
+ path_b_name = argv[1];
+ paths_b = read_svgd(path_b_name);
+ }
+ NearestPoints()
+ : total_handles(0), choice('0'), closed_toggle(false)
+ {
+ handles.push_back(&psh);
+ handles.push_back(&ph);
+ ph.pos = Point(uniform()*400, uniform()*400);
+ toggles.emplace_back("ALL NP", false );
+ toggles.emplace_back("CLOSED", false );
+ }
+
+private:
+ PointSetHandle psh;
+ PointHandle ph;
+ std::vector<Toggle> toggles;
+ unsigned int total_handles;
+ char choice;
+ bool closed_toggle;
+};
+
+const char* NearestPoints::menu_items[] =
+{
+ "",
+ "LineSegment",
+ "EllipticalArc",
+ "SBasisCurve",
+ "Piecewise",
+ "Path",
+ "Path from SVGD"
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new NearestPoints() );
+ 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/src/toys/portion-test.cpp b/src/toys/portion-test.cpp
new file mode 100644
index 0000000..bc64db3
--- /dev/null
+++ b/src/toys/portion-test.cpp
@@ -0,0 +1,105 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// TODO:
+// use path2
+// replace Ray stuff with path2 line segments.
+
+//-----------------------------------------------
+
+class PortionTester: public Toy {
+ PointSetHandle curve_handle;
+ PointHandle sample_point1, sample_point2;
+ std::vector<Toggle> toggles;
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ draw_toggles(cr, toggles);
+ D2<SBasis> B = curve_handle.asBezier();
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ sample_point1.pos[1]=400;
+ sample_point1.pos[0]=std::max(150.,sample_point1.pos[0]);
+ sample_point1.pos[0]=std::min(450.,sample_point1.pos[0]);
+ sample_point2.pos[1]=400;
+ sample_point2.pos[0]=std::max(150.,sample_point2.pos[0]);
+ sample_point2.pos[0]=std::min(450.,sample_point2.pos[0]);
+ cairo_move_to(cr, Geom::Point(150,400));
+ cairo_line_to(cr, Geom::Point(450,400));
+ cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8);
+ cairo_stroke(cr);
+
+ double t0=std::max(0.,std::min(1.,(sample_point1.pos[0]-150)/300.));
+ double t1=std::max(0.,std::min(1.,(sample_point2.pos[0]-150)/300.));
+
+ Path P;
+ P.append(B);
+
+ if (toggles[0].on) {
+ if (toggles[1].on)
+ cairo_curve(cr, P.portion(t0,t1)[0]);
+ else
+ cairo_path(cr, P.portion(t0,t1));
+ } else
+ cairo_d2_sb(cr, portion(B,t0,t1));
+
+
+ cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ PortionTester(){
+ toggles.emplace_back("Path", true);
+ toggles[0].bounds = Rect(Point(10,100), Point(100, 130));
+ toggles.emplace_back("Curve", true);
+ toggles[1].bounds = Rect(Point(10,130), Point(100, 160));
+ if(handles.empty()) {
+ handles.push_back(&curve_handle);
+ handles.push_back(&sample_point1);
+ handles.push_back(&sample_point2);
+ for(unsigned i = 0; i < 4; i++)
+ curve_handle.push_back(150+uniform()*300,150+uniform()*300);
+ sample_point1.pos = Geom::Point(250,300);
+ sample_point2.pos = Geom::Point(350,300);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl;
+ init(argc, argv, new PortionTester);
+ 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/precise-flat.cpp b/src/toys/precise-flat.cpp
new file mode 100644
index 0000000..0113619
--- /dev/null
+++ b/src/toys/precise-flat.cpp
@@ -0,0 +1,86 @@
+/**
+ * efficient and precise flattening of curves.
+ * incomplete rewrite (njh)
+ */
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+class PreciseFlat: public Toy {
+ PointSetHandle hand;
+void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_line_width (cr, 0.5);
+
+ D2<SBasis> B = hand.asBezier();
+ D2<SBasis> dB = derivative(B);
+ D2<SBasis> ddB = derivative(dB);
+ cairo_set_source_rgb(cr, 0,0,0);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ // draw the longest chord that is no worse than tol from the curve.
+
+ Geom::Point st = unit_vector(dB(0));
+ double s3 = fabs(dot(hand.pts[2] - hand.pts[0], rot90(st)));
+
+ SBasis inflect = dot(dB, rot90(ddB));
+ std::vector<double> rts = roots(inflect);
+ double f = 3;
+ for(double rt : rts) {
+ draw_handle(cr, B(rt));
+
+ double tp = rt;
+ Geom::Point st = unit_vector(dB(tp));
+ Geom::Point O = B(tp);
+ double s4 = fabs(dot(hand.pts[3] - O, rot90(st)));
+ double tf = pow(f/s4, 1./3);
+ Geom::Point t1p = B(tp + tf*(1-tp));
+ Geom::Point t1m = B(tp - tf*(1-tp));
+ cairo_move_to(cr, t1m);
+ cairo_line_to(cr, t1p);
+ cairo_stroke(cr);
+ //std::cout << tp << ", " << t1m << ", " << t1p << std::endl;
+ }
+
+ cairo_move_to(cr, B(0));
+ double t0 = 2*sqrt(f/(3*s3));
+ //std::cout << t0 << std::endl;
+ cairo_line_to(cr, B(t0));
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+}
+
+public:
+PreciseFlat () {
+ for(unsigned i = 0; i < 4; i++)
+ hand.pts.emplace_back(uniform()*400, uniform()*400);
+ handles.push_back(&hand);
+}
+
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PreciseFlat());
+
+ 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/src/toys/pw-compose-test.cpp b/src/toys/pw-compose-test.cpp
new file mode 100644
index 0000000..a7e9438
--- /dev/null
+++ b/src/toys/pw-compose-test.cpp
@@ -0,0 +1,98 @@
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class PwToy: public Toy {
+public:
+ vector<PointSetHandle*> pw_handles;
+ PointSetHandle slids;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0.5, 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ D2<Piecewise<SBasis> > pws;
+ for(unsigned i = 0; i < pw_handles.size(); i++) {
+ D2<SBasis> foo = pw_handles[i]->asBezier();
+ cairo_d2_sb(cr, foo);
+ for(unsigned d = 0; d < 2; d++) {
+ pws[d].cuts.push_back(150*i);
+ pws[d].segs.push_back(foo[d]);
+ }
+ }
+ for(unsigned d = 0; d < 2; d++)
+ pws[d].cuts.push_back(150*pw_handles.size());
+
+ slids.pts[0][1]=450;
+ slids.pts[1][1]=450;
+ slids.pts[2][1]=450;
+ slids.pts[3][1]=450;
+
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ D2<SBasis> foo = slids.asBezier();
+ SBasis g = foo[0] - Linear(150);
+ cairo_d2_sb(cr, foo);
+ for(unsigned i=0;i<20;i++){
+ double t=i/20.;
+ draw_handle(cr, foo(t));
+ }
+ cairo_stroke(cr);
+ foo[1]=foo[0];
+ foo[0]=Linear(150,450);
+ cairo_d2_sb(cr, foo);
+
+ cairo_d2_pw_sb(cr, pws);
+
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1);
+ D2<Piecewise<SBasis> > res = compose(pws, Piecewise<SBasis>(g));
+ cairo_d2_pw_sb(cr, res);
+ for(unsigned i=0;i<20;i++){
+ double t=(res[0].cuts.back()-res[0].cuts.front())*i/20.;
+ draw_handle(cr, Point(res[0](t),res[1](t)));
+ }
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ bool should_draw_numbers() override { return false; }
+
+ public:
+ PwToy () {
+ unsigned segs = 5;
+ unsigned handles_per_seg = 4;
+ double x = 150;
+ for(unsigned a = 0; a < segs; a++) {
+ PointSetHandle* psh = new PointSetHandle;
+
+ for(unsigned i = 0; i < handles_per_seg; i++, x+= 25)
+ psh->push_back(Point(x, uniform() * 150));
+ pw_handles.push_back(psh);
+ handles.push_back(psh);
+ }
+ for(unsigned i = 0; i < 4; i++)
+ slids.push_back(Point(150 + segs*50*i,100));
+ handles.push_back(&slids);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PwToy());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/pw-funcs.cpp b/src/toys/pw-funcs.cpp
new file mode 100644
index 0000000..f4c6471
--- /dev/null
+++ b/src/toys/pw-funcs.cpp
@@ -0,0 +1,100 @@
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+
+using namespace Geom;
+using namespace std;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void cairo_horiz(cairo_t *cr, double y, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, i, y);
+ cairo_rel_line_to(cr, 0, 10);
+ }
+}
+
+void cairo_vert(cairo_t *cr, double x, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, x, i);
+ cairo_rel_line_to(cr, 10, 0);
+ }
+}
+
+Piecewise<SBasis> log(Interval in) {
+ Piecewise<SBasis> I = integral(Geom::reciprocal(Linear(in.min(), in.max())));
+ return I + Piecewise<SBasis> (-I.segs[0][0] + log(in.min()));
+}
+
+Piecewise<SBasis> xlogx(Interval in) {
+ Piecewise<SBasis> I = integral(log(in) + Piecewise<SBasis>(1));
+ return I + Piecewise<SBasis> (-I.segs[0][0] + in.min()*log(in.min()));
+}
+
+class PwToy: public Toy {
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 1);
+
+ Piecewise<SBasis> pws;
+
+ //pws = Geom::cos(Linear(0,100)) + 3;
+ pws = Geom::sqrt(Linear(0,100));
+ //pws = log(Interval(1,8));
+ //Piecewise<SBasis> l(Linear(-100,100));
+ //Piecewise<SBasis> one(Linear(1,1));
+ //pws = Geom::reciprocal(l*l + one)*l + l;
+ //pws = xlogx(Interval(0.5,3));
+ //pws = Geom::reciprocal(pws);
+ //pws = -integral(Geom::reciprocal(Linear(1,2)))*Piecewise<SBasis>(Linear(1,2));
+
+ pws = -pws*width/4 + width/2;
+ pws.scaleDomain(width/2);
+ pws.offsetDomain(width/4);
+
+ cairo_pw(cr, pws);
+
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 0., 0., .5, 1.);
+ cairo_horiz(cr, 500, pws.cuts);
+ cairo_stroke(cr);
+
+ *notify << "total pieces: " << pws.size();
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ bool should_draw_numbers() override { return false; }
+ int should_draw_bounds() override { return 2; }
+ public:
+ PwToy () {}
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PwToy());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/pw-toy.cpp b/src/toys/pw-toy.cpp
new file mode 100644
index 0000000..93ca8ea
--- /dev/null
+++ b/src/toys/pw-toy.cpp
@@ -0,0 +1,117 @@
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+
+using namespace Geom;
+using namespace std;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = Linear(150) + p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void cairo_horiz(cairo_t *cr, double y, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, i, y);
+ cairo_rel_line_to(cr, 0, 10);
+ }
+}
+
+void cairo_vert(cairo_t *cr, double x, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, x, i);
+ cairo_rel_line_to(cr, 10, 0);
+ }
+}
+
+#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file)
+
+class PwToy: public Toy {
+ unsigned segs, handles_per_curve, curves;
+ PWSBHandle pwsbh[2];
+ PointHandle interval_test[2];
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 1);
+
+ std::vector<Piecewise<SBasis> > pws(curves);
+ for(unsigned a = 0; a < curves; a++) {
+ pws[a] = pwsbh[a].value();
+ cairo_pw(cr, pws[a]);
+ }
+ cairo_stroke(cr);
+
+ Piecewise<SBasis> pw_out = pws[0] + pws[1];
+
+ cairo_set_source_rgba (cr, 0., 0., .5, 1.);
+ cairo_horiz(cr, 500, pw_out.cuts);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0., 0., .5, 1.);
+ cairo_pw(cr, pws[0] + pws[1]);
+ cairo_stroke(cr);
+
+ Interval bs = *bounds_local(pw_out, Interval(interval_test[0].pos[0],
+ interval_test[1].pos[0]));
+ vector<double> vec;
+ vec.push_back(bs.min() + 150); vec.push_back(bs.max() + 150);
+ cairo_set_source_rgba (cr, .5, 0., 0., 1.);
+ cairo_vert(cr, 100, vec);
+ cairo_stroke(cr);
+
+ /* Portion demonstration
+ Piecewise<SBasis> pw_out = portion(pws[0], handles[handles.size() - 2][0], handles[handles.size() - 1][0]);
+ cairo_set_source_rgba (cr, 0, .5, 0, .25);
+ cairo_set_line_width(cr, 3);
+ cairo_pw(cr, pw_out);
+ cairo_stroke(cr);
+ */
+
+ *notify << pws[0].segN(interval_test[0].pos[0]) << "; " << pws[0].segT(interval_test[0].pos[0]);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ bool should_draw_numbers() override { return false; }
+
+ public:
+ PwToy () {
+ segs = 3;
+ handles_per_curve = 4 * segs;
+ curves = 2;
+ for(unsigned a = 0; a < curves; a++) {
+ pwsbh[a] = PWSBHandle(4, 3);
+ handles.push_back(&pwsbh[a]);
+ for(unsigned i = 0; i < handles_per_curve; i++)
+ pwsbh[a].push_back(150 + 300*i/(4*segs), uniform() * 150 + 150 - 50 * a);
+ }
+ interval_test[0].pos = Point(150, 400);
+ interval_test[1].pos = Point(300, 400);
+ handles.push_back(&interval_test[0]);
+ handles.push_back(&interval_test[1]);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new PwToy());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/pw-toy.py b/src/toys/pw-toy.py
new file mode 100644
index 0000000..011c917
--- /dev/null
+++ b/src/toys/pw-toy.py
@@ -0,0 +1,180 @@
+#!/usr/bin/python
+
+import py2geom
+import toyframework
+import random,gtk
+from py2geom_glue import *
+
+def cairo_pw(cr, p):
+ for i in range(p.size()):
+ a,b = p.cuts[i], p.cuts[i+1]
+ bez = sbasis_to_bezier(p[i], 0)
+ cr.move_to(a, bez[0])
+ cr.curve_to(lerp(a, b, 1./3), bez[1],
+ lerp(a,b, 2./3), bez[2],
+ b, bez[3])
+
+def cairo_horiz(cr, y, ps):
+ for p in ps:
+ cr.move_to(p, y);
+ cr.rel_line_to(0, 10)
+
+def cairo_vert(cr, x, ps):
+ for p in ps:
+ cr.move_to(x, p)
+ cr.rel_line_to(10, 0)
+
+def l2s(l):
+ sb = py2geom.SBasis(l)
+ return sb
+
+def constant(l):
+ pws = py2geom.PiecewiseSBasis()
+
+ pws.push_cut(0)
+ pws.push_seg(l2s(py2geom.Linear(l,l)) )
+ pws.push_cut(1000)
+ return pws
+
+X = l2s(py2geom.Linear(0, 1))
+OmX = l2s(py2geom.Linear(1, 0))
+def bezier_to_sbasis(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)))
+
+
+def bez_to_sbasis(handles, order):
+ return bezier_to_sbasis([h[1] for h in handles], order)
+
+class PWSBHandle(toyframework.Handle):
+ def __init__(self, cs, segs):
+ self.handles_per_curve = cs*segs
+ self.curve_size = cs
+ self.segs = segs
+ self.pts = []
+ def append(self, x, y):
+ self.pts.append(py2geom.Point(x,y))
+ def value(self, y_0=0):
+ pws = py2geom.PiecewiseSBasis()
+ for i in range(0, self.handles_per_curve, self.curve_size):
+ pws.push_cut(self.pts[i][0]);
+ for j in range(i, i + self.curve_size):
+ self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] - y_0)
+ hnd = self.pts[i:i+self.curve_size]
+ pws.push_seg( bez_to_sbasis(hnd, self.curve_size-1));
+ for j in range(i, i + self.curve_size):
+ self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] + y_0);
+
+ pws.push_cut(self.pts[self.handles_per_curve - 1][0]);
+ assert(pws.invariants());
+ return pws
+
+ def draw(self, cr, annotes):
+ for p in self.pts:
+ toyframework.draw_circ(cr, p)
+
+ def hit(self, mouse):
+ for i,p in enumerate(self.pts):
+ if(py2geom.distance(py2geom.Point(*mouse), p) < 5):
+ return i
+ return None
+
+ def move_to(self, hit, om, m):
+ om = py2geom.Point(*om)
+ m = py2geom.Point(*m)
+ if hit != None:
+ i,hand = hit
+ self.pts[hand] = m
+ for i in range(self.curve_size, self.handles_per_curve, self.curve_size):
+ self.pts[i-1] = py2geom.Point(self.pts[i][0],self.pts[i-1][1])
+
+ for i in range(0, self.handles_per_curve, self.curve_size):
+ for j in range(1, (self.curve_size-1)):
+ t = float(j)/(self.curve_size-1)
+ x = lerp(self.pts[i][0], self.pts[i+self.curve_size-1][0],t)
+ self.pts[i+j] = py2geom.Point(x, self.pts[i+j][1])
+
+
+
+class PwToy(toyframework.Toy):
+ def __init__(self):
+ toyframework.Toy.__init__(self)
+ self.segs = 2
+ self.handles_per_curve = 4 * self.segs
+ self.curves = 2
+ self.pwsbh = []
+ self.interval_test = []
+ for a in range(self.curves):
+ self.pwsbh.append(PWSBHandle(4, self.curves))
+ self.handles.append(self.pwsbh[a])
+ for i in range(self.handles_per_curve):
+ t = 150 + 300*i/(4*self.segs)
+ self.pwsbh[a].append(t, random.uniform(0,1) * 150 + 150 - 50 * a)
+ self.interval_test.append(toyframework.PointHandle(150, 400))
+ self.interval_test.append(toyframework.PointHandle(300, 400))
+ self.handles.append(self.interval_test[0])
+ self.handles.append(self.interval_test[1])
+ self.func = "pws[0] + pws[1]"
+ def gtk_ready(self):
+ import gtk
+ self.sb_entry = gtk.Entry()
+ self.sb_entry.connect("changed", self.sb_changed)
+ toyframework.get_vbox().add(self.sb_entry)
+ toyframework.get_vbox().show_all()
+ self.sb_entry.set_text(self.func)
+ def sb_changed(self, sb):
+ self.func = sb.get_text()
+ self.redraw()
+ def draw(self, cr, pos, save):
+ cr.set_source_rgba (0., 0., 0., 1)
+ cr.set_line_width (1)
+
+ pws = [self.pwsbh[i].value() for i in range(self.curves)]
+ for p in pws:
+ cairo_pw(cr, p)
+ cr.stroke()
+
+ d = locals().copy()
+ for i in dir(py2geom):
+ d[i] = py2geom.__dict__[i]
+ d['l2s'] = l2s
+ d['constant'] = constant
+ pw_out = eval(self.func, d)
+
+ bs = py2geom.bounds_local(pw_out, py2geom.OptInterval(
+ py2geom.Interval(self.interval_test[0].pos[0],
+ self.interval_test[1].pos[0])));
+ if not bs.isEmpty():
+ bs = bs.toInterval()
+ for ph in self.interval_test:
+ ph.pos= py2geom.Point(ph.pos[0], bs.middle())
+ cr.save()
+ cr.set_source_rgba (.0, 0.25, 0.5, 1.)
+ cr.rectangle(self.interval_test[0].pos[0], bs.min(),
+ self.interval_test[1].pos[0]-self.interval_test[0].pos[0], bs.extent())
+ cr.stroke()
+ bs = py2geom.bounds_exact(pw_out);
+ cr.set_source_rgba (0.25, 0.25, .5, 1.);
+ if not bs.isEmpty():
+ bs = bs.toInterval()
+ cairo_horiz(cr, bs.middle(), pw_out.cuts);
+ cr.stroke()
+ cr.restore()
+
+ cr.set_source_rgba (0., 0., .5, 1.);
+ cairo_pw(cr, pw_out)
+ cr.stroke()
+
+
+ self.notify = str(bs)
+ toyframework.Toy.draw(self, cr, pos, save)
+
+t = PwToy()
+import sys
+
+toyframework.init(sys.argv, t, 500, 500)
diff --git a/src/toys/pwsbhandle.cpp b/src/toys/pwsbhandle.cpp
new file mode 100644
index 0000000..ce4f21e
--- /dev/null
+++ b/src/toys/pwsbhandle.cpp
@@ -0,0 +1,77 @@
+class PWSBHandle : public Handle{
+public:
+ unsigned handles_per_curve, curve_size, segs;
+ PWSBHandle() {}
+ PWSBHandle(unsigned cs, unsigned segs) :handles_per_curve(cs*segs),curve_size(cs), segs(segs) {}
+ std::vector<Geom::Point> pts;
+ virtual void draw(cairo_t *cr, bool annotes = false);
+
+ virtual void* hit(Geom::Point mouse);
+ virtual void move_to(void* hit, Geom::Point om, Geom::Point m);
+ void push_back(double x, double y) {pts.push_back(Geom::Point(x,y));}
+ Piecewise<SBasis> value(double y_0=150) {
+ Piecewise<SBasis> pws;
+ Point* base = &pts[0];
+ for(unsigned i = 0; i < handles_per_curve; i+=curve_size) {
+ pws.push_cut(base[i][0]);
+ //Bad hack to move 0 to 150
+ for(unsigned j = i; j < i + curve_size; j++)
+ base[j] = Point(base[j][0], base[j][1] - y_0);
+ pws.push_seg( Geom::handles_to_sbasis(base+i, curve_size-1)[1]);
+ for(unsigned j = i; j < i + curve_size; j++)
+ base[j] = Point(base[j][0], base[j][1] + y_0);
+ }
+ pws.push_cut(base[handles_per_curve - 1][0]);
+ assert(pws.invariants());
+ return pws;
+ }
+ virtual void load(FILE* f);
+ virtual void save(FILE* f);
+};
+
+void PWSBHandle::draw(cairo_t *cr, bool /*annotes*/) {
+ for(auto & pt : pts) {
+ draw_circ(cr, pt);
+ }
+}
+
+void* PWSBHandle::hit(Geom::Point mouse) {
+ for(auto & pt : pts) {
+ if(Geom::distance(mouse, pt) < 5)
+ return (void*)(&pt);
+ }
+ return 0;
+}
+
+void PWSBHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) {
+ if(hit) {
+ *(Geom::Point*)hit = m;
+ Point* base = &pts[0];
+ for(unsigned i = curve_size; i < handles_per_curve; i+=curve_size) {
+ base[i-1][0] = base[i][0];
+ }
+ for(unsigned i = 0; i < handles_per_curve; i+=curve_size) {
+ for(unsigned j = 1; j < (curve_size-1); j++) {
+ double t = float(j)/(curve_size-1);
+ base[i+j][0] = (1 - t)*base[i][0] + t*base[i+curve_size-1][0];
+ }
+ }
+ }
+}
+
+void PWSBHandle::load(FILE* f) {
+ unsigned n = 0;
+ assert(3 == fscanf(f, "%d %d %d\n", &curve_size, &segs, &n));
+ assert(n == curve_size*segs);
+ pts.clear();
+ for(unsigned i = 0; i < n; i++) {
+ pts.push_back(read_point(f));
+ }
+}
+
+void PWSBHandle::save(FILE* f) {
+ fprintf(f, "%d %d %lu\n", curve_size, segs, pts.size());
+ for(auto & pt : pts) {
+ fprintf(f, "%lf %lf\n", pt[0], pt[1]);
+ }
+}
diff --git a/src/toys/py2geom_glue.py b/src/toys/py2geom_glue.py
new file mode 100644
index 0000000..fa2b5ae
--- /dev/null
+++ b/src/toys/py2geom_glue.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+
+def choose(n, k):
+ r = 1
+ for i in range(1, k+1):
+ r = (r*(n-k+i))/i
+ return r
+
+def W(n, j, k):
+ q = (n+1)/2.
+ if((n & 1) == 0 and j == q and k == q):
+ return 1
+ if(k > n-k):
+ return W(n, n-j, n-k)
+ assert((k <= q))
+ if(k >= q):
+ return 0
+ # assert(!(j >= n-k));
+ if(j >= n-k):
+ return 0
+ # assert(!(j < k));
+ if(j < k):
+ return 0
+ return float(choose(n-2*k-1, j-k)) / choose(n,j);
+
+# this produces a degree 2q bezier from a degree k sbasis
+def sbasis_to_bezier(B, q):
+ if(q == 0):
+ q = len(B)
+ n = q*2
+ result = [0.0 for i in range(n)]
+ if(q > len(B)):
+ q = len(B)
+ n -= 1
+ for k in range(q):
+ for j in range(n-k+1):
+ result[j] += (W(n, j, k)*B[k][0] +
+ W(n, n-j, k)*B[k][1])
+ return result
+
+def lerp(a, b, t):
+ return (1-t)*a + t*b
diff --git a/src/toys/ray_test.py b/src/toys/ray_test.py
new file mode 100644
index 0000000..6ec8b01
--- /dev/null
+++ b/src/toys/ray_test.py
@@ -0,0 +1,20 @@
+# test of 2geom ray bindings
+
+import py2geom as g
+
+# find one point along a ray
+a = g.Point(0,0)
+b = g.Point(2,2)
+
+r = g.Ray(a,b)
+from math import sqrt
+print r.pointAt(sqrt(2))
+
+# measure the angle between two rays
+c = g.Point(2,-2)
+r2 = g.Ray(a,c)
+from math import degrees
+# FIXME: the third argument (clockwise) ought to be optional, but has to be supplied
+print degrees(g.angle_between(r, r2, True))
+print degrees(g.angle_between(r, r2))
+
diff --git a/src/toys/rdm-area.cpp b/src/toys/rdm-area.cpp
new file mode 100644
index 0000000..22cc104
--- /dev/null
+++ b/src/toys/rdm-area.cpp
@@ -0,0 +1,479 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+#include <list>
+#include <algorithm>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 2
+
+struct Triangle{
+ Point a,b,c;
+ double area;
+};
+
+//TODO: this would work only for C1 pw<d2<sb>> input. Split the input at corners to work with pwd2sb...
+//TODO: for more general purpose, return a path...
+void toPoly(D2<SBasis> const &f, std::list<Point> &p, double tol, bool include_first=true){
+ D2<SBasis> df = derivative(f);
+ D2<SBasis> d2f = derivative(df);
+ double t=0;
+ if ( include_first ){ p.push_back( f.at0() );}
+ while (t<1){
+ Point v = unit_vector(df.valueAt(t));
+ //OptInterval bounds = bounds_local(df[X]*v[Y]-df[Y]*v[X], Interval(t,1));
+ OptInterval bounds = bounds_local(d2f[X]*v[Y]-d2f[Y]*v[X], Interval(t,1));
+ if (bounds) {
+ double bds_max = (-bounds->min()>bounds->max() ? -bounds->min() : bounds->max());
+ double dt;
+ //if (bds_max<tol) dt = 1;
+ //else dt = tol/bds_max;
+ if (bds_max<tol/4) dt = 1;
+ else dt = 2*std::sqrt( tol / bds_max );
+ t+=dt*5;
+ if (t>1) t = 1;
+ }else{
+ t = 1;
+ }
+ p.push_back( f.valueAt(t) );
+ }
+ return;
+}
+
+std::list<Point> toPoly(std::vector<Piecewise<D2<SBasis> > > f, double tol){
+ assert ( f.size() >0 && f[0].size() >0 );
+ std::list<Point> res;
+
+ for (unsigned i = 0; i<f.size(); i++){
+ for (unsigned j = 0; j<f[i].size(); j++){
+ toPoly(f[i][j],res,tol, j==0);
+ }
+ if ( f[i].segs.front().at0() != f[i].segs.back().at1() ){
+ res.push_back( f[i].segs.front().at0() );
+ }
+ if ( i>0 ) res.push_back( f[0][0].at0() );
+ }
+ return res;
+}
+
+//TODO: this is an ugly hack, use path intersection instead!!
+bool intersect(Point const &a0, Point const &b0, Point const &a1, Point const &b1, Point &c, double tol=.0001){
+ double abaa1 = cross( b0-a0, a1-a0);
+ double abab1 = cross( b0-a0, b1-a0);
+ double abaa0 = cross( b1-a1, a0-a1);
+ double abab0 = cross( b1-a1, b0-a1);
+ if ( abaa1 * abab1 < -tol && abaa0 * abab0 < -tol ){
+ c = a1 - (b1-a1) * abaa1/(abab1-abaa1);
+ return true;
+ }
+#if 1
+ return false;//TODO: handle limit cases!!
+#else
+ if ( abaa1 == 0 && dot( a0-a1, b0-a1 ) < 0 ) {
+ c = a1;
+ return true;
+ }
+ if ( abab1 == 0 && dot( a0-b1, b0-b1 ) < 0 ) {
+ c = b1;
+ return true;
+ }
+ if ( abaa0 == 0 && dot( a1-a0, b1-a0 ) < 0 ) {
+ c = a0;
+ return true;
+ }
+ if ( abab0 == 0 && dot( a1-b0, b1-b0 ) < 0 ) {
+ c = b0;
+ return true;
+ }
+ return false;
+#endif
+}
+
+//TODO: use path intersection stuff!
+void uncross(std::list<Point> &loop){
+ std::list<Point>::iterator b0 = loop.begin(),a0,b1,a1;
+ if ( b0 == loop.end() ) return;
+ a0 = b0;
+ ++b0;
+ if ( b0 == loop.end() ) return;
+ //now a0,b0 are 2 consecutive points.
+ while ( b0 != loop.end() ){
+ b1 = b0;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ a1 = b1;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ //now a0,b0,a1,b1 are 4 consecutive points.
+ Point c;
+ while ( b1 != loop.end() ){
+ if ( intersect(*a0,*b0,*a1,*b1,c) ){
+ if ( c != (*a0) && c != (*b0) ){
+ loop.insert(b1,c);
+ loop.insert(b0,c);
+ ++a1;
+ std::list<Point> loop_piece;
+ loop_piece.insert(loop_piece.begin(), b0, a1 );
+ loop_piece.reverse();
+ loop.erase( b0, a1 );
+ loop.splice( a1, loop_piece );
+ b0 = a0;
+ ++b0;
+ //a1 = b1; a1--;//useless
+ }else{
+ //TODO: handle degenerated crossings...
+ }
+ }else{
+ a1=b1;
+ ++b1;
+ }
+ }
+ }
+ }
+ a0 = b0;
+ ++b0;
+ }
+ return;//We should never reach this point.
+}
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+void triangulate(std::list<Point> &pts, std::vector<Triangle> &tri, bool clockwise = false, double tol=.001){
+ pts.unique();
+ while ( !pts.empty() && pts.front() == pts.back() ){ pts.pop_back(); }
+ if ( pts.size() < 3 ) return;
+ //cycle by 1 to have a better looking output...
+ pts.push_back(pts.front()); pts.pop_front();
+ std::list<Point>::iterator a,b,c,m;
+ int sign = (clockwise ? -1 : 1 );
+ a = pts.end(); --a;
+ b = pts.begin();
+ c = b; ++c;
+ //now a,b,c are 3 consecutive points.
+ if ( pts.size() == 3 ) {
+ Triangle abc;
+ abc.a = (*a);
+ abc.b = (*b);
+ abc.c = (*c);
+ abc.area = sign *( cross((*b) - (*a),(*c) - (*b))/2) ;
+ if ( abc.area >0 ){
+ tri.push_back(abc);
+ pts.clear();
+ }
+ return;
+ }
+ bool found = false;
+ while( c != pts.end() ){
+ double abac = cross((*b)-(*a),(*c)-(*a));
+ if ( fabs(abac)<tol && dot( *b-*a, *c-*b ) <= 0) {
+ //this is a degenerated triangle. Remove it and continue.
+ pts.erase(b);
+ triangulate(pts,tri,clockwise);
+ return;
+ }
+ m = c;
+ ++m;
+ while ( m != pts.end() && !found && m!=a){
+ bool pointing_inside;
+ double abam = cross((*b)-(*a),(*m)-(*a));
+ double bcbm = cross((*c)-(*b),(*m)-(*b));
+ if ( sign * abac > 0 ){
+ pointing_inside = ( sign * abam >= 0 ) && ( sign * bcbm >= 0 );
+ }else {
+ pointing_inside = ( sign * abam >=0 ) || ( sign * bcbm >=0);
+ }
+ if ( pointing_inside ){
+ std::list<Point>::iterator p=c,q=++p;
+ Point inter;
+ while ( q != pts.end() && !intersect(*b,*m,*p,*q,inter) ){
+ p=q;
+ ++q;
+ }
+ if ( q == pts.end() ){
+ found = true;
+ }else{
+ ++m;
+ }
+ }else{
+ ++m;
+ }
+ }
+ if ( found ){
+ std::list<Point>pts_beg;
+ pts.insert(b,*b);
+ pts.insert(m,*m);
+ pts_beg.splice(pts_beg.begin(), pts, b, m);
+ triangulate(pts_beg,tri,clockwise);
+ triangulate(pts,tri,clockwise);
+ return;
+ }else{
+ a = b;
+ b = c;
+ ++c;
+ }
+ }
+ //we should never reach this point.
+}
+
+double
+my_rand_generator(){
+ double x = std::rand();
+ return x/RAND_MAX;
+}
+
+class RandomGenerator {
+public:
+ RandomGenerator();
+ RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ ~RandomGenerator(){};
+ void setDomain(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ void set_generator(double (*rand_func)());
+ void resetRandomizer();
+ Point pt();
+ double area();
+
+protected:
+ double (*rand)();//set this to your favorite generator of numbers in [0,1] (an inkscape param for instance!)
+ long start_seed;
+ long seed;
+ std::vector<Triangle> triangles;
+ std::vector<double> areas;
+};
+
+RandomGenerator::RandomGenerator(){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+}
+RandomGenerator::RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+ setDomain(f_in, tol);
+}
+void RandomGenerator::setDomain(Piecewise<D2<SBasis> >f_in, double tol){
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(f_in);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ ++b;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ ++a;++b;
+ }
+ bool clockwise = tot_area < 0;
+ triangles = std::vector<Triangle>();
+ triangulate(p,triangles,clockwise);
+ areas = std::vector<double>(triangles.size(),0.);
+ double cumul = 0;
+ for (unsigned i = 0; i<triangles.size(); i++){
+ cumul += triangles[i].area;
+ areas[i] = cumul;
+ }
+}
+
+void RandomGenerator::resetRandomizer(){
+ seed = start_seed;
+}
+Point RandomGenerator::pt(){
+ if (areas.empty()) return Point(0,0);
+ double pick_area = rand()*areas.back();
+ std::vector<double>::iterator picked = std::lower_bound( areas.begin(), areas.end(), pick_area);
+ unsigned i = picked - areas.begin();
+ double x = (*rand)();
+ double y = (*rand)();
+ if ( x+y > 1) {
+ x = 1-x;
+ y = 1-y;
+ }
+ //x=.3; y=.3;
+ Point res;
+ res = triangles[i].a;
+ res += x * ( triangles[i].b - triangles[i].a );
+ res += y * ( triangles[i].c - triangles[i].a );
+ return res;
+}
+double RandomGenerator::area(){
+ if (areas.empty()) return 0;
+ return areas.back();
+}
+void RandomGenerator::set_generator(double (*f)()){
+ rand = f;//set this to your favorite generator of numbers in [0,1]!
+}
+
+
+
+
+
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class RandomToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ srand(10);
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double tol = (400-adjuster[0].pos[Y])/300.*5+0.05;
+ double tau = (400-adjuster[1].pos[Y])/300.;
+// double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+// double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+// double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+// double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+// double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+// double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> are;
+
+ Point centroid_tmp(0,0);
+ are = integral(dot(B, rot90(derivative(B))))*0.5;
+ are = (are - are.firstValue())*(height/10) / (are.lastValue() - are.firstValue());
+
+ D2<Piecewise<SBasis> > are_graph(Piecewise<SBasis>(Linear(0, width)), are );
+ std::cout << are.firstValue() << "," << are.lastValue() << std::endl;
+ cairo_save(cr);
+ cairo_d2_pw_sb(cr, are_graph);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+
+#if 0
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(B);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ cairo_move_to(cr, p.front());
+ for (std::list<Point>::iterator pt = p.begin(); pt!=p.end(); ++pt){
+ cairo_line_to(cr, *pt);
+ //if (i++>p.size()*tau) break;
+ }
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 1., 0., 0., .5);
+ cairo_stroke(cr);
+
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ b++;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ a++;b++;
+ }
+ bool clockwise = tot_area < 0;
+
+ std::vector<Triangle> tri;
+ int nbiter =0;
+ triangulate(p,tri,clockwise);
+ cairo_set_source_rgba (cr, 1., 1., 0., 1);
+ cairo_stroke(cr);
+ for (unsigned i=0; i<tri.size(); i++){
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., .9, .5);
+ cairo_stroke(cr);
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_source_rgba (cr, 0.5, 0., .9, .1);
+ cairo_fill(cr);
+ }
+#endif
+
+ RandomGenerator rdm = RandomGenerator(B, tol);
+ for(int i = 0; i < rdm.area()/5*tau; i++) {
+ draw_handle(cr, rdm.pt());
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ RandomToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new RandomToy);
+ 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/src/toys/rect-toy.cpp b/src/toys/rect-toy.cpp
new file mode 100644
index 0000000..929afb7
--- /dev/null
+++ b/src/toys/rect-toy.cpp
@@ -0,0 +1,395 @@
+/*
+ * Rect Toy
+ *
+ * Copyright 2008 njh <>
+ * multitoy approach
+ * Copyright 2008 Marco <>
+ *
+ * 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/line.h>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/transforms.h>
+
+#include <2geom/angle.h>
+
+#include <vector>
+#include <string>
+#include <optional>
+
+using namespace Geom;
+
+
+
+std::string angle_formatter(double angle)
+{
+ return default_formatter(decimal_round(deg_from_rad(angle),2));
+}
+
+class LineToy : public Toy
+{
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_CREATE,
+ TEST_PROJECTION,
+ TEST_ORTHO,
+ TEST_DISTANCE,
+ TEST_POSITION,
+ TEST_SEG_BISEC,
+ TEST_ANGLE_BISEC,
+ TEST_COLLINEAR,
+ TEST_INTERSECTIONS,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ enum handle_label_t
+ {
+ };
+
+ enum toggle_label_t
+ {
+ };
+
+ enum slider_label_t
+ {
+ ANGLE_SLIDER = 0,
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &LineToy::draw_menu;
+ }
+
+ void init_common()
+ {
+ set_common_control_geometry = true;
+ set_control_geometry = true;
+
+ sliders.clear();
+ toggles.clear();
+ handles.clear();
+ }
+
+
+ virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/ )
+ {
+ init_common_ctrl_geom(cr, width, height, notify);
+ }
+
+
+ void init_create()
+ {
+ init_common();
+
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+
+ sliders.emplace_back(0, 2*M_PI, 0, 0, "angle");
+ sliders[ANGLE_SLIDER].formatter(&angle_formatter);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&(sliders[ANGLE_SLIDER]));
+ }
+
+ void draw_create(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+ init_create_ctrl_geom(cr, notify, width, height);
+
+ Rect r1(p1.pos, p2.pos);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ cairo_rectangle(cr, r1);
+
+ Affine rot = Translate(-r1.midpoint())*Rotate(sliders[ANGLE_SLIDER].value())*Translate(r1.midpoint());
+ cairo_rectangle(cr, r1*rot);
+ cairo_move_to(cr, r1.corner(3)*rot);
+ for(int i = 0; i < 4; i++) {
+ cairo_line_to(cr, r1.corner(i)*rot);
+ }
+
+ cairo_stroke(cr);
+
+ draw_label(cr, p1, "P1");
+ draw_label(cr, p2, "P2");
+ }
+
+
+
+ void init_intersections()
+ {
+ init_common();
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+ p3.pos = Point(100, 250);
+ p4.pos = Point(200, 450);
+ p5.pos = Point(50, 150);
+ p6.pos = Point(480, 60);
+
+ handles.push_back(&p1);
+ handles.push_back(&p2);
+ handles.push_back(&p3);
+ handles.push_back(&p4);
+ handles.push_back(&p5);
+ handles.push_back(&p6);
+ }
+
+ void draw_intersections(cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save,
+ std::ostringstream */*timer_stream*/)
+ {
+ draw_common(cr, notify, width, height, save);
+
+ Rect r1(p1.pos, p2.pos);
+ Rect r2(p3.pos, p4.pos);
+ Line l1(p5.pos, p6.pos);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ cairo_rectangle(cr, r1);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr, 0.0, 0.3, 0.0, 1.0);
+ cairo_rectangle(cr, r2);
+ cairo_stroke(cr);
+
+ OptRect r1xr2 = intersect(r1, r2);
+ if(r1xr2) {
+ cairo_set_source_rgba(cr, 1.0, 0.7, 0.7, 1.0);
+ cairo_rectangle(cr, *r1xr2);
+ cairo_fill(cr);
+ }
+
+ cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ draw_line(cr, l1);
+ cairo_stroke(cr);
+
+
+ std::optional<LineSegment> ls = rect_line_intersect(r1, LineSegment(p5.pos, p6.pos));
+ *notify << "intersects: " << ((ls)?"true":"false") << std::endl;
+ if(ls) {
+ draw_handle(cr, (*ls)[0]);
+ draw_handle(cr, (*ls)[1]);
+ cairo_save(cr);
+ cairo_set_line_width(cr, 2);
+ cairo_move_to(cr, (*ls)[0]);
+ cairo_line_to(cr, (*ls)[1]);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+
+
+ }
+
+ void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ }
+ }
+
+ void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry )
+ {
+ set_control_geometry = false;
+
+ sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180);
+ }
+ }
+
+
+
+ void draw_segment(cairo_t* cr, Point const& p1, Point const& p2)
+ {
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+ }
+
+ void draw_segment(cairo_t* cr, Point const& p1, double angle, double length)
+ {
+ Point p2;
+ p2[X] = length * std::cos(angle);
+ p2[Y] = length * std::sin(angle);
+ p2 += p1;
+ draw_segment(cr, p1, p2);
+ }
+
+ void draw_segment(cairo_t* cr, LineSegment const& ls)
+ {
+ draw_segment(cr, ls[0], ls[1]);
+ }
+
+ void draw_ray(cairo_t* cr, Ray const& r)
+ {
+ double angle = r.angle();
+ draw_segment(cr, r.origin(), angle, m_length);
+ }
+
+ void draw_line(cairo_t* cr, Line const& l)
+ {
+ double angle = l.angle();
+ draw_segment(cr, l.origin(), angle, m_length);
+ draw_segment(cr, l.origin(), angle, -m_length);
+ }
+
+ void draw_label(cairo_t* cr, PointHandle const& ph, const char* label)
+ {
+ draw_text(cr, ph.pos+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Line const& l, const char* label)
+ {
+ draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label);
+ }
+
+ void draw_label(cairo_t* cr, LineSegment const& ls, const char* label)
+ {
+ draw_text(cr, middle_point(ls[0], ls[1])+op, label);
+ }
+
+ void draw_label(cairo_t* cr, Ray const& r, const char* label)
+ {
+ Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30)));
+ if (L2(r.origin() - prj) < 100)
+ {
+ prj = r.origin() + 100*r.vector();
+ }
+ draw_text(cr, prj+op, label);
+ }
+
+ void init_menu()
+ {
+ handles.clear();
+ sliders.clear();
+ toggles.clear();
+ }
+
+ void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/,
+ std::ostringstream */*timer_stream*/)
+ {
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &LineToy::draw_menu;
+ break;
+ case 'B':
+ init_create();
+ draw_f = &LineToy::draw_create;
+ break;
+ case 'C':
+ init_intersections();
+ draw_f = &LineToy::draw_intersections;
+ break;
+ }
+ redraw();
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ LineToy()
+ {
+ op = Point(5,5);
+ }
+
+ private:
+ typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ PointHandle p1, p2, p3, p4, p5, p6, O;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ Point op;
+ double m_width, m_height, m_length;
+
+}; // end class LineToy
+
+
+const char* LineToy::menu_items[] =
+{
+ "show this menu",
+ "rect generation",
+ "intersection with a line"
+};
+
+const char LineToy::keys[] =
+{
+ 'A', 'B', 'C'
+};
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new LineToy());
+ 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/src/toys/rect_01.cpp b/src/toys/rect_01.cpp
new file mode 100644
index 0000000..333843b
--- /dev/null
+++ b/src/toys/rect_01.cpp
@@ -0,0 +1,98 @@
+/*
+ * SimpleRect examples
+ *
+ * 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.
+ */
+
+/*
+Very Simple example of a toy that creates a rectangle on screen
+
+I am still very inexperienced with lib2geom
+so don't use these examples as a reference :)
+*/
+
+
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+#include <2geom/transforms.h>
+
+using std::vector;
+using namespace Geom;
+
+
+
+
+class SimpleRect: public Toy {
+ PointSetHandle psh;
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ {
+ cairo_save(cr);
+ Path p1;
+ p1.appendNew<LineSegment>(Point(0, 0));
+ p1.appendNew<LineSegment>(Point(100, 0));
+ p1.appendNew<LineSegment>(Point(100, 100));
+ p1.appendNew<LineSegment>(Point(0, 100));
+ p1.appendNew<LineSegment>(Point(0, 0));
+ p1.close();
+
+ Path p2 = p1 * Rotate::from_degrees(45); //
+
+ cairo_set_source_rgb(cr, 0,0,0);
+ cairo_path(cr, p1);
+ cairo_path(cr, p2);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ PointHandle p1, p2;
+ p1.pos = Point(300, 50);
+ p2.pos = Point(450, 450);
+
+ Rect r1(p1.pos, p2.pos);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ cairo_rectangle(cr, r1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ SimpleRect(){
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SimpleRect());
+
+ return 0;
+}
+
diff --git a/src/toys/rect_02.cpp b/src/toys/rect_02.cpp
new file mode 100644
index 0000000..a903e2d
--- /dev/null
+++ b/src/toys/rect_02.cpp
@@ -0,0 +1,81 @@
+/*
+ * SimpleRect examples
+ *
+ * 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.
+ */
+
+/*
+Simple example of a toy that creates a rectangle on screen.
+We also add two handles (that we don't use in our program)
+
+I am still very inexperienced with lib2geom
+so don't use these examples as a reference :)
+*/
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+
+class SimpleRect: public Toy {
+ PointSetHandle psh;
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+
+ PointHandle p1, p2;
+ p1.pos = Point(400, 50);
+ p2.pos = Point(450, 450);
+
+ Rect r1(p1.pos, p2.pos);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ cairo_rectangle(cr, r1);
+
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+ public:
+ SimpleRect (unsigned no_of_handles) {
+ handles.push_back(&psh);
+ for(unsigned i = 0; i < no_of_handles; i++)
+ psh.push_back( 200 + ( i * 10 ), 300 + ( i * 10 ) );
+ }
+};
+
+int main(int argc, char **argv) {
+ unsigned no_of_handles=2;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &no_of_handles);
+ init(argc, argv, new SimpleRect(no_of_handles));
+
+ return 0;
+}
+
diff --git a/src/toys/rect_03.cpp b/src/toys/rect_03.cpp
new file mode 100644
index 0000000..89be320
--- /dev/null
+++ b/src/toys/rect_03.cpp
@@ -0,0 +1,78 @@
+/*
+ * SimpleRect examples
+ *
+ * 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.
+ */
+
+/*
+Simple example of a toy that creates a rectangle on screen.
+Now we use the 2 handles to define the rect
+
+I am still very inexperienced with lib2geom
+so don't use these examples as a reference :)
+*/
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+
+
+class SimpleRect: public Toy {
+ PointSetHandle psh;
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ Rect r1(psh.pts[0], psh.pts[1]);
+
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width(cr, 0.3);
+ cairo_rectangle(cr, r1);
+
+
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+}
+ public:
+ SimpleRect (unsigned no_of_handles) {
+ handles.push_back(&psh);
+ for(unsigned i = 0; i < no_of_handles; i++)
+ psh.push_back( 200 + ( i * 20 ), 300 + ( i * 20 ) );
+ }
+};
+
+int main(int argc, char **argv) {
+ unsigned no_of_handles=2;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &no_of_handles);
+ init(argc, argv, new SimpleRect(no_of_handles));
+
+ return 0;
+}
+
diff --git a/src/toys/root-finder-comparer.cpp b/src/toys/root-finder-comparer.cpp
new file mode 100644
index 0000000..3952d7c
--- /dev/null
+++ b/src/toys/root-finder-comparer.cpp
@@ -0,0 +1,247 @@
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/solver.h>
+#include <2geom/sbasis-poly.h>
+#include "../2geom/orphan-code/nearestpoint.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file)
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define ZROOTS_TEST 0
+#if ZROOTS_TEST
+#include <2geom/zroots.c>
+#endif
+
+using std::swap;
+
+namespace Geom{
+extern void subdiv_sbasis(SBasis const & s,
+ std::vector<double> & roots,
+ double left, double right);
+};
+
+double eval_bernstein(double* w, double t, unsigned N) {
+ double Vtemp[2*N];
+ //const int degree = N-1;
+ for (unsigned i = 0; i < N; i++)
+ Vtemp[i] = w[i];
+
+ /* Triangle computation */
+ const double omt = (1-t);
+ //Left[0] = Vtemp[0];
+ //Right[degree] = Vtemp[degree];
+ double *prev_row = Vtemp;
+ double *row = Vtemp + N;
+ for (unsigned i = 1; i < N; i++) {
+ for (unsigned j = 0; j < N - i; j++) {
+ row[j] = omt*prev_row[j] + t*prev_row[j+1];
+ }
+ //Left[i] = row[0];
+ //Right[degree-i] = row[degree-i];
+ swap(prev_row, row);
+ }
+ return prev_row[0];
+}
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+#ifdef HAVE_GSL
+#include <complex>
+using std::complex;
+#endif
+
+class RootFinderComparer: public Toy {
+public:
+ PointSetHandle psh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ std::vector<Geom::Point> trans;
+ trans.resize(psh.size());
+ for(unsigned i = 0; i < handles.size(); i++) {
+ trans[i] = psh.pts[i] - Geom::Point(0, height/2);
+ }
+
+ Timer tm;
+
+
+ std::vector<double> solutions;
+ solutions.resize(6);
+
+ tm.ask_for_timeslice();
+ tm.start();
+ FindRoots(&trans[0], 5, &solutions[0], 0);
+ Timer::Time als_time = tm.lap();
+ *notify << "original time = " << als_time << std::endl;
+
+ D2<SBasis> test_sb = psh.asBezier();//handles_to_sbasis(handles.begin(),5);
+ Interval bs = *bounds_exact(test_sb[1]);
+ cairo_move_to(cr, test_sb[0](0), bs.min());
+ cairo_line_to(cr, test_sb[0](1), bs.min());
+ cairo_move_to(cr, test_sb[0](0), bs.max());
+ cairo_line_to(cr, test_sb[0](1), bs.max());
+ cairo_stroke(cr);
+ *notify << "sb bounds = "<<bs.min()<< ", " <<bs.max()<<std::endl;
+ Poly ply = sbasis_to_poly(test_sb[1]);
+ ply = Poly(height/2) - ply;
+ cairo_move_to(cr, 0, height/2);
+ cairo_line_to(cr, width, height/2);
+ cairo_stroke(cr);
+#ifdef HAVE_GSL
+ vector<complex<double> > complex_solutions;
+ complex_solutions = solve(ply);
+#if 1
+ *notify << "gsl: ";
+ std::copy(complex_solutions.begin(), complex_solutions.end(), std::ostream_iterator<std::complex<double> >(*notify, ",\t"));
+ *notify << std::endl;
+#endif
+#endif
+
+#if ZROOTS_TEST
+ fcomplex a[ply.size()];
+ for(unsigned i = 0; i < ply.size(); i++) {
+ a[i] = ply[i];
+ }
+ //copy(a, a+ply.size(), ply.begin());
+ fcomplex rts[ply.size()];
+
+ zroots(a, ply.size(), rts, true);
+ for(unsigned i = 0; i < ply.size(); i++) {
+ if(! a[i].imag())
+ solutions[i] = a[i].real();
+ }
+#endif
+
+#ifdef HAVE_GSL
+
+ tm.ask_for_timeslice();
+ tm.start();
+ solve(ply);
+ als_time = tm.lap();
+ *timer_stream << "gsl poly = " << als_time << std::endl;
+#endif
+
+ #if ZROOTS_TEST
+ tm.ask_for_timeslice();
+ tm.start();
+ zroots(a, ply.size(), rts, false);
+ als_time = tm.lap();
+ *timer_stream << "zroots poly = " << als_time << std::endl;
+ #endif
+
+ #if LAGUERRE_TEST
+ tm.ask_for_timeslice();
+ tm.start();
+ Laguerre(ply);
+ als_time = tm.lap();
+ *timer_stream << "Laguerre poly = " << als_time << std::endl;
+ complex_solutions = Laguerre(ply);
+
+ #endif
+
+ #define SBASIS_SUBDIV_TEST 0
+ #if SBASIS_SUBDIV_TEST
+ tm.ask_for_timeslice();
+ tm.start();
+ subdiv_sbasis(-test_sb[1] + Linear(3*width/4),
+ rts, 0, 1);
+ als_time = tm.lap();
+ *timer_stream << "sbasis subdivision = " << als_time << std::endl;
+ #endif
+ #if 0
+ tm.ask_for_timeslice();
+ tm.start();
+ solutions.resize(0);
+ find_parametric_bezier_roots(&trans[0], 5, solutions, 0);
+ als_time = tm.lap();
+ *timer_stream << "solver parametric = " << als_time << std::endl;
+ #endif
+ double ys[trans.size()];
+ for(unsigned i = 0; i < trans.size(); i++) {
+ ys[i] = trans[i][1];
+ double x = double(i)/(trans.size()-1);
+ x = (1-x)*height/4 + x*height*3/4;
+ draw_handle(cr, Geom::Point(x, height/2 + ys[i]));
+ }
+
+ solutions.resize(0);
+ tm.ask_for_timeslice();
+ tm.start();
+ find_bernstein_roots(ys, 5, solutions, 0, 0, 1, false);
+ als_time = tm.lap();
+ *notify << "found sub solutions at:\n";
+ std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ","));
+ *notify << "solver 1d bernstein subdivision n_slns = " << solutions.size()
+ << ", time = " << als_time << std::endl;
+
+ solutions.resize(0);
+ tm.ask_for_timeslice();
+ tm.start();
+ find_bernstein_roots(ys, 5, solutions, 0, 0, 1, true);
+ als_time = tm.lap();
+
+ *notify << "solver 1d bernstein secant subdivision slns" << solutions.size()
+ << ", time = " << als_time << std::endl;
+ *notify << "found secant solutions at:\n";
+ std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ","));
+ *notify << "solver 1d bernstein subdivision accuracy:"
+ << std::endl;
+ for(double solution : solutions) {
+ *notify << solution << ":" << eval_bernstein(ys, solution, trans.size()) << ",";
+ }
+ tm.ask_for_timeslice();
+ tm.start();
+ solutions = roots( -test_sb[1] + Linear(height/2));
+ als_time = tm.lap();
+#if 1
+ *notify << "sbasis roots: ";
+ std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double>(*notify, ",\t"));
+ *notify << "\n time = " << als_time << std::endl;
+#endif
+ for(double solution : solutions) {
+ double x = test_sb[0](solution);
+ draw_cross(cr, Geom::Point(x, height/2));
+
+ }
+
+ *notify << "found " << solutions.size() << "solutions at:\n";
+ std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ","));
+
+ D2<SBasis> B = psh.asBezier();//handles_to_sbasis(handles.begin(), 5);
+ Geom::Path pb;
+ pb.append(B);
+ pb.close(false);
+ cairo_path(cr, pb);
+
+ B[0] = Linear(width/4, 3*width/4);
+ cairo_d2_sb(cr, B);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ RootFinderComparer(unsigned degree)
+ {
+ for(unsigned i = 0; i < degree; i++) psh.push_back(Geom::Point(uniform()*400, uniform()*400));
+ handles.push_back(&psh);
+ }
+};
+
+int main(int argc, char **argv) {
+ unsigned bez_ord = 6;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &bez_ord);
+ init(argc, argv, new RootFinderComparer(bez_ord));
+
+ 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/src/toys/rtree-toy.cpp b/src/toys/rtree-toy.cpp
new file mode 100644
index 0000000..b7872fa
--- /dev/null
+++ b/src/toys/rtree-toy.cpp
@@ -0,0 +1,573 @@
+/*
+ * 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 <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+#include <sstream>
+#include <getopt.h>
+
+#include <2geom/orphan-code/rtree.h>
+#include "../2geom/orphan-code/rtree.cpp"
+
+
+//using std::vector;
+using namespace Geom;
+using namespace std;
+
+// make sure that in RTreeToy() constructor you assign the same number of colors
+// otherwise, they extra will be black :P
+const int no_of_colors = 8;
+const string search_str = "Mode: Search (Area: Click whitespace and Drag)";
+const string update_str = "Mode: Update (Click Bounding Box (dark gray) and Drag) NOT implemented";
+const string insert_str = "Mode: Insert (Click whitespace and Drag)" ;
+const string erase_str = "Mode: Delete (Click on Bounding Box (dark gray))";
+const string help_str = "'I': Insert, 'U': Update, 'S': Search, 'D': Delete";
+
+
+const char* program_name;
+
+class RTreeToy: public Toy
+{
+
+// PointSetHandle handle_set;
+// std::vector< Handle > rectangle_handles;
+ std::vector< RectHandle > rectangles;
+
+ Geom::Point starting_point; // during click and drag: start point of click
+ Geom::Point ending_point; // during click and drag: end point of click (release)
+ Geom::Point highlight_point; // not used
+
+ // colors we are going to use for different purposes
+ colour color_shape, color_shape_guide;
+ colour color_select_area, color_select_area_guide; // red(a=0.6), red
+
+ bool add_new_rect;
+ bool delete_rect;
+
+ Rect rect_chosen; // the rectangle of the search area
+ Rect dummy_draw; // the "helper" rectangle that is shown during the click and drag (before the mouse release)
+
+ // save the bounding boxes of the tree in here
+ std::vector< std::vector< Rect > > rects_level;
+ std::vector<colour> color_rtree_level;
+ unsigned drawBB_color;
+ bool drawBB_color_all;
+
+ enum the_modes {
+ INSERT_MODE = 0,
+ UPDATE_MODE,
+ DELETE_MODE,
+ SEARCH_MODE,
+ } mode; // insert/alter, search, delete modes
+ bool drawBB; // draw bounding boxes of RTree
+ string out_str, drawBB_str, drawBB_color_str;
+
+ // printing of the tree
+ //int help_counter; // the "x" of the label of each node
+ static const int label_size = 15 ; // size the label of each node
+
+ Geom::RTree rtree;
+
+ void * hit;
+ unsigned rect_id;
+
+
+ // used for the keys that switch between modes
+ enum menu_item_t
+ {
+ INSERT_I = 0,
+ UPDATE_U,
+ DELETE_D,
+ SEARCH_S,
+ BB_TOGGLE_T,
+ BB_DRAW_0,
+ BB_DRAW_1,
+ BB_DRAW_2,
+ BB_DRAW_3,
+ BB_DRAW_4,
+ BB_DRAW_5,
+ BB_DRAW_ALL_O,
+ TOTAL_ITEMS // this one must be the last item
+ };
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+
+
+ void draw( cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream ) override {
+ cairo_set_line_width( cr, 1 );
+ cairo_set_source_rgba( cr, color_shape );
+ cairo_stroke( cr );
+
+ // draw the shapes we save in the rtree
+ for(auto & rectangle : rectangles){
+ rectangle.draw( cr, true );
+ }
+ cairo_set_source_rgba( cr, color_shape );
+ cairo_stroke( cr );
+
+ // draw a rect if we did click & drag (so that we know what we are going to create)
+ if( add_new_rect ){
+ dummy_draw = Rect( starting_point, ending_point );
+ cairo_rectangle( cr, dummy_draw );
+ if( mode == INSERT_MODE ){
+ cairo_set_source_rgba( cr, color_shape_guide );
+ }
+ else if( mode == SEARCH_MODE ){
+ cairo_set_source_rgba( cr, color_select_area_guide );
+ }
+ cairo_stroke( cr );
+ }
+
+ // draw a rect for the search area
+ cairo_rectangle( cr, rect_chosen );
+ cairo_set_source_rgba( cr, color_select_area );
+ cairo_stroke( cr );
+
+ *notify << help_str << "\n"
+ << "'T': Bounding Boxes: " << drawBB_str << ", '0'-'" << no_of_colors << "', 'P': Show Layer: " << drawBB_color_str << "\n"
+ << out_str;
+
+ if( drawBB ){
+ for(unsigned color = 0 ; color < rects_level.size() ; color++ ){
+ if( drawBB_color == color || drawBB_color_all ){
+ for(auto & j : rects_level){
+ cairo_rectangle( cr, j );
+ }
+ cairo_set_source_rgba( cr, color_rtree_level[color] );
+ cairo_stroke( cr );
+ }
+ }
+ }
+
+ Toy::draw( cr, notify, width, height, save,timer_stream );
+ }
+
+ void mouse_moved( GdkEventMotion* e ) override{
+ if(add_new_rect &&
+ ( mode == INSERT_MODE || mode == SEARCH_MODE ) )
+ {
+ Toy::mouse_moved( e );
+ ending_point = Point( e->x, e->y );
+ }
+ }
+
+ void mouse_pressed( GdkEventButton* e ) override {
+ Toy::mouse_pressed( e );
+ if(e->button == 1){ // left mouse button
+ if( mode == INSERT_MODE ){
+ starting_point = Point( e->x, e->y );
+ ending_point = starting_point;
+ add_new_rect = true;
+ }
+ else if( mode == SEARCH_MODE ){
+ starting_point = Point( e->x, e->y );
+ ending_point = starting_point;
+ add_new_rect = true;
+ }
+ else if( mode == DELETE_MODE ) {
+ Geom::Point mouse(e->x, e->y);
+ unsigned i = 0;
+ for( i = 0; i < rectangles.size(); i++) {
+ hit = rectangles[i].hit(mouse);
+ if( hit ) {
+ break;
+ }
+ }
+ if( hit ){
+ // erase specific element
+ stringstream shape_id( rectangles[i].name );
+ unsigned shape_id_int;
+ shape_id >> shape_id_int;
+
+ rtree.erase( rectangles[i].pos, shape_id_int );
+ rectangles.erase( rectangles.begin() + i );
+// check_if_deleted( );
+// check_if_duplicates( );
+ delete_rect = true;
+ }
+ hit = NULL;
+ }
+ }
+ else if( e->button == 2 ){ //middle button
+ }
+ else if( e->button == 3 ){ //right button
+ }
+ }
+
+ void mouse_released( GdkEventButton* e ) override {
+ Toy::mouse_released( e );
+ if( e->button == 1 ) { //left mouse button
+ if( mode == INSERT_MODE ) {
+ if( add_new_rect ){
+ ending_point = Point( e->x, e->y );
+ RectHandle t( Rect(starting_point, ending_point), false );
+
+ std::stringstream out;
+ out << rect_id;;
+ t.name = out.str();
+ rectangles.push_back( t );
+ rect_id++;
+
+ insert_in_tree_the_last_rect();
+ find_rtree_subtrees_bounding_boxes( rtree );
+ add_new_rect = false;
+ }
+ }
+ else if( mode == SEARCH_MODE ){
+ if( add_new_rect ){
+ ending_point = Point( e->x, e->y );
+ rect_chosen = Rect( starting_point, ending_point );
+
+ std::vector< int > result(0);
+
+ if( rtree.root ){
+ rtree.search( rect_chosen, &result, rtree.root );
+ }
+ std::cout << "Search results: " << result.size() << std::endl;
+ for(int i : result){
+ std::cout << i << ", " ;
+ }
+ std::cout << std::endl;
+
+ add_new_rect = false;
+ }
+ }
+ else if( mode == DELETE_MODE ) { // mode: delete
+ if( delete_rect ){
+ delete_rect = false;
+ if( rtree.root ){
+ find_rtree_subtrees_bounding_boxes( rtree );
+ }
+ std::cout << " \nTree:\n" << std::endl;
+ rtree.print_tree( rtree.root, 0 );
+ std::cout << "...done\n" << std::endl;
+ }
+ }
+ }
+ else if( e->button == 2 ){ //middle button
+ }
+ else if( e->button == 3 ){ //right button
+ }
+ }
+
+
+ void key_hit( GdkEventKey *e ) override
+ {
+ char choice = std::toupper( e->keyval );
+ switch ( choice )
+ {
+ case 'I':
+ mode = INSERT_MODE;
+ out_str = insert_str;
+ break;
+ case 'S':
+ mode = SEARCH_MODE;
+ out_str = search_str;
+ break;
+ case 'D':
+ mode = DELETE_MODE;
+ out_str = erase_str;
+ break;
+ case 'U':
+ mode = UPDATE_MODE;
+ out_str = update_str;
+ break;
+ case 'T':
+ if( drawBB ){
+ drawBB = false;
+ drawBB_str = "OFF";
+ }
+ else{
+ drawBB = true;
+ drawBB_str = "ON";
+ }
+ break;
+ case 'P':
+ drawBB_color_all = true;
+ drawBB_color = 9;
+ drawBB_color_str = "all";
+ break;
+ case '0':
+ drawBB_color_all = false;
+ drawBB_color = 0;
+ drawBB_color_str = "0";
+ break;
+ case '1':
+ drawBB_color_all = false;
+ drawBB_color = 1;
+ drawBB_color_str = "1";
+ break;
+ case '2':
+ drawBB_color_all = false;
+ drawBB_color = 2;
+ drawBB_color_str = "2";
+ break;
+ case '3':
+ drawBB_color_all = false;
+ drawBB_color = 3;
+ drawBB_color_str = "3";
+ break;
+ case '4':
+ drawBB_color_all = false;
+ drawBB_color = 4;
+ drawBB_color_str = "4";
+ break;
+ case '5':
+ drawBB_color_all = false;
+ drawBB_color = 5;
+ drawBB_color_str = "5";
+ break;
+ case '6':
+ drawBB_color_all = false;
+ drawBB_color = 6;
+ drawBB_color_str = "6";
+ break;
+ case '7':
+ drawBB_color_all = false;
+ drawBB_color = 7;
+ drawBB_color_str = "7";
+ break;
+ }
+ redraw();
+ }
+
+
+ void insert_in_tree_the_last_rect(){
+ unsigned i = rectangles.size() - 1;
+ Rect r1 = rectangles[i].pos;
+
+ stringstream shape_id( rectangles[i].name );
+ unsigned shape_id_int;
+ shape_id >> shape_id_int;
+
+ // insert in R tree
+ rtree.insert( r1, shape_id_int );
+ std::cout << " \nTree:\n" << std::endl;
+ rtree.print_tree( rtree.root, 0 );
+ std::cout << "...done\n" << std::endl;
+ };
+
+
+ void find_rtree_subtrees_bounding_boxes( Geom::RTree tree ){
+ if( tree.root ){
+ // clear existing bounding boxes
+ for(auto & color : rects_level){
+ color.clear();
+ }
+ save_bb( tree.root, 0);
+ }
+ };
+
+ // TODO fix this.
+ void save_bb( Geom::RTreeNode* subtree_root, int depth )
+ {
+ if( subtree_root->children_nodes.size() > 0 ){
+
+ // descend in each one of the elements and call print_tree
+ for(auto & children_node : subtree_root->children_nodes){
+ Rect r1( children_node.bounding_box );
+ rects_level[ depth ].push_back( r1 );
+
+ if( depth == no_of_colors - 1 ){ // if we reached Nth levels of colors, roll back to color 0
+ save_bb( children_node.data, 0);
+ }
+ else{
+ save_bb( children_node.data, depth+1);
+ }
+ }
+ }
+ // else do nothing, entries are the rects themselves...
+ };
+
+
+
+public:
+ RTreeToy(unsigned rmin, unsigned rmax, char /*handlefile*/):
+ rectangles(0),
+ color_shape(0, 0, 0, 0.9), color_shape_guide(1, 0, 0, 1),
+ color_select_area(1, 0, 0, 0.6 ), color_select_area_guide(1, 0, 0, 1 ), //1, 0, 0, 1
+ add_new_rect( false ), delete_rect( false ),
+ rect_chosen(), dummy_draw(),
+ rects_level( no_of_colors ),
+ color_rtree_level( no_of_colors, colour(0, 0, 0, 0) ),
+ drawBB_color(9), drawBB_color_all(true),
+ mode( INSERT_MODE ), drawBB(true),
+ out_str( insert_str ),
+ drawBB_str("ON"), drawBB_color_str("all"),
+ rtree( rmin, rmax, QUADRATIC_SPIT ),
+ hit( 0 ), rect_id( 0 )
+ {
+ // only "bright" colors
+ color_rtree_level[0] = colour(0, 0.80, 1, 1); // cyan
+ color_rtree_level[1] = colour(0, 0.85, 0, 1); // green
+ color_rtree_level[2] = colour(0.75, 0, 0.75, 1); // purple
+ color_rtree_level[3] = colour(0, 0, 1, 1); // blue
+ color_rtree_level[4] = colour(1, 0.62, 0, 1); // orange
+ color_rtree_level[5] = colour(1, 0, 0.8, 1); // pink
+ color_rtree_level[6] = colour(0.47, 0.26, 0.12, 1);
+ color_rtree_level[7] = colour(1, 0.90, 0, 1); // yellow
+ }
+
+};
+
+
+
+int main(int argc, char **argv) {
+
+ char* min_arg = NULL;
+ char* max_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'},
+ {"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: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 '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" <<
+ " -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 == 3 ){
+ 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;
+ }
+
+
+ char handlefile = 'T';
+ std::cout << "rmin: " << rmin << " rmax:" << rmax << std::endl;
+ init(argc, argv, new RTreeToy( rmin, rmax, handlefile) );
+
+ return 0;
+}
+
+
+
+const char* RTreeToy::menu_items[] =
+{
+ "Insert / Alter Rectangle",
+ "Search Rectangle",
+ "Delete Reactangle",
+ "Toggle"
+};
+
+const char RTreeToy::keys[] =
+{
+ 'I', 'U', 'S', 'D', 'T',
+ '0', '1', '2', '3', '4', '5', 'P'
+};
+
+
+
+
+
+
+/*
+intersection test
+ Rect r1( Point(100, 100), Point(150, 150)),
+ r2( Point(200, 200), Point(250, 250)),
+ r3( Point(50, 50), Point(100, 100));
+ OptRect a_intersection_b;
+ a_intersection_b = intersect( r1, r2 );
+ std::cout << "r1, r2 " << a_intersection_b.empty() << std::endl;
+ a_intersection_b = intersect( r1, r3 );
+ std::cout << "r1, r3 " << a_intersection_b.empty() << std::endl;
+*/
diff --git a/src/toys/sanitize.cpp b/src/toys/sanitize.cpp
new file mode 100644
index 0000000..1d9a81f
--- /dev/null
+++ b/src/toys/sanitize.cpp
@@ -0,0 +1,204 @@
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/utils.h>
+#include <cstdlib>
+#include <2geom/crossing.h>
+#include <2geom/path-intersection.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/pathvector.h>
+
+using namespace Geom;
+
+struct EndPoint {
+ public:
+ Point point, norm;
+ double time;
+ EndPoint() : time(0) { }
+ EndPoint(Point p, Point n, double t) : point(p), norm(n), time(t) { }
+};
+
+struct Edge {
+ public:
+ EndPoint from, to;
+ int ix;
+ bool cw;
+ Edge() { }
+ Edge(EndPoint f, EndPoint t, int i, bool c) : from(f), to(t), ix(i), cw(c) { }
+ bool operator==(Edge const &other) { return from.time == other.from.time && to.time == other.to.time; }
+};
+
+typedef std::vector<Edge> Edges;
+
+Edges edges(Path const &p, Crossings const &cr, unsigned ix) {
+ Edges ret = Edges();
+ EndPoint prev;
+ for(unsigned i = 0; i <= cr.size(); i++) {
+ double t = cr[i == cr.size() ? 0 : i].getTime(ix);
+ Point pnt = p.pointAt(t);
+ Point normal = p.pointAt(t+0.01) - pnt;
+ normal.normalize();
+ std::cout << pnt << "\n";
+ EndPoint cur(pnt, normal, t);
+ if(i == 0) { prev = cur; continue; }
+ ret.push_back(Edge(prev, cur, ix, false));
+ ret.push_back(Edge(prev, cur, ix, true));
+ prev = cur;
+ }
+ return ret;
+}
+
+template<class T>
+void append(std::vector<T> &vec, std::vector<T> const &other) {
+ vec.insert(vec.end(),other.begin(), other.end());
+}
+
+Edges edges(PathVector const &ps, CrossingSet const &crs) {
+ Edges ret = Edges();
+ for(unsigned i = 0; i < crs.size(); i++) {
+ Edges temp = edges(ps[i], crs[i], i);
+ append(ret, temp);
+ }
+ return ret;
+}
+
+PathVector edges_to_paths(Edges const &es, PathVector const &ps) {
+ PathVector ret;
+ for(const auto & e : es) {
+ ret.push_back(ps[e.ix].portion(e.from.time, e.to.time));
+ }
+ return ret;
+}
+
+void draw_cell(cairo_t *cr, Edges const &es, PathVector const &ps) {
+ cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), 0.5);
+ cairo_set_line_width(cr, uniform() * 10);
+ PathVector paths = edges_to_paths(es, ps);
+ Piecewise<D2<SBasis> > pw = paths_to_pw(paths);
+ double area;
+ Point centre;
+ Geom::centroid(pw, centre, area);
+ cairo_path(cr, paths); //* (Translate(-centre) * Scale(0.2) * Translate(centre*2)));
+ cairo_stroke(cr);
+}
+
+//Only works for normal
+double ang(Point n1, Point n2) {
+ return (dot(n1, n2)+1) * (cross(n1, n2) < 0 ? -1 : 1);
+}
+
+template<class T>
+void remove(std::vector<T> &vec, T const &val) {
+ for (typename std::vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) {
+ if(*it == val) {
+ vec.erase(it);
+ return;
+ }
+ }
+}
+
+std::vector<Edges> cells(cairo_t */*cr*/, PathVector const &ps) {
+ CrossingSet crs = crossings_among(ps);
+ Edges es = edges(ps, crs);
+ std::vector<Edges> ret = std::vector<Edges>();
+
+ while(!es.empty()) {
+ std::cout << "hello!\n";
+ Edge start = es.front();
+ Path p = Path();
+ Edge cur = start;
+ bool rev = false;
+ Edges cell = Edges();
+ do {
+ std::cout << rev << " " << cur.from.time << ", " << cur.to.time << "\n";
+ double a = 0;
+ Edge was = cur;
+ EndPoint curpnt = rev ? cur.from : cur.to;
+ Point norm = rev ? -curpnt.norm : curpnt.norm;
+ //Point to = curpnt.point + norm *20;
+
+ //std::cout << norm;
+ for(auto & e : es) {
+ if(e == was || e.cw != start.cw) continue;
+ if((!are_near(curpnt.time, e.from.time)) &&
+ are_near(curpnt.point, e.from.point, 0.1)) {
+ double v = ang(norm, e.from.norm);
+ //draw_line_seg(cr, curpnt.point, to);
+ //draw_line_seg(cr, to, es[i].from.point + es[i].from.norm*30);
+ //std::cout << v << "\n";
+ if(start.cw ? v < a : v > a ) {
+ a = v;
+ cur = e;
+ rev = false;
+ }
+ }
+ if((!are_near(curpnt.time, e.to.time)) &&
+ are_near(curpnt.point, e.to.point, 0.1)) {
+ double v = ang(norm, -e.to.norm);
+ if(start.cw ? v < a : v > a) {
+ a = v;
+ cur = e;
+ rev = true;
+ }
+ }
+ }
+ cell.push_back(cur);
+ remove(es, cur);
+ if(cur == was) break;
+ } while(!(cur == start));
+ if(are_near(start.to.point, rev ? cur.from.point : cur.to.point)) {
+ ret.push_back(cell);
+ }
+ }
+ return ret;
+}
+
+int cellWinding(Edges const &/*es*/, PathVector const &/*ps*/) {
+ return 0;
+}
+
+class Sanitize: public Toy {
+ PathVector paths;
+ std::vector<Edges> es;
+ PointSetHandle angh;
+ PointSetHandle pathix;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save,
+ std::ostringstream *timer_stream) override
+ {
+ int ix = pathix.pts[0][X] / 10;
+ es = cells(cr, paths);
+ draw_cell(cr, es[ix], paths);
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ //cairo_path(cr, paths);
+ //cairo_stroke(cr);
+
+ Point ap = angh.pts[1] - angh.pts[0], bp = angh.pts[2] - angh.pts[0];
+ ap.normalize(); bp.normalize();
+ *notify << ang(ap, bp);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ Sanitize () {}
+ void first_time(int argc, char** argv) override {
+ const char *path_name="sanitize_examples.svgd";
+ if(argc > 1)
+ path_name = argv[1];
+ paths = read_svgd(path_name);
+
+ handles.push_back(&angh); handles.push_back(&pathix);
+ angh.push_back(100, 100);
+ angh.push_back(80, 100);
+ angh.push_back(100, 80);
+ pathix.push_back(30, 200);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sanitize());
+ return 0;
+}
diff --git a/src/toys/sb-math-test.cpp b/src/toys/sb-math-test.cpp
new file mode 100644
index 0000000..9964e32
--- /dev/null
+++ b/src/toys/sb-math-test.cpp
@@ -0,0 +1,164 @@
+/*
+ * sb-math-test.cpp - some std functions to work with (pw)s-basis
+ *
+ * Authors:
+ * Jean-Francois Barraud
+ *
+ * Copyright (C) 2006-2007 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/sbasis-math.h>
+#include <2geom/piecewise.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <2geom/path.h>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+#define ZERO 1e-3
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#include <stdio.h>
+#include <math.h>
+
+#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file)
+
+//-Plot---------------------------------------------------------------
+static void plot(cairo_t* cr, double (*f)(double), Piecewise<SBasis> const &x, double vscale=1){
+ int NbPts=40;
+ for(int i=0; i<NbPts; i++){
+ double t=double(i)/NbPts;
+ t=x.cuts.front()*(1-t) + x.cuts.back()*t;
+ draw_handle(cr, Point(150+i*300./NbPts,300-(*f)(x(t))*vscale));
+ cairo_stroke(cr);
+ }
+}
+
+static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){
+ D2<Piecewise<SBasis> > plot;
+ plot[1]=-f;
+ plot[1]*=vscale;
+ plot[1]+=300;
+
+ plot[0].cuts.push_back(f.cuts.front());
+ plot[0].cuts.push_back(f.cuts.back());
+ plot[0].segs.emplace_back(Linear(150,450));
+
+ cairo_d2_pw_sb(cr, plot);
+
+ for (unsigned i=1; i<f.size(); i++){
+ cairo_move_to(cr, Point(150+f.cuts[i]*300,300));
+ cairo_line_to(cr, Point(150+f.cuts[i]*300,300-vscale*f.segs[i].at0()));
+ }
+}
+
+double my_inv(double x){return (1./x);}
+
+#define SIZE 4
+
+class SbCalculusToy: public Toy {
+ PWSBHandle pwsbh;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ //Let the user input sbasis coefs.
+ cairo_move_to(cr, Geom::Point(0,300));
+ cairo_line_to(cr, Geom::Point(600,300));
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ Piecewise<SBasis> f = pwsbh.value(300);
+
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .8);
+ plot(cr,f,1);
+ cairo_stroke(cr);
+
+/* cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8);
+ plot(cr,abs(f),1);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8);
+ plot(cr,signSb(f),75);
+ cairo_stroke(cr);*/
+
+// cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8);
+// plot(cr,maxSb(f, -f+50),1);
+// cairo_stroke(cr);
+
+// cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1);
+// plot(cr,sqrt(abs(f),.01,3),10);
+// plot(cr,&sqrt,abs(f),10);
+// cairo_stroke(cr);
+
+// cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1);
+// plot(cr,cos(f,.01,3),75);
+// plot(cr,&cos,f,75);
+// cairo_stroke(cr);
+
+// cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1);
+// plot(cr,sin(f,.01,3),75);
+// plot(cr,&sin,f,75);
+// cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1);
+ plot(cr,divide(Linear(1),f,.01,3),2);
+ plot(cr,&my_inv,f,10);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SbCalculusToy() : pwsbh(4,1){
+ for(int i = 0; i < 2*SIZE; i++)
+ pwsbh.push_back(i*100,150+150+uniform()*300*0);
+ handles.push_back(&pwsbh);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbCalculusToy);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-of-interval.cpp b/src/toys/sb-of-interval.cpp
new file mode 100644
index 0000000..3044620
--- /dev/null
+++ b/src/toys/sb-of-interval.cpp
@@ -0,0 +1,187 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#define SIZE 4
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*(-vscale);
+ plot[1]+=300;
+ cairo_d2_sb(cr, plot);
+// cairo_stroke(cr);
+}
+static void plot(cairo_t* cr, SBasisOf<Interval> const &f, double vscale=1,double /*dx*/=.05, double a=0,double b=1){
+ cairo_save(cr);
+#if 0
+ double t = a;
+ while (t<=b){
+ Interval i = f.valueAt(t);
+ std::cout<<i.min()<<","<<i.max()<<"\n";
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.min()*vscale ) );
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.max()*vscale ) );
+ t+=dx;
+ }
+#endif
+ D2<SBasis> plot;
+ Path pth;
+ pth.setStitching(true);
+ SBasis fmin(f.size(), Linear());
+ SBasis fmax(f.size(), Linear());
+ for(unsigned i = 0; i < f.size(); i++) {
+ for(unsigned j = 0; j < 2; j++) {
+ fmin[i][j] = f[i][j].min();
+ fmax[i][j] = f[i][j].max();
+ }
+ }
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=fmin*(-vscale);
+ plot[1]+=300;
+ pth.append(plot);
+ plot[1]=fmax*(-vscale);
+ plot[1]+=300;
+ pth.append(reverse(plot));
+ cairo_path(cr, pth);
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
+ cairo_fill(cr);
+ cairo_restore(cr);
+}
+
+
+
+class SbOfInterval: public Toy {
+
+ unsigned size;
+ PointHandle adjuster_a[3*SIZE];
+ PointHandle adjuster_b[3*SIZE];
+
+ void drawSliders(cairo_t *cr, PointHandle adjuster[], double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, Geom::Point(adjuster[3*i].pos[X],y_min));
+ cairo_line_to(cr, Geom::Point(adjuster[3*i].pos[X],y_max));
+ }
+ }
+ void drawIntervals(cairo_t *cr, PointHandle adjuster[], double /*x*/, double /*dx*/, double /*y_min*/, double /*y_max*/){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, adjuster[3*i+1].pos);
+ cairo_line_to(cr, adjuster[3*i+2].pos);
+ }
+ }
+ void setupSliders(PointHandle adjuster[], double x, double dx, double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ for (unsigned j=0; j < 3; j++){
+ adjuster[3*i+j].pos[X] = x + dx*i;
+ if (adjuster[3*i+j].pos[Y] < y_min) adjuster[3*i+j].pos[Y] = y_min;
+ if (adjuster[3*i+j].pos[Y] > y_max) adjuster[3*i+j].pos[Y] = y_max;
+ }
+ if (adjuster[3*i+1].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i+1].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] > adjuster[3*i+1].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+1].pos[Y];
+ }
+ }
+
+ PointSetHandle hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ SBasisOf<Interval> f;
+ double min=150, max=450;
+ setupSliders(adjuster_a, 100, 15, min, max);
+ setupSliders(adjuster_b, 500, 15, min, max);
+ drawSliders(cr, adjuster_a, min, max);
+ drawSliders(cr, adjuster_b, min, max);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+ drawIntervals(cr, adjuster_a, 100, 15, min, max);
+ drawIntervals(cr, adjuster_b, 500, 15, min, max);
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 0.8, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ cairo_move_to(cr, Geom::Point(150,300));
+ cairo_line_to(cr, Geom::Point(450,300));
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ for (unsigned i=0; i < size; i++){
+ double amin = (max+min)/2 - adjuster_a[3*i+1].pos[Y];
+ double amax = (max+min)/2 - adjuster_a[3*i+2].pos[Y];
+ Interval ai(amin*std::pow(4.,(int)i), amax*std::pow(4.,(int)i));
+ double bmin = (max+min)/2 - adjuster_b[3*i+1].pos[Y];
+ double bmax = (max+min)/2 - adjuster_b[3*i+2].pos[Y];
+ Interval bi(bmin*std::pow(4.,(int)i), bmax*std::pow(4.,(int)i));
+ f.push_back(LinearOf<Interval>(ai,bi));
+ }
+ SBasis f_dble(size, Linear());
+ for (unsigned i=0; i < size; i++){
+ double ai = (max+min)/2 - adjuster_a[3*i].pos[Y];
+ double bi = (max+min)/2 - adjuster_b[3*i].pos[Y];
+ f_dble[i] = Linear(ai*std::pow(4.,(int)i),bi*std::pow(4.,(int)i));
+ }
+
+ plot(cr,f_dble);
+ plot(cr,f);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SbOfInterval(){
+ size=SIZE;
+ for(unsigned i=0; i < size; i++){
+ adjuster_a[3*i].pos[X] = 0;
+ adjuster_a[3*i].pos[Y] = 300;
+ adjuster_a[3*i+1].pos[X] = 0;
+ adjuster_a[3*i+1].pos[Y] = 350;
+ adjuster_a[3*i+2].pos[X] = 0;
+ adjuster_a[3*i+2].pos[Y] = 250;
+ adjuster_b[3*i].pos[X] = 0;
+ adjuster_b[3*i].pos[Y] = 300;
+ adjuster_b[3*i+1].pos[X] = 0;
+ adjuster_b[3*i+1].pos[Y] = 350;
+ adjuster_b[3*i+2].pos[X] = 0;
+ adjuster_b[3*i+2].pos[Y] = 250;
+ }
+
+ for(unsigned i = 0; i < size; i++) {
+ handles.push_back(&(adjuster_a[3*i]));
+ handles.push_back(&(adjuster_a[3*i+1]));
+ handles.push_back(&(adjuster_a[3*i+2]));
+ handles.push_back(&(adjuster_b[3*i]));
+ handles.push_back(&(adjuster_b[3*i+1]));
+ handles.push_back(&(adjuster_b[3*i+2]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbOfInterval);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-of-sb.cpp b/src/toys/sb-of-sb.cpp
new file mode 100644
index 0000000..d2ee2e6
--- /dev/null
+++ b/src/toys/sb-of-sb.cpp
@@ -0,0 +1,478 @@
+#include <time.h>
+#include <vector>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using namespace Geom;
+using namespace std;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+#if 0
+template<unsigned dim>
+class LinearDim;
+template<unsigned dim>
+class SBasisDim;
+
+template<unsigned dim>
+class LinearDim : public LinearOf<SBasisDim<dim-1> >{
+public:
+ LinearDim() : LinearOf<SBasisDim<dim-1> >() {};
+ LinearDim(SBasisDim <dim-1> const &a, SBasisDim<dim-1> const &b ) : LinearOf<SBasisDim<dim-1> >(a,b) {};
+ LinearDim(LinearDim <dim-1> const &a, LinearDim<dim-1> const &b ) :
+ LinearOf<SBasisDim<dim-1> >(SBasisDim<dim-1>(a),SBasisDim<dim-1>(b)) {};
+};
+
+template<>
+class LinearDim<1> : public LinearOf<double>{
+public:
+ LinearDim() : LinearOf<double>() {};
+ LinearDim(double const &a, double const &b ) : LinearOf<double>(a,b) {};
+};
+
+
+template<unsigned dim>
+class SBasisDim : public SBasisOf<SBasisDim<dim-1> >{
+public:
+ SBasisDim() : SBasisOf<SBasisDim<dim-1> >() {};
+ SBasisDim(LinearDim<dim> const &lin) :
+ SBasisOf<SBasisDim<dim-1> >(LinearOf<SBasisDim<dim-1> >(lin[0],lin[1])) {};
+};
+
+template<>
+class SBasisDim<1> : public SBasisOf<double>{
+/*
+public:
+ SBasisDim<1>() : SBasisOf<double>() {};
+ SBasisDim(SBasisOf<double> other) : SBasisOf<double>(other) {};
+ SBasisDim(LinearOf<double> other) : SBasisOf<double>(other) {};
+*/
+};
+
+SBasis toSBasis(SBasisDim<1> f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+
+template<unsigned dim_f,unsigned dim_g>
+SBasisDim<dim_g> compose(SBasisDim<dim_f> const &f, std::vector<SBasisDim<dim_g> > const &g ){
+
+ assert( dim_f <= g.size() );
+
+ SBasisDim<dim_g> u0 = g[dim_f-1];
+ SBasisDim<dim_g> u1 = -u0 + 1;
+ SBasisDim<dim_g> s = multiply(u0,u1);
+ SBasisDim<dim_g> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ if ( dim_f>1 ){
+ r = s*r + compose(f[i][0],g)*u0 + compose(f[i][1],g)*u1;
+ }else{
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ }
+ return r;
+}
+
+#endif
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u0 = g;
+ SBasisOf<T> u1 = -u0 + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasisOf<double> > const &X){
+ return compose(f, X[0], X[1]);
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasis> const &X){
+ return compose(f, toSBasisOfDouble(X[0]), toSBasisOfDouble(X[1]));
+}
+
+/*
+static
+SBasis eval_v(SBasisOf<SBasis> const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}
+static
+SBasis eval_v(SBasisOf<SBasisOf<double> > const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}*/
+static
+SBasisOf<double> eval_dim(SBasisOf<SBasisOf<double> > const &f, double t, unsigned dim){
+ if (dim == 1) return f.valueAt(t);
+ SBasisOf<double> result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(LinearOf<double>(f[i][0].valueAt(t),f[i][1].valueAt(t)));
+ }
+ return result;
+}
+
+/*
+static
+SBasis eval_v(SBasisDim<2> const &f, double v){
+ SBasis result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(Linear(f[i][0].valueAt(v),f[i][1].valueAt(v)));
+ }
+ return result;
+}
+*/
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+ // find the point on the x,y plane that projects to P
+ Point unproject(Point P) {
+ return P * from_basis(x, y, O).inverse();
+ }
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasisOf<double> const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + toSBasis(z)*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ plot3d(cr, Linear(0,1), Linear(i/(iMax-1.)), eval_dim(f, i/(iMax-1.), 0), frame);
+ plot3d(cr, Linear(i/(iMax-1.)), Linear(0,1), eval_dim(f, i/(iMax-1.), 1), frame);
+ }
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integral(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ }
+ return result;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,1),
+ LinearOf<double>(0,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,0),
+ LinearOf<double>(1,1)));
+ SBasisOf<SBasisOf<double> > v_u = v - u*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(dom_f.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,(v - u*(dom_f.extent()/dom_g.extent())));
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ Piecewise<SBasis> result;
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ double rho = dom_f.extent()/dom_g.extent();
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0.,0.));
+ b = SBasisOf<double>(LinearOf<double>(0.,1/rho));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent(),dom_f.min()/dom_g.extent()+1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0.,1-1/rho));
+ b = SBasisOf<double>(LinearOf<double>(1/rho,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent()+1, dom_f.max()/dom_g.extent() ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(1.-1/rho,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.max()/dom_g.extent(), dom_f.max()/dom_g.extent()+1 ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> subderivative(SBasisOf<T> const& f) {
+ SBasisOf<T> res;
+ for(unsigned i = 0; i < f.size(); i++) {
+ res.push_back(LinearOf<T>(derivative(f[i][0]), derivative(f[i][1])));
+ }
+ return res;
+}
+
+OptInterval bounds_fast(SBasisOf<double> const &f) {
+ return bounds_fast(toSBasis(f));
+}
+
+/**
+ * Finds a path which traces the 0 contour of f, traversing from A to B as a single cubic d2<sbasis>.
+ * The algorithm is based on matching direction and curvature at each end point.
+ */
+//TODO: handle the case when B is "behind" A for the natural orientation of the level set.
+//TODO: more generally, there might be up to 4 solutions. Choose the best one!
+D2<SBasis>
+sbofsb_cubic_solve(SBasisOf<SBasisOf<double> > const &f, Geom::Point const &A, Geom::Point const &B){
+ D2<SBasis>result;//(Linear(A[X],B[X]),Linear(A[Y],B[Y]));
+ //g_warning("check 0 = %f = %f!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y]));
+
+ SBasisOf<SBasisOf<double> > f_u = derivative(f);
+ SBasisOf<SBasisOf<double> > f_v = subderivative(f);
+ SBasisOf<SBasisOf<double> > f_uu = derivative(f_u);
+ SBasisOf<SBasisOf<double> > f_uv = derivative(f_v);
+ SBasisOf<SBasisOf<double> > f_vv = subderivative(f_v);
+
+ Geom::Point dfA(f_u.valueAt(A[X]).valueAt(A[Y]),f_v.valueAt(A[X]).valueAt(A[Y]));
+ Geom::Point dfB(f_u.valueAt(B[X]).valueAt(B[Y]),f_v.valueAt(B[X]).valueAt(B[Y]));
+
+ Geom::Point V0 = rot90(dfA);
+ Geom::Point V1 = rot90(dfB);
+
+ double D2fVV0 = f_uu.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[X]+
+ 2*f_uv.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[Y]+
+ f_vv.valueAt(A[X]).valueAt(A[Y])*V0[Y]*V0[Y];
+ double D2fVV1 = f_uu.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[X]+
+ 2*f_uv.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[Y]+
+ f_vv.valueAt(B[X]).valueAt(B[Y])*V1[Y]*V1[Y];
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1);
+ if (candidates.size()==0) {
+ return D2<SBasis>(SBasis(A[X],B[X]),SBasis(A[Y],B[Y]));
+ }
+ //TODO: I'm sure std algorithm could do that for me...
+ double error = -1;
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ Interval bounds = *bounds_fast(compose(f,candidates[i]));
+ double new_error = (fabs(bounds.max())>fabs(bounds.min()) ? fabs(bounds.max()) : fabs(bounds.min()) );
+ if ( new_error < error || error < 0 ){
+ error = new_error;
+ best = i;
+ }
+ }
+ return candidates[best];
+}
+
+class SBasis0fSBasisToy: public Toy {
+ PointSetHandle hand;
+ PointSetHandle cut_hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ //double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ //double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+
+
+ SBasisOf<SBasisOf<double> > f,u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,-1),LinearOf<double>(1,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,1),LinearOf<double>(-1,1)));
+#if 1
+ f = u*u + v*v - LinearOf<SBasisOf<double> >(LinearOf<double>(1,1),LinearOf<double>(1,1));
+ //*notify << "input dim = " << f.input_dim() <<"\n";
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ LineSegment ls(frame.unproject(cut_hand.pts[0]),
+ frame.unproject(cut_hand.pts[1]));
+ SBasis cutting = toSBasis(compose(f, ls.toSBasis()));
+ //cairo_sb(cr, cutting);
+ //cairo_stroke(cr);
+ plot3d(cr, ls.toSBasis()[0], ls.toSBasis()[1], SBasis(0.0), frame);
+ vector<double> rts = roots(cutting);
+ if(rts.size() >= 2) {
+ Geom::Point A = ls.pointAt(rts[0]);
+ Geom::Point B = ls.pointAt(rts[1]);
+
+ //Geom::Point A(1,0.5);
+ //Geom::Point B(0.5,1);
+ D2<SBasis> zeroset = sbofsb_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+ }
+#else
+
+ SBasisOf<SBasisOf<double> > g = u - v ;
+ g += LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(.5,.5)));
+ SBasisOf<double> h;
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(1,1));
+
+ f = multi_compose(h,g);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0.25, 0.25, 1.);
+ cairo_stroke(cr);
+/*
+ SBasisDim<1> g = SBasisOf<double>(LinearOf<double>(0,1));
+ g.push_back(LinearOf<double>(-1,-1));
+ std::vector<SBasisDim<2> > vars;
+ vars.push_back(ff);
+ plot3d(cr,compose(g,vars),frame);
+ cairo_set_source_rgba (cr, .5, 0.9, 0.5, 1.);
+ cairo_stroke(cr);
+*/
+#endif
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasis0fSBasisToy(){
+ handles.push_back(&hand);
+ handles.push_back(&cut_hand);
+ cut_hand.push_back(100,100);
+ cut_hand.push_back(500,500);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasis0fSBasisToy);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-to-bez.cpp b/src/toys/sb-to-bez.cpp
new file mode 100644
index 0000000..337180f
--- /dev/null
+++ b/src/toys/sb-to-bez.cpp
@@ -0,0 +1,404 @@
+/*
+ * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier.
+ *
+ * Copyright 2007 jf barraud.
+ *
+ * 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.
+ *
+ */
+
+// mainly experimental atm...
+// do not expect to find anything understandable here atm.
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/basic-intersection.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define ZERO 1e-7
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#include <stdio.h>
+#include <gsl/gsl_poly.h>
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale);
+ B[1] = Linear(450) - p[i]*vscale;
+ cairo_d2_sb(cr, B);
+ }
+}
+
+//===================================================================================
+
+D2<SBasis>
+naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){
+
+ Piecewise<D2<SBasis> > dM = derivative(M);
+ Point M0 = M(t0);
+ Point dM0 = dM(t0)*(t1-t0);
+ Point M1 = M(t1);
+ Point dM1 = dM(t1)*(t1-t0);
+ D2<SBasis> result;
+ for (unsigned dim=0; dim<2; dim++){
+ SBasis r(2, Linear());
+ r[0] = Linear(M0[dim],M1[dim]);
+ r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim]));
+ result[dim] = r;
+ }
+ return result;
+}
+
+D2<SBasis>
+sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){
+ Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1;
+ Piecewise<D2<SBasis> > dM,d2M;
+ dM=derivative(M);
+ d2M=derivative(dM);
+ M0 =M(t0);
+ M1 =M(t1);
+ dM0 =dM(t0);
+ dM1 =dM(t1);
+ d2M0=d2M(t0);
+ d2M1=d2M(t1);
+ A0=M(t0);
+ A1=M(t1);
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1);
+ if (candidates.empty()){
+ return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ;
+ }
+ double maxlength = -1;
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ double l = length(candidates[i]);
+ if ( l < maxlength || maxlength < 0 ){
+ maxlength = l;
+ best = i;
+ }
+ }
+ return candidates[best];
+}
+#include <2geom/sbasis-to-bezier.h>
+
+int goal_function_type = 0;
+
+double goal_function(Piecewise<D2<SBasis> >const &A,
+ Piecewise<D2<SBasis> >const&B) {
+ if(goal_function_type) {
+ OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B))));
+ //double h_dist = bnds.dimensions().length();
+//0 is in the rect!, TODO:gain factor ~2 for free.
+// njh: not really, the benefit is actually rather small.
+ double h_dist = 0;
+ if(bnds)
+ h_dist = bnds->extent();
+ return h_dist ;
+ } else {
+ Rect bnds = *bounds_fast(A - B);
+ return max(bnds.min().length(), bnds.max().length());
+ }
+}
+
+int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) {
+ if (t0>=t1) return 0;//TODO: fix me...
+ if (t0+0.001>=t1) return 0;//TODO: fix me...
+
+ //TODO: don't re-compute derivative(f) at each try!!
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1);
+
+ if(k_bez[0].size() > 1 and k_bez[1].size() > 1) {
+ Piecewise<SBasis> s = arcLengthSb(k_bez);
+ s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1();
+ s += t0;
+ double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez));
+ if(h_dist < precision) {
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.93);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ draw_handle(cr, k_bez.at0());
+ cairo_d2_sb(cr, k_bez);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ return 1;
+ }
+ }
+ //TODO: find a better place where to cut (at the worst fit?).
+ return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) +
+ recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision);
+}
+
+double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) {
+ if (t0>=t1) return 0;//TODO: fix me...
+ if (t0+0.001>=t1) return 0;//TODO: fix me...
+
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1);
+
+ if(k_bez[0].size() > 1 and k_bez[1].size() > 1) {
+ Piecewise<SBasis> s = arcLengthSb(k_bez);
+ s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1();
+ s += t0;
+ return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez));
+ }
+ return 1e100;
+}
+
+struct quadratic_params
+{
+ Piecewise<D2<SBasis> > const *f;
+ double t0, precision;
+};
+
+
+double quadratic (double x, void *params) {
+ struct quadratic_params *p
+ = (struct quadratic_params *) params;
+
+ return single_curvature_fitter(*p->f, p->t0, x) - p->precision;
+}
+
+#include <stdio.h>
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_roots.h>
+
+
+int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) {
+ if(t0 >= t1) return 0;
+
+ double r = t1;
+ if(single_curvature_fitter(f, t0, t1) > precision) {
+ int status;
+ int iter = 0, max_iter = 100;
+ const gsl_root_fsolver_type *T;
+ gsl_root_fsolver *s;
+ gsl_function F;
+ struct quadratic_params params = {&f, t0, precision};
+
+ F.function = &quadratic;
+ F.params = &params;
+
+ T = gsl_root_fsolver_brent;
+ s = gsl_root_fsolver_alloc (T);
+ gsl_root_fsolver_set (s, &F, t0, t1);
+
+ do
+ {
+ iter++;
+ status = gsl_root_fsolver_iterate (s);
+ r = gsl_root_fsolver_root (s);
+ double x_lo = gsl_root_fsolver_x_lower (s);
+ double x_hi = gsl_root_fsolver_x_upper (s);
+ status = gsl_root_test_interval (x_lo, x_hi,
+ 0, 0.001);
+
+
+ }
+ while (status == GSL_CONTINUE && iter < max_iter);
+
+ double x_lo = gsl_root_fsolver_x_lower (s);
+ double x_hi = gsl_root_fsolver_x_upper (s);
+ printf ("%5d [%.7f, %.7f] %.7f %.7f\n",
+ iter, x_lo, x_hi,
+ r,
+ x_hi - x_lo);
+ gsl_root_fsolver_free (s);
+ }
+ D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r);
+
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.93);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ draw_handle(cr, k_bez.at0());
+ cairo_d2_sb(cr, k_bez);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ if(r < t1)
+ return sequential_curvature_fitter(cr, f, r, t1, precision) + 1;
+ return 1;
+}
+
+
+class SbToBezierTester: public Toy {
+ //std::vector<Slider> sliders;
+ std::vector<PointSetHandle*> path_psh;
+ PointHandle adjuster, adjuster2;
+ std::vector<Toggle> toggles;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_save(cr);
+ for(unsigned i = 1; i < path_psh.size(); i++)
+ path_psh[i-1]->pts.back() = path_psh[i]->pts[0];
+ Piecewise<D2<SBasis> > f_as_pw(path_psh[0]->asBezier());
+ for(unsigned i = 1; i < path_psh.size(); i++) {
+ f_as_pw.push_seg(path_psh[i]->asBezier());
+ }
+ //f=handles_to_sbasis(handles.begin(), SIZE-1);
+ adjuster.pos[1]=450;
+ adjuster.pos[0]=std::max(adjuster.pos[0],150.);
+ adjuster.pos[0]=std::min(adjuster.pos[0],450.);
+ double t0=0;//(adjuster.pos[0]-150)/300;
+ double t1=(adjuster.pos[0]-150)/300;
+ //if (t0>t1) {double temp=t0;t0=t1;t1=temp;}
+
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 0.5);
+ cairo_pw_d2_sb(cr, f_as_pw);
+ cairo_stroke(cr);
+ if (t0==t1) return;//TODO: fix me...
+#if 0
+ if(0) {
+ Piecewise<D2<SBasis> > g = f_as_pw;
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0., 0., 0.9, .7);
+ double error=0;
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., .7);
+ D2<SBasis> naive_bez = naive_sb_seg_to_bez(g,0,t1);
+ cairo_d2_sb(cr, naive_bez);
+ cairo_stroke(cr);
+
+ adjuster2.pos[0]=150;
+ adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.);
+
+ double scale0=(450-adjuster2.pos[1])/150;
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, .7);
+ D2<SBasis> k_bez = sb_seg_to_bez(g,t0,t1);
+ cairo_d2_sb(cr, k_bez);
+ cairo_stroke(cr);
+ double h_a_t = 0, h_b_t = 0;
+
+ double h_dist = hausdorfl( k_bez, f, 1e-6, &h_a_t, &h_b_t);
+ {
+ Point At = k_bez(h_a_t);
+ Point Bu = f(h_b_t);
+ cairo_move_to(cr, At);
+ cairo_line_to(cr, Bu);
+ draw_handle(cr, At);
+ draw_handle(cr, Bu);
+ cairo_save(cr);
+ cairo_set_line_width (cr, 0.3);
+ cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ *notify << "Move handle 6 to set the segment to be approximated by cubic bezier.\n";
+ *notify << " -red: bezier approx derived from parametrization.\n";
+ *notify << " -blue: bezier approx derived from curvature.\n";
+ *notify << " max distance (to original): "<<h_dist<<"\n";
+ }
+#endif
+
+
+ f_as_pw = arc_length_parametrization(f_as_pw);
+ adjuster2.pos[0]=150;
+ adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.);
+ cairo_move_to(cr, 150, 150);
+ cairo_line_to(cr, 150, 450);
+ cairo_stroke(cr);
+ ostringstream val_s;
+ double scale0=(450-adjuster2.pos[1])/300;
+ double curve_precision = pow(10, scale0*5-2);
+ val_s << curve_precision;
+ draw_text(cr, adjuster2.pos, val_s.str().c_str());
+
+ int segs = 0;
+ goal_function_type = toggles[1].on;
+ if(toggles[0].on)
+ segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision);
+ else {
+ segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision);
+ }
+ Geom::PathVector vpt = path_from_piecewise(f_as_pw, curve_precision, true);
+ unsigned default_number_curves = 0;
+ for(const auto & i : vpt) {
+ default_number_curves += i.size();
+ }
+
+ *notify << " segments from default algorithm: "<< default_number_curves <<"\n";
+ *notify << " total segments: "<< segs <<"\n";
+ cairo_restore(cr);
+ Point p(25, height - 100), d(50,25);
+ toggles[0].bounds = Rect(p, p + d);
+ p+= Point(75, 0);
+ toggles[1].bounds = Rect(p, p + d);
+ draw_toggles(cr, toggles);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ void key_hit(GdkEventKey *e) override {
+ if(e->keyval == 's') toggles[0].toggle();
+ redraw();
+ }
+ void mouse_pressed(GdkEventButton* e) override {
+ toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ }
+ SbToBezierTester() {
+ //if(handles.empty()) {
+ for(int j = 0; j < 3; j++) {
+ path_psh.push_back(new PointSetHandle());
+ for(unsigned i = 0; i < 6; i++)
+ path_psh.back()->push_back(150+300*uniform(),150+300*uniform());
+ handles.push_back(path_psh.back());
+ }
+ adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform());
+ handles.push_back(&adjuster);
+ adjuster2.pos = Geom::Point(150,300);
+ handles.push_back(&adjuster2);
+ toggles.emplace_back("Seq", true);
+ toggles.emplace_back("Linfty", true);
+ //}
+ //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t"));
+ //handles.push_back(&(sliders[0]));
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbToBezierTester);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-zeros.cpp b/src/toys/sb-zeros.cpp
new file mode 100644
index 0000000..ebe3aea
--- /dev/null
+++ b/src/toys/sb-zeros.cpp
@@ -0,0 +1,63 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+#define SIZE 4
+
+class SBZeros: public Toy {
+ PointSetHandle pB1, pB2;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ D2<SBasis> B1 = pB1.asBezier();
+ D2<SBasis> B2 = pB2.asBezier();
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.concat(Piecewise<D2<SBasis> >(B2));
+ std::vector<Point> e;
+ std::vector<Piecewise<D2<SBasis> > > s;
+ s.push_back(derivative(B));
+ for(int j = 0; j < 5; j++) s.push_back(derivative(s.back()));
+ for(int j = 0; j <= 5; j++) {
+ for(unsigned d = 0; d < 2; d++) {
+ std::vector<double> r = roots(make_cuts_independent(s[j])[d]);
+ for(double k : r) e.push_back(B.valueAt(k));
+ }
+ }
+ for(auto & i : e) draw_cross(cr, i);
+
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_pw_d2_sb(cr, B);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ SBZeros () {
+ for(unsigned i = 0; i < SIZE; i++)
+ pB1.push_back(150+uniform()*300,150+uniform()*300);
+ for(unsigned i = 0; i < SIZE; i++)
+ pB2.push_back(150+uniform()*300,150+uniform()*300);
+ handles.push_back(&pB1);
+ handles.push_back(&pB2);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBZeros());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/sb1d.cpp b/src/toys/sb1d.cpp
new file mode 100644
index 0000000..dabbebb
--- /dev/null
+++ b/src/toys/sb1d.cpp
@@ -0,0 +1,125 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/choose.h>
+#include <2geom/convex-hull.h>
+
+#include <2geom/path.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+extern unsigned total_steps, total_subs;
+
+double& handle_to_sb(unsigned i, unsigned n, SBasis &sb) {
+ assert(i < n);
+ assert(n <= sb.size()*2);
+ unsigned k = i;
+ if(k >= n/2) {
+ k = n - k - 1;
+ return sb[k][1];
+ } else
+ return sb[k][0];
+}
+
+double handle_to_sb_t(unsigned i, unsigned n) {
+ double k = i;
+ if(i >= n/2)
+ k = n - k - 1;
+ double t = k/(2*k+1);
+ if(i >= n/2)
+ return 1 - t;
+ else
+ return t;
+}
+
+class Sb1d: public Toy {
+public:
+ PointSetHandle hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0.5, 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ if(!save) {
+ for(unsigned i = 0; i < hand.pts.size(); i++) {
+ hand.pts[i][0] = width*handle_to_sb_t(i, hand.pts.size())/2 + width/4;
+ if(i)
+ cairo_line_to(cr, hand.pts[i]);
+ else
+ cairo_move_to(cr, hand.pts[i]);
+ }
+ }
+
+ D2<SBasis> B;
+ B[0] = Linear(width/4, 3*width/4);
+ B[1].resize(hand.pts.size()/2);
+ for(auto & i : B) {
+ i = Linear(0);
+ }
+ for(unsigned i = 0; i < hand.pts.size(); i++) {
+ handle_to_sb(i, hand.pts.size(), B[1]) = 3*width/4 - hand.pts[i][1];
+ }
+ for(unsigned i = 1; i < B[1].size(); i++) {
+ B[1][i] = B[1][i]*choose<double>(2*i+1, i);
+ }
+
+ Interval bs = *bounds_fast(B[1]);
+ double lo, hi;
+ lo = 3*width/4 - bs.min();
+ hi = 3*width/4 - bs.max();
+ cairo_move_to(cr, B[0](0), lo);
+ cairo_line_to(cr, B[0](1), lo);
+ cairo_move_to(cr, B[0](0), hi);
+ cairo_line_to(cr, B[0](1), hi);
+ cairo_stroke(cr);
+ *notify << "sb bounds = "<<lo << ", " <<hi<<std::endl;
+ //B[1] = SBasis(Linear(3*width/4)) - B[1];
+ *notify << B[0] << ", ";
+ *notify << B[1];
+ Geom::Path pb;
+ B[1] = SBasis(Linear(3*width/4)) - B[1];
+ pb.append(B);
+ pb.close(false);
+ cairo_path(cr, pb);
+
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+
+ Geom::ConvexHull ch(hand.pts);
+
+ cairo_move_to(cr, ch.back());
+ for(auto i : ch) {
+ cairo_line_to(cr, i);
+ }
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+public:
+Sb1d () {
+ hand.pts.emplace_back(0,450);
+ for(unsigned i = 0; i < 4; i++)
+ hand.pts.emplace_back(uniform()*400, uniform()*400);
+ hand.pts.emplace_back(0,450);
+ handles.push_back(&hand);
+}
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb1d());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/sb2d-solver.cpp b/src/toys/sb2d-solver.cpp
new file mode 100644
index 0000000..a60f8cd
--- /dev/null
+++ b/src/toys/sb2d-solver.cpp
@@ -0,0 +1,282 @@
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <gsl/gsl_poly.h>
+
+using std::vector;
+using namespace Geom;
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+SBasis2d conic_sb2d(vector<double> /*coeff*/) {
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.empty()||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+#if 0
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+
+#elif 0
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#else
+ SBasis2d f = conic_sb2d(vector<double>());
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#endif
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 1; degree < 4; degree++) {
+ //D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ D2<SBasis> zeroset = sb2d_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ 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/src/toys/sb2d.cpp b/src/toys/sb2d.cpp
new file mode 100644
index 0000000..b449d01
--- /dev/null
+++ b/src/toys/sb2d.cpp
@@ -0,0 +1,83 @@
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+unsigned total_pieces_sub;
+unsigned total_pieces_inc;
+
+class Sb2d: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2d() {
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ SBasis2d sb2;
+ sb2.us = 2;
+ sb2.vs = 2;
+ const int depth = sb2.us*sb2.vs;
+ const int surface_handles = 4*depth;
+ sb2.resize(depth, Linear2d(0));
+ vector<Geom::Point> display_handles(surface_handles);
+ Geom::Point dir(1,-2);
+ if(hand.pts.empty()) {
+ for(unsigned vi = 0; vi < sb2.vs; vi++)
+ for(unsigned ui = 0; ui < sb2.us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++)
+ hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+
+ hand.pts.push_back(Geom::Point(3*width/4., width/4.) + 30*dir);
+ }
+ dir = (hand.pts[surface_handles] - Geom::Point(3*width/4., width/4.)) / 30;
+ if(!save) {
+ cairo_move_to(cr, 3*width/4., width/4.);
+ cairo_line_to(cr, hand.pts[surface_handles]);
+ }
+ for(unsigned vi = 0; vi < sb2.vs; vi++)
+ for(unsigned ui = 0; ui < sb2.us; ui++)
+ for(unsigned iv = 0; iv < 2; iv++)
+ for(unsigned iu = 0; iu < 2; iu++) {
+ unsigned corner = iu + 2*iv;
+ unsigned i = ui + vi*sb2.us;
+ Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4.,
+ (2*(iv+vi)/(2.*vi+1)+1)*width/4.);
+ double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir);
+ display_handles[corner+4*i] = dl*dir + base;
+ sb2[i][corner] = dl*10/(width/2)*pow(4.,(double)ui+vi);
+ }
+ cairo_sb2d(cr, sb2, dir*0.1, width);
+
+ *notify << "bo = " << sb2.index(0,0);
+
+ cairo_set_source_rgba (cr, 0., 0.125, 0, 1);
+ cairo_stroke(cr);
+ if(!save)
+ for(auto & display_handle : display_handles)
+ draw_circ(cr, display_handle);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2d());
+ 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/src/toys/sbasis-fitting.cpp b/src/toys/sbasis-fitting.cpp
new file mode 100644
index 0000000..9963790
--- /dev/null
+++ b/src/toys/sbasis-fitting.cpp
@@ -0,0 +1,214 @@
+/*
+ * SBasis Fitting Example
+ *
+ * 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/numeric/fitting-tool.h>
+#include <2geom/numeric/fitting-model.h>
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+
+using namespace Geom;
+
+
+class SBasisFitting : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ sliders[0].geometry(Point(50, 30), 100);
+
+ size_t value = (size_t)(sliders[0].value());
+
+ if (degree != value)
+ {
+ degree = value;
+ total_handles = degree + 1;
+ order = total_handles/2 - 1;
+ dstep = interval_length/degree;
+ step = 1.0/degree;
+
+ double x = sx;
+ psh.pts.clear();
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ psh.push_back(x, 300*uniform()+50);
+ x += dstep;
+ }
+
+ handles[0] = &psh;
+
+ if (fmsb != NULL) delete fmsb;
+ fmsb = new NL::LFMSBasis(order);
+ assert(fmsb != NULL);
+ if (lsf_sb != NULL) delete lsf_sb;
+ lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25);
+ assert(lsf_sb != NULL);
+
+ double t = 0;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ lsf_sb->append(t);
+ t += step;
+ }
+ lsf_sb->update();
+
+ curr_ys.clear();
+ curr_ys.resize(total_handles);
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ curr_ys[i] = psh.pts[i][Y];
+ }
+ prev_ys = curr_ys;
+
+ fmsb->instance(sb_curve, lsf_sb->result(curr_ys));
+ }
+ else
+ {
+ double x = sx;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ psh.pts[i][X] = x;
+ curr_ys[i] = psh.pts[i][Y];
+ x += dstep;
+ }
+ fmsb->instance(sb_curve, lsf_sb->result(prev_ys, curr_ys));
+ prev_ys = curr_ys;
+ }
+
+
+ D2<SBasis> curve;
+ curve[X] = SBasis(Linear(sx,sx) + interval_length * Linear(0, 1));
+ curve[Y] = sb_curve;
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0);
+ cairo_set_line_width (cr, 0.3);
+ cairo_d2_sb(cr, curve);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ SBasisFitting()
+ : degree(3),
+ total_handles(degree+1),
+ order(total_handles/2 - 1),
+ interval_length(400),
+ dstep(interval_length/degree),
+ step(1.0/degree)
+ {
+ sx = 50;
+ double x = sx;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ psh.push_back(x, 300*uniform()+50);
+ x += dstep;
+ }
+
+ handles.push_back(&psh);
+
+ fmsb = new NL::LFMSBasis(order);
+ assert(fmsb != NULL);
+ lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25);
+ assert(lsf_sb != NULL);
+
+ double t = 0;
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ lsf_sb->append(t);
+ t += step;
+ }
+ lsf_sb->update();
+
+ curr_ys.clear();
+ curr_ys.resize(total_handles);
+ for (size_t i = 0; i < total_handles; ++i)
+ {
+ curr_ys[i] = psh.pts[i][Y];
+ }
+ prev_ys = curr_ys;
+
+ fmsb->instance(sb_curve, lsf_sb->result(curr_ys));
+
+
+ sliders.emplace_back(1, 11, 2, 3, "degree");
+ handles.push_back(&(sliders[0]));
+ }
+
+ ~SBasisFitting() override
+ {
+ if (fmsb != NULL) delete fmsb;
+ if (lsf_sb != NULL) delete lsf_sb;
+ }
+
+ private:
+ size_t degree;
+ size_t total_handles;
+ size_t order;
+ double interval_length;
+ double dstep;
+ double step;
+ double sx;
+ std::vector<double> curr_ys, prev_ys;
+ SBasis sb_curve;
+ NL::LFMSBasis* fmsb;
+ NL::least_squeares_fitter<NL::LFMSBasis>* lsf_sb;
+ PointSetHandle psh;
+ std::vector<Slider> sliders;
+};
+
+
+
+
+int main(int argc, char **argv)
+{
+ init( argc, argv, new SBasisFitting(), 600, 600 );
+ 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/src/toys/sbasisdim.cpp b/src/toys/sbasisdim.cpp
new file mode 100644
index 0000000..3804090
--- /dev/null
+++ b/src/toys/sbasisdim.cpp
@@ -0,0 +1,262 @@
+#include <iostream>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/linearN.h>
+#include <2geom/orphan-code/sbasisN.h>
+
+using namespace Geom;
+using namespace std;
+
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, double x, double y, double z, Frame frame){
+ Point p;
+ for (unsigned dim=0; dim<2; dim++){
+ p[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ p[dim] += frame.O[dim];
+ }
+ draw_cross(cr, p);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr, LinearN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toLinear(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toLinear(f.partialEval(t, 0)), frame);
+ }
+}
+void
+plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toSBasis(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toSBasis(f.partialEval(t, 0)), frame);
+ }
+}
+void
+dot_plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 15;
+ double t[2];
+ for (int i=0; i<iMax; i++){
+ t[0] = i/(iMax-1.);
+ for (int j=0; j<iMax; j++){
+ t[1] = j/(iMax-1.);
+ plot3d(cr, t[0], t[1], f.valueAt(t), frame);
+ }
+ }
+}
+
+
+class SBasisDimToy: public Toy {
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasisN<1> t = LinearN<1>(0,1);
+
+ LinearN<2> u,v;
+ setToVariable(u,0);
+ setToVariable(v,1);
+ SBasisN<2> f, x = u, y = v; //x,y are used for conversion :-(
+
+
+//--------------------
+//Basic MultiDegree<2> tests...
+//--------------------
+#if 0
+ unsigned sizes[2];
+ sizes[0] = 4;
+ sizes[1] = 3;
+ MultiDegree<2> d0;
+ d0.p[0]=3;
+ d0.p[1]=2;
+ std::cout<<"(3,2)->"<< d0.asIdx(sizes) <<"\n";
+ MultiDegree<2> d1(11,sizes);
+ std::cout<<"11->"<< d1.p[0] <<","<<d1.p[1] <<"\n";
+#endif
+
+//--------------------
+//Basic LinearN tests
+//--------------------
+#if 0
+ plot3d(cr, u, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0., 0., 1.);
+ cairo_stroke(cr);
+ plot3d(cr, v, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Basic SBasisN tests
+//--------------------
+#if 1
+ f = x*x + y*y;//(x-one*.5)*(x-one*.5);
+ std::cout<<"\nf: "<<f<<"\n";
+ std::cout<<"Degrees:\n";
+ std::cout<<"quick_deg: "<< f.quick_degree(0)<<", "<<f.quick_degree(1)<<"\n";
+ std::cout<<"real s_deg: "<< f.degree(0)<<", "<<f.degree(1)<<"\n";
+ std::cout<<"real t_deg: "<< f.real_t_degree(0)<<", "<<f.real_t_degree(1)<<"\n";
+ plot3d(cr, f, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+// SBasisOf<SBasisOf<double> > simulation tests
+//--------------------
+#if 1
+ SBasisN<1> y1d = LinearN<1>(0,1);
+ SBasisN<2> g,g1;
+ g.appendCoef(LinearN<1>(0.), y1d*y1d , 0);
+ g.appendCoef(y1d + 1, y1d, 0);
+ g1 = x*y*y + x*(-x+1)*( (-x+1)*(y+1) + x*y );
+ plot3d(cr, g-g1, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+//--------------------
+// SBasisN composition tests
+//--------------------
+#if 1
+ SBasisN<1> z;
+ std::vector<SBasisN<1> > var;
+ t -=.5;
+ var.push_back( t*t + tA);
+ var.push_back( (t+.3)*t*(t-.3) + tB);
+ z = compose(f,var);
+ cairo_set_line_width(cr,1);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), Linear(0.), frame);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), toSBasis(z), frame);
+ cairo_set_source_rgba (cr, 0.75, 0., 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Some timing. TODO: Compare to SBasisOf<SBasisOf<double> >
+//--------------------
+#if 0
+ double units = 1e6;
+ std::string units_string("us");
+ double timer_precision = 0.1;
+ clock_t end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ // Base loop to remove overhead
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ long iterations = 0;
+ while(end_t > clock()) {
+ iterations++;
+ }
+ double overhead = timer_precision*units/iterations;
+
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ iterations = 0;
+ while(end_t > clock()) {
+ f.valueAt(t);
+ iterations++;
+ }
+ *notify << "recursive eval: "
+ << ", time = " << timer_precision*units/iterations-overhead
+ << units_string << std::endl;
+#endif
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasisDimToy(){
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasisDimToy);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/scribble.cpp b/src/toys/scribble.cpp
new file mode 100644
index 0000000..afaff65
--- /dev/null
+++ b/src/toys/scribble.cpp
@@ -0,0 +1,366 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/path-intersection.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/transforms.h>
+#include <2geom/angle.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <sstream>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+// TODO:
+// use path2
+// replace Ray stuff with path2 line segments.
+
+//-----------------------------------------------
+
+void draw_segment(cairo_t* cr, Point const& p1, Point const& p2)
+{
+ cairo_move_to(cr, p1);
+ cairo_line_to(cr, p2);
+}
+
+void draw_segment(cairo_t* cr, Point const& p1, double angle, double length)
+{
+ Point p2;
+ p2[X] = length * std::cos(angle);
+ p2[Y] = length * std::sin(angle);
+ p2 += p1;
+ draw_segment(cr, p1, p2);
+}
+
+void draw_segment(cairo_t* cr, LineSegment const& ls)
+{
+ draw_segment(cr, ls[0], ls[1]);
+}
+
+int num = 30;
+vector<double> c1_bot;
+vector<double> c1_top;
+vector<double> c2_bot;
+vector<double> c2_top;
+vector<double> c3_bot;
+vector<double> c3_top;
+vector<double> c4_bot;
+vector<double> c4_top;
+
+CubicBezier create_bezier(Point const &anchor, double angle /* in degrees */,
+ double length, double dx1, double dx2, cairo_t *cr = NULL) {
+ Point A = anchor;
+ Point dir = Point(1.0, 0) * Rotate(-angle) * length;
+ Point B = anchor + dir;
+
+ Point C = A - Point(1.0, 0) * dx1;
+ Point D = B + Point(1.0, 0) * dx1;
+ Point E = A + Point(1.0, 0) * dx2;
+ Point F = B - Point(1.0, 0) * dx2;
+
+ if (cr) {
+ draw_cross(cr, A);
+ draw_cross(cr, B);
+ draw_cross(cr, C);
+ draw_cross(cr, D);
+ draw_cross(cr, E);
+ draw_cross(cr, F);
+ }
+
+ return CubicBezier(C, E, F, D);
+}
+
+/*
+ * Draws a single "scribble segment" (we use many of these to cover the whole curve).
+ *
+ * Let I1, I2 be two adjacent intervals (bounded by the points A1, A2, A3) on the lower and J1, J2
+ * two adjacent intervals (bounded by B1, B2, B3) on the upper parallel. Then we specify:
+ *
+ * - The point in I1 where the scribble line starts (given by a value in [0,1])
+ * - The point in J2 where the scribble line ends (given by a value in [0,1])
+ * - A point in I2 (1st intermediate point of the Bezier curve)
+ * - A point in J1 (2nd intermediate point of the Bezier curve)
+ *
+ */
+CubicBezier
+create_bezier_again(Point const &anchor1, Point const &anchor2, Point const &dir1, Point const &dir2,
+ double /*c1*/, double /*c2*/, double c3, double c4, double mu, cairo_t *cr = NULL) {
+ Point A = anchor1;// - dir * c1;
+ Point B = anchor1 + dir1 * (c3 + mu);
+ Point C = anchor2 - dir2 * (c4 + mu);
+ Point D = anchor2;// + dir * c2;
+
+ if (cr) {
+ draw_cross(cr, A);
+ //draw_cross(cr, B);
+ //draw_cross(cr, C);
+ //draw_cross(cr, D);
+ }
+
+ return CubicBezier(A, B, C, D);
+}
+
+CubicBezier
+create_bezier_along_curve(Piecewise<D2<SBasis> > const &curve1,
+ Piecewise<D2<SBasis> > const &curve2,
+ double segdist,
+ Coord const t1, Coord const t2, Point const &n,
+ double c1, double c2, double /*c3*/, double /*c4*/, double /*mu*/, cairo_t *cr = NULL) {
+ cout << "create_bezier_along_curve -- start" << endl;
+ /*
+ Point A = curve1.valueAt(t1 - c1);
+ Point B = curve1.valueAt(t1) + n * (c3 + mu);
+ Point C = curve2.valueAt(t2) - n * (c4 + mu);
+ Point D = curve2.valueAt(t2 + c2);
+ */
+ Point A = curve1.valueAt(t1 - c1 * segdist);
+ Point B = curve1.valueAt(t1) + n * 0.1;
+ Point C = curve2.valueAt(t2) - n * 0.1;
+ Point D = curve2.valueAt(t2 + c2 * segdist);
+
+ if (cr) {
+ draw_cross(cr, A);
+ //draw_cross(cr, B);
+ //draw_cross(cr, C);
+ //draw_cross(cr, D);
+ }
+
+ cout << "create_bezier_along_curve -- end" << endl;
+ return CubicBezier(A, B, C, D);
+}
+
+class OffsetTester: public Toy {
+ PointSetHandle psh;
+ PointSetHandle psh_rand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ double w = 600;
+ double slider_top = w/4.;
+ double slider_bot = w*3./4.;
+ double slider_margin = 40;
+ double slider_middle = (slider_top + slider_bot) / 2;
+
+ if(psh.pts.empty()) {
+ psh.pts.emplace_back(200,300);
+ psh.pts.emplace_back(350,250);
+ psh.pts.emplace_back(500,280);
+ psh.pts.emplace_back(700,300);
+
+ psh.pts.emplace_back(400,300);
+ psh.pts.emplace_back(550,250);
+ psh.pts.emplace_back(700,280);
+ psh.pts.emplace_back(900,300);
+
+ psh.pts.emplace_back(900,500);
+ psh.pts.emplace_back(700,480);
+ psh.pts.emplace_back(550,450);
+ psh.pts.emplace_back(400,500);
+
+ psh_rand.pts.emplace_back(slider_margin,slider_bot);
+ psh_rand.pts.emplace_back(slider_margin,slider_top);
+ psh_rand.pts.emplace_back(slider_margin,slider_top);
+ psh_rand.pts.emplace_back(slider_margin,slider_top);
+ psh_rand.pts.emplace_back(slider_margin,slider_top);
+ psh_rand.pts.emplace_back(slider_margin,slider_bot);
+ psh_rand.pts.emplace_back(slider_margin,slider_middle);
+ }
+
+ psh_rand.pts[0][X] = slider_margin;
+ if (psh_rand.pts[0][Y]<slider_top) psh_rand.pts[0][Y] = slider_top;
+ if (psh_rand.pts[0][Y]>slider_bot) psh_rand.pts[0][Y] = slider_bot;
+ psh_rand.pts[1][X] = slider_margin + 15;
+ if (psh_rand.pts[1][Y]<slider_top) psh_rand.pts[1][Y] = slider_top;
+ if (psh_rand.pts[1][Y]>slider_bot) psh_rand.pts[1][Y] = slider_bot;
+ psh_rand.pts[2][X] = slider_margin + 30;
+ if (psh_rand.pts[2][Y]<slider_top) psh_rand.pts[2][Y] = slider_top;
+ if (psh_rand.pts[2][Y]>slider_bot) psh_rand.pts[2][Y] = slider_bot;
+ psh_rand.pts[3][X] = slider_margin + 45;
+ if (psh_rand.pts[3][Y]<slider_top) psh_rand.pts[3][Y] = slider_top;
+ if (psh_rand.pts[3][Y]>slider_bot) psh_rand.pts[3][Y] = slider_bot;
+ psh_rand.pts[4][X] = slider_margin + 60;
+ if (psh_rand.pts[4][Y]<slider_top) psh_rand.pts[4][Y] = slider_top;
+ if (psh_rand.pts[4][Y]>slider_bot) psh_rand.pts[4][Y] = slider_bot;
+ psh_rand.pts[5][X] = slider_margin + 75;
+ if (psh_rand.pts[5][Y]<slider_top) psh_rand.pts[5][Y] = slider_top;
+ if (psh_rand.pts[5][Y]>slider_bot) psh_rand.pts[5][Y] = slider_bot;
+ psh_rand.pts[6][X] = slider_margin + 90;
+ if (psh_rand.pts[6][Y]<slider_top) psh_rand.pts[6][Y] = slider_top;
+ if (psh_rand.pts[6][Y]>slider_bot) psh_rand.pts[6][Y] = slider_bot;
+
+ *notify << "Sliders:" << endl << endl << endl << endl;
+ *notify << "0 - segment distance" << endl;
+ *notify << "1 - start anchor randomization" << endl;
+ *notify << "2 - end anchor randomization" << endl;
+ *notify << "3 - start rounding randomization" << endl;
+ *notify << "4 - end rounding randomization" << endl;
+ *notify << "5 - start/end rounding increase randomization" << endl;
+ *notify << "6 - additional offset of the upper anchors (to modify the segment angle)" << endl;
+
+ for(unsigned i = 0; i < psh_rand.size(); ++i) {
+ cairo_move_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_top));
+ }
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+
+ // Draw the curve and its offsets
+ D2<SBasis> B = psh.asBezier();
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ Coord offset = 30;
+ Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B)));
+ Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(B)+n*offset;
+ Piecewise<D2<SBasis> > offset_curve2 = Piecewise<D2<SBasis> >(B)-n*offset;
+ PathVector offset_path1 = path_from_piecewise(offset_curve1, 0.1);
+ PathVector offset_path2 = path_from_piecewise(offset_curve2, 0.1);
+ Piecewise<D2<SBasis> > tangent1 = unitVector(derivative(offset_curve1));
+ Piecewise<D2<SBasis> > tangent2 = unitVector(derivative(offset_curve2));
+ cairo_set_line_width (cr, 1);
+ cairo_path(cr, offset_path1);
+ cairo_path(cr, offset_path2);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+
+ double lambda1 = 1.0 - (psh_rand.pts[1][Y] - slider_top) * 2.0/w;
+ double lambda2 = 1.0 - (psh_rand.pts[2][Y] - slider_top) * 2.0/w;
+ double lambda3 = 1.0 - (psh_rand.pts[3][Y] - slider_top) * 2.0/w;
+ double lambda4 = 1.0 - (psh_rand.pts[4][Y] - slider_top) * 2.0/w;
+ double mu = 1.0 - (psh_rand.pts[5][Y] - slider_top) * 2.0/w;
+ //Point dir = Point(1,0) * (slider_bot - psh_rand.pts[0][Y]) / 2.5;
+ double off = 0.5 - (psh_rand.pts[6][Y] - slider_top) * 2.0/w;
+
+ double segdist = (slider_bot - psh_rand.pts[0][Y]) / (slider_bot - slider_top) * 0.1;
+ if (segdist < 0.01) {
+ segdist = 0.01;
+ }
+
+ vector<Point> pts_bot;
+ vector<Point> pts_top;
+ vector<Point> dirs_bot;
+ vector<Point> dirs_top;
+ int counter = 0;
+ for(double i = 0.0; i < 1.0; i += segdist) {
+ draw_cross(cr, offset_curve1.valueAt(i));
+ pts_bot.push_back(offset_curve1.valueAt(i + segdist * c1_bot[counter] * lambda1));
+ pts_top.push_back(offset_curve2.valueAt(i + segdist * (c2_top[counter] * lambda2 + 1/2.0) + off));
+ dirs_bot.push_back(tangent1.valueAt(i) * 20);
+ dirs_top.push_back(tangent2.valueAt(i) * 20);
+ ++counter;
+ }
+
+ for(int i = 0; i < num; ++i) {
+ cout << "c1_bot[" << i << "]: " << c1_bot[i] << endl;
+ }
+
+ for (int i = 0; i < num-1; ++i) {
+ Path path1;
+ //cout << "dirs_bot[" << i << "]: " << dirs_bot[i] << endl;
+ cout << "c3_bot[" << i << "]: " << c3_bot[i] << endl;
+ CubicBezier bc = create_bezier_again(pts_bot[i], pts_top[i],
+ dirs_bot[i], dirs_top[i],
+ 0, 0, c3_bot[i] * lambda3, c4_top[i] * lambda4, mu, cr);
+ //c1_bot[i] * lambda1,
+ //c2_top[i] * lambda2,
+ //c3_bot[i] * lambda3,
+ //c4_top[i] * lambda4, mu, cr);
+
+ path1.append(bc);
+ cairo_path(cr, path1);
+
+ Path path2;
+ bc = create_bezier_again(pts_top[i], pts_bot[i+1],
+ dirs_top[i], dirs_bot[i+1],
+ 0, 0, c4_bot[i] * lambda4, c3_top[i] * lambda3, mu, cr);
+ /*
+ bc = create_bezier_again(pts_top[i+1], pts_bot[i], dir,
+ 1.0 - c2_top[i] * lambda2,
+ 1.0 - c1_bot[i+1] * lambda1,
+ c3_top[i] * lambda3,
+ c4_bot[i] * lambda4, mu, cr);
+ */
+ path2.append(bc);
+ cairo_path(cr, path2);
+ }
+
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ OffsetTester() {
+ handles.push_back(&psh);
+ handles.push_back(&psh_rand);
+ /*
+ psh.pts.clear();
+ Point A(100,300);
+ //Point B(100,200);
+ //Point C(200,330);
+ Point D(200,100);
+ psh.push_back(A);
+ //psh.push_back(B);
+ //psh.push_back(C);
+ psh.push_back(D);
+ psh.push_back(Geom::Point(slider_margin,slider_bot));
+ */
+
+ for (int i = 0; i < num; ++i) {
+ c1_bot.push_back(uniform() - 0.5);
+ c1_top.push_back(uniform() - 0.5);
+ //c1_bot.push_back(1.0);
+ //c1_top.push_back(1.0);
+ c2_bot.push_back(uniform() - 0.5);
+ c2_top.push_back(uniform() - 0.5);
+
+ c3_bot.push_back(uniform());
+ c3_top.push_back(uniform());
+ c4_bot.push_back(uniform());
+ c4_top.push_back(uniform());
+ }
+
+ /*
+ for (int i = 0; i < num; ++i) {
+ c1_bot[i] = c1_bot[i] / 10.0;
+ c2_bot[i] = c2_bot[i] / 10.0;
+ c3_bot[i] = c3_bot[i] / 10.0;
+ c4_bot[i] = c4_bot[i] / 10.0;
+
+ c1_top[i] = c1_top[i] / 10.0;
+ c2_top[i] = c2_top[i] / 10.0;
+ c3_top[i] = c3_top[i] / 10.0;
+ c4_top[i] = c4_top[i] / 10.0;
+ }
+ */
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new OffsetTester);
+ 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:encoding = utf-8:textwidth = 99 :
+
+
diff --git a/src/toys/self-intersect.cpp b/src/toys/self-intersect.cpp
new file mode 100644
index 0000000..840e058
--- /dev/null
+++ b/src/toys/self-intersect.cpp
@@ -0,0 +1,67 @@
+#include <2geom/basic-intersection.h>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+class SelfIntersect: public Toy {
+ PointSetHandle psh;
+void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_line_width (cr, 0.5);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+
+ D2<SBasis> A = psh.asBezier();
+ //Rect Ar = *bounds_fast(A);
+ cairo_d2_sb(cr, A);
+ cairo_stroke(cr);
+
+ std::vector<std::pair<double, double> > all_si;
+
+ find_self_intersections(all_si, A);
+
+ cairo_stroke(cr);
+ cairo_set_source_rgba (cr, 1., 0., 1, 1);
+ for(auto & i : all_si) {
+ draw_handle(cr, A(i.first));
+ }
+ cairo_stroke(cr);
+
+ *notify << "total intersections: " << all_si.size();
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+}
+public:
+SelfIntersect (unsigned bez_ord) {
+ handles.push_back(&psh);
+ for(unsigned i = 0; i < bez_ord; i++)
+ psh.push_back(uniform()*400, uniform()*400);
+}
+};
+
+int main(int argc, char **argv) {
+ unsigned bez_ord=5;
+ if(argc > 1)
+ sscanf(argv[1], "%d", &bez_ord);
+ init(argc, argv, new SelfIntersect(bez_ord));
+
+ 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/src/toys/sketch-fitter.cpp b/src/toys/sketch-fitter.cpp
new file mode 100644
index 0000000..2a74924
--- /dev/null
+++ b/src/toys/sketch-fitter.cpp
@@ -0,0 +1,923 @@
+/*
+ * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier.
+ *
+ * Copyright 2007 jf barraud.
+ * 2008 njh
+ *
+ * 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.
+ *
+ */
+
+// mainly experimental atm...
+// do not expect to find anything understandable here atm.
+
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/bezier-utils.h>
+
+#include <2geom/circle.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#define ZERO 1e-7
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#include <stdio.h>
+#include <gsl/gsl_poly.h>
+
+std::vector<Point> neighbors(std::vector<Point> const &pts, unsigned idx, double radius){
+ std::vector<Point> res;
+ Point p0 = pts[idx];
+ for (auto p : pts){
+ if ( L2(p-p0) < radius ) res.push_back(p);
+ }
+ return res;
+}
+
+double curvature(Point const &a, Point const &b, Point const &c){
+ Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) );
+ Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) );
+ OptCrossing o = intersection(med_ab, med_bc);
+ if (o){
+ Point oo = med_ab.pointAt(o->ta);
+ return(1./L2(oo-a));
+ }
+ else
+ return 0;
+}
+
+double avarageCurvature(std::vector<Point> const &pts, unsigned idx, double radius){
+ std::vector<Point> ngbrs = neighbors(pts, idx, radius);
+ if (ngbrs.size()<3) return 0;
+ double k=0;
+ double mass = 0;
+ for (unsigned i=0; i<5; i++){
+ unsigned ia = 0, ib = 0, ic = 0;
+ ia = rand()%ngbrs.size();
+ while (ib == ia)
+ ib = rand()%ngbrs.size();
+ while (ic == ia || ic == ib)
+ ic = rand()%ngbrs.size();
+ k += curvature(pts[ia],pts[ib],pts[ic]);
+ mass += 1; //smaller mass to closer triplets?
+ }
+ k /= mass;
+ return k;
+}
+
+Point massCenter(std::vector<Point> const &pts){
+ Point g = Point(0,0);
+ for (unsigned i=0; i<pts.size(); i++){
+ g += pts[i]/pts.size();
+ }
+ return g;
+}
+
+Line meanSquareLine(std::vector<Point> const &pts){
+ Point g = massCenter(pts);
+ double a = 0, b = 0, c = 0;
+ for (auto pt : pts){
+ a += (pt[Y]-g[Y])*(pt[Y]-g[Y]);
+ b +=-(pt[X]-g[X])*(pt[Y]-g[Y]);
+ c += (pt[X]-g[X])*(pt[X]-g[X]);
+ }
+ double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2;
+ Point u(-b,a-eigen);
+ return Line(g, g+u);
+}
+
+void tighten(std::vector<Point> &pts, double radius, bool linear){
+ for (unsigned i=0; i<pts.size(); i++){
+ std::vector<Point> ngbrs = neighbors(pts,i,radius);
+ if (linear){
+ Line d = meanSquareLine(ngbrs);
+ Point proj = projection( pts[i], d );
+ double t = 2./3.;
+ pts[i] = pts[i]*(1-t) + proj*t;
+ }else if (ngbrs.size()>=3) {
+ Circle c;
+ c.fit(ngbrs);
+ Point o = c.center();
+ double r = c.radius();
+ pts[i] = o + unit_vector(pts[i]-o)*r;
+ }
+ }
+}
+
+double dist_to(std::vector<Point> const &pts, Point const &p, unsigned *idx=NULL){
+ double d,d_min = std::numeric_limits<float>::infinity();
+ if (idx) *idx = pts.size();
+ for (unsigned i = 0; i<pts.size(); i++){
+ d = L2(pts[i]-p);
+ if ( d < d_min ){
+ d_min = d;
+ if (idx) *idx = i;
+ }
+ }
+ return d_min;
+}
+
+void fuse_close_points(std::vector<Point> &pts, double dist_min){
+ if (pts.size()==0) return;
+ std::vector<Point> reduced_pts;
+ reduced_pts.push_back(pts[0]);
+ for (auto & pt : pts){
+ double d = dist_to(reduced_pts, pt);
+ if ( d > dist_min ) reduced_pts.push_back(pt);
+ }
+ pts = reduced_pts;
+ return;
+}
+
+
+unsigned nearest_after(std::vector<Point>const &pts, unsigned idx, double *dist = NULL){
+ if ( idx >= pts.size()-1 ) return pts.size();
+ Point p = pts[idx];
+ unsigned res = idx+1;
+ double d_min = L2(p-pts[res]);
+ for (unsigned i=idx+2; i<pts.size(); i++){
+ double d = L2(p-pts[i]);
+ if (d < d_min) {
+ d_min = d;
+ res = i;
+ }
+ }
+ if (dist) *dist = d_min;
+ return res;
+}
+
+//TEST ME: use direction information to separate exaeco?
+void sort_nearest(std::vector<Point> &pts, double no_longer_than = 0){
+ double d;
+ Point p;
+ for (unsigned i=0; i<pts.size()-1; i++){
+ unsigned j = nearest_after(pts,i,&d);
+ if (no_longer_than >0.1 && d > no_longer_than){
+ pts.erase(pts.begin()+i+1, pts.end());
+ return;
+ }
+ p = pts[i+1];
+ pts[i+1] = pts[j];
+ pts[j] = p;
+ }
+}
+
+//FIXME: optimize me if further used...
+void sort_nearest_bis(std::vector<Point> &pts, double radius){
+ double d;
+ Point p;
+ for (unsigned i=0; i<pts.size()-1; i++){
+ bool already_visited = true;
+ unsigned next = 0; // silence warning
+ while ( i < pts.size()-1 && already_visited ){
+ next = nearest_after(pts,i,&d);
+ already_visited = false;
+ for (unsigned k=0; k<i; k++){
+ double d_k_next = L2( pts[next] - pts[k]);
+ if ( d_k_next < d && d_k_next < radius ){
+ already_visited = true;
+ pts.erase(pts.begin()+next);
+ break;
+ }
+ }
+ }
+ if (!already_visited){
+ p = pts[i+1];
+ pts[i+1] = pts[next];
+ pts[next] = p;
+ }
+ }
+}
+
+Path ordered_fit(std::vector<Point> &pts, double tol){
+ unsigned n_points = pts.size();
+ Geom::Point * b = g_new(Geom::Point, 4*n_points);
+ Geom::Point * points = g_new(Geom::Point, 4*n_points);
+ for (unsigned int i = 0; i < pts.size(); i++) {
+ points[i] = pts[i];
+ }
+ int max_segs = 4*n_points;
+ int const n_segs = bezier_fit_cubic_r(b, points, n_points,
+ tol*tol, max_segs);
+ Path res;
+ if ( n_segs > 0){
+ res = Path(b[0]);
+ for (int i=0; i<n_segs; i++){
+ res.appendNew<CubicBezier>(b[4*i+1],b[4*i+2],b[4*i+3]);
+ }
+ }
+ g_free(b);
+ g_free(points);
+ return res;
+}
+
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+
+
+std::vector<Point> eat(std::vector<Point> const &pts, double sampling){
+ std::vector<bool> visited(pts.size(),false);
+ std::vector<Point> res;
+ Point p = pts.front();
+ //Point q = p;
+ res.push_back(p);
+ while(true){
+ double num_nghbrs = 0;
+ Point next(0,0);
+ for(unsigned i = 0; i < pts.size(); i++) {
+ if (!visited[i] && L2(pts[i]-p)<sampling){
+ //TODO: rotate pts[i] so that last step was in dir -pi...
+ //dir += atan2(pts[i]-p);
+ visited[i] = true;
+ next+= pts[i]-p;
+ num_nghbrs += 1;
+ }
+ }
+ if (num_nghbrs == 0) break;
+ //q=p;
+ next *= 1./num_nghbrs;
+ p += next;
+ res.push_back(p);
+ }
+ return res;
+}
+
+
+
+
+
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+
+double exp_rescale(double x)
+{
+ return pow(10, x*5-2);
+}
+std::string exp_formatter(double x)
+{
+ return default_formatter(exp_rescale(x));
+}
+
+class SketchFitterToy: public Toy {
+
+ enum menu_item_t
+ {
+ SHOW_MENU = 0,
+ TEST_TIGHTEN,
+ TEST_EAT_BY_STEP,
+ TEST_TIGHTEN_EAT,
+ TEST_CURVATURE,
+ TEST_SORT,
+ TEST_NUMERICAL,
+ SHOW_HELP,
+ TOTAL_ITEMS // this one must be the last item
+ };
+
+ enum handle_label_t
+ {
+ };
+
+ enum toggle_label_t
+ {
+ DRAW_MOUSES = 0,
+ DRAW_IMPROVED_MOUSES,
+ DRAW_STROKE,
+ TIGHTEN_USE_CIRCLE,
+ SORT_BIS,
+ TOTAL_TOGGLES // this one must be the last item
+ };
+
+ enum slider_label_t
+ {
+ TIGHTEN_NBHD_SIZE = 0,
+ TIGHTEN_ITERRATIONS,
+ EAT_NBHD_SIZE,
+ SORT_RADIUS,
+ FUSE_RADIUS,
+ INTERPOLATE_RADIUS,
+ CURVATURE_NBHD_SIZE,
+ POINT_CHOOSER,
+ TOTAL_SLIDERS // this one must be the last item
+ };
+
+ static const char* menu_items[TOTAL_ITEMS];
+ static const char keys[TOTAL_ITEMS];
+
+ void fit_empty(){}
+ void first_time(int /*argc*/, char** /*argv*/) override
+ {
+ draw_f = &SketchFitterToy::draw_menu;
+ fit_f = &SketchFitterToy::fit_empty;
+ }
+
+ void init_common()
+ {
+ set_common_control_geometry = true;
+ set_control_geometry = true;
+
+ handles.clear();
+ handles.push_back(&(toggles[DRAW_MOUSES]));
+ handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES]));
+ handles.push_back(&(toggles[DRAW_STROKE]));
+
+ //sliders.clear();
+ //toggles.clear();
+ //handles.clear();
+ }
+ void init_common_ctrl_geom(cairo_t* /*cr*/, int width, int /*height*/, std::ostringstream* /*notify*/)
+ {
+ if ( set_common_control_geometry )
+ {
+ set_common_control_geometry = false;
+ Point p(10, 20), d(width/3-20,25);
+ toggles[DRAW_MOUSES].bounds = Rect(p, p + d);
+ p += Point ((width)/3, 0);
+ toggles[DRAW_IMPROVED_MOUSES].bounds = Rect(p, p + d);
+ p += Point ((width)/3, 0);
+ toggles[DRAW_STROKE].bounds = Rect(p, p + d);
+ }
+ }
+ virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool /*save*/, std::ostringstream */*timer_stream*/)
+ {
+ init_common_ctrl_geom(cr, width, height, notify);
+ if(!mouses.empty() && toggles[DRAW_MOUSES].on ) {
+ //cairo_move_to(cr, mouses[0]);
+ //for(unsigned i = 0; i < mouses.size(); i++) {
+ // cairo_line_to(cr, mouses[i]);
+ //}
+ for(auto & mouse : mouses) {
+ draw_cross(cr, mouse);
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0., .25);
+ cairo_set_line_width (cr, 0.5);
+ cairo_stroke(cr);
+ }
+
+ if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
+ cairo_move_to(cr, improved_mouses[0]);
+ for(auto & improved_mouse : improved_mouses) {
+ draw_cross(cr, improved_mouse);
+ }
+ cairo_set_source_rgba (cr, 1., 0., 0., 1);
+ cairo_set_line_width (cr, .75);
+ cairo_stroke(cr);
+ }
+
+ if(!stroke.empty() && toggles[DRAW_STROKE].on) {
+ cairo_pw_d2_sb(cr, stroke);
+ cairo_set_source_rgba (cr, 0., 0., 1., 1);
+ cairo_set_line_width (cr, .75);
+ cairo_stroke(cr);
+ }
+
+ *notify << "Press SHIFT to continue sketching. 'Z' to apply changes";
+ }
+
+
+//-----------------------------------------------------------------------------------------
+// Tighten: tries to move the points toward the common curve
+//-----------------------------------------------------------------------------------------
+ void init_tighten()
+ {
+ init_common();
+ handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
+ handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
+ handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
+ }
+ void init_tighten_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180);
+ sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
+
+ Point p(width-250, height - 50), d(225,25);
+ toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d);
+ }
+ }
+ void fit_tighten(){
+ improved_mouses = mouses;
+ double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
+ for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
+ tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
+ }
+ }
+ void draw_tighten(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_tighten_ctrl_geom(cr, notify, width, height);
+ }
+
+//-----------------------------------------------------------------------------------------
+// Eat by step: eats the curve moving at each step in the average direction of the neighbors.
+//-----------------------------------------------------------------------------------------
+ void init_eat()
+ {
+ init_common();
+ handles.push_back(&(sliders[EAT_NBHD_SIZE]));
+ }
+ void init_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ sliders[EAT_NBHD_SIZE].geometry(Point(50, height - 35*(0+2)), 180);
+ }
+ }
+ void fit_eat(){
+ double radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
+ improved_mouses = mouses;
+
+ tighten(improved_mouses, 20, true);
+
+ stroke = Piecewise<D2<SBasis> >();
+ improved_mouses = eat(improved_mouses, radius);
+ Path p(improved_mouses[0]);
+ for(unsigned i = 1; i < improved_mouses.size(); i++) {
+ p.appendNew<LineSegment>(improved_mouses[i]);
+ }
+ stroke = p.toPwSb();
+ }
+ void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_eat_ctrl_geom(cr, notify, width, height);
+ }
+
+//-----------------------------------------------------------------------------------------
+// Tighten + Eat
+//-----------------------------------------------------------------------------------------
+ void init_tighten_eat()
+ {
+ init_common();
+ handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
+ handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
+ handles.push_back(&(sliders[EAT_NBHD_SIZE]));
+ }
+ void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180);
+ sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
+ sliders[EAT_NBHD_SIZE ].geometry(Point(50, height - 35*4), 180);
+ }
+ }
+ void fit_tighten_eat(){
+ improved_mouses = mouses;
+ double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
+ for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
+ tighten(improved_mouses, radius, toggles[0].on);
+ }
+ stroke = Piecewise<D2<SBasis> >();
+ radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
+ improved_mouses = eat(improved_mouses, radius);
+ Path p(improved_mouses[0]);
+ for(unsigned i = 1; i < improved_mouses.size(); i++) {
+ p.appendNew<LineSegment>(improved_mouses[i]);
+ }
+ stroke = p.toPwSb();
+ }
+ void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_tighten_eat_ctrl_geom(cr, notify, width, height);
+ }
+
+//-----------------------------------------------------------------------------------------
+// Sort: tighten, then sort and eventually fuse.
+//-----------------------------------------------------------------------------------------
+ void init_sort()
+ {
+ init_common();
+ handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
+ handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
+ handles.push_back(&(sliders[SORT_RADIUS]));
+ handles.push_back(&(sliders[FUSE_RADIUS]));
+ handles.push_back(&(sliders[INTERPOLATE_RADIUS]));
+ handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
+ handles.push_back(&(toggles[SORT_BIS]));
+ }
+ void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180);
+ sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
+ sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180);
+ sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180);
+ sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180);
+
+ Point p(width-250, height - 50), d(225,25);
+ toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d);
+ p += Point(0,-30);
+ toggles[SORT_BIS].bounds = Rect(p, p + d);
+ }
+ }
+ void fit_sort(){
+ improved_mouses = mouses;
+ double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
+ for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
+ tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
+ }
+ double max_jump = exp_rescale(sliders[SORT_RADIUS].value());
+ if (toggles[SORT_BIS].on){
+ sort_nearest_bis(improved_mouses, max_jump);
+ }else{
+ sort_nearest(improved_mouses, max_jump);
+ }
+ radius = exp_rescale(sliders[FUSE_RADIUS].value());
+ fuse_close_points(improved_mouses, radius);
+
+ radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value());
+ Path p = ordered_fit(improved_mouses, radius/5);
+ stroke = p.toPwSb();
+ }
+ void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_sort_ctrl_geom(cr, notify, width, height);
+
+ if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
+ cairo_move_to(cr, improved_mouses[0]);
+ for(unsigned i = 1; i < improved_mouses.size(); i++) {
+ cairo_line_to(cr, improved_mouses[i]);
+ }
+ cairo_set_source_rgba (cr, 1., 0., 0., 1);
+ cairo_set_line_width (cr, .75);
+ cairo_stroke(cr);
+ }
+ }
+
+//-----------------------------------------------------------------------------------------
+// Average curvature.
+//-----------------------------------------------------------------------------------------
+ void init_curvature()
+ {
+ init_common();
+ handles.push_back(&(sliders[CURVATURE_NBHD_SIZE]));
+ handles.push_back(&(sliders[POINT_CHOOSER]));
+ }
+ void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180);
+ sliders[POINT_CHOOSER ].geometry(Point(50, height - 90), 180);
+ }
+ }
+ //just for fun!
+ void fit_curvature(){
+ std::vector<double> curvatures(mouses.size(),0);
+ std::vector<double> lengths(mouses.size(),0);
+ for (unsigned i=0; i<mouses.size(); i++){
+ double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
+ std::vector<Point> ngbrs = neighbors(mouses,i,radius);
+ if ( ngbrs.size()>2 ){
+ Circle c;
+ c.fit(ngbrs);
+ curvatures[i] = 1./c.radius();
+ Point v = (i<mouses.size()-1) ? mouses[i+1]-mouses[i] : mouses[i]-mouses[i-1];
+ if (cross(v, c.center()-mouses[i]) > 0 )
+ curvatures[i] *= -1;
+ }else{
+ curvatures[i] = 0;
+ }
+ if (i>0){
+ lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]);
+ }
+ }
+ Piecewise<SBasis> k = interpolate( lengths, curvatures , 1);
+ Piecewise<SBasis> alpha = integral(k);
+ Piecewise<D2<SBasis> > v = sectionize(tan2(alpha));
+ stroke = integral(v) + mouses[0];
+
+ Point sp = stroke.lastValue()-stroke.firstValue();
+ Point mp = mouses.back()-mouses.front();
+ Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]);
+ Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]);
+ mat1 = mat1.inverse()*mat2;
+ stroke = stroke*mat1;
+
+ }
+ void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_curvature_ctrl_geom(cr, notify, width, height);
+ if(!mouses.empty()) {
+ double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
+ unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. );
+ std::vector<Point> ngbrs = neighbors(mouses,i,radius);
+ if ( ngbrs.size()>2 ){
+ draw_cross(cr, mouses[i]);
+ Circle c;
+ c.fit(ngbrs);
+ cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI);
+ cairo_set_source_rgba (cr, 1., 0., 0., 1);
+ cairo_set_line_width (cr, .75);
+ cairo_stroke(cr);
+ }
+ cairo_pw_d2_sb(cr, stroke);
+ }
+ }
+
+//-----------------------------------------------------------------------------------------
+// Brutal optimization, number of segment fixed.
+//-----------------------------------------------------------------------------------------
+ void init_numerical()
+ {
+ init_common();
+ //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves"));
+ //handles.push_back(&(sliders[0]));
+ }
+ void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/)
+ {
+ if ( set_control_geometry ){
+ set_control_geometry = false;
+ //sliders[0].geometry(Point(50, height - 35*(0+2)), 180);
+ }
+ }
+ void fit_numerical(){
+ //Not implemented
+ }
+ void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ draw_common(cr, notify, width, height, save, timer_stream);
+ init_numerical_ctrl_geom(cr, notify, width, height);
+ if(!mouses.empty()) {
+ cairo_pw_d2_sb(cr, stroke);
+ cairo_set_source_rgba (cr, 1., 0., 0., 1);
+ cairo_set_line_width (cr, .75);
+ cairo_stroke(cr);
+ }
+ }
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+
+ void init_help()
+ {
+ handles.clear();
+ //sliders.clear();
+ //toggles.clear();
+ }
+ void draw_help( cairo_t * /*cr*/, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
+ {
+ *notify << "Tighten:\n";
+ *notify << " move points toward local\n";
+ *notify << " mean square line (or circle).\n";
+ *notify << "Eat:\n";
+ *notify << " eat points like a pacman; at each step, move to the\n";
+ *notify << " average of the not already visited neighbor points.\n";
+ *notify << "Sort:\n";
+ *notify << " move from one point to the nearest one.\n";
+ *notify << " Stop at the first jump longer than sort-radius\n";
+ *notify << "Sort-bis:\n";
+ *notify << " move from one point to the nearest one,\n";
+ *notify << " unless it was 'already visited' (i.e. it is closer to\n";
+ *notify << " an already sorted point with distance < sort-radius.\n";
+ *notify << "Fuse: \n";
+ *notify << " start from first point, remove all points closer to it\n";
+ *notify << " than fuse-radius, move to the first one that is not, and repeat.\n";
+ *notify << "Curvature: \n";
+ *notify << " Compute the curvature at a given point from the circle fitting the\n";
+ *notify << " nearby points (just for fun: the stroke is the 'integral' of this\n";
+ *notify << " average curvature)\n";
+ *notify << "Numerical: \n";
+ *notify << " still waiting for someone to implement me ;-)\n\n";
+ *notify << std::endl;
+ }
+
+//-----------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------
+
+public:
+ vector<Point> mouses;
+ int mouse_drag;
+ vector<Point> improved_mouses;
+ Piecewise<D2<SBasis > > stroke;
+
+ void mouse_pressed(GdkEventButton* e) override {
+ //toggle_events(toggles, e);
+ Toy::mouse_pressed(e);
+ if(!selected) {
+ mouse_drag = 1;
+ if (!(e->state & (GDK_SHIFT_MASK))){
+ mouses.clear();
+ }
+ }
+ }
+
+ void mouse_moved(GdkEventMotion* e) override {
+ if(mouse_drag) {
+ mouses.emplace_back(e->x, e->y);
+ redraw();
+ } else {
+ Toy::mouse_moved(e);
+ }
+ }
+
+ void mouse_released(GdkEventButton* e) override {
+ mouse_drag = 0;
+ if(!mouses.empty()) {
+ (this->*fit_f)();
+ }
+ Toy::mouse_released(e);
+ }
+
+ void init_menu()
+ {
+ handles.clear();
+ //sliders.clear();
+ //toggles.clear();
+ }
+ void draw_menu( cairo_t * cr, std::ostringstream *notify,
+ int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
+ {
+ *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n";
+ *notify << "Each menu below will transform your input.\n";
+ *notify << "Press 'Z' to make the result the new input\n";
+ *notify << " \n \n \n";
+ *notify << std::endl;
+ for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
+ {
+ *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
+ }
+ if(!mouses.empty()) {
+ cairo_move_to(cr, mouses[0]);
+ for(auto & mouse : mouses) {
+ cairo_line_to(cr, mouse);
+ }
+ for(auto & mouse : mouses) {
+ draw_cross(cr, mouse);
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0., .25);
+ cairo_set_line_width (cr, 0.5);
+ cairo_stroke(cr);
+ }
+ }
+
+ void key_hit(GdkEventKey *e) override
+ {
+ char choice = std::toupper(e->keyval);
+ switch ( choice )
+ {
+ case 'A':
+ init_menu();
+ draw_f = &SketchFitterToy::draw_menu;
+ break;
+ case 'B':
+ init_tighten();
+ fit_f = &SketchFitterToy::fit_tighten;
+ draw_f = &SketchFitterToy::draw_tighten;
+ break;
+ case 'C':
+ init_eat();
+ fit_f = &SketchFitterToy::fit_eat;
+ draw_f = &SketchFitterToy::draw_eat;
+ break;
+ case 'D':
+ init_tighten_eat();
+ fit_f = &SketchFitterToy::fit_tighten_eat;
+ draw_f = &SketchFitterToy::draw_tighten_eat;
+ break;
+ case 'E':
+ init_sort();
+ fit_f = &SketchFitterToy::fit_sort;
+ draw_f = &SketchFitterToy::draw_sort;
+ break;
+ case 'F':
+ init_curvature();
+ fit_f = &SketchFitterToy::fit_curvature;
+ draw_f = &SketchFitterToy::draw_curvature;
+ break;
+ case 'G':
+ init_numerical();
+ fit_f = &SketchFitterToy::fit_numerical;
+ draw_f = &SketchFitterToy::draw_numerical;
+ break;
+ case 'H':
+ init_help();
+ draw_f = &SketchFitterToy::draw_help;
+ break;
+ case 'Z':
+ mouses = improved_mouses;
+ break;
+ }
+ redraw();
+ }
+
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream ) override
+ {
+ m_width = width;
+ m_height = height;
+ m_length = (m_width > m_height) ? m_width : m_height;
+ m_length *= 2;
+ (this->*draw_f)(cr, notify, width, height, save, timer_stream);
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+
+ public:
+ SketchFitterToy()
+ {
+ srand ( time(NULL) );
+ sliders = std::vector<Slider>(TOTAL_SLIDERS, Slider(0., 1., 0, 0., ""));
+
+ sliders[TIGHTEN_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "neighborhood size");
+ sliders[TIGHTEN_NBHD_SIZE ].formatter(&exp_formatter);
+ sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations");
+ sliders[EAT_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "eating neighborhood size");
+ sliders[EAT_NBHD_SIZE ].formatter(&exp_formatter);
+ sliders[SORT_RADIUS ] = Slider(0., 1., 0, 0.65, "sort radius");
+ sliders[SORT_RADIUS ].formatter(&exp_formatter);
+ sliders[FUSE_RADIUS ] = Slider(0., 1., 0, 0.65, "fuse radius");
+ sliders[FUSE_RADIUS ].formatter(&exp_formatter);
+ sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision");
+ sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter);
+ sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size");
+ sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter);
+ sliders[POINT_CHOOSER ] = Slider(0, 100, 0, 50, "Point chooser(%)");
+
+ toggles = std::vector<Toggle>(TOTAL_TOGGLES, Toggle("",true));
+ toggles[DRAW_MOUSES] = Toggle("Draw mouses",true);
+ toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true);
+ toggles[DRAW_STROKE] = Toggle("Draw stroke",true);
+ toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false);
+ toggles[SORT_BIS ] = Toggle("Sort: bis",false);
+ }
+
+ private:
+ typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
+ draw_func_t draw_f;
+ typedef void (SketchFitterToy::* fit_func_t) ();
+ fit_func_t fit_f;
+ bool set_common_control_geometry;
+ bool set_control_geometry;
+ std::vector<Toggle> toggles;
+ std::vector<Slider> sliders;
+ double m_width, m_height, m_length;
+
+}; // end class SketchFitterToy
+
+
+const char* SketchFitterToy::menu_items[] =
+{
+ "show this menu",
+ "tighten",
+ "eat points step by step",
+ "tighten + eat",
+ "tighten + sort + fuse",
+ "curvature",
+ "numerical",
+ "help",
+};
+
+const char SketchFitterToy::keys[] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SketchFitterToy);
+ 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/smash-intersector.cpp b/src/toys/smash-intersector.cpp
new file mode 100644
index 0000000..b01acfb
--- /dev/null
+++ b/src/toys/smash-intersector.cpp
@@ -0,0 +1,583 @@
+/*
+ * Diffeomorphism-based intersector: given two curves
+ * M(t)=(x(t),y(t)) and N(u)=(X(u),Y(u))
+ * and supposing M is a graph over the x-axis, we compute y(x) and solve
+ * Y(u) - y(X(u)) = 0
+ * to get the intersections of the two curves...
+ *
+ * Notice the result can be far from intuitive because of the choice we have
+ * to make to consider a curve as a graph over x or y. For instance the two
+ * branches of xy=eps are never close from this point of view (!)...
+ *
+ * Authors:
+ * J.-F. Barraud <jfbarraud at 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 <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/path.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <cstdio>
+#include <set>
+#include <vector>
+#include <algorithm>
+
+#include <2geom/orphan-code/intersection-by-smashing.h>
+#include "../2geom/orphan-code/intersection-by-smashing.cpp"
+
+using namespace Geom;
+
+#define VERBOSE 0
+
+static double exp_rescale(double x){ return ::pow(10, x);}
+std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));}
+
+
+
+#if 0
+//useless here;
+Piecewise<D2<SBasis> > linearizeCusps( D2<SBasis> f, double tol){
+ D2<SBasis> df = derivative( f );
+ std::vector<Interval> xdoms = level_set( df[X], 0., tol);
+ std::vector<Interval> ydoms = level_set( df[Y], 0., tol);
+ std::vector<Interval> doms;
+ //TODO: use order!!
+ for ( unsigned i=0; i<xdoms.size(); i++ ){
+ OptInterval inter = xdoms[i];
+ for ( unsigned j=0; j<ydoms.size(); j++ ){
+ inter &= ydoms[j];
+ }
+ if (inter) {
+ doms.push_back( *inter );
+ }
+ }
+ Piecewise<D2<SBasis> > result;
+ if (doms.size() == 0 ) return Piecewise<D2<SBasis> >(f);
+ if (doms[0].min() > 0 ){
+ result.cuts.push_back( 0 );
+ result.cuts.push_back( doms[0].min() );
+ result.segs.push_back( portion( f, Interval( 0, doms[0].min() ) ) );
+ }
+ for ( unsigned i=0; i<doms.size(); i++ ){
+ Point a = result.segs.back().at1();
+ Point b = f.valueAt( doms[i].middle() );
+ Point c = f.valueAt( doms[i].max() );
+ result.cuts.push_back( doms[i].middle() );
+ result.segs.push_back( D2<SBasis>( Linear( a[X], b[X] ), Linear( a[Y], b[Y] ) ) );
+ result.cuts.push_back( doms[i].max() );
+ result.segs.push_back( D2<SBasis>( Linear( b[X], c[X] ), Linear( b[Y], c[Y] ) ) );
+ double t = ( i+1 == doms.size() )? 1 : doms[i+1].min();
+ result.cuts.push_back( t );
+ result.segs.push_back( portion( f, Interval( doms[i].max(), t ) ) );
+ }
+ return result;
+}
+#endif
+
+#if 0
+/* Computes the intersection of two sets given as (ordered) union intervals.
+ */
+std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){
+ std::vector<Interval> result;
+ //TODO: use order!
+ for (unsigned i=0; i < a.size(); i++){
+ for (unsigned j=0; j < b.size(); j++){
+ OptInterval c( a[i] );
+ c &= b[j];
+ if ( c ) {
+ result.push_back( *c );
+ }
+ }
+ }
+ return result;
+}
+
+/* Computes the top and bottom boundaries of the L_\infty neighborhood
+ * of a curve. The curve is supposed to be a graph over the x-axis.
+ */
+void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){
+ double signx = ( f[X].at0() > f[X].at1() )? -1 : 1;
+ double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1;
+
+ Piecewise<D2<SBasis> > top, bot;
+ top = Piecewise<D2<SBasis> > (f);
+ top.cuts.insert( top.cuts.end(), 2);
+ top.segs.insert( top.segs.end(), D2<SBasis>(Linear( f[X].at1(), f[X].at1()+2*tol*signx),
+ Linear( f[Y].at1() )) );
+ bot = Piecewise<D2<SBasis> >(f);
+ bot.cuts.insert( bot.cuts.begin(), - 1 );
+ bot.segs.insert( bot.segs.begin(), D2<SBasis>(Linear( f[X].at0()-2*tol*signx, f[X].at0()),
+ Linear( f[Y].at0() )) );
+ top += Point(-tol*signx, tol);
+ bot += Point( tol*signx, -tol);
+
+ if ( signy < 0 ){
+ swap( top, bot );
+ top += Point( 0, 2*tol);
+ bot += Point( 0, -2*tol);
+ }
+ topside = make_cuts_independent(top);
+ botside = make_cuts_independent(bot);
+}
+
+
+/*Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f.
+ * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol].
+ * if not, it is [f(t+tol)-tol, f(t-tol)+tol].
+ */
+void computeLinfinityNeighborhood( Piecewise<SBasis> const &f, double tol, Piecewise<SBasis> &top, Piecewise<SBasis> &bot){
+ top = f + tol;
+ top.offsetDomain( - tol );
+ top.cuts.insert( top.cuts.end(), f.domain().max() + tol);
+ top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) );
+
+ bot = f - tol;
+ bot.offsetDomain( tol );
+ bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol);
+ bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) );
+
+ if ( f.firstValue() > f.lastValue() ){
+ swap( top, bot );
+ top += 2*tol;
+ bot -= 2*tol;
+ }
+}
+
+std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){
+ std::vector<Interval> x_in_reg = level_set( f[X], region[X] );
+ std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] );
+ std::vector<Interval> result = intersect ( x_in_reg, y_in_reg );
+ return result;
+}
+
+void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){
+ if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb?
+ f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width );
+ f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) );
+ f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width );
+ f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) );
+}
+
+
+
+/* Returns the intervals over which the curve keeps its slope
+ * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x.
+ * WARNING: both curves are supposed to be a graphs over x or y axis,
+ * and the smaller the slopes the better (typically <=45°).
+ */
+std::vector<std::pair<Interval, Interval> > smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b,
+ double tol, cairo_t *cr , bool draw_more_stuff=false ){
+
+ std::vector<std::pair<Interval, Interval> > res;
+
+ // a and b or X and Y may have to be exchanged, so make local copies.
+ D2<SBasis> aa = a;
+ D2<SBasis> bb = b;
+ bool swapresult = false;
+ bool swapcoord = false;//debug only!
+
+ if ( draw_more_stuff ){
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba(cr, .5, .9, .7, 1 );
+ cairo_d2_sb(cr, aa);
+ cairo_d2_sb(cr, bb);
+ cairo_stroke(cr);
+ }
+
+#if 1
+ //if the (enlarged) bounding boxes don't intersect, stop.
+ if ( !draw_more_stuff ){
+ OptRect abounds = bounds_fast( a );
+ OptRect bbounds = bounds_fast( b );
+ if ( !abounds || !bbounds ) return res;
+ abounds->expandBy(tol);
+ if ( !(abounds->intersects(*bbounds))){
+ return res;
+ }
+ }
+#endif
+
+ //Choose the best curve to be re-parametrized by x or y values.
+ OptRect dabounds = bounds_exact(derivative(a));
+ OptRect dbbounds = bounds_exact(derivative(b));
+ if ( dbbounds->min().length() > dabounds->min().length() ){
+ aa=b;
+ bb=a;
+ swap( dabounds, dbbounds );
+ swapresult = true;
+ }
+
+ //Choose the best coordinate to use as new parameter
+ double dxmin = std::min( abs((*dabounds)[X].max()), abs((*dabounds)[X].min()) );
+ double dymin = std::min( abs((*dabounds)[Y].max()), abs((*dabounds)[Y].min()) );
+ if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0;
+ if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0;
+ assert (dxmin>=0 && dymin>=0);
+
+ if (dxmin < dymin) {
+ aa = D2<SBasis>( aa[Y], aa[X] );
+ bb = D2<SBasis>( bb[Y], bb[X] );
+ swapcoord = true;
+ }
+
+ //re-parametrize aa by the value of x.
+ Interval x_range_strict( aa[X].at0(), aa[X].at1() );
+ Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5);
+
+ //Compute top and bottom boundaries of the L^infty nbhd of aa.
+ Piecewise<SBasis> top_ay, bot_ay;
+ computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay);
+
+ Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol.
+
+ if ( draw_more_stuff ){
+ Piecewise<SBasis> dbg_x( SBasis( Linear( top_ay.domain().min(), top_ay.domain().max() ) ) );
+ dbg_x.setDomain( top_ay.domain() );
+ D2<Piecewise<SBasis> > dbg_side ( Piecewise<SBasis>( SBasis( Linear( 0 ) ) ),
+ Piecewise<SBasis>( SBasis( Linear( 0, 2*tol) ) ) );
+
+ D2<Piecewise<SBasis> > dbg_rgn;
+ unsigned h = ( swapcoord ) ? Y : X;
+ dbg_rgn[h].concat ( dbg_x );
+ dbg_rgn[h].concat ( dbg_side[X] + dbg_x.lastValue() );
+ dbg_rgn[h].concat ( reverse(dbg_x) );
+ dbg_rgn[h].concat ( dbg_side[X] + dbg_x.firstValue() );
+
+ dbg_rgn[1-h].concat ( bot_ay );
+ dbg_rgn[1-h].concat ( dbg_side[Y] + bot_ay.lastValue() );
+ dbg_rgn[1-h].concat ( reverse(top_ay) );
+ dbg_rgn[1-h].concat ( reverse( dbg_side[Y] ) + bot_ay.firstValue() );
+
+ cairo_set_line_width (cr, 1.);
+ cairo_set_source_rgba(cr, 0., 1., 0., .75 );
+ cairo_d2_pw_sb(cr, dbg_rgn );
+ cairo_stroke(cr);
+
+ D2<SBasis> bbb = bb;
+ if ( swapcoord ) swap( bbb[X], bbb[Y] );
+ //Piecewise<D2<SBasis> > dbg_rgnB = neighborhood( bbb, tol );
+ D2<Piecewise<SBasis> > dbg_topB, dbg_botB;
+ computeLinfinityNeighborhood( bbb, tol, dbg_topB, dbg_botB );
+ cairo_set_line_width (cr, 1.);
+ cairo_set_source_rgba(cr, .2, 8., .2, .4 );
+// cairo_pw_d2_sb(cr, dbg_rgnB );
+ cairo_d2_pw_sb(cr, dbg_topB );
+ cairo_d2_pw_sb(cr, dbg_botB );
+ cairo_stroke(cr);
+ }
+
+ std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range );
+
+ // find times when bb is in the neighborhood of aa.
+ std::vector<Interval> tbs;
+ for (unsigned i=0; i<bx_in_ax_range.size(); i++){
+ D2<Piecewise<SBasis> > bb_in;
+ bb_in[X] = Piecewise<SBasis> ( portion( bb[X], bx_in_ax_range[i] ) );
+ bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], bx_in_ax_range[i]) );
+ bb_in[X].setDomain( bx_in_ax_range[i] );
+ bb_in[Y].setDomain( bx_in_ax_range[i] );
+
+ Piecewise<SBasis> h;
+ Interval level;
+ h = bb_in[Y] - compose( top_ay, bb_in[X] );
+ level = Interval( -infinity(), 0 );
+ std::vector<Interval> rts_lo = level_set( h, level);
+ h = bb_in[Y] - compose( bot_ay, bb_in[X] );
+ level = Interval( 0, infinity());
+ std::vector<Interval> rts_hi = level_set( h, level);
+
+ std::vector<Interval> rts = intersect( rts_lo, rts_hi );
+ tbs.insert(tbs.end(), rts.begin(), rts.end() );
+ }
+
+ std::vector<std::pair<Interval, Interval> > result(tbs.size(),std::pair<Interval,Interval>());
+
+ /* for each solution I, find times when aa is in the neighborhood of bb(I).
+ * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this:
+ * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1).
+ * + faster computation.
+ * - implies little jumps depending on the subdivision of the input curve into monotonic pieces
+ * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-(
+ */
+ for (unsigned j=0; j<tbs.size(); j++){
+ result[j].second = tbs[j];
+ std::vector<Interval> tas;
+ Piecewise<SBasis> fat_y_of_x = y_of_x;
+ prolongateByConstants( fat_y_of_x, 100*(1+tol) );
+
+ D2<Piecewise<SBasis> > top_b, bot_b;
+ D2<SBasis> bbj = portion( bb, tbs[j] );
+ computeLinfinityNeighborhood( bbj, tol, top_b, bot_b );
+
+ Piecewise<SBasis> h;
+ Interval level;
+ h = top_b[Y] - compose( fat_y_of_x, top_b[X] );
+ level = Interval( +infinity(), 0 );
+ std::vector<Interval> rts_top = level_set( h, level);
+ for (unsigned idx=0; idx < rts_top.size(); idx++){
+ rts_top[idx] = Interval( top_b[X].valueAt( rts_top[idx].min() ),
+ top_b[X].valueAt( rts_top[idx].max() ) );
+ }
+ assert( rts_top.size() == 1 );
+
+ h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] );
+ level = Interval( 0, -infinity());
+ std::vector<Interval> rts_bot = level_set( h, level);
+ for (unsigned idx=0; idx < rts_bot.size(); idx++){
+ rts_bot[idx] = Interval( bot_b[X].valueAt( rts_bot[idx].min() ),
+ bot_b[X].valueAt( rts_bot[idx].max() ) );
+ }
+ assert( rts_bot.size() == 1 );
+
+#if VERBOSE
+ printf("range(aa[X]) = [%f, %f];\n", y_of_x.domain().min(), y_of_x.domain().max());
+ printf("range(bbj[X]) = [%f, %f]; tol= %f\n", bbj[X].at0(), bbj[X].at1(), tol);
+
+ printf("rts_top = ");
+ for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){
+ printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() );
+ }
+ printf("\n");
+ printf("rts_bot = ");
+ for (unsigned dbgi=0; dbgi<rts_bot.size(); dbgi++){
+ printf("[%f,%f]U", rts_bot[dbgi].min(), rts_bot[dbgi].max() );
+ }
+ printf("\n");
+#endif
+ rts_top = intersect( rts_top, rts_bot );
+#if VERBOSE
+ printf("intersection = ");
+ for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){
+ printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() );
+ }
+ printf("\n\n");
+
+ if (rts_top.size() != 1){
+ printf("!!!!!!!!!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!!!!!!!!!\n");
+ rts_top[0].unionWith( rts_top[1] );
+ assert( false );
+ }
+#endif
+ assert (rts_top.size() == 1);
+ Interval x_dom = rts_top[0];
+
+ if ( x_dom.max() <= x_range_strict.min() ){
+ tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) );
+ }else if ( x_dom.min() >= x_range_strict.max() ){
+ tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) );
+ }else{
+ tas = level_set(aa[X], x_dom );
+ }
+
+#if VERBOSE
+ if ( tas.size() != 1 ){
+ printf("Error: preimage of [%f, %f] by x:[0,1]->[%f, %f] is ",
+ x_dom.min(), x_dom.max(), x_range_strict.min(), x_range_strict.max());
+ if ( tas.size() == 0 ){
+ printf( "empty.\n");
+ }else{
+ printf("\n [%f,%f]", tas[0].min(), tas[0].max() );
+ for (unsigned toto=1; toto<tas.size(); toto++){
+ printf(" U [%f,%f]", tas[toto].min(), tas[toto].max() );
+ }
+ }
+ }
+#endif
+ assert( tas.size()==1 );
+ result[j].first = tas.front();
+ }
+
+ if (swapresult) {
+ for ( unsigned i=0; i<result.size(); i++){
+ Interval temp = result[i].first;
+ result[i].first = result[i].second;
+ result[i].second = temp;
+ }
+ }
+ return result;
+}
+
+#endif
+
+class Intersector : public Toy
+{
+ private:
+ void draw( cairo_t *cr, std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override
+ {
+ double tol = exp_rescale(slider.value());
+ D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), A_bez_ord-1);
+ D2<SBasis> B = handles_to_sbasis(psh.pts.begin()+A_bez_ord, B_bez_ord-1);
+ cairo_set_line_width (cr, .8);
+ cairo_set_source_rgba(cr,0.,0.,0.,.6);
+ cairo_d2_sb(cr, A);
+ cairo_d2_sb(cr, B);
+ cairo_stroke(cr);
+
+ Rect tolbytol( anchor.pos, anchor.pos );
+ tolbytol.expandBy( tol );
+ cairo_rectangle(cr, tolbytol);
+ cairo_stroke(cr);
+/*
+ Piecewise<D2<SBasis> > smthA = linearizeCusps(A+Point(0,10), tol);
+ cairo_set_line_width (cr, 1.);
+ cairo_set_source_rgba(cr, 1., 0., 1., 1. );
+ cairo_pw_d2_sb(cr, smthA);
+ cairo_stroke(cr);
+*/
+
+ std::vector<Interval> Acuts = monotonicSplit(A);
+ std::vector<Interval> Bcuts = monotonicSplit(B);
+
+#if 0
+ for (unsigned i=0; i<Acuts.size(); i++){
+ D2<SBasis> Ai = portion( A, Acuts[i]);
+ cairo_set_line_width (cr, .2);
+ cairo_set_source_rgba(cr, 0., 0., 0., 1. );
+ draw_cross(cr, Ai.at0());
+ cairo_stroke(cr);
+ for (unsigned j=0; j<Bcuts.size(); j++){
+ std::vector<std::pair<Interval, Interval> > my_intersections;
+ D2<SBasis> Bj = portion( B, Bcuts[j]);
+ cairo_set_line_width (cr, .2);
+ cairo_set_source_rgba(cr, 0., 0., 0., 1. );
+ draw_cross(cr, Bj.at0());
+ cairo_stroke(cr);
+ }
+ }
+#endif
+
+ std::vector<SmashIntersection> my_intersections;
+ my_intersections = smash_intersect( A, B, tol );
+
+ for (auto & my_intersection : my_intersections){
+ cairo_set_line_width (cr, 2.5);
+ cairo_set_source_rgba(cr, 1., 0., 0., .8 );
+ cairo_d2_sb(cr, portion( A, my_intersection.times[X]));
+ cairo_stroke(cr);
+ cairo_set_line_width (cr, 2.5);
+ cairo_set_source_rgba(cr, 0., 0., 1., .8 );
+ cairo_d2_sb(cr, portion( B, my_intersection.times[Y]));
+ cairo_stroke(cr);
+ }
+#if 0
+
+ unsigned apiece( slidera.value()/100. * Acuts.size() );
+ unsigned bpiece( sliderb.value()/100. * Bcuts.size() );
+
+
+ for (unsigned i=0; i<Acuts.size(); i++){
+ D2<SBasis> Ai = portion( A, Acuts[i]);
+ for (unsigned j=0; j<Bcuts.size(); j++){
+ if ( toggle.on && (i != apiece || j != bpiece) ) continue;
+
+ std::vector<SmashIntersection> my_intersections;
+ D2<SBasis> Bj = portion( B, Bcuts[j]);
+ bool draw_more = toggle.on && i == apiece && j == bpiece;
+// my_intersections = smash_intersect( Ai, Bj, tol, cr, draw_more );
+ my_intersections = monotonic_smash_intersect( Ai, Bj, tol );
+
+ for (unsigned k=0; k<my_intersections.size(); k++){
+ cairo_set_line_width (cr, 2.5);
+ cairo_set_source_rgba(cr, 1., 0., 0., .8 );
+ cairo_d2_sb(cr, portion( Ai, my_intersections[k].times[X]));
+ cairo_stroke(cr);
+ cairo_set_line_width (cr, 2.5);
+ cairo_set_source_rgba(cr, 0., 0., 1., .8 );
+ cairo_d2_sb(cr, portion( Bj, my_intersections[k].times[Y]));
+ cairo_stroke(cr);
+ }
+ }
+ }
+#endif
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ Intersector(unsigned int _A_bez_ord, unsigned int _B_bez_ord)
+ : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord)
+ {
+ unsigned int total_handles = A_bez_ord + B_bez_ord;
+ for ( unsigned int i = 0; i < total_handles; ++i )
+ psh.push_back(Geom::Point(uniform()*400, uniform()*400));
+ handles.push_back(&psh);
+ slider = Slider(-4, 2, 0, 1.2, "tolerance");
+ slider.geometry(Point(30, 20), 250);
+ slider.formatter(&exp_formatter);
+ handles.push_back(&slider);
+ slidera = Slider(0, 100, 1, 0., "piece on A");
+ slidera.geometry(Point(300, 50), 250);
+ handles.push_back(&slidera);
+ sliderb = Slider(0, 100, 1, 0., "piece on B");
+ sliderb.geometry(Point(300, 80), 250);
+ handles.push_back(&sliderb);
+ toggle = Toggle( Rect(Point(300,10), Point(440,30)), "Piece by piece", false );
+ handles.push_back(&toggle);
+ anchor = PointHandle ( Point(100, 100 ) );
+ handles.push_back(&anchor);
+ }
+
+ private:
+ unsigned int A_bez_ord;
+ unsigned int B_bez_ord;
+ PointSetHandle psh;
+ PointHandle anchor;
+ Slider slider,slidera,sliderb;
+ Toggle toggle;
+};
+
+
+int main(int argc, char **argv)
+{
+ unsigned int A_bez_ord=4;
+ unsigned int B_bez_ord=4;
+ if(argc > 2)
+ sscanf(argv[2], "%d", &B_bez_ord);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &A_bez_ord);
+
+ init( argc, argv, new Intersector(A_bez_ord, B_bez_ord));
+ 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/src/toys/squiggles.cpp b/src/toys/squiggles.cpp
new file mode 100644
index 0000000..eef33ad
--- /dev/null
+++ b/src/toys/squiggles.cpp
@@ -0,0 +1,216 @@
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <vector>
+
+#define NB_CTL_PTS 6
+#define K_SCALE .002
+
+using namespace Geom;
+using namespace std;
+
+void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ D2<SBasis> B;
+ B[0] = Linear(p.cuts[i], p.cuts[i+1]);
+ B[1] = Linear(150) + p[i];
+ cairo_d2_sb(cr, B);
+ }
+}
+
+void cairo_horiz(cairo_t *cr, double y, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, i, y);
+ cairo_rel_line_to(cr, 0, 10);
+ }
+}
+
+void cairo_vert(cairo_t *cr, double x, vector<double> p) {
+ for(double i : p) {
+ cairo_move_to(cr, x, i);
+ cairo_rel_line_to(cr, 10, 0);
+ }
+}
+
+/*
+Piecewise<SBasis> interpolate(std::vector<double> values, std::vector<double> times){
+ assert ( values.size() == times.size() );
+ if ( values.size() == 0 ) return Piecewise<SBasis>();
+ if ( values.size() == 1 ) return Piecewise<SBasis>(values[0]);//what about time??
+
+ SBasis bump_in = Linear(0,1);//Enough for piecewise linear interpolation.
+ //bump_in.push_back(Linear(-1,1));//uncomment for C^1 interpolation
+ SBasis bump_out = Linear(1,0);
+ //bump_out.push_back(Linear(1,-1));
+
+ Piecewise<SBasis> result;
+ result.cuts.push_back(times[0]);
+ for (unsigned i = 0; i<values.size()-1; i++){
+ result.push(bump_out*values[i]+bump_in*values[i+1],times[i+1]);
+ }
+ return result;
+}
+*/
+
+//#include <toys/pwsbhandle.cpp // FIXME: This looks like it may give problems later, (including a .cpp file)
+
+class Squiggles: public Toy {
+ PointSetHandle hand;
+ unsigned current_ctl_pt;
+ Point current_pos;
+ Point current_dir;
+ std::vector<double> curvatures;
+ std::vector<double> times;
+ Piecewise<D2<SBasis> > curve;
+ double tot_length;
+ int mode; //0=set curvature, 1=set curv.+rotation, 2=translate, 3=slide time.
+
+ void mouse_moved(GdkEventMotion* e) override{
+ mode = 0;
+ if((e->state & (GDK_SHIFT_MASK)) &&
+ (e->state & (GDK_CONTROL_MASK))) {
+ mode = 3;
+ }else if(e->state & (GDK_CONTROL_MASK)) {
+ mode = 1;
+ }else if(e->state & (GDK_SHIFT_MASK)) {
+ mode = 2;
+ }
+ Toy::mouse_moved(e);
+ }
+
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_set_line_width (cr, 1);
+
+ *notify << "Drag to set curvature,\n";
+ *notify << "SHIFT-Drag to move curve,\n";
+ *notify << "CTRL-Drag to rotate,\n";
+ *notify << "SHIFT-CTRL-Drag to slide handles.";
+ //Get user input
+ if (mouse_down && selected) {
+ for(unsigned i = 0; i < handles.size(); i++) {
+ if(selected == handles[i]){
+ current_ctl_pt = i;
+ break;
+ }
+ }
+ double time = times[current_ctl_pt];
+ current_pos = curve.valueAt(time);
+ current_dir = derivative(curve).valueAt(time);//*This should be a unit vector!*
+ Point hdle = dynamic_cast<PointHandle*>(handles[current_ctl_pt])->pos;
+ if (mode == 0){
+ curvatures[current_ctl_pt] = cross(hdle - current_pos,current_dir)*K_SCALE;
+ }else if (mode == 1){//Rotate
+ double sign = ( curvatures[current_ctl_pt]>=0 ? 1 : -1 );
+ //curvatures[current_ctl_pt] = sign*L2(hdle - current_pos)*K_SCALE;
+ current_dir = -sign*unit_vector(rot90(hdle - current_pos));
+ }else if (mode == 2){//Translate
+ Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir);
+ current_pos += hdle - old_pos;
+ curve += hdle - old_pos;
+ }else if (mode == 3){//Slide time
+ Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir);
+ double delta = dot(hdle - old_pos,current_dir);
+ double epsilon = 2;
+ if (current_ctl_pt>0 && times[current_ctl_pt]+delta < times[current_ctl_pt-1]+epsilon){
+ delta = times[current_ctl_pt-1] + epsilon - times[current_ctl_pt];
+ }
+ if (current_ctl_pt<times.size()-1 && times[current_ctl_pt]+delta > times[current_ctl_pt+1]-epsilon){
+ delta = times[current_ctl_pt+1] - epsilon - times[current_ctl_pt];
+ }
+ times[current_ctl_pt] += delta;
+ current_pos += delta*current_dir;
+ }
+ }
+
+ //Compute new curve
+
+ Piecewise<SBasis> curvature = interpolate( times, curvatures , 1);
+ Piecewise<SBasis> alpha = integral(curvature);
+ Piecewise<D2<SBasis> > v = sectionize(tan2(alpha));
+ curve = integral(v)+Point(100,100);
+
+ //transform to keep current point in place
+ double time = times[current_ctl_pt];
+ Point new_pos = curve.valueAt(time);
+ Point new_dir = v.valueAt(time);
+ Affine mat1 = Affine( new_dir[X], new_dir[Y], -new_dir[Y], new_dir[X], new_pos[X], new_pos[Y]);
+ Affine mat2 = Affine(current_dir[X],current_dir[Y],-current_dir[Y],current_dir[X],current_pos[X],current_pos[Y]);
+ mat1 = mat1.inverse()*mat2;
+ curve = curve*mat1;
+ v = v*mat1.withoutTranslation();
+
+ //update handles
+ cairo_save(cr);
+ double dashes[2] = {3, 2};
+ cairo_set_dash(cr, dashes, 2, 0);
+ cairo_set_line_width(cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0.5, 1);
+ for(unsigned i = 0; i < NB_CTL_PTS; i++) {
+ Point m = curve.valueAt(times[i]);
+ dynamic_cast<PointHandle*>(handles[i])->pos = m +
+ curvatures[i]/K_SCALE*rot90(v.valueAt(times[i]));
+ draw_handle(cr, m);
+ cairo_move_to(cr, m);
+ cairo_line_to(cr, dynamic_cast<PointHandle*>(handles[i])->pos);
+ }
+
+#if 0
+ D2<Piecewise<SBasis> > graphe;
+ graphe[X] = Piecewise<SBasis>(Linear(100,300));
+ graphe[Y] = -curvature/K_SCALE+400;
+ graphe[X].setDomain(graphe[Y].domain());
+ cairo_d2_pw_sb(cr, graphe);
+#endif
+
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ cairo_pw_d2_sb(cr, curve);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ bool should_draw_numbers() override { return false; }
+
+public:
+ Squiggles () {
+ current_ctl_pt = 0;
+ current_dir = Point(1,0);
+ current_pos = Point(100,100);
+ tot_length = 300;
+
+ curve = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(100.,300.),SBasis(100.,100.)));
+ for(unsigned i = 0; i < NB_CTL_PTS; i++) {
+ curvatures.push_back(0);
+ times.push_back(i*tot_length/(NB_CTL_PTS-1));
+ PointHandle *pt_hdle = new PointHandle(Geom::Point(100+i*tot_length/(NB_CTL_PTS-1), 100.));
+ handles.push_back(pt_hdle);
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Squiggles());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/star-gap.hand b/src/toys/star-gap.hand
new file mode 100644
index 0000000..6506970
--- /dev/null
+++ b/src/toys/star-gap.hand
@@ -0,0 +1 @@
+300.000000 300.000000
diff --git a/src/toys/svgd/2rect.svgd b/src/toys/svgd/2rect.svgd
new file mode 100644
index 0000000..2d8cdc2
--- /dev/null
+++ b/src/toys/svgd/2rect.svgd
@@ -0,0 +1 @@
+M 111.42857,92.362183 L 494.28572,92.362183 L 494.28572,275.21933 L 111.42857,275.21933 L 111.42857,92.362183 zM 217.14285,192.36218 L 660,192.36218 L 660,389.50504 L 217.14285,389.50504 L 217.14285,192.36218 z \ No newline at end of file
diff --git a/src/toys/svgd/4rect.svgd b/src/toys/svgd/4rect.svgd
new file mode 100644
index 0000000..af15296
--- /dev/null
+++ b/src/toys/svgd/4rect.svgd
@@ -0,0 +1 @@
+M 92.73692,69.488149 L 412.73692,69.488149 L 412.73692,266.63103 L 92.73692,266.63103 L 92.73692,69.488149 z M 430.12021,178.5218 L 564.40593,178.5218 L 564.40593,275.66466 L 430.12021,275.66466 L 430.12021,178.5218 z M 430.12021,286.66478 L 564.40593,286.66478 L 564.40593,383.80764 L 430.12021,383.80764 L 430.12021,286.66478 z M 430.12021,69.488149 L 564.40593,69.488149 L 564.40593,166.63101 L 430.12021,166.63101 L 430.12021,69.488149 z \ No newline at end of file
diff --git a/src/toys/svgd/ant.svgd b/src/toys/svgd/ant.svgd
new file mode 100644
index 0000000..a92d01a
--- /dev/null
+++ b/src/toys/svgd/ant.svgd
@@ -0,0 +1 @@
+M 60.03,2.56 C 58.71,2.43 57.16,15.65 57.09,16.44 C 56.11,27.39 60.84,32.53 60.56,34.03 C 60.29,35.53 60.27,41.43 64.63,47.53 C 63.29,48.91 62.08,50.27 61.0,51.63 C 58.67,47.53 55.81,43.15 53.78,41.13 C 51.65,38.99 42.4,33.78 40.5,33.09 C 39.83,32.85 31.78,24.38 30.88,25.28 C 29.97,26.19 37.86,34.75 38.66,34.63 C 41.17,38.09 51.17,45.0 51.91,46.06 C 52.64,47.13 57.55,56.07 57.59,56.13 C 55.72,59.04 55.17,61.1 54.47,63.59 C 51.13,60.05 41.55,57.28 34.66,57.28 C 28.94,57.28 19.55,57.14 17.81,57.44 C 11.32,58.55 0.64,71.64 2.13,73.13 C 3.3,74.3 11.14,60.86 17.63,59.75 C 19.5,59.43 34.08,62.18 36.25,62.03 C 38.42,61.88 45.45,65.59 49.38,66.16 C 45.65,65.36 41.81,66.1 38.66,69.25 C 26.96,80.95 28.35,91.77 32.88,96.41 C 32.88,96.41 32.87,96.43 32.88,96.44 C 32.89,96.45 32.9,96.46 32.91,96.47 C 32.92,96.48 32.93,96.49 32.94,96.5 C 32.95,96.51 32.96,96.52 32.97,96.53 C 37.61,101.05 48.41,102.4 60.09,90.72 C 63.03,87.79 63.88,84.23 63.34,80.75 C 64.22,84.76 67.48,91.09 67.34,93.13 C 67.2,95.3 69.91,109.84 69.59,111.72 C 68.49,118.21 55.04,126.07 56.22,127.25 C 57.7,128.73 70.8,118.05 71.91,111.56 C 72.2,109.82 72.06,100.44 72.06,94.72 C 72.06,87.82 69.3,78.25 65.75,74.91 C 68.24,74.21 70.33,73.62 73.25,71.75 C 73.3,71.79 82.21,76.73 83.28,77.47 C 84.35,78.21 91.25,88.21 94.72,90.72 C 94.59,91.52 103.16,99.41 104.06,98.5 C 104.97,97.6 96.49,89.51 96.25,88.84 C 95.57,86.94 90.39,77.73 88.25,75.59 C 86.22,73.57 81.81,70.68 77.72,68.34 C 79.07,67.27 80.46,66.09 81.84,64.75 C 87.95,69.1 93.85,69.09 95.34,68.81 C 96.84,68.54 101.98,73.27 112.94,72.28 C 113.72,72.21 126.95,70.66 126.81,69.34 C 126.65,67.79 114.41,69.33 113.59,69.72 C 111.42,68.99 99.94,66.69 98.78,66.19 C 97.62,65.68 91.92,62.98 84.78,61.19 C 86.71,57.89 86.76,54.39 85.59,51.34 C 88.51,54.25 91.2,55.8 94.41,52.59 C 97.92,49.08 100.48,45.17 101.94,41.44 C 106.66,42.93 110.64,43.45 111.56,43.88 C 112.49,44.3 113.68,43.63 114.75,40.0 C 116.5,34.03 117.56,28.4 116.88,28.09 C 116.19,27.79 110.98,37.62 111.34,41.22 C 108.58,39.29 106.28,38.51 102.97,37.91 C 103.76,34.04 103.18,30.66 101.03,28.44 C 101.03,28.43 101.04,28.41 101.03,28.41 C 101.01,28.39 100.99,28.36 100.97,28.34 C 98.75,26.18 95.31,25.58 91.44,26.38 C 90.83,23.07 90.08,20.76 88.16,18.0 C 91.75,18.37 101.59,13.18 101.28,12.5 C 100.97,11.82 95.35,12.84 89.38,14.59 C 85.74,15.66 85.07,16.89 85.5,17.81 C 85.93,18.74 86.45,22.72 87.94,27.44 C 84.21,28.89 80.29,31.46 76.78,34.97 C 73.58,38.17 75.12,40.87 78.03,43.78 C 74.98,42.62 71.48,42.67 68.19,44.59 C 66.4,37.45 63.66,31.75 63.16,30.59 C 62.65,29.43 60.39,17.92 59.66,15.75 C 60.05,14.94 61.59,2.72 60.03,2.56 z \ No newline at end of file
diff --git a/src/toys/svgd/arcs.svgd b/src/toys/svgd/arcs.svgd
new file mode 100644
index 0000000..8a69290
--- /dev/null
+++ b/src/toys/svgd/arcs.svgd
@@ -0,0 +1 @@
+M50,50 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25
diff --git a/src/toys/svgd/banana.svgd b/src/toys/svgd/banana.svgd
new file mode 100644
index 0000000..37a385e
--- /dev/null
+++ b/src/toys/svgd/banana.svgd
@@ -0,0 +1 @@
+M 265.71429,158.07648 C 190.6809,177.20365 122.07669,234.71591 104.63056,312.25563 C 77.156999,399.83651 70.794763,500.27985 114.77866,583.69036 C 144.53063,634.44937 199.36676,669.20393 257.74302,675.53709 C 270.81001,702.04856 314.06907,671.3079 286.33697,654.18295 C 255.1688,600.99542 216.46623,548.3017 211.13947,484.83476 C 202.31209,421.03768 205.02251,351.85571 239.06833,295.30186 C 257.00587,263.44474 279.73092,234.67917 301.98406,205.79097 C 300.02661,180.33683 358.06043,134.08979 326.58199,129.37189 C 312.92978,127.95692 277.30454,151.73686 265.71429,158.07648 z
diff --git a/src/toys/svgd/cat.svgd b/src/toys/svgd/cat.svgd
new file mode 100644
index 0000000..557f13d
--- /dev/null
+++ b/src/toys/svgd/cat.svgd
@@ -0,0 +1 @@
+M 153.04,308.88 C 67.07,342.82 57.97,453.92 130.83,506.41 C 245.3,553.77 426.19,542.77 445.18,392.24 C 446.78,351.09 426.76,300.40 382.24,291.24 M 338.24,282.24 C 285.72,273.19442 231.95477,285.06235 181.03,297.88016 M 308.58,391.49 C 327.09,380.94 348.61,376.39128 369.74,375.64253 M 118.31907,386.96814 C 134.17,392.253 150.03,397.53 165.88,402.82 M 206.65,418.65109 C 211.53912,410.76 255.11,411.00542 258.75668,418.65109 C 262.66,426.86 238.25,466.21 229.31,466.21866 C 220.88,466.21866 201.72358,426.62348 206.65,418.65109 z M 270.08,465.68 C 345.58,465.84 421.09,466.04 496.59,466.21 M 276.87,445.83 C 346.34,439.03 415.80,432.24 485.26,425.44 M 202.12,459.95 C 159.09,469.59 116.05,479.23 73.01,488.86 M 196.66,443.29 C 148.65,444.89 100.64,446.49 52.63,448.09769 M 205.04,480.06 C 221.65,480.065 238.26,480.06 254.87,480.06 M 95.66,350.69 C 91.13,315.21 86.60,279.72 82.07,244.23 C 133.11,252.96 156.26,279.88 190.80,312.19 M 338.03,289.53 C 360.08,241.80 427.37,231.27 452.87,221.21 C 450.44,257.57 440.48,289.67 428.64,323.51 M 196.76,290.19 C 201.26,252.13 242.44,234.63 272.70,219.28 C 417.48,158.60 583.94,152.87 733.23,201.03 C 853.85,234.84 966.06,304.90 931.20,445.90 C 850.68,533.28 734.22,618.52 613.28,632.83 C 572.66,633.17 536.29,602.83 578.98,592.96 C 664.47,600.17 743.16,553.80 807.85,502.83 C 831.31,480.70 855.70,455.39 863.54,423.18 M 742.13,302.52 C 696.70,300.78 645.69,308.71 616,347.22 C 578.46,386.94 583.35,447.38 587.45,497.88 C 584.24,518.69 614.07,549.65 579.61,545.01 C 533.59,551.10 487.13,554.76 441.40,562.48 C 397.18,588.94 462.84,613.68 489.95,609.31 C 514.10,610.80 538.52,610.13 562.38,605.92 M 211.18,531.90 C 167.07,548.50 234.68,593.43 260.49,576.39 C 326.21,562.34 388.19,535.46 447.53,504.56 C 489.62,482.86 548.71,461.63 557.51,412.45 C 563.23,380.53 543.26,346.99 536.94,314.20 C 532.43,276.36 513.09,264.81 475.14,265.25 M 450.71,504.40 C 494.88,523 543.39,525.09 590.45,519.27 M 100.78,485.00 C 87.77,505.40 71.18,555.90 114.73,539.59 C 131.70,538.38 137.48,525.86 139.17,510.47
diff --git a/src/toys/svgd/circle.svgd b/src/toys/svgd/circle.svgd
new file mode 100644
index 0000000..fb276fa
--- /dev/null
+++ b/src/toys/svgd/circle.svgd
@@ -0,0 +1 @@
+M 0 0 A 100 100 0 0 0 200 0 A 100 100 0 0 0 0 0 z \ No newline at end of file
diff --git a/src/toys/svgd/degenerate-line.svgd b/src/toys/svgd/degenerate-line.svgd
new file mode 100644
index 0000000..832bb17
--- /dev/null
+++ b/src/toys/svgd/degenerate-line.svgd
@@ -0,0 +1 @@
+m 23,42 0,0
diff --git a/src/toys/svgd/diederik.svgd b/src/toys/svgd/diederik.svgd
new file mode 100644
index 0000000..7e422ab
--- /dev/null
+++ b/src/toys/svgd/diederik.svgd
@@ -0,0 +1 @@
+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 \ No newline at end of file
diff --git a/src/toys/svgd/diederik1.svgd b/src/toys/svgd/diederik1.svgd
new file mode 100644
index 0000000..be181f5
--- /dev/null
+++ b/src/toys/svgd/diederik1.svgd
@@ -0,0 +1 @@
+m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981 \ No newline at end of file
diff --git a/src/toys/svgd/double-move.svgd b/src/toys/svgd/double-move.svgd
new file mode 100644
index 0000000..d2be8c3
--- /dev/null
+++ b/src/toys/svgd/double-move.svgd
@@ -0,0 +1 @@
+m 23,42 m 10,0
diff --git a/src/toys/svgd/ellipses.svgd b/src/toys/svgd/ellipses.svgd
new file mode 100644
index 0000000..c0f3a2c
--- /dev/null
+++ b/src/toys/svgd/ellipses.svgd
@@ -0,0 +1 @@
+M 310 640 A 290 320 0 0 1 310 0 A 290 320 0 0 1 310 640 z M 310 635 A 285 315 0 0 0 310 5 A 285 315 0 0 0 310 635 z M 310 630 A 280 310 0 0 1 310 10 A 280 310 0 0 1 310 630 z M 310 625 A 275 305 0 0 0 310 15 A 275 305 0 0 0 310 625 z M 310 620 A 270 300 0 0 1 310 20 A 270 300 0 0 1 310 620 z M 310 615 A 265 295 0 0 0 310 25 A 265 295 0 0 0 310 615 z M 310 610 A 260 290 0 0 1 310 30 A 260 290 0 0 1 310 610 z M 310 605 A 255 285 0 0 0 310 35 A 255 285 0 0 0 310 605 z M 310 600 A 250 280 0 0 1 310 40 A 250 280 0 0 1 310 600 z M 310 595 A 245 275 0 0 0 310 45 A 245 275 0 0 0 310 595 z M 310 590 A 240 270 0 0 1 310 50 A 240 270 0 0 1 310 590 z M 310 585 A 235 265 0 0 0 310 55 A 235 265 0 0 0 310 585 z M 310 580 A 230 260 0 0 1 310 60 A 230 260 0 0 1 310 580 z M 310 575 A 225 255 0 0 0 310 65 A 225 255 0 0 0 310 575 z M 310 570 A 220 250 0 0 1 310 70 A 220 250 0 0 1 310 570 z M 310 565 A 215 245 0 0 0 310 75 A 215 245 0 0 0 310 565 z M 310 560 A 210 240 0 0 1 310 80 A 210 240 0 0 1 310 560 z M 310 555 A 205 235 0 0 0 310 85 A 205 235 0 0 0 310 555 z M 310 550 A 200 230 0 0 1 310 90 A 200 230 0 0 1 310 550 z M 310 545 A 195 225 0 0 0 310 95 A 195 225 0 0 0 310 545 z M 310 540 A 190 220 0 0 1 310 100 A 190 220 0 0 1 310 540 z M 310 535 A 185 215 0 0 0 310 105 A 185 215 0 0 0 310 535 z M 310 530 A 180 210 0 0 1 310 110 A 180 210 0 0 1 310 530 z M 310 525 A 175 205 0 0 0 310 115 A 175 205 0 0 0 310 525 z M 310 520 A 170 200 0 0 1 310 120 A 170 200 0 0 1 310 520 z M 310 515 A 165 195 0 0 0 310 125 A 165 195 0 0 0 310 515 z M 310 510 A 160 190 0 0 1 310 130 A 160 190 0 0 1 310 510 z M 310 505 A 155 185 0 0 0 310 135 A 155 185 0 0 0 310 505 z M 310 500 A 150 180 0 0 1 310 140 A 150 180 0 0 1 310 500 z M 310 495 A 145 175 0 0 0 310 145 A 145 175 0 0 0 310 495 z M 310 490 A 140 170 0 0 1 310 150 A 140 170 0 0 1 310 490 z M 310 485 A 135 165 0 0 0 310 155 A 135 165 0 0 0 310 485 z M 310 480 A 130 160 0 0 1 310 160 A 130 160 0 0 1 310 480 z M 310 475 A 125 155 0 0 0 310 165 A 125 155 0 0 0 310 475 z M 310 470 A 120 150 0 0 1 310 170 A 120 150 0 0 1 310 470 z M 310 465 A 115 145 0 0 0 310 175 A 115 145 0 0 0 310 465 z M 310 460 A 110 140 0 0 1 310 180 A 110 140 0 0 1 310 460 z M 310 455 A 105 135 0 0 0 310 185 A 105 135 0 0 0 310 455 z M 310 450 A 100 130 0 0 1 310 190 A 100 130 0 0 1 310 450 z M 310 445 A 95 125 0 0 0 310 195 A 95 125 0 0 0 310 445 z M 310 440 A 90 120 0 0 1 310 200 A 90 120 0 0 1 310 440 z M 310 435 A 85 115 0 0 0 310 205 A 85 115 0 0 0 310 435 z M 310 430 A 80 110 0 0 1 310 210 A 80 110 0 0 1 310 430 z M 310 425 A 75 105 0 0 0 310 215 A 75 105 0 0 0 310 425 z M 310 420 A 70 100 0 0 1 310 220 A 70 100 0 0 1 310 420 z M 310 415 A 65 95 0 0 0 310 225 A 65 95 0 0 0 310 415 z M 310 410 A 60 90 0 0 1 310 230 A 60 90 0 0 1 310 410 z M 310 405 A 55 85 0 0 0 310 235 A 55 85 0 0 0 310 405 z M 310 400 A 50 80 0 0 1 310 240 A 50 80 0 0 1 310 400 z M 310 395 A 45 75 0 0 0 310 245 A 45 75 0 0 0 310 395 z M 310 390 A 40 70 0 0 1 310 250 A 40 70 0 0 1 310 390 z M 310 385 A 35 65 0 0 0 310 255 A 35 65 0 0 0 310 385 z M 310 380 A 30 60 0 0 1 310 260 A 30 60 0 0 1 310 380 z M 310 375 A 25 55 0 0 0 310 265 A 25 55 0 0 0 310 375 z M 310 370 A 20 50 0 0 1 310 270 A 20 50 0 0 1 310 370 z M 310 365 A 15 45 0 0 0 310 275 A 15 45 0 0 0 310 365 z M 310 360 A 10 40 0 0 1 310 280 A 10 40 0 0 1 310 360 z M 310 355 A 5 35 0 0 0 310 285 A 5 35 0 0 0 310 355 z \ No newline at end of file
diff --git a/src/toys/svgd/emptyset.svgd b/src/toys/svgd/emptyset.svgd
new file mode 100644
index 0000000..7e91eeb
--- /dev/null
+++ b/src/toys/svgd/emptyset.svgd
@@ -0,0 +1 @@
+M 551.42857,252.36218 52.857143,750.93361 M 440,495.21933 c 0,77.31986 -62.68014,140 -140,140 -77.31986,0 -140,-62.68014 -140,-140 0,-77.31987 62.68014,-140 140,-140 77.31986,0 140,62.68013 140,140 z \ No newline at end of file
diff --git a/src/toys/svgd/fan.svgd b/src/toys/svgd/fan.svgd
new file mode 100644
index 0000000..940fb71
--- /dev/null
+++ b/src/toys/svgd/fan.svgd
@@ -0,0 +1 @@
+ M 470 350 L 750 350 L 749.5904551097586 364.7203022982254 L 469.92772737231036 352.9440604596451 L 469.7110836003318 355.88102841977366 L 748.362807068547 379.4051420988682 L 746.3200133880255 394.01914233660864 L 469.3505905978869 358.80382846732175 L 468.84711682419385 361.70541932096774 L 743.4669953370983 408.5270966048386 L 739.810626086145 422.8940539709793 L 468.2018751916726 364.5788107941959 L 467.41642014393256 367.4170806352678 L 735.3597141489511 437.0854031763389 L 730.1249821622271 451.06695601766626 L 466.49264391098126 370.21339120353326 L 465.4327719506772 372.96100594190546 L 724.1190410538375 464.80502970952716 L 717.3563596619706 478.2665280290849 L 464.2393575874066 375.65330560581697 L 462.91527586090126 378.28380420955995 L 709.8532298784406 491.4190210477996 L 701.6277274000925 504.2308232579669 L 461.4637166000163 380.84616465159337 L 459.88817673815265 383.3342139811762 L 692.6996681828653 516.671069905881 L 683.090560703419 528.7097913477304 L 458.19245188883866 385.7419582695461 L 456.3806272017642 388.0635970498188 L 672.8235541433303 540.317985249094 L 661.9233826206857 551.4676864541059 L 454.4570675212975 390.2935372908212 L 452.4264068711928 392.4264068711929 L 650.4163056034258 562.1320343559646 L 638.3300446479859 572.2853376064882 L 450.29353729082106 394.45706752129763 L 448.06359704981867 396.3806272017643 L 625.693716615639 581.9031360088215 L 612.5377635274268 590.9622594441939 L 445.74195826954593 398.1924518888388 L 443.33421398117605 399.8881767381528 L 598.8938792266642 599.4408836907639 L 584.7949330256947 607.3185830000821 L 440.8461646515932 401.4637166000164 L 438.2838042095597 402.9152758609014 L 570.2748905208385 614.5763793045069 L 555.3687317662951 621.1967879370334 L 435.6533056058168 404.2393575874067 L 432.9610059419052 405.4327719506773 L 540.1123670041297 627.1638597533864 L 524.5425501533539 632.4632195549066 L 430.21339120353304 406.4926439109813 L 427.41708063526755 407.4164201439326 L 508.69679026651625 637.082100719663 L 492.6132611671087 641.0093759583635 L 424.57881079419565 408.2018751916727 L 421.7054193209675 408.8471168241939 L 476.33070948548254 644.2355841209694 L 459.8883613148219 646.7529529894346 L 418.8038284673215 409.35059059788694 L 415.88102841977343 409.71108360033185 L 443.3258277120495 648.5554180016593 L 426.68300927132094 649.638636861552 L 412.94406045964485 409.92772737231036 L 409.9999999999998 410 L 409.99999999999875 650.0000000000001 L 393.3169907286766 649.6386368615517 L 407.0559395403547 409.92772737231036 L 404.1189715802261 409.7110836003318 L 376.67417228794807 648.5554180016591 L 360.11163868517565 646.7529529894343 L 401.1961715326781 409.3505905978869 L 398.29458067903204 408.8471168241938 L 343.669290514515 644.235584120969 L 327.38673883288885 641.0093759583631 L 395.4211892058039 408.2018751916726 L 392.582919364732 407.4164201439325 L 311.30320973348137 637.0821007196624 L 295.4574498466437 632.4632195549059 L 389.78660879646657 406.4926439109812 L 387.0389940580944 405.43277195067714 L 279.887632995868 627.1638597533856 L 264.6312682337026 621.1967879370326 L 384.3466943941828 404.2393575874065 L 381.7161957904399 402.9152758609012 L 249.72510947915927 614.5763793045061 L 235.2050669743031 607.3185830000812 L 379.1538353484064 401.4637166000162 L 376.6657860188236 399.8881767381526 L 221.1061207733337 599.440883690763 L 207.4622364725711 590.9622594441928 L 374.2580417304537 398.19245188883855 L 371.936402950181 396.38062720176407 L 194.30628338435898 581.9031360088203 L 181.6699553520122 572.2853376064868 L 369.7064627091786 394.45706752129735 L 367.57359312880686 392.4264068711926 L 169.58369439657233 562.1320343559632 L 158.07661737931238 551.4676864541045 L 365.5429324787022 390.2935372908209 L 363.61937279823553 388.0635970498185 L 147.17644585666795 540.3179852490925 L 136.9094392965793 528.7097913477288 L 361.80754811116105 385.74195826954576 L 360.111823261847 383.3342139811759 L 127.30033181713321 516.6710699058793 L 118.37227259990618 504.2308232579651 L 358.53628339998346 380.846164651593 L 357.08472413909845 378.28380420955955 L 110.14677012155806 491.4190210477978 L 102.64364033802809 478.266528029083 L 355.7606424125932 375.6533056058166 L 354.5672280493226 372.96100594190506 L 95.8809589461614 464.8050297095253 L 89.87501783777185 451.0669560176643 L 353.5073560890186 370.21339120353286 L 352.5835798560673 367.4170806352674 L 84.64028585104796 437.0854031763369 L 80.18937391385413 422.8940539709773 L 351.7981248083272 364.5788107941955 L 351.15288317580604 361.70541932096734 L 76.53300466290085 408.5270966048366 L 73.67998661197373 394.01914233660654 L 350.649409402113 358.8038284673213 L 350.2889163996681 355.8810284197732 L 71.63719293145243 379.4051420988661 L 70.40954489024085 364.7203022982233 L 350.0722726276896 352.9440604596447 L 349.99999999999994 349.99999999999955 L 69.99999999999955 349.99999999999784 L 70.40954489024102 335.27969770177236 L 350.0722726276896 347.0559395403545 L 350.28891639966815 344.1189715802259 L 71.63719293145283 320.59485790112956 L 73.67998661197436 305.98085766338914 L 350.6494094021131 341.19617153267785 L 351.1528831758062 338.2945806790318 L 76.5330046629017 291.47290339515916 L 80.1893739138552 277.10594602901847 L 351.7981248083274 335.42118920580367 L 352.5835798560675 332.58291936473177 L 84.64028585104933 262.9145968236589 L 89.87501783777338 248.93304398233158 L 353.50735608901886 329.78660879646634 L 354.5672280493229 327.03899405809415 L 95.88095894616305 235.19497029047068 L 102.64364033802997 221.73347197091297 L 355.76064241259354 324.3466943941826 L 357.08472413909885 321.71619579043966 L 110.14677012156017 208.58097895219828 L 118.37227259990851 195.76917674203108 L 358.53628339998386 319.15383534840623 L 360.11182326184746 316.6657860188234 L 127.30033181713577 183.32893009411694 L 136.90943929658204 171.29020865226764 L 361.8075481111615 314.25804173045356 L 363.61937279823604 311.9364029501808 L 147.1764458566709 159.68201475090402 L 158.07661737931554 148.5323135458922 L 365.5429324787027 309.7064627091784 L 367.5735931288075 307.5735931288067 L 169.5836943965756 137.8679656440335 L 181.66995535201565 127.71466239351008 L 369.7064627091792 305.542932478702 L 371.9364029501816 303.61937279823536 L 194.3062833843626 118.09686399117678 L 207.46223647257486 109.03774055580445 L 374.2580417304544 301.8075481111609 L 376.6657860188243 300.1118232618469 L 221.10612077333755 100.55911630923444 L 235.20506697430704 92.68141699991645 L 379.15383534840714 298.5362833999833 L 381.71619579044057 297.08472413909834 L 249.72510947916334 85.4236206954917 L 264.6312682337068 78.80321206296531 L 384.34669439418354 295.76064241259303 L 387.0389940580951 294.56722804932247 L 279.8876329958723 72.83614024661233 L 295.4574498466481 67.53678044509223 L 389.7866087964673 293.50735608901846 L 392.5829193647328 292.58357985606716 L 311.3032097334858 62.91789928033586 L 327.3867388328934 58.99062404163544 L 395.4211892058047 291.7981248083271 L 398.2945806790329 291.1528831758059 L 343.6692905145196 55.76441587902963 L 360.11163868518037 53.247047010564586 L 401.1961715326789 290.64940940211295 L 404.11897158022697 290.288916399668 L 376.67417228795284 51.44458199833997 L 393.3169907286814 50.36136313844747 L 407.05593954035555 290.0722726276895 L 410.0000000000006 289.9999999999999 L 410.0000000000036 49.99999999999932 L 426.6830092713258 50.36136313844776 L 412.94406045964575 290.0722726276896 L 415.8810284197743 290.2889163996681 L 443.3258277120543 51.444581998340595 L 459.8883613148268 53.24704701056555 L 418.8038284673224 290.6494094021131 L 421.70541932096836 291.15288317580615 L 476.33070948548743 55.76441587903088 L 492.6132611671136 58.99062404163698 L 424.5788107941965 291.7981248083274 L 427.4170806352684 292.58357985606756 L 508.69679026652113 62.91789928033768 L 524.5425501533587 67.53678044509428 L 430.2133912035339 293.50735608901886 L 432.9610059419061 294.5672280493229 L 540.1123670041344 72.83614024661466 L 555.3687317662998 78.80321206296787 L 435.6533056058176 295.7606424125936 L 438.28380420956057 297.0847241390989 L 570.2748905208431 85.42362069549449 L 584.7949330256993 92.68141699991958 L 440.846164651594 298.5362833999839 L 443.3342139811768 300.1118232618476 L 598.8938792266686 100.5591163092378 L 612.5377635274311 109.03774055580809 L 445.74195826954667 301.8075481111616 L 448.0635970498194 303.61937279823616 L 625.6937166156432 118.09686399118067 L 638.3300446479899 127.7146623935142 L 450.29353729082175 305.5429324787028 L 452.4264068711935 307.57359312880754 L 650.4163056034297 137.86796564403784 L 661.9233826206896 148.53231354589673 L 454.45706752129814 309.70646270917933 L 456.3806272017648 311.9364029501818 L 672.8235541433339 159.6820147509088 L 683.0905607034226 171.2902086522726 L 458.1924518888393 314.2580417304545 L 459.8881767381533 316.6657860188244 L 692.6996681828685 183.32893009412211 L 701.6277274000955 195.76917674203642 L 461.4637166000169 319.1538353484073 L 462.91527586090183 321.71619579044074 L 709.8532298784436 208.5809789522038 L 717.3563596619734 221.7334719709186 L 464.2393575874071 324.3466943941837 L 465.43277195067765 327.0389940580953 L 724.1190410538401 235.19497029047645 L 730.1249821622295 248.9330439823375 L 466.49264391098166 329.7866087964675 L 467.4164201439329 332.582919364733 L 735.3597141489532 262.91459682366497 L 739.8106260861471 277.1059460290246 L 468.201875191673 335.4211892058049 L 468.84711682419413 338.29458067903306 L 743.4669953371001 291.4729033951654 L 746.3200133880272 305.98085766339545 L 469.35059059788716 341.1961715326791 L 469.7110836003321 344.1189715802272 L 748.3628070685484 320.5948579011359 L 749.5904551097599 335.2796977017788 L 469.9277273723105 347.0559395403558 z
diff --git a/src/toys/svgd/lotsarect.svgd b/src/toys/svgd/lotsarect.svgd
new file mode 100644
index 0000000..ab2d186
--- /dev/null
+++ b/src/toys/svgd/lotsarect.svgd
@@ -0,0 +1 @@
+M 39.75993,80.899849 L 359.75993,80.899849 L 359.75993,278.04273 L 39.75993,278.04273 L 39.75993,80.899849 zM 377.14322,406.21942 L 511.42894,406.21942 L 511.42894,503.36228 L 377.14322,503.36228 L 377.14322,406.21942 zM 377.14322,189.9335 L 511.42894,189.9335 L 511.42894,287.07636 L 377.14322,287.07636 L 377.14322,189.9335 zM 377.14322,298.07648 L 511.42894,298.07648 L 511.42894,395.21934 L 377.14322,395.21934 L 377.14322,298.07648 zM 377.14322,80.899849 L 511.42894,80.899849 L 511.42894,178.04271 L 377.14322,178.04271 L 377.14322,80.899849 zM 377.14322,514.36237 L 511.42894,514.36237 L 511.42894,611.50523 L 377.14322,611.50523 L 377.14322,514.36237 zM 49.495197,612.79767 L 359.4294,612.79767 L 359.4294,622.22533 L 49.495197,622.22533 L 49.495197,612.79767 zM 374.74936,625.76074 L 512.62885,625.76074 L 512.62885,757.74794 L 374.74936,757.74794 L 374.74936,625.76074 z \ No newline at end of file
diff --git a/src/toys/svgd/monkey.svgd b/src/toys/svgd/monkey.svgd
new file mode 100644
index 0000000..9bdbbc3
--- /dev/null
+++ b/src/toys/svgd/monkey.svgd
@@ -0,0 +1 @@
+M 5.9562726,54.79058 C 13.541683,37.50935 29.441883,27.76099 41.447803,33.03085 c 12.00591,5.26987 15.59357,23.57221 8.00816,40.85344 -7.58541,17.28124 -9.90029,25.53083 -32.70294,16.30215 C 4.5991026,85.26753 -1.6291374,72.07182 5.9562726,54.79058 z M 59.77575,139.78799 c 17.6347,24.11274 67.29975,30.95068 91.77238,0.71978 m -34.6436,-37.40404 c 0,3.47102 -5.75618,6.2881 -12.84862,6.2881 -7.09242,0 -12.848597,-2.81708 -12.848597,-6.2881 0,-3.471028 5.756177,-6.288105 12.848597,-6.288105 7.09244,0 12.84862,2.817077 12.84862,6.288105 z m 8.28521,-17.104633 c -2.97718,0.127204 -5.59649,-4.52155 -5.84666,-10.376686 -0.25017,-5.855125 1.96307,-10.710355 4.94026,-10.837559 2.97718,-0.127204 5.59649,4.52155 5.84665,10.376676 0.25017,5.855135 -1.96307,10.710365 -4.94025,10.837569 z M 83.28913,64.783523 c 2.977573,-0.11776 5.582124,4.539282 5.813719,10.395185 0.231594,5.855893 -1.997037,10.704081 -4.97461,10.821841 -2.977572,0.11776 -5.582124,-4.539282 -5.813718,-10.395175 -0.231594,-5.855903 1.997036,-10.704092 4.974609,-10.821851 z M 45.217471,85.556076 c 0.822591,11.141413 3.633402,22.142554 10.599469,31.583504 0,0 -15.4694,5.45785 -13.31599,26.99188 2.87914,28.79134 33.63174,40.93745 66.22007,40.66777 27.38883,-0.21003 58.61221,-13.31662 59.02225,-39.2282 0.29666,-23.0823 -6.83795,-24.11275 -12.95611,-28.79134 6.43867,-10.04933 9.12075,-19.882931 9.80868,-31.216258 m -7.09398,-26.793307 c -5.29439,-5.661365 -13.31177,-9.59154 -23.94831,-11.089645 -10.28695,-1.43893 -26.99188,5.03849 -29.15123,5.03849 -1.79946,0 -23.47674,-7.30799 -34.5496,-4.6786 -6.377516,1.507416 -11.596875,4.060909 -15.573817,7.756776 M 47.116071,35.239853 C 60.692058,16.061922 81.318474,3.8164724 104.40235,3.8164724 c 24.26701,0 45.81819,13.5328816 59.31755,34.4261516 m 14.55081,56.509427 c -0.76518,14.403499 -4.61365,27.847529 -10.79081,39.459189 M 42.237336,135.88313 C 35.362956,123.6046 31.132611,109.16372 30.481189,93.641902 M 204.76611,54.79058 C 197.1807,37.50935 181.2805,27.76099 169.27458,33.03085 c -12.00591,5.26987 -15.59357,23.57221 -8.00816,40.85344 7.58541,17.28124 9.90029,25.53083 32.70294,16.30215 12.15392,-4.91891 18.38216,-18.11462 10.79675,-35.39586 z
diff --git a/src/toys/svgd/nasty.svgd b/src/toys/svgd/nasty.svgd
new file mode 100644
index 0000000..2a26bd2
--- /dev/null
+++ b/src/toys/svgd/nasty.svgd
@@ -0,0 +1,3 @@
+M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z
+M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z
+M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z
diff --git a/src/toys/svgd/onlyarcs.svgd b/src/toys/svgd/onlyarcs.svgd
new file mode 100644
index 0000000..b8fe8d1
--- /dev/null
+++ b/src/toys/svgd/onlyarcs.svgd
@@ -0,0 +1,10 @@
+M 0,0
+A 40,20 0 0 0 100,0
+ 40,20 0 0 0 200,0
+ 40,20 90 0 0 200,100
+ 40,20 90 0 0 200,200
+ 40,20 0 0 0 100,200
+ 40,20 0 0 0 0,200
+ 40,20 90 0 0 0,100
+ 40,20 90 0 0 0,0
+ z
diff --git a/src/toys/svgd/ptitle.svgd b/src/toys/svgd/ptitle.svgd
new file mode 100644
index 0000000..2df8860
--- /dev/null
+++ b/src/toys/svgd/ptitle.svgd
@@ -0,0 +1 @@
+M 115.1808,25.588188 C 127.80826,26.557245 141.79964,22.715822 153.00893,30.053032 C 167.1951,42.822758 151.75887,64.216819 134.75853,59.865532 C 126.06809,57.038779 129.18533,66.019697 128.71596,71.414009 C 131.4913,80.259661 123.09665,77.935329 117.16748,78.076469 C 112.56276,76.886302 116.47422,67.179968 115.1808,62.6785 C 115.1808,50.315063 115.1808,37.951625 115.1808,25.588188 z M 131.60308,50.056938 C 148.57642,53.813235 146.23212,30.658565 130.43,35.396782 C 127.29211,38.94083 127.23081,46.355022 131.60308,50.056938 z M 183.06752,60.357719 C 165.63447,60.295503 183.24033,80.518401 189.00011,65.31476 C 192.06883,58.337714 187.11171,60.778459 183.06752,60.357719 z M 202.29799,55.611626 C 202.29799,63.099907 202.29799,70.588188 202.29799,78.076469 C 197.62085,76.91918 187.20205,81.503563 189.60658,73.264604 C 182.8671,85.729177 152.16107,75.907493 165.13563,57.951724 C 168.48007,47.659856 195.18552,59.239016 187.25111,47.525688 C 177.86756,43.881758 158.53169,53.222176 167.93494,39.294753 C 181.3394,36.315116 203.97321,35.500521 202.29799,55.611626 z M 243.32533,49.424126 C 229.50261,42.165491 224.74932,58.12986 226.66127,68.597202 C 228.09365,76.076455 226.04486,80.074546 217.99991,78.076469 C 210.48863,79.788485 215.52906,68.948786 214.07533,64.603246 C 214.07533,55.969321 214.07533,47.335395 214.07533,38.701469 C 218.71947,39.856601 229.10169,35.273643 226.66127,43.513335 C 227.95656,41.232134 248.09079,31.257919 243.31691,46.694818 C 243.31972,47.604587 243.32252,48.514356 243.32533,49.424126 z M 267.30191,60.357719 C 249.86884,60.295482 267.47469,80.518412 273.23449,65.31476 C 276.30322,58.33771 271.34611,60.778461 267.30191,60.357719 z M 286.53235,55.611626 C 286.53235,63.099907 286.53235,70.588188 286.53235,78.076469 C 281.85522,76.919181 271.43643,81.503563 273.84097,73.264604 C 267.10148,85.729174 236.39544,75.907495 249.37001,57.951724 C 252.71445,47.659856 279.41992,59.239015 271.48549,47.525688 C 262.10194,43.88176 242.76606,53.222171 252.16932,39.294753 C 265.57378,36.315114 288.20758,35.500525 286.53236,55.611626 M 334.80191,45.240532 C 345.49046,28.171278 366.87108,43.167474 361.59096,59.874146 C 361.59097,65.941587 361.59097,72.009028 361.59097,78.076469 C 356.92475,76.919987 346.51835,81.503682 348.93472,73.264604 C 348.50839,65.231031 349.97235,56.898734 347.77455,49.142876 C 335.14645,42.504341 335.807,61.093114 336.24329,69.326636 C 337.9621,76.849177 335.16475,79.855557 327.58193,78.076469 C 320.00816,79.827535 325.02155,68.991435 323.58704,64.603246 C 326.31279,55.038882 318.58105,38.272161 311.38782,53.827464 C 310.41918,61.855715 311.09896,69.997063 310.89566,78.076469 C 306.22944,76.919987 295.82304,81.503682 298.23941,73.264604 C 298.23941,61.743559 298.23941,50.222514 298.23941,38.701469 C 302.90563,39.857951 313.31203,35.274257 310.89566,43.513335 C 314.6641,37.643381 330.44834,33.922886 334.8019,45.240532 M 412.70816,58.283501 C 413.74716,65.329673 402.8183,60.407999 398.27256,61.869438 C 392.57307,62.588118 377.07559,58.69943 386.48158,68.513969 C 394.57691,78.279659 414.21137,57.534567 410.84485,73.681704 C 404.09284,81.288741 385.15052,81.142742 376.35658,73.681938 C 363.9865,62.168354 371.67984,37.148572 389.80164,37.838507 C 401.97647,35.889384 413.6668,45.751205 412.70815,58.283501 M 399.77066,54.099907 C 393.61653,30.04231 370.10862,61.928589 399.77066,54.099907 z M 435.94644,27.521782 C 435.96201,33.478312 433.93472,41.4777 442.68305,38.701469 C 449.91823,34.769304 451.1939,46.589839 446.99432,47.701469 C 441.05173,47.687042 433.16506,45.728538 435.94644,54.438081 C 435.39823,61.49483 434.91359,71.865095 445.20998,69.076469 C 450.19974,68.732922 449.24732,82.168413 442.05502,78.076469 C 430.09932,80.727323 420.36946,71.930596 423.36049,59.588823 C 422.71262,54.349502 426.24292,44.932013 417.58627,47.701469 C 415.00348,41.807039 418.08141,37.377201 423.3605,37.739096 C 422.6103,31.016795 422.60176,25.036149 431.05949,27.521782 C 432.68847,27.521782 434.31746,27.521782 435.94644,27.521782 z M 485.90347,49.424126 C 472.08074,42.165489 467.32744,58.129851 469.2394,68.597202 C 470.67179,76.076455 468.62301,80.074546 460.57805,78.076469 C 453.06677,79.788485 458.1072,68.948786 456.65347,64.603246 C 456.65347,55.969321 456.65347,47.335395 456.65347,38.701469 C 461.29761,39.856601 471.67983,35.273643 469.23941,43.513335 C 470.53469,41.232124 490.6689,31.257926 485.89505,46.694818 C 485.89785,47.604587 485.90066,48.514356 485.90347,49.424126 z M 492.23157,38.701469 C 496.8757,39.856601 507.25793,35.273643 504.8175,43.513335 C 504.8175,55.03438 504.8175,66.555425 504.8175,78.076469 C 500.17337,76.921338 489.79114,81.504296 492.23157,73.264604 C 492.23157,61.743559 492.23157,50.222514 492.23157,38.701469 z M 492.23157,23.373344 C 496.8757,24.528476 507.25793,19.945518 504.8175,28.18521 C 507.42558,36.781485 497.41711,32.761224 492.30665,33.638969 C 492.13675,30.373397 492.27768,26.760041 492.23157,23.373344 z M 548.79797,39.931938 C 552.97763,56.979712 538.66501,41.438745 530.05971,49.846001 C 518.41065,63.70638 539.40292,76.166974 548.79799,67.577905 C 553.09286,81.766505 534.23374,80.517459 525.20155,76.9674 C 507.36245,69.798238 511.84153,38.785564 531.6681,38.095047 C 537.4159,37.270461 543.34813,37.942021 548.79799,39.931938 M 590.49329,39.931938 C 594.79689,57.039848 577.52986,40.882708 568.9513,48.158523 C 572.15538,57.881524 601.33708,52.315812 591.96984,71.985653 C 585.95746,81.25109 561.1725,82.610066 558.00892,72.785571 C 554.84332,59.391839 574.62439,78.16019 582.36692,66.985848 C 570.73978,64.945587 547.5903,55.865125 561.70033,40.775688 C 570.39079,35.821962 581.23811,37.826772 590.4933,39.931938
diff --git a/src/toys/svgd/rect.svgd b/src/toys/svgd/rect.svgd
new file mode 100644
index 0000000..29ee442
--- /dev/null
+++ b/src/toys/svgd/rect.svgd
@@ -0,0 +1 @@
+M 120,98.076469 L 551.42856,98.076469 L 551.42856,323.79075 L 120,323.79075 L 120,98.076469 z \ No newline at end of file
diff --git a/src/toys/svgd/sanitize-examples.svgd b/src/toys/svgd/sanitize-examples.svgd
new file mode 100644
index 0000000..486188d
--- /dev/null
+++ b/src/toys/svgd/sanitize-examples.svgd
@@ -0,0 +1 @@
+m 103.3207,92.186815 c 61.67475,0 77.09344,123.349515 138.76821,123.349515 92.51214,0 92.51214,-123.349515 0,-123.349515 -61.67476,0 -77.09345,123.349515 -138.76821,123.349515 -92.512144,0 -92.512144,-123.349515 0,-123.349515 m 172.70652,592.36216 c -116.436115,0 -206.29787,-170 -79.999995,-170 119.740085,0 239.999995,110 79.999995,110 -159.999995,0 -39.74009,-110 80,-110 126.29788,0 36.43613,170 -80,170 \ No newline at end of file
diff --git a/src/toys/svgd/scribble.svgd b/src/toys/svgd/scribble.svgd
new file mode 100644
index 0000000..206563d
--- /dev/null
+++ b/src/toys/svgd/scribble.svgd
@@ -0,0 +1 @@
+M 205.71429,509.50504 C 191.27639,435.33396 124.96983,282.9985 237.14286,240.93361 C 298.33002,217.98843 410.42812,416.03951 425.71429,458.07647 C 456.36209,542.35792 211.91703,513.49252 165.71429,483.79075 C 20.478436,390.42485 89.745454,432.24443 157.14286,589.50504 C 207.33723,706.62525 264.31061,792.65288 391.42857,818.07647 C 466.24819,833.04039 419.17914,844.21704 428.57143,806.6479 C 455.45597,699.10972 205.78614,535.50673 188.57143,466.6479 C 186.58441,458.69982 179.04762,453.31456 174.28571,446.6479 C 173.27225,445.22905 206.50053,444.8262 242.85714,426.6479 C 302.27468,396.93913 311.42857,331.66273 311.42857,272.36218 C 311.42857,232.18181 300.5556,209.69024 257.14286,195.21933 C 217.28192,181.93235 106.94618,129.21667 94.285714,123.79075 C 79.18517,117.31909 126.94244,129.50504 168.57143,129.50504 C 301.00757,129.50504 430.66079,118.07647 562.85714,118.07647 C 597.38432,118.07647 452.97541,72.362183 425.71429,72.362183 C 289.53604,72.362183 290.63424,45.059235 377.14286,218.07647 C 439.49432,342.7794 589.94279,380.17165 680,455.21933 C 715.67553,484.94893 625.63444,427.43 577.14286,406.6479 C 390.81914,326.79487 363.97733,373.48237 502.85714,512.36218 C 519.79024,529.29528 656.23138,719.67741 540,680.93361 C 493.67741,665.49275 225.40147,535.28189 382.85714,503.79075 C 399.28655,500.50487 561.71346,472.36218 517.14286,472.36218 C 410.68431,472.36218 181.50374,350.75815 111.42857,292.36218 C 21.960157,217.80517 195.28155,363.95164 220,378.07647 C 486.98782,530.64094 333.26177,540.93361 142.85714,540.93361 C 33.897473,540.93361 -105.50257,492.78561 -34.285714,635.21933 C 4.462011,712.71478 133.2726,753.67122 208.57143,783.79075 C 234.8422,794.29906 219.78391,839.52186 208.57143,855.21933 C 168.50791,911.30825 225.30435,836.44417 274.28571,826.6479 C 322.06141,817.09276 466.28439,799.2233 485.71429,740.93361 C 493.89344,716.39615 516.95233,712.23517 474.28571,683.79075 C 439.34382,660.49616 441.2221,580.11609 397.14286,558.07647 C 373.83188,546.42098 339.15563,564.21294 322.85714,572.36218 C 305.88053,580.85049 256.57431,543.50648 240,535.21933 C 222.14649,526.29257 195.2496,535.66674 205.71429,509.50504 C 206.5716,507.36174 165.95426,541.31306 205.71429,509.50504 C 209.43271,506.5303 205.71429,519.02885 205.71429,523.79075 C 205.71429,528.55266 205.71429,514.26694 205.71429,509.50504 z \ No newline at end of file
diff --git a/src/toys/svgd/spiral.svgd b/src/toys/svgd/spiral.svgd
new file mode 100644
index 0000000..cf096f9
--- /dev/null
+++ b/src/toys/svgd/spiral.svgd
@@ -0,0 +1 @@
+M 425.71429,452.36218 C 428.61778,445.53492 436.34979,452.9077 437.06166,457.18797 C 438.99077,468.78726 426.04452,475.99734 416.06271,475.05691 C 398.20761,473.37469 388.47706,454.23374 391.67221,437.88481 C 396.36121,413.89211 422.40756,401.33715 445.01745,406.97273 C 475.15289,414.48407 490.6018,447.65442 482.45111,476.49113 C 472.1943,512.7792 431.80767,531.14987 396.75955,520.44636 C 354.31159,507.48297 333.00474,459.83266 346.28275,418.58165 C 361.93043,369.96877 416.872,345.71719 464.32061,361.58328 C 519.10192,379.9013 546.30379,442.1516 527.84056,495.79429 C 506.86147,556.74653 437.29069,586.90252 377.45639,565.83581 C 310.33136,542.20215 277.21849,465.30257 300.8933,399.27849 C 327.17683,325.97925 411.41129,289.90746 483.62377,316.19383 \ No newline at end of file
diff --git a/src/toys/svgd/star.svg b/src/toys/svgd/star.svg
new file mode 100644
index 0000000..113f523
--- /dev/null
+++ b/src/toys/svgd/star.svg
@@ -0,0 +1,60 @@
+<?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://web.resource.org/cc/"
+ 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="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ sodipodi:docbase="/home/njh/svn/lib2geom/src/toys"
+ sodipodi:docname="star.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="375"
+ inkscape:cy="520"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="910"
+ inkscape:window-height="631"
+ inkscape:window-x="0"
+ inkscape:window-y="25" />
+ <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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="fill:#ffff00;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z "
+ id="path3134" />
+ </g>
+</svg>
diff --git a/src/toys/svgd/star.svgd b/src/toys/svgd/star.svgd
new file mode 100644
index 0000000..c6eb2d4
--- /dev/null
+++ b/src/toys/svgd/star.svgd
@@ -0,0 +1 @@
+M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z \ No newline at end of file
diff --git a/src/toys/svgd/tadpole.svgd b/src/toys/svgd/tadpole.svgd
new file mode 100644
index 0000000..8895582
--- /dev/null
+++ b/src/toys/svgd/tadpole.svgd
@@ -0,0 +1 @@
+m 371.42857,212.36218 c -32.48101,-26.68142 -91.58939,10.54383 -64.64475,49.96244 5.57459,42.57277 70.53374,68.98077 78.10803,11.90685 14.41443,-52.55004 -40.97633,-99.82411 -91.27953,-91.66162 -45.76637,3.77115 -95.66566,43.4207 -88.25336,93.06866 8.10715,43.84559 57.30139,51.48387 83.49912,79.21169 62.5378,56.33364 174.00162,56.411 226.54697,-13.71412 33.15536,-44.32089 27.37475,-103.8585 15.40989,-154.44966 C 518.94598,147.80633 481.97543,121.49829 442.21462,117.80826 360.77206,103.76604 275.59142,128.77168 196.14437,100.04722 138.26682,85.91059 75.980997,126.30734 62.339274,183.76981 43.90764,241.6785 65.723336,304.74593 104.98132,348.74653 179.31517,452.93484 311.47528,524.69632 441.07626,499.66492 521.15261,482.71575 593.45793,431.55112 633.57498,359.85355 666.74394,302.92229 686.82287,232.82246 669.17746,167.67963 652.45446,117.60598 601.61006,86.174529 550.15809,84.370797 388.03362,61.260938 223.67779,65.532229 60.679408,54.420286 c -85.3275,5.078677 -174.754978,36.378483 -230.507428,103.801754 -26.33214,35.75102 -22.70039,88.23391 8.40942,120.02678 31.2681,37.67952 76.41934,59.44396 112.847171,91.25622 \ No newline at end of file
diff --git a/src/toys/svgd/touchboxes.svgd b/src/toys/svgd/touchboxes.svgd
new file mode 100644
index 0000000..55e6f9d
--- /dev/null
+++ b/src/toys/svgd/touchboxes.svgd
@@ -0,0 +1 @@
+m 350,380 200,0 0,300 -200,0 0,-300 z M 80,180 l 270,0 0,400 -270,0 0,-400 z \ No newline at end of file
diff --git a/src/toys/svgd/toy.svgd b/src/toys/svgd/toy.svgd
new file mode 100644
index 0000000..9983796
--- /dev/null
+++ b/src/toys/svgd/toy.svgd
@@ -0,0 +1 @@
+M 685.71429 132.36218 L 337.14286+238.07647 608.57143,298.07647 C 608.57143,298.07647 685.71429,543.79075 517.14286,552.36218 348.57143,560.93361 280,858.07647 422.85714,852.36218 565.71429,846.6479 691.42857,612.36218 691.42857,612.36218 Q 348.57143,560.93361 685.71429,132.36218 Z
diff --git a/src/toys/svgd/triarrange.svgd b/src/toys/svgd/triarrange.svgd
new file mode 100644
index 0000000..f5d82ee
--- /dev/null
+++ b/src/toys/svgd/triarrange.svgd
@@ -0,0 +1 @@
+M 495,400 45,380 M 210,230 450,460 M 100,440 370,230 \ No newline at end of file
diff --git a/src/toys/svgd/tricky.svgd b/src/toys/svgd/tricky.svgd
new file mode 100644
index 0000000..1b2fa9a
--- /dev/null
+++ b/src/toys/svgd/tricky.svgd
@@ -0,0 +1 @@
+M 458.65625,31.34375 L 455.0625,31.4375 L 451.6875,32.65625 C 434.07714,39.013007 420.84501,52.305976 414.03125,67.25 C 407.21749,82.194024 405.96292,98.410997 408.28125,113.8125 C 412.4771,141.68706 428.51783,169.60215 456.40625,181.6875 C 538.70194,312.80904 554.38602,481.52856 510.34375,629.90625 L 509.8125,631.71875 L 509.5625,633.59375 C 506.87733,655.34401 495.49324,679.1849 479.46875,695.25 C 463.70165,711.05705 445.14595,719.37248 423.84375,716.625 C 403.50709,711.46674 391.72366,697.93145 383.4375,677.25 C 375.01895,656.2381 372.48837,628.78961 374.375,606.03125 C 385.44095,485.85838 388.01837,349.21084 313,239 C 270.11084,152.28462 127.55134,177.04972 109.25,269.96875 L 108.5,273.6875 L 109.03125,277.4375 C 117.41998,339.02101 116.06431,402.35901 112.375,466.09375 L 112.21875,469.125 L 112.84375,472.09375 C 117.1301,492.47365 108.37169,516.28486 92.03125,532.75 C 78.58177,546.30212 61.816491,553.83892 44.78125,552.625 C 26.079739,485.94082 42.403549,411.80134 67.125,342.90625 L 24.3125,327.53125 C -3.3082192,404.50618 -24.482689,494.297 5.71875,580.03125 L 9.5625,590.90625 L 20.5625,594.25 C 59.671156,606.15028 98.586445,590.76637 124.34375,564.8125 C 149.33808,539.62742 164.42386,503.26605 157.9375,466.125 C 161.49046,403.72415 162.85377,340.00411 154.65625,276.1875 C 166.8006,229.69869 250.73728,213.68098 272.65625,260.03125 L 273.46875,261.6875 L 274.5,263.21875 C 340.18519,358.54848 339.90385,483.98933 329.03125,602.0625 L 329.03125,602.15625 L 329.03125,602.25 C 326.66071,630.84572 329.10103,663.98958 341.1875,694.15625 C 353.27397,724.32292 377.30928,752.4189 414.28125,761.1875 L 415.28125,761.40625 L 416.25,761.5625 C 453.7973,767.02344 487.87231,751.21921 511.65625,727.375 C 534.86042,704.11203 549.85821,673.25525 554.3125,641.53125 C 602.10825,479.2135 584.5466,295.0552 490.8125,150.71875 L 486.125,143.53125 L 477.90625,141.21875 C 466.97267,138.13618 455.72384,123.25825 453.28125,107.03125 C 452.05996,98.91775 453.07904,91.297618 455.4375,86.125 C 457.36175,81.904709 459.73336,79.094036 464.03125,76.90625 C 495.13555,76.980202 529.29685,90.275009 566.03125,101.59375 C 610.4676,115.59228 646.8459,148.41525 668.6875,190.40625 L 668.8125,190.65625 L 668.9375,190.875 C 697.92762,243.68554 717.84744,301.48146 706.25,356.90625 L 706.21875,357.15625 L 706.15625,357.40625 C 694.18886,421.72169 679.2601,488.02392 680.625,557.96875 C 678.68079,606.53403 686.86775,651.99143 686.21875,693.84375 C 670.05571,787.71759 590.84823,878.44142 494,879.59375 L 493.96875,879.59375 L 493.9375,879.59375 C 395.82741,881.09756 293.40089,802.00173 289.84375,701.78125 L 289.8125,700.875 L 289.71875,700 C 282.30077,635.83434 290.72304,569.30623 297.15625,501.21875 L 298.0625,491.5625 L 291.75,484.21875 C 284.80041,476.14619 277.49477,469.76476 268.21875,465.84375 C 258.94273,461.92274 247.2182,461.69521 238.125,465.375 C 219.9386,472.73458 212.83021,487.30247 206.46875,501.0625 C 200.10729,514.82253 195.45919,529.80289 191.34375,543.03125 C 187.22831,556.25961 183.18481,568.24935 181.34375,571.875 L 181.28125,572.03125 L 181.21875,572.1875 C 142.65707,651.07426 94.549143,727.5865 58.8125,812.3125 C 49.294957,829.15506 36.457563,832.16994 22.90625,829.09375 C 9.2095614,825.98456 -3.2515511,815.31431 -6,797.40625 L -6.28125,795.53125 L -6.875,793.71875 C -20.296536,753.1447 -21.015532,704.32135 -39.625,654.96875 C -73.90942,524.31702 -90.602855,380.79943 -28.71875,262.375 C 1.9598227,205.26365 67.457357,164.07188 103.28125,90.9375 L 62.4375,70.9375 C 33.995201,129.00238 -30.775413,170.01981 -68.9375,241.0625 L -69,241.1875 L -69.0625,241.28125 C -139.47511,376.02623 -119.00391,531.98526 -83.5,667.0625 L -83.1875,668.28125 L -82.71875,669.46875 C -67.458776,708.96795 -66.587036,756.94282 -50.5625,806.375 C -44.27864,841.61077 -17.523667,866.57525 12.84375,873.46875 C 43.80863,880.49788 80.448846,867.38803 98.96875,833.78125 L 99.5625,832.71875 L 100.03125,831.59375 C 133.90634,750.79708 181.84088,674.53399 222.09375,592.1875 C 227.62217,581.30025 230.72079,569.58288 234.78125,556.53125 C 238.84171,543.47962 243.26444,529.92624 247.78125,520.15625 C 248.49865,518.60449 249.23407,517.44847 249.96875,516.1875 C 243.90866,577.21519 237.04535,640.29773 244.46875,704.90625 C 244.48076,705.01077 244.48792,705.11422 244.5,705.21875 C 250.176,833.46768 372.77047,926.96151 494.625,925.09375 C 619.20181,923.6115 712.56579,813.78448 731.40625,699.875 L 731.65625,698.28125 L 731.6875,696.65625 C 732.75618,647.53405 724.20107,601.36335 726.09375,558.53125 L 726.125,557.8125 L 726.125,557.09375 C 724.89958,494.29702 738.71436,431.07277 750.875,365.71875 C 765.84238,294.18884 740.23516,226.21058 708.8125,168.96875 C 681.8812,117.19267 636.59507,76.122901 579.5625,58.15625 L 579.5,58.125 L 579.4375,58.125 C 544.17995,47.261312 504.09579,29.800274 458.65625,31.34375 z \ No newline at end of file
diff --git a/src/toys/svgd/winding.svgd b/src/toys/svgd/winding.svgd
new file mode 100644
index 0000000..e0d9b59
--- /dev/null
+++ b/src/toys/svgd/winding.svgd
@@ -0,0 +1 @@
+M 237.8317,-0.00079806585 C 209.05129,2.0908553 194.49466,33.03178 168.59187,39.198673 C 144.83493,42.756195 133.04439,64.090923 112.23618,72.228008 C 96.390481,93.25412 79.059139,109.36615 55.144754,121.38967 C 28.073728,135.55225 28.749612,175.16363 61.142614,182.29572 C 59.032188,207.27758 51.935234,245.41728 19.327194,232.92932 C -15.949549,243.07701 3.1745321,287.497 13.519564,310.39525 C 17.791104,331.61525 13.405542,357.01005 26.625846,375.92214 C 24.121233,402.01179 53.313158,415.34704 52.967142,440.29059 C 53.481143,471.00236 85.579755,514.66901 114.61929,479.98667 C 130.54404,473.62405 176.35717,503.20612 155.39995,524.08371 C 144.17379,562.84736 195.91996,565.77913 221.73596,560.74671 C 247.29609,564.51298 273.27675,571.81621 298.92308,570.02889 C 324.04728,573.62413 345.47989,554.14368 370.64594,564.03304 C 402.54533,573.02054 442.7131,544.20356 415.3454,511.64988 C 431.35565,493.15457 459.52837,465.86203 478.13411,494.82269 C 512.36196,508.17908 524.21953,461.82677 528.84872,436.94541 C 539.13223,415.75136 555.80382,396.04476 559.09515,372.30135 C 573.74637,351.25358 558.73087,324.13051 575.26348,304.02273 C 593.5935,277.76628 586.3979,225.87087 544.09643,239.90933 C 527.05985,225.53034 511.89014,182.31837 545.28987,174.97401 C 567.85671,145.99111 526.28917,121.32068 504.46509,108.82388 C 488.46914,94.238944 477.21301,71.049919 455.38968,63.601767 C 442.2435,41.019814 410.90027,47.427299 396.59221,27.25649 C 378.5237,4.3455996 329.19512,-15.054231 323.43731,28.290685 C 321.44896,48.974453 272.63938,43.051302 266.79567,31.393533 C 265.68977,16.25095 255.66504,-2.8664064 237.8317,-0.00079806585 z M 214.63232,41.285294 C 229.03834,39.461472 281.82707,44.654146 243.16774,50.095915 C 215.41861,55.13544 186.824,66.887416 160.08963,70.064158 C 171.40031,49.377676 189.54369,36.977634 214.63232,41.285294 z M 364.21881,43.044488 C 389.3526,38.086584 423.75203,50.784111 426.68647,74.129598 C 394.45708,65.080185 361.92396,54.41391 328.76005,47.772323 C 335.0792,39.285706 353.90158,45.472989 364.21881,43.044488 z M 299.89827,49.421568 C 334.38633,45.796396 318.92415,87.04827 327.35648,107.9555 C 332.43473,138.01064 340.0154,167.56295 347.72637,197.02897 C 326.17931,210.45393 292.14883,198.94677 265.91081,202.13508 C 245.36668,206.92129 230.45935,196.7415 243.8515,176.32177 C 254.2743,135.80305 265.45003,94.976539 267.46313,52.939957 C 278.24641,51.687903 288.9237,47.465671 299.89827,49.421568 z M 137.22777,69.707278 C 166.23618,69.494992 120.49304,93.04016 137.22777,69.707278 z M 451.29893,73.445566 C 466.93112,101.11163 425.11973,69.858477 451.29893,73.445566 z M 161.69157,80.207469 C 195.12998,110.69979 212.21109,153.72778 229.83786,194.32162 C 218.52396,208.95034 195.22647,168.57531 181.29522,159.91955 C 163.37572,141.33152 146.8742,120.6359 137.28274,96.425042 C 142.33212,87.653771 151.47682,79.604025 161.69157,80.207469 z M 432.38759,84.605455 C 469.44406,97.732695 430.28001,132.80731 416.73442,150.68472 C 399.36069,169.02554 380.60198,186.72201 360.97525,202.252 C 349.31821,186.47906 377.05703,160.7322 382.09772,141.74406 C 393.9733,119.90955 408.28746,98.903859 427.49484,82.84626 L 432.38759,84.605455 z M 128.54175,91.752182 C 115.85675,118.87612 92.85066,143.11942 78.60652,170.73021 C 62.000206,188.85762 85.067031,127.73706 91.166453,114.95867 C 96.261463,99.592362 115.43004,97.567564 128.54175,91.752182 z M 461.79912,96.095193 C 494.61461,101.4137 503.85761,132.95303 510.27958,161.07074 C 516.23377,173.44499 515.53354,194.75452 505.81343,172.70604 C 492.0604,146.83354 474.56757,123.01553 458.88546,98.239211 C 458.10505,96.914376 460.38765,93.356115 461.79912,96.095193 z M 79.944004,189.00264 C 125.91433,201.82926 173.80854,204.82394 221.2293,207.58413 C 227.40504,232.22763 205.75574,261.03157 200.77991,287.0056 C 198.98453,308.21225 184.56547,318.80777 169.17926,299.94722 C 133.95987,277.46442 98.659981,254.15242 59.27347,239.46953 C 61.480079,223.18569 64.663812,195.41065 79.944004,189.00264 z M 511.49636,197.57872 C 525.19335,217.70705 535.43047,253.2069 500.46557,254.90752 C 464.15836,271.3807 430.27042,292.57043 396.81888,314.12534 C 377.50066,297.83942 377.83971,261.83829 366.73344,237.87322 C 355.70058,219.68486 361.03872,202.51102 384.63974,208.83172 C 426.3772,206.34293 468.63407,204.30758 509.18742,193.18073 L 511.49636,197.57872 z M 254.26917,235.40139 C 267.88776,248.222 300.90986,260.92788 269.02803,272.58167 C 260.2717,268.87591 255.10883,237.44794 254.26917,235.40139 z M 331.50879,237.05064 C 324.01248,255.18663 320.23797,289.128 300.05044,261.46924 C 304.69039,252.09852 327.86234,239.96464 331.50879,237.05064 z M 50.312574,243.81254 C 48.701291,276.28009 44.929165,308.91572 44.438642,341.59724 C 38.920345,352.62589 18.235924,313.83416 27.328216,302.38831 C 33.148841,283.15159 39.24851,257.94992 50.312574,243.81254 z M 536.45493,250.2446 C 543.88366,277.15913 566.74879,308.63556 551.43159,335.47436 C 546.44721,350.66062 532.20173,359.53588 537.73084,335.75981 C 539.32919,307.71959 531.27597,272.91081 536.06286,249.15412 L 536.45493,250.2446 z M 263.28504,288.34215 C 282.51337,317.65756 244.52942,307.7959 226.78176,309.61741 C 238.1088,304.60141 254.71447,286.66664 263.28504,288.34215 z M 326.06629,289.99139 C 335.50507,298.75069 371.96349,315.98071 340.11655,310.44928 C 318.61751,314.37824 306.58789,306.07219 322.5447,287.44802 L 326.06629,289.99139 z M 281.9215,316.10443 C 315.80881,306.99659 294.53894,340.10829 290.93737,357.61043 C 289.75062,345.2197 277.62158,323.68751 281.9215,316.10443 z M 184.01134,318.7982 C 187.74469,337.09704 148.67935,342.5761 134.89818,354.17134 C 111.56567,365.55352 86.689349,375.07418 60.537891,376.68669 C 34.857135,351.83888 71.020358,335.11628 93.620682,331.96846 C 123.12694,324.08505 153.65422,321.18138 184.01134,318.7982 z M 384.50452,320.1176 C 389.61697,323.73995 377.26894,318.84237 384.50452,320.1176 z M 398.90793,321.38202 C 443.1239,326.51593 489.45667,330.51222 528.63386,353.62958 C 538.96391,391.76884 496.17809,381.49967 476.30611,371.10904 C 448.0456,360.17677 421.89729,344.64344 396.10421,328.91357 C 393.51601,325.98521 395.11468,321.97061 398.90793,321.38202 z M 203.30751,332.26703 C 227.72727,353.07009 264.71284,370.58598 280.20641,394.90874 C 262.93503,412.3019 241.56903,425.95006 223.50819,442.94849 C 205.20907,458.95886 187.67677,475.97038 172.41166,494.93753 C 148.89686,486.37063 113.76917,464.1279 143.2293,439.75157 C 162.91673,405.20393 177.76059,368.20734 192.14762,331.22251 C 195.29635,327.04196 200.53018,328.93547 203.30751,332.26703 z M 386.48362,331.16754 C 400.18586,350.95872 404.22928,376.64387 415.28715,398.36989 C 425.20431,421.90616 436.33007,445.05076 450.41934,466.4056 C 434.12186,488.50845 401.66036,511.25603 387.04573,475.11066 C 360.70616,446.5202 330.78928,421.55644 300.72289,397.02737 C 310.13036,373.45503 344.5899,362.82508 363.91108,344.81642 C 371.25297,341.05885 379.09963,331.94719 386.48362,331.16754 z M 42.781023,362.72308 C 57.078154,387.91587 19.840772,365.01162 42.781023,362.72308 z M 542.33724,366.4064 C 556.06367,380.56137 523.55694,385.37945 539.8395,367.13493 C 539.57763,366.48977 542.65437,362.78508 542.33724,366.4064 z M 53.336189,383.50357 C 75.268719,404.32092 91.325113,433.37386 113.18698,455.63259 C 124.54299,476.44851 74.161701,435.968 63.852016,426.25618 C 50.708583,416.70395 54.964638,397.77227 53.336189,383.50357 z M 528.09876,391.41994 C 533.1255,424.20402 506.16623,442.7593 481.40591,457.52287 C 471.49835,466.88801 451.01521,473.12226 468.89427,456.9389 C 489.1958,435.80533 506.44563,411.83156 525.1851,389.27592 C 526.23268,388.092 530.2919,389.29239 528.09876,391.41994 z M 294.45576,401.04054 C 302.97032,444.77201 314.18465,490.50238 303.45686,534.78351 C 269.8116,555.70792 267.45319,510.26453 271.11797,487.87885 C 272.91786,458.43851 279.8073,429.65527 286.31949,400.98556 C 288.31716,397.74595 292.52433,397.64886 294.45576,401.04054 z M 180.49295,506.64717 C 207.67798,522.26301 246.52699,528.98157 268.55252,545.52857 C 242.42325,558.17886 217.83828,546.5235 198.29377,527.97033 C 194.69189,524.69972 159.29181,498.82287 180.49295,506.64717 z M 404.51536,507.63672 C 390.01309,525.27528 368.18947,538.74082 347.86304,550.23549 C 335.91524,557.74156 287.21939,542.66729 323.67227,537.66117 C 350.78566,529.02491 379.06974,516.04027 404.51536,507.63672 z M 292.69657,548.86784 C 304.93678,562.57073 267.95665,553.48912 288.01486,547.90629 L 290.34982,548.11682 L 292.69657,548.86784 z
diff --git a/src/toys/sweep.cpp b/src/toys/sweep.cpp
new file mode 100644
index 0000000..4f26b81
--- /dev/null
+++ b/src/toys/sweep.cpp
@@ -0,0 +1,89 @@
+#include <2geom/sweep-bounds.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using namespace Geom;
+
+class Sweep: public Toy {
+public:
+ PointSetHandle hand;
+ unsigned count_a, count_b;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ std::vector<Rect> rects_a, rects_b;
+ cairo_set_source_rgb(cr, 0,0,0);
+
+ for(unsigned i = 0; i < count_a; i++)
+ rects_a.emplace_back(hand.pts[i*2], hand.pts[i*2+1]);
+
+ for(unsigned i = 0; i < count_b; i++)
+ rects_b.emplace_back(hand.pts[i*2 + count_a*2], hand.pts[i*2+1 + count_a*2]);
+
+ {
+ std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a);
+ cairo_set_line_width(cr,0.5);
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for(unsigned i = 0; i < res.size(); i++) {
+ for(unsigned j = 0; j < res[i].size(); j++) {
+ draw_line_seg(cr, rects_a[i].midpoint(), rects_a[res[i][j]].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ cairo_restore(cr);
+ }{
+ std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a, rects_b);
+ cairo_set_line_width(cr,0.5);
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 0.5, 0, 0.5);
+ for(unsigned i = 0; i < res.size(); i++) {
+ for(unsigned j = 0; j < res[i].size(); j++) {
+ draw_line_seg(cr, rects_a[i].midpoint(), rects_b[res[i][j]].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ cairo_restore(cr);
+ }
+ cairo_set_line_width(cr,3);
+ cairo_set_source_rgba(cr,1,0,0,1);
+ for(unsigned i = 0; i < count_a; i++)
+ cairo_rectangle(cr, rects_a[i].left(), rects_a[i].top(), rects_a[i].width(), rects_a[i].height());
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba(cr,0,0,1,1);
+ for(unsigned i = 0; i < count_b; i++)
+ cairo_rectangle(cr, rects_b[i].left(), rects_b[i].top(), rects_b[i].width(), rects_b[i].height());
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+ bool should_draw_numbers() override { return false; }
+ public:
+ Sweep () {
+ count_a = 20;
+ count_b = 10;
+ for(unsigned i = 0; i < (count_a + count_b); i++) {
+ Point dim(uniform() * 90 + 10, uniform() * 90 + 10),
+ pos(uniform() * 500 + 50, uniform() * 500 + 50);
+ hand.pts.push_back(pos - dim/2);
+ hand.pts.push_back(pos + dim/2);
+ }
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sweep());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/sweeper-toy.cpp b/src/toys/sweeper-toy.cpp
new file mode 100644
index 0000000..5ca28db
--- /dev/null
+++ b/src/toys/sweeper-toy.cpp
@@ -0,0 +1,170 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+
+
+#include <cstdlib>
+#include <set>
+#include <vector>
+#include <algorithm>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/ord.h>
+
+using namespace Geom;
+using namespace std;
+
+#include "sweeper.cpp"
+
+double exp_rescale(double x){ return pow(10, x);}
+std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));}
+
+
+class SweeperToy: public Toy {
+ int nb_paths;
+ int nb_curves_per_path;
+ int degree;
+
+ std::vector<PointSetHandle> paths_handles;
+ std::vector<Slider> sliders;
+ Sweeper sweeper;
+
+ void drawTile( cairo_t *cr, unsigned idx , unsigned line_width=1){
+ if (idx>=sweeper.tiles_data.size()) return;
+ Rect box;
+ box = sweeper.tiles_data[idx].fbox;
+ box[X].expandBy(1);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0., .5);
+ cairo_set_line_width (cr, line_width);
+ cairo_stroke(cr);
+ box = sweeper.tiles_data[idx].tbox;
+ box[Y].expandBy(1);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 0., 0., 1., .5);
+ cairo_set_line_width (cr, line_width);
+ cairo_stroke(cr);
+
+ Sweeper::Tile tile = sweeper.tiles_data[idx];
+ D2<SBasis> p = sweeper.paths[tile.path][tile.curve].toSBasis();
+ Interval dom = Interval(tile.f,tile.t);
+ cairo_set_source_rgba (cr, 0., 1., .5, .8);
+ p = portion(p, dom);
+ cairo_d2_sb(cr, p);
+ cairo_set_line_width (cr, line_width);
+ cairo_stroke(cr);
+ }
+
+ void drawTiles( cairo_t *cr ){
+ for (unsigned i=0; i<sweeper.tiles_data.size(); i++){
+ drawTile( cr, i );
+ }
+
+// for (unsigned i=0; i<sweeper.vtxboxes.size(); i++){
+// cairo_rectangle(cr, sweeper.vtxboxes[i]);
+// cairo_set_source_rgba (cr, 0., 0., 0, 1);
+// cairo_set_line_width (cr, 1);
+// cairo_stroke(cr);
+// }
+ }
+
+ void enlightTile( cairo_t *cr, unsigned idx){
+ drawTile(cr, idx, 4);
+ }
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+
+ PathVector paths;
+ for (int i = 0; i < nb_paths; i++){
+ paths_handles[i].pts.back()=paths_handles[i].pts.front();
+ paths.push_back(Path(paths_handles[i].pts[0]));
+ for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){
+ D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree);
+ paths[i].append(c);
+ }
+ paths[i].close();
+ }
+
+ //cairo_path(cr, paths);
+ cairo_set_source_rgba (cr, 0., 0., 0, 1);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+
+ double tol = exp_rescale(sliders[3].value());
+ Rect tolbytol( Point(50,110), Point(50,110) );
+ tolbytol.expandBy( tol );
+ cairo_rectangle(cr, tolbytol);
+ cairo_stroke(cr);
+
+ sweeper = Sweeper(paths,X, tol);
+ unsigned idx = (unsigned)(sliders[0].value()*(sweeper.tiles_data.size()-1));
+ drawTiles(cr);
+ enlightTile(cr, idx);
+
+ Toy::draw(cr, notify, width, height, save, timer_stream);
+ }
+
+ public:
+ SweeperToy(int paths, int curves_in_path, int degree) :
+ nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) {
+ for (int i = 0; i < nb_paths; i++){
+ paths_handles.emplace_back();
+ }
+ for(int i = 0; i < nb_paths; i++){
+ for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){
+ paths_handles[i].push_back(uniform()*400, 100+ uniform()*300);
+ }
+ handles.push_back(&paths_handles[i]);
+ }
+ sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser");
+ sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser");
+ sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser");
+ sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser");
+ handles.push_back(&(sliders[0]));
+ handles.push_back(&(sliders[1]));
+ handles.push_back(&(sliders[2]));
+ handles.push_back(&(sliders[3]));
+ sliders[0].geometry(Point(50, 20), 250);
+ sliders[1].geometry(Point(50, 50), 250);
+ sliders[2].geometry(Point(50, 80), 250);
+ sliders[3].geometry(Point(50, 110), 250);
+ sliders[3].formatter(&exp_formatter);
+ }
+
+ void first_time(int /*argc*/, char** /*argv*/) override {
+
+ }
+};
+
+int main(int argc, char **argv) {
+ unsigned paths=10;
+ unsigned curves_in_path=3;
+ unsigned degree=1;
+ if(argc > 3)
+ sscanf(argv[3], "%d", &degree);
+ if(argc > 2)
+ sscanf(argv[2], "%d", &curves_in_path);
+ if(argc > 1)
+ sscanf(argv[1], "%d", &paths);
+ init(argc, argv, new SweeperToy(paths, curves_in_path, degree));
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/sweeper.cpp b/src/toys/sweeper.cpp
new file mode 100644
index 0000000..7dae586
--- /dev/null
+++ b/src/toys/sweeper.cpp
@@ -0,0 +1,1135 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+
+#include <cstdlib>
+#include <cstdio>
+#include <set>
+#include <vector>
+#include <algorithm>
+
+#include <limits.h>
+#define NULL_IDX UINT_MAX
+
+#include <2geom/orphan-code/intersection-by-smashing.h>
+#include "../2geom/orphan-code/intersection-by-smashing.cpp"
+
+using namespace Geom;
+using namespace std;
+
+
+/*
+The sweeper class takes a PathVector as input and generates "events" to let clients construct the relevant graph.
+
+The basic strategy is the following:
+The path is split into "tiles": a tile consists in 2 boxes related by a (monotonic) curve.
+
+The tiles are created at the very beginning, using a sweep, but *no care* is taken to topology
+information at this step! All the boxes of all the tiles are then enlarged so that they are
+either equal or disjoint.
+[TODO: we should look for curves traversing boxes, split them and repeat the process...]
+
+The sweeper maintains a virtual sweepline, that is the limit of the "known area". The tiles can have 2 states:
+open if they have one end in the known area, and one in the unknown, closed otherwise.
+[TODO: open/close should belong to tiles pointers, not tiles...]
+
+The sorted list of open tiles intersecting the sweep line is called the "context".
+*!*WARNING*!*: because the sweep line is not straight, closed tiles can still be in the context!!
+they can only be removed once the end of the last box is reached.
+
+The events are changes in the context when the sweep line crosses boxes.
+They are obtained by sorting the tiles according to one or the other of theire end boxes depending
+on the open/close state.
+
+A "big" event happens when the sweep line reaches a new 'box'. After such a "big" event, the sweep
+line goes round the new box along it's 3 other sides.
+N.B.: in an ideal world, all tiles ending at one box would be on one side, all the tiles starting
+there on the other. Unfortunately, because we have boxes as vertices, things are not that nice:
+open/closed tiles can appear in any order around a vertex, even in the monotonic case(!). Morover,
+our fat vertices have a non zero "duration", during which many things can happen: this is why we
+have to keep closed edges in the context until both ends of theire boxes are reached...
+
+
+To keep things uniform, such "big" events are split into elementary ones: opening/closing of a single
+edge. One such event is generated for each tile around the current 'box', in CCW order (geometrically,
+the sweepline is deformed in a neighborhood of the box to go round it for a certain amount, enter the
+box and come back inside the box; the piece inside the box is a "virtual edge" that is not added for
+good but that we keep track of). The event knows if it's the last one in such a sequence, so that the
+client knows when to do the additional work required to "close" the vertex construction. Hmmm. It's
+hard to explain the moves without a drawing here...(see sweep.svg in the doc dir). There are
+
+*Closings: insert a new the relevant tile in the context with a "exit" flag.
+
+*Openings: insert a new the relevant tile in the context with a "entry" flag.
+
+At the end of a box, the relevant exit/entries are purged from the context.
+
+
+N.B. I doubt we can do boolops without building the full graph, i.e. having different clients to obtain
+different outputs. So splitting sweeper/grpah builder is maybe not so relevant w/r to functionality
+(only code organization).
+*/
+
+
+//TODO: decline intersections algorithms for each kind of curves...
+//TODO: write an intersector that can work on sub domains.
+//TODO: factor computation of derivative and the like out.
+std::vector<SmashIntersection> monotonic_smash_intersect( Curve const &a, Interval a_dom,
+ Curve const &b, Interval b_dom, double tol){
+ std::vector<SmashIntersection> result;
+ D2<SBasis> asb = a.toSBasis();
+ asb = portion( asb, a_dom );
+ D2<SBasis> bsb = b.toSBasis();
+ bsb = portion( bsb, b_dom );
+ result = monotonic_smash_intersect(asb, bsb, tol );
+ for (auto & i : result){
+ i.times[X] *= a_dom.extent();
+ i.times[X] += a_dom.min();
+ i.times[Y] *= b_dom.extent();
+ i.times[Y] += b_dom.min();
+ }
+ return result;
+}
+
+
+
+class Sweeper{
+public:
+
+ //---------------------------
+ // utils...
+ //---------------------------
+
+ //near predicate utilized in process_splits
+ template<typename T>
+ struct NearPredicate {
+ double tol;
+ NearPredicate(double eps):tol(eps){}
+ NearPredicate(){tol = EPSILON;}//???
+ bool operator()(T x, T y) { return are_near(x, y, tol); } };
+
+ // ensures that f and t are elements of a vector, sorts and uniqueifies
+ // also asserts that no values fall outside of f and t
+ // if f is greater than t, the sort is in reverse
+ void process_splits(std::vector<double> &splits, double f, double t, double tol=EPSILON) {
+ //splits.push_back(f);
+ //splits.push_back(t);
+ std::sort(splits.begin(), splits.end());
+ std::vector<double>::iterator end = std::unique(splits.begin(), splits.end(), NearPredicate<double>(tol));
+ splits.resize(end - splits.begin());
+
+ //remove any splits which fall outside t / f
+ while(!splits.empty() && splits.front() < f+tol) splits.erase(splits.begin());
+ splits.insert(splits.begin(), f);
+ //splits[0] = f;
+ while(!splits.empty() && splits.back() > t-tol) splits.erase(splits.end() - 1);
+ splits.push_back(t);
+ //splits.back() = t;
+ }
+
+ struct IntersectionMinTimeOrder {
+ unsigned which;
+ IntersectionMinTimeOrder (unsigned idx) : which(idx) {}
+ bool operator()(SmashIntersection const &a, SmashIntersection const &b) const {
+ return a.times[which].min() < b.times[which].min();
+ }
+ };
+
+ // ensures that f and t are elements of a vector, sorts and uniqueifies
+ // also asserts that no values fall outside of f and t
+ // if f is greater than t, the sort is in reverse
+ std::vector<std::pair<Interval, Rect> >
+ process_intersections(std::vector<SmashIntersection> &inters, unsigned which, unsigned tileidx) {
+ std::vector<std::pair<Interval, Rect> > result;
+ std::pair<Interval, Rect> apair;
+ Interval dom ( tiles_data[tileidx].f, tiles_data[tileidx].t );
+ apair.first = Interval( dom.min() );
+ apair.second = tiles_data[tileidx].fbox;
+ result.push_back( apair );
+
+ std::sort(inters.begin(), inters.end(), IntersectionMinTimeOrder(which) );
+ for (auto & inter : inters){
+ if ( !inter.times[which].intersects( dom ) )//this should never happen.
+ continue;
+ if ( result.back().first.intersects( inter.times[which] ) ){
+ result.back().first.unionWith( inter.times[which] );
+ result.back().second.unionWith( inter.bbox );
+ }else{
+ apair.first = inter.times[which];
+ apair.second = inter.bbox;
+ result.push_back( apair );
+ }
+ }
+ apair.first = Interval( dom.max() );
+ apair.second = tiles_data[tileidx].tbox;
+ if ( result.size() > 1 && result.back().first.intersects( apair.first ) ){
+ result.back().first.unionWith( apair.first );
+ result.back().second.unionWith( apair.second );
+ }else{
+ result.push_back( apair );
+ }
+ return result;
+ }
+
+
+ //---------------------------
+ // Tiles.
+ //---------------------------
+
+ //A tile is a "light edge": just two boxes, joint by a curve.
+ //it is open iff intersected by the sweepline.
+ class Tile{
+ public:
+ unsigned path;
+ unsigned curve;
+ double f;
+ double t;
+ Rect fbox, tbox;
+ bool reversed;//with respect to sweep direction. Flip f/t instead?
+ bool open;//means sweepline currently cuts it (i.e. one end in the known area, the other in the unknown).
+ int state;//-1: both ends in unknown area, 0:one end in each, 1: both in known area.
+ //Warning: we can not delete a tile immediately when it's past(=closed again), only when the end of it's tbox is!.
+ Rect bbox(){Rect b = fbox; b.unionWith(tbox); return b;}
+ Point min(){return ( bbox().min() ); }
+ Point max(){return ( bbox().max() ); }
+// Rect cur_box() const {return ((open)^(reversed) ) ? tbox : fbox; }
+ Rect cur_box() const { return ((state>=0)^(reversed) ) ? tbox : fbox; }
+ Rect first_box() const {return ( reversed ) ? tbox : fbox; }
+ Rect last_box() const {return ( reversed ) ? fbox : tbox; }
+ };
+
+ D2<SBasis> tileToSB(Tile const &tile){
+ //TODO: don't convert each time!!!!!!
+ assert( tile.path < paths.size() );
+ assert( tile.curve < paths[tile.path].size() );
+ D2<SBasis> c = paths[tile.path][tile.curve].toSBasis();
+ c = portion( c, Interval( tile.f, tile.t ) );
+ return c;
+ }
+
+ //SweepOrder for Rects or Tiles.
+ class SweepOrder{
+ public:
+ Dim2 dim;
+ SweepOrder(Dim2 d) : dim(d) {}
+ bool operator()(const Rect &a, const Rect &b) const {
+ return Point::LexLessRt(dim)(a.min(), b.min());
+ }
+ bool operator()(const Tile &a, const Tile &b) const {
+ return Point::LexLessRt(dim)(a.cur_box().min(), b.cur_box().min());
+ }
+ };
+
+ class PtrSweepOrder{
+ public:
+ Dim2 dim;
+ std::vector<Tile>::iterator const begin;
+ PtrSweepOrder(std::vector<Tile>::iterator const beg, Dim2 d) : dim(d), begin(beg){}
+ bool operator()(const unsigned a, const unsigned b) const {
+ return Point::LexLessRt(dim)((begin+a)->cur_box().min(), (begin+b)->cur_box().min());
+ }
+ };
+
+
+ //---------------------------
+ // Vertices.
+ //---------------------------
+
+ //A ray is nothing but an edge ending or starting at a given vertex, + some info about when/where it exited a "separating" box;
+ struct Ray{
+ public:
+ unsigned tile;
+ bool centrifuge;//true if the intrinsic orientation of curve points away from the vertex.
+ //exit info:
+ unsigned exit_side;//0:y=min; 1:x=max; 2:y=max; 3:x=min.
+ double exit_place; //x or y value on the exit line.
+ double exit_time; //exit time on curve.
+ Ray(){tile = NULL_IDX; exit_side = 4;}
+ Ray(unsigned tile_idx, unsigned s, double p, double t){
+ tile = tile_idx;
+ exit_side =s;
+ exit_place = p;
+ exit_time = t;
+ }
+ Ray(unsigned tile_idx, bool outward){
+ tile = tile_idx;
+ exit_side = 4;
+ centrifuge = outward;
+ exit_time = (centrifuge) ? 2 : -1 ;
+ }
+ void setExitInfo( unsigned side, double place, double time){
+ exit_side = side;
+ exit_place = place;
+ exit_time = time;
+ }
+ };
+
+ class FatVertex : public Rect{
+ public:
+ std::vector<Ray> rays;
+ FatVertex(const Rect &r, unsigned const tile, bool centrifuge) : Rect(r){
+ rays.push_back( Ray(tile, centrifuge) );
+ }
+ FatVertex(Rect r) : Rect(r){}
+ FatVertex() : Rect(){}
+ void erase(unsigned from, unsigned to){
+ unsigned size = to-from;
+ from = from % rays.size();
+ to = from + size;
+
+ if (to >= rays.size() ){
+ to = to % rays.size();
+ rays.erase( rays.begin()+from, rays.end() );
+ rays.erase( rays.begin(), rays.begin()+to );
+ }else{
+ rays.erase( rays.begin()+from, rays.begin()+to );
+ }
+
+ }
+ };
+
+ //---------------------------
+ // Context related stuff.
+ //---------------------------
+
+ class Event{
+ public:
+ bool opening;//true means an edge is added, otherwise an edge is removed from context.
+ unsigned tile;//which tile to open/close.
+ unsigned insert_at;//where to insert the next tile in the context.
+ //unsigned erase_at;//idx of the tile to close in the context. = context.find(tile).
+ bool to_be_continued;
+ bool empty(){
+ return tile==NULL_IDX;
+ }
+ Event(){
+ opening = false;
+ insert_at = 0;
+ //erase_at = 0;
+ tile = NULL_IDX;
+ to_be_continued = false;
+ }
+ };
+
+ void printEvent(Event const &e){
+ std::printf("Event: ");
+ std::printf("%s, ", e.opening?"opening":"closing");
+ std::printf("insert_at:%u, ", e.insert_at);
+ //std::printf("erase_at:%u, ", e.erase_at);
+ std::printf("tile:%u.\n", e.tile);
+ }
+
+ class Context : public std::vector<std::pair<unsigned,bool> >{//first = tile, second = true if it's a birth (+).
+ public:
+ Point last_pos;
+ FatVertex pending_vertex;
+ Event pending_event;
+
+ unsigned find(unsigned const tile, bool positive_only=false){
+ for (unsigned i=0; i<size(); i++){
+ if ( (*this)[i].first == tile ){
+ if ( (*this)[i].second || !positive_only ) return i;
+ }
+ }
+ return (*this).size();
+ }
+ };
+ void printContext(){
+ std::printf("context:[");
+ for (unsigned i=0; i<context.size(); i++){
+ unsigned tile = context[i].first;
+ assert( tile<tiles_data.size() );
+ std::printf(" %s%u%s", (tiles_data[ tile ].reversed)?"-":"+", tile, (context[i].second)?"o":"c");
+// assert( context[i].second || !tiles_data[ tile ].open);
+ assert( context[i].second || tiles_data[ tile ].state==1);
+ }
+ std::printf("]\n");
+ }
+
+
+
+ //----
+ //This is the heart of it all!! Take particular care to non linear sweep line...
+ //----
+ //Given a point on the sweep line, (supposed to be the min() of a vertex not yet connected to the already known part),
+ //find the first edge "after" it in the context. Pretty tricky atm :-(
+ //TODO: implement this as a lower_bound (?).
+
+ unsigned contextRanking(Point const &pt){
+
+// std::printf("contextRanking:------------------------------------\n");
+
+ unsigned rank = context.size();
+ std::vector<unsigned> unmatched_closed_tiles = std::vector<unsigned>();
+
+// std::printf("Scan context.\n");
+
+ for (unsigned i=0; i<context.size(); i++){
+
+ unsigned tile_idx = context[i].first;
+ assert( tile_idx < tiles_data.size() );
+// std::printf("testing %u (e=%u),", i, tile_idx);
+
+ Tile tile = tiles_data[tile_idx];
+ assert( tile.state >= 0 );
+
+ //if the tile is open (i.e. not both ends in the known area) and point is below/above the tile's bbox:
+ if ( tile.state == 0 ){
+// std::printf("opened tile, ");
+ if (pt[1-dim] < tile.min()[1-dim] ) {
+// printContext();
+// std::printf("below bbox %u!\n", i);
+ rank = i;
+ break;
+ }
+ if (pt[1-dim] > tile.max()[1-dim] ){
+// std::printf("above bbox %u!\n", i);
+ continue;
+ }
+
+ //TODO: don't convert each time!!!!!!
+ D2<SBasis> c = tileToSB( tile );
+
+ std::vector<double> times = roots(c[dim]-pt[dim]);
+ if (times.size()==0){
+ assert( tile.first_box()[dim].contains(pt[dim]) );
+ if ( pt[1-dim] < tile.first_box()[1-dim].min() ){
+// std::printf("open+hit box %u!\n", i);
+ rank = i;
+ break;
+ }else{
+ continue;
+ }
+ }
+ if ( pt[1-dim] < c[1-dim](times.front()) ){
+// std::printf("open+hit curve %u!\n", i);
+ rank = i;
+ break;
+ }
+ }
+
+// std::printf("closed tile, ");
+
+
+ //At this point, the tile is closed (i.e. both ends are in the known area)
+ //Such tiles do 'nested parens' like travels in the unknown area.
+ //We are interested in the second occurrence only (to give a chance to open tiles to exist in between).
+ if ( unmatched_closed_tiles.size()==0 || tile_idx != unmatched_closed_tiles.back() ){
+ unmatched_closed_tiles.push_back( tile_idx );
+// std::printf("open paren %u\n",tile_idx);
+ continue;
+ }
+ unmatched_closed_tiles.pop_back();
+
+// std::printf("close paren, ");
+
+ if ( !tile.bbox().contains( pt ) ){
+ continue;
+ }
+
+// std::printf("in bbox, ");
+
+ //At least one of fbox[dim], tbox[dim] has to contain the pt[dim]: assert it?
+
+ //Find intersection with the hline(vline if dim=Y) through the point
+ double hit_place;
+ //TODO: don't convert each time!!!!!!
+ D2<SBasis> c = tileToSB( tile );
+ std::vector<double> times = roots(c[1-dim]-pt[1-dim]);
+ if ( times.size()>0 ){
+// std::printf("hit curve,");
+ hit_place = c[dim](times.front());
+ }else{
+// std::printf("hit box, ");
+ //if there was no intersection, the line went through the first_box
+ assert( tile.first_box()[1-dim].contains(pt[1-dim]) );
+ continue;
+ }
+
+ if ( pt[dim] > hit_place ){
+// std::printf("wrong side, ");
+ continue;
+ }
+// std::printf("good side, ");
+ rank = i;
+ break;
+ }
+
+// std::printf("rank %u.\n", rank);
+// printContext();
+ assert( rank<=tiles_data.size() );
+ return rank;
+ }
+
+ //TODO: optimize this.
+ //it's done the slow way for debugging purpose...
+ void purgeDeadTiles(){
+ //std::printf("purge ");
+ //printContext();
+ for (unsigned i=0; i<context.size(); i++){
+ assert( context[i].first<tiles_data.size() );
+ Tile tile = tiles_data[context[i].first];
+ if (tile.state==1 && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){
+// if (!tile.open && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){
+ unsigned j;
+ for (j=i+1; j<context.size() && context[j].first != context[i].first; j++){}
+ assert ( j < context.size() );
+ if ( context[j].first == context[i].first){
+ assert ( context[j].second == !context[i].second );
+ context.erase(context.begin()+j);
+ context.erase(context.begin()+i);
+// printContext();
+ i--;
+ }
+ }
+ }
+ return;
+ }
+
+ void applyEvent(Event event){
+// std::printf("Apply event : ");
+ if(event.empty()){
+// std::printf("empty event!\n");
+ return;
+ }
+
+// printEvent(event);
+// std::printf(" old ");
+// printContext();
+
+ assert ( context.begin() + event.insert_at <= context.end() );
+
+ if (!event.opening){
+// unsigned idx = event.erase_at;
+// assert( idx == context.find(event.tile) );
+// assert( context[idx].first == event.tile);
+ tiles_data[event.tile].open = false;
+ tiles_data[event.tile].state = 1;
+ //context.erase(context.begin()+idx);
+ unsigned idx = event.insert_at;
+ context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, false) );
+ }else{
+ unsigned idx = event.insert_at;
+ tiles_data[event.tile].open = true;
+ tiles_data[event.tile].state = 0;
+ context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, true) );
+ sortTiles();
+ }
+ context.last_pos = context.pending_vertex.min();
+ context.last_pos[1-dim] = context.pending_vertex.max()[1-dim];
+
+// std::printf(" new ");
+// printContext();
+// std::printf("\n");
+ //context.pending_event = Event();is this a good idea?
+ }
+
+
+
+ //---------------------------
+ // Sweeper.
+ //---------------------------
+
+ PathVector paths;
+ std::vector<Tile> tiles_data;
+ std::vector<unsigned> tiles;
+ std::vector<Rect> vtxboxes;
+ Context context;
+ double tol;
+ Dim2 dim;
+
+
+ //-------------------------------
+ //-- Tiles preparation.
+ //-------------------------------
+
+ //split input paths into monotonic pieces...
+ void createMonotonicTiles(){
+ for ( unsigned i=0; i<paths.size(); i++){
+ for ( unsigned j=0; j<paths[i].size(); j++){
+ //find the points where slope is 0°, 45°, 90°, 135°...
+ D2<SBasis> deriv = derivative( paths[i][j].toSBasis() );
+ std::vector<double> splits0 = roots( deriv[X] );
+ std::vector<double> splits90 = roots( deriv[Y] );
+ std::vector<double> splits45 = roots( deriv[X]- deriv[Y] );
+ std::vector<double> splits135 = roots( deriv[X] + deriv[Y] );
+ std::vector<double> splits;
+ splits.insert(splits.begin(), splits0.begin(), splits0.end() );
+ splits.insert(splits.begin(), splits90.begin(), splits90.end() );
+ splits.insert(splits.begin(), splits45.begin(), splits45.end() );
+ splits.insert(splits.begin(), splits135.begin(), splits135.end() );
+ process_splits(splits,0,1);
+
+ for(unsigned k = 1; k < splits.size(); k++){
+ Tile tile;
+ tile.path = i;
+ tile.curve = j;
+ tile.f = splits[k-1];
+ tile.t = splits[k];
+ //TODO: use meaningful tolerance here!!
+ Point fp = paths[i][j].pointAt(tile.f);
+ Point tp = paths[i][j].pointAt(tile.t);
+ tile.fbox = Rect(fp, fp );
+ tile.tbox = Rect(tp, tp );
+ tile.open = false;
+ tile.state = -1;
+ tile.reversed = Point::LexLessRt(dim)(tp, fp);
+
+ tiles_data.push_back(tile);
+ }
+ }
+ }
+ std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
+ }
+
+ void splitTile(unsigned i, double t, double tolerance=0, bool sort = true){
+ assert( i<tiles_data.size() );
+ Tile newtile = tiles_data[i];
+ assert( newtile.f < t && t < newtile.t );
+ newtile.f = t;
+ //newtile.fbox = fatPoint(paths[newtile.path][newtile.curve].pointAt(t), tolerance );
+ Point p = paths[newtile.path][newtile.curve].pointAt(t);
+ newtile.fbox = Rect(p, p);
+ newtile.fbox.expandBy( tolerance );
+ tiles_data[i].tbox = newtile.fbox;
+ tiles_data[i].t = t;
+ tiles_data.insert(tiles_data.begin()+i+1, newtile);
+ if (sort)
+ std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
+ }
+ void splitTile(unsigned i, SmashIntersection inter, unsigned which,bool sort = true){
+ double t = inter.times[which].middle();
+ assert( i<tiles_data.size() );
+ Tile newtile = tiles_data[i];
+ assert( newtile.f < t && t < newtile.t );
+ newtile.f = t;
+ newtile.fbox = inter.bbox;
+ tiles_data[i].tbox = newtile.fbox;
+ tiles_data[i].t = t;
+ tiles_data.insert(tiles_data.begin()+i+1, newtile);
+ if (sort)
+ std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
+ }
+#if 0
+ void splitTile(unsigned i, std::vector<double> const &times, double tolerance=0, bool sort = true){
+ if ( times.size()<3 ) return;
+ assert( i<tiles_data.size() );
+ std::vector<Tile> pieces ( times.size()-2, tiles_data[i] );
+ Rect prevbox = tiles_data[i].fbox;
+ for (unsigned k=0; k < times.size()-2; k++){
+ pieces[k].f = times[k];
+ pieces[k].t = times[k+1];
+ pieces[k].fbox = prevbox;
+ //TODO: use relevant precision here.
+ prevbox = fatPoint(paths[tiles_data[i].path][tiles_data[i].curve].pointAt(times[k+1]), tolerance );
+ pieces[k].tbox = prevbox;
+ }
+ tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end() );
+ unsigned newi = i + times.size()-2;
+ assert( newi<tiles_data.size() );
+ assert( newi>=1 );
+ tiles_data[newi].f = tiles_data[newi-1].t;
+ tiles_data[newi].fbox = tiles_data[newi-1].tbox;
+
+ if (sort)
+ std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) );
+ }
+#else
+ void splitTile(unsigned i, std::vector<std::pair<Interval,Rect> > const &cuts, bool sort = true){
+ assert ( cuts.size() >= 2 );
+ assert( i<tiles_data.size() );
+ std::vector<Tile> pieces ( cuts.size()-1, tiles_data[i] );
+ for (unsigned k=1; k+1 < cuts.size(); k++){
+ pieces[k-1].t = cuts[k].first.middle();
+ pieces[k ].f = cuts[k].first.middle();
+ pieces[k-1].tbox = cuts[k].second;
+ pieces[k ].fbox = cuts[k].second;
+ }
+ pieces.front().fbox.unionWith( cuts[0].second );
+ pieces.back().tbox.unionWith( cuts.back().second );
+
+ tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end()-1 );
+ unsigned newi = i + cuts.size()-2;
+ assert( newi < tiles_data.size() );
+ tiles_data[newi] = pieces.back();
+
+ if (sort)
+ std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) );
+ }
+#endif
+
+ //TODO: maybe not optimal. For a fully optimized sweep, it would be nice to have
+ //an efficient way to way find *only the first* intersection (in sweep direction)...
+ void splitIntersectingTiles(){
+ //make sure it is sorted, but should be ok. (remove sorting at the end of monotonic tiles creation?
+ std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
+
+// std::printf("\nFind intersections: tiles_data.size():%u\n", tiles_data.size() );
+
+ for (unsigned i=0; i+1<tiles_data.size(); i++){
+ //std::printf("\ni=%u (%u([%f,%f]))\n", i, tiles_data[i].curve, tiles_data[i].f, tiles_data[i].t );
+ std::vector<SmashIntersection> inters_on_i;
+ for (unsigned j=i+1; j<tiles_data.size(); j++){
+ //std::printf(" j=%u (%u)\n", j,tiles_data[j].curve );
+ if ( Point::LexLessRt(dim)(tiles_data[i].max(), tiles_data[j].min()) ) break;
+
+ unsigned pi = tiles_data[i].path;
+ unsigned ci = tiles_data[i].curve;
+ unsigned pj = tiles_data[j].path;
+ unsigned cj = tiles_data[j].curve;
+ std::vector<SmashIntersection> intersections;
+
+ intersections = monotonic_smash_intersect(paths[pi][ci], Interval(tiles_data[i].f, tiles_data[i].t),
+ paths[pj][cj], Interval(tiles_data[j].f, tiles_data[j].t), tol );
+ inters_on_i.insert( inters_on_i.end(), intersections.begin(), intersections.end() );
+ std::vector<std::pair<Interval, Rect> > cuts = process_intersections(intersections, 1, j);
+
+// std::printf(" >|%u/%u|=%u. times_j:%u", i, j, crossings.size(), times_j.size() );
+
+ splitTile(j, cuts, false);
+ j += cuts.size()-2;
+ }
+
+ //process_splits(times_i, tiles_data[i].f, tiles_data[i].t);
+ //assert(times_i.size()>=2);
+ //splitTile(i, times_i, tol, false);
+ //i+=times_i.size()-2;
+ std::vector<std::pair<Interval, Rect> > cuts_on_i = process_intersections(inters_on_i, 0, i);
+ splitTile(i, cuts_on_i, false);
+ i += cuts_on_i.size()-2;
+ //std::printf("new i:%u, tiles_data: %u\n",i ,tiles_data.size());
+ //std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
+ std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
+ }
+ //this last sorting should be useless!!
+ std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
+ }
+
+ void sortTiles(){
+ std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) );
+ }
+
+
+ //-------------------------------
+ //-- Vertices boxes cookup.
+ //-------------------------------
+
+ void fuseInsert(const Rect &b, std::vector<Rect> &boxes, Dim2 dim){
+ //TODO: this can be optimized...
+ for (unsigned i=0; i<boxes.size(); i++){
+ if ( Point::LexLessRt(dim)( b.max(), boxes[i].min() ) ) break;
+ if ( b.intersects( boxes[i] ) ){
+ Rect bigb = b;
+ bigb.unionWith( boxes[i] );
+ boxes.erase( boxes.begin()+i );
+ fuseInsert( bigb, boxes, dim);
+ return;
+ }
+ }
+ std::vector<Rect>::iterator pos = std::lower_bound(boxes.begin(), boxes.end(), b, SweepOrder(dim) );
+ boxes.insert( pos, b );
+ }
+
+ //debug only!!
+ bool isContained(Rect b, std::vector<Rect> const &boxes ){
+ for (const auto & boxe : boxes){
+ if ( boxe.contains(b) ) return true;
+ }
+ return false;
+ }
+
+ //Collect vertex boxes. Fuse overlapping ones.
+ //NB: enlarging a vertex may create intersection with already scanned ones...
+ std::vector<Rect> collectBoxes(){
+ std::vector<Rect> ret;
+ for (auto & i : tiles_data){
+ fuseInsert(i.fbox, ret, dim);
+ fuseInsert(i.tbox, ret, dim);
+ }
+ return ret;
+ }
+
+ //enlarge tiles ends to match the vertices bounding boxes.
+ //remove edges fully contained in one vertex bbox.
+ void enlargeTilesEnds(const std::vector<Rect> &boxes ){
+ for (unsigned i=0; i<tiles_data.size(); i++){
+ std::vector<Rect>::const_iterator f_it;
+ f_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].fbox, SweepOrder(dim) );
+ if ( f_it==boxes.end() ) f_it--;
+ while (!(*f_it).contains(tiles_data[i].fbox) && f_it != boxes.begin()){
+ f_it--;
+ }
+ assert( (*f_it).contains(tiles_data[i].fbox) );
+ tiles_data[i].fbox = *f_it;
+
+ std::vector<Rect>::const_iterator t_it;
+ t_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].tbox, SweepOrder(dim) );
+ if ( t_it==boxes.end() ) t_it--;
+ while (!(*t_it).contains(tiles_data[i].tbox) && t_it != boxes.begin()){
+ t_it--;
+ }
+ assert( (*t_it).contains(tiles_data[i].tbox) );
+ tiles_data[i].tbox = *t_it;
+
+ //NB: enlarging the ends may swapp their sweep order!!!
+ tiles_data[i].reversed = Point::LexLessRt(dim)( tiles_data[i].tbox.min(), tiles_data[i].fbox.min());
+
+ if ( f_it==t_it ){
+ tiles_data.erase(tiles_data.begin()+i);
+ i-=1;
+ }
+ }
+ }
+
+ //Make sure tiles stop at vertices. Split them if needed.
+ //Returns true if at least one tile was split.
+ bool splitTilesThroughFatPoints(std::vector<Rect> &boxes ){
+
+ std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) );
+ std::sort(boxes.begin(), boxes.end(), SweepOrder(dim) );
+
+ bool result = false;
+ for (unsigned i=0; i<tiles_data.size(); i++){
+ for (auto & boxe : boxes){
+ if ( Point::LexLessRt(dim)( tiles_data[i].max(), boxe.min()) ) break;
+ if ( Point::LexLessRt(dim)( boxe.max(), tiles_data[i].min()) ) continue;
+ if ( !boxe.intersects( tiles_data[i].bbox() ) ) continue;
+ if ( tiles_data[i].fbox.intersects( boxe ) ) continue;
+ if ( tiles_data[i].tbox.intersects( boxe ) ) continue;
+
+ //at this point box[k] intersects the curve bbox away from the fbox and tbox.
+
+ D2<SBasis> c = tileToSB( tiles_data[i] );
+//----------> use level-set!!
+ for (unsigned corner=0; corner<4; corner++){
+ unsigned D = corner % 2;
+ double val = boxe.corner(corner)[D];
+ std::vector<double> times = roots( c[D] - val );
+ if ( times.size()>0 ){
+ double t = lerp( times.front(), tiles_data[i].f, tiles_data[i].t );
+ double hit_place = c[1-D](times.front());
+ if ( boxe[1-D].contains(hit_place) ){
+ result = true;
+ //taking a point on the boundary is dangerous!!
+ //Either use >0 tolerance here, or find 2 intersection points and split in between.
+ splitTile( i, t, tol, false);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+
+
+
+
+ //TODO: rewrite all this!...
+ //-------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------------------------------
+ //-------------------------------
+ //-- ccw Sorting of rays around a vertex.
+ //-------------------------------
+ //-------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------------------------------
+ //returns an (infinite) rect around "a" separating it from "b". Nota: 3 sides are infinite!
+ //TODO: place the cut where there is most space...
+ OptRect separate(Rect const &a, Rect const &b){
+ Rect ret ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) );
+ double gap = 0;
+ unsigned dir = 4;
+ if (b[X].min() - a[X].max() > gap){
+ gap = b[X].min() - a[X].max();
+ dir = 0;
+ }
+ if (a[X].min() - b[X].max() > gap){
+ gap = a[X].min() - b[X].max();
+ dir = 1;
+ }
+ if (b[Y].min() - a[Y].max() > gap){
+ gap = b[Y].min() - a[Y].max();
+ dir = 2;
+ }
+ if (a[Y].min() - b[Y].max() > gap){
+ gap = a[Y].min() - b[Y].max();
+ dir = 3;
+ }
+ switch (dir) {
+ case 0: ret[X].setMax(( a.max()[X] + b.min()[X] )/ 2); break;
+ case 1: ret[X].setMin(( b.max()[X] + a.min()[X] )/ 2); break;
+ case 2: ret[Y].setMax(( a.max()[Y] + b.min()[Y] )/ 2); break;
+ case 3: ret[Y].setMin(( b.max()[Y] + a.min()[Y] )/ 2); break;
+ case 4: return OptRect();
+ }
+ return OptRect(ret);
+ }
+
+ //Find 4 lines (returned as a Rect sides) that cut all the rays (=edges). *!* some side might be infinite.
+ OptRect isolateVertex(Rect const &box){
+ OptRect sep ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) );
+ //separate this vertex from the others. Find a better way.
+ for (auto & vtxboxe : vtxboxes){
+ if ( Point::LexLessRt(dim)( sep->max(), vtxboxe.min() ) ){
+ break;
+ }
+ if ( vtxboxe!=box ){//&& !vtxboxes[i].intersects(box) ){
+ OptRect sepi = separate(box, vtxboxe);
+ if ( sep && sepi ){
+ sep = intersect(*sep, *sepi);
+ }else{
+ std::cout<<"box="<<box<<"\n";
+ std::cout<<"vtxboxes[i]="<<vtxboxe<<"\n";
+ assert(sepi);
+ }
+ }
+ }
+ if (!sep) THROW_EXCEPTION("Invalid intersection data.");
+ return sep;
+ }
+
+ //TODO: argh... rewrite to have "dim"=min first place.
+ struct ExitPoint{
+ public:
+ unsigned side; //0:y=min; 1:x=max; 2:y=max; 3:x=min.
+ double place; //x or y value on the exit line.
+ unsigned ray_idx;
+ double time; //exit time on curve.
+ ExitPoint(){}
+ ExitPoint(unsigned s, double p, unsigned r, double t){
+ side =s;
+ place = p;
+ ray_idx = r;
+ time = t;
+ }
+ };
+
+
+ class ExitOrder{
+ public:
+ bool operator()(Ray a, Ray b) const {
+ if ( a.exit_side < b.exit_side ) return true;
+ if ( a.exit_side > b.exit_side ) return false;
+ if ( a.exit_side <= 1) {
+ return ( a.exit_place < b.exit_place );
+ }
+ return ( a.exit_place > b.exit_place );
+ }
+ };
+
+ void printRay(Ray const &r){
+ std::printf("Ray: tile=%u, centrifuge=%u, side=%u, place=%f\n",
+ r.tile, r.centrifuge, r.exit_side, r.exit_place);
+ }
+ void printVertex(FatVertex const &v){
+ std::printf("Vertex: [%f,%f]x[%f,%f]\n", v[X].min(),v[X].max(),v[Y].min(),v[Y].max() );
+ for (const auto & ray : v.rays){
+ printRay(ray);
+ }
+ }
+
+ //TODO: use a partial order on input coming from the context + Try quadrant order just in case it's enough.
+ //TODO: use monotonic assumption.
+ void sortRays( FatVertex &v ){
+ OptRect sep = isolateVertex(v);
+
+ for (unsigned i=0; i < v.rays.size(); i++){
+ v.rays[i].centrifuge = (tiles_data[ v.rays[i].tile ].fbox == v);
+ v.rays[i].exit_time = v.rays[i].centrifuge ? 1 : 0 ;
+ }
+
+ for (unsigned i=0; i < v.rays.size(); i++){
+ //TODO: don't convert each time!!!
+ assert( v.rays[i].tile < tiles_data.size() );
+ D2<SBasis> c = tileToSB( tiles_data[ v.rays[i].tile ] );
+
+ for (unsigned side=0; side<4; side++){//scan X or Y direction, on level min or max...
+ double level = sep->corner( side )[1-side%2];
+ if (level != infinity() && level != -infinity() ){
+ std::vector<double> times = roots(c[1-side%2]-level);
+ if ( times.size() > 0 ) {
+ double t;
+ assert( v.rays[i].tile < tiles_data.size() );
+ if (tiles_data[ v.rays[i].tile ].fbox == v){
+ t = times.front();
+ if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time > t ){
+ v.rays[i].setExitInfo( side, c[side%2](t), t);
+ }
+ }else{
+ t = times.back();
+ if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time < t ){
+ v.rays[i].setExitInfo( side, c[side%2](t), t);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Rk: at this point, side == 4 means the edge is contained in the intersection box (?)...;
+ std::sort( v.rays.begin(), v.rays.end(), ExitOrder() );
+ }
+
+
+ //-------------------------------
+ //-- initialize all data.
+ //-------------------------------
+
+ Sweeper(){}
+ Sweeper(PathVector const &input_paths, Dim2 sweep_dir, double tolerance=1e-5){
+ paths = input_paths;//use a ptr...
+ dim = sweep_dir;
+ tol = tolerance;
+
+ //split paths into monotonic tiles
+ createMonotonicTiles();
+
+ //split at tiles intersections
+ splitIntersectingTiles();
+
+ //handle overlapping end boxes/and tiles traversing boxes.
+ do{
+ vtxboxes = collectBoxes();
+ }while ( splitTilesThroughFatPoints(vtxboxes) );
+
+ enlargeTilesEnds(vtxboxes);
+
+ //now create the pointers to the tiles.
+ tiles = std::vector<unsigned>(tiles_data.size(), 0);
+ for (unsigned i=0; i<tiles_data.size(); i++){
+ tiles[i] = i;
+ }
+ sortTiles();
+
+ //initialize the context.
+ if (tiles_data.size()>0){
+ context.clear();
+ context.last_pos = tiles_data[tiles.front()].min();
+ context.last_pos[dim] -= 1;
+ context.pending_vertex = FatVertex();
+ context.pending_event = Event();
+ }
+
+// std::printf("Sweeper initialized (%u tiles)\n", tiles_data.size());
+ }
+
+
+ //-------------------------------
+ //-- Event walk.
+ //-------------------------------
+
+
+ Event getNextEvent(){
+// std::printf("getNextEvent():\n");
+
+// std::printf("initial contex:\n");
+// printContext();
+ Event old_event = context.pending_event, event;
+// std::printf("apply old event\n");
+ applyEvent(context.pending_event);
+// printContext();
+
+ if (context.pending_vertex.rays.size()== 0){
+// std::printf("cook up a new vertex\n");
+
+ //find the edges at the next vertex.
+ //TODO: implement this as a lower bound!!
+ std::vector<unsigned>::iterator low, high;
+ //Warning: bad looking test, but make sure we advance even in case of 0 width boxes...
+ for ( low = tiles.begin(); low != tiles.end() &&
+ ( tiles_data[*low].state==1 || Point::LexLessRt(dim)(tiles_data[*low].cur_box().min(), context.last_pos) ); low++){}
+
+ if ( low == tiles.end() ){
+// std::printf("no more event found\n");
+ return(Event());
+ }
+ Rect pos = tiles_data[ *low ].cur_box();
+ context.last_pos = pos.min();
+ context.last_pos[1-dim] = pos.max()[1-dim];
+
+// printContext();
+// std::printf("purgeDeadTiles\n");
+ purgeDeadTiles();
+// printContext();
+
+ FatVertex v(pos);
+ high = low;
+ do{
+// v.rays.push_back( Ray(*high, !tiles_data[*high].open) );
+ v.rays.push_back( Ray(*high, tiles_data[*high].state!=0) );
+ high++;
+ }while( high != tiles.end() && tiles_data[ *high ].cur_box()==pos );
+
+// std::printf("sortRays\n");
+ sortRays(v);
+
+ //Look for an opened tile
+ unsigned i=0;
+
+ for( i=0; i<v.rays.size(); ++i){assert( v.rays[i].tile<tiles_data.size() );}
+// for( i=0; i<v.rays.size() && !tiles_data[ v.rays[i].tile ].open; ++i){}
+ for( i=0; i<v.rays.size() && tiles_data[ v.rays[i].tile ].state!=0; ++i){}
+
+ //if there are only openings:
+ if (i == v.rays.size() ){
+// std::printf("only openings!\n");
+ event.insert_at = contextRanking(pos.min());
+ }
+ //if there is at least one closing: make it first, and catch 'insert_at'.
+ else{
+// std::printf("not only openings\n");
+ if( i > 0 ){
+ std::vector<Ray> head;
+ head.assign ( v.rays.begin(), v.rays.begin()+i );
+ v.rays.erase ( v.rays.begin(), v.rays.begin()+i );
+ v.rays.insert( v.rays.end(), head.begin(), head.end());
+ }
+ //assert( tiles_data[ v.rays[0].tile ].open );
+ assert( tiles_data[ v.rays[0].tile ].state==0 );
+ event.insert_at = context.find( v.rays.front().tile )+1;
+// event.erase_at = context.find( v.rays.front().tile );
+// std::printf("at least one closing!\n");
+ }
+ context.pending_vertex = v;
+ }else{
+// std::printf("continue biting exiting vertex\n");
+ event.tile = context.pending_vertex.rays.front().tile;
+ event.insert_at = old_event.insert_at;
+// if (old_event.opening){
+// event.insert_at++;
+// }else{
+// if (old_event.erase_at < old_event.insert_at){
+// event.insert_at--;
+// }
+// }
+ event.insert_at++;
+ }
+ event.tile = context.pending_vertex.rays.front().tile;
+// event.opening = !tiles_data[ event.tile ].open;
+ event.opening = tiles_data[ event.tile ].state!=0;
+ event.to_be_continued = context.pending_vertex.rays.size()>1;
+// if ( !event.opening ) event.erase_at = context.find(event.tile);
+
+ context.pending_vertex.rays.erase(context.pending_vertex.rays.begin());
+ context.pending_event = event;
+// printEvent(event);
+ return event;
+ }
+};
+
+
+/*
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/topology.cpp b/src/toys/topology.cpp
new file mode 100644
index 0000000..25830ce
--- /dev/null
+++ b/src/toys/topology.cpp
@@ -0,0 +1,668 @@
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/exception.h>
+
+#include <vector>
+#include <algorithm>
+#include "sweeper.cpp"
+
+/*
+Topology Class:
+This class mainly consists in 3 vectors: vertices, edges, and areas.
+-edges: have start/end, left/right pointing to vertices or areas.
+-vertices: have a "boundary"= the sequence of edges sorted in CCW order.
+-areas: have one outer "boundary" + a vector of inner boundaries, which are
+ sequence of edges.
+
+To build this data, the strategy is to let a line sweep the plane (from left
+to right, say) and consider the topology of what is on the left of the sweep line.
+Topology changes are called events, and we call an external "sweeper" to generate
+them for us.
+
+So we start with an empty data, and respond to events to always describe the
+topology of what is on the left of the sweep line. [more precisely, we start with
+one region that has empty boundary, and since the external sweeper knows how many
+edges we'll have at the end, so we create them from scratch, leaving their ends
+as "unknown"]
+
+Note: see the sweeper for more info about events; they are essentially generated
+when the sweep line crosses a vertex (which is in fact a box), but are in fact split
+into smaller events, one for each edge around the vertex...
+
+
+The code is using a lot of vectors: unsing pointers instead of vectors could speed
+things up (?), but vector indices are easier to debug than memory addresses.:P
+*/
+
+using namespace Geom;
+using namespace std;
+
+class Topology {
+public:
+
+ // -!- convention:
+ // In a boundary, reversed edges point away from the vertex or CW around the area.
+ struct OrientedEdge{
+ unsigned edge; //edge index.
+ bool reversed; //true if the intrinsic edge orientation points away (from vertex) or backward (along area boundary)
+ OrientedEdge(unsigned edge_idx, bool o){
+ edge = edge_idx;
+ reversed = o;
+ }
+ OrientedEdge(){
+ edge = NULL_IDX;
+ reversed = false;
+ }
+ bool operator == ( OrientedEdge const &other) const {
+ return (edge == other.edge && edge!=NULL_IDX && reversed == other.reversed);
+ }
+ };
+
+ class Boundary : public std::vector<OrientedEdge>{
+ public:
+ bool of_area;//true if this is the boundary of an area. Fix this with templates?
+ Boundary(bool area_type): of_area(area_type){}
+ };
+
+ class Vertex{
+ public:
+ Boundary boundary; // list of edges in CCW order around the vertex
+ Geom::Rect bounds;
+ Vertex():boundary(false){}
+ };
+
+ class Area {//an area is a connected comp of the complement of the graph. .
+ public:
+ Boundary boundary; // outermost boundary component, CCW oriented (i.e. area is on the left of the boundary).
+ std::vector<Boundary> inner_boundaries;//same conventions, area on the left, so this gives the CW orientation for inner components.
+ std::vector<int> windings;//one winding number for each input path.
+ Area(unsigned size): boundary(true), windings(size, 0){}
+ };
+
+ class Edge {
+ public:
+ unsigned left, right;// the indices of the areas on the left and on the right this edge.
+ unsigned start, end; // the indices of vertices at start and at end of this edge.
+ Geom::Interval portion;
+ unsigned path;
+ unsigned curve;
+ Edge(){
+ left = NULL_IDX;
+ right =NULL_IDX;
+ start = NULL_IDX;
+ end = NULL_IDX;
+ portion = Interval();
+ path = NULL_IDX;
+ curve = NULL_IDX;
+ }
+ };
+
+ vector<Area> areas;
+ vector<Edge> edges;
+ vector<Vertex> vertices;
+
+ PathVector input_paths;//we don't need our own copy...
+ cairo_t* cr;
+
+ //debug only!!
+ int steps_max;
+ //----------
+
+
+ //----------------------------------------------------
+ //-- utils...
+ //----------------------------------------------------
+
+ void printIdx(unsigned idx){ (idx == NULL_IDX)? std::printf("?") : std::printf("%u", idx); }
+ void printVertex(unsigned i){
+ std::printf("vertex %u: ", i);
+ printBoundary(vertices[i].boundary);
+ std::printf("\n");
+ }
+ void printEdge(unsigned i){
+ std::printf("edge %u: ", i);
+ printIdx(edges[i].start);
+ std::printf(" -> ");
+ printIdx(edges[i].end);
+ std::printf(" ^");
+ printIdx(edges[i].left);
+ std::printf(" _");
+ printIdx(edges[i].right);
+ std::printf("\n");
+ }
+ void printArea(unsigned i){
+ std::printf("area %u: ", i);
+ printBoundary(areas[i].boundary);
+ for (auto & inner_boundarie : areas[i].inner_boundaries){
+ std::printf(", ");
+ printBoundary(inner_boundarie);
+ }
+ std::printf("\n");
+ }
+
+ void printOrientedEdge(OrientedEdge const &f){
+ ( f.reversed ) ? std::printf("-") : std::printf("+");
+ printIdx(f.edge);
+ std::printf(" ");
+ }
+ void printBoundary(Boundary const &bndry){
+ (bndry.of_area) ? std::printf("[") : std::printf("<");
+ for (unsigned i=0; i<bndry.size(); i++){
+ printOrientedEdge(bndry[i]);
+ }
+ (bndry.of_area) ? std::printf("]") : std::printf(">");
+ }
+
+ void print(){
+ std::cout<<"\nCrossing Data:\n";
+ for (unsigned i=0; i<vertices.size(); i++){
+ printVertex(i);
+ }
+ std::cout<<"\n";
+ for (unsigned i=0; i<edges.size(); i++){
+ printEdge(i);
+ }
+ std::cout<<"\n";
+ for (unsigned i=0; i<areas.size(); i++){
+ printArea(i);
+ }
+ }
+
+ D2<SBasis> edgeAsSBasis(unsigned e){
+ //beurk! optimize me.
+ D2<SBasis> c = input_paths[edges[e].path][edges[e].curve].toSBasis();
+ return portion(c, edges[e].portion);
+ }
+
+ Path edgeToPath(Topology::OrientedEdge o_edge){
+ Topology::Edge e = edges[o_edge.edge];
+ D2<SBasis> p = input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ p = portion(p, dom);
+ if ( o_edge.reversed ){
+ p = compose( p, Linear(1.,0.) );
+ }
+ Path ret;
+ ret.setStitching(true);
+ Point center;
+ unsigned c_idx = source(o_edge, true);
+ if ( c_idx == NULL_IDX ){
+ ret.append(p);
+ }else{
+ center = vertices[c_idx].bounds.midpoint();
+ ret = Path(center);
+ ret.append(p);
+ }
+ c_idx = target(o_edge, true);
+ if ( c_idx == NULL_IDX ){
+ return ret;
+ }else{
+ center = vertices[c_idx].bounds.midpoint();
+ if ( center != p.at1() ) ret.appendNew<LineSegment>(center);
+ return ret;
+ }
+ }
+
+ Path boundaryToPath(Topology::Boundary b){
+ Point pt;
+ Path bndary;
+ bndary.setStitching(true);
+
+ if (b.size()==0){ return Path(); }
+
+ Topology::OrientedEdge o_edge = b.front();
+ unsigned first_v = source(o_edge, true);
+ if ( first_v != NULL_IDX ){
+ pt = vertices[first_v].bounds.midpoint();
+ bndary = Path(pt);
+ }
+
+ for (unsigned i = 0; i < b.size(); i++){
+ bndary.append( edgeToPath(b[i]));
+ }
+ bndary.close();
+ return bndary;
+ }
+
+
+
+ //----------------------------------------------------
+ //-- Boundary Navigation/Modification
+ //----------------------------------------------------
+
+ //TODO: this should be an OrientedEdge method, be requires access to the edges.
+ unsigned source(OrientedEdge const &f, bool as_area_bndry){
+ unsigned prev;
+ if (f.reversed )
+ prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right;
+ else
+ prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left;
+ return prev;
+ }
+ unsigned target(OrientedEdge const &f, bool as_area_bndry){
+ unsigned prev;
+ if (f.reversed )
+ prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left;
+ else
+ prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right;
+ return prev;
+ }
+
+ //TODO: this should be a Boundary method, but access to the full data is required...
+ bool prolongate( Boundary &bndry, OrientedEdge const &f){
+ if ( bndry.empty() ){
+ bndry.push_back(f);
+ return true;
+ }
+ unsigned src = source(f, bndry.of_area);
+ if ( src == target( bndry.back(), bndry.of_area ) && src != NULL_IDX ){
+ bndry.push_back(f);
+ return true;
+ }
+ unsigned tgt = target( f, bndry.of_area );
+ if ( tgt == source( bndry.front(), bndry.of_area ) && tgt != NULL_IDX ){
+ bndry.insert( bndry.begin(), f);
+ return true;
+ }
+ return false;
+ }
+
+ bool prolongate(Boundary &a, Boundary &b){
+ if (a.size()==0 || b.size()==0 || (a.of_area != b.of_area) ) return false;
+ unsigned src;
+ src = source(a.front(), a.of_area);
+
+// unsigned af = a.front().edge, as=source(a.front(), a.of_area), ab=a.back().edge, at=target(a.back(), a.of_area);
+// unsigned bf = b.front().edge, bs=source(b.front(), b.of_area), bb=b.back().edge, bt=target(b.back(), b.of_area);
+// std::printf("a=%u(%u)...(%u)%u\n", as, af,ab,at);
+// std::printf("b=%u(%u)...(%u)%u\n", bs, bf,bb,bt);
+
+// std::printf("%u == %u?\n", src, target( b.back(), b.of_area ));
+ if ( src == target( b.back(), b.of_area ) && src != NULL_IDX ){
+ a.insert( a.begin(), b.begin(), b.end() );
+// std::printf("boundaries fused!!\n");
+ return true;
+ }
+ src = source(b.front(), b.of_area);
+ if ( src == target( a.back(), a.of_area ) && src != NULL_IDX ){
+ a.insert( a.end(), b.begin(), b.end() );
+ return true;
+ }
+ return false;
+ }
+
+ //TODO: this should be a Boundary or Area method, but requires access to the full data...
+ //TODO: systematically check for connected boundaries before returning?
+ void addAreaBoundaryPiece(unsigned a, OrientedEdge const &f){
+ if ( areas[a].boundary.size()>0 && prolongate( areas[a].boundary, f ) ) return;
+ for (auto & inner_boundarie : areas[a].inner_boundaries){
+// printBoundary(areas[a].inner_boundaries[i]);
+// printf(" matches ");
+// printOrientedEdge(f);
+// printf("?");
+ if ( inner_boundarie.size()>0 && prolongate( inner_boundarie, f) ) return;
+// printf("no. (%u vs %u)", target(areas[a].inner_boundaries[i].back(), true), source(f, true));
+ }
+ Boundary new_comp(true);
+ new_comp.push_back(f);
+ areas[a].inner_boundaries.push_back(new_comp);
+ }
+
+
+ bool fuseConnectedBoundaries(unsigned a){
+// std::printf(" fuseConnectedBoundaries %u\n",a);
+
+ bool ret = false;
+ if ( areas[a].boundary.size()>0 ){
+ for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){
+ if ( prolongate( areas[a].boundary, areas[a].inner_boundaries[i] ) ){
+ areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+i);
+ i--;
+ ret = true;
+ }
+ }
+ }
+ for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){
+ for ( unsigned j=i+1; j<areas[a].inner_boundaries.size(); j++){
+ if ( prolongate( areas[a].inner_boundaries[i], areas[a].inner_boundaries[j] ) ){
+ areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+j);
+ j--;
+ ret = true;
+ }
+ }
+ }
+ return ret;
+ }
+
+ //-------------------------------
+ //-- Some basic area manipulation.
+ //-------------------------------
+
+ void renameArea(unsigned oldi, unsigned newi){
+ for (auto & edge : edges){
+ if ( edge.left == oldi ) edge.left = newi;
+ if ( edge.right == oldi ) edge.right = newi;
+ }
+ }
+ void deleteArea(unsigned a0){//ptrs would definitely be helpful here...
+ assert(a0<areas.size());
+ for (unsigned a=a0+1; a<areas.size(); a++){
+ renameArea(a,a-1);
+ }
+ areas.erase(areas.begin()+a0);
+ }
+
+ //fuse open(=not finished!) areas. The boundaries are supposed to match. true on success.
+ void fuseAreas(unsigned a, unsigned b){
+// std::printf("fuse Areas %u and %u\n", a, b);
+ if (a==b) return;
+ if (a>b) swap(a,b);//this is important to keep track of the outermost component!!
+
+ areas[a].inner_boundaries.push_back(areas[b].boundary);
+ for (unsigned i=0; i<areas[b].inner_boundaries.size(); i++){
+ areas[a].inner_boundaries.push_back(areas[b].inner_boundaries[i]);
+ }
+ renameArea(b,a);
+ deleteArea(b);
+ assert( fuseConnectedBoundaries(a) );
+ return;
+ }
+
+ PathVector areaToPath(unsigned a){
+ PathVector bndary;
+ if ( areas[a].boundary.size()!=0 ){//this is not the unbounded component...
+ bndary.push_back( boundaryToPath(areas[a].boundary ) );
+ }
+ for (auto & inner_boundarie : areas[a].inner_boundaries){
+ bndary.push_back( boundaryToPath(inner_boundarie) );
+ }
+ return bndary;
+ }
+
+ //DEBUG ONLY: we add a rect round the unbounded comp, and glue the bndries
+ //for easy drawing in the toys...
+ Path glued_areaToPath(unsigned a){
+ Path bndary;
+ if ( areas[a].boundary.size()==0 ){//this is the unbounded component...
+ OptRect bbox = bounds_fast( input_paths );
+ if (!bbox ){return Path();}//???
+ bbox->expandBy(50);
+ bndary = Path(bbox->corner(0));
+ bndary.appendNew<LineSegment>(bbox->corner(1));
+ bndary.appendNew<LineSegment>(bbox->corner(2));
+ bndary.appendNew<LineSegment>(bbox->corner(3));
+ bndary.appendNew<LineSegment>(bbox->corner(0));
+ }else{
+ bndary = boundaryToPath(areas[a].boundary);
+ }
+ for (auto & inner_boundarie : areas[a].inner_boundaries){
+ bndary.append( boundaryToPath(inner_boundarie));
+ bndary.appendNew<LineSegment>( bndary.initialPoint() );
+ }
+ bndary.close();
+ return bndary;
+ }
+
+ void drawAreas( cairo_t *cr, bool fill=true ){
+ //don't draw the first one...
+ for (unsigned a=0; a<areas.size(); a++){
+ drawArea(cr, a, fill);
+ }
+ }
+ void drawArea( cairo_t *cr, unsigned a, bool fill=true ){
+ if (a>=areas.size()) return;
+ Path bndary = glued_areaToPath(a);
+ cairo_path(cr, bndary);
+ if (fill){
+ cairo_fill(cr);
+ }else{
+ cairo_stroke(cr);
+ }
+ }
+ void highlightRay( cairo_t *cr, unsigned b, unsigned r ){
+ if (b>=vertices.size()) return;
+ if (r>=vertices[b].boundary.size()) return;
+ Rect box = vertices[b].bounds;
+ //box.expandBy(2);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, 1.0);
+ cairo_set_line_width (cr, 1);
+ cairo_fill(cr);
+ unsigned eidx = vertices[b].boundary[r].edge;
+ Topology::Edge e = edges[eidx];
+ D2<SBasis> p = input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ if (vertices[b].boundary[r].reversed){
+ //dom[0] += e.portion.extent()*2./3;
+ cairo_set_source_rgba (cr, 0., 1., 0., 1.0);
+ }else{
+ //dom[1] -= e.portion.extent()*2./3;
+ cairo_set_source_rgba (cr, 0., 0., 1., 1.0);
+ }
+ p = portion(p, dom);
+ cairo_d2_sb(cr, p);
+ cairo_set_source_rgba (cr, 1., 0., 0, 1.0);
+ cairo_set_line_width (cr, 5);
+ cairo_stroke(cr);
+ }
+
+ void drawEdge( cairo_t *cr, unsigned eidx ){
+ if (eidx>=edges.size()) return;
+ Topology::Edge e = edges[eidx];
+ D2<SBasis> p = input_paths[e.path][e.curve].toSBasis();
+ Interval dom = e.portion;
+ p = portion(p, dom);
+ cairo_d2_sb(cr, p);
+ if (e.start == NULL_IDX || e.end == NULL_IDX )
+ cairo_set_source_rgba (cr, 0., 1., 0, 1.0);
+ else
+ cairo_set_source_rgba (cr, 0., 0., 0, 1.0);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+ }
+ void drawEdges( cairo_t *cr){
+ for (unsigned e=0; e<edges.size(); e++){
+ drawEdge(cr, e);
+ }
+ }
+ void drawKnownEdges( cairo_t *cr){
+ for (auto & vertice : vertices){
+ for (unsigned e=0; e<vertice.boundary.size(); e++){
+ drawEdge(cr, vertice.boundary[e].edge);
+ }
+ }
+ }
+
+
+ void drawBox( cairo_t *cr, unsigned b ){
+ if (b>=vertices.size()) return;
+ Rect box = vertices[b].bounds;
+ //box.expandBy(5);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, .5);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke(cr);
+ cairo_rectangle(cr, box);
+ cairo_set_source_rgba (cr, 1., 0., 0, .2);
+ cairo_fill(cr);
+ }
+
+ void drawBoxes( cairo_t *cr){
+ for (unsigned b=0; b<vertices.size(); b++){
+ drawBox(cr, b);
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+ //----------------------------------------------------
+ //-- Fill data using a sweeper...
+ //----------------------------------------------------
+
+ Topology(){}
+ ~Topology(){}
+ Topology(PathVector const &paths, cairo_t* cairo, double tol=EPSILON, int stepsmax=-1){
+// std::printf("\n---------------------\n---------------------\n---------------------\n");
+// std::printf("Topology creation\n");
+ cr = cairo;
+
+ //debug only:
+ steps_max = stepsmax;
+ //-------------
+
+ input_paths = paths;
+
+ vertices.clear();
+ edges.clear();
+ areas.clear();
+ Area empty( input_paths.size() );
+ areas.push_back(empty);
+
+ Sweeper sweeper( paths, X, tol );
+
+ edges = std::vector<Edge>( sweeper.tiles_data.size(), Edge() );
+ for (unsigned i=0; i<edges.size(); i++){
+ edges[i].path = sweeper.tiles_data[i].path;
+ edges[i].curve = sweeper.tiles_data[i].curve;
+ edges[i].portion = Interval(sweeper.tiles_data[i].f, sweeper.tiles_data[i].t);
+ }
+
+ //std::printf("entering event loop:\n");
+ int step=0;
+ for(Sweeper::Event event = sweeper.getNextEvent(); ; event = sweeper.getNextEvent() ){
+// std::printf(" new event received: ");
+ //print();
+ //debug only!!!
+ if ( steps_max >= 0 && step > steps_max ){
+ break;
+ }else{
+ step++;
+ }
+ //---------
+
+ if (event.empty()){
+ //std::printf(" empty event received\n");
+ break;
+ }
+
+ //std::printf(" non empty event received:");
+ //sweeper.printEvent(event);
+
+ //is this a new event or the continuation of an old one?
+ unsigned v;
+ Rect r = sweeper.context.pending_vertex;
+ if (vertices.empty() || !r.intersects( vertices.back().bounds ) ){
+ v = vertices.size();
+ vertices.push_back(Vertex());
+ vertices[v].bounds = r;
+// std::printf(" new intersection created (%u).\n",v);
+ }else{
+ v = vertices.size()-1;
+// std::printf(" continue last intersection (%u).\n",v);
+ }
+
+ //--Closing an edge:-------------
+ if( !event.opening ){
+ unsigned e = event.tile, a, b;
+// std::printf(" closing edge %u\n", e);
+ bool reversed = sweeper.tiles_data[e].reversed;//Warning: true means v==e.start
+ if (reversed){
+ edges[e].start = v;
+ a = edges[e].right;
+ b = edges[e].left;
+ }else{
+ edges[e].end = v;
+ a = edges[e].left;
+ b = edges[e].right;
+ }
+ OrientedEdge vert_edge(e, reversed);
+ if (vertices[v].boundary.size()>0){//Make sure areas are compatible (only relevant if the last event was an opening).
+ fuseAreas ( a, target( vertices[v].boundary.back(), false ) );
+ }
+ assert( prolongate( vertices[v].boundary, vert_edge) );
+ fuseConnectedBoundaries(a);//there is no doing both: tests are performed twice but for 2 areas.
+ fuseConnectedBoundaries(b);//
+ }else{
+ //--Opening an edge:-------------
+ unsigned e = event.tile;
+// std::printf(" opening edge %u\n", e);
+ bool reversed = !sweeper.tiles_data[e].reversed;//Warning: true means v==start.
+
+ //--Find first and last area around this vertex:-------------
+ unsigned cur_a;
+ if ( vertices[v].boundary.size() > 0 ){
+ cur_a = target( vertices[v].boundary.back(), false );
+ }else{//this vertex is empty
+ if ( event.insert_at < sweeper.context.size() ){
+ unsigned upper_tile = sweeper.context[event.insert_at].first;
+ cur_a = (sweeper.tiles_data[upper_tile].reversed) ? edges[upper_tile].left : edges[upper_tile].right;
+ }else{
+ cur_a = 0;
+ }
+ }
+
+ unsigned new_a = areas.size();
+
+ Area new_area(paths.size());
+ new_area.boundary.push_back( OrientedEdge(e, !reversed ) );
+ new_area.windings = areas[cur_a].windings;//FIXME: escape boundary cases!!!
+ if ( input_paths[edges[e].path].closed() ){
+ new_area.windings[edges[e].path] += (reversed) ? +1 : -1;
+ }
+ areas.push_back(new_area);
+
+ //update edge
+ if (reversed){
+ edges[e].start = v;
+ edges[e].left = new_a;
+ edges[e].right = cur_a;
+ }else{
+ edges[e].end = v;
+ edges[e].left = cur_a;
+ edges[e].right = new_a;
+ }
+ //update vertex
+ OrientedEdge f(e, reversed);
+ assert( prolongate( vertices[v].boundary, f) );
+ addAreaBoundaryPiece(cur_a, OrientedEdge(e, reversed) );
+ }
+ if (!event.to_be_continued && vertices[v].boundary.size()>0){
+ unsigned first_a = source( vertices[v].boundary.front(), false );
+ unsigned last_a = target( vertices[v].boundary.back(), false );
+ fuseAreas(first_a, last_a);
+ }
+
+// this->print();
+// std::printf("----------------\n");
+ //std::printf("\n");
+ }
+ }
+
+
+
+ //----------------------------------------------------
+ //-- done.
+ //----------------------------------------------------
+};
+
+
+/*
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/toy-framework-2.cpp b/src/toys/toy-framework-2.cpp
new file mode 100644
index 0000000..54166cf
--- /dev/null
+++ b/src/toys/toy-framework-2.cpp
@@ -0,0 +1,972 @@
+#include <cstring>
+#include <cstdint>
+#include <typeinfo>
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <toys/toy-framework-2.h>
+
+#include <cairo-features.h>
+#if CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+#if CAIRO_HAS_SVG_SURFACE
+#include <cairo-svg.h>
+#endif
+
+GtkApplicationWindow* the_window = nullptr;
+static GtkWidget *the_canvas = nullptr;
+Toy* the_toy = nullptr;
+int the_requested_height = 0;
+int the_requested_width = 0;
+gchar **the_emulated_argv = nullptr;
+
+gchar *arg_spool_filename = nullptr;
+gchar *arg_handles_filename = nullptr;
+gchar *arg_screenshot_filename = nullptr;
+gchar **arg_extra_files = nullptr;
+
+//Utility functions
+
+double uniform() {
+ return double(rand()) / RAND_MAX;
+}
+
+colour colour::from_hsv( float H, // hue shift (in degrees)
+ float S, // saturation shift (scalar)
+ float V, // value multiplier (scalar)
+ float A
+ )
+{
+ double inr = 1;
+ double ing = 0;
+ double inb = 0;
+ float k = V/3;
+ float a = V*S*cos(H)/3;
+ float b = V*S*sin(H)/3;
+
+ return colour(
+ (k+2*a)*inr - 2*b*ing + (k-a-b)*inb,
+ (-k+a+3*b)*inr + (3*a-b)*ing + (-k+a+2*b)*inb,
+ (2*k-2*a)*inr + 2*b*ing + (2*k+a+b)*inb,
+ A);
+}
+
+ // Given H,S,L in range of 0-1
+
+ // Returns a Color (RGB struct) in range of 0-255
+
+colour colour::from_hsl(float h, float sl, float l, float a) {
+ h /= M_PI*2;
+ colour rgba(l,l,l,a); // default to gray
+
+ double v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
+
+ if (v > 0) {
+ double m;
+ double sv;
+ int sextant;
+ double fract, vsf, mid1, mid2;
+
+ m = l + l - v;
+ sv = (v - m ) / v;
+ h *= 6.0;
+ sextant = (int)h;
+ fract = h - sextant;
+ vsf = v * sv * fract;
+ mid1 = m + vsf;
+ mid2 = v - vsf;
+ switch (sextant%6) {
+ case 0:
+ rgba.r = v;
+ rgba.g = mid1;
+ rgba.b = m;
+ break;
+
+ case 1:
+ rgba.r = mid2;
+ rgba.g = v;
+ rgba.b = m;
+ break;
+
+ case 2:
+ rgba.r = m;
+ rgba.g = v;
+ rgba.b = mid1;
+ break;
+
+ case 3:
+ rgba.r = m;
+ rgba.g = mid2;
+ rgba.b = v;
+ break;
+
+ case 4:
+ rgba.r = mid1;
+ rgba.g = m;
+ rgba.b = v;
+ break;
+
+ case 5:
+ rgba.r = v;
+ rgba.g = m;
+ rgba.b = mid2;
+ break;
+ }
+ }
+ return rgba;
+}
+
+void cairo_set_source_rgba(cairo_t* cr, colour c) {
+ cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a);
+}
+
+void draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom, const char* fontdesc) {
+ PangoLayout* layout = pango_cairo_create_layout (cr);
+ pango_layout_set_text(layout, txt, -1);
+ PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc);
+ pango_layout_set_font_description(layout, font_desc);
+ pango_font_description_free (font_desc);
+ PangoRectangle logical_extent;
+ pango_layout_get_pixel_extents(layout, NULL, &logical_extent);
+ cairo_move_to(cr, loc - Geom::Point(0, bottom ? logical_extent.height : 0));
+ pango_cairo_show_layout(cr, layout);
+}
+
+void draw_text(cairo_t *cr, Geom::Point loc, const std::string& txt, bool bottom, const std::string& fontdesc) {
+ draw_text(cr, loc, txt.c_str(), bottom, fontdesc.c_str());
+}
+
+void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name, bool bottom) {
+ std::ostringstream number;
+ if (name.size())
+ number << name;
+ number << num;
+ draw_text(cr, pos, number.str().c_str(), bottom);
+}
+
+void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name, bool bottom) {
+ std::ostringstream number;
+ if (name.size())
+ number << name;
+ number << num;
+ draw_text(cr, pos, number.str().c_str(), bottom);
+}
+
+void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name, bool bottom) {
+ std::ostringstream number;
+ if (name.size())
+ number << name;
+ number << num;
+ draw_text(cr, pos, number.str().c_str(), bottom);
+}
+
+//Framework Accessors
+void redraw() { gtk_widget_queue_draw(GTK_WIDGET(the_window)); }
+
+void Toy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool /*save*/, std::ostringstream *timer_stream)
+{
+ if(should_draw_bounds() == 1) {
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
+ cairo_set_line_width (cr, 0.5);
+ for(unsigned i = 1; i < 4; i+=2) {
+ cairo_move_to(cr, 0, i*width/4);
+ cairo_line_to(cr, width, i*width/4);
+ cairo_move_to(cr, i*width/4, 0);
+ cairo_line_to(cr, i*width/4, height);
+ }
+ }
+ else if(should_draw_bounds() == 2) {
+ cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
+ cairo_set_line_width (cr, 0.5);
+ cairo_move_to(cr, 0, width/2);
+ cairo_line_to(cr, width, width/2);
+ cairo_move_to(cr, width/2, 0);
+ cairo_line_to(cr, width/2, height);
+ }
+
+ cairo_set_line_width (cr, 1);
+ for(auto & handle : handles) {
+ cairo_set_source_rgb (cr, handle->rgb[0], handle->rgb[1], handle->rgb[2]);
+ handle->draw(cr, should_draw_numbers());
+ }
+
+ cairo_set_source_rgba (cr, 0.5, 0, 0, 1);
+ if(selected && mouse_down == true)
+ selected->draw(cr, should_draw_numbers());
+
+ cairo_set_source_rgba (cr, 0.5, 0.25, 0, 1);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgba (cr, 0., 0.5, 0, 0.8);
+ {
+ *notify << std::ends;
+ draw_text(cr, Geom::Point(0, height-notify_offset), notify->str().c_str(), true);
+ }
+ if(show_timings) {
+ *timer_stream << std::ends;
+ draw_text(cr, Geom::Point(0, notify_offset), timer_stream->str().c_str(), false);
+ }
+}
+
+void Toy::mouse_moved(GdkEventMotion* e)
+{
+ Geom::Point mouse(e->x, e->y);
+
+ if(e->state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) {
+ if(selected)
+ selected->move_to(hit_data, old_mouse_point, mouse);
+ }
+ old_mouse_point = mouse;
+ redraw();
+}
+
+void Toy::mouse_pressed(GdkEventButton* e) {
+ Geom::Point mouse(e->x, e->y);
+ selected = NULL;
+ hit_data = NULL;
+ canvas_click_button = e->button;
+ if(e->button == 1) {
+ for(auto & handle : handles) {
+ void * hit = handle->hit(mouse);
+ if(hit) {
+ selected = handle;
+ hit_data = hit;
+ }
+ }
+ mouse_down = true;
+ }
+ old_mouse_point = mouse;
+ redraw();
+}
+
+void Toy::scroll(GdkEventScroll* /*e*/) {
+}
+
+void Toy::canvas_click(Geom::Point at, int button) {
+ (void)at;
+ (void)button;
+}
+
+void Toy::mouse_released(GdkEventButton* e) {
+ if(selected == NULL) {
+ Geom::Point mouse(e->x, e->y);
+ canvas_click(mouse, canvas_click_button);
+ canvas_click_button = 0;
+ }
+ selected = NULL;
+ hit_data = NULL;
+ if(e->button == 1)
+ mouse_down = false;
+ redraw();
+}
+
+void Toy::load(FILE* f) {
+ char data[1024];
+ if (fscanf(f, "%1024s", data)) {
+ name = data;
+ }
+ for(auto & handle : handles) {
+ handle->load(f);
+ }
+}
+
+void Toy::save(FILE* f) {
+ fprintf(f, "%s\n", name.c_str());
+ for(auto & handle : handles)
+ handle->save(f);
+}
+
+//Gui Event Callbacks
+
+void show_about_dialog(GSimpleAction *, GVariant *, gpointer) {
+ GtkWidget* about_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(about_window), "About");
+ gtk_window_set_resizable(GTK_WINDOW(about_window), FALSE);
+
+ GtkWidget* about_text = gtk_text_view_new();
+ GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(about_text));
+ gtk_text_buffer_set_text(buf, "Toy lib2geom application", -1);
+ gtk_container_add(GTK_CONTAINER(about_window), about_text);
+
+ gtk_widget_show_all(about_window);
+}
+
+void quit(GSimpleAction *, GVariant *, gpointer) {
+ g_application_quit(g_application_get_default());
+}
+
+Geom::Point read_point(FILE* f) {
+ Geom::Point p;
+ for(unsigned i = 0; i < 2; i++)
+ assert(fscanf(f, " %lf ", &p[i]));
+ return p;
+}
+
+Geom::Interval read_interval(FILE* f) {
+ Geom::Interval p;
+ Geom::Coord a, b;
+ assert(fscanf(f, " %lf ", &a));
+ assert(fscanf(f, " %lf ", &b));
+ p.setEnds(a, b);
+ return p;
+}
+
+void open_handles(GSimpleAction *, GVariant *, gpointer) {
+ if (!the_toy) return;
+ GtkWidget* d = gtk_file_chooser_dialog_new(
+ "Open handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_OPEN,
+ "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
+ FILE* f = fopen(filename, "r");
+ the_toy->load(f);
+ fclose(f);
+ }
+ gtk_widget_destroy(d);
+}
+
+void save_handles(GSimpleAction *, GVariant *, gpointer) {
+ if (!the_toy) return;
+ GtkWidget* d = gtk_file_chooser_dialog_new(
+ "Save handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE,
+ "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL);
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
+ FILE* f = fopen(filename, "w");
+ the_toy->save(f);
+ fclose(f);
+ }
+ gtk_widget_destroy(d);
+}
+
+void write_image(const char* filename) {
+ cairo_surface_t* cr_s;
+ unsigned l = strlen(filename);
+ int width = gdk_window_get_width(gtk_widget_get_window(the_canvas));
+ int height = gdk_window_get_height(gtk_widget_get_window(the_canvas));
+ bool save_png = false;
+
+ if (l >= 4 && strcmp(filename + l - 4, ".png") == 0) {
+ cr_s = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, width, height );
+ save_png = true;
+ }
+
+#if CAIRO_HAS_PDF_SURFACE
+ else if (l >= 4 && strcmp(filename + l - 4, ".pdf") == 0)
+ cr_s = cairo_pdf_surface_create(filename, width, height);
+#endif
+#if CAIRO_HAS_SVG_SURFACE
+#if CAIRO_HAS_PDF_SURFACE
+ else
+#endif
+ cr_s = cairo_svg_surface_create(filename, width, height);
+#endif
+ cairo_t* cr = cairo_create(cr_s);
+
+ if(save_png) {
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1,1,1);
+ cairo_paint(cr);
+ cairo_restore(cr);
+ }
+ if(the_toy != NULL) {
+ std::ostringstream * notify = new std::ostringstream;
+ std::ostringstream * timer_stream = new std::ostringstream;
+ the_toy->draw(cr, notify, width, height, true, timer_stream);
+ delete notify;
+ delete timer_stream;
+ }
+
+ cairo_show_page(cr);
+ if(save_png)
+ cairo_surface_write_to_png(cr_s, filename);
+ cairo_destroy (cr);
+ cairo_surface_destroy (cr_s);
+}
+
+void save_cairo(GSimpleAction *, GVariant *, gpointer) {
+ GtkWidget* d = gtk_file_chooser_dialog_new(
+ "Save file as svg, pdf or png", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE,
+ "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL);
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ const gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
+ write_image(filename);
+ }
+ gtk_widget_destroy(d);
+}
+
+static gint delete_event(GtkWidget*, GdkEventAny*, gpointer) {
+ quit(nullptr, nullptr, nullptr);
+ return FALSE;
+}
+
+
+static void toggle_action(GSimpleAction *action, GVariant *, gpointer) {
+ GVariant *state = g_action_get_state(G_ACTION(action));
+ g_action_change_state(G_ACTION(action), g_variant_new_boolean(!g_variant_get_boolean(state)));
+ g_variant_unref(state);
+}
+
+static void set_show_timings(GSimpleAction *action, GVariant *variant, gpointer) {
+ the_toy->show_timings = g_variant_get_boolean(variant);
+ g_simple_action_set_state(action, variant);
+}
+
+static gboolean draw_callback(GtkWidget *widget, cairo_t *cr)
+{
+ int width = gdk_window_get_width(gtk_widget_get_window(widget));
+ int height = gdk_window_get_height(gtk_widget_get_window(widget));
+
+ std::ostringstream notify;
+
+ static bool resized = false;
+ if(!resized) {
+ Geom::Rect alloc_size(Geom::Interval(0, width),
+ Geom::Interval(0, height));
+ if(the_toy != NULL)
+ the_toy->resize_canvas(alloc_size);
+ resized = true;
+ }
+ cairo_rectangle(cr, 0, 0, width, height);
+ cairo_set_source_rgba(cr,1,1,1,1);
+ cairo_fill(cr);
+ if (the_toy != NULL) {
+ std::ostringstream * timer_stream = new std::ostringstream;
+
+ if (the_toy->spool_file) {
+ the_toy->save(the_toy->spool_file);
+ }
+
+ the_toy->draw(cr, &notify, width, height, false, timer_stream);
+ delete timer_stream;
+ }
+
+ return TRUE;
+}
+
+static gint mouse_motion_event(GtkWidget* widget, GdkEventMotion* e, gpointer data) {
+ (void)(data);
+ (void)(widget);
+
+ if(the_toy != NULL) the_toy->mouse_moved(e);
+
+ return FALSE;
+}
+
+static gint mouse_event(GtkWidget* widget, GdkEventButton* e, gpointer data) {
+ (void)(data);
+ (void)(widget);
+
+ if(the_toy != NULL) the_toy->mouse_pressed(e);
+
+ return FALSE;
+}
+
+static gint scroll_event(GtkWidget* widget, GdkEventScroll* e, gpointer data) {
+ (void)(data);
+ (void)(widget);
+ if(the_toy != NULL) the_toy->scroll(e);
+
+ return FALSE;
+}
+
+static gint mouse_release_event(GtkWidget* widget, GdkEventButton* e, gpointer data) {
+ (void)(data);
+ (void)(widget);
+
+ if(the_toy != NULL) the_toy->mouse_released(e);
+
+ return FALSE;
+}
+
+static gint key_press_event(GtkWidget *widget, GdkEventKey *e, gpointer data) {
+ (void)(data);
+ (void)(widget);
+
+ if(the_toy != NULL) the_toy->key_hit(e);
+
+ return FALSE;
+}
+
+static gint size_allocate_event(GtkWidget* widget, GtkAllocation *allocation, gpointer data) {
+ (void)(data);
+ (void)(widget);
+
+ Geom::Rect alloc_size(Geom::Interval(allocation->x, allocation->x+ allocation->width),
+ Geom::Interval(allocation->y, allocation->y+allocation->height));
+ if(the_toy != NULL) the_toy->resize_canvas(alloc_size);
+
+ return FALSE;
+}
+
+
+const char *the_builder_xml = R"xml(
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="menu">
+ <submenu>
+ <attribute name="label">File</attribute>
+ <section>
+ <item>
+ <attribute name="label">Open Handles...</attribute>
+ <attribute name="action">app.open-handles</attribute>
+ </item>
+ <item>
+ <attribute name="label">Save Handles...</attribute>
+ <attribute name="action">app.save-handles</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label">Save as SVG of PDF...</attribute>
+ <attribute name="action">app.save-image</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label">Show Timings</attribute>
+ <attribute name="action">app.show-timings</attribute>
+ </item>
+ <item>
+ <attribute name="label">Quit</attribute>
+ <attribute name="action">app.quit</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <attribute name="label">Help</attribute>
+ <item>
+ <attribute name="label">About...</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </submenu>
+ </menu>
+</interface>
+)xml";
+
+static GActionEntry the_actions[] =
+{
+ {"open-handles", open_handles, nullptr, nullptr, nullptr},
+ {"save-handles", save_handles, nullptr, nullptr, nullptr},
+ {"save-image", save_cairo, nullptr, nullptr, nullptr},
+ {"show-timings", toggle_action, nullptr, "false", set_show_timings},
+ {"quit", quit, nullptr, nullptr, nullptr},
+ {"about", show_about_dialog, nullptr, nullptr, nullptr},
+};
+
+static GOptionEntry const the_options[] = {
+ {"handles", 'h', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_handles_filename,
+ "Load handle positions from given file", "FILE"},
+ {"spool", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_spool_filename,
+ "Record all interaction to the given file", "FILE"},
+ {"screenshot", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_screenshot_filename,
+ "Take screenshot and exit", nullptr},
+ {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &arg_extra_files,
+ "Additional data files", "FILES..."},
+ {nullptr, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr},
+};
+
+static void activate(GApplication *app, gpointer);
+static void startup(GApplication *app, gpointer);
+
+void init(int argc, char **argv, Toy* t, int width, int height) {
+ the_toy = t;
+ the_requested_width = width;
+ the_requested_height = height;
+
+ std::string app_name = "org.inkscape.lib2geom.toy.";
+ char const *dir_pos = strrchr(argv[0], G_DIR_SEPARATOR);
+ std::string argv_name = dir_pos ? dir_pos + 1 : argv[0];
+
+ // Erase extension for Windows
+ size_t dot_pos = argv_name.rfind('.');
+ if (dot_pos != std::string::npos) {
+ argv_name.erase(dot_pos);
+ }
+ the_toy->name = argv_name;
+ app_name += argv_name;
+
+ GtkApplication* app = gtk_application_new(app_name.c_str(), G_APPLICATION_FLAGS_NONE);
+ g_application_add_main_option_entries(G_APPLICATION(app), the_options);
+ g_action_map_add_action_entries(G_ACTION_MAP(app), the_actions, G_N_ELEMENTS(the_actions), nullptr);
+ g_signal_connect(G_OBJECT(app), "startup", G_CALLBACK(startup), nullptr);
+ g_signal_connect(G_OBJECT(app), "activate", G_CALLBACK(activate), nullptr);
+
+ g_application_run(G_APPLICATION(app), argc, argv);
+ g_object_unref(app);
+}
+
+static void startup(GApplication *app, gpointer) {
+ GtkBuilder *builder = gtk_builder_new_from_string(the_builder_xml, -1);
+ GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
+ gtk_application_set_menubar(GTK_APPLICATION(app), menu);
+ g_object_unref(builder);
+}
+
+static void activate(GApplication *app, gpointer) {
+ if (arg_spool_filename) {
+ the_toy->spool_file = fopen(arg_spool_filename, "w");
+ }
+
+ int const emulated_argc = arg_extra_files ? g_strv_length(arg_extra_files) + 1 : 1;
+ gchar const **emulated_argv = new gchar const*[emulated_argc];
+ emulated_argv[0] = the_toy->name.c_str();
+ for (int i = 1; i < emulated_argc; ++i) {
+ emulated_argv[i] = arg_extra_files[i-1];
+ }
+ the_toy->first_time(emulated_argc, const_cast<char**>(emulated_argv));
+ delete[] emulated_argv;
+
+ if (arg_handles_filename) {
+ FILE *handles_file = fopen(arg_handles_filename, "r");
+ the_toy->load(handles_file);
+ fclose(handles_file);
+ }
+
+ if (arg_screenshot_filename) {
+ write_image(arg_screenshot_filename);
+ g_application_quit(app);
+ return;
+ }
+
+ the_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(g_application_get_default())));
+ gtk_window_set_title(GTK_WINDOW(the_window), the_toy->name.c_str());
+ g_signal_connect(G_OBJECT(the_window), "delete_event", G_CALLBACK(delete_event), NULL);
+
+ the_canvas = gtk_drawing_area_new();
+ gtk_widget_add_events(the_canvas, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK));
+ g_signal_connect(G_OBJECT(the_canvas), "draw", G_CALLBACK(draw_callback), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "scroll-event", G_CALLBACK(scroll_event), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "button-press-event", G_CALLBACK(mouse_event), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "button-release-event", G_CALLBACK(mouse_release_event), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "motion-notify-event", G_CALLBACK(mouse_motion_event), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "key-press-event", G_CALLBACK(key_press_event), 0);
+ g_signal_connect(G_OBJECT(the_canvas), "size-allocate", G_CALLBACK(size_allocate_event), 0);
+
+ gtk_container_add(GTK_CONTAINER(the_window), the_canvas);
+ gtk_window_set_default_size(GTK_WINDOW(the_window), the_requested_width, the_requested_height);
+ gtk_widget_show_all(GTK_WIDGET(the_window));
+
+ // Make sure the canvas can receive key press events.
+ gtk_widget_set_can_focus(the_canvas, TRUE);
+ gtk_widget_grab_focus(the_canvas);
+ assert(gtk_widget_is_focus(the_canvas));
+}
+
+
+void Toggle::draw(cairo_t *cr, bool /*annotes*/) {
+ cairo_pattern_t* source = cairo_get_source(cr);
+ double rc, gc, bc, aa;
+ cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa);
+ cairo_set_source_rgba(cr,0,0,0,1);
+ cairo_rectangle(cr, bounds.left(), bounds.top(),
+ bounds.width(), bounds.height());
+ if(on) {
+ cairo_fill(cr);
+ cairo_set_source_rgba(cr,1,1,1,1);
+ } //else cairo_stroke(cr);
+ cairo_stroke(cr);
+ draw_text(cr, bounds.corner(0) + Geom::Point(5,2), text);
+ cairo_set_source_rgba(cr, rc, gc, bc, aa);
+}
+
+void Toggle::toggle() {
+ on = !on;
+}
+void Toggle::set(bool state) {
+ on = state;
+}
+
+
+void Toggle::handle_click(GdkEventButton* e) {
+ if(bounds.contains(Geom::Point(e->x, e->y)) && e->button == 1) toggle();
+}
+
+void* Toggle::hit(Geom::Point mouse)
+{
+ if (bounds.contains(mouse))
+ {
+ toggle();
+ return this;
+ }
+ return 0;
+}
+
+void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e) {
+ for(auto & t : ts) t.handle_click(e);
+}
+
+void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts) {
+ for(auto & t : ts) t.draw(cr);
+}
+
+
+
+Slider::value_type Slider::value() const
+{
+ Slider::value_type v = m_handle.pos[m_dir] - m_pos[m_dir];
+ v = ((m_max - m_min) / m_length) * v;
+ //std::cerr << "v : " << v << std::endl;
+ if (m_step != 0)
+ {
+ int k = std::floor(v / m_step);
+ v = k * m_step;
+ }
+ v = v + m_min;
+ //std::cerr << "v : " << v << std::endl;
+ return v;
+}
+
+void Slider::value(Slider::value_type _value)
+{
+ if ( _value < m_min ) _value = m_min;
+ if ( _value > m_max ) _value = m_max;
+ if (m_step != 0)
+ {
+ _value = _value - m_min;
+ int k = std::floor(_value / m_step);
+ _value = k * m_step + m_min;
+ }
+ m_handle.pos[m_dir]
+ = (m_length / (m_max - m_min)) * (_value - m_min) + m_pos[m_dir];
+}
+
+void Slider::max_value(Slider::value_type _value)
+{
+ Slider::value_type v = value();
+ m_max = _value;
+ value(v);
+}
+
+void Slider::min_value(Slider::value_type _value)
+{
+ Slider::value_type v = value();
+ m_min = _value;
+ value(v);
+}
+
+// dir = X horizontal slider dir = Y vertical slider
+void Slider::geometry( Geom::Point _pos,
+ Slider::value_type _length,
+ Geom::Dim2 _dir )
+{
+ Slider::value_type v = value();
+ m_pos = _pos;
+ m_length = _length;
+ m_dir = _dir;
+ Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 );
+ m_handle.pos[fix_dir] = m_pos[fix_dir];
+ value(v);
+}
+
+void Slider::draw(cairo_t* cr, bool annotate)
+{
+ cairo_pattern_t* source = cairo_get_source(cr);
+ double rc, gc, bc, aa;
+ cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa);
+ double lw = cairo_get_line_width(cr);
+ std::ostringstream os;
+ os << m_label << ": " << (*m_formatter)(value());
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.7, 1.0);
+ cairo_set_line_width(cr, 0.7);
+ m_handle.draw(cr, annotate);
+ cairo_stroke(cr);
+ cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0);
+ cairo_set_line_width(cr, 0.4);
+ m_handle.draw(cr, annotate);
+ cairo_move_to(cr, m_pos[Geom::X], m_pos[Geom::Y]);
+ Geom::Point offset;
+ if ( m_dir == Geom::X )
+ {
+ cairo_rel_line_to(cr, m_length, 0);
+ offset = Geom::Point(0,5);
+ }
+ else
+ {
+ cairo_rel_line_to(cr, 0, m_length);
+ offset = Geom::Point(5,0);
+ }
+ cairo_stroke(cr);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
+ draw_text(cr, m_pos + offset, os.str().c_str());
+ cairo_set_source_rgba(cr, rc, gc, bc, aa);
+ cairo_set_line_width(cr, lw);
+}
+
+void Slider::move_to(void* hit, Geom::Point om, Geom::Point m)
+{
+ // fix_dir == ! m_dir
+ Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 );
+ m[fix_dir] = m_pos[fix_dir];
+ double diff = m[m_dir] - m_pos[m_dir];
+// if (m_step != 0)
+// {
+// double step = (m_step * m_length) / (m_max - m_min) ;
+// int k = std::floor(diff / step);
+// double v = k * step;
+// m[m_dir] = v + m_pos[m_dir];
+// }
+ if ( diff < 0 ) m[m_dir] = m_pos[m_dir];
+ if ( diff > m_length ) m[m_dir] = m_pos[m_dir] + m_length;
+ m_handle.move_to(hit, om, m);
+}
+
+
+
+void PointHandle::draw(cairo_t *cr, bool /*annotes*/) {
+ draw_circ(cr, pos);
+}
+
+void* PointHandle::hit(Geom::Point mouse) {
+ if(Geom::distance(mouse, pos) < 5)
+ return this;
+ return 0;
+}
+
+void PointHandle::move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point m) {
+ pos = m;
+}
+
+void PointHandle::load(FILE* f) {
+ pos = read_point(f);
+}
+
+void PointHandle::save(FILE* f) {
+ fprintf(f, "%lf %lf\n", pos[0], pos[1]);
+}
+
+void PointSetHandle::draw(cairo_t *cr, bool annotes) {
+ for(unsigned i = 0; i < pts.size(); i++) {
+ draw_circ(cr, pts[i]);
+ if(annotes) draw_number(cr, pts[i], i, name);
+ }
+}
+
+void* PointSetHandle::hit(Geom::Point mouse) {
+ for(auto & pt : pts) {
+ if(Geom::distance(mouse, pt) < 5)
+ return (void*)(&pt);
+ }
+ return 0;
+}
+
+void PointSetHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) {
+ if(hit) {
+ *(Geom::Point*)hit = m;
+ }
+}
+
+void PointSetHandle::load(FILE* f) {
+ int n = 0;
+ assert(1 == fscanf(f, "%d\n", &n));
+ pts.clear();
+ for(int i = 0; i < n; i++) {
+ pts.push_back(read_point(f));
+ }
+}
+
+void PointSetHandle::save(FILE* f) {
+ fprintf(f, "%d\n", (int)pts.size());
+ for(auto & pt : pts) {
+ fprintf(f, "%lf %lf\n", pt[0], pt[1]);
+ }
+}
+
+#include <2geom/bezier-to-sbasis.h>
+
+Geom::D2<Geom::SBasis> PointSetHandle::asBezier() {
+ return handles_to_sbasis(pts.begin(), size()-1);
+}
+
+void RectHandle::draw(cairo_t *cr, bool /*annotes*/) {
+ cairo_rectangle(cr, pos);
+ cairo_stroke(cr);
+ if(show_center_handle) {
+ draw_circ(cr, pos.midpoint());
+ }
+ draw_text(cr, pos.corner(0), name);
+}
+
+void* RectHandle::hit(Geom::Point mouse) {
+ if(show_center_handle) {
+ if(Geom::distance(mouse, pos.midpoint()) < 5)
+ return (void*)(intptr_t)1;
+ }
+ for(int i = 0; i < 4; i++) {
+ if(Geom::distance(mouse, pos.corner(i)) < 5)
+ return (void*)(intptr_t)(2+i);
+ }
+ for(int i = 0; i < 4; i++) {
+ Geom::LineSegment ls(pos.corner(i), pos.corner(i+1));
+ if(Geom::distance(ls.pointAt(ls.nearestTime(mouse)),mouse) < 5)
+ return (void*)(intptr_t)(6+i);
+ }
+ return 0;
+
+}
+
+void RectHandle::move_to(void* hit, Geom::Point om, Geom::Point m) {
+ using Geom::X;
+ using Geom::Y;
+
+ unsigned h = (unsigned)(uintptr_t)(hit);
+ if(h == 1)
+ pos += (m-om);
+ else if(h >= 2 and h <= 5) {// corners
+ int xi = (h-2)& 1;
+ int yi = (h-2)&2;
+ if(yi)
+ xi = 1-xi; // clockwise
+ if (xi) {
+ pos[X].setMax(m[0]);
+ } else {
+ pos[X].setMin(m[0]);
+ }
+ if (yi/2) {
+ pos[Y].setMax(m[1]);
+ } else {
+ pos[Y].setMax(m[1]);
+ }
+ } else if(h >= 6 and h <= 9) {// edges
+ int side, d;
+ switch(h-6) {
+ case 0: d = 1; side = 0; break;
+ case 1: d = 0; side = 1; break;
+ case 2: d = 1; side = 1; break;
+ case 3: d = 0; side = 0; break;
+ }
+ if (side) {
+ pos[d].setMax(m[d]);
+ } else {
+ pos[d].setMin(m[d]);
+ }
+ }
+}
+
+void RectHandle::load(FILE* f) {
+ assert(0 == fscanf(f, "r\n"));
+ for(int i = 0; i < 2; i++) {
+ pos[i] = read_interval(f);
+ }
+
+}
+
+void RectHandle::save(FILE* f) {
+ fprintf(f, "r\n");
+ for(unsigned i = 0; i < 2; i++) {
+ fprintf(f, "%lf %lf\n", pos[i].min(), pos[i].max());
+ }
+}
+
+
+
+/*
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/toy-template.cpp b/src/toys/toy-template.cpp
new file mode 100644
index 0000000..f7e5090
--- /dev/null
+++ b/src/toys/toy-template.cpp
@@ -0,0 +1,35 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework.h>
+
+using namespace Geom;
+
+class MyToy: public Toy {
+ virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
+ //draw code here
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ MyToy () {
+ //Initialization here
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new MyToy());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/toyframework.py b/src/toys/toyframework.py
new file mode 100644
index 0000000..c580c1e
--- /dev/null
+++ b/src/toys/toyframework.py
@@ -0,0 +1,435 @@
+#!/usr/bin/python
+
+import gtk,math
+import pangocairo,cairo
+import gobject
+
+# def draw_text(cr, pos, txt, bottom = False):
+# def draw_number(cr, pos, num):
+
+def draw_circ(cr, (x, y)):
+ cr.new_sub_path()
+ cr.arc(x, y, 3, 0, math.pi*2)
+ cr.stroke()
+def draw_cross(cr, (x, y)):
+ cr.move_to(x-3, y-3)
+ cr.line_to(x+3, y+3)
+ cr.move_to(x-3, y+3)
+ cr.line_to(x+3, y-3)
+ cr.stroke()
+
+class Handle:
+ def __init__(self):
+ pass
+ def draw(self, cr, annotes):
+ pass
+ def hit(self, (x, y)):
+ return None
+ def move_to(self, hit, om, m):
+ pass
+ def scroll(self, pos, dir):
+ pass
+
+class PointHandle(Handle):
+ def __init__(self, x, y, name=""):
+ Handle.__init__(self)
+ self.pos = (x,y)
+ self.name = name
+ def draw(self, cr, annotes):
+ draw_circ(cr, self.pos)
+ if annotes:
+ draw_text(cr, self.pos, str(self.name))
+ def hit(self, mouse):
+ if math.hypot(mouse[0] - self.pos[0], mouse[1] - self.pos[1]) < 5:
+ return 0
+ return None
+ def move_to(self, hit, om, m):
+ self.pos = m
+
+class PointSetHandle(Handle):
+ def __init__(self, pts=None, name=""):
+ Handle.__init__(self)
+ self.pts = pts or []
+ self.name = name
+ def draw(self, cr, annotes):
+ for p in self.pts:
+ draw_circ(cr, p)
+ if annotes:
+ draw_text(cr, p, str(self.name))
+ def hit(self, mouse):
+ for i,p in enumerate(self.pts):
+ if math.hypot(mouse[0] - p[0], mouse[1] - p[1]) < 5:
+ return i
+ return None
+ def move_to(self, hit, om, m):
+ self.pts[hit[1]] = m
+ def append(self, x,y):
+ self.pts.append((x,y))
+
+class Toy:
+ def __init__(self):
+ self.handles = []
+ self.mouse_down = False
+ self.old_mouse = None
+ self.selected = None
+ self.notify = "notify"
+ self.origin = [0,0]
+ self.interactive_level = 0
+ self.transform = None
+
+ def draw(self, cr, (width, height), save):
+ bounds = self.should_draw_bounds()
+ self.transform = cr.get_matrix()
+ self.transform.invert()
+ if bounds == 1:
+ cr.set_source_rgba(0., 0., 0, 0.8)
+ cr.set_line_width (0.5)
+ for i in [1,3]:
+ cr.move_to(0, i*width/4)
+ cr.line_to(width, i*width/4)
+ cr.move_to(i*width/4, 0)
+ cr.line_to(i*width/4, height)
+ elif bounds == 2:
+ cr.set_source_rgba (0., 0., 0, 0.8)
+ cr.set_line_width (0.5)
+ cr.move_to(0, width/2)
+ cr.line_to(width, width/2)
+ cr.move_to(width/2, 0)
+ cr.line_to(width/2, height)
+
+ cr.set_source_rgba (0., 0.5, 0, 1)
+ cr.set_line_width (1)
+ annotes = self.should_draw_numbers()
+ for i,h in enumerate(self.handles):
+ cr.save()
+ if self.selected and i == self.selected[0]:
+ cr.set_source_rgba (0.5, 0, 0, 1)
+ h.draw(cr, annotes)
+ cr.restore()
+
+ cr.set_source_rgba (0., 0.5, 0, 0.8)
+ if self.notify:
+ cr.save()
+ cr.identity_matrix()
+ bnds = draw_text(cr, (0, height), self.notify, True)
+ l,t,w,h = bnds[1]
+ cr.set_source_rgba(1,1,1,0.9)
+ cr.rectangle(l,t+height-h, w, h)
+ cr.fill()
+ cr.set_source_rgba(0,0,0,1)
+ draw_text(cr, (0, height), self.notify, True)
+ cr.restore()
+
+ def mouse_moved(self, e):
+ mouse = (e.x, e.y)
+ if self.transform != None:
+ mouse = self.transform.transform_point(e.x, e.y)
+
+ if e.state & (gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK):
+ self.interactive_level = 2
+ if self.selected:
+ self.handles[self.selected[0]].move_to(self.selected, self.old_mouse, mouse)
+ self.old_mouse = mouse
+ self.canvas.queue_draw()
+ #self.redraw()
+ def mouse_pressed(self, e):
+ self.interactive_level = 1
+ mouse = (e.x, e.y)
+ if self.transform != None:
+ mouse = self.transform.transform_point(e.x, e.y)
+ if e.button == 1:
+ for i,h in enumerate(self.handles):
+ hit = h.hit(mouse)
+ if hit != None:
+ self.selected = (i, hit)
+ self.mouse_down = True
+ self.old_mouse = mouse
+ self.canvas.queue_draw()
+ #self.redraw()
+
+ def mouse_released(self, e):
+ self.interactive_level = 1
+ self.selected = None
+ if e.button == 1:
+ self.mouse_down = False
+ self.canvas.queue_draw()
+ #self.redraw()
+
+ def scroll_event(self, da, ev):
+ self.interactive_level = 1
+ #print 'scroll:'+'\n'.join([str((x, getattr(ev, x))) for x in dir(ev)])
+ #print 'end'
+ da.queue_draw()
+ hit = None
+ for i,h in enumerate(self.handles):
+ hit = h.scroll((ev.x,ev.y), ev.direction)
+ if hit != None:
+ break
+ return hit != None
+
+ def key_hit(self, e):
+ pass
+
+ def should_draw_numbers(self):
+ return True
+ def should_draw_bounds(self):
+ return 0
+
+ def first_time(self, argv):
+ pass
+
+ def gtk_ready(self):
+ pass
+
+ def resize_canvas(self, s):
+ pass
+ def redraw(self):
+ self.window.queue_draw()
+ def get_save_size(self):
+ return (0,0)+self.window.window.get_size()
+ def delete_event(self, window, e):
+ gtk.main_quit()
+ return False
+
+ def expose_event(self, widget, event):
+ cr = widget.window.cairo_create()
+
+ width, height = widget.window.get_size()
+ global resized
+
+ if not resized:
+ alloc_size = ((0, width), (0, height))
+ self.resize_canvas(alloc_size)
+ resized = True
+ cr.translate(self.origin[0], self.origin[1])
+ self.draw(cr, (width, height), False)
+
+ return True
+
+ def mouse_motion_event(self, widget, e):
+ e.x -= self.origin[0]
+ e.y -= self.origin[1]
+ self.mouse_moved(e)
+
+ return False
+
+ def mouse_event(self, widget, e):
+ e.x -= self.origin[0]
+ e.y -= self.origin[1]
+ self.mouse_pressed(e)
+
+ return False
+
+ def mouse_release_event(self, widget, e):
+ e.x -= self.origin[0]
+ e.y -= self.origin[1]
+ self.mouse_released(e)
+
+ return False
+
+ def key_release_event(self, widget, e):
+ self.key_hit(e)
+
+ return False
+
+ def size_allocate_event(self, widget, allocation):
+ alloc_size = ((allocation.x, allocation.x + allocation.width),
+ (allocation.y, allocation.y+allocation.height))
+ self.resize_canvas(alloc_size)
+
+ return False
+
+ def relax_interaction_timeout(self):
+ if self.interactive_level > 0:
+ self.interactive_level -= 1
+ self.canvas.queue_draw()
+ return True
+
+
+class Toggle:
+ def __init__(self):
+ self.bounds = (0,0,0,0)
+ self.text = ""
+ self.on = False
+ def draw(self, cr):
+ cr.set_source_rgba(0,0,0,1)
+ cr.rectangle(bounds.left(), bounds.top(),
+ bounds.width(), bounds.height())
+ if(on):
+ cr.fill()
+ cr.set_source_rgba(1,1,1,1)
+ else:
+ cr.stroke()
+ draw_text(cr, bounds.corner(0) + (5,2), text)
+
+ def toggle(self):
+ self.on = not self.on
+ def set(self, state):
+ self.on = state
+ def handle_click(self, e):
+ if bounds.contains((e.x, e,y)) and e.button == 1:
+ toggle()
+
+def toggle_events(toggles, e):
+ for t in toggles:
+ t.handle_click(e)
+
+def draw_toggles(cr, toggles):
+ for t in toggles:
+ t.draw(cr)
+
+
+current_toys = []
+
+def draw_text(cr, loc, txt, bottom = False, font="Sans 12"):
+ import pango
+ layout = pangocairo.CairoContext.create_layout (cr)
+ layout.set_font_description (pango.FontDescription(font))
+
+ layout.set_text(txt)
+ bounds = layout.get_pixel_extents()
+ cr.move_to(loc[0], loc[1])
+ if bottom:
+ if bottom == "topright":
+ cr.move_to(loc[0] - bounds[1][2],
+ loc[1])
+ elif bottom == "bottomright":
+ cr.move_to(loc[0] - bounds[1][2],
+ loc[1] - bounds[1][3])
+ elif bottom == "topleft":
+ cr.move_to(loc[0],
+ loc[1])
+ else:
+ cr.move_to(loc[0], loc[1] - bounds[1][3])
+ cr.show_layout(layout)
+ return bounds
+
+# Framework Accessors
+
+# Gui Event Callbacks
+
+def make_about(evt):
+ about_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ about_window.set_title("About")
+ about_window.set_resizable(True)
+
+ about_text = gtk.TextView()
+ buf = about_text.get_buffer()
+ buf.set_text("Toy lib2geom application", -1)
+ about_window.add(about_text)
+
+ about_window.show_all()
+
+def save_cairo(evt):
+ d = gtk.FileChooserDialog("Save file as svg, png or pdf",
+ None,
+ gtk.FILE_CHOOSER_ACTION_SAVE,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
+
+ if(d.run() == gtk.RESPONSE_ACCEPT):
+ filename = d.get_filename()
+ cr_s = None
+ left, top, width, height = current_toys[0].get_save_size()
+
+ if filename[-4:] == ".pdf":
+ cr_s = cairo.PDFSurface(filename, width, height)
+ elif filename[-4:] == ".png":
+ cr_s = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height )
+ else:
+ cr_s = cairo.SVGSurface(filename, width, height)
+ cr = cairo.Context(cr_s)
+ cr = pangocairo.CairoContext(cairo.Context(cr_s))
+ cr.translate(-left, -top)
+ current_toys[0].draw(cr, (width, height), True)
+
+ cr.show_page()
+ del cr
+ del cr_s
+ d.destroy()
+
+resized = False
+
+
+def FileMenuAction():
+ pass
+
+ui = """
+<ui>
+ <menubar>
+ <menu name="FileMenu" action="FileMenuAction">
+ <menuitem name="Save" action="save_cairo" />
+ <menuitem name="Quit" action="gtk.main_quit" />
+ <placeholder name="FileMenuAdditions" />
+ </menu>
+ <menu name="HelpMenu" action="HelpMenuAction">
+ <menuitem name="About" action="make_about" />
+ </menu>
+ </menubar>
+</ui>
+"""
+
+def init(argv, t, width, height):
+ global current_toys
+ current_toys.append(t)
+
+ t.first_time(argv)
+
+ t.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ t.window.set_title("title")
+
+# Creates the menu from the menu data above
+ uim = gtk.UIManager()
+ ag = gtk.ActionGroup('menu_actiongroup')
+ ag.add_actions([('FileMenuAction', None, 'File', None, None, None),
+ ('save_cairo', None, 'Save screenshot', None, None, save_cairo),
+ ('gtk.main_quit', None, 'Quit', None, None, gtk.main_quit),
+ ('HelpMenuAction', None, 'Help', None, None, None),
+ ('make_about', None, 'About', None, None, make_about)
+ ])
+ uim.insert_action_group(ag, 0)
+ uim.add_ui_from_string(ui)
+ menu = uim.get_widget("/ui/menubar")
+ # Creates the menu from the menu data above
+ #uim = gtk.UIManager()
+ #uim.add_ui_from_string(ui)
+ #menu = uim.get_widget("ui/menubar")
+
+ t.window.connect("delete_event", t.delete_event)
+
+ t.canvas = gtk.DrawingArea()
+
+ t.canvas.add_events((gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.KEY_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.SCROLL_MASK))
+
+ t.canvas.connect("expose_event", t.expose_event)
+ t.canvas.connect("button_press_event", t.mouse_event)
+ t.canvas.connect("button_release_event", t.mouse_release_event)
+ t.canvas.connect("motion_notify_event", t.mouse_motion_event)
+ t.canvas.connect("key_press_event", t.key_release_event)
+ t.canvas.connect("size-allocate", t.size_allocate_event)
+ t.canvas.connect("scroll_event", t.scroll_event)
+
+ t.vbox = gtk.VBox(False, 0)
+ t.window.add(t.vbox)
+
+ t.vbox.pack_start (menu, False, False, 0)
+
+ pain = gtk.VPaned()
+ t.vbox.pack_start(pain, True, True, 0)
+ pain.add1(t.canvas)
+
+ t.canvas.set_size_request(width, height)
+ t.window.show_all()
+
+ # Make sure the canvas can receive key press events.
+ t.canvas.set_flags(gtk.CAN_FOCUS)
+ t.canvas.grab_focus()
+ assert(t.canvas.is_focus())
+
+ t.gtk_ready()
+ gobject.timeout_add(1000, t.relax_interaction_timeout)
+
+ gtk.main()
+
+def get_vbox():
+ return current_toys[0].vbox
diff --git a/src/toys/uncross.cpp b/src/toys/uncross.cpp
new file mode 100644
index 0000000..aea5438
--- /dev/null
+++ b/src/toys/uncross.cpp
@@ -0,0 +1,500 @@
+#include <iostream>
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/pathvector.h>
+
+#include <cstdlib>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/ord.h>
+using namespace Geom;
+using namespace std;
+
+void draw_rect(cairo_t *cr, Point tl, Point br) {
+ cairo_move_to(cr, tl[X], tl[Y]);
+ cairo_line_to(cr, br[X], tl[Y]);
+ cairo_line_to(cr, br[X], br[Y]);
+ cairo_line_to(cr, tl[X], br[Y]);
+ cairo_close_path(cr);
+}
+
+void draw_bounds(cairo_t *cr, PathVector ps) {
+ srand(0);
+ vector<Rect> bnds;
+ for(auto & p : ps) {
+ for(const auto & it : p) {
+ Rect bounds = (it.boundsFast());
+ bnds.push_back(bounds);
+ cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5);
+ //draw_rect(cr, bounds.min(), bounds.max());
+ cairo_stroke(cr);
+ }
+ }
+ {
+ std::vector<std::vector<unsigned> > res = sweep_bounds(bnds);
+ cairo_set_line_width(cr,0.5);
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for(unsigned i = 0; i < res.size(); i++) {
+ for(unsigned j = 0; j < res[i].size(); j++) {
+ draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ cairo_restore(cr);
+ }
+}
+
+void mark_verts(cairo_t *cr, PathVector ps) {
+ for(auto & p : ps)
+ for(const auto & it : p)
+ draw_cross(cr, it.initialPoint());
+}
+
+int winding(PathVector ps, Point p) {
+ int wind = 0;
+ for(const auto & i : ps)
+ wind += winding(i,p);
+ return wind;
+}
+
+template<typename T>
+//std::vector<T>::iterator
+T* insort(std::vector<T> &v, const T& val)
+{
+ T* iter = upper_bound(v.begin(), v.end(), val);
+
+ unsigned offset = iter - v.begin();
+ v.insert(iter, val);
+
+ return offset + v.begin();
+}
+
+
+class Uncross{
+public:
+ class Piece{
+ public:
+ Rect bounds;
+ Interval parameters;
+ Curve const* curve;
+ D2<SBasis> sb;
+ int mark;
+ int id;
+ };
+ class Crossing{
+ public:
+ vector<int> joins;
+ //double crossing_product; // first cross(d^nA, d^nB) for n that is non-zero, or 0 if the two curves are the same
+ };
+ vector<Rect> rs;
+ PathVector* pths;
+ cairo_t* cr;
+ std::vector<Piece> pieces;
+
+ Uncross(PathVector &pt, cairo_t* cr):pths(&pt), cr(cr) {}
+
+ void build() {
+ cairo_save(cr);
+ PathVector &ps(*pths);
+ for(auto & p : ps) {
+ for(const auto & it : p) {
+ Rect bounds = (it.boundsExact());
+ rs.push_back(bounds);
+ //cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5);
+ //draw_rect(cr, bounds.min(), bounds.max());
+ cairo_stroke(cr);
+ pieces.emplace_back();
+ pieces.back().bounds = bounds;
+ pieces.back().curve = &it;
+ pieces.back().parameters = Interval(0,1);
+ pieces.back().sb = it.toSBasis();
+ }
+ }
+ cairo_restore(cr);
+ }
+
+ struct Event {
+ double x;
+ unsigned ix;
+ bool closing;
+ Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {}
+// Lexicographic ordering by x then closing
+ bool operator<(Event const &other) const {
+ if(x < other.x) return true;
+ if(x > other.x) return false;
+ return closing < other.closing;
+ }
+
+ };
+
+ std::vector<Event> events;
+ std::vector<unsigned> open;
+
+ int cmpy(Piece* a, Piece* b, Interval X) {
+ if(a->bounds[Y].max() < b->bounds[Y].min()) { // bounds are strictly ordered
+ return -1;
+ }
+ if(a->bounds[Y].min() > b->bounds[Y].max()) { // bounds are strictly ordered
+ return 1;
+ }
+ std::vector<std::pair<double, double> > xs;
+ find_intersections(xs, a->sb, b->sb);
+ if(!xs.empty()) {
+ polish_intersections( xs, a->sb, b->sb);
+ // must split around these points to make new Pieces
+ for(auto & x : xs) {
+ std::cout << "cross:" << x.first << " , " << x.second << "\n";
+
+ cairo_save(cr);
+ draw_circ(cr, a->sb(x.first));
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ int ix = events[0].ix;
+ if(0){
+ pop_heap(events.begin(), events.end());
+ events.pop_back();
+ std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix);
+ open.erase(iter);
+ std::cout << "kill\n";
+ }
+
+ }
+ } else { // any point gives an order
+ vector<double> ar = roots(a->sb[0] - X.middle());
+ vector<double> br = roots(b->sb[0] - X.middle());
+ if ((ar.size() == 1) and (br.size() == 1))
+
+ return a->sb[1](ar[0]) < b->sb[1](br[0]);
+ }
+ return 0; // FIXME
+ }
+
+
+ static void
+ draw_interval(cairo_t* cr, Interval I, Point origin, Point /*dir*/) {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.5);
+
+ cairo_move_to(cr, Point(I.min(), -3) + origin);
+ cairo_line_to(cr, Point(I.min(), +3) + origin);
+ cairo_move_to(cr, Point(I.max(), -3) + origin);
+ cairo_line_to(cr, Point(I.max(), +3) + origin);
+
+ cairo_move_to(cr, Point(I.min(), 0) + origin);
+ cairo_line_to(cr, Point(I.min(), 0) + origin);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ }
+ void broke_sweep_bounds() {
+ cairo_save(cr);
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ events.reserve(rs.size()*2);
+ std::vector<std::vector<unsigned> > pairs(rs.size());
+
+ for(unsigned i = 0; i < rs.size(); i++) {
+ events.emplace_back(rs[i].left(), i, false);
+ events.emplace_back(rs[i].right(), i, true);
+ }
+ //std::sort(events.begin(), events.end());
+ std::make_heap(events.begin(), events.end());
+
+ //for(unsigned i = 0; i < events.size(); i++) {
+
+ int i = 0;
+ while(!events.empty()) {
+ unsigned ix = events[0].ix;
+ if(events[0].closing) {
+ std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix);
+ if(iter != open.end()) {
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 0, 1, 0);
+ cairo_set_line_width(cr, 0.25);
+ cairo_rectangle(cr, rs[*iter]);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ open.erase(iter);}
+ } else {
+ draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1));
+ for(unsigned int jx : open) {
+ OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]);
+ if(oiy) {
+ pairs[jx].push_back(ix);
+ std::cout << "oiy:" << *oiy << std::endl;
+ OptInterval oix = intersect(rs[ix][X], rs[jx][X]);
+ if(oix) {
+ std::cout << *oix;
+ std::cout << cmpy(&pieces[ix], &pieces[jx], *oix);
+ continue;
+ }
+ //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ open.push_back(ix);
+ }
+ pop_heap(events.begin(), events.end());
+ events.pop_back();
+ i++;
+ }
+ //return pairs;
+ cairo_restore(cr);
+ }
+
+ void sweep_bounds() {
+ cairo_save(cr);
+
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ events.reserve(rs.size()*2);
+ std::vector<std::vector<unsigned> > pairs(rs.size());
+
+ for(unsigned i = 0; i < rs.size(); i++) {
+ events.emplace_back(rs[i].left(), i, false);
+ events.emplace_back(rs[i].right(), i, true);
+ }
+ std::sort(events.begin(), events.end());
+
+ for(unsigned i = 0; i < events.size(); i++) {
+ unsigned ix = events[i].ix;
+ if(events[i].closing) {
+ std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix);
+ if(iter != open.end()) {
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 0, 1, 0);
+ cairo_set_line_width(cr, 0.25);
+ cairo_rectangle(cr, rs[*iter]);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ open.erase(iter);
+ }
+ } else {
+ draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1));
+ for(unsigned int jx : open) {
+ OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]);
+ if(oiy) {
+ pairs[jx].push_back(ix);
+ std::cout << "oiy:" << *oiy << std::endl;
+ OptInterval oix = intersect(rs[ix][X], rs[jx][X]);
+ if(oix) {
+ std::cout << *oix;
+ std::cout << cmpy(&pieces[ix], &pieces[jx], *oix);
+ continue;
+ }
+ //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ open.push_back(ix);
+ }
+ }
+
+ cairo_restore(cr);
+ }
+
+
+};
+
+class WindingTest: public Toy {
+ PathVector path;
+ PointHandle test_pt_handle;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_set_line_width(cr, 0.5);
+ cairo_save(cr);
+ cairo_path(cr, path);
+ cairo_set_source_rgb(cr, 1, 1, 0);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ //mark_verts(cr, path);
+ cairo_stroke(cr);
+ cairo_set_line_width(cr, 1);
+ //draw_bounds(cr, path);
+ if(0) {
+ Uncross uc(path, cr);
+ uc.build();
+
+ uc.sweep_bounds();
+
+ //draw_bounds(cr, path); mark_verts(cr, path);
+ }
+
+ cairo_save(cr);
+ PathVector &ps(path);
+ vector<Rect> rs;
+ std::vector<Uncross::Piece> pieces;
+ std::vector<Uncross::Crossing> crosses;
+ int id_counter = 0;
+ for(auto & p : ps) {
+ int piece_start = pieces.size();
+ for(Path::iterator it = p.begin(); it != p.end(); ++it) {
+ Rect bounds = (it->boundsExact());
+ rs.push_back(bounds);
+ /*cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5);
+ draw_rect(cr, bounds.min(), bounds.max());
+ cairo_stroke(cr);*/
+ pieces.emplace_back();
+ pieces.back().bounds = bounds;
+ pieces.back().curve = &*it;
+ pieces.back().parameters = Interval(0,1);
+ pieces.back().sb = it->toSBasis();
+ pieces.back().mark = 0;
+ pieces.back().id = id_counter++;
+ if(it != p.begin() and !crosses.empty())
+ crosses.back().joins.push_back(pieces.back().id);
+ crosses.emplace_back();
+ crosses.back().joins.push_back(pieces.back().id);
+ }
+ crosses.back().joins.push_back(pieces[piece_start].id);
+ //crosses[cross_start].joins.push_back(pieces.back().id);
+ }
+ cairo_restore(cr);
+ std::vector<std::vector<unsigned> > prs = sweep_bounds(rs);
+
+ cairo_save(cr);
+ std::vector<Uncross::Piece> new_pieces;
+ for(unsigned i = 0; i < prs.size(); i++) {
+ int ix = i;
+ Uncross::Piece& A = pieces[ix];
+ for(int jx : prs) {
+ Uncross::Piece& B = pieces[jx];
+ cairo_set_source_rgb(cr, 0, 1, 0);
+ draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint());
+ cairo_stroke(cr);
+ cout << ix << ", " << jx << endl;
+ std::vector<std::pair<double, double> > xs;
+ find_intersections(xs, A.sb, B.sb);
+ if(not xs.empty()) {
+ polish_intersections( xs, A.sb, B.sb);
+ // must split around these points to make new Pieces
+ double A_t_prev = 0;
+ double B_t_prev = 0;
+ int A_prec_id = A.id;
+ int B_prec_id = B.id;
+ for(unsigned cv_idx = 0; cv_idx <= xs.size(); cv_idx++) {
+ double A_t = 1;
+ double B_t = 1;
+ if(cv_idx < xs.size()) {
+ A_t = xs[cv_idx].first;
+ B_t = xs[cv_idx].second;
+ }
+
+ cairo_save(cr);
+ draw_circ(cr, A.sb(xs[cv_idx].first));
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ Interval A_slice(A_t_prev, A_t);
+ Interval B_slice(B_t_prev, B_t);
+ if((A_slice.extent() > 0) or (B_slice.extent() > 0)) {
+ cout << "Aslice" <<A_slice << endl;
+ D2<SBasis> Asb = portion(A.sb, A_slice);
+ OptRect Abnds = bounds_exact(Asb);
+ if(Abnds) {
+ new_pieces.emplace_back();
+ new_pieces.back().bounds = *Abnds;
+ new_pieces.back().curve = A.curve;
+ new_pieces.back().parameters = A_slice;
+ new_pieces.back().sb = Asb;
+ new_pieces.back().id = id_counter++;
+ crosses.emplace_back();
+ crosses.back().joins.push_back(A_prec_id);
+ crosses.back().joins.push_back(new_pieces.back().id);
+ A.mark = 1;
+ A_prec_id = new_pieces.back().id;
+ }
+
+ cout << "Bslice" <<B_slice << endl;
+ D2<SBasis> Bsb = portion(B.sb, B_slice);
+ OptRect Bbnds = bounds_exact(Bsb);
+ if(Bbnds) {
+ new_pieces.emplace_back();
+ new_pieces.back().bounds = *Bbnds;
+ new_pieces.back().curve = B.curve;
+ new_pieces.back().parameters = B_slice;
+ new_pieces.back().sb = Bsb;
+ new_pieces.back().id = id_counter++;
+ crosses.emplace_back();
+ crosses.back().joins.push_back(B_prec_id);
+ crosses.back().joins.push_back(new_pieces.back().id);
+ B.mark = 1;
+ B_prec_id = new_pieces.back().id;
+ }
+ }
+ A_t_prev = A_t;
+ B_t_prev = B_t;
+ }
+ }
+ }
+ }
+ if(1)for(unsigned i = 0; i < prs.size(); i++) {
+ if(not pieces[i].mark)
+ new_pieces.push_back(pieces[i]);
+ }
+ cairo_restore(cr);
+
+ for(auto & new_piece : new_pieces) {
+ cout << new_piece.parameters << ", " <<new_piece.id <<endl;
+ cairo_save(cr);
+ cairo_rectangle(cr, new_piece.bounds);
+ cairo_set_source_rgba(cr, 0,1,0,0.1);
+ cairo_fill(cr);
+ cairo_set_source_rgba(cr, 0.3,0.3,0,0.1);
+ cairo_rectangle(cr, new_piece.bounds);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ cairo_d2_sb(cr, new_piece.sb);
+ cairo_stroke(cr);
+ }
+
+
+ cout << "crossings:";
+ for(auto & cr : crosses) {
+ for(int join : cr.joins) {
+ cout << join << ", ";
+ }
+ cout << endl;
+ }
+
+ std::streambuf* cout_buffer = std::cout.rdbuf();
+ std::cout.rdbuf(notify->rdbuf());
+ *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n";
+ std::cout.rdbuf(cout_buffer);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ WindingTest () : test_pt_handle(300,300) {}
+ void first_time(int argc, char** argv) override {
+ const char *path_name="winding.svgd";
+ if(argc > 1)
+ path_name = argv[1];
+ path = read_svgd(path_name);
+ OptRect bounds = bounds_exact(path);
+ if (bounds) {
+ path *= Translate(Point(10,10) - bounds->min());
+ }
+
+ handles.push_back(&test_pt_handle);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new WindingTest());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/winding-test.cpp b/src/toys/winding-test.cpp
new file mode 100644
index 0000000..a0f7608
--- /dev/null
+++ b/src/toys/winding-test.cpp
@@ -0,0 +1,107 @@
+#include <2geom/path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/path-intersection.h>
+
+#include <iostream>
+#include <cstdlib>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+#include <2geom/ord.h>
+using namespace Geom;
+
+void draw_rect(cairo_t *cr, Point tl, Point br) {
+ cairo_move_to(cr, tl[X], tl[Y]);
+ cairo_line_to(cr, br[X], tl[Y]);
+ cairo_line_to(cr, br[X], br[Y]);
+ cairo_line_to(cr, tl[X], br[Y]);
+ cairo_close_path(cr);
+}
+
+void draw_bounds(cairo_t *cr, PathVector ps) {
+ srand(0);
+ vector<Rect> bnds;
+ for(auto & p : ps) {
+ for(const auto & it : p) {
+ Rect bounds = (it.boundsFast());
+ bnds.push_back(bounds);
+ cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5);
+ //draw_rect(cr, bounds.min(), bounds.max());
+ cairo_stroke(cr);
+ }
+ }
+ {
+ std::vector<std::vector<unsigned> > res = sweep_bounds(bnds);
+ cairo_set_line_width(cr,0.5);
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, 1, 0, 0);
+ for(unsigned i = 0; i < res.size(); i++) {
+ for(unsigned j = 0; j < res[i].size(); j++) {
+ draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint());
+ cairo_stroke(cr);
+ }
+ }
+ cairo_restore(cr);
+ }
+}
+
+void mark_verts(cairo_t *cr, PathVector ps) {
+ for(auto & p : ps)
+ for(const auto & it : p)
+ draw_cross(cr, it.initialPoint());
+}
+
+int winding(PathVector ps, Point p) {
+ int wind = 0;
+ for(const auto & i : ps)
+ wind += winding(i,p);
+ return wind;
+}
+
+class WindingTest: public Toy {
+ PathVector path;
+ PointHandle test_pt_handle;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_path(cr, path);
+ cairo_stroke(cr);
+ mark_verts(cr, path);
+ draw_bounds(cr, path);
+
+ //draw_bounds(cr, path); mark_verts(cr, path);
+
+ std::streambuf* cout_buffer = std::cout.rdbuf();
+ std::cout.rdbuf(notify->rdbuf());
+ *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n";
+ std::cout.rdbuf(cout_buffer);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+ public:
+ WindingTest () : test_pt_handle(300,300) {}
+ void first_time(int argc, char** argv) override {
+ const char *path_name="winding.svgd";
+ if(argc > 1)
+ path_name = argv[1];
+ path = read_svgd(path_name);
+
+ handles.push_back(&test_pt_handle);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new WindingTest());
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/worms.cpp b/src/toys/worms.cpp
new file mode 100644
index 0000000..81e7558
--- /dev/null
+++ b/src/toys/worms.cpp
@@ -0,0 +1,138 @@
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis-math.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+//Random walkers toy, written by mgsloan, initially for a school video proj.
+
+using namespace Geom;
+
+static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double min, double max, double space=10){
+ for( double t = min; t < max; t += space) {
+ Point pos = M(t), perp = M.valueAndDerivatives(t, 2)[1].cw() * 3;
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_stroke(cr);
+}
+
+D2<SBasis> random_d2() {
+ D2<SBasis> ret(SBasis(6, Linear()),
+ SBasis(6, Linear()));
+ ret[0][0] = Linear(uniform()*720, uniform()*720);
+ ret[1][0] = Linear(uniform()*480, uniform()*480);
+
+ int mul = 1;
+ for(int i = 1; i < 6; i++) {
+ ret[0][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000);
+ ret[1][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000);
+ mul*=2;
+ }
+ return ret;
+}
+
+class Worm {
+ Piecewise<D2<SBasis> > path;
+ int spawn_time, last_time;
+ double red, green, blue, length;
+ public:
+ void tele(int t) {
+ Piecewise<D2<SBasis> > new_path(portion(path, 0, t - last_time));
+ new_path.push(random_d2(), path.domain().max()+1);
+ path = arc_length_parametrization(new_path);
+ }
+ void add_section(const D2<SBasis> x) {
+ Piecewise<D2<SBasis> > new_path(path);
+ D2<SBasis> seg(x);
+ seg[0][0][0] = path.segs.back()[0][0][1];
+ seg[1][0][0] = path.segs.back()[1][0][1];
+ new_path.push(seg, path.domain().max()+1);
+ path = arc_length_parametrization(new_path);
+ }
+ Worm (int t, double r, double g, double b, double l) : spawn_time(t), last_time(t), red(r), green(g), blue(b), length(l) {
+ path = Piecewise<D2<SBasis> >(random_d2());
+ add_section(random_d2());
+ }
+ void draw(cairo_t *cr, int t) {
+ if(t - last_time > path.domain().max()) add_section(random_d2());
+ if(t - last_time - length > path.cuts[1]) {
+ Piecewise<D2<SBasis> > new_path;
+ new_path.push_cut(0);
+ for(unsigned i = 1; i < path.size(); i++) {
+ new_path.push(path[i], path.cuts[i+1] - path.cuts[1]);
+ }
+ last_time = t - length;
+ path = new_path;
+ }
+ cairo_set_source_rgb(cr, red, green, blue);
+ Piecewise<D2<SBasis> > port = portion(path, std::max(t - last_time - length, 0.), t - last_time);
+ cairo_pw_d2_sb(cr, port);
+ cairo_stroke(cr);
+
+ double d = 4;
+ cairo_set_dash(cr, &d, 1, 0);
+ for(unsigned i = 1; i < path.size(); i++) {
+ if(path[i].at0() != path[i-1].at1()) {
+ draw_line_seg(cr, path[i].at0(), path[i-1].at1());
+ }
+ }
+ cairo_stroke(cr);
+ cairo_set_dash(cr, &d, 0, 0);
+
+ cairo_set_source_rgb(cr, 0., 0., 1.);
+ dot_plot(cr, path, std::max(t - last_time - length, 0.), t - last_time);
+ }
+ void reverse_direction(int t) {
+ path = portion(path, 0, t - last_time);
+ D2<SBasis> seg = random_d2(), last = path[path.size()-1];
+ for(unsigned c = 0; c < 2; c++)
+ for(unsigned d = 1; d < seg[c].size() && d < last[c].size(); d++)
+ seg[c][d][0] = -last[c][d][1];
+ add_section(seg);
+ }
+};
+
+class Intro: public Toy {
+ int t;
+ vector<Worm> worms;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+ t++;
+ if(t < 40 && t % 2 == 0) {
+ worms.emplace_back(t, uniform(), uniform(), uniform(), uniform() * 200 + 50);
+ }
+
+ for(auto & worm : worms) {
+ worm.draw(cr, t);
+ if(uniform() > .999) worm.tele(t);
+ }
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ redraw();
+ }
+
+ int should_draw_bounds() override { return 0; }
+
+ public:
+ Intro () {
+ t = 0;
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Intro(), 720, 480);
+ 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
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 :
diff --git a/tools/lib2geom_gdb.py b/tools/lib2geom_gdb.py
new file mode 100644
index 0000000..f98421c
--- /dev/null
+++ b/tools/lib2geom_gdb.py
@@ -0,0 +1,107 @@
+import gdb.printing
+import math
+
+def xstr(val):
+ if val.is_optimized_out:
+ return "<optimized out>"
+ else:
+ return str(val)
+
+class GeomPointPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return "(" + xstr(self.val["_pt"][0]) + ", " + xstr(self.val["_pt"][1]) + ")"
+
+class GeomIntervalPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return "[" + xstr(self.val["_b"][0]) + ", " + xstr(self.val["_b"][1]) + ")"
+
+class GeomOptIntervalPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ if self.val["m_initialized"]:
+ addr = self.val["m_storage"]["dummy_"]["data"].address
+ if self.val.type == gdb.lookup_type('Geom::OptInterval'):
+ return str(addr.cast(gdb.lookup_type('Geom::Interval').pointer()).dereference())
+ else:
+ return str(addr.cast(gdb.lookup_type('Geom::GenericInterval<int>').pointer()).dereference())
+ else:
+ return "empty"
+
+class GeomRectPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return str(self.val["f"][0]) + " x " + str(self.val["f"][1])
+
+class GeomOptRectPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ if self.val["m_initialized"]:
+ addr = self.val["m_storage"]["dummy_"]["data"].address
+ if self.val.type == gdb.lookup_type('Geom::OptRect'):
+ return str(addr.cast(gdb.lookup_type('Geom::Rect').pointer()).dereference())
+ else:
+ return str(addr.cast(gdb.lookup_type('Geom::GenericRect<int>').pointer()).dereference())
+ else:
+ return "empty"
+
+class GeomAffinePrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return ("\n | %8f %8f 0 |" +
+ "\n | %8f %8f 0 |" +
+ "\n | %8f %8f 1 |") % tuple(self.val["_c"][x] for x in range(0,6))
+
+class GeomTranslatePrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return "translate by " + str(self.val["vec"])
+
+class GeomScalePrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return "scale by " + str(self.val["vec"])
+
+class GeomRotatePrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ x = self.val["vec"][0]
+ y = self.val["vec"][1]
+ angle = math.atan2(y, x) / math.pi * 180.0
+ return "rotate by %s = %f degrees" % (str(self.val["vec"]), angle)
+
+class GeomZoomPrinter:
+ def __init__(self, val):
+ self.val = val
+ def to_string(self):
+ return "zoom %f%% at %s" % (self.val["_scale"] * 100, str(self.val["_trans"]))
+
+
+def lib2geom_pretty_printer():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter("lib2geom")
+ pp.add_printer('Geom::Point', '^Geom::(Int)?Point$', GeomPointPrinter)
+ pp.add_printer('Geom::Interval', '^(Geom::Interval|Geom::GenericInterval<int>)$', GeomIntervalPrinter)
+ pp.add_printer('Geom::OptInterval', '^(Geom::OptInterval|Geom::GenericOptInterval<int>)$', GeomOptIntervalPrinter)
+ pp.add_printer('Geom::Rect', '^(Geom::Rect|Geom::GenericRect<int>)$', GeomRectPrinter)
+ pp.add_printer('Geom::OptRect', '^(Geom::OptRect|Geom::GenericOptRect<int>)$', GeomOptRectPrinter)
+ pp.add_printer('Geom::Affine', '^Geom::Affine$', GeomAffinePrinter)
+ pp.add_printer('Geom::Translate', '^Geom::Translate$', GeomTranslatePrinter)
+ pp.add_printer('Geom::Scale', '^Geom::Scale', GeomScalePrinter)
+ pp.add_printer('Geom::Rotate', '^Geom::Rotate$', GeomRotatePrinter)
+ pp.add_printer('Geom::Zoom', '^Geom::Zoom$', GeomZoomPrinter)
+ return pp
+
+def register_lib2geom_printers():
+ gdb.printing.register_pretty_printer(
+ gdb.current_objfile(),
+ lib2geom_pretty_printer())
diff --git a/toy.pc.in b/toy.pc.in
new file mode 100644
index 0000000..e2e64f7
--- /dev/null
+++ b/toy.pc.in
@@ -0,0 +1,13 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+
+Name: toy
+Description: A library for creating toy apps for 2geom
+Version: @2GEOM_VERSION@
+
+Requires: 2geom
+Libs: -L${libdir} -l2geom
+Cflags: -I${includedir}/2geom
+