├── .gitignore
├── .travis.yml
├── Readme.md
├── dist
└── bezier-intersect.js
├── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── cubic.js
├── polynomial.js
└── quadratic.js
└── test
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 |
5 | env:
6 | global:
7 | - BUILD_TIMEOUT=10000
8 |
9 | install:
10 | - npm install
11 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # bezier-intersect [](https://badge.fury.io/js/bezier-intersect) [](https://travis-ci.org/w8r/bezier-intersect)
2 |
3 | Set of functions to find intersections between lines and rectangles and Bezier curves of order 2 and 3. Based on [thelonious/js-intersections](https://github.com/thelonious/js-intersections/), but with the abstractions removed and some performance tweaking.
4 |
5 | ## Install
6 |
7 | ```
8 | npm i -S bezier-intersect
9 | ```
10 |
11 | ```js
12 | import {
13 | quadBezierLine,
14 | cubicBezierLine,
15 | quadBezierAABB,
16 | cubicBezierAABB
17 | } from 'bezier-intersect';
18 | ```
19 |
20 | ```html
21 |
22 |
27 | ```
28 |
29 | ## API
30 |
31 | ### `quadBezierLine(ax, ay, cx, cy, bx, by, l1x, l1y, l2x, l2y, [result:Array]):number`
32 |
33 | Calculates the intersection points between the quadratic Bezier curve and line segment. If `result` is passed, returns the exact number of intersections, and stores them in `result` as `[x, y, x, y]`. If not - stops at the first intersection and returns `1` or `0` if there are no intersections.
34 |
35 | ### `quadBezierAABB(ax, ay, cx, cy, bx, by, minx, miny, maxx, maxy, [result:Array]):number`
36 |
37 | Calculates the intersection points between the quadratic Bezier curve and axis-aligned box. If `result` is passed, returns the exact number of intersections, and stores them in `result` as `[x, y, x, y]`. If not - stops at the first intersection and returns `1` or `0` if there are no intersections.
38 |
39 | ### `cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, l1x, l1y, l2x, l2y, [result:Array]):number`
40 |
41 | Calculates the intersection points between the cubic Bezier curve and line segment. If `result` is passed, returns the exact number of intersections, and stores them in `result` as `[x, y, x, y]`. If not - stops at the first intersection and returns `1` or `0` if there are no intersections.
42 |
43 | ### `cubicBezierAABB(ax, ay, c1x, c1y, c2x, c2y, bx, by, minx, miny, maxx, maxy, [result:Array]):number`
44 |
45 | Calculates the intersection points between the cubic Bezier curve and axis-aligned box. If `result` is passed, returns the exact number of intersections, and stores them in `result` as `[x, y, x, y]`. If not - stops at the first intersection and returns `1` or `0` if there are no intersections.
46 |
47 | ## TODO
48 |
49 | - [ ] More tests
50 | - [ ] Bezier/Polygon
51 | - [ ] Bezier/Ellipse/Circle
52 |
53 | ## License
54 |
55 | MIT
56 |
--------------------------------------------------------------------------------
/dist/bezier-intersect.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 | (factory((global.bezierIntersect = {})));
5 | }(this, (function (exports) { 'use strict';
6 |
7 | var POLYNOMIAL_TOLERANCE = 1e-6;
8 | var TOLERANCE = 1e-12;
9 |
10 | function getPolynomialRoots() {
11 | var C = arguments;
12 | var degree = C.length - 1;
13 | var n = degree;
14 | var results = [];
15 | for (var i = 0; i <= degree; i++) {
16 | if (Math.abs(C[i]) <= TOLERANCE) { degree--; } else { break; }
17 | }
18 |
19 | switch (degree) {
20 | case 1: getLinearRoots(C[n], C[n - 1], results); break;
21 | case 2: getQuadraticRoots(C[n], C[n - 1], C[n - 2], results); break;
22 | case 3: getCubicRoots(C[n], C[n - 1], C[n - 2], C[n - 3], results); break;
23 | default: break;
24 | }
25 |
26 | return results;
27 | }
28 |
29 | function getLinearRoots(C0, C1, results) {
30 | if ( results === void 0 ) results = [];
31 |
32 | if (C1 !== 0) { results.push(-C0 / C1); }
33 | return results;
34 | }
35 |
36 | function getQuadraticRoots(C0, C1, C2, results) {
37 | if ( results === void 0 ) results = [];
38 |
39 | var a = C2;
40 | var b = C1 / a;
41 | var c = C0 / a;
42 | var d = b * b - 4 * c;
43 |
44 | if (d > 0) {
45 | var e = Math.sqrt(d);
46 |
47 | results.push(0.5 * (-b + e));
48 | results.push(0.5 * (-b - e));
49 | } else if (d === 0) {
50 | results.push( 0.5 * -b);
51 | }
52 |
53 | return results;
54 | }
55 |
56 |
57 | function getCubicRoots(C0, C1, C2, C3, results) {
58 | if ( results === void 0 ) results = [];
59 |
60 | var c3 = C3;
61 | var c2 = C2 / c3;
62 | var c1 = C1 / c3;
63 | var c0 = C0 / c3;
64 |
65 | var a = (3 * c1 - c2 * c2) / 3;
66 | var b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27;
67 | var offset = c2 / 3;
68 | var discrim = b * b / 4 + a * a * a / 27;
69 | var halfB = b / 2;
70 | var tmp, root;
71 |
72 | if (Math.abs(discrim) <= POLYNOMIAL_TOLERANCE) { discrim = 0; }
73 |
74 | if (discrim > 0) {
75 | var e = Math.sqrt(discrim);
76 |
77 | tmp = -halfB + e;
78 | if ( tmp >= 0 ) { root = Math.pow( tmp, 1/3); }
79 | else { root = -Math.pow(-tmp, 1/3); }
80 |
81 | tmp = -halfB - e;
82 | if ( tmp >= 0 ) { root += Math.pow( tmp, 1/3); }
83 | else { root -= Math.pow(-tmp, 1/3); }
84 |
85 | results.push(root - offset);
86 | } else if (discrim < 0) {
87 | var distance = Math.sqrt(-a/3);
88 | var angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3;
89 | var cos = Math.cos(angle);
90 | var sin = Math.sin(angle);
91 | var sqrt3 = Math.sqrt(3);
92 |
93 | results.push( 2 * distance * cos - offset);
94 | results.push(-distance * (cos + sqrt3 * sin) - offset);
95 | results.push(-distance * (cos - sqrt3 * sin) - offset);
96 | } else {
97 | if (halfB >= 0) { tmp = -Math.pow(halfB, 1/3); }
98 | else { tmp = Math.pow(-halfB, 1/3); }
99 |
100 | results.push(2 * tmp - offset);
101 | // really should return next root twice, but we return only one
102 | results.push(-tmp - offset);
103 | }
104 |
105 | return results;
106 | }
107 |
108 | function quadBezierLine(
109 | p1x, p1y, p2x, p2y, p3x, p3y,
110 | a1x, a1y, a2x, a2y, result) {
111 | var ax, ay, bx, by; // temporary variables
112 | var c2x, c2y, c1x, c1y, c0x, c0y; // coefficients of quadratic
113 | var cl; // c coefficient for normal form of line
114 | var nx, ny; // normal for normal form of line
115 | // used to determine if point is on line segment
116 | var minx = Math.min(a1x, a2x),
117 | miny = Math.min(a1y, a2y),
118 | maxx = Math.max(a1x, a2x),
119 | maxy = Math.max(a1y, a2y);
120 |
121 | ax = p2x * -2; ay = p2y * -2;
122 | c2x = p1x + ax + p3x;
123 | c2y = p1y + ay + p3y;
124 |
125 | ax = p1x * -2; ay = p1y * -2;
126 | bx = p2x * 2; by = p2y * 2;
127 | c1x = ax + bx;
128 | c1y = ay + by;
129 |
130 | c0x = p1x; c0y = p1y; // vec
131 |
132 | // Convert line to normal form: ax + by + c = 0
133 | // Find normal to line: negative inverse of original line's slope
134 | nx = a1y - a2y; ny = a2x - a1x;
135 |
136 | // Determine new c coefficient
137 | cl = a1x * a2y - a2x * a1y;
138 |
139 | // Transform cubic coefficients to line's coordinate system
140 | // and find roots of cubic
141 | var roots = getPolynomialRoots(
142 | // dot products => x * x + y * y
143 | nx * c2x + ny * c2y,
144 | nx * c1x + ny * c1y,
145 | nx * c0x + ny * c0y + cl
146 | );
147 |
148 | // Any roots in closed interval [0,1] are intersections on Bezier, but
149 | // might not be on the line segment.
150 | // Find intersections and calculate point coordinates
151 | for (var i = 0; i < roots.length; i++) {
152 | var t = roots[i];
153 | if ( 0 <= t && t <= 1 ) { // We're within the Bezier curve
154 | // Find point on Bezier
155 | // lerp: x1 + (x2 - x1) * t
156 | var p4x = p1x + (p2x - p1x) * t;
157 | var p4y = p1y + (p2y - p1y) * t;
158 |
159 | var p5x = p2x + (p3x - p2x) * t;
160 | var p5y = p2y + (p3y - p2y) * t;
161 |
162 | // candidate
163 | var p6x = p4x + (p5x - p4x) * t;
164 | var p6y = p4y + (p5y - p4y) * t;
165 |
166 | // See if point is on line segment
167 | // Had to make special cases for vertical and horizontal lines due
168 | // to slight errors in calculation of p6
169 | if (a1x === a2x) {
170 | if (miny <= p6y && p6y <= maxy) {
171 | if (result) { result.push(p6x, p6y); }
172 | else { return 1; }
173 | }
174 | } else if (a1y === a2y) {
175 | if (minx <= p6x && p6x <= maxx) {
176 | if (result) { result.push(p6x, p6y); }
177 | else { return 1; }
178 | }
179 |
180 | // gte: (x1 >= x2 && y1 >= y2)
181 | // lte: (x1 <= x2 && y1 <= y2)
182 | } else if (p6x >= minx && p6y >= miny && p6x <= maxx && p6y <= maxy) {
183 | if (result) { result.push(p6x, p6y); }
184 | else { return 1; }
185 | }
186 | }
187 | }
188 | return result ? result.length / 2 : 0;
189 | }
190 |
191 |
192 | function quadBezierAABB(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymax, result) {
193 | if (result) { // all intersections
194 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymin, result);
195 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmax, ymin, xmax, ymax, result);
196 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymax, xmax, ymax, result);
197 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmin, ymax, result);
198 | return result.length / 2;
199 | } else { // any intersections
200 | // trivial cases
201 | if (xmin <= ax && xmax >= ax && ymin <= ay && ymax >= ay) { return 1; }
202 | if (xmin <= bx && xmax >= bx && ymin <= by && ymax >= by) { return 1; }
203 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymin)) { return 1; }
204 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmax, ymin, xmax, ymax)) { return 1; }
205 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymax, xmax, ymax)) { return 1; }
206 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmin, ymax)) { return 1; }
207 | return 0;
208 | }
209 | }
210 |
211 | function cubicBezierLine(
212 | p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y,
213 | a1x, a1y, a2x, a2y, result) {
214 | var ax, ay, bx, by, cx, cy, dx, dy; // temporary variables
215 | var c3x, c3y, c2x, c2y, c1x, c1y, c0x, c0y; // coefficients of cubic
216 | var cl; // c coefficient for normal form of line
217 | var nx, ny; // normal for normal form of line
218 |
219 | // used to determine if point is on line segment
220 | var minx = Math.min(a1x, a2x),
221 | miny = Math.min(a1y, a2y),
222 | maxx = Math.max(a1x, a2x),
223 | maxy = Math.max(a1y, a2y);
224 |
225 | // Start with Bezier using Bernstein polynomials for weighting functions:
226 | // (1-t^3)P1 + 3t(1-t)^2P2 + 3t^2(1-t)P3 + t^3P4
227 | //
228 | // Expand and collect terms to form linear combinations of original Bezier
229 | // controls. This ends up with a vector cubic in t:
230 | // (-P1+3P2-3P3+P4)t^3 + (3P1-6P2+3P3)t^2 + (-3P1+3P2)t + P1
231 | // /\ /\ /\ /\
232 | // || || || ||
233 | // c3 c2 c1 c0
234 |
235 | // Calculate the coefficients
236 | ax = p1x * -1; ay = p1y * -1;
237 | bx = p2x * 3; by = p2y * 3;
238 | cx = p3x * -3; cy = p3y * -3;
239 | dx = ax + bx + cx + p4x;
240 | dy = ay + by + cy + p4y;
241 | c3x = dx; c3y = dy; // vec
242 |
243 | ax = p1x * 3; ay = p1y * 3;
244 | bx = p2x * -6; by = p2y * -6;
245 | cx = p3x * 3; cy = p3y * 3;
246 | dx = ax + bx + cx;
247 | dy = ay + by + cy;
248 | c2x = dx; c2y = dy; // vec
249 |
250 | ax = p1x * -3; ay = p1y * -3;
251 | bx = p2x * 3; by = p2y * 3;
252 | cx = ax + bx;
253 | cy = ay + by;
254 | c1x = cx;
255 | c1y = cy; // vec
256 |
257 | c0x = p1x;
258 | c0y = p1y;
259 |
260 | // Convert line to normal form: ax + by + c = 0
261 | // Find normal to line: negative inverse of original line's slope
262 | nx = a1y - a2y;
263 | ny = a2x - a1x;
264 |
265 | // Determine new c coefficient
266 | cl = a1x * a2y - a2x * a1y;
267 |
268 | // ?Rotate each cubic coefficient using line for new coordinate system?
269 | // Find roots of rotated cubic
270 | var roots = getPolynomialRoots(
271 | // dot products => x * x + y * y
272 | nx * c3x + ny * c3y,
273 | nx * c2x + ny * c2y,
274 | nx * c1x + ny * c1y,
275 | nx * c0x + ny * c0y + cl
276 | );
277 |
278 | // Any roots in closed interval [0,1] are intersections on Bezier, but
279 | // might not be on the line segment.
280 | // Find intersections and calculate point coordinates
281 | for (var i = 0; i < roots.length; i++) {
282 | var t = roots[i];
283 |
284 | if (0 <= t && t <= 1) { // We're within the Bezier curve
285 | // Find point on Bezier
286 | // lerp: x1 + (x2 - x1) * t
287 | var p5x = p1x + (p2x - p1x) * t;
288 | var p5y = p1y + (p2y - p1y) * t; // lerp(p1, p2, t);
289 |
290 | var p6x = p2x + (p3x - p2x) * t;
291 | var p6y = p2y + (p3y - p2y) * t;
292 |
293 | var p7x = p3x + (p4x - p3x) * t;
294 | var p7y = p3y + (p4y - p3y) * t;
295 |
296 | var p8x = p5x + (p6x - p5x) * t;
297 | var p8y = p5y + (p6y - p5y) * t;
298 |
299 | var p9x = p6x + (p7x - p6x) * t;
300 | var p9y = p6y + (p7y - p6y) * t;
301 |
302 | // candidate
303 | var p10x = p8x + (p9x - p8x) * t;
304 | var p10y = p8y + (p9y - p8y) * t;
305 |
306 | // See if point is on line segment
307 | if (a1x === a2x) { // vertical
308 | if (miny <= p10y && p10y <= maxy) {
309 | if (result) { result.push(p10x, p10y); }
310 | else { return 1; }
311 | }
312 | } else if (a1y === a2y) { // horizontal
313 | if (minx <= p10x && p10x <= maxx) {
314 | if (result) { result.push(p10x, p10y); }
315 | else { return 1; }
316 | }
317 | } else if (p10x >= minx && p10y >= miny && p10x <= maxx && p10y <= maxy) {
318 | if (result) { result.push(p10x, p10y); }
319 | else { return 1; }
320 | }
321 | }
322 | }
323 | return result ? result.length / 2 : 0;
324 | }
325 |
326 |
327 | function cubicBezierAABB(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymax, result) {
328 | if (result) { // all intersections
329 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymin, result);
330 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmax, ymin, xmax, ymax, result);
331 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymax, xmax, ymax, result);
332 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmin, ymax, result);
333 | return result.length / 2;
334 | } else { // any intersections
335 | // trivial cases
336 | if (xmin <= ax && xmax >= ax && ymin <= ay && ymax >= ay) { return 1; }
337 | if (xmin <= bx && xmax >= bx && ymin <= by && ymax >= by) { return 1; }
338 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymin)) { return 1; }
339 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmax, ymin, xmax, ymax)) { return 1; }
340 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymax, xmax, ymax)) { return 1; }
341 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmin, ymax)) { return 1; }
342 | return 0;
343 | }
344 | }
345 |
346 | exports.quadBezierLine = quadBezierLine;
347 | exports.quadBezierAABB = quadBezierAABB;
348 | exports.cubicBezierLine = cubicBezierLine;
349 | exports.cubicBezierAABB = cubicBezierAABB;
350 |
351 | Object.defineProperty(exports, '__esModule', { value: true });
352 |
353 | })));
354 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export * from './src/quadratic';
2 | export * from './src/cubic';
3 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bezier-intersect",
3 | "version": "0.0.3",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "acorn": {
8 | "version": "3.3.0",
9 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
10 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
11 | "dev": true
12 | },
13 | "acorn-jsx": {
14 | "version": "3.0.1",
15 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
16 | "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
17 | "dev": true,
18 | "requires": {
19 | "acorn": "3.3.0"
20 | }
21 | },
22 | "acorn-object-spread": {
23 | "version": "1.0.0",
24 | "resolved": "https://registry.npmjs.org/acorn-object-spread/-/acorn-object-spread-1.0.0.tgz",
25 | "integrity": "sha1-SOrQ9KjrFplaF6Dbn/xqyq2kumg=",
26 | "dev": true,
27 | "requires": {
28 | "acorn": "3.3.0"
29 | }
30 | },
31 | "ansi-regex": {
32 | "version": "2.1.1",
33 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
34 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
35 | "dev": true
36 | },
37 | "ansi-styles": {
38 | "version": "2.2.1",
39 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
40 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
41 | "dev": true
42 | },
43 | "arr-diff": {
44 | "version": "2.0.0",
45 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
46 | "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
47 | "dev": true,
48 | "requires": {
49 | "arr-flatten": "1.1.0"
50 | }
51 | },
52 | "arr-flatten": {
53 | "version": "1.1.0",
54 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
55 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
56 | "dev": true
57 | },
58 | "array-unique": {
59 | "version": "0.2.1",
60 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
61 | "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
62 | "dev": true
63 | },
64 | "assertion-error": {
65 | "version": "1.1.0",
66 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
67 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
68 | "dev": true
69 | },
70 | "balanced-match": {
71 | "version": "1.0.0",
72 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
73 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
74 | "dev": true
75 | },
76 | "brace-expansion": {
77 | "version": "1.1.8",
78 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
79 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
80 | "dev": true,
81 | "requires": {
82 | "balanced-match": "1.0.0",
83 | "concat-map": "0.0.1"
84 | }
85 | },
86 | "braces": {
87 | "version": "1.8.5",
88 | "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
89 | "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
90 | "dev": true,
91 | "requires": {
92 | "expand-range": "1.8.2",
93 | "preserve": "0.2.0",
94 | "repeat-element": "1.1.2"
95 | }
96 | },
97 | "browser-stdout": {
98 | "version": "1.3.0",
99 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
100 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
101 | "dev": true
102 | },
103 | "buble": {
104 | "version": "0.15.2",
105 | "resolved": "https://registry.npmjs.org/buble/-/buble-0.15.2.tgz",
106 | "integrity": "sha1-VH/EdIP45egXbYKqXrzLGDsC1hM=",
107 | "dev": true,
108 | "requires": {
109 | "acorn": "3.3.0",
110 | "acorn-jsx": "3.0.1",
111 | "acorn-object-spread": "1.0.0",
112 | "chalk": "1.1.3",
113 | "magic-string": "0.14.0",
114 | "minimist": "1.2.0",
115 | "os-homedir": "1.0.2"
116 | }
117 | },
118 | "builtin-modules": {
119 | "version": "1.1.1",
120 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
121 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
122 | "dev": true
123 | },
124 | "chai": {
125 | "version": "4.1.2",
126 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
127 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
128 | "dev": true,
129 | "requires": {
130 | "assertion-error": "1.1.0",
131 | "check-error": "1.0.2",
132 | "deep-eql": "3.0.1",
133 | "get-func-name": "2.0.0",
134 | "pathval": "1.1.0",
135 | "type-detect": "4.0.6"
136 | }
137 | },
138 | "chalk": {
139 | "version": "1.1.3",
140 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
141 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
142 | "dev": true,
143 | "requires": {
144 | "ansi-styles": "2.2.1",
145 | "escape-string-regexp": "1.0.5",
146 | "has-ansi": "2.0.0",
147 | "strip-ansi": "3.0.1",
148 | "supports-color": "2.0.0"
149 | }
150 | },
151 | "check-error": {
152 | "version": "1.0.2",
153 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
154 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
155 | "dev": true
156 | },
157 | "commander": {
158 | "version": "2.11.0",
159 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
160 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
161 | "dev": true
162 | },
163 | "concat-map": {
164 | "version": "0.0.1",
165 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
166 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
167 | "dev": true
168 | },
169 | "debug": {
170 | "version": "3.1.0",
171 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
172 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
173 | "dev": true,
174 | "requires": {
175 | "ms": "2.0.0"
176 | }
177 | },
178 | "deep-eql": {
179 | "version": "3.0.1",
180 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
181 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
182 | "dev": true,
183 | "requires": {
184 | "type-detect": "4.0.6"
185 | }
186 | },
187 | "diff": {
188 | "version": "3.3.1",
189 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz",
190 | "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==",
191 | "dev": true
192 | },
193 | "escape-string-regexp": {
194 | "version": "1.0.5",
195 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
196 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
197 | "dev": true
198 | },
199 | "estree-walker": {
200 | "version": "0.2.1",
201 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz",
202 | "integrity": "sha1-va/oCVOD2EFNXcLs9MkXO225QS4=",
203 | "dev": true
204 | },
205 | "expand-brackets": {
206 | "version": "0.1.5",
207 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
208 | "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
209 | "dev": true,
210 | "requires": {
211 | "is-posix-bracket": "0.1.1"
212 | }
213 | },
214 | "expand-range": {
215 | "version": "1.8.2",
216 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
217 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
218 | "dev": true,
219 | "requires": {
220 | "fill-range": "2.2.3"
221 | }
222 | },
223 | "extglob": {
224 | "version": "0.3.2",
225 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
226 | "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
227 | "dev": true,
228 | "requires": {
229 | "is-extglob": "1.0.0"
230 | }
231 | },
232 | "filename-regex": {
233 | "version": "2.0.1",
234 | "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
235 | "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
236 | "dev": true
237 | },
238 | "fill-range": {
239 | "version": "2.2.3",
240 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
241 | "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
242 | "dev": true,
243 | "requires": {
244 | "is-number": "2.1.0",
245 | "isobject": "2.1.0",
246 | "randomatic": "1.1.7",
247 | "repeat-element": "1.1.2",
248 | "repeat-string": "1.6.1"
249 | }
250 | },
251 | "for-in": {
252 | "version": "1.0.2",
253 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
254 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
255 | "dev": true
256 | },
257 | "for-own": {
258 | "version": "0.1.5",
259 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
260 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
261 | "dev": true,
262 | "requires": {
263 | "for-in": "1.0.2"
264 | }
265 | },
266 | "fs.realpath": {
267 | "version": "1.0.0",
268 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
269 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
270 | "dev": true
271 | },
272 | "get-func-name": {
273 | "version": "2.0.0",
274 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
275 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
276 | "dev": true
277 | },
278 | "glob": {
279 | "version": "7.1.2",
280 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
281 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
282 | "dev": true,
283 | "requires": {
284 | "fs.realpath": "1.0.0",
285 | "inflight": "1.0.6",
286 | "inherits": "2.0.3",
287 | "minimatch": "3.0.4",
288 | "once": "1.4.0",
289 | "path-is-absolute": "1.0.1"
290 | }
291 | },
292 | "glob-base": {
293 | "version": "0.3.0",
294 | "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
295 | "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
296 | "dev": true,
297 | "requires": {
298 | "glob-parent": "2.0.0",
299 | "is-glob": "2.0.1"
300 | }
301 | },
302 | "glob-parent": {
303 | "version": "2.0.0",
304 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
305 | "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
306 | "dev": true,
307 | "requires": {
308 | "is-glob": "2.0.1"
309 | }
310 | },
311 | "growl": {
312 | "version": "1.10.3",
313 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
314 | "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
315 | "dev": true
316 | },
317 | "has-ansi": {
318 | "version": "2.0.0",
319 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
320 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
321 | "dev": true,
322 | "requires": {
323 | "ansi-regex": "2.1.1"
324 | }
325 | },
326 | "has-flag": {
327 | "version": "2.0.0",
328 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
329 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
330 | "dev": true
331 | },
332 | "he": {
333 | "version": "1.1.1",
334 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
335 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
336 | "dev": true
337 | },
338 | "inflight": {
339 | "version": "1.0.6",
340 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
341 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
342 | "dev": true,
343 | "requires": {
344 | "once": "1.4.0",
345 | "wrappy": "1.0.2"
346 | }
347 | },
348 | "inherits": {
349 | "version": "2.0.3",
350 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
351 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
352 | "dev": true
353 | },
354 | "is-buffer": {
355 | "version": "1.1.6",
356 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
357 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
358 | "dev": true
359 | },
360 | "is-dotfile": {
361 | "version": "1.0.3",
362 | "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
363 | "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
364 | "dev": true
365 | },
366 | "is-equal-shallow": {
367 | "version": "0.1.3",
368 | "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
369 | "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
370 | "dev": true,
371 | "requires": {
372 | "is-primitive": "2.0.0"
373 | }
374 | },
375 | "is-extendable": {
376 | "version": "0.1.1",
377 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
378 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
379 | "dev": true
380 | },
381 | "is-extglob": {
382 | "version": "1.0.0",
383 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
384 | "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
385 | "dev": true
386 | },
387 | "is-glob": {
388 | "version": "2.0.1",
389 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
390 | "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
391 | "dev": true,
392 | "requires": {
393 | "is-extglob": "1.0.0"
394 | }
395 | },
396 | "is-module": {
397 | "version": "1.0.0",
398 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
399 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
400 | "dev": true
401 | },
402 | "is-number": {
403 | "version": "2.1.0",
404 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
405 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
406 | "dev": true,
407 | "requires": {
408 | "kind-of": "3.2.2"
409 | }
410 | },
411 | "is-posix-bracket": {
412 | "version": "0.1.1",
413 | "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
414 | "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
415 | "dev": true
416 | },
417 | "is-primitive": {
418 | "version": "2.0.0",
419 | "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
420 | "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
421 | "dev": true
422 | },
423 | "isarray": {
424 | "version": "1.0.0",
425 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
426 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
427 | "dev": true
428 | },
429 | "isobject": {
430 | "version": "2.1.0",
431 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
432 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
433 | "dev": true,
434 | "requires": {
435 | "isarray": "1.0.0"
436 | }
437 | },
438 | "kind-of": {
439 | "version": "3.2.2",
440 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
441 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
442 | "dev": true,
443 | "requires": {
444 | "is-buffer": "1.1.6"
445 | }
446 | },
447 | "magic-string": {
448 | "version": "0.14.0",
449 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.14.0.tgz",
450 | "integrity": "sha1-VyJK7xcByu7Sc7F6OalW5ysXJGI=",
451 | "dev": true,
452 | "requires": {
453 | "vlq": "0.2.3"
454 | }
455 | },
456 | "micromatch": {
457 | "version": "2.3.11",
458 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
459 | "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
460 | "dev": true,
461 | "requires": {
462 | "arr-diff": "2.0.0",
463 | "array-unique": "0.2.1",
464 | "braces": "1.8.5",
465 | "expand-brackets": "0.1.5",
466 | "extglob": "0.3.2",
467 | "filename-regex": "2.0.1",
468 | "is-extglob": "1.0.0",
469 | "is-glob": "2.0.1",
470 | "kind-of": "3.2.2",
471 | "normalize-path": "2.1.1",
472 | "object.omit": "2.0.1",
473 | "parse-glob": "3.0.4",
474 | "regex-cache": "0.4.4"
475 | }
476 | },
477 | "minimatch": {
478 | "version": "3.0.4",
479 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
480 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
481 | "dev": true,
482 | "requires": {
483 | "brace-expansion": "1.1.8"
484 | }
485 | },
486 | "minimist": {
487 | "version": "1.2.0",
488 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
489 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
490 | "dev": true
491 | },
492 | "mkdirp": {
493 | "version": "0.5.1",
494 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
495 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
496 | "dev": true,
497 | "requires": {
498 | "minimist": "0.0.8"
499 | },
500 | "dependencies": {
501 | "minimist": {
502 | "version": "0.0.8",
503 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
504 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
505 | "dev": true
506 | }
507 | }
508 | },
509 | "mocha": {
510 | "version": "5.0.0",
511 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz",
512 | "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==",
513 | "dev": true,
514 | "requires": {
515 | "browser-stdout": "1.3.0",
516 | "commander": "2.11.0",
517 | "debug": "3.1.0",
518 | "diff": "3.3.1",
519 | "escape-string-regexp": "1.0.5",
520 | "glob": "7.1.2",
521 | "growl": "1.10.3",
522 | "he": "1.1.1",
523 | "mkdirp": "0.5.1",
524 | "supports-color": "4.4.0"
525 | },
526 | "dependencies": {
527 | "supports-color": {
528 | "version": "4.4.0",
529 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
530 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
531 | "dev": true,
532 | "requires": {
533 | "has-flag": "2.0.0"
534 | }
535 | }
536 | }
537 | },
538 | "ms": {
539 | "version": "2.0.0",
540 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
541 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
542 | "dev": true
543 | },
544 | "normalize-path": {
545 | "version": "2.1.1",
546 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
547 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
548 | "dev": true,
549 | "requires": {
550 | "remove-trailing-separator": "1.1.0"
551 | }
552 | },
553 | "object.omit": {
554 | "version": "2.0.1",
555 | "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
556 | "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
557 | "dev": true,
558 | "requires": {
559 | "for-own": "0.1.5",
560 | "is-extendable": "0.1.1"
561 | }
562 | },
563 | "once": {
564 | "version": "1.4.0",
565 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
566 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
567 | "dev": true,
568 | "requires": {
569 | "wrappy": "1.0.2"
570 | }
571 | },
572 | "os-homedir": {
573 | "version": "1.0.2",
574 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
575 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
576 | "dev": true
577 | },
578 | "parse-glob": {
579 | "version": "3.0.4",
580 | "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
581 | "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
582 | "dev": true,
583 | "requires": {
584 | "glob-base": "0.3.0",
585 | "is-dotfile": "1.0.3",
586 | "is-extglob": "1.0.0",
587 | "is-glob": "2.0.1"
588 | }
589 | },
590 | "path-is-absolute": {
591 | "version": "1.0.1",
592 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
593 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
594 | "dev": true
595 | },
596 | "path-parse": {
597 | "version": "1.0.5",
598 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
599 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
600 | "dev": true
601 | },
602 | "pathval": {
603 | "version": "1.1.0",
604 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
605 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
606 | "dev": true
607 | },
608 | "preserve": {
609 | "version": "0.2.0",
610 | "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
611 | "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
612 | "dev": true
613 | },
614 | "randomatic": {
615 | "version": "1.1.7",
616 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
617 | "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
618 | "dev": true,
619 | "requires": {
620 | "is-number": "3.0.0",
621 | "kind-of": "4.0.0"
622 | },
623 | "dependencies": {
624 | "is-number": {
625 | "version": "3.0.0",
626 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
627 | "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
628 | "dev": true,
629 | "requires": {
630 | "kind-of": "3.2.2"
631 | },
632 | "dependencies": {
633 | "kind-of": {
634 | "version": "3.2.2",
635 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
636 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
637 | "dev": true,
638 | "requires": {
639 | "is-buffer": "1.1.6"
640 | }
641 | }
642 | }
643 | },
644 | "kind-of": {
645 | "version": "4.0.0",
646 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
647 | "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
648 | "dev": true,
649 | "requires": {
650 | "is-buffer": "1.1.6"
651 | }
652 | }
653 | }
654 | },
655 | "regex-cache": {
656 | "version": "0.4.4",
657 | "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
658 | "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
659 | "dev": true,
660 | "requires": {
661 | "is-equal-shallow": "0.1.3"
662 | }
663 | },
664 | "remove-trailing-separator": {
665 | "version": "1.1.0",
666 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
667 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
668 | "dev": true
669 | },
670 | "repeat-element": {
671 | "version": "1.1.2",
672 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
673 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
674 | "dev": true
675 | },
676 | "repeat-string": {
677 | "version": "1.6.1",
678 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
679 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
680 | "dev": true
681 | },
682 | "resolve": {
683 | "version": "1.5.0",
684 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
685 | "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==",
686 | "dev": true,
687 | "requires": {
688 | "path-parse": "1.0.5"
689 | }
690 | },
691 | "rollup": {
692 | "version": "0.46.3",
693 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.46.3.tgz",
694 | "integrity": "sha512-ygbpqQczZTLr7ca2tVgi/AUPIRiI2sSp+3xKQRHgovM489GJPaH5mYUeRDb43Sq1td3zq6GUGovLi9vzmbQmVw==",
695 | "dev": true
696 | },
697 | "rollup-plugin-buble": {
698 | "version": "0.15.0",
699 | "resolved": "https://registry.npmjs.org/rollup-plugin-buble/-/rollup-plugin-buble-0.15.0.tgz",
700 | "integrity": "sha1-g8PonH/SJmx5GPQbo5gDE1Gcf9A=",
701 | "dev": true,
702 | "requires": {
703 | "buble": "0.15.2",
704 | "rollup-pluginutils": "1.5.2"
705 | }
706 | },
707 | "rollup-plugin-commonjs": {
708 | "version": "8.2.6",
709 | "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.2.6.tgz",
710 | "integrity": "sha512-qK0+uhktmnAgZkHkqFuajNmPw93fjrO7+CysDaxWE5jrUR9XSlSvuao5ZJP+XizxA8weakhgYYBtbVz9SGBpjA==",
711 | "dev": true,
712 | "requires": {
713 | "acorn": "5.3.0",
714 | "estree-walker": "0.5.1",
715 | "magic-string": "0.22.4",
716 | "resolve": "1.5.0",
717 | "rollup-pluginutils": "2.0.1"
718 | },
719 | "dependencies": {
720 | "acorn": {
721 | "version": "5.3.0",
722 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
723 | "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==",
724 | "dev": true
725 | },
726 | "estree-walker": {
727 | "version": "0.5.1",
728 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.1.tgz",
729 | "integrity": "sha512-7HgCgz1axW7w5aOvgOQkoR1RMBkllygJrssU3BvymKQ95lxXYv6Pon17fBRDm9qhkvXZGijOULoSF9ShOk/ZLg==",
730 | "dev": true
731 | },
732 | "magic-string": {
733 | "version": "0.22.4",
734 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.4.tgz",
735 | "integrity": "sha512-kxBL06p6iO2qPBHsqGK2b3cRwiRGpnmSuVWNhwHcMX7qJOUr1HvricYP1LZOCdkQBUp0jiWg2d6WJwR3vYgByw==",
736 | "dev": true,
737 | "requires": {
738 | "vlq": "0.2.3"
739 | }
740 | },
741 | "rollup-pluginutils": {
742 | "version": "2.0.1",
743 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz",
744 | "integrity": "sha1-fslbNXP2VDpGpkYb2afFRFJdD8A=",
745 | "dev": true,
746 | "requires": {
747 | "estree-walker": "0.3.1",
748 | "micromatch": "2.3.11"
749 | },
750 | "dependencies": {
751 | "estree-walker": {
752 | "version": "0.3.1",
753 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.3.1.tgz",
754 | "integrity": "sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao=",
755 | "dev": true
756 | }
757 | }
758 | }
759 | }
760 | },
761 | "rollup-plugin-node-resolve": {
762 | "version": "3.0.2",
763 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.0.2.tgz",
764 | "integrity": "sha512-ZwmMip/yqw6cmDQJuCQJ1G7gw2z11iGUtQNFYrFZHmqadRHU+OZGC3nOXwXu+UTvcm5lzDspB1EYWrkTgPWybw==",
765 | "dev": true,
766 | "requires": {
767 | "builtin-modules": "1.1.1",
768 | "is-module": "1.0.0",
769 | "resolve": "1.5.0"
770 | }
771 | },
772 | "rollup-pluginutils": {
773 | "version": "1.5.2",
774 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz",
775 | "integrity": "sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg=",
776 | "dev": true,
777 | "requires": {
778 | "estree-walker": "0.2.1",
779 | "minimatch": "3.0.4"
780 | }
781 | },
782 | "strip-ansi": {
783 | "version": "3.0.1",
784 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
785 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
786 | "dev": true,
787 | "requires": {
788 | "ansi-regex": "2.1.1"
789 | }
790 | },
791 | "supports-color": {
792 | "version": "2.0.0",
793 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
794 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
795 | "dev": true
796 | },
797 | "type-detect": {
798 | "version": "4.0.6",
799 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.6.tgz",
800 | "integrity": "sha512-qZ3bAurt2IXGPR3c57PyaSYEnQiLRwPeS60G9TahElBZsdOABo+iKYch/PhRjSTZJ5/DF08x43XMt9qec2g3ig==",
801 | "dev": true
802 | },
803 | "vlq": {
804 | "version": "0.2.3",
805 | "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
806 | "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==",
807 | "dev": true
808 | },
809 | "wrappy": {
810 | "version": "1.0.2",
811 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
812 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
813 | "dev": true
814 | }
815 | }
816 | }
817 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bezier-intersect",
3 | "version": "0.0.3",
4 | "description": "intersections between bezier curves of order 2, 3 and lines and rectangles",
5 | "main": "dist/bezier-intersect.js",
6 | "module": "index",
7 | "jsnext:main": "index",
8 | "scripts": {
9 | "build": "rollup -c",
10 | "dev": "rollup -c -w",
11 | "test": "mocha test/test.js",
12 | "pretest": "npm run build",
13 | "prepublish": "npm run build"
14 | },
15 | "devDependencies": {
16 | "chai": "^4.1.2",
17 | "mocha": "^5.0.0",
18 | "rollup": "^0.46.0",
19 | "rollup-plugin-buble": "^0.15.0",
20 | "rollup-plugin-commonjs": "^8.0.2",
21 | "rollup-plugin-node-resolve": "^3.0.0"
22 | },
23 | "keywords": [
24 | "bezier",
25 | "curve",
26 | "intersection",
27 | "quadratic",
28 | "cubic"
29 | ],
30 | "files": [
31 | "dist",
32 | "src",
33 | "index.js"
34 | ],
35 | "author": "Alexander Milevski ",
36 | "license": "MIT",
37 | "directories": {
38 | "test": "test"
39 | },
40 | "dependencies": {},
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/w8r/bezier-intersect.git"
44 | },
45 | "bugs": {
46 | "url": "https://github.com/w8r/bezier-intersect/issues"
47 | },
48 | "homepage": "https://github.com/w8r/bezier-intersect#readme"
49 | }
50 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import buble from 'rollup-plugin-buble';
4 | import pkg from './package.json';
5 |
6 | export default [
7 | // browser-friendly UMD build
8 | {
9 | entry: 'index.js',
10 | dest: pkg.main,
11 | format: 'umd',
12 | moduleName: 'bezierIntersect',
13 | plugins: [
14 | resolve(), // so Rollup can find `ms`
15 | commonjs(), // so Rollup can convert `ms` to an ES module
16 | buble({ // transpile ES2015+ to ES5
17 | exclude: ['node_modules/**']
18 | })
19 | ]
20 | }
21 |
22 | // CommonJS (for Node) and ES module (for bundlers) build.
23 | // (We could have three entries in the configuration array
24 | // instead of two, but it's quicker to generate multiple
25 | // builds from a single configuration where possible, using
26 | // the `targets` option which can specify `dest` and `format`)
27 | // {
28 | // entry: 'src/main.js',
29 | // external: ['ms'],
30 | // targets: [
31 | // { dest: pkg.main, format: 'cjs' },
32 | // { dest: pkg.module, format: 'es' }
33 | // ],
34 | // plugins: [
35 | // buble({
36 | // exclude: ['node_modules/**']
37 | // })
38 | // ]
39 | // }
40 | ];
41 |
--------------------------------------------------------------------------------
/src/cubic.js:
--------------------------------------------------------------------------------
1 | import { getPolynomialRoots } from './polynomial';
2 |
3 | export function cubicBezierLine(
4 | p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y,
5 | a1x, a1y, a2x, a2y, result) {
6 | var ax, ay, bx, by, cx, cy, dx, dy; // temporary variables
7 | var c3x, c3y, c2x, c2y, c1x, c1y, c0x, c0y; // coefficients of cubic
8 | var cl; // c coefficient for normal form of line
9 | var nx, ny; // normal for normal form of line
10 |
11 | // used to determine if point is on line segment
12 | var minx = Math.min(a1x, a2x),
13 | miny = Math.min(a1y, a2y),
14 | maxx = Math.max(a1x, a2x),
15 | maxy = Math.max(a1y, a2y);
16 |
17 | // Start with Bezier using Bernstein polynomials for weighting functions:
18 | // (1-t^3)P1 + 3t(1-t)^2P2 + 3t^2(1-t)P3 + t^3P4
19 | //
20 | // Expand and collect terms to form linear combinations of original Bezier
21 | // controls. This ends up with a vector cubic in t:
22 | // (-P1+3P2-3P3+P4)t^3 + (3P1-6P2+3P3)t^2 + (-3P1+3P2)t + P1
23 | // /\ /\ /\ /\
24 | // || || || ||
25 | // c3 c2 c1 c0
26 |
27 | // Calculate the coefficients
28 | ax = p1x * -1; ay = p1y * -1;
29 | bx = p2x * 3; by = p2y * 3;
30 | cx = p3x * -3; cy = p3y * -3;
31 | dx = ax + bx + cx + p4x;
32 | dy = ay + by + cy + p4y;
33 | c3x = dx; c3y = dy; // vec
34 |
35 | ax = p1x * 3; ay = p1y * 3;
36 | bx = p2x * -6; by = p2y * -6;
37 | cx = p3x * 3; cy = p3y * 3;
38 | dx = ax + bx + cx;
39 | dy = ay + by + cy;
40 | c2x = dx; c2y = dy; // vec
41 |
42 | ax = p1x * -3; ay = p1y * -3;
43 | bx = p2x * 3; by = p2y * 3;
44 | cx = ax + bx;
45 | cy = ay + by;
46 | c1x = cx;
47 | c1y = cy; // vec
48 |
49 | c0x = p1x;
50 | c0y = p1y;
51 |
52 | // Convert line to normal form: ax + by + c = 0
53 | // Find normal to line: negative inverse of original line's slope
54 | nx = a1y - a2y;
55 | ny = a2x - a1x;
56 |
57 | // Determine new c coefficient
58 | cl = a1x * a2y - a2x * a1y;
59 |
60 | // ?Rotate each cubic coefficient using line for new coordinate system?
61 | // Find roots of rotated cubic
62 | var roots = getPolynomialRoots(
63 | // dot products => x * x + y * y
64 | nx * c3x + ny * c3y,
65 | nx * c2x + ny * c2y,
66 | nx * c1x + ny * c1y,
67 | nx * c0x + ny * c0y + cl
68 | );
69 |
70 | // Any roots in closed interval [0,1] are intersections on Bezier, but
71 | // might not be on the line segment.
72 | // Find intersections and calculate point coordinates
73 | for (var i = 0; i < roots.length; i++) {
74 | var t = roots[i];
75 |
76 | if (0 <= t && t <= 1) { // We're within the Bezier curve
77 | // Find point on Bezier
78 | // lerp: x1 + (x2 - x1) * t
79 | var p5x = p1x + (p2x - p1x) * t;
80 | var p5y = p1y + (p2y - p1y) * t; // lerp(p1, p2, t);
81 |
82 | var p6x = p2x + (p3x - p2x) * t;
83 | var p6y = p2y + (p3y - p2y) * t;
84 |
85 | var p7x = p3x + (p4x - p3x) * t;
86 | var p7y = p3y + (p4y - p3y) * t;
87 |
88 | var p8x = p5x + (p6x - p5x) * t;
89 | var p8y = p5y + (p6y - p5y) * t;
90 |
91 | var p9x = p6x + (p7x - p6x) * t;
92 | var p9y = p6y + (p7y - p6y) * t;
93 |
94 | // candidate
95 | var p10x = p8x + (p9x - p8x) * t;
96 | var p10y = p8y + (p9y - p8y) * t;
97 |
98 | // See if point is on line segment
99 | if (a1x === a2x) { // vertical
100 | if (miny <= p10y && p10y <= maxy) {
101 | if (result) result.push(p10x, p10y);
102 | else return 1;
103 | }
104 | } else if (a1y === a2y) { // horizontal
105 | if (minx <= p10x && p10x <= maxx) {
106 | if (result) result.push(p10x, p10y);
107 | else return 1;
108 | }
109 | } else if (p10x >= minx && p10y >= miny && p10x <= maxx && p10y <= maxy) {
110 | if (result) result.push(p10x, p10y);
111 | else return 1;
112 | }
113 | }
114 | }
115 | return result ? result.length / 2 : 0;
116 | }
117 |
118 |
119 | export function cubicBezierAABB(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymax, result) {
120 | if (result) { // all intersections
121 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymin, result);
122 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmax, ymin, xmax, ymax, result);
123 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymax, xmax, ymax, result);
124 | cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmin, ymax, result);
125 | return result.length / 2;
126 | } else { // any intersections
127 | // trivial cases
128 | if (xmin <= ax && xmax >= ax && ymin <= ay && ymax >= ay) return 1;
129 | if (xmin <= bx && xmax >= bx && ymin <= by && ymax >= by) return 1;
130 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmax, ymin)) return 1;
131 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmax, ymin, xmax, ymax)) return 1;
132 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymax, xmax, ymax)) return 1;
133 | if (cubicBezierLine(ax, ay, c1x, c1y, c2x, c2y, bx, by, xmin, ymin, xmin, ymax)) return 1;
134 | return 0;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/polynomial.js:
--------------------------------------------------------------------------------
1 | const POLYNOMIAL_TOLERANCE = 1e-6;
2 | const TOLERANCE = 1e-12;
3 |
4 | export function getPolynomialRoots() {
5 | var C = arguments;
6 | var degree = C.length - 1;
7 | var n = degree;
8 | var results = [];
9 | for (var i = 0; i <= degree; i++) {
10 | if (Math.abs(C[i]) <= TOLERANCE) degree--; else break;
11 | }
12 |
13 | switch (degree) {
14 | case 1: getLinearRoots(C[n], C[n - 1], results); break;
15 | case 2: getQuadraticRoots(C[n], C[n - 1], C[n - 2], results); break;
16 | case 3: getCubicRoots(C[n], C[n - 1], C[n - 2], C[n - 3], results); break;
17 | default: break;
18 | }
19 |
20 | return results;
21 | }
22 |
23 | export function getLinearRoots(C0, C1, results = []) {
24 | if (C1 !== 0) results.push(-C0 / C1);
25 | return results;
26 | }
27 |
28 | export function getQuadraticRoots(C0, C1, C2, results = []) {
29 | var a = C2;
30 | var b = C1 / a;
31 | var c = C0 / a;
32 | var d = b * b - 4 * c;
33 |
34 | if (d > 0) {
35 | var e = Math.sqrt(d);
36 |
37 | results.push(0.5 * (-b + e));
38 | results.push(0.5 * (-b - e));
39 | } else if (d === 0) {
40 | results.push( 0.5 * -b);
41 | }
42 |
43 | return results;
44 | }
45 |
46 |
47 | export function getCubicRoots(C0, C1, C2, C3, results = []) {
48 | var c3 = C3;
49 | var c2 = C2 / c3;
50 | var c1 = C1 / c3;
51 | var c0 = C0 / c3;
52 |
53 | var a = (3 * c1 - c2 * c2) / 3;
54 | var b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27;
55 | var offset = c2 / 3;
56 | var discrim = b * b / 4 + a * a * a / 27;
57 | var halfB = b / 2;
58 | var tmp, root;
59 |
60 | if (Math.abs(discrim) <= POLYNOMIAL_TOLERANCE) discrim = 0;
61 |
62 | if (discrim > 0) {
63 | var e = Math.sqrt(discrim);
64 |
65 | tmp = -halfB + e;
66 | if ( tmp >= 0 ) root = Math.pow( tmp, 1/3);
67 | else root = -Math.pow(-tmp, 1/3);
68 |
69 | tmp = -halfB - e;
70 | if ( tmp >= 0 ) root += Math.pow( tmp, 1/3);
71 | else root -= Math.pow(-tmp, 1/3);
72 |
73 | results.push(root - offset);
74 | } else if (discrim < 0) {
75 | var distance = Math.sqrt(-a/3);
76 | var angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3;
77 | var cos = Math.cos(angle);
78 | var sin = Math.sin(angle);
79 | var sqrt3 = Math.sqrt(3);
80 |
81 | results.push( 2 * distance * cos - offset);
82 | results.push(-distance * (cos + sqrt3 * sin) - offset);
83 | results.push(-distance * (cos - sqrt3 * sin) - offset);
84 | } else {
85 | if (halfB >= 0) tmp = -Math.pow(halfB, 1/3);
86 | else tmp = Math.pow(-halfB, 1/3);
87 |
88 | results.push(2 * tmp - offset);
89 | // really should return next root twice, but we return only one
90 | results.push(-tmp - offset);
91 | }
92 |
93 | return results;
94 | }
95 |
--------------------------------------------------------------------------------
/src/quadratic.js:
--------------------------------------------------------------------------------
1 | import { getPolynomialRoots } from './polynomial';
2 |
3 | export function quadBezierLine(
4 | p1x, p1y, p2x, p2y, p3x, p3y,
5 | a1x, a1y, a2x, a2y, result) {
6 | var ax, ay, bx, by; // temporary variables
7 | var c2x, c2y, c1x, c1y, c0x, c0y; // coefficients of quadratic
8 | var cl; // c coefficient for normal form of line
9 | var nx, ny; // normal for normal form of line
10 | // used to determine if point is on line segment
11 | var minx = Math.min(a1x, a2x),
12 | miny = Math.min(a1y, a2y),
13 | maxx = Math.max(a1x, a2x),
14 | maxy = Math.max(a1y, a2y);
15 |
16 | ax = p2x * -2; ay = p2y * -2;
17 | c2x = p1x + ax + p3x;
18 | c2y = p1y + ay + p3y;
19 |
20 | ax = p1x * -2; ay = p1y * -2;
21 | bx = p2x * 2; by = p2y * 2;
22 | c1x = ax + bx;
23 | c1y = ay + by;
24 |
25 | c0x = p1x; c0y = p1y; // vec
26 |
27 | // Convert line to normal form: ax + by + c = 0
28 | // Find normal to line: negative inverse of original line's slope
29 | nx = a1y - a2y; ny = a2x - a1x;
30 |
31 | // Determine new c coefficient
32 | cl = a1x * a2y - a2x * a1y;
33 |
34 | // Transform cubic coefficients to line's coordinate system
35 | // and find roots of cubic
36 | var roots = getPolynomialRoots(
37 | // dot products => x * x + y * y
38 | nx * c2x + ny * c2y,
39 | nx * c1x + ny * c1y,
40 | nx * c0x + ny * c0y + cl
41 | );
42 |
43 | // Any roots in closed interval [0,1] are intersections on Bezier, but
44 | // might not be on the line segment.
45 | // Find intersections and calculate point coordinates
46 | for (var i = 0; i < roots.length; i++) {
47 | var t = roots[i];
48 | if ( 0 <= t && t <= 1 ) { // We're within the Bezier curve
49 | // Find point on Bezier
50 | // lerp: x1 + (x2 - x1) * t
51 | var p4x = p1x + (p2x - p1x) * t;
52 | var p4y = p1y + (p2y - p1y) * t;
53 |
54 | var p5x = p2x + (p3x - p2x) * t;
55 | var p5y = p2y + (p3y - p2y) * t;
56 |
57 | // candidate
58 | var p6x = p4x + (p5x - p4x) * t;
59 | var p6y = p4y + (p5y - p4y) * t;
60 |
61 | // See if point is on line segment
62 | // Had to make special cases for vertical and horizontal lines due
63 | // to slight errors in calculation of p6
64 | if (a1x === a2x) {
65 | if (miny <= p6y && p6y <= maxy) {
66 | if (result) result.push(p6x, p6y);
67 | else return 1;
68 | }
69 | } else if (a1y === a2y) {
70 | if (minx <= p6x && p6x <= maxx) {
71 | if (result) result.push(p6x, p6y);
72 | else return 1;
73 | }
74 |
75 | // gte: (x1 >= x2 && y1 >= y2)
76 | // lte: (x1 <= x2 && y1 <= y2)
77 | } else if (p6x >= minx && p6y >= miny && p6x <= maxx && p6y <= maxy) {
78 | if (result) result.push(p6x, p6y);
79 | else return 1;
80 | }
81 | }
82 | }
83 | return result ? result.length / 2 : 0;
84 | }
85 |
86 |
87 | export function quadBezierAABB(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymax, result) {
88 | if (result) { // all intersections
89 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymin, result);
90 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmax, ymin, xmax, ymax, result);
91 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymax, xmax, ymax, result);
92 | quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmin, ymax, result);
93 | return result.length / 2;
94 | } else { // any intersections
95 | // trivial cases
96 | if (xmin <= ax && xmax >= ax && ymin <= ay && ymax >= ay) return 1;
97 | if (xmin <= bx && xmax >= bx && ymin <= by && ymax >= by) return 1;
98 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmax, ymin)) return 1;
99 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmax, ymin, xmax, ymax)) return 1;
100 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymax, xmax, ymax)) return 1;
101 | if (quadBezierLine(ax, ay, c1x, c1y, bx, by, xmin, ymin, xmin, ymax)) return 1;
102 | return 0;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const {
2 | cubicBezierLine,
3 | quadBezierAABB,
4 | cubicBezierAABB
5 | } = require('../dist/bezier-intersect');
6 |
7 | const { assert } = require('chai');
8 |
9 | describe('intersectCubicBezierLine', () => {
10 | it ('vertical line crosses', () => {
11 | const result = [];
12 | assert.isTrue(!!cubicBezierLine(0,0, 100, 100, 200, 100, 300, 0, 100, 0, 100, 100, result))
13 | assert.deepEqual(result, [99.99999999999997, 66.66666666666666]);
14 | });
15 |
16 | it ('diagonal line crosses', () => {
17 | const result = [];
18 | assert.isTrue(!!cubicBezierLine(0,0, 100, 100, 200, 100, 300, 0, 100, 0, 200, 100, result))
19 | assert.deepEqual(result, [173.20508075688772, 73.20508075688774]);
20 | });
21 | });
22 |
23 | describe('intersectCubicBezierAABB', () => {
24 |
25 | it ('intersects', () => {
26 | const x = 981.7516776530498, y = 1202.2380271093887;
27 | const dx = 860.2542723247064, dy = 1323.735432437732;
28 | const res = [];
29 | const q = [843, 1228, 943, 1328];
30 | assert.equal(cubicBezierAABB(x, y, dx, y, x, dy, x, y, q[0], q[1], q[2], q[3], res), 2);
31 | assert.deepEqual(res, [ 927.7910501404327, 1228, 942.9999999999999, 1252.974906993395 ]);
32 | });
33 |
34 | it ('does not intersect', () => {
35 | const x = 981.7516776530498, y = 1202.2380271093887;
36 | const dx = 860.2542723247064, dy = 1323.735432437732;
37 | const res = [];
38 | const q = [843 - 100, 1228 - 100, 943 -100, 1328 - 100]
39 | assert.equal(cubicBezierAABB(x, y, dx, y, x, dy, x, y, q[0], q[1], q[2], q[3], res), 0);
40 | assert.deepEqual(res, []);
41 | });
42 |
43 | });
44 |
45 | describe('quad bezier rectangle', () => {
46 | it ('crosses the rectangle at 2 points', () => {
47 | const result = [];
48 | assert.equal(quadBezierAABB(0, 0, 150, 100, 300, 0, 100, 0, 200, 100, result), 2);
49 | assert.deepEqual(result, [200, 44.44444444444445, 100, 44.44444444444444]);
50 | });
51 | });
52 |
--------------------------------------------------------------------------------