├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── TriVis.ts ├── example.png ├── package-lock.json ├── package.json ├── rollup.config.js ├── strain.png ├── test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | # Optional npm cache directory 4 | .npm 5 | coverage 6 | 7 | # Generated by rollup 8 | lib/ 9 | test/ 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "delaunaytests"] 2 | path = delaunaytests 3 | url = https://github.com/kninnug/delaunaytests.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright 2021, Marco Gunnink 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose 6 | with or without fee is hereby granted, provided that the above copyright notice 7 | and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 15 | THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TriVis 2 | ====== 3 | 4 | Compute visibility polygons by Triangular Expansion. 5 | 6 | ![Visbility polygon example](example.png) 7 | 8 | Example 9 | ------- 10 | 11 | // Points to be triangulated 12 | const points = [[53,98],[5,201],[194,288],[280,195],[392,148],[413,43],[278,5],[169,71],[146,171]], 13 | // Edges to be constrained 14 | edges = [[5, 8]], 15 | // Triangulate 16 | del = Delaunator.from(points), 17 | // Constrain the triangulation 18 | con = new Constrainautor(del); 19 | con.constrainAll(edges); 20 | 21 | // Query point 22 | const qx = 162, qy = 262, 23 | // Obstruction callback: use constrained edges as obstructions 24 | obstructs = (edg) => con.isConstrained(edg), 25 | // Left & right end-points of the initial viewing cone (optional) 26 | ilx = 45, ily = 144, irx = 280, iry = 145, 27 | // Compute visibility polygon 28 | poly = triangularExpansion(del, qx, qy, obstructs, ilx, ily, irx, iry); 29 | 30 | for(const [lx, ly, rx, ry] of poly){ 31 | drawTriangle(lx, ly, qx, qy, rx, ry); 32 | } 33 | 34 | ![Visibility polygon](strain.png) 35 | 36 | Install 37 | ------- 38 | 39 | Install from NPM: 40 | 41 | npm install @kninnug/trivis 42 | 43 | Use in Node.js: 44 | 45 | const triangularExpansion = require('@kninnug/trivis'); 46 | 47 | or as an ECMAScript/ES6 module: 48 | 49 | import triangularExpansion from '@kninnug/trivis'; 50 | 51 | or in the browser: 52 | 53 | 54 | 55 | or minified: 56 | 57 | 58 | 59 | The TriVis library does not depend on Delaunator itself, but the input is 60 | expected to be in the format that Delaunator outputs. The ES module variant 61 | (`TriVis.mjs`) depends on [robust-predicates](https://www.npmjs.com/package/robust-predicates) 62 | and [containing-triangle](https://www.npmjs.com/package/@kninnug/containing-triangle), 63 | but the CommonJS, browser, and minified versions (`lib/TriVis.cjs`, 64 | `lib/TriVis.js`, and `TriVis.min.js`) come with these dependencies compiled in, 65 | and can be used standalone. The (source) TypeScript version is in `TriVis.ts`. 66 | 67 | Usage 68 | ----- 69 | 70 | ### poly = triangularExpansion(del, qx, qy, obstructs, ilx = NaN, ily = NaN, irx = NaN, iry = NaN) 71 | 72 | Parameters: 73 | 74 | - `del`: The triangulation in the format that Delaunator outputs. 75 | - `qx`, `qy`: The coordinates of the query point. 76 | - `obstructs`: A callback that receives an edge id of the triangulation and must 77 | indicate whether it obstructs the view. Edges on the hull of the triangulation 78 | are always considered to be obstructing. 79 | - `ilx`, `ily`, `irx`, `iry`: If given, i.e. not `NaN`, the coordinates of the 80 | left and right points restricting the viewing cone. The angles between these 81 | points and the query point should not be greater than 180°. If these arguments 82 | are not given, the visibility polygon is computed in all directions. 83 | 84 | Return value: 85 | 86 | An array of 4-element arrays, `[lx, ly, rx, ry]` with the coordinates of the 87 | left- and right-hand side end-points of the segments that make up the visibility 88 | polygon. Each triplet `(lx, ly) (qx, qy) (rx, ry)` forms a counter-clockwise 89 | triangle that is entirely visible from the query point. The segments are also 90 | ordered counter-clockwise around `(qx, qy)`. 91 | 92 | Changes 93 | ------- 94 | 95 | ### 2.0.0 96 | - Convert to TypeScript. 97 | - Move built files to `lib/`. 98 | 99 | ### 1.0.1 100 | - Update dependencies. 101 | - Move test files to separate repository. 102 | 103 | ### 1.0.0 104 | - Initial version. 105 | 106 | Attributions 107 | ------------ 108 | 109 | - The Triangular Expansion algorithm is adapted from [Efficient Computation of 110 | Visibility Polygons](https://arxiv.org/abs/1403.3905), March 18, 2014, 111 | Francisc Bungiu, Michael Hemmer, John Hershberger, Kan Huang, Alexander Kröller. 112 | - Uses Vladimir Agafonkin's [robust-predicates](https://github.com/mourner/robust-predicates) port 113 | of Jonathan Shewchuk's [Adaptive Precision Floating-Point Arithmetic and Fast Robust Predicates 114 | for Computational Geometry](http://www.cs.cmu.edu/~quake/robust.html). 115 | - Ray-segment intersection computation adapted from Nicky Case's 116 | [Sight & Light tutorial](https://ncase.me/sight-and-light/). 117 | - The example image and initial idea for this library were inspired by Amit 118 | Patel's article on [2D visibility](https://www.redblobgames.com/articles/visibility/). 119 | -------------------------------------------------------------------------------- /TriVis.ts: -------------------------------------------------------------------------------- 1 | import {orient2d} from 'robust-predicates'; 2 | import containingTriangle from '@kninnug/containing-triangle'; 3 | 4 | export interface DelaunatorLike { 5 | coords: {readonly length: number, readonly [n: number]: number}; 6 | triangles: {readonly length: number, [n: number]: number}; 7 | halfedges: {readonly length: number, [n: number]: number}; 8 | hull: {readonly length: number, readonly [n: number]: number}; 9 | }; 10 | 11 | /** A 4-tuple indicating the left-x/-y and right-x/-y of a segment. */ 12 | export type Segment = [number, number, number, number]; 13 | 14 | /** 15 | * Compute if and where a ray, or half-line, intersects with a segment. Returns 16 | * the relative location of the intersection point on the ray. I.e, if it 17 | * intersects: 18 | * 19 | * const r = segIntersectRay(s1x, s1y, s2x, s2y, r1x, r1y, r2x, r2y), 20 | * dx = r2x - r1x, 21 | * dy = r2y - r1y, 22 | * // intersection is at: 23 | * x = r1x + r * dx, 24 | * y = r1y + r * dy; 25 | * 26 | * @source https://ncase.me/sight-and-light/ 27 | * @param s1x The x coordinate of point 1 of the segment. 28 | * @param s1y The y coordinate of point 1 of the segment. 29 | * @param s2x The x coordinate of point 2 of the segment. 30 | * @param s2y The y coordinate of point 2 of the segment. 31 | * @param r1x The x coordinate of the origin of the ray. 32 | * @param r1y The y coordinate of the origin of the ray. 33 | * @param r2x The x coordinate of a second point along the ray. 34 | * @param r2y The y coordinate of a second point along the ray. 35 | * @return The relative point of the intersection, or Infinity if the 36 | * ray never intersects the segment. 37 | */ 38 | function segIntersectRay(s1x: number, s1y: number, s2x: number, s2y: number, r1x: number, r1y: number, r2x: number, r2y: number){ 39 | const rdx = r2x - r1x, 40 | rdy = r2y - r1y, 41 | 42 | sdx = s2x - s1x, 43 | sdy = s2y - s1y, 44 | 45 | rmag = Math.sqrt(rdx * rdx + rdy * rdy), 46 | smag = Math.sqrt(sdx * sdx + sdy * sdy); 47 | 48 | if(rdx / rmag === sdx / smag && rdy / rmag === sdy / smag){ 49 | return Infinity; 50 | } 51 | 52 | const T2 = (rdx * (s1y - r1y) + rdy * (r1x - s1x)) / (sdx * rdy - sdy * rdx), 53 | T1 = rdx ? (s1x + sdx*T2 - r1x) / rdx : (s1y + sdy*T2 - r1y) / rdy; 54 | 55 | if(T1 < 0 || T2 < 0 || T2 > 1.0){ 56 | return Infinity; 57 | } 58 | 59 | return T1; 60 | } 61 | 62 | /** 63 | * Square distance between two points of a triangulation. 64 | * 65 | * @param x1 First point x coordinate. 66 | * @param y1 First point y coordinate. 67 | * @param x2 Second point x coordinate. 68 | * @param y2 Second point y coordinate. 69 | * @return The squared distance. 70 | */ 71 | function sqdist(x1: number, y1: number, x2: number, y2: number){ 72 | const dx = x2 - x1, 73 | dy = y2 - y1; 74 | 75 | return dx * dx + dy * dy; 76 | } 77 | 78 | /** 79 | * Next half-edge counter-clockwise in a triangle. 80 | * 81 | * @param e Half-edge id. 82 | * @return Id of the next half-edge. 83 | */ 84 | function nextEdge(e: number){ return (e % 3 === 2) ? e - 2 : e + 1; } 85 | /** 86 | * Previous half-edge counter-clockwise in a triangle. 87 | * 88 | * @param e Half-edge id. 89 | * @return Id of the previous half-edge. 90 | */ 91 | function prevEdge(e: number){ return (e % 3 === 0) ? e + 2 : e - 1; } 92 | /** 93 | * Half-edges of the given triangle. 94 | * 95 | * @param t Triangle id. 96 | * @return Ids of the half-edges. 97 | */ 98 | function edgesOfTri(t: number){ return [t * 3, t * 3 + 1, t * 3 + 2]; } 99 | /** 100 | * Point indices of the given triangle. 101 | * 102 | * @param del The triangulation. 103 | * @param t Triangle id. 104 | * @return Indices into the points array of the triangle's points. 105 | */ 106 | function pointsOfTri(del: DelaunatorLike, t: number){ 107 | const p1 = del.triangles[t * 3], 108 | p2 = del.triangles[t * 3 + 1], 109 | p3 = del.triangles[t * 3 + 2]; 110 | return [p1, p2, p3]; 111 | } 112 | 113 | /** 114 | * Whether a point is left of the line defined by two other points. 115 | * 116 | * @param x1 The x coordinate of the first point on the line. 117 | * @param y1 The y coordinate of the first point on the line. 118 | * @param x2 The x coordinate of the second point on the line. 119 | * @param y2 The y coordinate of the second point on the line. 120 | * @param px The x coordinate of the query point. 121 | * @param py The y coordinate of the query point. 122 | * @return True if (px, py) is strictly to the left of the segment. 123 | */ 124 | function isLeftOf(x1: number, y1: number, x2: number, y2: number, px: number, py: number){ 125 | return orient2d(x1, y1, x2, y2, px, py) > 0; 126 | } 127 | 128 | /** 129 | * Whether a point is right of the line defined by two other points. 130 | * 131 | * @param x1 The x coordinate of the first point on the line. 132 | * @param y1 The y coordinate of the first point on the line. 133 | * @param x2 The x coordinate of the second point on the line. 134 | * @param y2 The y coordinate of the second point on the line. 135 | * @param px The x coordinate of the query point. 136 | * @param py The y coordinate of the query point. 137 | * @return True if (px, py) is strictly to the right of the segment. 138 | */ 139 | function isRightOf(x1: number, y1: number, x2: number, y2: number, px: number, py: number){ 140 | return orient2d(x1, y1, x2, y2, px, py) < 0; 141 | } 142 | 143 | /** 144 | * Compute the centroid of a triangle in a triangulation. 145 | * 146 | * @param del The triangulation. 147 | * @param tri The triangle id. 148 | * @return The coordinates of the centroid. 149 | */ 150 | function centroid(del: DelaunatorLike, tri: number){ 151 | const [p1, p2, p3] = pointsOfTri(del, tri), 152 | p1x = del.coords[p1 * 2], 153 | p1y = del.coords[p1 * 2 + 1], 154 | p2x = del.coords[p2 * 2], 155 | p2y = del.coords[p2 * 2 + 1], 156 | p3x = del.coords[p3 * 2], 157 | p3y = del.coords[p3 * 2 + 1]; 158 | return [(p1x + p2x + p3x) / 3, (p1y + p2y + p3y) / 3]; 159 | } 160 | 161 | /** 162 | * Order two points with respect to a query point. 163 | * 164 | * @param qx The x coordinate of the query point. 165 | * @param qy The y coordinate of the query point. 166 | * @param p1x The x coordinate of the first point. 167 | * @param p1y The y coordinate of the first point. 168 | * @param p2x The x coordinate of the second point. 169 | * @param p2y The y coordinate of the second point. 170 | * @return The coordinates of the two points in order [left-x, 171 | * left-y, right-x, right-y]. 172 | */ 173 | function orderAngles(qx: number, qy: number, p1x: number, p1y: number, p2x: number, p2y: number){ 174 | const segLeft = isLeftOf(qx, qy, p2x, p2y, p1x, p1y), 175 | segRight = !segLeft, 176 | lx = segLeft ? p1x : p2x, 177 | ly = segLeft ? p1y : p2y, 178 | rx = segRight ? p1x : p2x, 179 | ry = segRight ? p1y : p2y; 180 | return [lx, ly, rx, ry]; 181 | } 182 | 183 | /** 184 | * Order two points of a triangulation with respect to a query point. 185 | * 186 | * @param del The triangulation. 187 | * @param qx The x coordinate of the query point. 188 | * @param qy The y coordinate of the query point. 189 | * @param p1 The id of the first point. 190 | * @param p2 The id of the second point. 191 | * @return The coordinates of the two points in order [left-x, 192 | * left-y, right-x, right-y]. 193 | */ 194 | function orderDelAngles(del: DelaunatorLike, qx: number, qy: number, p1: number, p2: number){ 195 | const p1x = del.coords[p1 * 2], 196 | p1y = del.coords[p1 * 2 + 1], 197 | p2x = del.coords[p2 * 2], 198 | p2y = del.coords[p2 * 2 + 1]; 199 | return orderAngles(qx, qy, p1x, p1y, p2x, p2y); 200 | } 201 | 202 | /** 203 | * Whether a segment is within a viewing cone. 204 | * 205 | * @param px The x coordinate of the query point. 206 | * @param py The y coordinate of the query point. 207 | * @param slx The x coordinate of the segment's left point. 208 | * @param sly The y coordinate of the segment's left point. 209 | * @param srx The x coordinate of the segment's right point. 210 | * @param sry The y coordinate of the segment's right point. 211 | * @param rlx The x coordinate of the viewing cone's left point. 212 | * @param rly The y coordinate of the viewing cone's left point. 213 | * @param rrx The x coordinate of the viewing cone's right point. 214 | * @param rry The y coordinate of the viewing cone's right point. 215 | * @return True if the segment is within the viewing cone. 216 | */ 217 | function isWithinCone(px: number, py: number, slx: number, sly: number, srx: number, sry: number, rlx: number, rly: number, rrx: number, rry: number){ 218 | // o >----- o #====# o -----< o 219 | // rl sl sr rr --> (rl leftOf sl && sl leftOf rr) || (rl leftOf sr && sr leftOf rr) 220 | // o #===== o >====# o -----< o 221 | // sl rl sr rr --> sl leftOr rl && rl leftOf sr 222 | // o >----- o #====< o =====# o 223 | // rl sl rr sr --> rl leftOf sl && sl leftOf rr 224 | // o #===== o >====< o =====# o 225 | // sl rl rr sr --> (sl leftOf rl && rl leftOf sr) || (sl leftOf rr && rr leftOf sr) 226 | 227 | // o >----< o ------ o #====# o 228 | // rl rr sl sr --> rr leftOf sl 229 | if(isLeftOf(px, py, slx, sly, rrx, rry)){ 230 | return false; 231 | } 232 | // o #====# o ------ o >----< o 233 | // sl sr rl rr --> sr leftOf rl 234 | if(isLeftOf(px, py, rlx, rly, srx, sry)){ 235 | return false; 236 | } 237 | // o >--------< o #========# o 238 | // rl rr sl sr --> 239 | if(rrx === slx && rry === sly){ 240 | return false; 241 | } 242 | // o #========# o >--------< o 243 | // sl sr rl rr --> 244 | if(srx === rlx && sry === rly){ 245 | return false; 246 | } 247 | 248 | return true; 249 | } 250 | 251 | /** 252 | * Restrict a viewing cone by an edge. 253 | * 254 | * @param px The x coordinate of the query point. 255 | * @param py The y coordinate of the query point. 256 | * @param slx The x coordinate of the segment's left point. 257 | * @param sly The y coordinate of the segment's left point. 258 | * @param srx The x coordinate of the segment's right point. 259 | * @param sry The y coordinate of the segment's right point. 260 | * @param rlx The x coordinate of the viewing cone's left point. 261 | * @param rly The y coordinate of the viewing cone's left point. 262 | * @param rrx The x coordinate of the viewing cone's right point. 263 | * @param rry The y coordinate of the viewing cone's right point. 264 | * @return The coordinates of the new viewing cone, as 265 | * well as two booleans indicating whether the left and/or the right 266 | * points were changed from the original cone to the segment. 267 | */ 268 | function restrictAngles(px: number, py: number, 269 | slx: number, sly: number, srx: number, sry: number, 270 | rlx: number, rly: number, rrx: number, rry: number): [...Segment, boolean, boolean] { 271 | let nlx = rlx, 272 | nly = rly, 273 | resLeft = false; 274 | if(isRightOf(px, py, rlx, rly, slx, sly)){ 275 | nlx = slx; 276 | nly = sly; 277 | resLeft = true; 278 | } 279 | 280 | let nrx = rrx, 281 | nry = rry, 282 | resRight = false; 283 | if(isLeftOf(px, py, rrx, rry, srx, sry)){ 284 | nrx = srx; 285 | nry = sry; 286 | resRight = true; 287 | } 288 | 289 | return [nlx, nly, nrx, nry, resLeft, resRight]; 290 | } 291 | 292 | /** 293 | * Compute a visibility polygon of line segments that are visible from a given 294 | * query point in a triangulation. 295 | * 296 | * @source "Efficient Computation of Visibility Polygons" - Francisc Bungiu et al. 297 | * @param del The triangulation. 298 | * @param qx The x coordinate of the query point. 299 | * @param qy The y coordinate of the query point. 300 | * @param obstructs A callback that indicates whether an edge in the 301 | * triangulation obstructs visibility. Called with an edge id and 302 | * expected to return a truthy value. 303 | * @param ilx The x coordinate of the left point restricting the viewing cone. 304 | * @param ily The y coordinate of the left point restricting the viewing cone. 305 | * @param irx The x coordinate of the right point restricting the viewing cone. 306 | * @param iry The y coordinate of the right point restricting the viewing cone. 307 | * @return An array of number quadruples that define the 308 | * line segments making up the visibility polygon. 309 | */ 310 | function triangularExpansion(del: DelaunatorLike, qx: number, qy: number, obstructs: (edge: number) => boolean, 311 | ilx = NaN, ily = NaN, irx = NaN, iry = NaN): Segment[] { 312 | /** 313 | * Expand the visibility polygon through the next triangle. 314 | * 315 | * @param edgIn The edge in this triangle through which we came in. 316 | * @param rlx The x coordinate of the left restricting point. 317 | * @param rly The y coordinate of the left restricting point. 318 | * @param rrx The x coordinate of the right restricting point. 319 | * @param rry The y coordinate of the right restricting point. 320 | * @return The segment quadruples of this subsection of 321 | * the visibility polygon. 322 | */ 323 | function expand(edgIn: number, rlx: number, rly: number, rrx: number, rry: number): Segment[] { 324 | const ret: Segment[] = [], 325 | edges = [nextEdge(edgIn), prevEdge(edgIn)]; 326 | 327 | for(let i = 0; i < 2; i++){ 328 | const edg = edges[i], 329 | p1 = del.triangles[edg], 330 | p2 = del.triangles[nextEdge(edg)], 331 | adjOut = del.halfedges[edg]; 332 | // Segment left and right 333 | let [slx, sly, srx, sry] = orderDelAngles(del, qx, qy, p1, p2); 334 | 335 | // Edge outside viewing cone 336 | if(!isWithinCone(qx, qy, slx, sly, srx, sry, rlx, rly, rrx, rry)){ 337 | continue; 338 | } 339 | 340 | // Next restricting left & right points 341 | const [nlx, nly, nrx, nry, resL, resR] = restrictAngles(qx, qy, 342 | slx, sly, srx, sry, rlx, rly, rrx, rry); 343 | 344 | // Viewing cone degenerated to zero-area triangle 345 | if(orient2d(qx, qy, nrx, nry, nlx, nly) <= 0.0){ 346 | continue; 347 | } 348 | 349 | if(adjOut !== -1 && !obstructs(edg)){ 350 | // Expand into the next triangle 351 | ret.push(...expand(adjOut, nlx, nly, nrx, nry)); 352 | continue; 353 | } 354 | // Hit a wall, or the hull 355 | 356 | // Left restricting point is now on the segment 357 | if(!resL){ 358 | const int = segIntersectRay(slx, sly, srx, sry, qx, qy, rlx, rly); 359 | if(int !== Infinity){ 360 | const dx = rlx - qx, 361 | dy = rly - qy; 362 | slx = qx + int * dx; 363 | sly = qy + int * dy; 364 | } 365 | } 366 | // Right restricting point is now on the segment 367 | if(!resR){ 368 | const int = segIntersectRay(slx, sly, srx, sry, qx, qy, rrx, rry); 369 | if(int !== Infinity){ 370 | const dx = rrx - qx, 371 | dy = rry - qy; 372 | srx = qx + int * dx; 373 | sry = qy + int * dy; 374 | } 375 | } 376 | 377 | ret.push([slx, sly, srx, sry]); 378 | } 379 | 380 | return ret; 381 | } 382 | 383 | const triStart = containingTriangle(del, qx, qy); 384 | if(triStart === -1){ 385 | return []; 386 | } 387 | 388 | // Order w.r.t. the centroid, rather than the view point to avoid 389 | // instability when the view point is on a triangle edge. 390 | const [cx, cy] = centroid(del, triStart), 391 | startEdges = edgesOfTri(triStart), 392 | ret: Segment[] = [], 393 | prestrict = !isNaN(ilx); 394 | 395 | // Have an initial viewing cone 396 | if(prestrict){ // assume all are given, or none 397 | ([ilx, ily, irx, iry] = orderAngles(qx, qy, ilx, ily, irx, iry)); 398 | } 399 | 400 | for(let i = 0; i < 3; i++){ 401 | const edg = startEdges[i], 402 | p1 = del.triangles[edg], 403 | p2 = del.triangles[nextEdge(edg)]; 404 | let [slx, sly, srx, sry] = orderDelAngles(del, cx, cy, p1, p2); 405 | // Fix for stack-overflows when (qx, qy) is exactly on an end-point 406 | if(sqdist(slx, sly, qx, qy) === 0.0 || sqdist(srx, sry, qx, qy) === 0.0){ 407 | return []; 408 | } 409 | 410 | if(prestrict){ 411 | const intL = segIntersectRay(slx, sly, srx, sry, qx, qy, ilx, ily); 412 | if(intL !== Infinity){ 413 | const dx = ilx - qx, 414 | dy = ily - qy; 415 | ([, , slx, sly] = orderAngles(qx, qy, qx + intL * dx, qy + intL * dy, slx, sly)); 416 | } 417 | const intR = segIntersectRay(slx, sly, srx, sry, qx, qy, irx, iry); 418 | if(intR !== Infinity){ 419 | const dx = irx - qx, 420 | dy = iry - qy; 421 | ([srx, sry, , ] = orderAngles(qx, qy, srx, sry, qx + intR * dx, qy + intR * dy)); 422 | } 423 | 424 | ([slx, sly, srx, sry] = orderAngles(qx, qy, slx, sly, srx, sry)); 425 | }else{ 426 | ilx = slx; 427 | ily = sly; 428 | irx = srx; 429 | iry = sry; 430 | } 431 | 432 | const [rlx, rly, rrx, rry] = restrictAngles(qx, qy, slx, sly, srx, sry, ilx, ily, irx, iry); 433 | 434 | if(!isWithinCone(qx, qy, slx, sly, srx, sry, ilx, ily, irx, iry)){ 435 | continue; 436 | } 437 | 438 | const adj = del.halfedges[edg]; 439 | if(adj === -1 || obstructs(edg)){ 440 | ret.push([slx, sly, srx, sry]); 441 | }else{ 442 | ret.push(...expand(adj, rlx, rly, rrx, rry)); 443 | } 444 | } 445 | 446 | return ret; 447 | } 448 | 449 | export default triangularExpansion; 450 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kninnug/TriVis/00c84aad9426b8bdfdee54ec16d453306b0de194/example.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kninnug/trivis", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.12.13", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", 10 | "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.12.13" 14 | } 15 | }, 16 | "@babel/helper-validator-identifier": { 17 | "version": "7.14.0", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", 19 | "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", 20 | "dev": true 21 | }, 22 | "@babel/highlight": { 23 | "version": "7.14.0", 24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", 25 | "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", 26 | "dev": true, 27 | "requires": { 28 | "@babel/helper-validator-identifier": "^7.14.0", 29 | "chalk": "^2.0.0", 30 | "js-tokens": "^4.0.0" 31 | } 32 | }, 33 | "@kninnug/constrainautor": { 34 | "version": "3.0.0", 35 | "resolved": "https://registry.npmjs.org/@kninnug/constrainautor/-/constrainautor-3.0.0.tgz", 36 | "integrity": "sha512-p6j9LBzsCFOPZJ4PdtjR0mjlKkp/3yLj76S4cLf0jXfdcfbs328I7DFsCarTTM9lwWu7nbhpnLqCQqd8t2KSgw==", 37 | "dev": true, 38 | "requires": { 39 | "robust-predicates": "^3.0.1" 40 | } 41 | }, 42 | "@kninnug/containing-triangle": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/@kninnug/containing-triangle/-/containing-triangle-2.0.0.tgz", 45 | "integrity": "sha512-4VSyET+jlQtJN1NuPOATqQS5T3Wy59+716Lj8Ks38ETBrHmqOHkuUhnujOHTnGa6dy4hysSuWvbYRL15WN2Jsg==", 46 | "requires": { 47 | "robust-predicates": "^3.0.1" 48 | } 49 | }, 50 | "@rollup/plugin-commonjs": { 51 | "version": "21.0.1", 52 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", 53 | "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", 54 | "dev": true, 55 | "requires": { 56 | "@rollup/pluginutils": "^3.1.0", 57 | "commondir": "^1.0.1", 58 | "estree-walker": "^2.0.1", 59 | "glob": "^7.1.6", 60 | "is-reference": "^1.2.1", 61 | "magic-string": "^0.25.7", 62 | "resolve": "^1.17.0" 63 | }, 64 | "dependencies": { 65 | "estree-walker": { 66 | "version": "2.0.2", 67 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 68 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 69 | "dev": true 70 | } 71 | } 72 | }, 73 | "@rollup/plugin-node-resolve": { 74 | "version": "11.2.1", 75 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", 76 | "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", 77 | "dev": true, 78 | "requires": { 79 | "@rollup/pluginutils": "^3.1.0", 80 | "@types/resolve": "1.17.1", 81 | "builtin-modules": "^3.1.0", 82 | "deepmerge": "^4.2.2", 83 | "is-module": "^1.0.0", 84 | "resolve": "^1.19.0" 85 | } 86 | }, 87 | "@rollup/plugin-replace": { 88 | "version": "3.0.1", 89 | "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.1.tgz", 90 | "integrity": "sha512-989J5oRzf3mm0pO/0djTijdfEh9U3n63BIXN5X7T4U9BP+fN4oxQ6DvDuBvFaHA6scaHQRclqmKQEkBhB7k7Hg==", 91 | "dev": true, 92 | "requires": { 93 | "@rollup/pluginutils": "^3.1.0", 94 | "magic-string": "^0.25.7" 95 | } 96 | }, 97 | "@rollup/plugin-typescript": { 98 | "version": "8.3.0", 99 | "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz", 100 | "integrity": "sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==", 101 | "dev": true, 102 | "requires": { 103 | "@rollup/pluginutils": "^3.1.0", 104 | "resolve": "^1.17.0" 105 | } 106 | }, 107 | "@rollup/pluginutils": { 108 | "version": "3.1.0", 109 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", 110 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", 111 | "dev": true, 112 | "requires": { 113 | "@types/estree": "0.0.39", 114 | "estree-walker": "^1.0.1", 115 | "picomatch": "^2.2.2" 116 | } 117 | }, 118 | "@types/delaunator": { 119 | "version": "5.0.0", 120 | "resolved": "https://registry.npmjs.org/@types/delaunator/-/delaunator-5.0.0.tgz", 121 | "integrity": "sha512-00nrXdKbhF9FQsq6MskmdUgEqd5MWzA+x8CoDt8OJqbS4QB528SgkOfEcnQMa2sIg2XTKowwwVQx8iGsAX6CAQ==", 122 | "dev": true 123 | }, 124 | "@types/estree": { 125 | "version": "0.0.39", 126 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 127 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 128 | "dev": true 129 | }, 130 | "@types/node": { 131 | "version": "15.0.1", 132 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", 133 | "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==", 134 | "dev": true 135 | }, 136 | "@types/resolve": { 137 | "version": "1.17.1", 138 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", 139 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", 140 | "dev": true, 141 | "requires": { 142 | "@types/node": "*" 143 | } 144 | }, 145 | "@types/tape": { 146 | "version": "4.13.2", 147 | "resolved": "https://registry.npmjs.org/@types/tape/-/tape-4.13.2.tgz", 148 | "integrity": "sha512-V1ez/RtYRGN9cNYApw5xf27DpMkTB0033X6a2i3KUmKhSojBfbWN0i3EgZxboUG96WJLHLdOyZ01aiZwVW5aSA==", 149 | "dev": true, 150 | "requires": { 151 | "@types/node": "*" 152 | } 153 | }, 154 | "ansi-styles": { 155 | "version": "3.2.1", 156 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 157 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 158 | "dev": true, 159 | "requires": { 160 | "color-convert": "^1.9.0" 161 | } 162 | }, 163 | "array-filter": { 164 | "version": "1.0.0", 165 | "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", 166 | "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", 167 | "dev": true 168 | }, 169 | "available-typed-arrays": { 170 | "version": "1.0.2", 171 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", 172 | "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", 173 | "dev": true, 174 | "requires": { 175 | "array-filter": "^1.0.0" 176 | } 177 | }, 178 | "balanced-match": { 179 | "version": "1.0.2", 180 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 181 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 182 | "dev": true 183 | }, 184 | "brace-expansion": { 185 | "version": "1.1.11", 186 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 187 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 188 | "dev": true, 189 | "requires": { 190 | "balanced-match": "^1.0.0", 191 | "concat-map": "0.0.1" 192 | } 193 | }, 194 | "buffer-from": { 195 | "version": "1.1.1", 196 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 197 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 198 | "dev": true 199 | }, 200 | "builtin-modules": { 201 | "version": "3.2.0", 202 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", 203 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", 204 | "dev": true 205 | }, 206 | "call-bind": { 207 | "version": "1.0.2", 208 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 209 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 210 | "dev": true, 211 | "requires": { 212 | "function-bind": "^1.1.1", 213 | "get-intrinsic": "^1.0.2" 214 | } 215 | }, 216 | "chalk": { 217 | "version": "2.4.2", 218 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 219 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 220 | "dev": true, 221 | "requires": { 222 | "ansi-styles": "^3.2.1", 223 | "escape-string-regexp": "^1.0.5", 224 | "supports-color": "^5.3.0" 225 | } 226 | }, 227 | "color-convert": { 228 | "version": "1.9.3", 229 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 230 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 231 | "dev": true, 232 | "requires": { 233 | "color-name": "1.1.3" 234 | } 235 | }, 236 | "color-name": { 237 | "version": "1.1.3", 238 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 239 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 240 | "dev": true 241 | }, 242 | "commander": { 243 | "version": "2.20.3", 244 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 245 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 246 | "dev": true 247 | }, 248 | "commondir": { 249 | "version": "1.0.1", 250 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 251 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", 252 | "dev": true 253 | }, 254 | "concat-map": { 255 | "version": "0.0.1", 256 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 257 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 258 | "dev": true 259 | }, 260 | "deep-equal": { 261 | "version": "2.0.5", 262 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", 263 | "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", 264 | "dev": true, 265 | "requires": { 266 | "call-bind": "^1.0.0", 267 | "es-get-iterator": "^1.1.1", 268 | "get-intrinsic": "^1.0.1", 269 | "is-arguments": "^1.0.4", 270 | "is-date-object": "^1.0.2", 271 | "is-regex": "^1.1.1", 272 | "isarray": "^2.0.5", 273 | "object-is": "^1.1.4", 274 | "object-keys": "^1.1.1", 275 | "object.assign": "^4.1.2", 276 | "regexp.prototype.flags": "^1.3.0", 277 | "side-channel": "^1.0.3", 278 | "which-boxed-primitive": "^1.0.1", 279 | "which-collection": "^1.0.1", 280 | "which-typed-array": "^1.1.2" 281 | } 282 | }, 283 | "deepmerge": { 284 | "version": "4.2.2", 285 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 286 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 287 | "dev": true 288 | }, 289 | "define-properties": { 290 | "version": "1.1.3", 291 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 292 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 293 | "dev": true, 294 | "requires": { 295 | "object-keys": "^1.0.12" 296 | } 297 | }, 298 | "defined": { 299 | "version": "1.0.0", 300 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 301 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 302 | "dev": true 303 | }, 304 | "delaunator": { 305 | "version": "5.0.0", 306 | "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", 307 | "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", 308 | "dev": true, 309 | "requires": { 310 | "robust-predicates": "^3.0.0" 311 | } 312 | }, 313 | "dotignore": { 314 | "version": "0.1.2", 315 | "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", 316 | "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", 317 | "dev": true, 318 | "requires": { 319 | "minimatch": "^3.0.4" 320 | } 321 | }, 322 | "es-abstract": { 323 | "version": "1.18.0", 324 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", 325 | "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", 326 | "dev": true, 327 | "requires": { 328 | "call-bind": "^1.0.2", 329 | "es-to-primitive": "^1.2.1", 330 | "function-bind": "^1.1.1", 331 | "get-intrinsic": "^1.1.1", 332 | "has": "^1.0.3", 333 | "has-symbols": "^1.0.2", 334 | "is-callable": "^1.2.3", 335 | "is-negative-zero": "^2.0.1", 336 | "is-regex": "^1.1.2", 337 | "is-string": "^1.0.5", 338 | "object-inspect": "^1.9.0", 339 | "object-keys": "^1.1.1", 340 | "object.assign": "^4.1.2", 341 | "string.prototype.trimend": "^1.0.4", 342 | "string.prototype.trimstart": "^1.0.4", 343 | "unbox-primitive": "^1.0.0" 344 | } 345 | }, 346 | "es-get-iterator": { 347 | "version": "1.1.2", 348 | "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", 349 | "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", 350 | "dev": true, 351 | "requires": { 352 | "call-bind": "^1.0.2", 353 | "get-intrinsic": "^1.1.0", 354 | "has-symbols": "^1.0.1", 355 | "is-arguments": "^1.1.0", 356 | "is-map": "^2.0.2", 357 | "is-set": "^2.0.2", 358 | "is-string": "^1.0.5", 359 | "isarray": "^2.0.5" 360 | } 361 | }, 362 | "es-to-primitive": { 363 | "version": "1.2.1", 364 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 365 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 366 | "dev": true, 367 | "requires": { 368 | "is-callable": "^1.1.4", 369 | "is-date-object": "^1.0.1", 370 | "is-symbol": "^1.0.2" 371 | } 372 | }, 373 | "escape-string-regexp": { 374 | "version": "1.0.5", 375 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 376 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 377 | "dev": true 378 | }, 379 | "estree-walker": { 380 | "version": "1.0.1", 381 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 382 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", 383 | "dev": true 384 | }, 385 | "for-each": { 386 | "version": "0.3.3", 387 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 388 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 389 | "dev": true, 390 | "requires": { 391 | "is-callable": "^1.1.3" 392 | } 393 | }, 394 | "foreach": { 395 | "version": "2.0.5", 396 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 397 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 398 | "dev": true 399 | }, 400 | "fs.realpath": { 401 | "version": "1.0.0", 402 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 403 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 404 | "dev": true 405 | }, 406 | "fsevents": { 407 | "version": "2.3.2", 408 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 409 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 410 | "dev": true, 411 | "optional": true 412 | }, 413 | "function-bind": { 414 | "version": "1.1.1", 415 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 416 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 417 | "dev": true 418 | }, 419 | "get-intrinsic": { 420 | "version": "1.1.1", 421 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 422 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 423 | "dev": true, 424 | "requires": { 425 | "function-bind": "^1.1.1", 426 | "has": "^1.0.3", 427 | "has-symbols": "^1.0.1" 428 | } 429 | }, 430 | "glob": { 431 | "version": "7.1.6", 432 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 433 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 434 | "dev": true, 435 | "requires": { 436 | "fs.realpath": "^1.0.0", 437 | "inflight": "^1.0.4", 438 | "inherits": "2", 439 | "minimatch": "^3.0.4", 440 | "once": "^1.3.0", 441 | "path-is-absolute": "^1.0.0" 442 | } 443 | }, 444 | "has": { 445 | "version": "1.0.3", 446 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 447 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 448 | "dev": true, 449 | "requires": { 450 | "function-bind": "^1.1.1" 451 | } 452 | }, 453 | "has-bigints": { 454 | "version": "1.0.1", 455 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", 456 | "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", 457 | "dev": true 458 | }, 459 | "has-flag": { 460 | "version": "3.0.0", 461 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 462 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 463 | "dev": true 464 | }, 465 | "has-symbols": { 466 | "version": "1.0.2", 467 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 468 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 469 | "dev": true 470 | }, 471 | "inflight": { 472 | "version": "1.0.6", 473 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 474 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 475 | "dev": true, 476 | "requires": { 477 | "once": "^1.3.0", 478 | "wrappy": "1" 479 | } 480 | }, 481 | "inherits": { 482 | "version": "2.0.4", 483 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 484 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 485 | "dev": true 486 | }, 487 | "is-arguments": { 488 | "version": "1.1.0", 489 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", 490 | "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", 491 | "dev": true, 492 | "requires": { 493 | "call-bind": "^1.0.0" 494 | } 495 | }, 496 | "is-bigint": { 497 | "version": "1.0.1", 498 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", 499 | "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", 500 | "dev": true 501 | }, 502 | "is-boolean-object": { 503 | "version": "1.1.0", 504 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", 505 | "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", 506 | "dev": true, 507 | "requires": { 508 | "call-bind": "^1.0.0" 509 | } 510 | }, 511 | "is-callable": { 512 | "version": "1.2.3", 513 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", 514 | "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", 515 | "dev": true 516 | }, 517 | "is-core-module": { 518 | "version": "2.3.0", 519 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz", 520 | "integrity": "sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==", 521 | "dev": true, 522 | "requires": { 523 | "has": "^1.0.3" 524 | } 525 | }, 526 | "is-date-object": { 527 | "version": "1.0.2", 528 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 529 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 530 | "dev": true 531 | }, 532 | "is-map": { 533 | "version": "2.0.2", 534 | "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", 535 | "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", 536 | "dev": true 537 | }, 538 | "is-module": { 539 | "version": "1.0.0", 540 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 541 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 542 | "dev": true 543 | }, 544 | "is-negative-zero": { 545 | "version": "2.0.1", 546 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", 547 | "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", 548 | "dev": true 549 | }, 550 | "is-number-object": { 551 | "version": "1.0.4", 552 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", 553 | "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", 554 | "dev": true 555 | }, 556 | "is-reference": { 557 | "version": "1.2.1", 558 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 559 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", 560 | "dev": true, 561 | "requires": { 562 | "@types/estree": "*" 563 | } 564 | }, 565 | "is-regex": { 566 | "version": "1.1.2", 567 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", 568 | "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", 569 | "dev": true, 570 | "requires": { 571 | "call-bind": "^1.0.2", 572 | "has-symbols": "^1.0.1" 573 | } 574 | }, 575 | "is-set": { 576 | "version": "2.0.2", 577 | "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", 578 | "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", 579 | "dev": true 580 | }, 581 | "is-string": { 582 | "version": "1.0.5", 583 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", 584 | "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", 585 | "dev": true 586 | }, 587 | "is-symbol": { 588 | "version": "1.0.3", 589 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 590 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 591 | "dev": true, 592 | "requires": { 593 | "has-symbols": "^1.0.1" 594 | } 595 | }, 596 | "is-typed-array": { 597 | "version": "1.1.5", 598 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", 599 | "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", 600 | "dev": true, 601 | "requires": { 602 | "available-typed-arrays": "^1.0.2", 603 | "call-bind": "^1.0.2", 604 | "es-abstract": "^1.18.0-next.2", 605 | "foreach": "^2.0.5", 606 | "has-symbols": "^1.0.1" 607 | } 608 | }, 609 | "is-weakmap": { 610 | "version": "2.0.1", 611 | "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", 612 | "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", 613 | "dev": true 614 | }, 615 | "is-weakset": { 616 | "version": "2.0.1", 617 | "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", 618 | "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", 619 | "dev": true 620 | }, 621 | "isarray": { 622 | "version": "2.0.5", 623 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 624 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 625 | "dev": true 626 | }, 627 | "jest-worker": { 628 | "version": "26.6.2", 629 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", 630 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", 631 | "dev": true, 632 | "requires": { 633 | "@types/node": "*", 634 | "merge-stream": "^2.0.0", 635 | "supports-color": "^7.0.0" 636 | }, 637 | "dependencies": { 638 | "has-flag": { 639 | "version": "4.0.0", 640 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 641 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 642 | "dev": true 643 | }, 644 | "supports-color": { 645 | "version": "7.2.0", 646 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 647 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 648 | "dev": true, 649 | "requires": { 650 | "has-flag": "^4.0.0" 651 | } 652 | } 653 | } 654 | }, 655 | "js-tokens": { 656 | "version": "4.0.0", 657 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 658 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 659 | "dev": true 660 | }, 661 | "magic-string": { 662 | "version": "0.25.7", 663 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", 664 | "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", 665 | "dev": true, 666 | "requires": { 667 | "sourcemap-codec": "^1.4.4" 668 | } 669 | }, 670 | "merge-stream": { 671 | "version": "2.0.0", 672 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 673 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 674 | "dev": true 675 | }, 676 | "minimatch": { 677 | "version": "3.0.4", 678 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 679 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 680 | "dev": true, 681 | "requires": { 682 | "brace-expansion": "^1.1.7" 683 | } 684 | }, 685 | "minimist": { 686 | "version": "1.2.5", 687 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 688 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 689 | "dev": true 690 | }, 691 | "object-inspect": { 692 | "version": "1.10.2", 693 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", 694 | "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==", 695 | "dev": true 696 | }, 697 | "object-is": { 698 | "version": "1.1.5", 699 | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", 700 | "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", 701 | "dev": true, 702 | "requires": { 703 | "call-bind": "^1.0.2", 704 | "define-properties": "^1.1.3" 705 | } 706 | }, 707 | "object-keys": { 708 | "version": "1.1.1", 709 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 710 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 711 | "dev": true 712 | }, 713 | "object.assign": { 714 | "version": "4.1.2", 715 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", 716 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", 717 | "dev": true, 718 | "requires": { 719 | "call-bind": "^1.0.0", 720 | "define-properties": "^1.1.3", 721 | "has-symbols": "^1.0.1", 722 | "object-keys": "^1.1.1" 723 | } 724 | }, 725 | "once": { 726 | "version": "1.4.0", 727 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 728 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 729 | "dev": true, 730 | "requires": { 731 | "wrappy": "1" 732 | } 733 | }, 734 | "path-is-absolute": { 735 | "version": "1.0.1", 736 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 737 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 738 | "dev": true 739 | }, 740 | "path-parse": { 741 | "version": "1.0.6", 742 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 743 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 744 | "dev": true 745 | }, 746 | "picomatch": { 747 | "version": "2.2.3", 748 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", 749 | "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", 750 | "dev": true 751 | }, 752 | "randombytes": { 753 | "version": "2.1.0", 754 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 755 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 756 | "dev": true, 757 | "requires": { 758 | "safe-buffer": "^5.1.0" 759 | } 760 | }, 761 | "regexp.prototype.flags": { 762 | "version": "1.3.1", 763 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", 764 | "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", 765 | "dev": true, 766 | "requires": { 767 | "call-bind": "^1.0.2", 768 | "define-properties": "^1.1.3" 769 | } 770 | }, 771 | "resolve": { 772 | "version": "1.20.0", 773 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", 774 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", 775 | "dev": true, 776 | "requires": { 777 | "is-core-module": "^2.2.0", 778 | "path-parse": "^1.0.6" 779 | } 780 | }, 781 | "resumer": { 782 | "version": "0.0.0", 783 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 784 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 785 | "dev": true, 786 | "requires": { 787 | "through": "~2.3.4" 788 | } 789 | }, 790 | "robust-predicates": { 791 | "version": "3.0.1", 792 | "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", 793 | "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" 794 | }, 795 | "rollup": { 796 | "version": "2.48.0", 797 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.48.0.tgz", 798 | "integrity": "sha512-wl9ZSSSsi5579oscSDYSzGn092tCS076YB+TQrzsGuSfYyJeep8eEWj0eaRjuC5McuMNmcnR8icBqiE/FWNB1A==", 799 | "dev": true, 800 | "requires": { 801 | "fsevents": "~2.3.1" 802 | } 803 | }, 804 | "rollup-plugin-terser": { 805 | "version": "7.0.2", 806 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", 807 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", 808 | "dev": true, 809 | "requires": { 810 | "@babel/code-frame": "^7.10.4", 811 | "jest-worker": "^26.2.1", 812 | "serialize-javascript": "^4.0.0", 813 | "terser": "^5.0.0" 814 | } 815 | }, 816 | "safe-buffer": { 817 | "version": "5.2.1", 818 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 819 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 820 | "dev": true 821 | }, 822 | "serialize-javascript": { 823 | "version": "4.0.0", 824 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", 825 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", 826 | "dev": true, 827 | "requires": { 828 | "randombytes": "^2.1.0" 829 | } 830 | }, 831 | "side-channel": { 832 | "version": "1.0.4", 833 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 834 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 835 | "dev": true, 836 | "requires": { 837 | "call-bind": "^1.0.0", 838 | "get-intrinsic": "^1.0.2", 839 | "object-inspect": "^1.9.0" 840 | } 841 | }, 842 | "source-map": { 843 | "version": "0.7.3", 844 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 845 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 846 | "dev": true 847 | }, 848 | "source-map-support": { 849 | "version": "0.5.19", 850 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 851 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 852 | "dev": true, 853 | "requires": { 854 | "buffer-from": "^1.0.0", 855 | "source-map": "^0.6.0" 856 | }, 857 | "dependencies": { 858 | "source-map": { 859 | "version": "0.6.1", 860 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 861 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 862 | "dev": true 863 | } 864 | } 865 | }, 866 | "sourcemap-codec": { 867 | "version": "1.4.8", 868 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 869 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 870 | "dev": true 871 | }, 872 | "string.prototype.trim": { 873 | "version": "1.2.4", 874 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.4.tgz", 875 | "integrity": "sha512-hWCk/iqf7lp0/AgTF7/ddO1IWtSNPASjlzCicV5irAVdE1grjsneK26YG6xACMBEdCvO8fUST0UzDMh/2Qy+9Q==", 876 | "dev": true, 877 | "requires": { 878 | "call-bind": "^1.0.2", 879 | "define-properties": "^1.1.3", 880 | "es-abstract": "^1.18.0-next.2" 881 | } 882 | }, 883 | "string.prototype.trimend": { 884 | "version": "1.0.4", 885 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", 886 | "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", 887 | "dev": true, 888 | "requires": { 889 | "call-bind": "^1.0.2", 890 | "define-properties": "^1.1.3" 891 | } 892 | }, 893 | "string.prototype.trimstart": { 894 | "version": "1.0.4", 895 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", 896 | "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", 897 | "dev": true, 898 | "requires": { 899 | "call-bind": "^1.0.2", 900 | "define-properties": "^1.1.3" 901 | } 902 | }, 903 | "supports-color": { 904 | "version": "5.5.0", 905 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 906 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 907 | "dev": true, 908 | "requires": { 909 | "has-flag": "^3.0.0" 910 | } 911 | }, 912 | "tape": { 913 | "version": "5.2.2", 914 | "resolved": "https://registry.npmjs.org/tape/-/tape-5.2.2.tgz", 915 | "integrity": "sha512-grXrzPC1ly2kyTMKdqxh5GiLpb0BpNctCuecTB0psHX4Gu0nc+uxWR4xKjTh/4CfQlH4zhvTM2/EXmHXp6v/uA==", 916 | "dev": true, 917 | "requires": { 918 | "call-bind": "^1.0.2", 919 | "deep-equal": "^2.0.5", 920 | "defined": "^1.0.0", 921 | "dotignore": "^0.1.2", 922 | "for-each": "^0.3.3", 923 | "glob": "^7.1.6", 924 | "has": "^1.0.3", 925 | "inherits": "^2.0.4", 926 | "is-regex": "^1.1.2", 927 | "minimist": "^1.2.5", 928 | "object-inspect": "^1.9.0", 929 | "object-is": "^1.1.5", 930 | "object.assign": "^4.1.2", 931 | "resolve": "^2.0.0-next.3", 932 | "resumer": "^0.0.0", 933 | "string.prototype.trim": "^1.2.4", 934 | "through": "^2.3.8" 935 | }, 936 | "dependencies": { 937 | "resolve": { 938 | "version": "2.0.0-next.3", 939 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", 940 | "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", 941 | "dev": true, 942 | "requires": { 943 | "is-core-module": "^2.2.0", 944 | "path-parse": "^1.0.6" 945 | } 946 | } 947 | } 948 | }, 949 | "terser": { 950 | "version": "5.7.0", 951 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", 952 | "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", 953 | "dev": true, 954 | "requires": { 955 | "commander": "^2.20.0", 956 | "source-map": "~0.7.2", 957 | "source-map-support": "~0.5.19" 958 | } 959 | }, 960 | "through": { 961 | "version": "2.3.8", 962 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 963 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 964 | "dev": true 965 | }, 966 | "tslib": { 967 | "version": "2.3.1", 968 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 969 | "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 970 | "dev": true 971 | }, 972 | "typescript": { 973 | "version": "4.5.4", 974 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", 975 | "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", 976 | "dev": true 977 | }, 978 | "unbox-primitive": { 979 | "version": "1.0.1", 980 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", 981 | "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", 982 | "dev": true, 983 | "requires": { 984 | "function-bind": "^1.1.1", 985 | "has-bigints": "^1.0.1", 986 | "has-symbols": "^1.0.2", 987 | "which-boxed-primitive": "^1.0.2" 988 | } 989 | }, 990 | "which-boxed-primitive": { 991 | "version": "1.0.2", 992 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 993 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 994 | "dev": true, 995 | "requires": { 996 | "is-bigint": "^1.0.1", 997 | "is-boolean-object": "^1.1.0", 998 | "is-number-object": "^1.0.4", 999 | "is-string": "^1.0.5", 1000 | "is-symbol": "^1.0.3" 1001 | } 1002 | }, 1003 | "which-collection": { 1004 | "version": "1.0.1", 1005 | "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", 1006 | "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", 1007 | "dev": true, 1008 | "requires": { 1009 | "is-map": "^2.0.1", 1010 | "is-set": "^2.0.1", 1011 | "is-weakmap": "^2.0.1", 1012 | "is-weakset": "^2.0.1" 1013 | } 1014 | }, 1015 | "which-typed-array": { 1016 | "version": "1.1.4", 1017 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", 1018 | "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", 1019 | "dev": true, 1020 | "requires": { 1021 | "available-typed-arrays": "^1.0.2", 1022 | "call-bind": "^1.0.0", 1023 | "es-abstract": "^1.18.0-next.1", 1024 | "foreach": "^2.0.5", 1025 | "function-bind": "^1.1.1", 1026 | "has-symbols": "^1.0.1", 1027 | "is-typed-array": "^1.1.3" 1028 | } 1029 | }, 1030 | "wrappy": { 1031 | "version": "1.0.2", 1032 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1033 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1034 | "dev": true 1035 | } 1036 | } 1037 | } 1038 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kninnug/trivis", 3 | "version": "1.0.1", 4 | "description": "Compute visibility polygons by Triangular Expansion", 5 | "main": "./lib/TriVis.min.js", 6 | "module": "./lib/TriVis.mjs", 7 | "exports": { 8 | "import": "./lib/TriVis.mjs", 9 | "require": "./lib/TriVis.cjs" 10 | }, 11 | "types": "TriVis.ts", 12 | "files": [ 13 | "TriVis.ts", 14 | "lib/TriVis.cjs", 15 | "lib/TriVis.mjs", 16 | "lib/TriVis.js", 17 | "lib/TriVis.min.js" 18 | ], 19 | "scripts": { 20 | "test": "npm run build && node test/test.mjs", 21 | "build": "rollup -c", 22 | "prepare": "npm run build", 23 | "clean": "rm -r ./coverage ./lib/ ./test/ || true" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/kninnug/TriVis.git" 28 | }, 29 | "keywords": [ 30 | "visibility", 31 | "polygon", 32 | "triangular", 33 | "expansion", 34 | "Delaunator" 35 | ], 36 | "author": "Marco Gunnink", 37 | "license": "ISC", 38 | "bugs": { 39 | "url": "https://github.com/kninnug/TriVis/issues" 40 | }, 41 | "homepage": "https://github.com/kninnug/TriVis#readme", 42 | "dependencies": { 43 | "@kninnug/containing-triangle": "^2.0.0", 44 | "robust-predicates": "^3.0.1" 45 | }, 46 | "devDependencies": { 47 | "@kninnug/constrainautor": "^3.0.0", 48 | "@rollup/plugin-commonjs": "^21.0.1", 49 | "@rollup/plugin-node-resolve": "^11.2.1", 50 | "@rollup/plugin-replace": "^3.0.1", 51 | "@rollup/plugin-typescript": "^8.3.0", 52 | "@types/delaunator": "^5.0.0", 53 | "@types/tape": "^4.13.2", 54 | "delaunator": "^5.0.0", 55 | "rollup": "^2.48.0", 56 | "rollup-plugin-terser": "^7.0.2", 57 | "tape": "^5.2.2", 58 | "tslib": "^2.3.1", 59 | "typescript": "^4.5.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {terser} from 'rollup-plugin-terser'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import typescript from '@rollup/plugin-typescript'; 5 | import replace from '@rollup/plugin-replace'; 6 | import * as path from 'path'; 7 | 8 | function build(name){ 9 | return [ 10 | { 11 | input: `${name}.ts`, 12 | output: { 13 | name, 14 | format: 'es', 15 | file: `lib/${name}.mjs` 16 | }, 17 | external: ['robust-predicates', '@kninnug/containing-triangle'], 18 | plugins: [typescript()] 19 | }, 20 | { 21 | input: `lib/${name}.mjs`, // typescript & resolve don't play well together 22 | output: { 23 | name, 24 | format: 'commonjs', 25 | file: `lib/${name}.cjs`, 26 | exports: 'default' 27 | }, 28 | plugins: [resolve(), typescript()] 29 | }, 30 | { 31 | input: `lib/${name}.mjs`, 32 | output: { 33 | name, 34 | format: 'umd', 35 | file: `lib/${name}.js` 36 | }, 37 | plugins: [commonjs(), resolve()] 38 | }, 39 | { 40 | input: `lib/${name}.mjs`, 41 | output: { 42 | name, 43 | format: 'umd', 44 | file: `lib/${name}.min.js` 45 | }, 46 | plugins: [resolve(), commonjs(), terser()] 47 | } 48 | ]; 49 | } 50 | 51 | function test(name){ 52 | return [ 53 | { 54 | input: `${name}.ts`, 55 | output: { 56 | name, 57 | format: 'es', 58 | file: `test/${name}.mjs` 59 | }, 60 | external: ['robust-predicates', 'robust-segment-intersect', 'tape', 'delaunator', 61 | '@kninnug/constrainautor', '@kninnug/containing-triangle', 'fs', 'path'], 62 | plugins: [replace({ 63 | preventAssignment: true, 64 | values: { 65 | 'import.meta.url': function(file){ 66 | return JSON.stringify('file://' + file.replace(path.sep, '/')); 67 | } 68 | } 69 | }), typescript(), commonjs()] 70 | } 71 | ] 72 | } 73 | 74 | export default [ 75 | ...build('TriVis'), 76 | ...test('test') 77 | ]; 78 | -------------------------------------------------------------------------------- /strain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kninnug/TriVis/00c84aad9426b8bdfdee54ec16d453306b0de194/strain.png -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import {orient2d} from 'robust-predicates'; 3 | import Delaunator from 'delaunator'; 4 | import Constrainautor from '@kninnug/constrainautor'; 5 | import triangularExpansion from './TriVis'; 6 | import {loadTests, findTest, TestFile} from './delaunaytests/loader'; 7 | 8 | import type {Segment} from './TriVis'; 9 | import type {Test} from 'tape'; 10 | type P2 = [number, number]; 11 | type TestCase = { 12 | file: string, 13 | query: P2 | [...P2, ...Segment], 14 | output: Segment[] 15 | } 16 | 17 | const EPSILON = 2**-50, 18 | testFiles = loadTests(false); 19 | const references: TestCase[] = [{ 20 | file: 'amitexp.json', 21 | query: [563, 241], 22 | output: [ 23 | [770, 270, 590, 270], 24 | [780, 188.03149606299212, 780, 271.4009661835749], 25 | [670, 210, 690, 210], 26 | [530, 210, 670, 210], 27 | [510, 210, 530, 210], 28 | [470, 190, 470, 186.60377358490567], 29 | [450, 190, 470, 190], 30 | [446, 190, 450, 190], 31 | [434, 190, 446, 190], 32 | [430, 190, 434, 190], 33 | [410, 190, 430, 190], 34 | [370, 270, 370, 176.66666666666669], 35 | [450, 270, 370, 270], 36 | [450, 290, 450, 270], 37 | [370, 346.60377358490564, 370, 324.69026548672565], 38 | [530, 270, 510, 270], 39 | [530, 430, 530, 270], 40 | [560, 430, 530, 430], 41 | [560, 450, 560, 430], 42 | [600.0745610164117, 780.0000000000011, 555.2631578947369, 779.9999999999975], 43 | [591.3911190204142, 590, 588.233031891709, 607.8446454055273], 44 | [597.3508893593265, 556.3245553203368, 591.3911190204142, 590], 45 | [602.570792255239, 538.3161194238622, 597.3508893593265, 556.3245553203368], 46 | [606.5835921350013, 524.4721359549995, 602.570792255239, 538.3161194238622], 47 | [614.9343910395271, 507.7705381459478, 606.5835921350013, 524.4721359549995], 48 | [617.0585914563812, 503.52213731223964, 614.9343910395271, 507.7705381459478], 49 | [604.4721359549997, 346.58359213500125, 586.5835921350013, 355.52786404500046], 50 | [646.180339887499, 430, 604.4721359549997, 346.58359213500125], 51 | [650, 430, 646.180339887499, 430], 52 | [780, 634.8148148148148, 780, 712.4137931034484], 53 | [590, 270, 590, 290] 54 | ] 55 | }, { 56 | file: 'amitexp.json', 57 | query: [530, 234], 58 | output: [ 59 | [770, 270, 590, 270], 60 | [780, 196.5, 780, 271.5], 61 | [670, 210, 690, 210], 62 | [530, 210, 670, 210], 63 | [510, 210, 530, 210], 64 | [470, 170, 470, 162], 65 | [470, 190, 470, 170], 66 | [450, 190, 470, 190], 67 | [446, 190, 450, 190], 68 | [434, 190, 446, 190], 69 | [430, 190, 434, 190], 70 | [410, 190, 430, 190], 71 | [370, 270, 370, 175.33333333333334], 72 | [450, 270, 370, 270], 73 | [450, 290, 450, 270], 74 | [370, 450, 370, 346], 75 | [376.6666666666667, 510, 325.55555555555554, 510], 76 | [530, 270, 510, 270], 77 | [560, 430, 530, 430], 78 | [588.6075312369494, 612.5403531267184, 589.3339210354185, 621.6482840980675], 79 | [588.233031891709, 607.8446454055273, 588.6075312369494, 612.5403531267184], 80 | [591.3911190204142, 590, 588.233031891709, 607.8446454055273], 81 | [597.3508893593265, 556.3245553203368, 591.3911190204142, 590], 82 | [602.570792255239, 538.3161194238622, 597.3508893593265, 556.3245553203368], 83 | [606.5835921350013, 524.4721359549995, 602.570792255239, 538.3161194238622], 84 | [614.9343910395271, 507.7705381459478, 606.5835921350013, 524.4721359549995], 85 | [630, 477.63932022500194, 614.9343910395271, 507.7705381459478], 86 | [630, 448.7758024182177, 630, 477.63932022500194], 87 | [604.4721359549997, 346.58359213500125, 586.5835921350013, 355.52786404500046], 88 | [780, 467.33333333333337, 780, 611.9386433975478], 89 | [590, 270, 590, 290] 90 | ] 91 | }, { 92 | file: 'amitexp.json', 93 | query: [387, 479], 94 | output: [ 95 | [430, 510, 290, 510], 96 | [430, 470, 430, 510], 97 | [510, 450, 525.5555555555555, 450], 98 | [510, 430, 510, 450], 99 | [510, 270, 510, 430], 100 | [530, 210, 545.311004784689, 210], 101 | [510, 210, 530, 210], 102 | [510, 190, 510, 210], 103 | [510, 110, 510, 190], 104 | [370, 290, 450, 290], 105 | [370, 450, 370, 290], 106 | [350, 450, 370, 450], 107 | [193.97028311431427, 345.57988645363065, 179.27433670388717, 316.1879936327764], 108 | [199.7103162846646, 357.0599527943313, 198.6824314212446, 348.83687388697126], 109 | [203.41640786499872, 364.47213595499954, 199.7103162846646, 357.0599527943313], 110 | [200.800343220057, 365.7801682774704, 203.41640786499872, 364.47213595499954], 111 | [203.7592105464341, 386.43506512139453, 200.800343220057, 365.7801682774704], 112 | [290, 510, 290, 430] 113 | ] 114 | }, { 115 | file: 'amitexp.json', 116 | query: [530, 233, 740.0107140124046, 496.75082938258225, 266.2491706174178, 443.0107140124046], 117 | output: [ 118 | [370, 450, 370, 360.3994638069705], 119 | [380.27027027027026, 510, 325.7603686635945, 510], 120 | [530, 270, 510, 270], 121 | [560, 430, 530, 430], 122 | [588.6075312369494, 612.5403531267184, 589.4976524169139, 623.7012508710677], 123 | [588.233031891709, 607.8446454055273, 588.6075312369494, 612.5403531267184], 124 | [591.3911190204142, 590, 588.233031891709, 607.8446454055273], 125 | [597.3508893593265, 556.3245553203368, 591.3911190204142, 590], 126 | [602.570792255239, 538.3161194238622, 597.3508893593265, 556.3245553203368], 127 | [606.5835921350013, 524.4721359549995, 602.570792255239, 538.3161194238622], 128 | [614.9343910395271, 507.7705381459478, 606.5835921350013, 524.4721359549995], 129 | [630, 477.63932022500194, 614.9343910395271, 507.7705381459478], 130 | [630, 449.5430991950185, 630, 477.63932022500194], 131 | [604.4721359549997, 346.58359213500125, 586.5835921350013, 355.52786404500046], 132 | [780, 546.9730639730642, 780, 614.2956036457548] 133 | ] 134 | }, { 135 | file: 'amitp.json', 136 | query: [550, 536], 137 | output: [ 138 | [620, 520, 610, 550], 139 | [640, 480, 620, 520], 140 | [640, 440, 640, 480], 141 | [600, 360, 640, 440], 142 | [600, 280, 622.7272727272727, 279.9999999999999], 143 | [550, 200, 615.625, 200], 144 | [520, 440, 550, 440], 145 | [423.19101123595505, 140, 426.25, 140], 146 | [420, 180, 436, 180], 147 | [323.984375, 10, 357.92134831460675, 10], 148 | [360, 280, 440, 280], 149 | [360, 439.27272727272725, 360, 280], 150 | [440, 520, 440, 480], 151 | [440, 536, 440, 520], 152 | [280, 545.8181818181819, 280, 536], 153 | [440, 556, 440, 540], 154 | [280, 594.9090909090909, 280, 585.0909090909091], 155 | [440, 576, 440, 560], 156 | [280, 644, 280, 634.1818181818182], 157 | [440, 596, 440, 580], 158 | [113.4375, 790, 84.33333333333331, 790], 159 | [440, 616, 440, 600], 160 | [217.3809523809524, 790, 200.75, 790], 161 | [440, 636, 440, 620], 162 | [281.34615384615387, 790, 270.6, 790], 163 | [440, 656, 440, 640], 164 | [324.67741935483866, 790, 317.16666666666663, 790], 165 | [440, 676, 440, 660], 166 | [355.9722222222223, 790, 350.42857142857144, 790], 167 | [440, 760, 440, 680], 168 | [672.9032258064516, 790, 425.26785714285717, 790], 169 | [600, 620, 610, 660], 170 | [600, 600, 600, 620], 171 | [610, 550, 600, 600] 172 | ] 173 | }, { 174 | file: 'amitp.json', 175 | query: [550, 536, 479.28932188134524, 536, 550, 465.28932188134524], 176 | output: [ 177 | [520, 440, 550, 440], 178 | [423.19101123595505, 140, 426.25, 140], 179 | [420, 180, 436, 180], 180 | [323.984375, 10, 357.92134831460675, 10], 181 | [360, 280, 440, 280], 182 | [360, 439.27272727272725, 360, 280], 183 | [440, 520, 440, 480], 184 | [440, 536, 440, 520] 185 | ] 186 | }, { 187 | file: 'amitp.json', 188 | query: [332, 386, 380.7903679018718, 290.5405845398161, 427.4594154601839, 434.7903679018718], 189 | output: [[360, 331.2173913043478, 360, 400.31111111111113]] 190 | }, { 191 | file: 'amitp.json', 192 | query: [332, 386, 345.6256212499073, 264.21600086401304, 423.0689887208956, 304.0034190142456], 193 | output: [ 194 | [360, 280, 360, 360.7893953820272], 195 | [343.8596520293065, 280.0000000000001, 360, 280] 196 | ] 197 | }, { 198 | file: 'ipa/mei-2.json', 199 | query: [459, 440], 200 | output: [ 201 | [332, 462, 450, 388], 202 | [347, 536, 332, 462], 203 | [386, 639, 254.27586206896552, 615.4778325123152], 204 | [459, 602.8724489795918, 386, 639], 205 | [533, 411, 459, 514], 206 | [730.3329161451815, 331.46683354192743, 731.9184706332667, 333.04546421128737], 207 | [561, 313, 609, 380], 208 | [532, 134, 627.9989491583714, 229.57973977340035], 209 | [416.73751474636254, 195.81675186787243, 532, 134] 210 | ] 211 | }, { 212 | file: 'strain.json', 213 | query: [97, 156], 214 | output: [ 215 | [5, 201, 53, 98], 216 | [194, 288, 5, 201], 217 | [294.9739572736521, 216.60427263479147, 194, 288], 218 | [413, 43, 146, 171], 219 | [278, 5, 413, 43], 220 | [196.30195510499638, 38.76852522326817, 278, 5], 221 | [53, 98, 196.30195510499638, 38.76852522326817] 222 | ] 223 | }, { 224 | file: 'strain.json', 225 | query: [330, 109], 226 | output: [ 227 | [392, 148, 248.2189868368568, 249.66334264060632], 228 | [413, 43, 392, 148], 229 | [146, 171, 413, 43], 230 | [194, 288, 26.963427829474142, 211.11014931832938], 231 | [248.2189868368568, 249.66334264060632, 194, 288] 232 | ] 233 | }, { 234 | file: 'strain.json', 235 | query: [102, 192], 236 | output: [ 237 | [194, 288, 5, 201], 238 | [324.46860547150584, 195.74947087873323, 194, 288], 239 | [392, 148, 324.46860547150584, 195.74947087873323], 240 | [412.8743718592965, 43.62814070351757, 392, 148], 241 | [413, 43, 146, 171], 242 | [278, 5, 413, 43], 243 | [184.0410117176336, 43.836381823378105, 278, 5], 244 | [53, 98, 184.0410117176336, 43.836381823378105], 245 | [5, 201, 53, 98] 246 | ] 247 | }, { 248 | file: 'strain.json', 249 | query: [146, 171], 250 | output: [] 251 | }, { 252 | file: 'tri.json', 253 | query: [125, 118], 254 | output: [] 255 | }, { 256 | file: 'ipa/matisse-nuit.json', 257 | query: [457, 249], 258 | output: [ 259 | [453, 234, 460, 250], 260 | [401, 138, 453, 234], 261 | [371, 99, 375.6817975487971, 87.81570585565134], 262 | [344.61708394698087, 89.79086892488957, 371, 99], 263 | [445, 238, 445, 232], 264 | [440, 242, 445, 238], 265 | [421, 275, 440, 242], 266 | [396, 359, 421, 275], 267 | [377, 415, 396, 359], 268 | [362, 472, 377, 415], 269 | [345, 547, 362, 472], 270 | [299, 689, 293.8902272105119, 682.9885026006023], 271 | [309.54466230936816, 701.653594771242, 299, 689], 272 | [337, 637, 328, 645], 273 | [337.19060585432265, 637.0190605854323, 337, 637], 274 | [383, 492, 369, 534], 275 | [401, 446, 383, 492], 276 | [420, 398, 401, 446], 277 | [441, 333, 420, 398], 278 | [455, 281, 441, 333], 279 | [460, 250, 455, 281] 280 | ] 281 | }, { 282 | file: 'ipa/matisse-nuit.json', 283 | query: [352, 602], 284 | output: [ 285 | [318, 610, 323, 595], 286 | [271, 623, 271.23023255813956, 621.0046511627907], 287 | [282, 669, 271, 623], 288 | [299, 689, 282, 669], 289 | [301.6601671309192, 692.1922005571031, 299, 689], 290 | [337, 637, 328, 645], 291 | [347, 638, 337, 637], 292 | [355, 647, 347, 638], 293 | [364, 685, 355, 647], 294 | [377, 759, 375.2477064220183, 762.7966360856267], 295 | [389, 749, 377, 759], 296 | [399, 762, 389, 749], 297 | [418, 768, 399, 762], 298 | [430.3447136563877, 757.3386563876652, 418, 768], 299 | [405, 704, 410, 717], 300 | [407, 684, 405, 704], 301 | [412.9230769230769, 672.1538461538462, 407, 684], 302 | [379, 633, 385, 640], 303 | [380, 614, 379, 633], 304 | [395, 576, 380, 614], 305 | [428, 509, 395, 576], 306 | [455, 461, 428, 509], 307 | [500, 378, 455, 461], 308 | [510.28985507246375, 355.36231884057975, 500, 378], 309 | [394.2061855670103, 535.9381443298969, 395, 535], 310 | [365, 551, 375, 566], 311 | [369, 534, 365, 551], 312 | [440, 242, 442.5, 240], 313 | [421, 275, 440, 242], 314 | [396, 359, 421, 275], 315 | [377, 415, 396, 359], 316 | [362, 472, 377, 415], 317 | [345, 547, 362, 472], 318 | [336, 550, 345, 547], 319 | [323, 527, 336, 550], 320 | [302, 520, 305.3826429980276, 481.43786982248525], 321 | [319, 571, 302, 520], 322 | [323, 595, 319, 571] 323 | ] 324 | }, { 325 | file: 'ipa/seidel-3.json', 326 | query: [256, 352], 327 | output: [ 328 | [175, 282, 247, 315], 329 | [112, 746, 114.77940300483155, 229.9575087696075], 330 | [198, 657, 112, 746], 331 | [285, 780, 198, 657], 332 | [294, 490, 285, 780], 333 | [384, 583, 294, 490], 334 | [430, 650, 384, 583], 335 | [513, 540, 430, 650], 336 | [616, 516, 582.9042881165919, 591.1362107623319], 337 | [707, 287, 616, 516], 338 | [579, 216, 707, 287], 339 | [594, 101, 579, 216], 340 | [506, 155, 594, 101], 341 | [505, 17, 506, 155], 342 | [451.89786812097174, 70.79176995537927, 505, 17], 343 | [247, 315, 318, 263] 344 | ] 345 | }, { 346 | file: 'ipa/seidel-3.json', 347 | query: [256, 352, 413.2187408195795, 485.0122833993383, 234.99260397048673, 556.8626108201768], 348 | output: [ 349 | [285, 780, 221.34034829332205, 689.9984234491795], 350 | [294, 490, 285, 780], 351 | [384, 583, 294, 490], 352 | [430, 650, 384, 583], 353 | [499.44587113039216, 557.9633033211669, 430, 650] 354 | ] 355 | }, { 356 | file: 'ipa/seidel-3.json', 357 | query: [256, 352, 355.5263016439092, 94.86284733417926, 524.4212271396464, 288.9520434839767], 358 | output: [ 359 | [579, 216, 655.1581590242263, 258.2439788337506], 360 | [594, 101, 579, 216], 361 | [506, 155, 594, 101], 362 | [505, 17, 506, 155], 363 | [451.89786812097174, 70.79176995537927, 505, 17], 364 | [279.5475259828696, 291.16237533648984, 318, 263] 365 | ] 366 | }, { 367 | file: 'ipa/seidel-3.json', 368 | query: [256, 352, 182.2758425261011, 355.8403912253514, 211.2025977215036, 293.3212751578686], 369 | output: [ 370 | [175, 282, 217.393275160481, 301.43025111522047], 371 | [114.08226559954085, 359.3926870185832, 114.77940300483158, 229.95750876960753] 372 | ] 373 | }]; 374 | 375 | function sqdist(x1: number, y1: number, x2: number, y2: number){ 376 | const dx = x2 - x1, 377 | dy = y2 - y1; 378 | 379 | return dx * dx + dy * dy; 380 | } 381 | 382 | function validatePoly(t: Test, qx: number, qy: number, poly: Segment[]){ 383 | const len = poly.length; 384 | if(!len){ 385 | return; 386 | } 387 | 388 | let [plx, ply, prx, pry] = poly[0], 389 | failed = false; 390 | for(let i = 1; i < len; i++){ 391 | const [lx, ly, rx, ry] = poly[i], 392 | oSeg = orient2d(qx, qy, rx, ry, lx, ly), 393 | oPrev = orient2d(qx, qy, prx, pry, rx, ry); 394 | if(oSeg < 0){ 395 | t.fail(`segment ${i} [${lx}, ${ly}, ${rx}, ${ry}] not oriented left-right (${oSeg})`); 396 | failed = true; 397 | } 398 | if(oPrev < 0){ 399 | t.fail(`segment ${i}: [${lx}, ${ly}, ${rx}, ${ry}] not left of previous: [${plx}, ${ply}, ${prx}, ${pry}] (${oPrev})`); 400 | failed = true; 401 | } 402 | ([plx, ply, prx, pry] = [lx, ly, rx, ry]); 403 | } 404 | 405 | t.assert(!failed, `segments oriented counter-clockwise`); 406 | return failed; 407 | } 408 | 409 | function compareSegs(p: Segment, r: Segment){ 410 | const dl = sqdist(p[0], p[1], r[0], r[1]), 411 | dr = sqdist(p[2], p[3], r[2], r[3]); 412 | return Math.max(dl, dr); 413 | } 414 | 415 | function comparePolys(t: Test, poly: Segment[], ref: Segment[]){ 416 | if(poly.length !== ref.length){ 417 | t.fail(`wrong number of segments: ${poly.length} !== ${ref.length}`); 418 | return true; 419 | } 420 | 421 | const len = poly.length; 422 | if(!len){ 423 | t.pass(`empty polygon as expected`); 424 | return false; 425 | } 426 | 427 | // Find the first segment, as it may be shifted from the reference. As long 428 | // as all the segments are present, we're fine. 429 | let start = 0; 430 | for(; start < len; start++){ 431 | const p = poly[start], 432 | r = ref[0], 433 | err = compareSegs(p, r); 434 | if(err <= EPSILON){ 435 | break; 436 | } 437 | } 438 | 439 | if(start === len){ 440 | t.fail(`starting segment not found`); 441 | return true; 442 | } 443 | 444 | let maxErr = 0, 445 | failed = false; 446 | for(let i = 0, j = start; i < len; i++, j++){ 447 | const p = poly[j % len], 448 | r = ref[i], 449 | err = compareSegs(p, r); 450 | if(err > EPSILON){ 451 | t.fail(`segment ${i} not equal to reference`); 452 | failed = true; 453 | } 454 | maxErr = Math.max(maxErr, err); 455 | } 456 | 457 | t.assert(!failed, `visiblity polygon equal to reference, maximum error: ${maxErr}`); 458 | return failed; 459 | } 460 | 461 | function testExample(t: Test){ 462 | // Points to be triangulated 463 | const points = [[53,98],[5,201],[194,288],[280,195],[392,148],[413,43],[278,5],[169,71],[146,171]], 464 | // Edges to be constrained 465 | edges: P2[] = [[5, 8]], 466 | // Triangulate 467 | del = Delaunator.from(points), 468 | // Constrain the triangulation 469 | con = new Constrainautor(del); 470 | con.constrainAll(edges); 471 | 472 | // Query point 473 | const qx = 162, qy = 262, 474 | // Obstruction callback: use constrained edges as obstructions 475 | obstructs = (edg: number) => con.isConstrained(edg), 476 | // Left & right end-points of the initial viewing cone (optional) 477 | ilx = 45, ily = 144, irx = 280, iry = 145, 478 | // Compute visibility polygon 479 | poly = triangularExpansion(del, qx, qy, obstructs, ilx, ily, irx, iry); 480 | 481 | validatePoly(t, qx, qy, poly); 482 | comparePolys(t, poly, [ 483 | [146, 171, 354.6687325689495, 70.96405330027889], 484 | [53, 98, 127.73364294495288, 67.11009424941949], 485 | [35.85927180355631, 134.78114592153543, 53, 98] 486 | ]); 487 | t.end(); 488 | } 489 | 490 | function testFile(t: Test, ref: TestCase, test: TestFile){ 491 | const {points, edges, name} = test, 492 | {file, query: [qx, qy, ilx = NaN, ily = NaN, irx = NaN, iry = NaN], output} = ref, 493 | del = Delaunator.from(points), 494 | con = new Constrainautor(del); 495 | con.constrainAll(edges); 496 | 497 | const vis = triangularExpansion(del, qx, qy, 498 | edg => con.isConstrained(edg), ilx, ily, irx, iry); 499 | 500 | validatePoly(t, qx, qy, vis); 501 | comparePolys(t, vis, output); 502 | 503 | t.end(); 504 | } 505 | 506 | function main(args: string[]){ 507 | if(!args.length){ 508 | tape("Example", testExample); 509 | } 510 | 511 | for(const ref of references){ 512 | tape(ref.file, (t) => testFile(t, ref, findTest(testFiles, ref.file)!)); 513 | } 514 | } 515 | 516 | main(process.argv.slice(2)); 517 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": false, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | "importHelpers": false, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | "strictNullChecks": true, /* Enable strict null checks. */ 27 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } --------------------------------------------------------------------------------