├── .gitattributes ├── LICENSE.txt ├── README.md ├── index.css ├── index.html ├── index.js ├── solution.js ├── test.css ├── test.html └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Oleksandr Goncharov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-intersect 2 | This is demo of polygons intersection algorithm written in JS. Visual part of project based on template provided by [kottans.org](http//kottans.org). 3 | Demo site is available via GitHub Pages here: [vrd.github.io/js-intersect] (http://vrd.github.io/js-intersect). 4 | 5 | Project consists of following files: 6 | - __index.html__ - main demo page 7 | - __test.html__ - page with test results for checking algorithm correctness 8 | - __index.js__ - code for drawing polygons and calling intersects() function 9 | - __solution.js__ - script with `intersect()` function and many other functions that realize algorithm 10 | - __test.js__ - script with tests 11 | - __index.css__ - styles for index.html 12 | - __test.css__ - styles for test.html 13 | 14 | ##### How to use: 15 | Function `intersect()` in file __solution.js__ is a top-level function of an algotithm. Function takes two arguments. Each argument is a polygon given in a form of list of its vertices. Every vertex is an object with two keys: "x" and "y". Example of polygon: 16 | `[{x: 100, y: 200}, {x: 150, y: 200}, {x: 150, y: 270}]` Polygon can consist of 3 to 100 vertices. 17 | Function returns list of polygons (result of polygons intersection) in same format. 18 | 19 | P.S. Algorithm can perform not only intersection of two polygons but other actions: union, complement etc. Look at function `filterPolygons()` in file solution.js for more information. 20 | 21 | 22 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | p { 7 | max-width: 700px; 8 | } 9 | 10 | .container { 11 | border: solid lightgray 3px; 12 | width: 400px; 13 | height: 400px; 14 | position: absolute; 15 | left: 50%; 16 | top: 50%; 17 | transform: translateX(-50%) translateY(-50%); 18 | } 19 | 20 | .container svg { 21 | position: absolute; 22 | left: 0; 23 | top: 0; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | svg.base polygon { 29 | stroke: black; 30 | stroke-width: 2px; 31 | opacity: 0.5; 32 | fill-rule:evenodd; 33 | } 34 | 35 | @keyframes blink { 36 | 0% { opacity: 0; } 37 | 50% { opacity: 1; } 38 | 100% { opacity: 0; } 39 | } 40 | 41 | svg.intersections { 42 | animation-name: blink; 43 | animation-duration: 1s; 44 | animation-iteration-count: infinite; 45 | } 46 | 47 | .control { 48 | position: absolute; 49 | left: 50%; 50 | top: 90%; 51 | transform: translateX(-50%) translateY(-90%); 52 | text-align:center; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Main | Intersections in JS 8 | 9 | 10 |

11 | js-intersect demo 12 |

13 |

This is demo page for visualization of polygons intersection algorithm. Press "Draw random" button to see intersection of two random polygons. You can change points number of random polygons from 3 to 100 (if polygons contain many points processing may take some time).

14 | test results 15 |
16 | GitHub 17 |
18 | 19 | 20 |
21 |
First polygon:   22 | 23 |     Second polygon:  24 | 25 |

26 | 27 |
28 | 29 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // пример многоугольников 2 | var examples = { 3 | first: [ 4 | { x: 60, y: 60 }, 5 | { x: 180, y: 0 }, 6 | { x: 300, y: 60 }, 7 | { x: 300, y: 300 }, 8 | { x: 240, y: 180 }, 9 | { x: 210, y: 180 }, 10 | { x: 180, y: 240 }, 11 | { x: 150, y: 180 }, 12 | { x: 120, y: 180 }, 13 | { x: 60, y: 300 }, 14 | ], 15 | second: [ 16 | { x: 300, y: 240 }, 17 | { x: 330, y: 220 }, 18 | { x: 330, y: 210 }, 19 | { x: 270, y: 90 }, 20 | { x: 230, y: 270 }, 21 | { x: 210, y: 90 }, 22 | { x: 180, y: 60 }, 23 | { x: 150, y: 90 }, 24 | { x: 140, y: 280 }, 25 | { x: 90, y: 90 }, 26 | { x: 30, y: 210 } 27 | ] 28 | 29 | }; 30 | 31 | function drawPolygon(data, container, color) { 32 | var pol = document.createElementNS('http://www.w3.org/2000/svg', 'polygon'); 33 | var str = ''; 34 | str += data.map(function (point) { 35 | return point.x + ',' + point.y; 36 | }).join(' '); 37 | pol.setAttribute('points', str); 38 | pol.style.fill = color; 39 | container.appendChild(pol); 40 | } 41 | 42 | 43 | // function getScalingParameter(pols, viewport_width, viewport_height) { 44 | 45 | // } 46 | 47 | // function scalePolygon(pol, scaleCoef) { 48 | // var new_pol = []; 49 | // for (var i = 0; i < pol.length; i++) { 50 | // new_pol.push({x: pol[i].x*scaleCoef, y: pol[i].y*scaleCoef}); 51 | // } 52 | // return new_pol; 53 | // } 54 | 55 | function getMoveParameters(pols, viewport_height, viewport_width) { 56 | var x_min = Infinity, x_max = -Infinity, y_min = Infinity, y_max = -Infinity; 57 | for (var i = 0; i < pols.length; i++) { 58 | for (var j = 0; j < pols[i].length; j++) { 59 | if (pols[i][j].x < x_min) x_min = pols[i][j].x; 60 | if (pols[i][j].x > x_max) x_max = pols[i][j].x; 61 | if (pols[i][j].y < y_min) y_min = pols[i][j].y; 62 | if (pols[i][j].y > y_max) y_max = pols[i][j].y; 63 | } 64 | } 65 | var center = {x:0,y:0}; 66 | center.x = (x_max + x_min)/2; 67 | center.y = (y_max + y_min)/2; 68 | var shift = {x:0,y:0}; 69 | shift.x = viewport_width/2 - center.x; 70 | shift.y = viewport_height/2 - center.y; 71 | return shift; 72 | } 73 | 74 | function movePolygon(pol, shiftX, shiftY) { 75 | var new_pol = []; 76 | for (var i = 0; i < pol.length; i++) { 77 | new_pol.push({x: pol[i].x+shiftX, y: pol[i].y+shiftY}); 78 | } 79 | return new_pol; 80 | } 81 | 82 | function drawAllPolygons(pol1, pol2) { 83 | // pols = [pol1, pol2]; 84 | // shift = getMoveParameters(pols, 500, 500); 85 | // pol1 = movePolygon(pol1, shift.x, shift.y); 86 | // pol2 = movePolygon(pol2, shift.x, shift.y); 87 | drawPolygon(pol1, document.querySelector('svg.base'), 'navy'); 88 | drawPolygon(pol2, document.querySelector('svg.base'), 'yellow'); 89 | var pols = []; 90 | pols = intersect(pol1, pol2); 91 | console.info("Found " + pols.length + " intersections"); 92 | pols.forEach(function (p) { 93 | drawPolygon(p, document.querySelector('svg.intersections'), 'red'); 94 | }); 95 | } 96 | 97 | function getTwoRandomPolygons(num1, num2) { 98 | var twoPolygons = [[],[]]; 99 | var x, y; 100 | nums = [num1, num2]; 101 | for (var i = 0; i < nums.length; i++) { 102 | for (var j = 0; j < nums[i]; j++) { 103 | x = Math.round(380*Math.random() + 10); 104 | y = Math.round(380*Math.random() + 10); 105 | twoPolygons[i].push({x: x, y: y}); 106 | } 107 | } 108 | //log(twoPolygons); 109 | return twoPolygons; 110 | } 111 | 112 | function drawDefault() { 113 | drawAllPolygons(examples.first, examples.second); 114 | } 115 | 116 | function drawRandom() { 117 | var svg1 = document.querySelector('svg.base'); 118 | var base = svg1.getElementsByTagName('*'); 119 | for (i = base.length - 1; i >= 0; i--) { 120 | svg1.removeChild(base[i]); 121 | } 122 | var svg2 = document.querySelector('svg.intersections'); 123 | base = svg2.getElementsByTagName('*'); 124 | for (i = base.length - 1; i >= 0; i--) { 125 | svg2.removeChild(base[i]); 126 | } 127 | const MAX = 100; 128 | const MIN = 3; 129 | var pol1 = document.getElementById('pol1'); 130 | pol1.value = Math.round(pol1.value); 131 | if (pol1.value > MAX) pol1.value = MAX; 132 | if (pol1.value < MIN) pol1.value = MIN; 133 | var pol2 = document.getElementById('pol2'); 134 | pol2.value = Math.round(pol2.value); 135 | if (pol2.value > MAX) pol2.value = MAX; 136 | if (pol2.value < MIN) pol2.value = MIN; 137 | var polygons = getTwoRandomPolygons(pol1.value, pol2.value); 138 | drawAllPolygons(polygons[0], polygons[1]); 139 | 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /solution.js: -------------------------------------------------------------------------------- 1 | function intersect(fig1, fig2) { 2 | for (let i = 0; i < fig1.length; i++) { 3 | fig1[i].x = +((fig1[i].x).toFixed(9)); 4 | fig1[i].y = +((fig1[i].y).toFixed(9)); 5 | } 6 | for (let i = 0; i < fig2.length; i++) { 7 | fig2[i].x = +((fig2[i].x).toFixed(9)); 8 | fig2[i].y = +((fig2[i].y).toFixed(9)); 9 | } 10 | var fig2a = alignPolygon(fig2, fig1); 11 | if (!checkPolygons(fig1, fig2a)) { 12 | return false; 13 | } 14 | var edges = edgify(fig1, fig2a); 15 | var polygons = polygonate(edges); 16 | var filteredPolygons = filterPolygons(polygons, fig1, fig2a, "intersect"); 17 | return filteredPolygons; 18 | } 19 | 20 | function alignPolygon(polygon, points) { 21 | for (let i = 0; i < polygon.length; i++) { 22 | for (let j = 0; j < points.length; j++) { 23 | if (distance(polygon[i], points[j]) < 0.00000001) 24 | polygon[i] = points[j]; 25 | } 26 | } 27 | return polygon; 28 | } 29 | 30 | function distance(p1, p2) { 31 | var dx = Math.abs(p1.x - p2.x); 32 | var dy = Math.abs(p1.y - p2.y); 33 | return Math.sqrt(dx*dx + dy*dy); 34 | } 35 | 36 | //check polygons for correctness 37 | function checkPolygons(fig1, fig2) { 38 | var figs = [fig1, fig2]; 39 | for (var i = 0; i < figs.length; i++) { 40 | if (figs[i].length < 3) { 41 | console.error("Polygon " + (i+1) + " is invalid!"); 42 | return false; 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | //create array of edges of all polygons 49 | function edgify(fig1, fig2) { 50 | //create primary array from all edges 51 | var primEdges = getEdges(fig1).concat(getEdges(fig2)); 52 | var secEdges = []; 53 | //check every edge 54 | for(var i = 0; i < primEdges.length; i++) { 55 | var points = []; 56 | //for intersection with every edge except itself 57 | for(var j = 0; j < primEdges.length; j++) { 58 | if (i != j) { 59 | var interPoints = findEdgeIntersection(primEdges[i], primEdges[j]); 60 | addNewPoints(interPoints, points); 61 | } 62 | } 63 | //add start and end points to intersection points 64 | startPoint = primEdges[i][0]; 65 | startPoint.t = 0; 66 | endPoint = primEdges[i][1]; 67 | endPoint.t = 1; 68 | addNewPoints([startPoint, endPoint], points); 69 | //sort all points by position on edge 70 | points = sortPoints(points); 71 | //break edge to parts 72 | for (var k = 0; k < points.length - 1; k++) { 73 | var edge = [ 74 | { x: points[k].x, y: points[k].y }, 75 | { x: points[k+1].x, y: points[k+1].y} 76 | ]; 77 | // check for existanse in sec.array 78 | if (!edgeExists(edge, secEdges)) { 79 | //push if not exists 80 | secEdges.push(edge); 81 | } 82 | } 83 | } 84 | return secEdges; 85 | } 86 | 87 | function addNewPoints(newPoints, points) { 88 | if (newPoints.length > 0) { 89 | //check for uniqueness 90 | for (var k = 0; k < newPoints.length; k++) { 91 | if (!pointExists(newPoints[k], points)) { 92 | points.push(newPoints[k]); 93 | } 94 | } 95 | } 96 | } 97 | 98 | function sortPoints(points) { 99 | var p = points; 100 | p.sort((a,b) => { 101 | if (a.t > b.t) return 1; 102 | if (a.t < b.t) return -1; 103 | }); 104 | return p; 105 | } 106 | 107 | function getEdges(fig) { 108 | var edges = []; 109 | var len = fig.length; 110 | for (var i = 0; i < len; i++) { 111 | edges.push([ 112 | {x: fig[(i % len)].x, y: fig[(i % len)].y}, 113 | {x: fig[((i+1) % len)].x, y: fig[((i+1) % len)].y} 114 | ]); 115 | } 116 | return edges; 117 | } 118 | 119 | function findEdgeIntersection(edge1, edge2) { 120 | var x1 = edge1[0].x; 121 | var x2 = edge1[1].x; 122 | var x3 = edge2[0].x; 123 | var x4 = edge2[1].x; 124 | var y1 = edge1[0].y; 125 | var y2 = edge1[1].y; 126 | var y3 = edge2[0].y; 127 | var y4 = edge2[1].y; 128 | var nom1 = +(((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)).toFixed(9)); 129 | var nom2 = +(((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)).toFixed(9)); 130 | var denom = +(((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)).toFixed(9)); 131 | var t1 = nom1 / denom; 132 | var t2 = nom2 / denom; 133 | var interPoints = []; 134 | //1. lines are parallel or edges don't intersect 135 | if (((denom === 0) && (nom1 !== 0)) || (t1 <= 0) || (t1 >= 1) || (t2 < 0 ) || (t2 > 1)) { 136 | return interPoints; 137 | } 138 | //2. lines are collinear 139 | else if ((nom1 === 0) && (denom === 0)) { 140 | //check if endpoints of edge2 lies on edge1 141 | for (var i = 0; i < 2; i++) { 142 | var classify = classifyPoint(edge2[i], edge1); 143 | //find position of this endpoints relatively to edge1 144 | if (classify.loc == "ORIGIN" || classify.loc == "DESTINATION") { 145 | interPoints.push({x: edge2[i].x, y: edge2[i].y, t: classify.t}); 146 | } 147 | else if (classify.loc == "BETWEEN") { 148 | x = +((x1 + classify.t*(x2 - x1)).toFixed(9)); 149 | y = +((y1 + classify.t*(y2 - y1)).toFixed(9)); 150 | interPoints.push({x: x, y: y, t: classify.t}); 151 | } 152 | } 153 | return interPoints; 154 | } 155 | //3. edges intersect 156 | else { 157 | for (var i = 0; i < 2; i++) { 158 | var classify = classifyPoint(edge2[i], edge1); 159 | if (classify.loc == "ORIGIN" || classify.loc == "DESTINATION") { 160 | interPoints.push({x: edge2[i].x, y: edge2[i].y, t: classify.t}); 161 | } 162 | } 163 | if (interPoints.length > 0) { 164 | return interPoints; 165 | } 166 | var x = +((x1 + t1*(x2 - x1)).toFixed(9)); 167 | var y = +((y1 + t1*(y2 - y1)).toFixed(9)); 168 | interPoints.push({x: x, y: y, t: t1}); 169 | return interPoints; 170 | } 171 | } 172 | 173 | function classifyPoint(p, edge) { 174 | var ax = edge[1].x - edge[0].x; 175 | var ay = edge[1].y - edge[0].y; 176 | var bx = p.x - edge[0].x; 177 | var by = p.y - edge[0].y; 178 | var sa = ax * by - bx * ay; 179 | if ((p.x === edge[0].x) && (p.y === edge[0].y)) { 180 | return {loc: "ORIGIN", t: 0}; 181 | } 182 | if ((p.x === edge[1].x) && (p.y === edge[1].y)) { 183 | return {loc: "DESTINATION", t: 1}; 184 | } 185 | var theta = (polarAngle([edge[1], edge[0]]) - 186 | polarAngle([{x: edge[1].x, y: edge[1].y}, {x: p.x, y: p.y}])) % 360; 187 | if (theta < 0) { 188 | theta = theta + 360; 189 | } 190 | if (sa < -0.000000001) { 191 | return {loc: "LEFT", theta: theta}; 192 | } 193 | if (sa > 0.000000001) { 194 | return {loc: "RIGHT", theta: theta}; 195 | } 196 | if (((ax * bx) < 0) || ((ay * by) < 0)) { 197 | return {loc: "BEHIND", theta: theta}; 198 | } 199 | if ((Math.sqrt(ax * ax + ay * ay)) < (Math.sqrt(bx * bx + by * by))) { 200 | return {loc: "BEYOND", theta: theta}; 201 | } 202 | var t; 203 | if (ax !== 0) { 204 | t = bx/ax; 205 | } else { 206 | t = by/ay; 207 | } 208 | return {loc: "BETWEEN", t: t}; 209 | } 210 | 211 | function polarAngle(edge) { 212 | var dx = edge[1].x - edge[0].x; 213 | var dy = edge[1].y - edge[0].y; 214 | if ((dx === 0) && (dy === 0)) { 215 | //console.error("Edge has zero length."); 216 | return false; 217 | } 218 | if (dx === 0) { 219 | return ((dy > 0) ? 90 : 270); 220 | } 221 | if (dy === 0) { 222 | return ((dx > 0) ? 0 : 180); 223 | } 224 | var theta = Math.atan(dy/dx)*360/(2*Math.PI); 225 | if (dx > 0) { 226 | return ((dy >= 0) ? theta : theta + 360); 227 | } else { 228 | return (theta + 180); 229 | } 230 | } 231 | 232 | function pointExists(p, points) { 233 | if (points.length === 0) { 234 | return false; 235 | } 236 | for (var i = 0; i < points.length; i++) { 237 | if ((p.x === points[i].x) && (p.y === points[i].y)) { 238 | return true; 239 | } 240 | } 241 | return false; 242 | } 243 | 244 | function edgeExists(e, edges) { 245 | if (edges.length === 0) { 246 | return false; 247 | } 248 | for (var i = 0; i < edges.length; i++) { 249 | if (equalEdges(e, edges[i])) 250 | return true; 251 | } 252 | return false; 253 | } 254 | 255 | function equalEdges(edge1, edge2) { 256 | if (((edge1[0].x === edge2[0].x) && 257 | (edge1[0].y === edge2[0].y) && 258 | (edge1[1].x === edge2[1].x) && 259 | (edge1[1].y === edge2[1].y)) || ( 260 | (edge1[0].x === edge2[1].x) && 261 | (edge1[0].y === edge2[1].y) && 262 | (edge1[1].x === edge2[0].x) && 263 | (edge1[1].y === edge2[0].y))) { 264 | return true; 265 | } else { 266 | return false; 267 | } 268 | } 269 | 270 | function polygonate(edges) { 271 | var polygons = []; 272 | var polygon = []; 273 | var len = edges.length; 274 | var midpoints = getMidpoints(edges); 275 | //start from every edge and create non-selfintersecting polygons 276 | for (var i = 0; i < len - 2; i++) { 277 | var org = {x: edges[i][0].x, y: edges[i][0].y}; 278 | var dest = {x: edges[i][1].x, y: edges[i][1].y}; 279 | var currentEdge = i; 280 | var point; 281 | var p; 282 | var direction; 283 | var stop; 284 | //while we havn't come to the starting edge again 285 | for (direction = 0; direction < 2; direction++) { 286 | polygon = []; 287 | stop = false; 288 | while ((polygon.length === 0) || (!stop)) { 289 | //add point to polygon 290 | polygon.push({x: org.x, y: org.y}); 291 | point = undefined; 292 | //look for edge connected with end of current edge 293 | for (var j = 0; j < len; j++) { 294 | p = undefined; 295 | //except itself 296 | if (!equalEdges(edges[j], edges[currentEdge])) { 297 | //if some edge is connected to current edge in one endpoint 298 | if ((edges[j][0].x === dest.x) && (edges[j][0].y === dest.y)) { 299 | p = edges[j][1]; 300 | } 301 | if ((edges[j][1].x === dest.x) && (edges[j][1].y === dest.y)) { 302 | p = edges[j][0]; 303 | } 304 | //compare it with last found connected edge for minimum angle between itself and current edge 305 | if (p) { 306 | var classify = classifyPoint(p, [org, dest]); 307 | //if this edge has smaller theta then last found edge update data of next edge of polygon 308 | if (!point || 309 | ((classify.theta < point.theta) && (direction === 0)) || 310 | ((classify.theta > point.theta) && (direction === 1))) { 311 | point = {x: p.x, y: p.y, theta: classify.theta, edge: j}; 312 | } 313 | } 314 | } 315 | } 316 | //change current edge to next edge 317 | org.x = dest.x; 318 | org.y = dest.y; 319 | dest.x = point.x; 320 | dest.y = point.y; 321 | currentEdge = point.edge; 322 | //if we reach start edge 323 | if (equalEdges([org, dest], edges[i])) { 324 | stop = true; 325 | //check polygon for correctness 326 | /*for (var k = 0; k < allPoints.length; k++) { 327 | //if some point is inside polygon it is incorrect 328 | if ((!pointExists(allPoints[k], polygon)) && (findPointInsidePolygon(allPoints[k], polygon))) { 329 | polygon = false; 330 | } 331 | }*/ 332 | for (k = 0; k < midpoints.length; k++) { 333 | //if some midpoint is inside polygon (edge inside polygon) it is incorrect 334 | if (findPointInsidePolygon(midpoints[k], polygon)) { 335 | polygon = false; 336 | } 337 | } 338 | } 339 | } 340 | //add created polygon if it is correct and was not found before 341 | if (polygon && !polygonExists(polygon, polygons)) { 342 | polygons.push(polygon); 343 | } 344 | } 345 | } 346 | //console.log("polygonate: " + JSON.stringify(polygons)); 347 | return polygons; 348 | } 349 | 350 | function polygonExists(polygon, polygons) { 351 | //if array is empty element doesn't exist in it 352 | if (polygons.length === 0) return false; 353 | //check every polygon in array 354 | for (var i = 0; i < polygons.length; i++) { 355 | //if lengths are not same go to next element 356 | if (polygon.length !== polygons[i].length) continue; 357 | //if length are same need to check 358 | else { 359 | //if all the points are same 360 | for (var j = 0; j < polygon.length; j++) { 361 | //if point is not found break forloop and go to next element 362 | if (!pointExists(polygon[j], polygons[i])) break; 363 | //if point found 364 | else { 365 | //and it is last point in polygon we found polygon in array! 366 | if (j === polygon.length - 1) return true; 367 | } 368 | } 369 | } 370 | } 371 | return false; 372 | } 373 | 374 | function filterPolygons(polygons, fig1, fig2, mode) { 375 | var filtered = []; 376 | var c1, c2; 377 | var point; 378 | var bigPolygons = removeSmallPolygons(polygons, 0.0001); 379 | for(var i = 0; i < bigPolygons.length; i++) { 380 | point = getPointInsidePolygon(bigPolygons[i]); 381 | c1 = findPointInsidePolygon(point, fig1); 382 | c2 = findPointInsidePolygon(point, fig2); 383 | if ( 384 | ((mode === "intersect") && c1 && c2) || //intersection 385 | ((mode === "cut1") && c1 && !c2) || //fig1 - fig2 386 | ((mode === "cut2") && !c1 && c2) || //fig2 - fig1 387 | ((mode === "sum") && (c1 || c2))) { //fig1 + fig2 388 | filtered.push(bigPolygons[i]); 389 | } 390 | } 391 | //console.log("filtered: " + JSON.stringify(filtered)); 392 | return filtered; 393 | } 394 | 395 | function removeSmallPolygons(polygons, minSize) { 396 | var big = []; 397 | for (var i = 0; i < polygons.length; i++) { 398 | if (polygonArea(polygons[i]) >= minSize) { 399 | big.push(polygons[i]); 400 | } 401 | } 402 | return big; 403 | } 404 | 405 | function polygonArea(p) { 406 | var len = p.length; 407 | var s = 0; 408 | for (var i = 0; i < len; i++) { 409 | s += (p[i % len].x * p[(i + 1) % len].y) - (p[i % len].y * 410 | p[(i + 1) % len].x); 411 | } 412 | return Math.abs(s/2); 413 | } 414 | 415 | function getPointInsidePolygon(polygon) { 416 | var point; 417 | var size = getSize(polygon); 418 | var edges = getEdges(polygon); 419 | var y = size.y.min + (size.y.max - size.y.min) / Math.PI; 420 | var dy = (size.y.max - size.y.min) / 13; 421 | var line = []; 422 | var points; 423 | var interPoints = []; 424 | var pointsOK = false; 425 | while (!pointsOK) { 426 | line = [{x: (size.x.min - 1), y: y},{x: (size.x.max + 1), y: y}]; 427 | //find intersections with all polygon edges 428 | for (var i = 0; i < edges.length; i++) { 429 | points = findEdgeIntersection(line, edges[i]); 430 | //if edge doesn't lie inside line 431 | if (points && (points.length === 1)) { 432 | interPoints.push(points[0]); 433 | } 434 | } 435 | interPoints = sortPoints(interPoints); 436 | //find two correct interpoints 437 | for (var i = 0; i < interPoints.length - 1; i++) { 438 | if (interPoints[i].t !== interPoints[i+1].t) { 439 | //enable exit from loop and calculate point coordinates 440 | pointsOK = true; 441 | point = {x: ((interPoints[i].x + interPoints[i+1].x) / 2), y: y}; 442 | } 443 | } 444 | //all points are incorrect, need to change line parameters 445 | y = y + dy; 446 | if (((y > size.y.max) || (y < size.y.min)) && (pointsOK === false)) { 447 | pointsOK = true; 448 | point = undefined; 449 | } 450 | } 451 | return point; 452 | } 453 | 454 | function getSize(polygon) { 455 | var size = { 456 | x: { 457 | min: polygon[0].x, 458 | max: polygon[0].x 459 | }, 460 | y: { 461 | min: polygon[0].y, 462 | max: polygon[0].y 463 | } 464 | }; 465 | for (var i = 1; i < polygon.length; i++) { 466 | if (polygon[i].x < size.x.min) size.x.min = polygon[i].x; 467 | if (polygon[i].x > size.x.max) size.x.max = polygon[i].x; 468 | if (polygon[i].y < size.y.min) size.y.min = polygon[i].y; 469 | if (polygon[i].y > size.y.max) size.y.max = polygon[i].y; 470 | } 471 | return size; 472 | } 473 | 474 | function findPointInsidePolygon(point, polygon) { 475 | var cross = 0; 476 | var edges = getEdges(polygon); 477 | var classify; 478 | var org, dest; 479 | for (var i = 0; i < edges.length; i++) { 480 | [org, dest] = edges[i]; 481 | classify = classifyPoint(point, [org, dest]); 482 | if ( ( 483 | (classify.loc === "RIGHT") && 484 | (org.y < point.y) && 485 | (dest.y >= point.y) 486 | ) || 487 | ( 488 | (classify.loc === "LEFT") && 489 | (org.y >= point.y) && 490 | (dest.y < point.y) 491 | ) 492 | ) { 493 | cross++; 494 | } 495 | if (classify.loc === "BETWEEN") return false; 496 | } 497 | if (cross % 2) { 498 | return true; 499 | } else { 500 | return false; 501 | } 502 | } 503 | 504 | function getMidpoints(edges) { 505 | var midpoints = []; 506 | var x, y; 507 | for (var i = 0; i < edges.length; i++) { 508 | x = (edges[i][0].x + edges[i][1].x) / 2; 509 | y = (edges[i][0].y + edges[i][1].y) / 2; 510 | classify = classifyPoint({x: x, y:y}, edges[i]); 511 | if (classify.loc != "BETWEEN") { 512 | console.error("Midpoint calculation error"); 513 | } 514 | midpoints.push({x: x, y: y}); 515 | } 516 | return midpoints; 517 | } 518 | 519 | function log(obj) { 520 | console.log(JSON.stringify(obj)); 521 | } 522 | 523 | 524 | // fig1 = [ 525 | // { x: 5.35328472172063, y: 3.3464605876540254 }, 526 | // { x: 31.10025450900146, y: 3.3464605876540254 }, 527 | // { x: 31.10025450900146, y: 38.65353941234598 }, 528 | // { x: 5.35328472172063, y: 38.65353941234598 } 529 | // ]; 530 | // fig2 = [ 531 | // { x: 31.10025450900146, y: 6.964961212615723 }, 532 | // { x: 5.35328472172063, y: 3.3464605876540254 }, 533 | // { x: 26.64671527827937, y: 38.65353941234598 } 534 | // ]; 535 | 536 | // default test 537 | // fig1 = [ 538 | // { x: 100, y: 200 }, 539 | // { x: 300, y: 150 }, 540 | // { x: 300, y: 250 } 541 | // ]; 542 | 543 | // fig2 = [ 544 | // { x: 200, y: 100 }, 545 | // { x: 200, y: 300 }, 546 | // { x: 350, y: 300 }, 547 | // { x: 350, y: 100 } 548 | // ]; 549 | 550 | // console.log("DEBUG STARTED"); 551 | // var result = intersect(fig1, fig2); 552 | // log(result); 553 | // console.log("DEBUG STOPPED"); 554 | -------------------------------------------------------------------------------- /test.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .container { 7 | border: solid lightgray 3px; 8 | width: 500px; 9 | height: 500px; 10 | position: absolute; 11 | right: 5%; 12 | top: 5%; 13 | 14 | } 15 | 16 | .container svg { 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | width: 100%; 21 | height: 100%; 22 | } 23 | 24 | svg.base polygon { 25 | stroke: black; 26 | stroke-width: 2px; 27 | opacity: 0.5; 28 | fill-rule:evenodd; 29 | } 30 | 31 | @keyframes blink { 32 | 0% { opacity: 0; } 33 | 50% { opacity: 1; } 34 | 100% { opacity: 0; } 35 | } 36 | 37 | svg.intersections { 38 | animation-name: blink; 39 | animation-duration: 1s; 40 | animation-iteration-count: infinite; 41 | } 42 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Tests | Intersections in JS 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 | 44 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | //default test 2 | fig1 = [ 3 | { x: 100, y: 200 }, 4 | { x: 300, y: 150 }, 5 | { x: 300, y: 250 } 6 | ]; 7 | 8 | fig2 = [ 9 | { x: 200, y: 100 }, 10 | { x: 200, y: 300 }, 11 | { x: 350, y: 300 }, 12 | { x: 350, y: 100 } 13 | ]; 14 | 15 | //test #1 from @HatScripts 16 | // fig1 = [ 17 | // { x: 5.35328472172063, y: 3.3464605876540254 }, 18 | // { x: 31.10025450900146, y: 3.3464605876540254 }, 19 | // { x: 31.10025450900146, y: 38.65353941234598 }, 20 | // { x: 5.35328472172063, y: 38.65353941234598 } 21 | // ]; 22 | // fig2 = [ 23 | // { x: 31.10025450900146, y: 6.964961212615723 }, 24 | // { x: 5.35328472172063, y: 3.3464605876540254 }, 25 | // { x: 26.64671527827937, y: 38.65353941234598 } 26 | // ]; 27 | 28 | //test #2 from @HatScripts 29 | // fig1 = [ 30 | // { x: 10.143593539448977, y: 18.143593539448986 }, 31 | // { x: 37.856406460551014, y: 18.143593539448986 }, 32 | // { x: 37.856406460551014, y: 45.856406460551014 }, 33 | // { x: 10.143593539448977, y: 45.856406460551014 } 34 | // ]; 35 | // fig2 = [ 36 | // { x: 37.856406460551014, y: 18.14359353944898 }, 37 | // { x: 10.143593539448977, y: 2.143593539448986 }, 38 | // { x: 21.85640646055102, y: 45.856406460551014 } 39 | // ]; 40 | 41 | //test #1 from @Skeptron 42 | // var fig1 = [ 43 | // {x: 1000, y: 800}, 44 | // {x: 800, y: 500}, 45 | // {x: 600, y: 500}, 46 | // {x: 474, y: 800}, 47 | // {x: 1000, y: 800} 48 | // ]; 49 | // var fig2 = [ 50 | // {x: 1000, y: 400}, 51 | // {x: 400, y: 200}, 52 | // {x: 200, y: 200}, 53 | // {x: 200, y: 300}, 54 | // {x: 449, y: 800}, 55 | // {x: 1000, y: 800} 56 | // ]; 57 | 58 | //test #2 from @Skeptron 59 | // fig1 = [ 60 | // {x: 926, y: 0}, 61 | // {x: 600, y: 500}, 62 | // {x: 600, y: 600}, 63 | // {x: 799, y: 600}, 64 | // {x: 1000, y: 559}, 65 | // {x: 1000, y: 0} 66 | // ]; 67 | // fig2 = [ 68 | // {x: 1000, y: 400}, 69 | // {x: 400, y: 200}, 70 | // {x: 200, y: 200}, 71 | // {x: 200, y: 300}, 72 | // {x: 449, y: 800}, 73 | // {x: 1000, y: 800} 74 | // ]; 75 | 76 | //test from @clemosm 77 | // fig1 = [ 78 | // {x:383.11684890961,y:822.2939549877}, 79 | // {x:383.08019699532,y:824.4200341334}, 80 | // {x:381.523363302535,y:824.3935903418}, 81 | // {x:381.46925809573,y:827.5166057295}, 82 | // {x:379.9211510492,y:827.4901618769}, 83 | // {x:378.91933205856,y:827.4742955655}, 84 | // {x:379.01183450892,y:822.2278456159}, 85 | // {x:380.21087570504,y:822.2463562398}, 86 | // {x:380.18120510776,y:823.9942891567}, 87 | // {x:380.844430223515,y:824.0075110482}, 88 | // {x:380.874100820794,y:822.2542893642} 89 | // ]; 90 | // fig2 = [ 91 | // {x:383.08019699532,y:824.4200341334}, 92 | // {x:381.523363302535,y:824.3935903418}, 93 | // {x:380.823486272486,y:824.3909459626}, 94 | // {x:380.844430223515,y:824.0075110482}, 95 | // {x:380.874100820794,y:822.2542893642}, 96 | // {x:383.11684890961,y:822.2939549877} 97 | // ]; 98 | 99 | //test from @prendradjaja 100 | // fig1 = [ 101 | // {"x":1,"y":0}, 102 | // {"x":1.0000000000000002,"y":1}, 103 | // {"x":0.29289321881345287,"y":1.7071067811865477}, 104 | // {"x":0.2928932188134526,"y":0.7071067811865477} 105 | // ]; 106 | // fig2 = [ 107 | // {"x":0.29289321881345254,"y":0.7071067811865476}, 108 | // {"x":1.0000000000000002,"y":1.414213562373095}, 109 | // {"x":2.220446049250313e-16,"y":1.4142135623730951}, 110 | // {"x":-0.7071067811865476,"y":0.7071067811865479} 111 | // ]; 112 | 113 | //second test from @prendradjaja 114 | // fig1 = [ 115 | // {"x":99.99999999999999,"y":0}, 116 | // {"x":200,"y":-1.2246467991473532e-14}, 117 | // {"x":200,"y":99.99999999999999}, 118 | // {"x":100,"y":100.00000000000003} 119 | // ]; 120 | // fig2 = [ 121 | // {"x":0,"y":0}, 122 | // {"x":200,"y":0}, 123 | // {"x":270.71067811865476,"y":70.71067811865476}, 124 | // {"x":270.71067811865476,"y":270.71067811865476}, 125 | // {"x":200,"y":341.4213562373095}, 126 | // {"x":0,"y":341.4213562373095}, 127 | // {"x":-70.71067811865476,"y":270.71067811865476}, 128 | // {"x":-70.71067811865476,"y":70.71067811865474} 129 | // ]; 130 | 131 | // Test for issue #8 from @northernstream 132 | // fig1=[ 133 | // { x: 10, y: 0 }, 134 | // { x: -10, y: 1.2246467991473533e-15 }, 135 | // { x: -10, y: 1.2246467991473533e-15 }, 136 | // { x: -9.92546151641322, y: 1.2186934340514755 }, 137 | // { x: -9.702957262759965, y: 2.4192189559966772 }, 138 | // { x: -9.335804264972015, y: 3.5836794954530067 }, 139 | // { x: -8.82947592858927, y: 4.694715627858907 }, 140 | // { x: -8.191520442889919, y: 5.73576436351046 }, 141 | // { x: -7.431448254773944, y: 6.691306063588581 }, 142 | // { x: -6.560590289905072, y: 7.547095802227721 }, 143 | // { x: -5.591929034707467, y: 8.290375725550417 }, 144 | // { x: -4.5399049973954675, y: 8.910065241883679 }, 145 | // { x: -3.420201433256687, y: 9.396926207859085 }, 146 | // { x: -2.249510543438648, y: 9.743700647852352 }, 147 | // { x: -1.0452846326765333, y: 9.945218953682733 }, 148 | // { x: 0.174524064372836, y: 9.998476951563912 }, 149 | // { x: 1.3917310096006545, y: 9.902680687415703 }, 150 | // { x: 2.5881904510252074, y: 9.659258262890683 }, 151 | // { x: 3.746065934159122, y: 9.271838545667872 }, 152 | // { x: 4.848096202463371, y: 8.746197071393958 }, 153 | // { x: 5.877852522924732, y: 8.090169943749475 }, 154 | // { x: 6.819983600624985, y: 7.313537016191704 }, 155 | // { x: 7.66044443118978, y: 6.4278760968653925 }, 156 | // { x: 8.386705679454241, y: 5.44639035015027 }, 157 | // { x: 8.98794046299167, y: 4.383711467890774 }, 158 | // { x: 9.455185755993169, y: 3.255681544571567 }, 159 | // { x: 9.781476007338057, y: 2.079116908177593 }, 160 | // { x: 9.961946980917455, y: 0.8715574274765816 }, 161 | // { x: 10, y: 0 } 162 | // ]; 163 | // fig2=[ 164 | // { x: -3.4641016151399997, y: 3.6399999999999997 }, 165 | // { x: -2.7767585183349683, y: 3.5979602952570557 }, 166 | // { x: -2.099662123957873, y: 3.47246789619662 }, 167 | // { x: -1.4429063797045063, y: 3.265393605444218 }, 168 | // { x: -0.816282001027576, y: 2.979824423724347 }, 169 | // { x: -0.22913051412010077, y: 2.620017529789913 }, 170 | // { x: 0.30979500472396015, y: 2.1913368156925035 }, 171 | // { x: 0.7924604173164336, y: 1.70017292350646 }, 172 | // { x: 1.211670294070434, y: 1.1538479755750122 }, 173 | // { x: 1.5611751812823944, y: 0.5605064185310433 }, 174 | // { x: 1.8357647660925234, y: -0.07100639164322864 }, 175 | // { x: 2.031345550248726, y: -0.7312760535006015 }, 176 | // { x: 2.1450018747370607, y: -1.4104594671704347 }, 177 | // { x: 2.1750393855420467, y: -2.0984315723062843 }, 178 | // { x: 2.1210102925624557, y: -2.7849362894147713 }, 179 | // { x: 1.9837200451303452, y: -3.4597394143782165 }, 180 | // { x: 1.7652153246166802, y: -4.112781186865746 }, 181 | // { x: 1.468753533126192, y: -4.7343262581893395 }, 182 | // { x: 1.0987542331347027, y: -5.315108822929549 }, 183 | // { x: 0.6607332619921222, y: -5.84647075075249 }, 184 | // { x: 0.16122050349208106, y: -6.320490659191036 }, 185 | // { x: -0.3923374576552501, y: -6.730102003212193 }, 186 | // { x: -0.9916883472496036, y: -7.069198421127302 }, 187 | // { x: -1.6278972240016387, y: -7.332724766380148 }, 188 | // { x: -2.2914796789278418, y: -7.516752468138664 }, 189 | // { x: -2.97254322604321, y: -7.618538097237445 }, 190 | // { x: -3.6609347765421094, y: -7.636564264387699 }, 191 | // { x: -4.3463919979669035, y: -7.570562240956576 }, 192 | // { x: -5.018696301947874, y: -7.4215159650921185 }, 193 | // { x: -5.667825179819504, y: -7.1916473734717625 }, 194 | // { x: -6.284101615140003, y: -6.884383277344233 }, 195 | // { x: -6.858338345717552, y: -6.504304276666732 }, 196 | // { x: -7.381974824528745, y: -6.057076473909991 }, 197 | // { x: -7.847204837757316, y: -5.549367005521084 }, 198 | // { x: -8.247092877462244, y: -4.988744650275275 }, 199 | // { x: -8.575677534026706, y: -4.383566996217544 }, 200 | // { x: -8.828060367044666, y: -3.742855848274705 }, 201 | // { x: -9.000478929784824, y: -3.076162733923712 }, 202 | // { x: -9.09036285860541, y: -2.393426511916864 }, 203 | // { x: -9.096372191155796, y: -1.7048252067897969 }, 204 | // { x: -9.018417342128853, y: -1.0206242779585133 }, 205 | // { x: -8.85766043877152, y: -0.3510235853637631 }, 206 | // { x: -8.616497996244268, y: 0.2939946669475142 }, 207 | // { x: -8.298525191099912, y: 0.9048147424927064 }, 208 | // { x: -7.908482265481911, y: 1.4723307208367133 }, 209 | // { x: -7.452183861032127, y: 1.988082245892128 }, 210 | // { x: -6.936432335976713, y: 2.4443806503419117 }, 211 | // { x: -6.368916357632706, y: 2.8344235759599137 }, 212 | // { x: -5.758096282087513, y: 3.1523963811042695 }, 213 | // { x: -5.113078029776235, y: 3.39355882363152 }, 214 | // { x: -4.443477337181487, y: 3.554315726988853 }, 215 | // { x: -3.7592764083502024, y: 3.632270576015796 }, 216 | // { x: -3.4641016151399997, y: 3.6399999999999997 } 217 | // ]; 218 | 219 | // Test for issue #12 from @ptarabbia 220 | // fig1 = [ 221 | // {"x": 555.185880441, "y": 1160.301884319}, 222 | // {"x": 545.182378187, "y": 934.767230421}, 223 | // {"x": 817.285926289, "y": 966.451869566} 224 | // ]; 225 | 226 | // fig2 = [ 227 | // {"x": 965.05163647, "y": 834.497760783}, 228 | // {"x": 838.848023757, "y": 968.962631672}, 229 | // {"x": 545.182378187, "y": 934.767230421} 230 | // ]; 231 | 232 | 233 | edges = [ 234 | [ 235 | { x: 100, y: 200 }, 236 | { x: 300, y: 150 } 237 | ], 238 | [ 239 | { x: 300, y: 150 }, 240 | { x: 300, y: 250 } 241 | ] 242 | ]; 243 | 244 | polygons = [ 245 | [ 246 | {x: 100, y: 200}, 247 | {x: 200, y: 175}, 248 | {x: 200, y: 225} 249 | ], 250 | [ 251 | {x: 200, y: 175}, 252 | {x: 300, y: 150}, 253 | {x: 300, y: 250}, 254 | {x: 200, y: 225} 255 | ], 256 | [ 257 | {x: 200, y: 175}, 258 | {x: 300, y: 150}, 259 | {x: 300, y: 250}, 260 | {x: 200, y: 225}, 261 | {x: 200, y: 300}, 262 | {x: 350, y: 300}, 263 | {x: 350, y: 100}, 264 | {x: 200, y: 100} 265 | ] 266 | ]; 267 | 268 | small = [ 269 | {x: 0, y: 0}, 270 | {x: 1, y: 0}, 271 | {x: 1, y: 1} 272 | ]; 273 | 274 | mesh = [ 275 | [ 276 | {x: 100, y: 200}, 277 | {x: 200, y: 175} 278 | ], 279 | [ 280 | {x: 200, y: 175}, 281 | {x: 300, y: 150} 282 | ], 283 | [ 284 | {x: 300, y: 150}, 285 | {x: 300, y: 250} 286 | ], 287 | [ 288 | {x: 300, y: 250}, 289 | {x: 200, y: 225} 290 | ], 291 | [ 292 | {x: 200, y: 225}, 293 | {x: 100, y: 200} 294 | ], 295 | [ 296 | {x: 200, y: 100}, 297 | {x: 200, y: 175} 298 | ], 299 | [ 300 | {x: 200, y: 175}, 301 | {x: 200, y: 225} 302 | ], 303 | [ 304 | {x: 200, y: 225}, 305 | {x: 200, y: 300} 306 | ], 307 | [ 308 | {x: 200, y: 300}, 309 | {x: 350, y: 300} 310 | ], 311 | [ 312 | {x: 350, y: 300}, 313 | {x: 350, y: 100} 314 | ], 315 | [ 316 | {x: 350, y: 100}, 317 | {x: 200, y: 100} 318 | ] 319 | ]; 320 | 321 | 322 | describe("intersect", function() { 323 | it("returns polygons intersection", function() { 324 | assert.deepEqual(intersect(fig1, fig2), [polygons[1]]); 325 | }); 326 | }); 327 | 328 | describe("edgify", function() { 329 | it("returns edges and points from polygons", function() { 330 | assert.deepEqual(edgify(fig1, fig2), mesh); 331 | }); 332 | }); 333 | 334 | describe("polygonate", function() { 335 | it("returns nonintersecting polygons from edges", function() { 336 | assert.deepEqual(polygonate(mesh), polygons); 337 | }); 338 | }); 339 | 340 | describe("filterPolygons", function() { 341 | it("returns polygons that satisfy condition: intersect", function() { 342 | assert.deepEqual(filterPolygons(polygons, fig1, fig2, "intersect"), [polygons[1]]); 343 | }); 344 | it("returns polygons that satisfy condition: cut1", function() { 345 | assert.deepEqual(filterPolygons(polygons, fig1, fig2, "cut1"), [polygons[0]]); 346 | }); 347 | it("returns polygons that satisfy condition: cut2", function() { 348 | assert.deepEqual(filterPolygons(polygons, fig1, fig2, "cut2"), [polygons[2]]); 349 | }); 350 | it("returns polygons that satisfy condition: sum", function() { 351 | assert.deepEqual(filterPolygons(polygons, fig1, fig2, "sum"), polygons); 352 | }); 353 | }); 354 | 355 | describe("polygonArea", function() { 356 | it("returnes polygon area", function() { 357 | assert.deepEqual(polygonArea(small), 0.5); 358 | }); 359 | }); 360 | 361 | describe("removeSmallPolygons", function() { 362 | it("returnes array without small polygons", function() { 363 | assert.deepEqual(removeSmallPolygons(polygons.concat(small), 0.00000001), polygons); 364 | }); 365 | }); 366 | 367 | describe("sortPoints", function() { 368 | it("returns array of points sorted by t value", function() { 369 | assert.deepEqual(sortPoints([{x: 1, y: 2, t: 3}, {x: 4, y: 5, t: 1}, {x: 6, y: 7, t: -1}]), 370 | [{x: 6, y: 7, t: -1}, {x: 4, y: 5, t: 1}, {x: 1, y: 2, t: 3}]); 371 | }); 372 | }); 373 | 374 | describe("findPointInsidePolygon", function() { 375 | it("returns true if point is inside polygon", function() { 376 | assert.deepEqual(findPointInsidePolygon({x: 200, y: 200}, fig1), true); 377 | }); 378 | it("returns false if point is outside polygon", function() { 379 | assert.deepEqual(findPointInsidePolygon({x: 50, y: 200}, fig1), false); 380 | }); 381 | }); 382 | 383 | describe("getSize", function() { 384 | it("returns size of polygon", function() { 385 | assert.deepEqual(getSize(fig1), {x: {min: 100, max: 300}, y: {min: 150, max: 250}}); 386 | }); 387 | }); 388 | 389 | // describe("getPointInsidePolygon", function() { 390 | 391 | // it("returns point located inside polygon 1", function() { 392 | // assert.deepEqual(getPointInsidePolygon(fig1), {x: 200, y: 200}); 393 | // }); 394 | // it("returns point located inside polygon 2", function() { 395 | // assert.deepEqual(getPointInsidePolygon(fig2), {x: 275, y: 200}); 396 | // }); 397 | // }); 398 | 399 | describe("checkPolygons", function() { 400 | it("returns true if polygons are valid", function() { 401 | assert.equal(checkPolygons(fig1, fig2), true); 402 | }); 403 | it("returns false if first polygon is invalid", function() { 404 | assert.equal(checkPolygons(fig1.slice(0, 2), fig2), false); 405 | }); 406 | it("returns false if second polygon is invalid", function() { 407 | assert.equal(checkPolygons(fig1, fig2.slice(0, 2)), false); 408 | }); 409 | }); 410 | 411 | describe("getEdges", function() { 412 | it("returns edges from polygon specified with points", function() { 413 | assert.deepEqual(getEdges(fig1), [ 414 | [fig1[0], fig1[1]], 415 | [fig1[1], fig1[2]], 416 | [fig1[2], fig1[0]] 417 | ]); 418 | }); 419 | }); 420 | 421 | describe("pointExists", function() { 422 | it("returns true if point exists in array", function() { 423 | assert.deepEqual(pointExists(fig1[0], fig1), true); 424 | }); 425 | it("returns false if point doesn't exist in array", function() { 426 | assert.deepEqual(pointExists(fig2[0], fig1), false); 427 | }); 428 | }); 429 | 430 | describe("edgeExists", function() { 431 | it("returns true if edge exists in array", function() { 432 | assert.deepEqual(edgeExists(edges[0], edges), true); 433 | }); 434 | it("returns false if edge doesn't exist in array", function() { 435 | assert.deepEqual(edgeExists([{x: 110, y: 220}, {x: 280, y: 180}], edges), false); 436 | }); 437 | }); 438 | 439 | describe("equalEdges", function() { 440 | it("returns true if edges have same points", function() { 441 | assert.deepEqual(equalEdges(edges[0], edges[0]), true); 442 | }); 443 | it("returns false if edges don't have same points", function() { 444 | assert.deepEqual(equalEdges(edges[0], edges[1]), false); 445 | }); 446 | }); 447 | 448 | describe("findEdgeIntersection", function() { 449 | it("returns false if edges are parallel", function() { 450 | assert.equal(findEdgeIntersection([{x: 3, y: 3}, {x: 5, y: 5}], [{x: 3, y: 4}, {x: 5, y: 6}]), false); 451 | }); 452 | it("returns false if edges don't intersect", function() { 453 | assert.equal(findEdgeIntersection([{x: 3, y: 3}, {x: 5, y: 5}], [{x: 1, y: 7}, {x: 3, y: 5}]), false); 454 | }); 455 | it("returns false if edges are collinear and don't intersect", function() { 456 | assert.equal(findEdgeIntersection([{x: 3, y: 3}, {x: 5, y: 5}], [{x: 6, y: 6}, {x: 8, y: 8}]), false); 457 | }); 458 | it("returns point if edges are collinear and intersect at one endpoint", function() { 459 | assert.deepEqual(findEdgeIntersection([{x: 3, y: 3}, {x: 5, y: 5}], [{x: 4, y: 4}, {x: 6, y: 6}]), 460 | [{x: 4, y: 4, t: 0.5}]); 461 | }); 462 | it("returns two points if edges are collinear and intersect at two endpoints", function() { 463 | assert.deepEqual(findEdgeIntersection([{x: 3, y: 3}, {x: 7, y: 7}], [{x: 4, y: 4}, {x: 6, y: 6}]), 464 | [{x: 4, y: 4, t: 0.25}, {x: 6, y: 6, t: 0.75}]); 465 | }); 466 | it("returns point if edges intersect", function() { 467 | assert.deepEqual(findEdgeIntersection([{x: 3, y: 3}, {x: 5, y: 5}], [{x: 3, y: 5}, {x: 5, y: 3}]), 468 | [{x: 4, y: 4, t: 0.5}]); 469 | }); 470 | }); 471 | 472 | describe("classifyPoint", function() { 473 | it("point is at right", function() { 474 | assert.deepEqual(classifyPoint({x: 5, y: 3}, [{x: 3, y: 3}, {x: 5, y: 5}]), {loc: "LEFT", theta: 315}); 475 | }); 476 | it("point is at left", function() { 477 | assert.deepEqual(classifyPoint({x: 3, y: 5}, [{x: 3, y: 3}, {x: 5, y: 5}]), {loc: "RIGHT", theta: 45}); 478 | }); 479 | it("point is beyond", function() { 480 | assert.deepEqual(classifyPoint({x: 6, y: 6}, [{x: 3, y: 3}, {x: 5, y: 5}]), {loc: "BEYOND", theta: 180}); 481 | }); 482 | it("point is between", function() { 483 | assert.deepEqual(classifyPoint({x: 4, y: 4}, [{x: 3, y: 3}, {x: 5, y: 5}]), {loc: "BETWEEN", t: 0.5}); 484 | }); 485 | }); 486 | 487 | describe("getMidpoints", function() { 488 | it("returns midpoints of edges", function() { 489 | assert.deepEqual(getMidpoints([[{x: 3, y: 3}, {x: 5, y: 5}], [{x: -1, y: 2}, {x: 0, y: 4}]]), 490 | [{x: 4, y: 4}, {x: -0.5, y: 3}]); 491 | }); 492 | }); 493 | 494 | describe("polarAngle", function() { 495 | 496 | it("returns false if edge has zero length", function() { 497 | assert.equal(polarAngle([{x: 3, y: 3}, {x: 3, y: 3}]), false); 498 | }); 499 | 500 | it("returns 45 if angle is 45", function() { 501 | assert.equal(polarAngle([{x: 3, y: 3}, {x: 5, y: 5}]), 45); 502 | }); 503 | 504 | it("returns 315 if angle is 315", function() { 505 | assert.equal(polarAngle([{x: 0, y: 0}, {x: 5, y: -5}]), 315); 506 | }); 507 | 508 | }); 509 | 510 | describe("polygonExists", function() { 511 | it("returns false if polygons array is empty", function() { 512 | assert.equal(polygonExists([fig2[0], fig1[1], fig1[2]], []), false); 513 | }); 514 | it("returns false if polygon doesn't exist in array", function() { 515 | assert.equal(polygonExists([fig2[0], fig1[1], fig1[2]], [fig1, fig2]), false); 516 | }); 517 | it("returns true if polygon existsin array", function() { 518 | assert.equal(polygonExists([fig1[0], fig1[1], fig1[2]], [fig1, fig2]), true); 519 | }); 520 | }); --------------------------------------------------------------------------------