├── .gitignore ├── LICENSE.txt ├── README.md ├── bench.js ├── intersections.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015 Elijah Insua 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2d-polygon-self-intersections 2 | 3 | find self-intersections in a 2d polygon 4 | 5 | This library may not be fast, but it is robust. Robust in the fact that it will find all of the self-intersections in a polygon - minus of course shared endpoints. 6 | 7 | You can expect a time complexity of O(n^2) 8 | 9 | Why wouldn't we use [Bentley–Ottmann](http://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm)? We may in the future, but that is going to take some time and having a functional mechanism for detecting self-intersections is far superior to a non-existant one. The api won't have to change for this to happen. 10 | 11 | ## install 12 | 13 | `npm install 2d-polygon-self-intersections` 14 | 15 | ## use 16 | 17 | ```javascript 18 | var isects = require('2d-polygon-self-intersections'); 19 | 20 | var poly = [ 21 | [0, 0], 22 | [10, 0], 23 | [0, 10], 24 | [10, 10] 25 | ]; 26 | 27 | var r = isects(poly); 28 | console.log(r); 29 | // outputs: [ [ 5, 5 ] ] 30 | ``` 31 | 32 | ### api 33 | 34 | __isects__(`polygon`[, `filterFn`]) 35 | 36 | * `polygon` - an array of 2 component arrays (i.e. a triangle `[[0, 0], [10, 0], [10, 10]]`) or an array of objects: `[{x:0, y:0}, {x:10, y:0}, {x:10, y:10}]` 37 | * `filterFn` - a filter function called whenever an intersection is found: `filterFn`(`isect`, `start0`, `end0`, `start1`, `end1`, `unique`) 38 | * `isect` - current intersection (e.g. `[5, 5]`) - mutations in this array get collected 39 | * `index0` - index of the segment (e.g `1`) 40 | * `start0` - start of the first segment (e.g `[0, 5]`) 41 | * `end0` - start of the first segment (e.g `[10, 5]`) 42 | * `index0` - index of the segment (e.g `3`) 43 | * `start1` - start of the first segment (e.g `[5, 0]`) 44 | * `end1` - start of the first segment (e.g `[5, 10]`) 45 | * `unique` - boolean representing whether or not this intersection point has been seen before 46 | * __return__ `true` to collect and `false` to discard 47 | 48 | __returns__ an empty array if no interesections or an array of 2 component arrays representing the intersection points. 49 | 50 | _NOTE_: this library assumes the polygon is closed, so manually adding the start point as the end point has no effect. 51 | 52 | Also note that there are 2 intersections per crossing, this library by default will only report one - all intersections will be unique. This behavior can be changed with the `filterFn`. 53 | 54 | ## license 55 | 56 | [MIT](LICENSE.txt) 57 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | var isects = require('./intersections') 2 | var Suite = require('benchmark').Suite 3 | 4 | var suite = new Suite; 5 | 6 | var square = [ 7 | [0, 0], 8 | [10, 0], 9 | [10, 10], 10 | [0, 10], 11 | [0, 0] 12 | ]; 13 | 14 | var hourglass = [ 15 | [0, 0], 16 | [10, 0], 17 | [0, 10], 18 | [10, 10], 19 | ]; 20 | 21 | var hourglassVec2 = [ 22 | { x: 0, y: 0 }, 23 | { x: 10, y: 0 }, 24 | { x: 0, y: 10 }, 25 | { x: 10, y: 10 } 26 | ]; 27 | 28 | 29 | // add tests 30 | suite 31 | .add('isect', function() { 32 | isects(hourglass); 33 | }) 34 | .add('isect vec2', function() { 35 | isects(hourglassVec2); 36 | }) 37 | .add('no isect', function() { 38 | isects(square); 39 | }) 40 | 41 | // add listeners 42 | .on('cycle', function(event) { 43 | console.log(String(event.target)); 44 | }) 45 | .on('complete', function() { 46 | console.log('Fastest is ' + this.filter('fastest').pluck('name')); 47 | }) 48 | // run async 49 | .run({ 'async': true }); 50 | -------------------------------------------------------------------------------- /intersections.js: -------------------------------------------------------------------------------- 1 | var isect = require('exact-segment-intersect'); 2 | var float = require('robust-estimate-float'); 3 | 4 | module.exports = selfIntersections; 5 | 6 | function cmp(a, b) { 7 | return a[0] === b[0] && a[1] === b[1]; 8 | } 9 | 10 | var pc = [0, 0]; 11 | var pn = [0, 0]; 12 | var oc = [0, 0]; 13 | var on = [0, 0]; 14 | 15 | function arrayOrObject(v, ret) { 16 | if (Array.isArray(v)) { 17 | ret[0] = v[0]; 18 | ret[1] = v[1]; 19 | } else { 20 | ret[0] = v.x; 21 | ret[1] = v.y; 22 | } 23 | } 24 | 25 | function selfIntersections(poly, filterFn) { 26 | var seen = {}; 27 | var l = poly.length; 28 | var isects = []; 29 | for (var o=0; o", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/tmpvar/2d-polygon-self-intersections/issues" 33 | }, 34 | "homepage": "https://github.com/tmpvar/2d-polygon-self-intersections" 35 | } 36 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var isects = require('./intersections'); 3 | 4 | test('no intersections (square)', function(t) { 5 | 6 | var poly = [ 7 | [0, 0], 8 | [10, 0], 9 | [10, 10], 10 | [0, 10], 11 | [0, 0] 12 | ]; 13 | 14 | var r = isects(poly, t.fail); 15 | t.equal(r.length, 0, 'no self-intersections'); 16 | 17 | t.end(); 18 | }); 19 | 20 | test('interesections (hourglass)', function(t) { 21 | 22 | var poly = [ 23 | [0, 0], 24 | [10, 0], 25 | [0, 10], 26 | [10, 10], 27 | ]; 28 | 29 | var r = isects(poly); 30 | 31 | t.equal(r.length, 1, 'no self-intersections'); 32 | t.deepEqual(r[0], [5, 5], 'isect at (5, 0)') 33 | t.end(); 34 | }); 35 | 36 | test('interesection visitor', function(t) { 37 | 38 | var poly = [ 39 | [0, 0], 40 | { x: 10, y: 0 }, 41 | [0, 10], 42 | [10, 10], 43 | ]; 44 | 45 | var expectedArgs = [ 46 | [[5, 5], 1, { x: 10, y: 0 }, [0, 10], 3, [10, 10], [0, 0], true], 47 | [[5, 5], 3, [10, 10], [0, 0], 1, { x: 10, y: 0 }, [0, 10], false] 48 | ]; 49 | 50 | var calls = 0; 51 | var r = isects(poly, function(isect, i0, s0, e0, i1, s1, e1, unique) { 52 | var args = []; 53 | Array.prototype.push.apply(args, arguments); 54 | t.deepEqual(args, expectedArgs[calls]); 55 | calls++; 56 | return true; 57 | }); 58 | 59 | t.equal(calls, 2, 'visitor called twice') 60 | t.equal(r.length, 2, 'no self-intersections'); 61 | t.deepEqual(r[0], [5, 5], 'isect at (5, 0)') 62 | t.deepEqual(r[1], [5, 5], 'isect at (5, 0)') 63 | t.end(); 64 | }); 65 | 66 | test('work with vec2', function(t) { 67 | 68 | var poly = [ 69 | { x: 0, y: 0 }, 70 | { x: 10, y: 0 }, 71 | { x: 0, y: 10 }, 72 | { x: 10, y: 10 } 73 | ]; 74 | 75 | var r = isects(poly); 76 | t.equal(r.length, 1, 'no self-intersections'); 77 | t.deepEqual(r[0], [5, 5], 'isect at (5, 0)') 78 | t.end(); 79 | }); 80 | 81 | test('better deduping', function(t) { 82 | 83 | var poly = [ 84 | [0, 0], 85 | [20, 0], 86 | [20, 5], 87 | [20, 10], 88 | ]; 89 | 90 | var r = isects(poly); 91 | t.equal(r.length, 0, 'no self-intersections'); 92 | t.end(); 93 | }); 94 | 95 | test('separated intersections', function(t) { 96 | 97 | /* 98 | o-----o 99 | | | 100 | o------x-----x------o 101 | | | | | 102 | | | | | 103 | | | | | 104 | o------o o------o 105 | */ 106 | 107 | var poly = [ 108 | [-10, 0], 109 | [10, 0], 110 | [10, 10], 111 | [1, 10], 112 | [1, -1], 113 | [-1, -1], 114 | [-1, 10], 115 | [-10, 1] 116 | ]; 117 | 118 | var r = isects(poly); 119 | 120 | t.equal(r.length, 2, 'two self-intersections'); 121 | t.end(); 122 | }); 123 | --------------------------------------------------------------------------------