├── .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 [![npm version](https://badge.fury.io/js/bezier-intersect.svg)](https://badge.fury.io/js/bezier-intersect) [![build](https://travis-ci.org/w8r/bezier-intersect.svg?branch=master)](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 | --------------------------------------------------------------------------------