├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── RELEASE-NOTES.md
├── dist
├── index-esm.js
├── index-esm.min.js
├── index-esm.min.js.map
├── index-umd.js
├── index-umd.min.js
└── index-umd.min.js.map
├── docs
├── ShapeInfo.md
├── ShapesApi.md
├── SvgShapesApi.md
├── images
│ ├── example-1.png
│ ├── example-2.png
│ └── example-3.png
└── jsdoc-config.js
├── examples
├── example-1.js
├── example-1.svg
├── example-2.js
├── example-2.svg
├── example-3.js
├── example-3.svg
├── path-line.js
├── path-line.svg
├── rotate-rectangle-line.js
├── rotate-rectangle-line.svg
├── rotated-ellipse-line.js
├── rotated-ellipse-line.svg
├── tessellate-cubic-beziers.js
└── tessellate-cubic-beziers.svg
├── index.js
├── lgtm.yml
├── lib
├── AffineShapes.js
├── Intersection.js
├── IntersectionQuery.js
├── PathHandler.js
├── ShapeInfo.js
├── Shapes.js
└── SvgShapes.js
├── package-lock.json
├── package.json
├── reference
├── Bezout.m
├── Cubic-Cubic Failure Case.nb
├── Cubic-Cubic Intersection.nb
└── Quad-Quad Intersection.nb
├── rollup.config.js
├── scratchpad
├── arc-angle-test.js
├── arc-fail.js
├── arc-test-2.js
├── arc-test.js
├── cubic-cubic-fail.js
├── find_parameters.js
├── shapeInfo-test.js
├── temp.svg
└── visual-test-conversion
│ ├── convert
│ └── transform_visual_test.xsl
├── test
├── Itertools.js
├── affine-shapes-test.js
├── intersection-tests.js
├── shape-info-test.js
└── shapes-test.js
└── visual-test
├── arc-fail.svg
├── bezier2_bezier2.svg
├── bezier2_bezier3.svg
├── bezier2_circle.svg
├── bezier2_ellipse.svg
├── bezier2_line.svg
├── bezier2_polygon.svg
├── bezier2_rect.svg
├── bezier3_bezier3.svg
├── bezier3_circle.svg
├── bezier3_ellipse.svg
├── bezier3_line.svg
├── bezier3_polygon.svg
├── bezier3_rect.svg
├── circle_circle.svg
├── circle_ellipse.svg
├── circle_line.svg
├── circle_polygon.svg
├── circle_rect.svg
├── ellipse_ellipse.svg
├── ellipse_line.svg
├── ellipse_polygon.svg
├── ellipse_rect.svg
├── index.html
├── line_line.svg
├── line_polygon.svg
├── line_rect.svg
├── line_rounded_rect.svg
├── line_rounded_rect_2.svg
├── path_circle.svg
├── path_ellipse.svg
├── path_line.svg
├── path_path.svg
├── path_polygon.svg
├── path_rect.svg
├── polygon_polygon.svg
├── polygon_rectangle.svg
├── rect_rect.svg
├── rel_path_circle.svg
├── rel_path_ellipse.svg
├── rel_path_line.svg
├── rel_path_path.svg
├── rel_path_polygon.svg
├── rel_path_rect.svg
└── show_intersections.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env"]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | docs/jsdoc
4 | examples
5 | scratchpad
6 | test
7 | visual-test
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | "env": {
5 | "browser": true,
6 | "es6": true,
7 | "node": true,
8 | "mocha": true
9 | },
10 | "extends": ["ash-nazg/sauron-node"],
11 | "parserOptions": {
12 | "ecmaVersion": 2018,
13 | "sourceType": "module"
14 | },
15 | "settings": {
16 | "polyfills": [
17 | "console",
18 | "JSON",
19 | "Array.isArray"
20 | ]
21 | },
22 | "overrides": [
23 | {
24 | files: "docs/jsdoc-config.js",
25 | globals: {
26 | "module": "readonly"
27 | },
28 | rules: {
29 | strict: "off",
30 | "import/unambiguous": "off",
31 | "import/no-commonjs": "off"
32 | }
33 | },
34 | {
35 | files: ["**/*.md"],
36 | settings: {
37 | polyfills: [
38 | "console",
39 | "document.querySelector"
40 | ]
41 | },
42 | rules: {
43 | "eol-last": "off",
44 | "no-console": "off",
45 | "no-undef": "off",
46 | "padded-blocks": "off",
47 | "import/unambiguous": "off",
48 | "import/no-unresolved": "off",
49 | "node/no-missing-import": "off",
50 | "import/no-commonjs": "off",
51 | "no-multi-spaces": "off",
52 | "no-unused-vars": ["error", {
53 | "varsIgnorePattern": "Point2D|ShapeInfo|Intersection|result|centers|radii|arcs|points|beziers|circles|ellipses|lines|paths|polylines|polygons|rectangles"
54 | }],
55 | "node/no-missing-require": ["error", {
56 | "allowModules": ["kld-intersections"]
57 | }]
58 | }
59 | }
60 | ],
61 | "rules": {
62 | "indent": [
63 | "error",
64 | 4,
65 | {"SwitchCase": 1}
66 | ],
67 | "quotes": [
68 | "error",
69 | "double"
70 | ],
71 | "space-before-function-paren": [
72 | "error",
73 | "never"
74 | ],
75 | "brace-style": [
76 | "error",
77 | "stroustrup"
78 | ],
79 | "arrow-parens": [
80 | "error",
81 | "as-needed"
82 | ],
83 | "no-multiple-empty-lines": "off",
84 | "max-len": "off",
85 | "node/exports-style": "off",
86 | "unicorn/no-zero-fractions": "off",
87 | "require-unicode-regexp": "off",
88 | "yoda": "off",
89 | "valid-jsdoc": 0
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | .idea
4 |
5 | node_modules
6 | npm-debug.log
7 |
8 | docs/jsdoc
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs/jsdoc
2 | examples
3 | scratchpad
4 | test
5 | visual-test
6 | rollup.config.js
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Kevin Lindsey
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | Neither the name of the copyright holder nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kld-intersections
2 |
3 | - [Installation](#installation)
4 | - [Importing](#importing)
5 | - [Usage](#usage)
6 | - [Queries](#queries)
7 | - [Known Issues](#known-issues)
8 | - [Links and Related Projects](#links-and-related-projects)
9 |
10 | ---
11 |
12 | A library of intersection algorithms covering all permutations for any two of the following SVG shapes:
13 |
14 | - Arcs
15 | - Quadratic Bézier
16 | - Cubic Bézier
17 | - Circle
18 | - Ellipse
19 | - Line
20 | - Paths
21 | - Polygon
22 | - Polyline
23 | - Rectangle
24 |
25 | # Installation
26 |
27 | ```
28 | npm install kld-intersections
29 | ```
30 |
31 | # Importing
32 |
33 | The following sections indicate how you can import the code for use in various environments.
34 |
35 | ## Node
36 |
37 | ```javascript
38 | const {ShapeInfo, Intersection} = require("kld-intersections");
39 | ```
40 |
41 | ## Browsers
42 |
43 | ```html
44 |
45 |
49 | ```
50 |
51 | ## Modern Browsers (ESM)
52 |
53 | ```javascript
54 | import {ShapeInfo, Intersection} from "./node_modules/kld-intersections/dist/index-esm.js";
55 | ```
56 |
57 | ## Bundlers
58 |
59 | ```javascript
60 | import {ShapeInfo, Intersection} from "kld-intersections";
61 | ```
62 |
63 | ## Usage
64 |
65 | In order to perform an intersection, you need to create descriptions of each shape to intersect. This is done using ShapeInfo.
66 |
67 | Once you have created your ShapeInfos, pass them into `Intersection.intersect` and you will get back an `Intersection` object. Intersection objects contain the intersections, an array of Point2D instances, in their `points` property.
68 |
69 | The following example creates a path (from SVG path data) and a line. The two shapes are passed into `Intersection.intersect` and the results are displayed in the console.
70 |
71 | ```javascript
72 | const {ShapeInfo, Intersection} = require("kld-intersections");
73 |
74 | const path = ShapeInfo.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
75 | const line = ShapeInfo.line([15, 75], [355, 140]);
76 | const intersections = Intersection.intersect(path, line);
77 |
78 | intersections.points.forEach(console.log);
79 | ```
80 |
81 | Each of the shape constructors in ShapeInfo supports a wide variety of formats. Be sure to look at the examples in the  docs to get an idea of how you can define shapes.
82 |
83 | Note that there are some older APIs that have been deprecated. If you need to work with those APIs or wish to use the Intersection methods directly, you can read about those in [Shapes API](#docs/ShapesApi.md).
84 |
85 | # Queries
86 |
87 | In the original intersection code written for kevlindev.com, there were some functions that allowed one to determine if a point was contained within a given shape. That code has been extracted into a separate class named `IntersectionQuery`. Those methods are currently limited to the following list:
88 |
89 | * IntersectionQuery.pointInCircle(**point** : Point2D, **center** : Point2D, **radius** : number)
90 | * IntersectionQuery.pointInEllipse(**point** : Point2D, **center** : Point2D, **radiusX** : number, **radiusY** : number)
91 | * IntersectionQuery.pointInPolyline(**point** : Point2D, **points** : Array)
92 | * IntersectionQuery.pointInPolygon(**point** : Point2D, **points** : Array)
93 | * IntersectionQuery.pointInRectangle(**point** : Point2D, **topLeft** : Point2D, **bottomRight** : Point2D)
94 |
95 | The first argument is the point you wish to test. The remaining parameters describe the shape to test against. All methods return a boolean value indicating whether or not the given point is contained within the shape.
96 |
97 | Note that currently bézier curves are not included in this list. As a workaround, bézier shapes can be approximated using polylines and then tested with `pointInPolyline` or `pointInPolygon`. See [tessellate-cubic-beziers.js](examples/tessellate-cubic-beziers.js) as an example of how you might tesselate bézier curves for this purpose.
98 |
99 | # Known Issues
100 |
101 | Please note that the bézier-bézier intersections may not be well-behaved. There is work being done to make these more stable, but no eta is available at this time. As a workaround, bézier shapes can be approximated using polylines and then intersected with an appropriate polyline intersection method. See [tessellate-cubic-beziers.js](examples/tessellate-cubic-beziers.js) as an example of how you might do this.
102 |
103 | # Links and Related Projects
104 |
105 | - [http://www.quazistax.com](http://www.quazistax.com/testIntersection.html)
106 | - [kld-path-parser](https://github.com/thelonious/kld-path-parser)
107 | - [kld-transform-parser](https://github.com/thelonious/kld-transform-parser)
108 | - [kld-affine](https://github.com/thelonious/kld-affine)
109 | - [kld-polynomial](https://github.com/thelonious/kld-polynomial)
110 | - [kld-contours](https://github.com/thelonious/kld-contours)
111 | - [kld-data-transformer](https://github.com/thelonious/kld-data-transformer)
112 |
--------------------------------------------------------------------------------
/RELEASE-NOTES.md:
--------------------------------------------------------------------------------
1 | - [0.6.0](#0.6.0)
2 | - [0.5.2](#0.5.2)
3 |
4 | ---
5 |
6 | # 0.6.0
7 |
8 | - Rename IntersectionArgs to ShapeInfo
9 | - Move Shape and AffineShape functionality into ShapeInfo
10 | - Deprecate IntersectionArgs, Shape, and AffineShape classes
11 |
12 | # 0.5.2
13 |
14 | - Removed console logging from Intersection.js
15 |
--------------------------------------------------------------------------------
/docs/ShapeInfo.md:
--------------------------------------------------------------------------------
1 | # Shape Info
2 |
3 | - [Arc](#arc)
4 | - [Arc Examples](#arc-examples)
5 | - [Quadratic Bezier](#quadratic-bezier)
6 | - [Quadratic Bezier Examples](#quadratic-bezier-examples)
7 | - [Cubic Bezier](#cubic-bezier)
8 | - [Cubic Bezier Examples](#cubic-bezier-examples)
9 | - [Circle](#circle)
10 | - [Circle Examples](#circle-examples)
11 | - [Ellipse](#ellipse)
12 | - [Ellipse Examples](#ellipse-examples)
13 | - [Line](#line)
14 | - [Line Examples](#line-examples)
15 | - [Path](#path)
16 | - [Path Examples](#path-examples)
17 | - [Polygon](#polygon)
18 | - [Polygon Examples](#polygon-examples)
19 | - [Polyline](#polyline)
20 | - [Polyline Examples](#polyline-examples)
21 | - [Rectangle](#rectangle)
22 | - [Rectangle Examples](#rectangle-examples)
23 |
24 | ---
25 |
26 | This document describes how to use the ShapeInfo class to describe shapes for intersection. This class is built up of the following shape creation methods:
27 |
28 | - ShapeInfo.arc(**...**)
29 | - ShapeInfo.quadraticBezier(**...**)
30 | - ShapeInfo.cubicBezier(**...**)
31 | - ShapeInfo.circle(**...**)
32 | - ShapeInfo.ellipse(**...**)
33 | - ShapeInfo.line(**...**)
34 | - ShapeInfo.path(**...**)
35 | - ShapeInfo.polygon(**...**)
36 | - ShapeInfo.polyline(**...**)
37 | - ShapeInfo.rectangle(**...**)
38 |
39 | In place of **...**, you can pass in one of the following:
40 |
41 | - An object describing the shape
42 | - An array describing the shape
43 | - A list of arguments, describing the shape. This is the same as the array format; however, the array's elements are spread into the method call.
44 |
45 | Each section below gives a description of the various formats that may be used to describe a given shape type.
46 |
47 | # Arc
48 |
49 | An arc is defined by:
50 |
51 | - A center point
52 | - An x radius and a y radius
53 | - A start angle
54 | - An end angle
55 |
56 | ## A Center Point
57 |
58 | The center may be represented four ways:
59 |
60 | - A `center` property that is an object with `x` and `y` number properties
61 | - A `center` property that is an array with two number elements, `x` being the first element and `y` being the second element
62 | - A `cx` number property and a `cy` number property
63 | - A `centerX` number property and a `centerY` number property
64 |
65 | ### Center Examples
66 |
67 | ```javascript
68 | const centers = [
69 | {center: new Point2D(10, 20)},
70 | {center: {x: 10, y: 20}},
71 | {center: [10, 20]},
72 | {cx: 10, cy: 20},
73 | {centerX: 10, centerY: 20}
74 | ];
75 | ```
76 |
77 | ## An X Radius and a Y Radius
78 |
79 | The radii may be represented four ways:
80 |
81 | - A `radii` property that is an object with `x` and `y` number properties
82 | - A `radii` property that is an array with two number elements, `rx` being the first element and `ry` being the second element
83 | - An `rx` number property and an `ry` number property
84 | - A `radiusX` number property and a `radiusY` number property
85 |
86 | ### X and Y Radii Examples
87 |
88 | ```javascript
89 | const radii = [
90 | {radii: {x: 5, y: 10}},
91 | {radii: [5, 10]},
92 | {rx: 5, ry: 10},
93 | {radiusX: 5, radiusY: 10}
94 | ];
95 | ```
96 |
97 | ## A start angle
98 |
99 | The starting angle is a `startAngle` number property. This value is measured in radians.
100 |
101 | ## An end angle
102 |
103 | The ending angle is a `endAngle` number property. This value is measured in radians.
104 |
105 | ## Array input
106 |
107 | The above discussed how an object may be used to describe an Arc. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
108 |
109 | There are two supported formats:
110 |
111 | - A 6 element array defined as follows:
112 | - a number element being `centerX`
113 | - a number element being `centerY`
114 | - a number element being `radiusX`
115 | - a number element being `radiusY`
116 | - a number element being `startRadians`
117 | - a number element being `endRadians`
118 | - A 5 element array defined as follows:
119 | - an object with `x` and `y` number properties being the `center`
120 | - a number element being `radiusX`
121 | - a number element being `radiusY`
122 | - a number element being `startRadians`
123 | - a number element being `endRadians`
124 |
125 | ## Arc Examples
126 |
127 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
128 |
129 | ```javascript
130 | const arcs = [
131 | ShapeInfo.arc({center: {x: 10, y: 20}, radii: {x: 5, y: 10}, startAngle: 0, endAngle: Math.PI}),
132 | ShapeInfo.arc({center: [10, 20], radii: [5, 10], startAngle: 0, endAngle: Math.PI}),
133 | ShapeInfo.arc({cx: 10, cy: 20, rx: 5, ry: 10, startAngle: 0, endAngle: Math.PI}),
134 | ShapeInfo.arc({centerX: 10, centerY: 20, radiusX: 5, radiusY: 10, startAngle: 0, endAngle: Math.PI}),
135 | ShapeInfo.arc([10, 20, 5, 10, 0, Math.PI]),
136 | ShapeInfo.arc([{x: 10, y: 20}, 5, 10, 0, Math.PI]),
137 | ShapeInfo.arc(10, 20, 5, 10, 0, Math.PI),
138 | ShapeInfo.arc({x: 10, y: 20}, 5, 10, 0, Math.PI)
139 | ];
140 | ```
141 |
142 | # Quadratic Bezier
143 |
144 | A quadratic bezier is defined by:
145 |
146 | - 3 control points: p1, p2, and p3
147 |
148 | ## Control Points
149 |
150 | A control point may be represented two ways:
151 |
152 | - An object with `x` and `y` number properties
153 | - An array with two number elements, `x` being the first element and `y` being the second element
154 |
155 | ### Control Point Examples
156 |
157 | ```javascript
158 | const points = [
159 | {p1: new Point2D(10, 20)},
160 | {p2: {x: 10, y: 20}},
161 | {p3: [10, 20]}
162 | ];
163 | ```
164 |
165 | ## Array input
166 |
167 | The above discussed how an object may be used to describe a Quadratic Bezier. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
168 |
169 | There are two supported formats:
170 |
171 | - A 6 element array defined as follows:
172 | - a number element being `p1.x`
173 | - a number element being `p1.y`
174 | - a number element being `p2.x`
175 | - a number element being `p2.y`
176 | - a number element being `p3.x`
177 | - a number element being `p3.y`
178 | - A 3 element array defined as follows:
179 | - an object with `x` and `y` number properties being `p1`
180 | - an object with `x` and `y` number properties being `p2`
181 | - an object with `x` and `y` number properties being `p3`
182 |
183 | ## Quadratic Bezier Examples
184 |
185 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
186 |
187 | ```javascript
188 | const beziers = [
189 | ShapeInfo.quadraticBezier({p1: {x: 10, y: 20}, p2: {x: 5, y: 10}, p3: {x: 15, y: 30}}),
190 | ShapeInfo.quadraticBezier({p1: [10, 20], p2: [5, 10], p3: [15, 30]}),
191 | ShapeInfo.quadraticBezier([10, 20, 5, 10, 15, 30]),
192 | ShapeInfo.quadraticBezier([{x: 10, y: 20}, {x: 5, y: 10}, {x: 15, y: 30}]),
193 | ShapeInfo.quadraticBezier(10, 20, 5, 10, 15, 30),
194 | ShapeInfo.quadraticBezier({x: 10, y: 20}, {x: 5, y: 10}, {x: 15, y: 30})
195 | ];
196 | ```
197 |
198 | # Cubic Bezier
199 |
200 | A cubic bezier is defined by:
201 |
202 | - 4 control points: p1, p2, p3, and p4
203 |
204 | ## Control Points
205 |
206 | A control point may be represented two ways:
207 |
208 | - An object with `x` and `y` number properties
209 | - An array with two number elements, `x` being the first element and `y` being the second element
210 |
211 | ### Control Point Examples
212 |
213 | ```javascript
214 | const points = [
215 | {p1: new Point2D(10, 20)},
216 | {p2: {x: 10, y: 20}},
217 | {p3: [10, 20]}
218 | ];
219 | ```
220 |
221 | ## Array input
222 |
223 | The above discussed how an object may be used to describe a Quadratic Bezier. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
224 |
225 | There are two supported formats:
226 |
227 | - A 8 element array defined as follows:
228 | - a number element being `p1.x`
229 | - a number element being `p1.y`
230 | - a number element being `p2.x`
231 | - a number element being `p2.y`
232 | - a number element being `p3.x`
233 | - a number element being `p3.y`
234 | - a number element being `p4.x`
235 | - a number element being `p4.y`
236 | - A 4 element array defined as follows:
237 | - an object with `x` and `y` number properties being `p1`
238 | - an object with `x` and `y` number properties being `p2`
239 | - an object with `x` and `y` number properties being `p3`
240 | - an object with `x` and `y` number properties being `p4`
241 |
242 | ## Cubic Bezier Examples
243 |
244 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
245 |
246 | ```javascript
247 | const beziers = [
248 | ShapeInfo.cubicBezier({p1: {x: 10, y: 20}, p2: {x: 5, y: 10}, p3: {x: 15, y: 30}, p4: {x: 20, y: 15}}),
249 | ShapeInfo.cubicBezier({p1: [10, 20], p2: [5, 10], p3: [15, 30], p4: [20, 15]}),
250 | ShapeInfo.cubicBezier([10, 20, 5, 10, 15, 30, 20, 15]),
251 | ShapeInfo.cubicBezier([{x: 10, y: 20}, {x: 5, y: 10}, {x: 15, y: 30}, {x: 20, y: 15}]),
252 | ShapeInfo.cubicBezier(10, 20, 5, 10, 15, 30, 20, 15),
253 | ShapeInfo.cubicBezier({x: 10, y: 20}, {x: 5, y: 10}, {x: 15, y: 30}, {x: 20, y: 15})
254 | ];
255 | ```
256 |
257 | # Circle
258 |
259 | An circle is defined by:
260 |
261 | - A center point
262 | - A radius
263 |
264 | ## A Center Point
265 |
266 | The center may be represented four ways:
267 |
268 | - A `center` property that is an object with `x` and `y` number properties
269 | - A `center` property that is an array with two number elements, `x` being the first element and `y` being the second element
270 | - A `cx` number property and a `cy` number property
271 | - A `centerX` number property and a `centerY` number property
272 |
273 | ### Center Examples
274 |
275 | ```javascript
276 | const centers = [
277 | {center: new Point2D(10, 20)},
278 | {center: {x: 10, y: 20}},
279 | {center: [10, 20]},
280 | {cx: 10, cy: 20},
281 | {centerX: 10, centerY: 20}
282 | ];
283 | ```
284 |
285 | ## A Radius
286 |
287 | The radius may be represented two ways:
288 |
289 | - An `r` number property
290 | - A `radius` number
291 |
292 | ### Radius Examples
293 |
294 | ```javascript
295 | const radii = [
296 | {r: 5},
297 | {radius: 5}
298 | ];
299 | ```
300 |
301 | ## Array input
302 |
303 | The above discussed how an object may be used to describe an Circle. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
304 |
305 | There are two supported formats:
306 |
307 | - A 3 element array defined as follows:
308 | - a number element being `centerX`
309 | - a number element being `centerY`
310 | - a number element being `radius`
311 | - A 2 element array defined as follows:
312 | - an object with `x` and `y` number properties being the `center`
313 | - a number element being `radius`
314 |
315 | ## Circle Examples
316 |
317 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
318 |
319 | ```javascript
320 | const circles = [
321 | ShapeInfo.circle({center: {x: 10, y: 20}, radius: 15}),
322 | ShapeInfo.circle({center: [10, 20], radius: 15}),
323 | ShapeInfo.circle({cx: 10, cy: 20, r: 15}),
324 | ShapeInfo.circle({centerX: 10, centerY: 20, radius: 15}),
325 | ShapeInfo.circle([10, 20, 15]),
326 | ShapeInfo.circle([{x: 10, y: 20}, 15]),
327 | ShapeInfo.circle(10, 20, 15),
328 | ShapeInfo.circle({x: 10, y: 20}, 15)
329 | ];
330 | ```
331 |
332 | # Ellipse
333 |
334 | An ellipse is defined by:
335 |
336 | - A center point
337 | - An x radius and a y radius
338 |
339 | ## A Center Point
340 |
341 | The center may be represented four ways:
342 |
343 | - A `center` property that is an object with `x` and `y` number properties
344 | - A `center` property that is an array with two number elements, `x` being the first element and `y` being the second element
345 | - A `cx` number property and a `cy` number property
346 | - A `centerX` number property and a `centerY` number property
347 |
348 | ### Center Examples
349 |
350 | ```javascript
351 | const centers = [
352 | {center: new Point2D(10, 20)},
353 | {center: {x: 10, y: 20}},
354 | {center: [10, 20]},
355 | {cx: 10, cy: 20},
356 | {centerX: 10, centerY: 20}
357 | ];
358 | ```
359 |
360 | ## An X Radius and a Y Radius
361 |
362 | The radii may be represented four ways:
363 |
364 | - A `radii` property that is an object with `x` and `y` number properties
365 | - A `radii` property that is an array with two number elements, `rx` being the first element and `ry` being the second element
366 | - An `rx` number property and an `ry` number property
367 | - A `radiusX` number property and a `radiusY` number property
368 |
369 | ### X and Y Radii Examples
370 |
371 | ```javascript
372 | const radii = [
373 | {radii: {x: 5, y: 10}},
374 | {radii: [5, 10]},
375 | {rx: 5, ry: 10},
376 | {radiusX: 5, radiusY: 10}
377 | ];
378 | ```
379 |
380 | ## Array input
381 |
382 | The above discussed how an object may be used to describe an Ellipse. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
383 |
384 | There are two supported formats:
385 |
386 | - A 4 element array defined as follows:
387 | - a number element being `centerX`
388 | - a number element being `centerY`
389 | - a number element being `radiusX`
390 | - a number element being `radiusY`
391 | - A 3 element array defined as follows:
392 | - an object with `x` and `y` number properties being the `center`
393 | - a number element being `radiusX`
394 | - a number element being `radiusY`
395 |
396 | ## Ellipse Examples
397 |
398 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
399 |
400 | ```javascript
401 | const ellipses = [
402 | ShapeInfo.ellipse({center: {x: 10, y: 20}, radii: {x: 5, y: 10}}),
403 | ShapeInfo.ellipse({center: [10, 20], radii: [5, 10]}),
404 | ShapeInfo.ellipse({cx: 10, cy: 20, rx: 5, ry: 10}),
405 | ShapeInfo.ellipse({centerX: 10, centerY: 20, radiusX: 5, radiusY: 10}),
406 | ShapeInfo.ellipse([10, 20, 3, 5]),
407 | ShapeInfo.ellipse([{x: 10, y: 20}, 3, 5]),
408 | ShapeInfo.ellipse(10, 20, 3, 5),
409 | ShapeInfo.ellipse({x: 10, y: 20}, 3, 5)
410 | ];
411 | ```
412 |
413 | # Line
414 |
415 | A line is defined by:
416 |
417 | - 2 points: p1 and p2
418 |
419 | ## Points
420 |
421 | A point may be represented two ways:
422 |
423 | - An object with `x` and `y` number properties
424 | - An array with two number elements, `x` being the first element and `y` being the second element
425 |
426 | ### Center Examples
427 |
428 | ```javascript
429 | const points = [
430 | {p1: new Point2D(10, 20)},
431 | {p2: {x: 10, y: 20}},
432 | {p1: [10, 20]}
433 | ];
434 | ```
435 |
436 | ## Array input
437 |
438 | The above discussed how an object may be used to describe a Quadratic Bezier. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
439 |
440 | There are two supported formats:
441 |
442 | - A 4 element array defined as follows:
443 | - a number element being `p1.x`
444 | - a number element being `p1.y`
445 | - a number element being `p2.x`
446 | - a number element being `p2.y`
447 | - A 2 element array defined as follows:
448 | - an object with `x` and `y` number properties being `p1`
449 | - an object with `x` and `y` number properties being `p2`
450 |
451 | ## Line Examples
452 |
453 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
454 |
455 | ```javascript
456 | const lines = [
457 | ShapeInfo.line({p1: {x: 10, y: 20}, p2: {x: 5, y: 10}}),
458 | ShapeInfo.line({p1: [10, 20], p2: [5, 10]}),
459 | ShapeInfo.line([10, 20, 5, 10]),
460 | ShapeInfo.line([{x: 10, y: 20}, {x: 5, y: 10}]),
461 | ShapeInfo.line(10, 20, 5, 10),
462 | ShapeInfo.line({x: 10, y: 20}, {x: 5, y: 10})
463 | ];
464 | ```
465 |
466 | # Path
467 |
468 | A path is defined by:
469 |
470 | - Path data
471 |
472 | ## Path Data
473 |
474 | Path data is a string following the path syntax as defined by the [SVG Specification](https://www.w3.org/TR/SVG2/paths.html#PathData).
475 |
476 | ## Path Examples
477 |
478 | ```javascript
479 | const paths = [
480 | ShapeInfo.path("M0,0 L100,100")
481 | ];
482 | ```
483 |
484 | # Polygon
485 |
486 | A polygon is defined by
487 |
488 | - An array of zero or points
489 |
490 | ## Points
491 |
492 | A point may be represented two ways:
493 |
494 | - An object with `x` and `y` number properties
495 | - An array with two number elements, `x` being the first element and `y` being the second element
496 |
497 | Each of these representations is concatenated into a single array.
498 |
499 | ## Polygon Examples
500 |
501 | ```javascript
502 | const polygons = [
503 | ShapeInfo.polygon([10, 20, 30, 40, 50, 60]),
504 | ShapeInfo.polygon([{x: 10, y: 20}, {x: 30, y: 40}, {x: 50, y: 60}])
505 | ];
506 | ```
507 |
508 | # Polyline
509 |
510 | A polyline is defined by
511 |
512 | - An array of zero or points
513 |
514 | ## Points
515 |
516 | A point may be represented two ways:
517 |
518 | - An object with `x` and `y` number properties
519 | - An array with two number elements, `x` being the first element and `y` being the second element
520 |
521 | Each of these representations is concatenated into a single array.
522 |
523 | ## Polyline Examples
524 |
525 | ```javascript
526 | const polylines = [
527 | ShapeInfo.polyline([10, 20, 30, 40, 50, 60]),
528 | ShapeInfo.polyline([{x: 10, y: 20}, {x: 30, y: 40}, {x: 50, y: 60}])
529 | ];
530 | ```
531 |
532 | # Rectangle
533 |
534 | A rectangle is defined by:
535 |
536 | - A topLeft point
537 | - A bottomRight point
538 | - Optional x and y radii
539 |
540 | ## Points and Vectors
541 |
542 | A point and a vector may be represented two ways:
543 |
544 | - An object with `x` and `y` number properties
545 | - An array with two number elements, `x` being the first element and `y` being the second element
546 |
547 | ## TopLeft Point
548 |
549 | The `topLeft` point may be represented four ways:
550 |
551 | - An object with a `topLeft` property that is a point as an object
552 | - An object with a `topLeft` property that is a point as an array
553 | - An object with a `x` number property and a `y` number property
554 | - An object with a `top` number property and a `left` number property
555 |
556 | ## BottomRight Point
557 |
558 | The `bottomRight` point may be represented six ways:
559 |
560 | - An object with a `bottomRight` property that is a point as an object
561 | - An object with a `bottomRight` property that is a point as an array
562 | - An object with a `w` number property and a `h` number property
563 | - An object with a `width` number property and a `height` number property
564 | - An object with a `size` property that is a vector as an object
565 | - An object with a `size` property that is a vector as an array
566 |
567 | ### Point Examples
568 |
569 | ```javascript
570 | const points = [
571 | {topLeft: {x: 10, y: 20}, bottomRight: {x: 30, y: 40}},
572 | {topLeft: [10, 20], bottomRight: [30, 40]},
573 | {x: 10, y: 20, w: 20, h: 20},
574 | {top: 20, left: 10, width: 20, height: 20},
575 | {topLeft: {x: 10, y: 20}, size: {x: 20, y: 20}},
576 | {topLeft: [10, 20], size: [20, 20]}
577 | ];
578 | ```
579 |
580 | ## An X Radius
581 |
582 | The x radius may be represented two ways:
583 |
584 | - An `rx` number property
585 | - A `radiusX` number property
586 |
587 | ## A Y Radius
588 |
589 | - An `ry` number property
590 | - A `radiusY` number property
591 |
592 | ## Array input
593 |
594 | The above discussed how an object may be used to describe a Quadratic Bezier. For backward compatibility with older shapes APIs, it is also possible to pass an array or a list of arguments.
595 |
596 | There are five supported formats:
597 |
598 | - A 4 element array defined as follows:
599 | - a number element being `x`
600 | - a number element being `y`
601 | - a number element being `width`
602 | - a number element being `height`
603 | - A 6 element array defined as follows:
604 | - a number element being `x`
605 | - a number element being `y`
606 | - a number element being `width`
607 | - a number element being `height`
608 | - a number element being `rx`
609 | - a number element being `ry`
610 | - A 2 element array defined as follows:
611 | - an object with `x` and `y` number properties being `topLeft`
612 | - an object with `x` and `y` number properties being `size` (width, height)
613 | - A 3 element array defined as follows:
614 | - an object with `x` and `y` number properties being `topLeft`
615 | - an object with `x` and `y` number properties being `size` (width, height)
616 | - an object with `rx` and `ry` number properties being `radii`
617 | - A 3 element array defined as follows:
618 | - an object with `x` and `y` number properties being `topLeft`
619 | - an object with `x` and `y` number properties being `size` (width, height)
620 | - an object with `radiusX` and `radiusY` number properties being `radii`
621 |
622 | ## Rectangle Examples
623 |
624 | Note that these examples are not exhaustive. You may combine the above descriptions in any way you wish.
625 |
626 | ```javascript
627 | const rectangles = [
628 | ShapeInfo.rectangle({topLeft: {x: 10, y: 20}, bottomRight: {x: 5, y: 10}}),
629 | ShapeInfo.rectangle({topLeft: {x: 10, y: 20}, bottomRight: {x: 5, y: 10}, radiusX: 10, radiusY: 15}),
630 | ShapeInfo.rectangle({topLeft: [10, 20], bottomRight: [5, 10]}),
631 | ShapeInfo.rectangle({topLeft: [10, 20], bottomRight: [5, 10], radiusX: 10, radiusY: 15}),
632 | ShapeInfo.rectangle({top: 20, left: 10, width: 5, height: 10}),
633 | ShapeInfo.rectangle({x: 10, y: 20, w: 5, h: 10, rx: 10, ry: 15}),
634 | ShapeInfo.rectangle([10, 20, 5, 10, 10, 15]),
635 | ShapeInfo.rectangle(10, 20, 5, 10, 10, 15)
636 | ];
637 | ```
638 |
--------------------------------------------------------------------------------
/docs/ShapesApi.md:
--------------------------------------------------------------------------------
1 | # Shapes API
2 |
3 | - [Intersection API](#intersection-api)
4 | - [Shapes API](#shapes-api)
5 | - [Affine Shapes API](#affine-shapes-api)
6 |
7 | ---
8 |
9 | kld-intersection allows you to find intersections using two general approaches:
10 |
11 | - By calling the intersection methods directly
12 | - By creating simple descriptors of shapes and then letting the library determine which method to invoke
13 |
14 | # Intersection API
15 |
16 | At the lowest level, you can invoke intersection methods directly in the `Intersection` class. In order to determine which intersection routine to invoke, you will need to determine two bits of information for each curve involved in the intersection:
17 |
18 | 1. The name the library uses to refer to the given curve type
19 | 2. The arguments used to represent the curve parameters to the library
20 |
21 | Below is a table listing each of the supported curve types. The `Name` column is the name you will need to use to refer to the shape of the given type. `Arguments` lists the parameters you will use to describe the shape of the given curve.
22 |
23 | | Shape | Name | Arguments |
24 | | --- | --- | --- |
25 | | Quadratic Bézier | Bezier2 | **c1** : *Point2D*, **c2** : *Point2D*, **c3**: *Point2D* |
26 | | Cubic Bézier | Bezier3 | **c1** : *Point2D*, **c2** : *Point2D*, **c3**: *Point2D*, **c4**: *Point2D* |
27 | | Circle | Circle | **center** : *Point2D*, **radius** : Number |
28 | | Ellipse | Ellipse | **center** : *Point2D*, **radiusX** : Number, **radiusY** : Number |
29 | | Arc | Arc | **center** : *Point2D*, **radiusX** : Number, **radiusY** : Number, **startRadians** : Number, **endRadians** : Number |
30 | | Line | Line | **p1** : *Point2D*, **p2** : *Point2D* |
31 | | Polygon | Polygon | **points** : *Array< Point2D >* |
32 | | Polyline | Polyline | **points** : *Array< Point2D >* |
33 | | Rectangle | Rectangle | **topLeft** : *Point2D*, **bottomRight** : *Point2D* |
34 | | Path | Path | **segments** : *Array< ShapeInfo >* |
35 |
36 | Once you've determined the names and arguments, you can build the intersection method name like so:
37 |
38 | 1. All intersections begin with `intersect`
39 | 2. Append the `Name` of the first curve type
40 | 3. Append the `Name` of the second curve type
41 |
42 | It is important to note that not all combinations of names are available in the API. The current implementation supports 10 types of curves. If we count all combinations of any two curves, we end up needing `10 * 10 = 100` methods to cover all cases. Fortunately, the order in which we specify the curves is not important.
43 |
44 | If we intersect `a rectangle with a circle` or `a circle with a rectangle`, we will get the same results, regardless of their order. Recognizing this allows us to reduce the number of methods to `10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 54`. Due to this reduction, one more restriction applies when determining the intersection method name:
45 |
46 | > Shape names must be appended to the method name in alphabetical order
47 |
48 | In our earlier example of intersecting a `Rectangle` and a `Circle`, we will need to append `Circle` before `Rectangle` giving us `intersectCircleRectangle` instead of `intersectRectangleCircle`.
49 |
50 | Now that you have determined the method name, you need to pass in arguments to describe the shape of each curve. You do that as below:
51 |
52 | 1. Review the `Arguments` list in the table above
53 | 2. Add all arguments for the first curve in the specified order to the method call
54 | 3. Add all arguments for the second curve in the specified order to the method call
55 |
56 | ## Example
57 |
58 | Lets intersect a `circle` and a `rectangle`. From the table above, we see that the circle's name is `Circle`, and the name of the rectangle is `Rectangle`. Following the rules stated above, this means our method name is:
59 |
60 | `intersectCircleRectangle`
61 |
62 | A circle is described with a center point and a radius. Those are our first two arguments.
63 |
64 | A rectangle is described with two points: the top-left corner and the bottom-right corner. Those are our final arguments.
65 |
66 | Putting this all together, we end up with something like the following:
67 |
68 | ```javascript
69 | const {Point2D, Intersection} = require("kld-intersections");
70 |
71 | const circle = {
72 | center: new Point2D(0, 0),
73 | radius: 50
74 | };
75 |
76 | const rectangle = {
77 | topLeft: new Point2D(0, 0),
78 | bottomRight: new Point2D(60, 30)
79 | };
80 |
81 | const result = Intersection.intersectCircleRectangle(
82 | circle.center, circle.radius,
83 | rectangle.topLeft, rectangle.bottomRight
84 | );
85 |
86 | console.log(result);
87 | ```
88 |
89 | Note that the `circle` and `rectangle` variables were used to make the code more readable. You could easily remove those objects, passing in their arguments directly. That would make a minimal example like the following:
90 |
91 | ```javascript
92 | const {Point2D, Intersection} = require("kld-intersections");
93 |
94 | const result = Intersection.intersectCircleRectangle(
95 | new Point2D(0, 0), 50,
96 | new Point2D(0, 0), new Point2D(60, 30)
97 | );
98 |
99 | console.log(result);
100 | ```
101 |
102 | ## Result
103 |
104 | ```
105 | Intersection {
106 | status: 'Intersection',
107 | points:
108 | [ Point2D { x: 50, y: 0 },
109 | Point2D { x: 40.00000000000001, y: 30.000000000000004 } ] }
110 | ```
111 |
112 | Note that `x` and `y` in the second point are not exactly 40 and 30 due to floating point imprecision.
113 |
114 | ## Visual Representation
115 |
116 | If we were to plot the results, we would end up with an image like the following.
117 |
118 | 
119 |
120 | # Shapes API
121 |
122 | The Shapes API allows you to create descriptions of shapes and curves which the intersection library can then use to determine which intersection method to invoke. This API allows you to create the following shapes:
123 |
124 | - quadraticBezier
125 | - cubicBezier
126 | - circle
127 | - ellipse
128 | - arc
129 | - line
130 | - polygon
131 | - polyline
132 | - rectangle
133 | - path
134 |
135 | To create these shapes, invoke the method name from the list above, passing in the arguments as described in the table in [Intersection API](#intersection-api). However, when passing in, for example, a `Point2D`, you will need to pass in the `x` and `y` values separately. This allows your code to utilize the intersection library without having to commit to the kld-affine classes.
136 |
137 | To find the intersections of these shapes, invoke `Intersection.intersect` passing in your two shape descriptors, in any order.
138 |
139 | ## Example
140 |
141 | ```javascript
142 | const {Point2D, Intersection, Shapes} = require("kld-intersections");
143 |
144 | const circle = Shapes.circle(0, 0, 50);
145 | const rectangle = Shapes.rectangle(0, 0, 60, 30);
146 | const result = Intersection.intersect(circle, rectangle);
147 |
148 | console.log(result);
149 | ```
150 |
151 | ## Result
152 |
153 | ```
154 | Intersection {
155 | status: 'Intersection',
156 | points:
157 | [ Point2D { x: 50, y: 0 },
158 | Point2D { x: 40.00000000000001, y: 30.000000000000004 } ] }
159 | ```
160 |
161 | ## Visual Representation
162 |
163 | 
164 |
165 |
166 | ## Working with Path Data
167 |
168 | Building paths from the other atomic shapes can be tedious and error-prone. In order to simplify this process, the Shapes API includes a `pathData` method. Simply pass in a string of path data and you will get a path shape with all of its segments populated for you.
169 |
170 | ```javascript
171 | import {Shapes} from "kld-intersections";
172 |
173 | // create a path from path data
174 | const path = Shapes.pathData("M0,0 C100,0 100,100 0,100z m20,0 c100,0 100,100 0,100z");
175 |
176 | // create a line
177 | const line = Shapes.line(0, -10, 110, 110);
178 |
179 | // intersect
180 | const result = Intersection.intersect(path, line);
181 | ```
182 |
183 | # Affine Shapes API
184 |
185 | The Affine Shapes API is very similar to the Shapes API but it allows you to use a slightly higher level of abstraction by utilizing the kld-affine classes. This API allows you to create the following shapes:
186 |
187 | - quadraticBezier
188 | - cubicBezier
189 | - circle
190 | - ellipse
191 | - arc
192 | - line
193 | - polygon
194 | - polyline
195 | - rectangle
196 | - path
197 |
198 | To create these shapes, invoke the method name from the list above, passing in the arguments as described in the table in [Intersection API](#intersection-api).
199 |
200 | To find the intersections of these shapes, invoke `Intersection.intersect` passing in your two shape descriptors, in any order.
201 |
202 | # Example
203 |
204 | ```javascript
205 | const {Point2D, Vector2D, Intersection, AffineShapes} = require("kld-intersections");
206 |
207 | const circle = AffineShapes.circle(new Point2D(0, 0), 50);
208 | const rectangle = AffineShapes.rectangle(new Point2D(0, 0), new Vector2D(60, 30));
209 | const result = Intersection.intersect(circle, rectangle);
210 |
211 | console.log(result);
212 | ```
213 |
214 | # Result
215 |
216 | ```
217 | Intersection {
218 | status: 'Intersection',
219 | points:
220 | [ Point2D { x: 50, y: 0 },
221 | Point2D { x: 40.00000000000001, y: 30.000000000000004 } ] }
222 | ```
223 |
224 | # Visual Representation
225 |
226 | 
227 |
228 | # Working with Path Data
229 |
230 | Building paths from the other atomic shapes can be tedious and error-prone. In order to simplify this process, the AffineShapes API includes a `pathData` method. Simply pass in a string of path data and you will get a path shape with all of its segments populated for you.
231 |
232 | ```javascript
233 | import {AffineShapes} from "kld-intersections";
234 |
235 | // create a path from path data
236 | const path = AffineShapes.pathData("M0,0 C100,0 100,100 0,100z m20,0 c100,0 100,100 0,100z");
237 |
238 | // create a line
239 | const line = AffineShapes.line(0, -10, 110, 110);
240 |
241 | // intersect
242 | const result = Intersection.intersect(path, line);
243 | ```
244 |
245 | ## Use Your Own Objects
246 |
247 | If you have a look at the various API classes, you'll find that they are quite simple. These APIs create instances of the `ShapeInfo` class. In turn the `ShapeInfo` class is quite simple too, consisting of two properties: `name` and `args`. `name` is the name of the shape or curve as defined in the table describing the [Intersection API](#intersection-api). Likewise, `args` is an array of the arguments described in the arguments column in that same table. So, you can pass in any object to `Intersection.intersect` as long as it contains those two properties which need to follow the name and argument conventions just described.
248 |
--------------------------------------------------------------------------------
/docs/SvgShapesApi.md:
--------------------------------------------------------------------------------
1 | # SVG Shapes API
2 |
3 | The SVG Shapes API allows you to create descriptions of shapes and curves using SVG elements from the DOM. With this API, you can create shape descriptions for the following elements:
4 |
5 | - circle
6 | - ellipse
7 | - line
8 | - path
9 | - polygon
10 | - polyline
11 | - rect
12 |
13 | When converting path elements, the element's path data will be used to generate shape descriptions, one shape description per path segment. All SVG path commands are supported.
14 |
15 | In order to simplify code, `SvgShapes` includes an `element` method. Pass in an SVG element and this method will dispatch to the appropriate conversion method. An exception will be thrown for unsupported SVG element types.
16 |
17 | The example below shows how you might convert and intersect two arbitrary SVG elements.
18 |
19 | ### Example
20 |
21 | ```javascript
22 | const {SvgShapes, Intersection} = require("kld-intersections");
23 |
24 | const shape1 = SvgShapes.element(document.querySelector("#a"));
25 | const shape2 = SvgShapes.element(document.querySelector("#b"));
26 | const result = Intersection.intersect(shape1, shape2);
27 |
28 | console.log(result);
29 | ```
30 |
31 | For more complete examples, have a look at the SVG files in the `visual-test` directory. These cover all combinations of shape intersections.
32 |
--------------------------------------------------------------------------------
/docs/images/example-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thelonious/kld-intersections/3e329985e2f997fdae84396a3a0839b9a1652371/docs/images/example-1.png
--------------------------------------------------------------------------------
/docs/images/example-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thelonious/kld-intersections/3e329985e2f997fdae84396a3a0839b9a1652371/docs/images/example-2.png
--------------------------------------------------------------------------------
/docs/images/example-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thelonious/kld-intersections/3e329985e2f997fdae84396a3a0839b9a1652371/docs/images/example-3.png
--------------------------------------------------------------------------------
/docs/jsdoc-config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | plugins: [],
5 | recurseDepth: 10,
6 | source: {
7 | exclude: [
8 | "node_modules",
9 | "dist",
10 | "scratchpad",
11 | "test"
12 | ],
13 | excludePattern: "rollup*"
14 | },
15 | sourceType: "module",
16 | tags: {
17 | allowUnknownTags: false
18 | },
19 | templates: {
20 | cleverLinks: true,
21 | monospaceLinks: false
22 | },
23 | opts: {
24 | recurse: true,
25 | verbose: true,
26 | destination: "docs/jsdoc"
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/examples/example-1.js:
--------------------------------------------------------------------------------
1 | import {Point2D, Intersection} from "../index.js";
2 |
3 | const circle = {
4 | center: new Point2D(0, 0),
5 | radius: 50
6 | };
7 |
8 | const rectangle = {
9 | topLeft: new Point2D(0, 0),
10 | bottomRight: new Point2D(60, 30)
11 | };
12 |
13 | const result = Intersection.intersectCircleRectangle(
14 | circle.center, circle.radius,
15 | rectangle.topLeft, rectangle.bottomRight
16 | );
17 |
18 | // build SVG file showing the shapes, the center point, and intersection points
19 | const intersectionSVG = result.points.map(p => {
20 | return ``;
21 | }).join("\n ");
22 |
23 | const svg = ``;
30 |
31 | console.log(svg);
32 |
--------------------------------------------------------------------------------
/examples/example-1.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/examples/example-2.js:
--------------------------------------------------------------------------------
1 | import {Intersection, Shapes} from "../index.js";
2 |
3 | const circle = Shapes.circle(0, 0, 50);
4 | const rectangle = Shapes.rectangle(0, 0, 60, 30);
5 | const result = Intersection.intersect(circle, rectangle);
6 |
7 | // build SVG file showing the shapes, the center point, and intersection points
8 | const intersectionSVG = result.points.map(p => {
9 | return ``;
10 | }).join("\n ");
11 |
12 | const svg = ``;
19 |
20 | console.log(svg);
21 |
--------------------------------------------------------------------------------
/examples/example-2.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/examples/example-3.js:
--------------------------------------------------------------------------------
1 | import {Point2D, Vector2D, Intersection, AffineShapes} from "../index.js";
2 |
3 | const circle = AffineShapes.circle(new Point2D(0, 0), 50);
4 | const rectangle = AffineShapes.rectangle(new Point2D(0, 0), new Vector2D(60, 30));
5 | const result = Intersection.intersect(circle, rectangle);
6 |
7 | // build SVG file showing the shapes, the center point, and intersection points
8 | const intersectionSVG = result.points.map(p => {
9 | return ``;
10 | }).join("\n ");
11 |
12 | const svg = ``;
19 |
20 | console.log(svg);
21 |
--------------------------------------------------------------------------------
/examples/example-3.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/examples/path-line.js:
--------------------------------------------------------------------------------
1 | import {Shapes, Intersection} from "../index.js";
2 |
3 | // parser path data
4 | const pathData = "M0,0 C100,0 100,100 0,100z m20,0 c100,0 100,100 0,100z";
5 | // let pathData = 'M0,0 Q100,50 0,100z m10,0 q100,50 0,100z';
6 | // let pathData = 'M0,0 H100 V100 H0z m5,5 h100 v100 h-100z';
7 |
8 | // create path
9 | const path = Shapes.pathData(pathData);
10 |
11 | // create line
12 | const line = Shapes.line(0, -10, 110, 110);
13 |
14 | // intersect
15 | const result = Intersection.intersect(path, line);
16 |
17 | // build SVG file showing the shapes, the center point, and intersection points
18 | const intersectionSVG = result.points.map(p => {
19 | return ``;
20 | }).join("\n ");
21 |
22 | const svg = ``;
29 |
30 | console.log(svg);
31 |
--------------------------------------------------------------------------------
/examples/path-line.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/examples/rotate-rectangle-line.js:
--------------------------------------------------------------------------------
1 | import {Intersection, Point2D, Matrix2D} from "../index.js";
2 |
3 | // define line
4 | const line = {
5 | p1: new Point2D(40, 0),
6 | p2: new Point2D(70, 130)
7 | };
8 |
9 | // define rectangle
10 | const rect = {
11 | topLeft: new Point2D(10, 10),
12 | bottomRight: new Point2D(110, 110)
13 | };
14 |
15 | // convert rectangle corners to polygon (list of points)
16 | const poly = [
17 | rect.topLeft,
18 | new Point2D(rect.bottomRight.x, rect.topLeft.y),
19 | rect.bottomRight,
20 | new Point2D(rect.topLeft.x, rect.bottomRight.y)
21 | ];
22 |
23 | // find center point of rectangle
24 | const center = rect.topLeft.lerp(rect.bottomRight, 0.5);
25 |
26 | // define rotation in radians
27 | const angle = 45.0 * Math.PI / 180.0;
28 |
29 | // create matrix for rotating around center of rectangle
30 | const rotation = Matrix2D.rotationAt(angle, center);
31 |
32 | // create new rotated polygon
33 | const rotatedPoly = poly.map(p => p.transform(rotation));
34 |
35 | // find intersections
36 | const result = Intersection.intersectLinePolygon(line.p1, line.p2, rotatedPoly);
37 |
38 | // build SVG file showing the shapes, the center point, and intersection points
39 | const intersectionSVG = result.points.map(p => {
40 | return ``;
41 | }).join("\n ");
42 |
43 | const svg = ``;
51 |
52 | console.log(svg);
53 |
--------------------------------------------------------------------------------
/examples/rotate-rectangle-line.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/examples/rotated-ellipse-line.js:
--------------------------------------------------------------------------------
1 | import {Intersection, Point2D, Matrix2D} from "../index.js";
2 |
3 | // define line
4 | const line = {
5 | p1: new Point2D(40, 5),
6 | p2: new Point2D(70, 150)
7 | };
8 |
9 | // define rotated ellipse
10 | const ellipse = {
11 | center: new Point2D(110, 60),
12 | radiusX: 100,
13 | radiusY: 50,
14 | angle: 10
15 | };
16 |
17 | // rotate the line into the ellipse's axis-aligned coordinate system
18 | const radians = ellipse.angle * Math.PI / 180.0;
19 | const rotation = Matrix2D.rotation(-radians);
20 | const rotatedLine = {
21 | p1: line.p1.transform(rotation),
22 | p2: line.p2.transform(rotation)
23 | };
24 |
25 | // find intersections
26 | const result = Intersection.intersectEllipseLine(
27 | ellipse.center, ellipse.radiusX, ellipse.radiusY,
28 | rotatedLine.p1, rotatedLine.p2
29 | );
30 |
31 | // build SVG file showing the shapes, the center point, and intersection points
32 | const intersectionSVG = result.points.map(p => {
33 | return ``;
34 | }).join("\n ");
35 |
36 | //
37 | // NOTE: This SVG transforms the intersection points back into the original
38 | // coordinate system. If you need to do that in your code, use something like
39 | // the following:
40 | //
41 | // const unrotation = Matrix2D.rotation(radians);
42 | // const unrotated_points = result.points(p => p.transform(unrotation));
43 | //
44 |
45 | const svg = ``;
57 |
58 | console.log(svg);
59 |
--------------------------------------------------------------------------------
/examples/rotated-ellipse-line.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/examples/tessellate-cubic-beziers.js:
--------------------------------------------------------------------------------
1 | import {CubicBezier2D} from "kld-contours";
2 | import {Intersection, Point2D} from "../index.js";
3 |
4 | // create beziers
5 | const b1 = new CubicBezier2D(
6 | new Point2D(203, 140),
7 | new Point2D(206, 359),
8 | new Point2D(245, 6),
9 | new Point2D(248, 212)
10 | );
11 | const b2 = new CubicBezier2D(
12 | new Point2D(177, 204),
13 | new Point2D(441, 204),
14 | new Point2D(8, 149),
15 | new Point2D(265, 154)
16 | );
17 |
18 | // create polylines
19 | const p1 = b1.toPolygon2D();
20 | const p2 = b2.toPolygon2D();
21 |
22 | // find intersections
23 | const result = Intersection.intersectPolylinePolyline(p1.points, p2.points);
24 |
25 | // build SVG file showing beziers, polylines, and intersection points
26 | const intersectionSVG = result.points.map(p => {
27 | return ``;
28 | }).join("\n ");
29 |
30 | const svg = ``;
39 |
40 | console.log(svg);
41 |
--------------------------------------------------------------------------------
/examples/tessellate-cubic-beziers.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module kld-intersections
3 | */
4 |
5 | export {default as Intersection} from "./lib/Intersection.js";
6 |
7 | /**
8 | * @deprecated Use ShapeInfo instead
9 | */
10 | export {default as IntersectionArgs} from "./lib/ShapeInfo.js";
11 |
12 | /**
13 | * @namespace ShapeInfo
14 | * @implements {module:Shapes~ShapeInfo}
15 | */
16 | export {default as ShapeInfo} from "./lib/ShapeInfo.js";
17 |
18 | /**
19 | * @namespace Shapes
20 | * @implements {module:Shapes~Shapes}
21 | */
22 | export {default as Shapes} from "./lib/Shapes.js";
23 |
24 | /**
25 | * @namespace AffineShapes
26 | * @implements {module:AffineShapes~AffineShapes}
27 | */
28 | export {default as AffineShapes} from "./lib/AffineShapes.js";
29 |
30 | /**
31 | * @namespace SvgShapes
32 | * @implements {module:SvgShapes~SvgShapes}
33 | */
34 | export {default as SvgShapes} from "./lib/SvgShapes.js";
35 |
36 | /**
37 | * @namespace IntersectionQuery
38 | * @implements {module:IntersectionQuery~IntersectionQuery}
39 | */
40 | export {default as IntersectionQuery} from "./lib/IntersectionQuery.js";
41 |
42 | // Expose affine module classes
43 |
44 | /**
45 | * @external Point2D
46 | */
47 |
48 | /**
49 | * @external Vector2D
50 | */
51 |
52 | /**
53 | * @external Matrix2D
54 | */
55 |
56 | /**
57 | * @class Point2D
58 | * @memberof module:kld-intersections
59 | * @implements {external:Point2D}
60 | */
61 | export {Point2D} from "kld-affine";
62 |
63 | /**
64 | * @class Vector2D
65 | * @memberof module:kld-intersections
66 | * @implements {external:Vector2D}
67 | */
68 | export {Vector2D} from "kld-affine";
69 |
70 | /**
71 | * @class Matrix2D
72 | * @memberof module:kld-intersections
73 | * @implements {external:Matrix2D}
74 | */
75 | export {Matrix2D} from "kld-affine";
76 |
77 |
78 | /**
79 | * @external Polynomial
80 | */
81 |
--------------------------------------------------------------------------------
/lgtm.yml:
--------------------------------------------------------------------------------
1 | extraction:
2 | javascript:
3 | index:
4 | filters:
5 | - exclude: "dist"
6 |
--------------------------------------------------------------------------------
/lib/AffineShapes.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-rest-params */
2 | /**
3 | * AffineShapes
4 | *
5 | * @copyright 2017-2019, Kevin Lindsey
6 | * @module AffineShapes
7 | * @deprecated use ShapeInfo
8 | */
9 |
10 | import ShapeInfo from "./ShapeInfo.js";
11 |
12 | /**
13 | * Build shapes for intersection
14 | */
15 | class AffineShapes {
16 | /**
17 | * arc
18 | *
19 | * @deprecated use ShapeInfo.arc
20 | * @param {module:kld-intersections.Point2D} center
21 | * @param {number} radiusX
22 | * @param {number} radiusY
23 | * @param {number} startRadians
24 | * @param {number} endRadians
25 | * @returns {module:kld-intersections.ShapeInfo}
26 | */
27 | static arc(center, radiusX, radiusY, startRadians, endRadians) {
28 | return ShapeInfo.arc(...arguments);
29 | }
30 |
31 | /**
32 | * quadraticBezier
33 | *
34 | * @deprecated use ShapeInfo.quadraticBezier
35 | * @param {module:kld-intersections.Point2D} p1
36 | * @param {module:kld-intersections.Point2D} p2
37 | * @param {module:kld-intersections.Point2D} p3
38 | * @returns {module:kld-intersections.ShapeInfo}
39 | */
40 | static quadraticBezier(p1, p2, p3) {
41 | return ShapeInfo.quadraticBezier(...arguments);
42 | }
43 |
44 | /**
45 | * cubicBezier
46 | *
47 | * @deprecated use ShapeInfo.cubicBezier
48 | * @param {module:kld-intersections.Point2D} p1
49 | * @param {module:kld-intersections.Point2D} p2
50 | * @param {module:kld-intersections.Point2D} p3
51 | * @param {module:kld-intersections.Point2D} p4
52 | * @returns {module:kld-intersections.ShapeInfo}
53 | */
54 | static cubicBezier(p1, p2, p3, p4) {
55 | return ShapeInfo.cubicBezier(...arguments);
56 | }
57 |
58 | /**
59 | * circle
60 | *
61 | * @deprecated use ShapeInfo.circle
62 | * @param {module:kld-intersections.Point2D} center
63 | * @param {number} radius
64 | * @returns {module:kld-intersections.ShapeInfo}
65 | */
66 | static circle(center, radius) {
67 | return ShapeInfo.circle(...arguments);
68 | }
69 |
70 | /**
71 | * ellipse
72 | *
73 | * @deprecated use ShapeInfo.ellipse
74 | * @param {module:kld-intersections.Point2D} center
75 | * @param {number} radiusX
76 | * @param {number} radiusY
77 | * @returns {module:kld-intersections.ShapeInfo}
78 | */
79 | static ellipse(center, radiusX, radiusY) {
80 | return ShapeInfo.ellipse(...arguments);
81 | }
82 |
83 | /**
84 | * line
85 | *
86 | * @deprecated use ShapeInfo.line
87 | * @param {module:kld-intersections.Point2D} p1
88 | * @param {module:kld-intersections.Point2D} p2
89 | * @returns {module:kld-intersections.ShapeInfo}
90 | */
91 | static line(p1, p2) {
92 | return ShapeInfo.line(...arguments);
93 | }
94 |
95 | /**
96 | * path
97 | *
98 | * @deprecated use ShapeInfo.path
99 | * @param {string} pathData
100 | * @returns {module:kld-intersections.ShapeInfo}
101 | */
102 | static path(pathData) {
103 | return ShapeInfo.path(...arguments);
104 | }
105 |
106 | /**
107 | * polygon
108 | *
109 | * @deprecated use ShapeInfo.polygon
110 | * @param {Array} points
111 | * @returns {module:kld-intersections.ShapeInfo}
112 | */
113 | static polygon(points) {
114 | return ShapeInfo.polygon(...arguments);
115 | }
116 |
117 | /**
118 | * polyline
119 | *
120 | * @deprecated use ShapeInfo.polyline
121 | * @param {Array} points
122 | * @returns {module:kld-intersections.ShapeInfo}
123 | */
124 | static polyline(points) {
125 | return ShapeInfo.polyline(...arguments);
126 | }
127 |
128 | /**
129 | * rectangle
130 | *
131 | * @deprecated use ShapeInfo.rectangle
132 | * @param {module:kld-intersections.Point2D} topLeft
133 | * @param {module:kld-intersections.Vector2D} size
134 | * @param {number} [rx]
135 | * @param {number} [ry]
136 | * @returns {module:kld-intersections.ShapeInfo}
137 | */
138 | static rectangle(topLeft, size, rx = 0, ry = 0) {
139 | return ShapeInfo.rectangle(...arguments);
140 | }
141 | }
142 |
143 | export default AffineShapes;
144 |
--------------------------------------------------------------------------------
/lib/IntersectionQuery.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * IntersectionQuery.js
4 | *
5 | * @copyright 2017 Kevin Lindsey
6 | * @module IntersectionQuery
7 | */
8 |
9 | import {Vector2D} from "kld-affine";
10 |
11 | /**
12 | * @namespace
13 | */
14 | const IntersectionQuery = {};
15 |
16 |
17 | /**
18 | * pointInCircle
19 | *
20 | * @param {module:kld-intersections.Point2D} point
21 | * @param {module:kld-intersections.Point2D} center
22 | * @param {number} radius
23 | * @returns {boolean}
24 | */
25 | IntersectionQuery.pointInCircle = function(point, center, radius) {
26 | const v = Vector2D.fromPoints(center, point);
27 |
28 | return v.length() <= radius;
29 | };
30 |
31 |
32 | /**
33 | * pointInEllipse
34 | *
35 | * @param {module:kld-intersections.Point2D} point
36 | * @param {module:kld-intersections.Point2D} center
37 | * @param {number} radiusX
38 | * @param {number} radiusY
39 | * @returns {boolean}
40 | */
41 | IntersectionQuery.pointInEllipse = function(point, center, radiusX, radiusY) {
42 | const len = point.subtract(center);
43 |
44 | return (len.x * len.x) / (radiusX * radiusX) + (len.y * len.y) / (radiusY * radiusY) <= 1;
45 | };
46 |
47 |
48 | /**
49 | * pointInPolyline
50 | *
51 | * @param {module:kld-intersections.Point2D} point
52 | * @param {Array} points
53 | */
54 | IntersectionQuery.pointInPolyline = function(point, points) {
55 | const {length: len} = points;
56 | let counter = 0;
57 | let xInter;
58 |
59 | let p1 = points[0];
60 |
61 | for (let i = 1; i <= len; i++) {
62 | const p2 = points[i % len];
63 | const minY = Math.min(p1.y, p2.y);
64 | const maxY = Math.max(p1.y, p2.y);
65 | const maxX = Math.max(p1.x, p2.x);
66 |
67 | if (p1.y !== p2.y && minY < point.y && point.y <= maxY && point.x <= maxX) {
68 | xInter = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
69 |
70 | if (p1.x === p2.x || point.x <= xInter) {
71 | counter++;
72 | }
73 | }
74 |
75 | p1 = p2;
76 | }
77 |
78 | return (counter % 2 === 1);
79 | };
80 |
81 |
82 | /**
83 | * pointInPolyline
84 | *
85 | * @param {module:kld-intersections.Point2D} point
86 | * @param {Array} points
87 | */
88 | IntersectionQuery.pointInPolygon = IntersectionQuery.pointInPolyline;
89 |
90 |
91 | /**
92 | * pointInRectangle
93 | *
94 | * @param {module:kld-intersections.Point2D} point
95 | * @param {module:kld-intersections.Point2D} topLeft
96 | * @param {module:kld-intersections.Point2D} bottomRight
97 | * @returns {boolean}
98 | */
99 | IntersectionQuery.pointInRectangle = function(point, topLeft, bottomRight) {
100 | return (
101 | topLeft.x <= point.x && point.x < bottomRight.x &&
102 | topLeft.y <= point.y && point.y < bottomRight.y
103 | );
104 | };
105 |
106 |
107 | export default IntersectionQuery;
108 |
--------------------------------------------------------------------------------
/lib/PathHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PathHandler.js
3 | *
4 | * @copyright 2017 Kevin Lindsey
5 | */
6 |
7 | import {Point2D, Vector2D} from "kld-affine";
8 |
9 | const TWO_PI = 2.0 * Math.PI;
10 |
11 | /**
12 | * Based on the SVG 1.1 specification, Appendix F: Implementation Requirements,
13 | * Section F.6 "Elliptical arc implementation notes"
14 | * {@see https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes}
15 | *
16 | * @param {module:kld-affine.Point2D} startPoint
17 | * @param {module:kld-affine.Point2D} endPoint
18 | * @param {number} rx
19 | * @param {number} ry
20 | * @param {number} angle
21 | * @param {boolean} arcFlag
22 | * @param {boolean} sweepFlag
23 | * @returns {Array}
24 | */
25 | function getArcParameters(startPoint, endPoint, rx, ry, angle, arcFlag, sweepFlag) {
26 | angle = angle * Math.PI / 180;
27 |
28 | const c = Math.cos(angle);
29 | const s = Math.sin(angle);
30 | const TOLERANCE = 1e-6;
31 |
32 | // Section (F.6.5.1)
33 | const halfDiff = startPoint.subtract(endPoint).multiply(0.5);
34 | const x1p = halfDiff.x * c + halfDiff.y * s;
35 | const y1p = halfDiff.x * -s + halfDiff.y * c;
36 |
37 | // Section (F.6.6.1)
38 | rx = Math.abs(rx);
39 | ry = Math.abs(ry);
40 |
41 | // Section (F.6.6.2)
42 | const x1px1p = x1p * x1p;
43 | const y1py1p = y1p * y1p;
44 | const lambda = (x1px1p / (rx * rx)) + (y1py1p / (ry * ry));
45 |
46 | // Section (F.6.6.3)
47 | if (lambda > 1) {
48 | const factor = Math.sqrt(lambda);
49 |
50 | rx *= factor;
51 | ry *= factor;
52 | }
53 |
54 | // Section (F.6.5.2)
55 | const rxrx = rx * rx;
56 | const ryry = ry * ry;
57 | const rxy1 = rxrx * y1py1p;
58 | const ryx1 = ryry * x1px1p;
59 |
60 | let factor = (rxrx * ryry - rxy1 - ryx1) / (rxy1 + ryx1);
61 |
62 | if (Math.abs(factor) < TOLERANCE) {
63 | factor = 0;
64 | }
65 |
66 | let sq = Math.sqrt(factor);
67 |
68 | if (arcFlag === sweepFlag) {
69 | sq = -sq;
70 | }
71 |
72 | // Section (F.6.5.3)
73 | const mid = startPoint.add(endPoint).multiply(0.5);
74 | const cxp = sq * rx * y1p / ry;
75 | const cyp = sq * -ry * x1p / rx;
76 |
77 | // Section (F.6.5.5 - F.6.5.6)
78 | const xcr1 = (x1p - cxp) / rx;
79 | const xcr2 = (x1p + cxp) / rx;
80 | const ycr1 = (y1p - cyp) / ry;
81 | const ycr2 = (y1p + cyp) / ry;
82 |
83 | const theta1 = new Vector2D(1, 0).angleBetween(new Vector2D(xcr1, ycr1));
84 | // let deltaTheta = normalizeAngle(new Vector2D(xcr1, ycr1).angleBetween(new Vector2D(-xcr2, -ycr2)));
85 | let deltaTheta = new Vector2D(xcr1, ycr1).angleBetween(new Vector2D(-xcr2, -ycr2));
86 |
87 | if (sweepFlag === false) {
88 | deltaTheta -= TWO_PI;
89 | }
90 |
91 | return [
92 | cxp * c - cyp * s + mid.x,
93 | cxp * s + cyp * c + mid.y,
94 | rx,
95 | ry,
96 | theta1,
97 | theta1 + deltaTheta
98 | ];
99 | }
100 |
101 | /**
102 | * PathHandler
103 | */
104 | class PathHandler {
105 | /**
106 | * PathHandler
107 | *
108 | * @param {ShapeInfo} shapeCreator
109 | */
110 | constructor(shapeCreator) {
111 | this.shapeCreator = shapeCreator;
112 | this.shapes = [];
113 | this.firstX = null;
114 | this.firstY = null;
115 | this.lastX = null;
116 | this.lastY = null;
117 | this.lastCommand = null;
118 | }
119 |
120 | /**
121 | * beginParse
122 | */
123 | beginParse() {
124 | // zero out the sub-path array
125 | this.shapes = [];
126 |
127 | // clear firstX, firstY, lastX, and lastY
128 | this.firstX = null;
129 | this.firstY = null;
130 | this.lastX = null;
131 | this.lastY = null;
132 |
133 | // need to remember last command type to determine how to handle the
134 | // relative Bezier commands
135 | this.lastCommand = null;
136 | }
137 |
138 | /**
139 | * addShape
140 | *
141 | * @param {ShapeInfo} shape
142 | */
143 | addShape(shape) {
144 | this.shapes.push(shape);
145 | }
146 |
147 | /**
148 | * arcAbs - A
149 | *
150 | * @param {number} rx
151 | * @param {number} ry
152 | * @param {number} xAxisRotation
153 | * @param {boolean} arcFlag
154 | * @param {boolean} sweepFlag
155 | * @param {number} x
156 | * @param {number} y
157 | */
158 | arcAbs(rx, ry, xAxisRotation, arcFlag, sweepFlag, x, y) {
159 | if (rx === 0 || ry === 0) {
160 | this.addShape(this.shapeCreator.line(
161 | this.lastX, this.lastY,
162 | x, y
163 | ));
164 | }
165 | else {
166 | const arcParameters = getArcParameters(
167 | new Point2D(this.lastX, this.lastY),
168 | new Point2D(x, y),
169 | rx, ry,
170 | xAxisRotation,
171 | arcFlag, sweepFlag
172 | );
173 |
174 | this.addShape(this.shapeCreator.arc(...arcParameters));
175 | }
176 |
177 | this.lastCommand = "A";
178 | this.lastX = x;
179 | this.lastY = y;
180 | }
181 |
182 | /**
183 | * arcRel - a
184 | *
185 | * @param {number} rx
186 | * @param {number} ry
187 | * @param {number} xAxisRotation
188 | * @param {boolean} arcFlag
189 | * @param {boolean} sweepFlag
190 | * @param {number} x
191 | * @param {number} y
192 | */
193 | arcRel(rx, ry, xAxisRotation, arcFlag, sweepFlag, x, y) {
194 | if (rx === 0 || ry === 0) {
195 | this.addShape(this.shapeCreator.line(
196 | this.lastX, this.lastY,
197 | this.lastX + x, this.lastY + y
198 | ));
199 | }
200 | else {
201 | const arcParameters = getArcParameters(
202 | new Point2D(this.lastX, this.lastY),
203 | new Point2D(this.lastX + x, this.lastY + y),
204 | rx, ry,
205 | xAxisRotation,
206 | arcFlag, sweepFlag
207 | );
208 |
209 | this.addShape(this.shapeCreator.arc(...arcParameters));
210 | }
211 |
212 | this.lastCommand = "a";
213 | this.lastX += x;
214 | this.lastY += y;
215 | }
216 |
217 | /**
218 | * curvetoCubicAbs - C
219 | *
220 | * @param {number} x1
221 | * @param {number} y1
222 | * @param {number} x2
223 | * @param {number} y2
224 | * @param {number} x
225 | * @param {number} y
226 | */
227 | curvetoCubicAbs(x1, y1, x2, y2, x, y) {
228 | this.addShape(this.shapeCreator.cubicBezier(
229 | this.lastX, this.lastY,
230 | x1, y1,
231 | x2, y2,
232 | x, y
233 | ));
234 |
235 | this.lastX = x;
236 | this.lastY = y;
237 | this.lastCommand = "C";
238 | }
239 |
240 | /**
241 | * curvetoCubicRel - c
242 | *
243 | * @param {number} x1
244 | * @param {number} y1
245 | * @param {number} x2
246 | * @param {number} y2
247 | * @param {number} x
248 | * @param {number} y
249 | */
250 | curvetoCubicRel(x1, y1, x2, y2, x, y) {
251 | this.addShape(this.shapeCreator.cubicBezier(
252 | this.lastX, this.lastY,
253 | this.lastX + x1, this.lastY + y1,
254 | this.lastX + x2, this.lastY + y2,
255 | this.lastX + x, this.lastY + y
256 | ));
257 |
258 | this.lastX += x;
259 | this.lastY += y;
260 | this.lastCommand = "c";
261 | }
262 |
263 | /**
264 | * linetoHorizontalAbs - H
265 | *
266 | * @param {number} x
267 | */
268 | linetoHorizontalAbs(x) {
269 | this.addShape(this.shapeCreator.line(
270 | this.lastX, this.lastY,
271 | x, this.lastY
272 | ));
273 |
274 | this.lastX = x;
275 | this.lastCommand = "H";
276 | }
277 |
278 | /**
279 | * linetoHorizontalRel - h
280 | *
281 | * @param {number} x
282 | */
283 | linetoHorizontalRel(x) {
284 | this.addShape(this.shapeCreator.line(
285 | this.lastX, this.lastY,
286 | this.lastX + x, this.lastY
287 | ));
288 |
289 | this.lastX += x;
290 | this.lastCommand = "h";
291 | }
292 |
293 | /**
294 | * linetoAbs - L
295 | *
296 | * @param {number} x
297 | * @param {number} y
298 | */
299 | linetoAbs(x, y) {
300 | this.addShape(this.shapeCreator.line(
301 | this.lastX, this.lastY,
302 | x, y
303 | ));
304 |
305 | this.lastX = x;
306 | this.lastY = y;
307 | this.lastCommand = "L";
308 | }
309 |
310 | /**
311 | * linetoRel - l
312 | *
313 | * @param {number} x
314 | * @param {number} y
315 | */
316 | linetoRel(x, y) {
317 | this.addShape(this.shapeCreator.line(
318 | this.lastX, this.lastY,
319 | this.lastX + x, this.lastY + y
320 | ));
321 |
322 | this.lastX += x;
323 | this.lastY += y;
324 | this.lastCommand = "l";
325 | }
326 |
327 | /**
328 | * movetoAbs - M
329 | *
330 | * @param {number} x
331 | * @param {number} y
332 | */
333 | movetoAbs(x, y) {
334 | this.firstX = x;
335 | this.firstY = y;
336 | this.lastX = x;
337 | this.lastY = y;
338 | this.lastCommand = "M";
339 | }
340 |
341 | /**
342 | * movetoRel - m
343 | *
344 | * @param {number} x
345 | * @param {number} y
346 | */
347 | movetoRel(x, y) {
348 | this.firstX += x;
349 | this.firstY += y;
350 | this.lastX += x;
351 | this.lastY += y;
352 | this.lastCommand = "m";
353 | }
354 |
355 | /**
356 | * curvetoQuadraticAbs - Q
357 | *
358 | * @param {number} x1
359 | * @param {number} y1
360 | * @param {number} x
361 | * @param {number} y
362 | */
363 | curvetoQuadraticAbs(x1, y1, x, y) {
364 | this.addShape(this.shapeCreator.quadraticBezier(
365 | this.lastX, this.lastY,
366 | x1, y1,
367 | x, y
368 | ));
369 |
370 | this.lastX = x;
371 | this.lastY = y;
372 | this.lastCommand = "Q";
373 | }
374 |
375 | /**
376 | * curvetoQuadraticRel - q
377 | *
378 | * @param {number} x1
379 | * @param {number} y1
380 | * @param {number} x
381 | * @param {number} y
382 | */
383 | curvetoQuadraticRel(x1, y1, x, y) {
384 | this.addShape(this.shapeCreator.quadraticBezier(
385 | this.lastX, this.lastY,
386 | this.lastX + x1, this.lastY + y1,
387 | this.lastX + x, this.lastY + y
388 | ));
389 |
390 | this.lastX += x;
391 | this.lastY += y;
392 | this.lastCommand = "q";
393 | }
394 |
395 | /**
396 | * curvetoCubicSmoothAbs - S
397 | *
398 | * @param {number} x2
399 | * @param {number} y2
400 | * @param {number} x
401 | * @param {number} y
402 | */
403 | curvetoCubicSmoothAbs(x2, y2, x, y) {
404 | let controlX, controlY;
405 |
406 | if (this.lastCommand.match(/^[SsCc]$/)) {
407 | const secondToLast = this.shapes[this.shapes.length - 1].args[2];
408 |
409 | controlX = 2 * this.lastX - secondToLast.x;
410 | controlY = 2 * this.lastY - secondToLast.y;
411 | }
412 | else {
413 | controlX = this.lastX;
414 | controlY = this.lastY;
415 | }
416 |
417 | this.addShape(this.shapeCreator.cubicBezier(
418 | this.lastX, this.lastY,
419 | controlX, controlY,
420 | x2, y2,
421 | x, y
422 | ));
423 |
424 | this.lastX = x;
425 | this.lastY = y;
426 | this.lastCommand = "S";
427 | }
428 |
429 | /**
430 | * curvetoCubicSmoothRel - s
431 | *
432 | * @param {number} x2
433 | * @param {number} y2
434 | * @param {number} x
435 | * @param {number} y
436 | */
437 | curvetoCubicSmoothRel(x2, y2, x, y) {
438 | let controlX, controlY;
439 |
440 | if (this.lastCommand.match(/^[SsCc]$/)) {
441 | const secondToLast = this.shapes[this.shapes.length - 1].args[2];
442 |
443 | controlX = 2 * this.lastX - secondToLast.x;
444 | controlY = 2 * this.lastY - secondToLast.y;
445 | }
446 | else {
447 | controlX = this.lastX;
448 | controlY = this.lastY;
449 | }
450 |
451 | this.addShape(this.shapeCreator.cubicBezier(
452 | this.lastX, this.lastY,
453 | controlX, controlY,
454 | this.lastX + x2, this.lastY + y2,
455 | this.lastX + x, this.lastY + y
456 | ));
457 |
458 | this.lastX += x;
459 | this.lastY += y;
460 | this.lastCommand = "s";
461 | }
462 |
463 | /**
464 | * curvetoQuadraticSmoothAbs - T
465 | *
466 | * @param {number} x
467 | * @param {number} y
468 | */
469 | curvetoQuadraticSmoothAbs(x, y) {
470 | let controlX, controlY;
471 |
472 | if (this.lastCommand.match(/^[QqTt]$/)) {
473 | const secondToLast = this.shapes[this.shapes.length - 1].args[1];
474 |
475 | controlX = 2 * this.lastX - secondToLast.x;
476 | controlY = 2 * this.lastY - secondToLast.y;
477 | }
478 | else {
479 | controlX = this.lastX;
480 | controlY = this.lastY;
481 | }
482 |
483 | this.addShape(this.shapeCreator.quadraticBezier(
484 | this.lastX, this.lastY,
485 | controlX, controlY,
486 | x, y
487 | ));
488 |
489 | this.lastX = x;
490 | this.lastY = y;
491 | this.lastCommand = "T";
492 | }
493 |
494 | /**
495 | * curvetoQuadraticSmoothRel - t
496 | *
497 | * @param {number} x
498 | * @param {number} y
499 | */
500 | curvetoQuadraticSmoothRel(x, y) {
501 | let controlX, controlY;
502 |
503 | if (this.lastCommand.match(/^[QqTt]$/)) {
504 | const secondToLast = this.shapes[this.shapes.length - 1].args[1];
505 |
506 | controlX = 2 * this.lastX - secondToLast.x;
507 | controlY = 2 * this.lastY - secondToLast.y;
508 | }
509 | else {
510 | controlX = this.lastX;
511 | controlY = this.lastY;
512 | }
513 |
514 | this.addShape(this.shapeCreator.quadraticBezier(
515 | this.lastX, this.lastY,
516 | controlX, controlY,
517 | this.lastX + x, this.lastY + y
518 | ));
519 |
520 | this.lastX += x;
521 | this.lastY += y;
522 | this.lastCommand = "t";
523 | }
524 |
525 | /**
526 | * linetoVerticalAbs - V
527 | *
528 | * @param {number} y
529 | */
530 | linetoVerticalAbs(y) {
531 | this.addShape(this.shapeCreator.line(
532 | this.lastX, this.lastY,
533 | this.lastX, y
534 | ));
535 |
536 | this.lastY = y;
537 |
538 | this.lastCommand = "V";
539 | }
540 |
541 | /**
542 | * linetoVerticalRel - v
543 | *
544 | * @param {number} y
545 | */
546 | linetoVerticalRel(y) {
547 | this.addShape(this.shapeCreator.line(
548 | this.lastX, this.lastY,
549 | this.lastX, this.lastY + y
550 | ));
551 |
552 | this.lastY += y;
553 |
554 | this.lastCommand = "v";
555 | }
556 |
557 | /**
558 | * closePath - z or Z
559 | */
560 | closePath() {
561 | this.addShape(this.shapeCreator.line(
562 | this.lastX, this.lastY,
563 | this.firstX, this.firstY
564 | ));
565 |
566 | this.lastX = this.firstX;
567 | this.lastY = this.firstY;
568 | this.lastCommand = "z";
569 | }
570 | }
571 |
572 | export default PathHandler;
573 |
--------------------------------------------------------------------------------
/lib/ShapeInfo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ShapeInfo.js
3 | * @copyright 2002, 2017 Kevin Lindsey
4 | */
5 |
6 | import {Point2D} from "kld-affine";
7 | import {PathParser} from "kld-path-parser";
8 | import PathHandler from "./PathHandler.js";
9 |
10 | const degree90 = Math.PI * 0.5;
11 | const parser = new PathParser();
12 |
13 |
14 | /**
15 | * getValues
16 | *
17 | * @param {Array} types
18 | * @param {Array} args
19 | * @returns {Array}
20 | */
21 | export function getValues(types, args) {
22 | const result = [];
23 |
24 | for (const [names, type] of types) {
25 | let value = null;
26 |
27 | if (type === "Point2D") {
28 | value = parsePoint(names, args);
29 | }
30 | else if (type === "Number") {
31 | value = parseNumber(names, args);
32 | }
33 | else if (type === "Array" || type === "Point2D[]") {
34 | const values = [];
35 |
36 | while (args.length > 0) {
37 | values.push(parsePoint(names, args));
38 | }
39 |
40 | if (values.length > 0) {
41 | value = values;
42 | }
43 | }
44 | else if (type === "Optional" || type === "Number?") {
45 | value = parseNumber(names, args);
46 |
47 | if (value === null) {
48 | value = undefined;
49 | }
50 | }
51 | else {
52 | throw new TypeError(`Unrecognized value type: ${type}`);
53 | }
54 |
55 | if (value !== null) {
56 | result.push(value);
57 | }
58 | else {
59 | throw new TypeError(`Unable to extract value for ${names}`);
60 | }
61 | }
62 |
63 | return result;
64 | }
65 |
66 | /**
67 | * parseNumber
68 | *
69 | * @param {Array} names
70 | * @param {Array} args
71 | * @returns {number}
72 | */
73 | export function parseNumber(names, args) {
74 | let result = null;
75 |
76 | if (args.length > 0) {
77 | const item = args[0];
78 | const itemType = typeof item;
79 |
80 | if (itemType === "number") {
81 | return args.shift();
82 | }
83 | else if (itemType === "object") {
84 | for (const prop of names) {
85 | if (prop in item && typeof item[prop] === "number") {
86 | result = item[prop];
87 | break;
88 | }
89 | }
90 | }
91 | }
92 |
93 | return result;
94 | }
95 |
96 | /**
97 | * parsePoint
98 | *
99 | * @param {Array} names
100 | * @param {Array} args
101 | * @returns {Array}
102 | */
103 | export function parsePoint(names, args) {
104 | let result = null;
105 |
106 | if (args.length > 0) {
107 | const item = args[0];
108 | const itemType = typeof item;
109 |
110 | if (itemType === "number") {
111 | if (args.length > 1) {
112 | const x = args.shift();
113 | const y = args.shift();
114 |
115 | result = new Point2D(x, y);
116 | }
117 | }
118 | else if (Array.isArray(item) && item.length > 1) {
119 | if (item.length === 2) {
120 | const [x, y] = args.shift();
121 |
122 | result = new Point2D(x, y);
123 | }
124 | else {
125 | throw new TypeError(`Unhandled array of length ${item.length}`);
126 | }
127 | }
128 | else if (itemType === "object") {
129 | if ("x" in item && "y" in item) {
130 | result = new Point2D(item.x, item.y);
131 | // eslint-disable-next-line compat/compat
132 | if (Object.keys(item).length === 2) {
133 | args.shift();
134 | }
135 | }
136 | else {
137 | for (const props of names) {
138 | if (Array.isArray(props)) {
139 | if (props.every(p => p in item)) {
140 | result = new Point2D(item[props[0]], item[props[1]]);
141 | break;
142 | }
143 | }
144 | else if (props in item) {
145 | result = parsePoint([], [item[props]]);
146 | break;
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
153 | return result;
154 | }
155 |
156 | /**
157 | * ShapeInfo
158 | * @memberof module:kld-intersections
159 | */
160 | export default class ShapeInfo {
161 | /**
162 | * @param {string} name
163 | * @param {Array} args
164 | * @returns {module:kld-intersections.ShapeInfo}
165 | */
166 | constructor(name, args) {
167 | this.name = name;
168 | this.args = args;
169 | }
170 |
171 | static arc(...args) {
172 | const types = [
173 | [["center", ["centerX", "centerY"], ["cx", "cy"]], "Point2D"],
174 | [["radiusX", "rx"], "Number"],
175 | [["radiusY", "ry"], "Number"],
176 | [["startRadians"], "Number"],
177 | [["endRadians"], "Number"]
178 | ];
179 | const values = getValues(types, args);
180 |
181 | return new ShapeInfo(ShapeInfo.ARC, values);
182 | }
183 |
184 | static quadraticBezier(...args) {
185 | const types = [
186 | [["p1", ["p1x", "p1y"]], "Point2D"],
187 | [["p2", ["p2x", "p2y"]], "Point2D"],
188 | [["p3", ["p3x", "p3y"]], "Point2D"]
189 | ];
190 | const values = getValues(types, args);
191 |
192 | return new ShapeInfo(ShapeInfo.QUADRATIC_BEZIER, values);
193 | }
194 |
195 | static cubicBezier(...args) {
196 | const types = [
197 | [["p1", ["p1x", "p1y"]], "Point2D"],
198 | [["p2", ["p2x", "p2y"]], "Point2D"],
199 | [["p3", ["p3x", "p3y"]], "Point2D"],
200 | [["p4", ["p4x", "p4y"]], "Point2D"]
201 | ];
202 | const values = getValues(types, args);
203 |
204 | return new ShapeInfo(ShapeInfo.CUBIC_BEZIER, values);
205 | }
206 |
207 | static circle(...args) {
208 | const types = [
209 | [["center", ["centerX", "centerY"], ["cx", "cy"]], "Point2D"],
210 | [["radius", "r"], "Number"]
211 | ];
212 | const values = getValues(types, args);
213 |
214 | return new ShapeInfo(ShapeInfo.CIRCLE, values);
215 | }
216 |
217 | static ellipse(...args) {
218 | const types = [
219 | [["center", ["centerX", "centerY"], ["cx", "cy"]], "Point2D"],
220 | [["radiusX", "rx"], "Number"],
221 | [["radiusY", "ry"], "Number"]
222 | ];
223 | const values = getValues(types, args);
224 |
225 | return new ShapeInfo(ShapeInfo.ELLIPSE, values);
226 | }
227 |
228 | static line(...args) {
229 | const types = [
230 | [["p1", ["p1x", "p1y"], ["x1", "y1"]], "Point2D"],
231 | [["p2", ["p2x", "p2y"], ["x2", "y2"]], "Point2D"]
232 | ];
233 | const values = getValues(types, args);
234 |
235 | return new ShapeInfo(ShapeInfo.LINE, values);
236 | }
237 |
238 | static path(...args) {
239 | parser.parseData(args[0]);
240 |
241 | return new ShapeInfo(ShapeInfo.PATH, handler.shapes);
242 | }
243 |
244 | static polygon(...args) {
245 | const types = [
246 | [[], "Array"]
247 | ];
248 | const values = getValues(
249 | types,
250 | args.length === 1 && Array.isArray(args[0]) ? args[0] : args
251 | );
252 |
253 | return new ShapeInfo(ShapeInfo.POLYGON, values);
254 | }
255 |
256 | static polyline(...args) {
257 | const types = [
258 | [[], "Array"]
259 | ];
260 | const values = getValues(
261 | types,
262 | args.length === 1 && Array.isArray(args[0]) ? args[0] : args
263 | );
264 |
265 | return new ShapeInfo(ShapeInfo.POLYLINE, values);
266 | }
267 |
268 | static rectangle(...args) {
269 | const types = [
270 | [["topLeft", ["x", "y"], ["left", "top"]], "Point2D"],
271 | [["size", ["width", "height"], ["w", "h"]], "Point2D"],
272 | [["radiusX", "rx"], "Optional"],
273 | [["radiusY", "ry"], "Optional"]
274 | ];
275 | const values = getValues(types, args);
276 |
277 | // fix up bottom-right point
278 | const p1 = values[0];
279 | const p2 = values[1];
280 | values[1] = new Point2D(p1.x + p2.x, p1.y + p2.y);
281 |
282 | // create shape info
283 | const result = new ShapeInfo(ShapeInfo.RECTANGLE, values);
284 |
285 | // handle possible rounded rectangle values
286 | let ry = result.args.pop();
287 | let rx = result.args.pop();
288 |
289 | rx = rx === undefined ? 0 : rx;
290 | ry = ry === undefined ? 0 : ry;
291 |
292 | if (rx === 0 && ry === 0) {
293 | return result;
294 | }
295 |
296 | const {x: p1x, y: p1y} = result.args[0];
297 | const {x: p2x, y: p2y} = result.args[1];
298 | const width = p2x - p1x;
299 | const height = p2y - p1y;
300 |
301 | if (rx === 0) {
302 | rx = ry;
303 | }
304 | if (ry === 0) {
305 | ry = rx;
306 | }
307 | if (rx > width * 0.5) {
308 | rx = width * 0.5;
309 | }
310 | if (ry > height * 0.5) {
311 | ry = height * 0.5;
312 | }
313 |
314 | const x0 = p1x;
315 | const y0 = p1y;
316 | const x1 = p1x + rx;
317 | const y1 = p1y + ry;
318 | const x2 = p2x - rx;
319 | const y2 = p2y - ry;
320 | const x3 = p2x;
321 | const y3 = p2y;
322 |
323 | const segments = [
324 | ShapeInfo.arc(x1, y1, rx, ry, 2 * degree90, 3 * degree90),
325 | ShapeInfo.line(x1, y0, x2, y0),
326 | ShapeInfo.arc(x2, y1, rx, ry, 3 * degree90, 4 * degree90),
327 | ShapeInfo.line(x3, y1, x3, y2),
328 | ShapeInfo.arc(x2, y2, rx, ry, 0, degree90),
329 | ShapeInfo.line(x2, y3, x1, y3),
330 | ShapeInfo.arc(x1, y2, rx, ry, degree90, 2 * degree90),
331 | ShapeInfo.line(x0, y2, x0, y1)
332 | ];
333 |
334 | return new ShapeInfo(ShapeInfo.PATH, segments);
335 | }
336 | }
337 |
338 | // define shape name constants
339 | ShapeInfo.ARC = "Arc";
340 | ShapeInfo.QUADRATIC_BEZIER = "Bezier2";
341 | ShapeInfo.CUBIC_BEZIER = "Bezier3";
342 | ShapeInfo.CIRCLE = "Circle";
343 | ShapeInfo.ELLIPSE = "Ellipse";
344 | ShapeInfo.LINE = "Line";
345 | ShapeInfo.PATH = "Path";
346 | ShapeInfo.POLYGON = "Polygon";
347 | ShapeInfo.POLYLINE = "Polyline";
348 | ShapeInfo.RECTANGLE = "Rectangle";
349 |
350 | // setup path parser handler after ShapeInfo has been defined
351 | const handler = new PathHandler(ShapeInfo);
352 |
353 | parser.setHandler(handler);
354 |
--------------------------------------------------------------------------------
/lib/Shapes.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-rest-params */
2 | /**
3 | * Shapes
4 | *
5 | * @copyright 2017, Kevin Lindsey
6 | * @module Shapes
7 | * @deprecated use ShapeInfo
8 | */
9 |
10 | import ShapeInfo from "./ShapeInfo.js";
11 |
12 | /**
13 | * Build shapes for intersection
14 | */
15 | class Shapes {
16 | /**
17 | * arc
18 | *
19 | * @deprecated use ShapeInfo.arc
20 | * @param {number} centerX
21 | * @param {number} centerY
22 | * @param {number} radiusX
23 | * @param {number} radiusY
24 | * @param {number} startRadians
25 | * @param {number} endRadians
26 | * @returns {module:kld-intersections.ShapeInfo}
27 | */
28 | static arc(centerX, centerY, radiusX, radiusY, startRadians, endRadians) {
29 | return ShapeInfo.arc(...arguments);
30 | }
31 |
32 | /**
33 | * quadraticBezier
34 | *
35 | * @deprecated use ShapeInfo.quadraticBezier
36 | * @param {number} p1x
37 | * @param {number} p1y
38 | * @param {number} p2x
39 | * @param {number} p2y
40 | * @param {number} p3x
41 | * @param {number} p3y
42 | * @returns {module:kld-intersections.ShapeInfo}
43 | */
44 | static quadraticBezier(p1x, p1y, p2x, p2y, p3x, p3y) {
45 | return ShapeInfo.quadraticBezier(...arguments);
46 | }
47 |
48 | /**
49 | * cubicBezier
50 | *
51 | * @deprecated use ShapeInfo.cubicBezier
52 | * @param {number} p1x
53 | * @param {number} p1y
54 | * @param {number} p2x
55 | * @param {number} p2y
56 | * @param {number} p3x
57 | * @param {number} p3y
58 | * @param {number} p4x
59 | * @param {number} p4y
60 | * @returns {module:kld-intersections.ShapeInfo}
61 | */
62 | static cubicBezier(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
63 | return ShapeInfo.cubicBezier(...arguments);
64 | }
65 |
66 | /**
67 | * circle
68 | *
69 | * @deprecated use ShapeInfo.circle
70 | * @param {number} centerX
71 | * @param {number} centerY
72 | * @param {number} radius
73 | * @returns {module:kld-intersections.ShapeInfo}
74 | */
75 | static circle(centerX, centerY, radius) {
76 | return ShapeInfo.circle(...arguments);
77 | }
78 |
79 | /**
80 | * ellipse
81 | *
82 | * @deprecated use ShapeInfo.ellipse
83 | * @param {number} centerX
84 | * @param {number} centerY
85 | * @param {number} radiusX
86 | * @param {number} radiusY
87 | * @returns {module:kld-intersections.ShapeInfo}
88 | */
89 | static ellipse(centerX, centerY, radiusX, radiusY) {
90 | return ShapeInfo.ellipse(...arguments);
91 | }
92 |
93 | /**
94 | * line
95 | *
96 | * @deprecated use ShapeInfo.line
97 | * @param {number} p1x
98 | * @param {number} p1y
99 | * @param {number} p2x
100 | * @param {number} p2y
101 | * @returns {module:kld-intersections.ShapeInfo}
102 | */
103 | static line(p1x, p1y, p2x, p2y) {
104 | return ShapeInfo.line(...arguments);
105 | }
106 |
107 | /**
108 | * path
109 | *
110 | * @deprecated use ShapeInfo.path
111 | * @param {string} pathData
112 | * @returns {module:kld-intersections.ShapeInfo}
113 | */
114 | static path(pathData) {
115 | return ShapeInfo.path(...arguments);
116 | }
117 |
118 | /**
119 | * polygon
120 | *
121 | * @deprecated use ShapeInfo.polygon
122 | * @param {Array} coords
123 | * @returns {module:kld-intersections.ShapeInfo}
124 | */
125 | static polygon(coords) {
126 | return ShapeInfo.polygon(...arguments);
127 | }
128 |
129 | /**
130 | * polyline
131 | *
132 | * @deprecated use ShapeInfo.polyline
133 | * @param {Array} coords
134 | * @returns {module:kld-intersections.ShapeInfo}
135 | */
136 | static polyline(coords) {
137 | return ShapeInfo.polyline(...arguments);
138 | }
139 |
140 | /**
141 | * rectangle
142 | *
143 | * @deprecated use ShapeInfo.rectangle
144 | * @param {number} x
145 | * @param {number} y
146 | * @param {number} width
147 | * @param {number} height
148 | * @param {number} [rx]
149 | * @param {number} [ry]
150 | * @returns {module:kld-intersections.ShapeInfo}
151 | */
152 | static rectangle(x, y, width, height, rx = 0, ry = 0) {
153 | return ShapeInfo.rectangle(...arguments);
154 | }
155 | }
156 |
157 | export default Shapes;
158 |
--------------------------------------------------------------------------------
/lib/SvgShapes.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-syntax */
2 | import {Point2D} from "kld-affine";
3 | import ShapeInfo from "./ShapeInfo.js";
4 |
5 | class SvgShapes {
6 | /**
7 | * circle
8 | *
9 | * @param {SVGCircleElement} circle
10 | * @returns {module:kld-intersections.ShapeInfo}
11 | */
12 | static circle(circle) {
13 | if (circle instanceof SVGCircleElement === false) {
14 | throw new TypeError(`Expected SVGCircleElement, but found ${circle}`);
15 | }
16 |
17 | const center = new Point2D(
18 | circle.cx.baseVal.value,
19 | circle.cy.baseVal.value
20 | );
21 | const radius = circle.r.baseVal.value;
22 |
23 | return ShapeInfo.circle(center, radius);
24 | }
25 |
26 | /**
27 | * ellipse
28 | *
29 | * @param {SVGEllipseElement} ellipse
30 | * @returns {module:kld-intersections.ShapeInfo}
31 | */
32 | static ellipse(ellipse) {
33 | if (ellipse instanceof SVGEllipseElement === false) {
34 | throw new TypeError(`Expected SVGEllipseElement, but found ${ellipse}`);
35 | }
36 |
37 | const center = new Point2D(
38 | ellipse.cx.baseVal.value,
39 | ellipse.cy.baseVal.value
40 | );
41 | const radiusX = ellipse.rx.baseVal.value;
42 | const radiusY = ellipse.ry.baseVal.value;
43 |
44 | return ShapeInfo.ellipse(center, radiusX, radiusY);
45 | }
46 |
47 | /**
48 | * line
49 | *
50 | * @param {SVGLineElement} line
51 | * @returns {module:kld-intersections.ShapeInfo}
52 | */
53 | static line(line) {
54 | if (line instanceof SVGLineElement === false) {
55 | throw new TypeError(`Expected SVGLineElement, but found ${line}`);
56 | }
57 |
58 | const p1 = new Point2D(
59 | line.x1.baseVal.value,
60 | line.y1.baseVal.value
61 | );
62 | const p2 = new Point2D(
63 | line.x2.baseVal.value,
64 | line.y2.baseVal.value
65 | );
66 |
67 | return ShapeInfo.line(p1, p2);
68 | }
69 |
70 | /**
71 | * path
72 | *
73 | * @param {SVGPathElement} path
74 | * @returns {module:kld-intersections.ShapeInfo}
75 | */
76 | static path(path) {
77 | if (path instanceof SVGPathElement === false) {
78 | throw new TypeError(`Expected SVGPathElement, but found ${path}`);
79 | }
80 |
81 | const pathData = path.getAttributeNS(null, "d");
82 |
83 | return ShapeInfo.path(pathData);
84 | }
85 |
86 | /**
87 | * polygon
88 | *
89 | * @param {SVGPolygonElement} polygon
90 | * @returns {module:kld-intersections.ShapeInfo}
91 | */
92 | static polygon(polygon) {
93 | if (polygon instanceof SVGPolygonElement === false) {
94 | throw new TypeError(`Expected SVGPolygonElement, but found ${polygon}`);
95 | }
96 |
97 | const points = [];
98 |
99 | for (let i = 0; i < polygon.points.numberOfItems; i++) {
100 | const point = polygon.points.getItem(i);
101 |
102 | points.push(new Point2D(point.x, point.y));
103 | }
104 |
105 | return ShapeInfo.polygon(points);
106 | }
107 |
108 | /**
109 | * polyline
110 | *
111 | * @param {SVGPolylineElement} polyline
112 | * @returns {module:kld-intersections.ShapeInfo}
113 | */
114 | static polyline(polyline) {
115 | if (polyline instanceof SVGPolylineElement === false) {
116 | throw new TypeError(`Expected SVGPolylineElement, but found ${polyline}`);
117 | }
118 |
119 | const points = [];
120 |
121 | for (let i = 0; i < polyline.points.numberOfItems; i++) {
122 | const point = polyline.points.getItem(i);
123 |
124 | points.push(new Point2D(point.x, point.y));
125 | }
126 |
127 | return ShapeInfo.polyline(points);
128 | }
129 |
130 | /**
131 | * rect
132 | *
133 | * @param {SVGRectElement} rect
134 | * @returns {module:kld-intersections.ShapeInfo}
135 | */
136 | static rect(rect) {
137 | if (rect instanceof SVGRectElement === false) {
138 | throw new TypeError(`Expected SVGRectElement, but found ${rect}`);
139 | }
140 |
141 | return ShapeInfo.rectangle(
142 | rect.x.baseVal.value,
143 | rect.y.baseVal.value,
144 | rect.width.baseVal.value,
145 | rect.height.baseVal.value,
146 | rect.rx.baseVal.value,
147 | rect.ry.baseVal.value
148 | );
149 | }
150 |
151 | /**
152 | * element
153 | *
154 | * @param {SVGElement} element
155 | * @returns {module:kld-intersections.ShapeInfo}
156 | */
157 | static element(element) {
158 | if (element instanceof SVGElement === false) {
159 | throw new TypeError(`Expected SVGElement, but found ${element}`);
160 | }
161 |
162 | /* eslint-disable-next-line prefer-destructuring */
163 | const tagName = element.tagName;
164 |
165 | switch (tagName) {
166 | case "circle":
167 | return SvgShapes.circle(element);
168 | case "ellipse":
169 | return SvgShapes.ellipse(element);
170 | case "line":
171 | return SvgShapes.line(element);
172 | case "path":
173 | return SvgShapes.path(element);
174 | case "polygon":
175 | return SvgShapes.polygon(element);
176 | case "polyline":
177 | return SvgShapes.polyline(element);
178 | case "rect":
179 | return SvgShapes.rect(element);
180 | default:
181 | throw new TypeError(`Unrecognized element type: '${tagName}'`);
182 | }
183 | }
184 | }
185 |
186 | export default SvgShapes;
187 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kld-intersections",
3 | "version": "0.7.0",
4 | "description": "A library of intersection algorithms covering all SVG shape types",
5 | "author": {
6 | "name": "Kevin Lindsey",
7 | "email": "kevin@kevlindev.com"
8 | },
9 | "contributors": [
10 | {
11 | "name": "Robert Benko (Quazistax)",
12 | "email": "quazistax@gmail.com"
13 | },
14 | "Brett Zamir"
15 | ],
16 | "license": "BSD-3-Clause",
17 | "bugs": "https://github.com/thelonious/kld-intersections/issues",
18 | "homepage": "https://github.com/thelonious/kld-intersections",
19 | "repository": {
20 | "type": "git",
21 | "url": "git://github.com/thelonious/kld-intersections.git"
22 | },
23 | "main": "dist/index-umd.js",
24 | "module": "index.js",
25 | "browserslist": [
26 | "cover 100%"
27 | ],
28 | "scripts": {
29 | "eslint": "eslint --ext js,md .",
30 | "rollup": "rollup -c",
31 | "test": "npm run eslint && npm run rollup && mocha --require @babel/register",
32 | "start": "static -p 8055",
33 | "build-docs": "rm -rf docs/jsdoc/*;jsdoc --pedantic -c docs/jsdoc-config.js .",
34 | "open-docs": "opn http://localhost:8055/docs/jsdoc/ && npm start"
35 | },
36 | "keywords": [
37 | "intersection",
38 | "svg",
39 | "arc",
40 | "bezier",
41 | "circle",
42 | "ellipse",
43 | "line",
44 | "path",
45 | "polygon",
46 | "rectangle"
47 | ],
48 | "dependencies": {
49 | "kld-affine": "^2.1.1",
50 | "kld-path-parser": "^0.2.1",
51 | "kld-polynomial": "^0.3.0"
52 | },
53 | "devDependencies": {
54 | "@babel/core": "^7.9.6",
55 | "@babel/preset-env": "^7.9.6",
56 | "@babel/register": "^7.9.0",
57 | "@mysticatea/eslint-plugin": "^10.0.3",
58 | "eslint": "^5.16.0",
59 | "eslint-config-ash-nazg": "^4.0.0",
60 | "eslint-config-standard": "^12.0.0",
61 | "eslint-plugin-compat": "^3.5.1",
62 | "eslint-plugin-eslint-comments": "^3.1.2",
63 | "eslint-plugin-import": "^2.20.2",
64 | "eslint-plugin-jsdoc": "^4.8.4",
65 | "eslint-plugin-markdown": "^1.0.2",
66 | "eslint-plugin-no-use-extend-native": "^0.4.1",
67 | "eslint-plugin-node": "^9.2.0",
68 | "eslint-plugin-promise": "^4.2.1",
69 | "eslint-plugin-standard": "^4.0.1",
70 | "eslint-plugin-unicorn": "^8.0.2",
71 | "esm": "^3.2.25",
72 | "jsdoc": "^3.6.4",
73 | "kld-contours": "^0.3.2",
74 | "mocha": "^6.2.3",
75 | "node-static": "^0.7.11",
76 | "opn-cli": "^4.1.0",
77 | "rollup": "^1.32.1",
78 | "rollup-plugin-babel": "^4.4.0",
79 | "rollup-plugin-node-resolve": "^4.2.4",
80 | "rollup-plugin-terser": "^4.0.4",
81 | "typescript": "^3.8.3"
82 | },
83 | "engines": {
84 | "node": ">= 10.15.3"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/reference/Bezout.m:
--------------------------------------------------------------------------------
1 | BeginPackage["Bezout`"]
2 |
3 | (* Mathematica 5.0 defines Global`BezoutMatrix as being protected with no
4 | value. I'm unprotecting it here. This may cause problems in future
5 | versions of Mathematica. *)
6 |
7 | ClearAttributes[BezoutMatrix, Protected]
8 |
9 | BezoutMatrix::usage =
10 | "Create a BezoutMatrix of order l without expanding its entries"
11 |
12 | Begin["`Private`"]
13 | bezoutMin[i_, j_, l_] := Min[l, 2 l - 1 - i - j]
14 | bezoutMax[i_, j_, l_] := Max[l - j, l - i]
15 | bezoutSub[i_, j_, k_, l_] := 2 l - 1 - i - j - k
16 |
17 | bezoutEntry[i_, j_, l_] := \!\(Sum[Global`v\_\(k, bezoutSub[i, j, k, l]\), {k, bezoutMax[i, j, l], bezoutMin[i, j, l]}]\)
18 | BezoutMatrix[l_Integer] := Table[bezoutEntry[i, j, l], {i, 0, l - 1}, {j, 0, l - 1}]
19 | End[]
20 | EndPackage[]
21 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from "rollup-plugin-node-resolve";
2 | import babel from "rollup-plugin-babel";
3 | import {terser} from "rollup-plugin-terser";
4 |
5 | /**
6 | * @external RollupConfig
7 | * @type {PlainObject}
8 | * @see {@link https://rollupjs.org/guide/en#big-list-of-options}
9 | */
10 |
11 | /**
12 | * @param {PlainObject} [config={}]
13 | * @param {boolean} [config.minifying]
14 | * @param {string} [config.format="umd"]
15 | * @returns {external:RollupConfig}
16 | */
17 | function getRollupObject({minifying, format = "umd"} = {}) {
18 | const nonMinified = {
19 | input: "index.js",
20 | output: {
21 | format,
22 | sourcemap: minifying,
23 | file: `dist/index-${format}${minifying ? ".min" : ""}.js`,
24 | name: "KldIntersections"
25 | },
26 | plugins: [
27 | babel({
28 | babelrc: false,
29 | presets: [
30 | ["@babel/env", {modules: false}]
31 | ]
32 | }),
33 | resolve()
34 | ]
35 | };
36 | if (minifying) {
37 | nonMinified.plugins.push(terser());
38 | }
39 | return nonMinified;
40 | }
41 |
42 | // eslint-disable-next-line import/no-anonymous-default-export
43 | export default [
44 | getRollupObject({minifying: true, format: "umd"}),
45 | getRollupObject({minifying: false, format: "umd"}),
46 | getRollupObject({minifying: true, format: "esm"}),
47 | getRollupObject({minifying: false, format: "esm"})
48 | ];
49 |
--------------------------------------------------------------------------------
/scratchpad/arc-angle-test.js:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Point2D, Vector2D} from "../index.js";
3 |
4 | const EPSILON = 0.1; //1e-6;
5 | const TWO_PI = Math.PI * 2.0;
6 |
7 | const angleStep = 100;
8 | const centerX = 100;
9 | const centerY = 75;
10 | const radius = 50;
11 |
12 | describe("Arc Angles", () => {
13 | for (let startAngle = 0; startAngle < 360; startAngle += angleStep) {
14 | for (let endAngle = 0; endAngle < 360; endAngle += angleStep) {
15 | if (startAngle === endAngle) {
16 | continue;
17 | }
18 |
19 | it(`${startAngle} - ${endAngle}`, () => {
20 | const [startPoint, endPoint, flagA, flagS] = getArcCommandParameters(
21 | centerX, centerY,
22 | radius, radius,
23 | toRadians(startAngle), toRadians(endAngle)
24 | );
25 | const [cx, cy, rx, ry, sa, ea] = getArcParameters(
26 | startPoint, endPoint,
27 | radius, radius,
28 | toRadians(endAngle - startAngle),
29 | flagA, flagS
30 | );
31 |
32 | // assertNumbers(cx, centerX, "center x");
33 | // assertNumbers(cy, centerY, "center y");
34 | assertNumbers(rx, radius, "radius x");
35 | assertNumbers(ry, radius, "radius y");
36 | assertNumbers(sa < 0 ? sa + TWO_PI : sa, toRadians(startAngle), "start angle");
37 | assertNumbers(ea < 0 ? ea + TWO_PI : ea, toRadians(endAngle), "end angle");
38 | });
39 | }
40 | }
41 | });
42 |
43 | function assertNumbers(a, b, message) {
44 | assert(Math.abs(a - b) < EPSILON, `${message}: ${a} != ${b}`);
45 | }
46 |
47 | function toRadians(degrees) {
48 | return Math.PI * degrees / 180.0;
49 | }
50 |
51 | function toCartesian(centerX, centerY, radiusX, radiusY, angleRadians) {
52 | return new Point2D(
53 | centerX + (radiusX * Math.cos(angleRadians)),
54 | centerY + (radiusY * Math.sin(angleRadians))
55 | );
56 | }
57 |
58 | function getArcCommandParameters(centerX, centerY, radiusX, radiusY, startRadians, endRadians) {
59 | return [
60 | toCartesian(centerX, centerY, radiusX, radiusY, startRadians),
61 | toCartesian(centerX, centerY, radiusX, radiusY, endRadians),
62 | (endRadians - startRadians) > Math.PI ? 1 : 0,
63 | (endRadians - startRadians) > 0 ? 1 : 0
64 | ];
65 | }
66 |
67 | function getArcParameters(startPoint, endPoint, rx, ry, angle, arcFlag, sweepFlag) {
68 | angle = angle * Math.PI / 180;
69 |
70 | const c = Math.cos(angle);
71 | const s = Math.sin(angle);
72 | const TOLERANCE = 1e-6;
73 |
74 | // Section (F.6.5.1)
75 | const halfDiff = startPoint.subtract(endPoint).multiply(0.5);
76 | const x1p = halfDiff.x * c + halfDiff.y * s;
77 | const y1p = halfDiff.x * -s + halfDiff.y * c;
78 |
79 | // Section (F.6.6.1)
80 | rx = Math.abs(rx);
81 | ry = Math.abs(ry);
82 |
83 | // Section (F.6.6.2)
84 | const x1px1p = x1p * x1p;
85 | const y1py1p = y1p * y1p;
86 | const lambda = (x1px1p / (rx * rx)) + (y1py1p / (ry * ry));
87 |
88 | // Section (F.6.6.3)
89 | if (lambda > 1) {
90 | const factor = Math.sqrt(lambda);
91 |
92 | rx *= factor;
93 | ry *= factor;
94 | }
95 |
96 | // Section (F.6.5.2)
97 | const rxrx = rx * rx;
98 | const ryry = ry * ry;
99 | const rxy1 = rxrx * y1py1p;
100 | const ryx1 = ryry * x1px1p;
101 |
102 | let factor = (rxrx * ryry - rxy1 - ryx1) / (rxy1 + ryx1);
103 |
104 | if (Math.abs(factor) < TOLERANCE) {
105 | factor = 0;
106 | }
107 |
108 | let sq = Math.sqrt(factor);
109 |
110 | if (arcFlag === sweepFlag) {
111 | sq = -sq;
112 | }
113 |
114 | // Section (F.6.5.3)
115 | const mid = startPoint.add(endPoint).multiply(0.5);
116 | const cxp = sq * rx * y1p / ry;
117 | const cyp = sq * -ry * x1p / rx;
118 |
119 | // Section (F.6.5.5 - F.6.5.6)
120 | const xcr1 = (x1p - cxp) / rx;
121 | const xcr2 = (x1p + cxp) / rx;
122 | const ycr1 = (y1p - cyp) / ry;
123 | const ycr2 = (y1p + cyp) / ry;
124 |
125 | const theta1 = new Vector2D(1, 0).angleBetween(new Vector2D(xcr1, ycr1));
126 | // let deltaTheta = normalizeAngle(new Vector2D(xcr1, ycr1).angleBetween(new Vector2D(-xcr2, -ycr2)));
127 | let deltaTheta = new Vector2D(xcr1, ycr1).angleBetween(new Vector2D(-xcr2, -ycr2)) % TWO_PI;
128 |
129 | if (sweepFlag === false) {
130 | deltaTheta -= TWO_PI;
131 | }
132 |
133 | console.log(deltaTheta);
134 |
135 | return [
136 | cxp * c - cyp * s + mid.x,
137 | cxp * s + cyp * c + mid.y,
138 | rx,
139 | ry,
140 | theta1,
141 | theta1 + deltaTheta
142 | ];
143 | }
144 |
--------------------------------------------------------------------------------
/scratchpad/arc-fail.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import {ShapeInfo, Intersection} from "../index.js";
4 |
5 | // create geometry
6 | const path = "M80,80 a20,20 0 0,0 0,40 h50 a20,20 0 0,0 0,-40 a10,10 0 0,0 -15,-10 a15,15 0 0,0 -35,10z"
7 | const line = [-105, -45, 245, 230]
8 | const [x1, y1, x2, y2] = line
9 |
10 | // create shapes
11 | const shape1 = ShapeInfo.path(path);
12 | const shape2 = ShapeInfo.line(line)
13 |
14 | // intersect
15 | const result = Intersection.intersect(shape1, shape2);
16 |
17 | // build SVG file showing the shapes and intersection points
18 | const intersectionSVG = result.points.map(p => {
19 | return ``;
20 | }).join("\n ");
21 |
22 | const svg = ``;
29 |
30 | console.log(svg);
31 |
--------------------------------------------------------------------------------
/scratchpad/arc-test-2.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import {Shapes, Intersection} from "../index.js";
4 |
5 | const pathData = "M100,50 A50 50 0 0 1 0,50";
6 | const path = Shapes.path(pathData);
7 |
8 | const x1 = 0;
9 | const y1 = 100;
10 | const x2 = 100;
11 | const y2 = 0;
12 | const line = Shapes.line(x1, y1, x2, y2);
13 |
14 | // intersect
15 | const result = Intersection.intersect(path, line);
16 |
17 | // build SVG file showing the shapes, the center point, and intersection points
18 | const intersectionSVG = result.points.map(p => {
19 | return ``;
20 | }).join("\n ");
21 |
22 | const svg = ``;
29 |
30 | console.log(svg);
31 |
--------------------------------------------------------------------------------
/scratchpad/arc-test.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import {Intersection, Shapes, Point2D} from "../index.js";
4 |
5 | function toCartesian(centerX, centerY, radiusX, radiusY, angleRadians) {
6 | return new Point2D(
7 | centerX + (radiusX * Math.cos(angleRadians)),
8 | centerY + (radiusY * Math.sin(angleRadians))
9 | );
10 | }
11 |
12 | const centerX = 50;
13 | const centerY = 50;
14 | const radiusX = 50;
15 | const radiusY = 50;
16 | const startRadians = 0;
17 | const endRadians = Math.PI;
18 | const arc = Shapes.arc(centerX, centerY, radiusX, radiusY, startRadians, endRadians);
19 |
20 | const x1 = 0;
21 | const y1 = 100;
22 | const x2 = 100;
23 | const y2 = 0;
24 | const line = Shapes.line(x1, y1, x2, y2);
25 |
26 | // console.log(arc);
27 | // console.log(line);
28 |
29 | const startPoint = toCartesian(centerX, centerY, radiusX, radiusY, startRadians);
30 | const endPoint = toCartesian(centerX, centerY, radiusX, radiusY, endRadians);
31 | const flagA = (endRadians - startRadians) > Math.PI ? 1 : 0;
32 | const flagS = (endRadians - startRadians) > 0 ? 1 : 0;
33 |
34 | // intersect
35 | const result = Intersection.intersect(arc, line);
36 |
37 | // build SVG file showing the shapes, the center point, and intersection points
38 | const intersectionSVG = result.points.map(p => {
39 | return ``;
40 | }).join("\n ");
41 |
42 | const svg = ``;
49 |
50 | console.log(svg);
51 |
--------------------------------------------------------------------------------
/scratchpad/cubic-cubic-fail.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import {Shapes, Intersection} from "../index.js";
4 |
5 | // define path data
6 | const pathData1 = "M150,150 C183.33333333333331,216.66666666666663 233.33333333333337,216.66666666666663 300,150";
7 | const pathData2 = "M100,200 C166.66666666666663,133.33333333333337 233.33333333333337,133.33333333333337 300,200";
8 |
9 | // create pathS
10 | const path1 = Shapes.path(pathData1);
11 | const path2 = Shapes.path(pathData2);
12 |
13 | // intersect
14 | const result = Intersection.intersect(path1, path2);
15 |
16 | // build SVG file showing the shapes, the center point, and intersection points
17 | const intersectionSVG = result.points.map(p => {
18 | return ``;
19 | }).join("\n ");
20 |
21 | const svg = ``;
28 |
29 | console.log(svg);
30 |
--------------------------------------------------------------------------------
/scratchpad/find_parameters.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import {CubicBezier2D} from "kld-contours";
4 | import {Point2D, Intersection, IntersectionArgs} from "../index.js";
5 |
6 | /**
7 | *
8 | * @param {CubicBezier2D} path
9 | * @param {Point2D} point
10 | * @returns {number}
11 | */
12 | function findParameter(path, point) {
13 | const {x, y} = path.getBernsteinPolynomials();
14 |
15 | x.coefs[0] -= point.x;
16 |
17 | const roots = x.getRootsInInterval(0.0, 1.0);
18 |
19 | for (const t of roots) {
20 | const candidateY = y.eval(t);
21 |
22 | if (Math.abs(candidateY - point.y) < 1e-6) {
23 | return t;
24 | }
25 | }
26 |
27 | return NaN;
28 | }
29 |
30 | // parser path data
31 | const b1 = new CubicBezier2D(
32 | new Point2D(150, 150),
33 | new Point2D(184, 217),
34 | new Point2D(233, 217),
35 | new Point2D(300, 150)
36 | );
37 | const b2 = new CubicBezier2D(
38 | new Point2D(100, 200),
39 | new Point2D(167, 133),
40 | new Point2D(233, 133),
41 | new Point2D(300, 200)
42 | );
43 |
44 | const path1 = new IntersectionArgs("Bezier3", [b1.p1, b1.p2, b1.p3, b1.p4]);
45 | const path2 = new IntersectionArgs("Bezier3", [b2.p1, b2.p2, b2.p3, b2.p4]);
46 |
47 | // intersect
48 | const result = Intersection.intersect(path1, path2);
49 |
50 | // find bezier parametric values from first intersection point
51 | const param1 = findParameter(b1, result.points[0]);
52 | const param2 = findParameter(b2, result.points[0]);
53 |
54 | // calculate points on curves from their parametric values
55 | const point1 = b1.getPointAtParameter(param1);
56 | const point2 = b2.getPointAtParameter(param2);
57 |
58 | // compare the results
59 | console.log();
60 | console.log("These should all be pretty close to the same point");
61 | console.log(`Reported intersection: ${result.points[0]}`);
62 | console.log(`Point on first curve: ${point1}, t=${param1}`);
63 | console.log(`Point on second curve: ${point2}, t=${param2}`);
64 |
--------------------------------------------------------------------------------
/scratchpad/shapeInfo-test.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node -r esm
2 |
3 | import util from "util";
4 | import ShapeInfo from "../lib/ShapeInfo.js";
5 | import {Point2D, Vector2D} from "kld-affine";
6 |
7 | const circle = ShapeInfo.circle({
8 | center: new Point2D(80, 60),
9 | radius: 40
10 | });
11 | const circle2 = ShapeInfo.circle(80, 60, 40);
12 | const circle3 = ShapeInfo.circle(new Point2D(80, 60), 40);
13 |
14 | const ellipse = ShapeInfo.ellipse({
15 | center: new Point2D(80, 60),
16 | radiusX: 30,
17 | radiusY: 40
18 | });
19 | const ellipse2 = ShapeInfo.ellipse(80, 60, 30, 40);
20 | const ellipse3 = ShapeInfo.ellipse(new Point2D(80, 60), 30, 40);
21 |
22 | const line = ShapeInfo.line({
23 | p1: new Point2D(10, 20),
24 | p2: new Point2D(110, 120)
25 | });
26 | const line2 = ShapeInfo.line(10, 20, 110, 120);
27 | const line3 = ShapeInfo.line(new Point2D(10, 20), new Point2D(110, 120));
28 |
29 | const path = ShapeInfo.path({ d: "M0 10 L100 110"});
30 | const path2 = ShapeInfo.path("M0 10 L100 110");
31 | // const path3 = ShapeInfo.path({
32 | // segments: [
33 | // ShapeInfo.line({p1: new Point2D(10, 10), p2: new Point2D(110, 110)})
34 | // ]
35 | // });
36 |
37 | const polygon = ShapeInfo.polygon({
38 | points: [
39 | new Point2D(10, 20),
40 | new Point2D(60, 20),
41 | new Point2D(60, 100),
42 | new Point2D(10, 100)
43 | ]
44 | });
45 | const polygon2 = ShapeInfo.polygon([10, 20, 60, 20, 60, 100, 10, 100]);
46 | const polygon3 = ShapeInfo.polygon([new Point2D(10, 20), new Point2D(60, 20), new Point2D(60, 100), new Point2D(10, 100)]);
47 |
48 | const rectangle = ShapeInfo.rectangle({
49 | topLeft: new Point2D(35, 25),
50 | bottomRight: new Point2D(80, 100)
51 | });
52 | const rectangle2 = ShapeInfo.rectangle(35, 25, 80, 100);
53 | const rectangle3 = ShapeInfo.rectangle(new Point2D(35, 25), new Vector2D(80, 100));
54 |
55 | const shapes = [
56 | circle, circle2, circle3,
57 | ellipse, ellipse2, ellipse3,
58 | line, line2, line3,
59 | path, path2, /*path3,*/
60 | polygon, polygon2, polygon3,
61 | rectangle, rectangle2, rectangle3
62 | ];
63 |
64 | shapes.forEach(shape => console.log(util.inspect(shape, {depth: 4})));
65 |
--------------------------------------------------------------------------------
/scratchpad/temp.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/scratchpad/visual-test-conversion/convert:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # batch convert all example SVGs from http://www.kevlindev.com/geometry/2D/intersections/index.htm
4 |
5 | for f in ../../visual-test-original/*.svg; do
6 | filename=$(basename ${f})
7 | target=${filename#*_}
8 | xsltproc --output "../../visual-test/${target}" transform_visual_test.xsl ${f}
9 | done
10 |
--------------------------------------------------------------------------------
/scratchpad/visual-test-conversion/transform_visual_test.xsl:
--------------------------------------------------------------------------------
1 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/test/Itertools.js:
--------------------------------------------------------------------------------
1 | // TODO: Consider switching these functions over to using iteration and then
2 | // turning into its own module
3 |
4 | function zip(...arrays) {
5 | const lengths = arrays.map(a => a.length);
6 | const minLength = Math.min(...lengths);
7 | const result = [];
8 |
9 | for (let i = 0; i < minLength; i++) {
10 | let element = arrays.map(a => a[i]);
11 |
12 | result.push(element);
13 | }
14 |
15 | return result;
16 | }
17 |
18 | class Product {
19 | constructor(...items) {
20 | const itemsLength = items.length;
21 |
22 | if (itemsLength > 0) {
23 | if (typeof items[itemsLength - 1] === "number" ) {
24 | this.repeating = items.pop();
25 | }
26 | else {
27 | this.repeating = 1;
28 | }
29 |
30 | this.items = items;
31 | }
32 | else {
33 | this.items = [];
34 | this.repeating = 1;
35 | }
36 | }
37 |
38 | [Symbol.iterator]() {
39 | return {
40 | indexes: Array(this.items.length * this.repeating).fill(0),
41 | lists: Array(this.repeating).fill(this.items).reduce((acc, val) => acc.concat(val), []),
42 | complete: this.items.count === 0,
43 |
44 | next() {
45 | if (this.complete === true) {
46 | return {done: true}
47 | }
48 |
49 | let result = [];
50 |
51 | for (let [list, index] of zip(this.lists, this.indexes)) {
52 | result.push(list[index]);
53 | }
54 |
55 | this.complete = true;
56 |
57 | for (let i = this.indexes.length - 1; i >= 0; i--) {
58 | const list = this.lists[i];
59 | let index = this.indexes[i];
60 |
61 | index += 1;
62 |
63 | if (index >= list.length) {
64 | this.indexes[i] = 0;
65 | }
66 | else {
67 | this.indexes[i] = index;
68 | this.complete = false;
69 | break;
70 | }
71 | }
72 |
73 | return {value: result, done: false};
74 | }
75 | }
76 | }
77 | }
78 |
79 | module.exports = {
80 | zip,
81 | Product,
82 | // Permutation
83 | };
84 |
--------------------------------------------------------------------------------
/test/affine-shapes-test.js:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Point2D, Vector2D} from "kld-affine";
3 | import AffineShapes from "../lib/AffineShapes.js";
4 | import ShapeInfo from "../lib/ShapeInfo.js";
5 |
6 |
7 | describe("Shapes API", () => {
8 | it("arc", () => {
9 | const result = AffineShapes.arc(new Point2D(10, 20), 30, 40, 0, 1.57);
10 | const expected = new ShapeInfo("Arc", [new Point2D(10, 20), 30, 40, 0, 1.57]);
11 |
12 | assert.deepStrictEqual(result, expected);
13 | });
14 | it("quadratic bezier", () => {
15 | const result = AffineShapes.quadraticBezier(new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60));
16 | const expected = new ShapeInfo("Bezier2", [new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)]);
17 |
18 | assert.deepStrictEqual(result, expected);
19 | });
20 | it("cubic bezier", () => {
21 | const result = AffineShapes.cubicBezier(new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60), new Point2D(70, 80));
22 | const expected = new ShapeInfo("Bezier3", [new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60), new Point2D(70, 80)]);
23 |
24 | assert.deepStrictEqual(result, expected);
25 | });
26 | it("circle", () => {
27 | const result = AffineShapes.circle(new Point2D(10, 20), 30);
28 | const expected = new ShapeInfo("Circle", [new Point2D(10, 20), 30]);
29 |
30 | assert.deepStrictEqual(result, expected);
31 | });
32 | it("ellipse", () => {
33 | const result = AffineShapes.ellipse(new Point2D(10, 20), 30, 40);
34 | const expected = new ShapeInfo("Ellipse", [new Point2D(10, 20), 30, 40]);
35 |
36 | assert.deepStrictEqual(result, expected);
37 | });
38 | it("line", () => {
39 | const result = AffineShapes.line(new Point2D(10, 20), new Point2D(30, 40));
40 | const expected = new ShapeInfo("Line", [new Point2D(10, 20), new Point2D(30, 40)]);
41 |
42 | assert.deepStrictEqual(result, expected);
43 | });
44 | it("path", () => {
45 | const result = AffineShapes.path("M0, 10 L100, 110");
46 | const expected = new ShapeInfo("Path", [
47 | AffineShapes.line(new Point2D(0, 10), new Point2D(100, 110))
48 | ]);
49 |
50 | assert.deepStrictEqual(result, expected);
51 | });
52 | it("polygon", () => {
53 | const result = AffineShapes.polygon(new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60));
54 | const expected = new ShapeInfo("Polygon", [
55 | [
56 | new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)
57 | ]
58 | ]);
59 |
60 | assert.deepStrictEqual(result, expected);
61 | });
62 | it("polyline", () => {
63 | const result = AffineShapes.polyline(new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60));
64 | const expected = new ShapeInfo("Polyline", [
65 | [
66 | new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)
67 | ]
68 | ]);
69 |
70 | assert.deepStrictEqual(result, expected);
71 | });
72 | it("rectangle", () => {
73 | const result = AffineShapes.rectangle(new Point2D(10, 20), new Vector2D(30, 40));
74 | const expected = new ShapeInfo("Rectangle", [new Point2D(10, 20), new Point2D(40, 60)]);
75 |
76 | assert.deepStrictEqual(result, expected);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/test/intersection-tests.js:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Shapes, Intersection, Point2D} from "../index.js";
3 |
4 | /**
5 | * A sorter for Point2Ds
6 | *
7 | * @param {Point2D} p1
8 | * @param {Point2D} p2
9 | * @returns {number}
10 | */
11 | function pointSorter(p1, p2) {
12 | if (p1.x < p2.x) {
13 | return -1;
14 | }
15 | else if (p1.x > p2.x) {
16 | return 1;
17 | }
18 | else if (p1.y < p2.y) {
19 | return -1;
20 | }
21 | else if (p1.y > p2.y) {
22 | return 1;
23 | }
24 |
25 | return 0;
26 | }
27 |
28 | /**
29 | * Assert two points for equality
30 | *
31 | * @param {Array} points1
32 | * @param {Array} points2
33 | */
34 | function assertEqualPoints(points1, points2) {
35 | assert.strictEqual(points1.length, points2.length);
36 |
37 | /* eslint-disable-next-line unicorn/no-for-loop */
38 | for (let i = 0; i < points1.length; i++) {
39 | const p1 = points1[i];
40 | const p2 = points2[i];
41 |
42 | assert(p1.equals(p2));
43 | }
44 | }
45 |
46 | /**
47 | * Intersect two shapes and assert the intersection points
48 | *
49 | * @param {ShapeInfo} shape1
50 | * @param {ShapeInfo} shape2
51 | * @param {Array} expected
52 | */
53 | function assertIntersections(shape1, shape2, expected) {
54 | const result = Intersection.intersect(shape1, shape2);
55 | const points = result.points.sort(pointSorter);
56 |
57 | assertEqualPoints(points, expected);
58 | }
59 |
60 | describe("Intersections", () => {
61 | it("Quadratic Bezier - Quadratic Bezier", () => {
62 | const shape1 = Shapes.path("M83,214 Q335,173 91,137");
63 | const shape2 = Shapes.path("M92,233 Q152,30 198,227");
64 |
65 | const expected = [
66 | new Point2D(98.72720538504241, 211.3625905201401),
67 | new Point2D(129.54187987698563, 143.27285394359626),
68 | new Point2D(173.83770343111445, 152.940611499889),
69 | new Point2D(188.27575037023598, 190.33459908605727)
70 | ];
71 |
72 | assertIntersections(shape1, shape2, expected);
73 | });
74 | it("Quadratic Bezier - Cubic Bezier", () => {
75 | const shape1 = Shapes.path("M123,47 Q146,255 188,47");
76 | const shape2 = Shapes.path("M171,143 C22,132 330,64 107,65");
77 | const expected = [
78 | new Point2D(125.13460408076253, 65.08475401519482),
79 | new Point2D(134.49717251169614, 120.3114904768965),
80 | new Point2D(140.0203543393166, 138.4178510972871),
81 | new Point2D(160.68034080878516, 142.07160371675803),
82 | new Point2D(177.7766308623799, 92.57967705087721),
83 | new Point2D(182.93500601086055, 70.88034340965095)
84 | ];
85 |
86 | assertIntersections(shape1, shape2, expected);
87 | });
88 | it("Quadratic Bezier - Circle", () => {
89 | const shape1 = Shapes.path("M50,20 Q200,300 350,10");
90 | const shape2 = Shapes.circle(80, 80, 40);
91 | const expected = [
92 | new Point2D(63.27523884077001, 43.66431004966725),
93 | new Point2D(108.75184271113037, 107.8088392550718)
94 | ];
95 |
96 | assertIntersections(shape1, shape2, expected);
97 | });
98 | it("Quadratic Bezier - Ellipse", () => {
99 | const shape1 = Shapes.path("M50,20 Q200,300 350,10");
100 | const shape2 = Shapes.ellipse(80, 80, 30, 40);
101 | const expected = [
102 | new Point2D(64.5070161096711, 45.74689113414642),
103 | new Point2D(104.55299413054938, 102.98407097584307)
104 | ];
105 |
106 | assertIntersections(shape1, shape2, expected);
107 | });
108 | it("Quadratic Bezier - Line", () => {
109 | const shape1 = Shapes.path("M50,20 Q200,300 350,10");
110 | const shape2 = Shapes.line(10, 20, 300, 210);
111 | const expected = [
112 | new Point2D(74.86365117074867, 62.49687490497326),
113 | new Point2D(216.42491507244551, 155.2439098750505)
114 | ];
115 |
116 | assertIntersections(shape1, shape2, expected);
117 | });
118 | it("Quadratic Bezier - Polygon", () => {
119 | const shape1 = Shapes.path("M50,20 Q200,300 350,10");
120 | const shape2 = Shapes.polygon([58, 30, 110, 27, 135, 108, 74, 145, 28, 108, 26, 53]);
121 | const expected = [
122 | new Point2D(56.18558820664384, 31.304108476474752),
123 | new Point2D(118.41228100507112, 118.06140332479293)
124 | ];
125 |
126 | assertIntersections(shape1, shape2, expected);
127 | });
128 | it("Quadratic Bezier - Rectangle", () => {
129 | const shape1 = Shapes.path("M50,20 Q200,300 350,10");
130 | const shape2 = Shapes.rectangle(35, 25, 80, 100);
131 | const expected = [
132 | new Point2D(52.70336708542367, 25.000000000000014),
133 | new Point2D(115, 114.575)
134 | ];
135 |
136 | assertIntersections(shape1, shape2, expected);
137 | });
138 | it("Cubic Bezier - Cubic Bezier", () => {
139 | const shape1 = Shapes.path("M203,140 C206,359 245,6 248,212");
140 | const shape2 = Shapes.path("M177,204 C441,204 8,149 265,154");
141 | const expected = [
142 | new Point2D(203.26720646036085, 154.38186574206628),
143 | new Point2D(203.80448029878028, 171.0080340325034),
144 | new Point2D(206.53089745589955, 203.7205799064048),
145 | new Point2D(218.26560490823644, 203.3977092167175),
146 | new Point2D(226.37034491873996, 177.96211427292837),
147 | new Point2D(234.28537892520342, 153.67909587175902),
148 | new Point2D(244.55650504358164, 153.7151359675932),
149 | new Point2D(247.269811682568, 184.6978038847097),
150 | new Point2D(247.8042453163769, 201.4484074305467)
151 | ];
152 |
153 | assertIntersections(shape1, shape2, expected);
154 | });
155 | it("Cubic Bezier - Circle", () => {
156 | const shape1 = Shapes.path("M50,20 C50,210 250,20 250,210");
157 | const shape2 = Shapes.circle(80, 80, 40);
158 | const expected = [
159 | new Point2D(52.22352295755968, 51.21689170519913),
160 | new Point2D(104.03665107361067, 111.97247887113267)
161 | ];
162 |
163 | assertIntersections(shape1, shape2, expected);
164 | });
165 | it("Cubic Bezier - Ellipse", () => {
166 | const shape1 = Shapes.path("M50,20 C50,210 250,20 250,210");
167 | const shape2 = Shapes.ellipse(80, 80, 30, 40);
168 | const expected = [
169 | new Point2D(53.96110954819515, 60.1348464093258),
170 | new Point2D(99.14716919990332, 110.79353212407386)
171 | ];
172 |
173 | assertIntersections(shape1, shape2, expected);
174 | });
175 | it("Cubic Bezier - Line", () => {
176 | const shape1 = Shapes.path("M50,20 C50,210 250,20 250,210");
177 | const shape2 = Shapes.line(10, 20, 300, 210);
178 | const expected = [
179 | new Point2D(51.64783510324115, 47.286512653847666),
180 | new Point2D(155.00539377733003, 115.0035338541128),
181 | new Point2D(247.19292496558265, 175.4022611843473)
182 | ];
183 |
184 | assertIntersections(shape1, shape2, expected);
185 | });
186 | it("Cubic Bezier - Polygon", () => {
187 | const shape1 = Shapes.path("M50,20 C50,210 250,20 250,210");
188 | const shape2 = Shapes.polygon([58, 30, 110, 27, 135, 108, 74, 145, 28, 108, 26, 53]);
189 | const expected = [
190 | new Point2D(50.48246685800199, 35.403226945811014),
191 | new Point2D(124.27379778337522, 114.50605708221505)
192 | ];
193 |
194 | assertIntersections(shape1, shape2, expected);
195 | });
196 | it("Cubic Bezier - Rectangle", () => {
197 | const shape1 = Shapes.path("M50,20 C50,210 250,20 250,210");
198 | const shape2 = Shapes.rectangle(35, 25, 80, 100);
199 | const expected = [
200 | new Point2D(50.04756726751318, 24.99999999999999),
201 | new Point2D(115, 113.72223572223957)
202 | ];
203 |
204 | assertIntersections(shape1, shape2, expected);
205 | });
206 | it("Circle - Circle", () => {
207 | const shape1 = Shapes.circle(70, 80, 50);
208 | const shape2 = Shapes.circle(140, 135, 80);
209 | const expected = [
210 | new Point2D(60.22271286332645, 129.03472908303905),
211 | new Point2D(115.32933760985969, 58.899024860178585)
212 | ];
213 |
214 | assertIntersections(shape1, shape2, expected);
215 | });
216 | it("Circle - Ellipse", () => {
217 | const shape1 = Shapes.circle(100, 100, 50);
218 | const shape2 = Shapes.ellipse(150, 120, 40, 50);
219 | const expected = [
220 | new Point2D(116.445893489817, 147.21792654619213),
221 | new Point2D(140.94197183977488, 71.29886863081731)
222 | ];
223 |
224 | assertIntersections(shape1, shape2, expected);
225 | });
226 | it("Circle - Line", () => {
227 | const shape1 = Shapes.circle(105, 115, 80);
228 | const shape2 = Shapes.line(16, 89, 200, 15);
229 | const expected = [
230 | new Point2D(31.84191758151497, 82.62879401612986),
231 | new Point2D(135.3745982325804, 40.990650710810044)
232 | ];
233 |
234 | assertIntersections(shape1, shape2, expected);
235 | });
236 | it("Circle - Polygon", () => {
237 | const shape1 = Shapes.circle(120, 75, 50);
238 | const shape2 = Shapes.polygon([48, 20, 100, 17, 125, 98, 64, 135, 18, 98, 16, 43]);
239 | const expected = [
240 | new Point2D(93.26257976100801, 117.25056637447055),
241 | new Point2D(103.34990500791412, 27.853692225641723)
242 | ];
243 |
244 | assertIntersections(shape1, shape2, expected);
245 | });
246 | it("Circle - Rectangle", () => {
247 | const shape1 = Shapes.circle(90, 55, 40);
248 | const shape2 = Shapes.rectangle(30, 35, 75, 100);
249 | const expected = [
250 | new Point2D(55.35898384862246, 35),
251 | new Point2D(105, 92.08099243547831)
252 | ];
253 |
254 | assertIntersections(shape1, shape2, expected);
255 | });
256 | it("Ellipse - Ellipse", () => {
257 | const shape1 = Shapes.ellipse(100, 100, 50, 40);
258 | const shape2 = Shapes.ellipse(150, 120, 40, 50);
259 | const expected = [
260 | new Point2D(112.88633296444475, 138.6487147334415),
261 | new Point2D(136.70828576029288, 72.84118698863836)
262 | ];
263 |
264 | assertIntersections(shape1, shape2, expected);
265 | });
266 | it("Ellipse - Line", () => {
267 | const shape1 = Shapes.ellipse(100, 100, 50, 40);
268 | const shape2 = Shapes.line(50, 50, 150, 150);
269 | const expected = [
270 | new Point2D(68.76524762227879, 68.76524762227879),
271 | new Point2D(131.2347523777212, 131.2347523777212)
272 | ];
273 |
274 | assertIntersections(shape1, shape2, expected);
275 | });
276 | it("Ellipse - Polygon", () => {
277 | const shape1 = Shapes.ellipse(100, 100, 50, 40);
278 | const shape2 = Shapes.polygon([48, 20, 100, 17, 125, 98, 64, 135, 18, 98, 16, 43]);
279 | const expected = [
280 | new Point2D(69.47373132371456, 131.67986788561578),
281 | new Point2D(113.74741995085054, 61.54164064075573)
282 | ];
283 |
284 | assertIntersections(shape1, shape2, expected);
285 | });
286 | it("Ellipse - Rectangle", () => {
287 | const shape1 = Shapes.ellipse(100, 100, 50, 40);
288 | const shape2 = Shapes.rectangle(35, 25, 80, 100);
289 | const expected = [
290 | new Point2D(60.96876251001001, 125),
291 | new Point2D(115, 61.842431943322175)
292 | ];
293 |
294 | assertIntersections(shape1, shape2, expected);
295 | });
296 | it("Line - Line", () => {
297 | const shape1 = Shapes.line(48, 27, 180, 116);
298 | const shape2 = Shapes.line(180, 27, 48, 116);
299 |
300 | const expected = [new Point2D(114, 71.5)];
301 |
302 | assertIntersections(shape1, shape2, expected);
303 | });
304 | it("Line - Polygon", () => {
305 | const shape1 = Shapes.line(20, 20, 100, 150);
306 | const shape2 = Shapes.polygon([48, 20, 100, 17, 125, 98, 64, 135, 18, 98, 16, 43]);
307 |
308 | const expected = [
309 | new Point2D(28.586666666666666, 33.95333333333333),
310 | new Point2D(83.4931129476584, 123.1763085399449)
311 | ];
312 |
313 | assertIntersections(shape1, shape2, expected);
314 | });
315 | it("Line - Rectangle", () => {
316 | const shape1 = Shapes.line(16, 89, 135, 59);
317 | const shape2 = Shapes.rectangle(35, 25, 80, 100);
318 |
319 | const expected = [
320 | new Point2D(35, 84.21008403361344),
321 | new Point2D(115, 64.0420168067227)
322 | ];
323 |
324 | assertIntersections(shape1, shape2, expected);
325 | });
326 | it("Path - Circle", () => {
327 | const shape1 = Shapes.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
328 | const shape2 = Shapes.circle(80, 80, 40);
329 | const expected = [
330 | new Point2D(40.49378036925451, 73.734330802915),
331 | new Point2D(48.0811192546348, 104.10777990531592),
332 | new Point2D(61.93906996427922, 115.69037413988258),
333 | new Point2D(118.18197295998756, 68.07788018501833)
334 | ];
335 |
336 | assertIntersections(shape1, shape2, expected);
337 | });
338 | it("Path - Ellipse", () => {
339 | const shape1 = Shapes.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
340 | const shape2 = Shapes.ellipse(80, 80, 30, 40);
341 | const expected = [
342 | new Point2D(65.40567989067512, 114.94772783703729),
343 | new Point2D(108.91468665042117, 69.33825981200226)
344 | ];
345 |
346 | assertIntersections(shape1, shape2, expected);
347 | });
348 | it("Path - Line", () => {
349 | const shape1 = Shapes.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
350 | const shape2 = Shapes.line(15, 75, 355, 140);
351 | const expected = [
352 | new Point2D(41.469363095003175, 80.0603194152212),
353 | new Point2D(90.39284374732412, 89.41333777522375),
354 | new Point2D(131.75979837885328, 97.32172616066313),
355 | new Point2D(147.372304199773, 100.30646992054483),
356 | new Point2D(174.0025359682078, 105.3975436409809),
357 | new Point2D(224.63128285241436, 115.07656878060865),
358 | new Point2D(278.6635091553598, 125.40625910323052),
359 | new Point2D(335.91913398904643, 136.35218738025884)
360 | ];
361 |
362 | assertIntersections(shape1, shape2, expected);
363 | });
364 | // it("Path - Path", () => assert.fail());
365 | it("Path - Polygon", () => {
366 | const shape1 = Shapes.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
367 | const shape2 = Shapes.polygon([100, 50, 160, 40, 185, 120, 120, 165, 70, 120, 80, 70]);
368 | const expected = [
369 | new Point2D(71.72741550068481, 111.36292249657606),
370 | new Point2D(182.8756171982404, 113.20197503436931)
371 | ];
372 |
373 | assertIntersections(shape1, shape2, expected);
374 | });
375 | it("Path - Rectangle", () => {
376 | const shape1 = Shapes.path("M40,70 Q50,150 90,90 T135,130 L160,70 C180,180 280,55 280,140 S400,110 290,100");
377 | const shape2 = Shapes.rectangle(94, 45, 80, 100);
378 | const expected = [
379 | new Point2D(94, 84.28119297784802),
380 | new Point2D(174, 105.39445191110119)
381 | ];
382 |
383 | assertIntersections(shape1, shape2, expected);
384 | });
385 | it("Relative Path - Circle", () => {
386 | const shape1 = Shapes.path("M40,70 q10,80 50,20 t45,40 l25,-60 c20,110 120,-15 120,70 s120,-30 10,-40");
387 | const shape2 = Shapes.circle(80, 80, 40);
388 | const expected = [
389 | new Point2D(40.49378036925451, 73.734330802915),
390 | new Point2D(48.0811192546348, 104.10777990531592),
391 | new Point2D(61.93906996427922, 115.69037413988258),
392 | new Point2D(118.18197295998756, 68.07788018501833)
393 | ];
394 |
395 | assertIntersections(shape1, shape2, expected);
396 | });
397 | it("Relative Path - Ellipse", () => {
398 | const shape1 = Shapes.path("M40,70 q10,80 50,20 t45,40 l25,-60 c20,110 120,-15 120,70 s120,-30 10,-40");
399 | const shape2 = Shapes.ellipse(80, 80, 30, 40);
400 | const expected = [
401 | new Point2D(65.40567989067512, 114.94772783703729),
402 | new Point2D(108.91468665042117, 69.33825981200226)
403 | ];
404 |
405 | assertIntersections(shape1, shape2, expected);
406 | });
407 | it("Relative Path - Line", () => {
408 | const shape1 = Shapes.path("M40,70 q10,80 50,20 t45,40 l25,-60 c20,110 120,-15 120,70 s120,-30 10,-40");
409 | const shape2 = Shapes.line(15, 75, 355, 140);
410 | const expected = [
411 | new Point2D(41.469363095003175, 80.0603194152212),
412 | new Point2D(90.39284374732412, 89.41333777522375),
413 | new Point2D(131.75979837885328, 97.32172616066313),
414 | new Point2D(147.372304199773, 100.30646992054483),
415 | new Point2D(174.0025359682078, 105.3975436409809),
416 | new Point2D(224.63128285241436, 115.07656878060865),
417 | new Point2D(278.6635091553598, 125.40625910323052),
418 | new Point2D(335.91913398904643, 136.35218738025884)
419 | ];
420 |
421 | assertIntersections(shape1, shape2, expected);
422 | });
423 | // it("Relative Path - Relative Path", () => assert.fail());
424 | it("Relative Path - Polygon", () => {
425 | const shape1 = Shapes.path("M40,70 q10,80 50,20 t45,40 l25,-60 c20,110 120,-15 120,70 s120,-30 10,-40");
426 | const shape2 = Shapes.polygon([100, 50, 160, 40, 185, 120, 120, 165, 70, 120, 80, 70]);
427 | const expected = [
428 | new Point2D(71.72741550068481, 111.36292249657606),
429 | new Point2D(182.8756171982404, 113.20197503436931)
430 | ];
431 |
432 | assertIntersections(shape1, shape2, expected);
433 | });
434 | it("Relative Path - Rectangle", () => {
435 | const shape1 = Shapes.path("M40,70 q10,80 50,20 t45,40 l25,-60 c20,110 120,-15 120,70 s120,-30 10,-40");
436 | const shape2 = Shapes.rectangle(94, 45, 80, 100);
437 | const expected = [
438 | new Point2D(94, 84.28119297784802),
439 | new Point2D(174, 105.39445191110119)
440 | ];
441 |
442 | assertIntersections(shape1, shape2, expected);
443 | });
444 | it("Polygon - Polygon", () => {
445 | const shape1 = Shapes.polygon([48, 20, 100, 17, 125, 98, 64, 130, 18, 98]);
446 | const shape2 = Shapes.polygon([20, 48, 17, 100, 98, 125, 130, 64, 98, 18, 43, 16]);
447 | const expected = [
448 | new Point2D(23.965325936199722, 102.1497919556172),
449 | new Point2D(82.59485061938305, 120.24532426524168),
450 | new Point2D(88.59479553903346, 17.65799256505576),
451 | new Point2D(102.1497919556172, 23.965325936199726),
452 | new Point2D(107.29032258064517, 107.29032258064515),
453 | new Point2D(120.24532426524168, 82.59485061938304)
454 | ];
455 |
456 | assertIntersections(shape1, shape2, expected);
457 | });
458 | it("Polygon - Rectangle", () => {
459 | const shape1 = Shapes.polygon([48, 20, 100, 17, 125, 98, 64, 130, 18, 98]);
460 | const shape2 = Shapes.rectangle(60, 30, 100, 80);
461 | const expected = [
462 | new Point2D(102.125, 110),
463 | new Point2D(104.01234567901234, 30)
464 | ];
465 |
466 | assertIntersections(shape1, shape2, expected);
467 | });
468 | it("Rectangle - Rectangle", () => {
469 | const shape1 = Shapes.rectangle(35, 25, 80, 100);
470 | const shape2 = Shapes.rectangle(55, 45, 120, 110);
471 | const expected = [
472 | new Point2D(55, 125),
473 | new Point2D(115, 45)
474 | ];
475 |
476 | assertIntersections(shape1, shape2, expected);
477 | });
478 | });
479 |
--------------------------------------------------------------------------------
/test/shape-info-test.js:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Point2D} from "kld-affine";
3 | import ShapeInfo from "../lib/ShapeInfo.js";
4 | import itertools from "./itertools.js";
5 | import util from "util";
6 |
7 | function buildPoints(count, useProperties = true) {
8 | const result = [];
9 | let x = 10;
10 | let y = 20;
11 |
12 | for (let i = 1; i <= count; i++) {
13 | if (useProperties) {
14 | let name = `p${i}`;
15 | let nameX = `${name}x`;
16 | let nameY = `${name}y`;
17 |
18 | result.push([
19 | {[name]: new Point2D(x, y)},
20 | {[name]: {x, y}},
21 | {[name]: [x, y]},
22 | {[nameX]: x, [nameY]: y}
23 | ]);
24 | }
25 | else {
26 | result.push([
27 | [new Point2D(x, y)],
28 | [{x, y}],
29 | [[x, y]],
30 | [x, y]
31 | ]);
32 | }
33 |
34 | x += 20;
35 | y += 20;
36 | }
37 |
38 | return result;
39 | }
40 |
41 | function buildObject(...parts) {
42 | let argObject = {};
43 |
44 | for (let part of parts) {
45 | argObject = Object.assign(argObject, part)
46 | }
47 |
48 | return argObject;
49 | }
50 |
51 |
52 | describe("Shapes API", () => {
53 | describe("Arc", () => {
54 | describe("Arc - Objects", () => {
55 | const centers = [
56 | {center: new Point2D(10, 20)},
57 | {center: {x: 10, y: 20}},
58 | {center: [10, 20]},
59 | {centerX: 10, centerY: 20},
60 | {cx: 10, cy: 20}
61 | ];
62 | const radiusXs = [
63 | {radiusX: 30},
64 | {rx: 30}
65 | ];
66 | const radiusYs = [
67 | {radiusY: 40},
68 | {ry: 40}
69 | ];
70 | const startRadians = [{startRadians: 50}];
71 | const endRadians = [{endRadians: 60}];
72 | const argProduct = new itertools.Product(centers, radiusXs, radiusYs, startRadians, endRadians);
73 | const expected = new ShapeInfo("Arc", [new Point2D(10, 20), 30, 40, 50, 60]);
74 |
75 | for (let args of argProduct) {
76 | const argObject = buildObject(...args);
77 | it(util.inspect(argObject, {breakLength: Infinity}), () => {
78 | const result = new ShapeInfo.arc(argObject);
79 |
80 | assert.deepStrictEqual(result, expected);
81 | });
82 | }
83 | });
84 | describe("Arc - Arrays", () => {
85 | const centers = [
86 | [new Point2D(10, 20)],
87 | [{x: 10, y: 20}],
88 | [[10, 20]],
89 | [10, 20]
90 | ];
91 | const radiusXs = [30];
92 | const radiusYs = [40];
93 | const startRadians = [50];
94 | const endRadians = [60];
95 | const argProduct = new itertools.Product(centers, radiusXs, radiusYs, startRadians, endRadians);
96 | const expected = new ShapeInfo("Arc", [new Point2D(10, 20), 30, 40, 50, 60]);
97 |
98 | for (let args of argProduct) {
99 | const flattenedArgs = args.reduce((acc, val) => acc.concat(val), []);
100 | it(util.inspect(flattenedArgs, {breakLength: Infinity}), () => {
101 | const result = new ShapeInfo.arc(...flattenedArgs);
102 |
103 | assert.deepStrictEqual(result, expected);
104 | });
105 | }
106 | });
107 | });
108 | describe("Quadratic Bezier", () => {
109 | describe("Quadratic Bezier - Objects", () => {
110 | const points = buildPoints(3);
111 | const pointsProduct = new itertools.Product(...points);
112 | const expected = new ShapeInfo("Bezier2", [
113 | new Point2D(10, 20),
114 | new Point2D(30, 40),
115 | new Point2D(50, 60)
116 | ]);
117 |
118 | for (let points of pointsProduct) {
119 | const argObject = buildObject(...points);
120 | it(util.inspect(argObject, {breakLength: Infinity}), () => {
121 | const result = new ShapeInfo.quadraticBezier(argObject);
122 |
123 | assert.deepStrictEqual(result, expected);
124 | });
125 | }
126 | });
127 | describe("Quadratic Bezier - Arrays", () => {
128 | const points = buildPoints(3, false);
129 | const pointsProduct = new itertools.Product(...points);
130 | const expected = new ShapeInfo("Bezier2", [
131 | new Point2D(10, 20),
132 | new Point2D(30, 40),
133 | new Point2D(50, 60)
134 | ]);
135 |
136 | for (let points of pointsProduct) {
137 | const flattenedPoints = points.reduce((acc, val) => acc.concat(val), []);
138 | it(util.inspect(flattenedPoints, {breakLength: Infinity}), () => {
139 | const result = new ShapeInfo.quadraticBezier(...flattenedPoints);
140 |
141 | assert.deepStrictEqual(result, expected);
142 | });
143 | }
144 | });
145 | });
146 | describe("Cubic Bezier", () => {
147 | describe("Cubic Bezier - Objects", () => {
148 | const points = buildPoints(4);
149 | const pointsProduct = new itertools.Product(...points);
150 | const expected = new ShapeInfo("Bezier3", [
151 | new Point2D(10, 20),
152 | new Point2D(30, 40),
153 | new Point2D(50, 60),
154 | new Point2D(70, 80)
155 | ]);
156 |
157 | for (let points of pointsProduct) {
158 | const argObject = buildObject(...points);
159 | it(util.inspect(argObject, {breakLength: Infinity}), () => {
160 | const result = new ShapeInfo.cubicBezier(argObject);
161 |
162 | assert.deepStrictEqual(result, expected);
163 | });
164 | }
165 | });
166 | describe("Cubic Bezier - Arrays", () => {
167 | const points = buildPoints(4, false);
168 | const pointsProduct = new itertools.Product(...points);
169 | const expected = new ShapeInfo("Bezier3", [
170 | new Point2D(10, 20),
171 | new Point2D(30, 40),
172 | new Point2D(50, 60),
173 | new Point2D(70, 80)
174 | ]);
175 |
176 | for (let points of pointsProduct) {
177 | const flattenedPoints = points.reduce((acc, val) => acc.concat(val), []);
178 | it(util.inspect(flattenedPoints, {breakLength: Infinity}), () => {
179 | const result = new ShapeInfo.cubicBezier(...flattenedPoints);
180 |
181 | assert.deepStrictEqual(result, expected);
182 | });
183 | }
184 | });
185 | });
186 | describe("Rounded Rectangle", () => {
187 | it("Rounded Rectangle - Object 1", () => {
188 | let rr = {
189 | x: 10, y: 20,
190 | width: 30, height: 40,
191 | rx: 3, ry: 4
192 | };
193 | try {
194 | let shape = ShapeInfo.rectangle(rr);
195 |
196 | for (let arg of shape.args) {
197 | // TODO: check segments
198 | }
199 |
200 | }
201 | catch (e) {
202 | assert.fail("Unable to parse object: " + JSON.stringify(rr));
203 | }
204 | });
205 | it("Rounded Rectangle - Object 2", () => {
206 | let rr = {
207 | x: 10, y: 20,
208 | w: 30, h: 40,
209 | rx: 3, ry: 4
210 | };
211 | try {
212 | let shape = ShapeInfo.rectangle(rr);
213 |
214 | for (let arg of shape.args) {
215 | // TODO: check segments
216 | }
217 |
218 | }
219 | catch (e) {
220 | assert.fail("Unable to parse object: " + JSON.stringify(rr));
221 | }
222 |
223 | });
224 | });
225 | });
226 |
--------------------------------------------------------------------------------
/test/shapes-test.js:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Point2D} from "kld-affine";
3 | import Shapes from "../lib/Shapes.js";
4 | import ShapeInfo from "../lib/ShapeInfo.js";
5 |
6 |
7 | describe("Shapes API", () => {
8 | it("arc", () => {
9 | const result = Shapes.arc(10, 20, 30, 40, 0, 1.57);
10 | const expected = new ShapeInfo("Arc", [new Point2D(10, 20), 30, 40, 0, 1.57]);
11 |
12 | assert.deepStrictEqual(result, expected);
13 | });
14 | it("quadratic bezier", () => {
15 | const result = Shapes.quadraticBezier(10, 20, 30, 40, 50, 60);
16 | const expected = new ShapeInfo("Bezier2", [new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)]);
17 |
18 | assert.deepStrictEqual(result, expected);
19 | });
20 | it("cubic bezier", () => {
21 | const result = Shapes.cubicBezier(10, 20, 30, 40, 50, 60, 70, 80);
22 | const expected = new ShapeInfo("Bezier3", [new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60), new Point2D(70, 80)]);
23 |
24 | assert.deepStrictEqual(result, expected);
25 | });
26 | it("circle", () => {
27 | const result = Shapes.circle(10, 20, 30);
28 | const expected = new ShapeInfo("Circle", [new Point2D(10, 20), 30]);
29 |
30 | assert.deepStrictEqual(result, expected);
31 | });
32 | it("ellipse", () => {
33 | const result = Shapes.ellipse(10, 20, 30, 40);
34 | const expected = new ShapeInfo("Ellipse", [new Point2D(10, 20), 30, 40]);
35 |
36 | assert.deepStrictEqual(result, expected);
37 | });
38 | it("line", () => {
39 | const result = Shapes.line(10, 20, 30, 40);
40 | const expected = new ShapeInfo("Line", [new Point2D(10, 20), new Point2D(30, 40)]);
41 |
42 | assert.deepStrictEqual(result, expected);
43 | });
44 | it("path", () => {
45 | const result = Shapes.path("M0, 10 L100, 110");
46 | const expected = new ShapeInfo("Path", [
47 | Shapes.line(0, 10, 100, 110)
48 | ]);
49 |
50 | assert.deepStrictEqual(result, expected);
51 | });
52 | it("polygon", () => {
53 | const result = Shapes.polygon(10, 20, 30, 40, 50, 60);
54 | const expected = new ShapeInfo("Polygon", [
55 | [
56 | new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)
57 | ]
58 | ]);
59 |
60 | assert.deepStrictEqual(result, expected);
61 | });
62 | it("polyline", () => {
63 | const result = Shapes.polyline(10, 20, 30, 40, 50, 60);
64 | const expected = new ShapeInfo("Polyline", [
65 | [
66 | new Point2D(10, 20), new Point2D(30, 40), new Point2D(50, 60)
67 | ]
68 | ]);
69 |
70 | assert.deepStrictEqual(result, expected);
71 | });
72 | it("rectangle", () => {
73 | const result = Shapes.rectangle(10, 20, 30, 40);
74 | const expected = new ShapeInfo("Rectangle", [new Point2D(10, 20), new Point2D(40, 60)]);
75 |
76 | assert.deepStrictEqual(result, expected);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/visual-test/arc-fail.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/visual-test/bezier2_bezier2.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_bezier3.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_circle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier2_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_bezier3.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_circle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/bezier3_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/circle_circle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/circle_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/circle_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/circle_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/circle_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/ellipse_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/ellipse_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/ellipse_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/ellipse_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/index.html:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/visual-test/line_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/line_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/line_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/line_rounded_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/line_rounded_rect_2.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_circle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_path.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/path_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/polygon_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/polygon_rectangle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rect_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_circle.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_ellipse.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_line.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_path.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_polygon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/rel_path_rect.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/visual-test/show_intersections.js:
--------------------------------------------------------------------------------
1 | const {SvgShapes, Intersection} = KldIntersections;
2 |
3 | /**
4 | * Intersect some shapes
5 | */
6 | function go() {
7 | const shapes = getShapes();
8 |
9 | if (shapes !== null) {
10 | const [shape1, shape2] = shapes;
11 | const result = Intersection.intersect(shape1, shape2);
12 |
13 | const xml = result.points.map(point => {
14 | return ``;
15 | }).join("\n");
16 |
17 | document.getElementById("result").innerHTML = xml;
18 | }
19 | }
20 |
21 | /**
22 | * Get the shapes to intersect
23 | *
24 | * @returns {null|module:kld-intersections.ShapeInfo[]}
25 | */
26 | function getShapes() {
27 | const container = document.getElementById("shapes");
28 | const shapes = container.querySelectorAll("circle,ellipse,line,path,polygon,polyline,rect");
29 |
30 | if (shapes.length >= 2) {
31 | return [
32 | SvgShapes.element(shapes[0]),
33 | SvgShapes.element(shapes[1])
34 | ];
35 | }
36 |
37 | return null;
38 | }
39 |
--------------------------------------------------------------------------------