├── .gitignore ├── LICENSE ├── README.org ├── create.js ├── docs ├── .nojekyll ├── _bundle.js ├── diagrams.js ├── index.html └── style.css ├── index.d.ts ├── index.js ├── package.json ├── pnpm-lock.yaml └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | /*.graphdata 2 | node_modules/ 3 | 4 | /dist/dual-mesh.js 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | [[http://unmaintained.tech/][http://unmaintained.tech/badge.svg]] 2 | 3 | *This is my 2017 library* but in 2023 I abandoned this and switched to a [[https://www.redblobgames.com/x/2312-dual-mesh/][newer library]]. 4 | 5 | Dual mesh library for my polygon map generator projects (mapgen2, mapgen4). Feel free to use this, but it's not a stable library and I do make breaking changes. The create.js interface is the most likely to change in the future. 6 | 7 | This is a wrapper around [[https://mapbox.github.io/delaunator/][Delaunator]]. I wrote the [[https://mapbox.github.io/delaunator/][Delaunator Guide]] based on the code from this project. The code in the guide is easier to read and more general but less efficient than the code in this library. 8 | 9 | [[https://redblobgames.github.io/dual-mesh/][Documentation is here]], but it's a bit rough. See [[http://www.redblobgames.com/x/1721-voronoi-alternative/][my blog post about centroid polygons]] and [[http://www.redblobgames.com/x/1722-b-rep-triangle-meshes/][my blog post about the dual mesh data structure]] for the history. Those blog posts used the names “seeds, edges, triangles” but now I call them “regions, sides, triangles”, and I use “ghost” elements to eliminate the boundaries. 10 | 11 | The naming convention is: =x_name_y= takes type x (r, s, t) as input and produces type y (r, s, t) as output. For example, =s_begin_r= is a function that takes a side (s) as input and returns a region (r), and could be called ~r = mesh.s_begin_r(s)~. In 2023 [[https://www.redblobgames.com/x/2312-dual-mesh/][I changed conventions]] to use =y_from_x=, putting the output first. 12 | 13 | For efficiency, the accessors never allocate new arrays, but take a parameter where the result should be written: 14 | 15 | #+begin_src js 16 | let out_r = []; 17 | mesh.t_circulate_r(out_r, t); 18 | // output written into out_r 19 | #+end_src 20 | 21 | For convenience, they also return the array, so this works: 22 | 23 | #+begin_src js 24 | let out_r = mesh.t_circulate_r([], t); 25 | #+end_src 26 | 27 | To create a mesh, use the =MeshBuilder=: 28 | 29 | #+begin_src js 30 | let mesh = new MeshBuilder() 31 | .addPoints(array_of_points) 32 | .create(); 33 | #+end_src 34 | 35 | #+begin_src js 36 | let Poisson = require('poisson-disk-sampling'); 37 | let mesh = new MeshBuilder({boundarySpacing: 75}) 38 | .addPoisson(Poisson, 75) 39 | .create(); 40 | #+end_src 41 | 42 | ** Built with 43 | 44 | - [[https://github.com/mapbox/delaunator][delaunator]] to build the Delaunay triangulation 45 | - [[https://github.com/kchapelier/poisson-disk-sampling][poisson-disk-sampling]] to choose evenly spaced points 46 | 47 | -------------------------------------------------------------------------------- /create.js: -------------------------------------------------------------------------------- 1 | /* 2 | * From https://github.com/redblobgames/dual-mesh 3 | * Copyright 2017 Red Blob Games 4 | * License: Apache v2.0 5 | * 6 | * Generate a random triangle mesh for the area 0 <= x <= 1000, 0 <= y <= 1000 7 | * 8 | * This program runs on the command line (node) 9 | */ 10 | 11 | 'use strict'; 12 | 13 | let Delaunator = require('delaunator'); // ISC licensed 14 | let TriangleMesh = require('./'); 15 | 16 | function s_next_s(s) { return (s % 3 == 2) ? s-2 : s+1; } 17 | 18 | 19 | function checkPointInequality({_r_vertex, _triangles, _halfedges}) { 20 | // TODO: check for collinear vertices. Around each red point P if 21 | // there's a point Q and R both connected to it, and the angle P→Q and 22 | // the angle P→R are 180° apart, then there's collinearity. This would 23 | // indicate an issue with point selection. 24 | } 25 | 26 | 27 | function checkTriangleInequality({_r_vertex, _triangles, _halfedges}) { 28 | // check for skinny triangles 29 | const badAngleLimit = 30; 30 | let summary = new Array(badAngleLimit).fill(0); 31 | let count = 0; 32 | for (let s = 0; s < _triangles.length; s++) { 33 | let r0 = _triangles[s], 34 | r1 = _triangles[s_next_s(s)], 35 | r2 = _triangles[s_next_s(s_next_s(s))]; 36 | let p0 = _r_vertex[r0], 37 | p1 = _r_vertex[r1], 38 | p2 = _r_vertex[r2]; 39 | let d0 = [p0[0]-p1[0], p0[1]-p1[1]]; 40 | let d2 = [p2[0]-p1[0], p2[1]-p1[1]]; 41 | let dotProduct = d0[0] * d2[0] + d0[1] + d2[1]; 42 | let angleDegrees = 180 / Math.PI * Math.acos(dotProduct); 43 | if (angleDegrees < badAngleLimit) { 44 | summary[angleDegrees|0]++; 45 | count++; 46 | } 47 | } 48 | // NOTE: a much faster test would be the ratio of the inradius to 49 | // the circumradius, but as I'm generating these offline, I'm not 50 | // worried about speed right now 51 | 52 | // TODO: consider adding circumcenters of skinny triangles to the point set 53 | if (count > 0) { 54 | console.log(' bad angles:', summary.join(" ")); 55 | } 56 | } 57 | 58 | 59 | function checkMeshConnectivity({_r_vertex, _triangles, _halfedges}) { 60 | // 1. make sure each side's opposite is back to itself 61 | // 2. make sure region-circulating starting from each side works 62 | let ghost_r = _r_vertex.length - 1, out_s = []; 63 | for (let s0 = 0; s0 < _triangles.length; s0++) { 64 | if (_halfedges[_halfedges[s0]] !== s0) { 65 | console.log(`FAIL _halfedges[_halfedges[${s0}]] !== ${s0}`); 66 | } 67 | let s = s0, count = 0; 68 | out_s.length = 0; 69 | do { 70 | count++; out_s.push(s); 71 | s = s_next_s(_halfedges[s]); 72 | if (count > 100 && _triangles[s0] !== ghost_r) { 73 | console.log(`FAIL to circulate around region with start side=${s0} from region ${_triangles[s0]} to ${_triangles[s_next_s(s0)]}, out_s=${out_s}`); 74 | break; 75 | } 76 | } while (s !== s0); 77 | } 78 | } 79 | 80 | 81 | /* 82 | * Add vertices evenly along the boundary of the mesh; 83 | * use a slight curve so that the Delaunay triangulation 84 | * doesn't make long thing triangles along the boundary. 85 | * These points also prevent the Poisson disc generator 86 | * from making uneven points near the boundary. 87 | */ 88 | function addBoundaryPoints(spacing, size) { 89 | let N = Math.ceil(size/spacing); 90 | let points = []; 91 | for (let i = 0; i <= N; i++) { 92 | let t = (i + 0.5) / (N + 1); 93 | let w = size * t; 94 | let offset = Math.pow(t - 0.5, 2); 95 | points.push([offset, w], [size-offset, w]); 96 | points.push([w, offset], [w, size-offset]); 97 | } 98 | return points; 99 | } 100 | 101 | 102 | function addGhostStructure({_r_vertex, _triangles, _halfedges}) { 103 | const numSolidSides = _triangles.length; 104 | const ghost_r = _r_vertex.length; 105 | 106 | let numUnpairedSides = 0, firstUnpairedEdge = -1; 107 | let r_unpaired_s = []; // seed to side 108 | for (let s = 0; s < numSolidSides; s++) { 109 | if (_halfedges[s] === -1) { 110 | numUnpairedSides++; 111 | r_unpaired_s[_triangles[s]] = s; 112 | firstUnpairedEdge = s; 113 | } 114 | } 115 | 116 | let r_newvertex = _r_vertex.concat([[500, 500]]); 117 | let s_newstart_r = new Int32Array(numSolidSides + 3 * numUnpairedSides); 118 | s_newstart_r.set(_triangles); 119 | let s_newopposite_s = new Int32Array(numSolidSides + 3 * numUnpairedSides); 120 | s_newopposite_s.set(_halfedges); 121 | 122 | for (let i = 0, s = firstUnpairedEdge; 123 | i < numUnpairedSides; 124 | i++, s = r_unpaired_s[s_newstart_r[s_next_s(s)]]) { 125 | 126 | // Construct a ghost side for s 127 | let ghost_s = numSolidSides + 3 * i; 128 | s_newopposite_s[s] = ghost_s; 129 | s_newopposite_s[ghost_s] = s; 130 | s_newstart_r[ghost_s] = s_newstart_r[s_next_s(s)]; 131 | 132 | // Construct the rest of the ghost triangle 133 | s_newstart_r[ghost_s + 1] = s_newstart_r[s]; 134 | s_newstart_r[ghost_s + 2] = ghost_r; 135 | let k = numSolidSides + (3 * i + 4) % (3 * numUnpairedSides); 136 | s_newopposite_s[ghost_s + 2] = k; 137 | s_newopposite_s[k] = ghost_s + 2; 138 | } 139 | 140 | return { 141 | numSolidSides, 142 | _r_vertex: r_newvertex, 143 | _triangles: s_newstart_r, 144 | _halfedges: s_newopposite_s 145 | }; 146 | } 147 | 148 | 149 | 150 | /** 151 | * Build a dual mesh from points, with ghost triangles around the exterior. 152 | * 153 | * The builder assumes 0 ≤ x < 1000, 0 ≤ y < 1000 154 | * 155 | * Options: 156 | * - To have equally spaced points added around the 1000x1000 boundary, 157 | * pass in boundarySpacing > 0 with the spacing value. If using Poisson 158 | * disc points, I recommend 1.5 times the spacing used for Poisson disc. 159 | * 160 | * Phases: 161 | * - Your own set of points 162 | * - Poisson disc points 163 | * 164 | * The mesh generator runs some sanity checks but does not correct the 165 | * generated points. 166 | * 167 | * Examples: 168 | * 169 | * Build a mesh with poisson disc points and a boundary: 170 | * 171 | * new MeshBuilder({boundarySpacing: 150}) 172 | * .addPoisson(Poisson, 100) 173 | * .create() 174 | */ 175 | class MeshBuilder { 176 | /** If boundarySpacing > 0 there will be a boundary added around the 1000x1000 area */ 177 | constructor ({boundarySpacing=0} = {}) { 178 | let boundaryPoints = boundarySpacing > 0 ? addBoundaryPoints(boundarySpacing, 1000) : []; 179 | this.points = boundaryPoints; 180 | this.numBoundaryRegions = boundaryPoints.length; 181 | } 182 | 183 | /** Points should be [x, y] */ 184 | addPoints(newPoints) { 185 | for (let p of newPoints) { 186 | this.points.push(p); 187 | } 188 | return this; 189 | } 190 | 191 | /** Points will be [x, y] */ 192 | getNonBoundaryPoints() { 193 | return this.points.slice(this.numBoundaryRegions); 194 | } 195 | 196 | /** (used for more advanced mixing of different mesh types) */ 197 | clearNonBoundaryPoints() { 198 | this.points.splice(this.numBoundaryRegions, this.points.length); 199 | return this; 200 | } 201 | 202 | /** Pass in the constructor from the poisson-disk-sampling module */ 203 | addPoisson(Poisson, spacing, random=Math.random) { 204 | let generator = new Poisson({ 205 | shape: [1000, 1000], 206 | minDistance: spacing, 207 | }, random); 208 | this.points.forEach(p => generator.addPoint(p)); 209 | this.points = generator.fill(); 210 | return this; 211 | } 212 | 213 | /** Build and return a TriangleMesh */ 214 | create(runChecks=false) { 215 | // TODO: use Float32Array instead of this, so that we can 216 | // construct directly from points read in from a file 217 | let delaunator = Delaunator.from(this.points); 218 | let graph = { 219 | _r_vertex: this.points, 220 | _triangles: delaunator.triangles, 221 | _halfedges: delaunator.halfedges 222 | }; 223 | 224 | if (runChecks) { 225 | checkPointInequality(graph); 226 | checkTriangleInequality(graph); 227 | } 228 | 229 | graph = addGhostStructure(graph); 230 | graph.numBoundaryRegions = this.numBoundaryRegions; 231 | if (runChecks) { 232 | checkMeshConnectivity(graph); 233 | } 234 | 235 | return new TriangleMesh(graph); 236 | } 237 | } 238 | 239 | 240 | module.exports = MeshBuilder; 241 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_bundle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | (() => { 3 | var __getOwnPropNames = Object.getOwnPropertyNames; 4 | var __commonJS = (cb, mod) => function __require() { 5 | return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 6 | }; 7 | 8 | // index.js 9 | var require_dual_mesh = __commonJS({ 10 | "index.js"(exports, module) { 11 | "use strict"; 12 | var TriangleMesh = class _TriangleMesh { 13 | static s_to_t(s) { 14 | return s / 3 | 0; 15 | } 16 | static s_prev_s(s) { 17 | return s % 3 === 0 ? s + 2 : s - 1; 18 | } 19 | static s_next_s(s) { 20 | return s % 3 === 2 ? s - 2 : s + 1; 21 | } 22 | /** 23 | * Constructor takes partial mesh information and fills in the rest; the 24 | * partial information is generated in create.js or in fromDelaunator. 25 | */ 26 | constructor({ numBoundaryRegions, numSolidSides, _r_vertex, _triangles, _halfedges }) { 27 | Object.assign(this, { 28 | numBoundaryRegions, 29 | numSolidSides, 30 | _r_vertex, 31 | _triangles, 32 | _halfedges 33 | }); 34 | this._t_vertex = []; 35 | this._update(); 36 | } 37 | /** 38 | * Update internal data structures from Delaunator 39 | */ 40 | update(points, delaunator) { 41 | this._r_vertex = points; 42 | this._triangles = delaunator.triangles; 43 | this._halfedges = delaunator.halfedges; 44 | this._update(); 45 | } 46 | /** 47 | * Update internal data structures to match the input mesh. 48 | * 49 | * Use if you have updated the triangles/halfedges with Delaunator 50 | * and want the dual mesh to match the updated data. Note that 51 | * this DOES not update boundary regions or ghost elements. 52 | */ 53 | _update() { 54 | let { _triangles, _halfedges, _r_vertex, _t_vertex } = this; 55 | this.numSides = _triangles.length; 56 | this.numRegions = _r_vertex.length; 57 | this.numSolidRegions = this.numRegions - 1; 58 | this.numTriangles = this.numSides / 3; 59 | this.numSolidTriangles = this.numSolidSides / 3; 60 | if (this._t_vertex.length < this.numTriangles) { 61 | const numOldTriangles = _t_vertex.length; 62 | const numNewTriangles = this.numTriangles - numOldTriangles; 63 | _t_vertex = _t_vertex.concat(new Array(numNewTriangles)); 64 | for (let t = numOldTriangles; t < this.numTriangles; t++) { 65 | _t_vertex[t] = [0, 0]; 66 | } 67 | this._t_vertex = _t_vertex; 68 | } 69 | this._r_in_s = new Int32Array(this.numRegions); 70 | for (let s = 0; s < _triangles.length; s++) { 71 | let endpoint = _triangles[_TriangleMesh.s_next_s(s)]; 72 | if (this._r_in_s[endpoint] === 0 || _halfedges[s] === -1) { 73 | this._r_in_s[endpoint] = s; 74 | } 75 | } 76 | for (let s = 0; s < _triangles.length; s += 3) { 77 | let t = s / 3, a = _r_vertex[_triangles[s]], b = _r_vertex[_triangles[s + 1]], c = _r_vertex[_triangles[s + 2]]; 78 | if (this.s_ghost(s)) { 79 | let dx = b[0] - a[0], dy = b[1] - a[1]; 80 | let scale = 10 / Math.sqrt(dx * dx + dy * dy); 81 | _t_vertex[t][0] = 0.5 * (a[0] + b[0]) + dy * scale; 82 | _t_vertex[t][1] = 0.5 * (a[1] + b[1]) - dx * scale; 83 | } else { 84 | _t_vertex[t][0] = (a[0] + b[0] + c[0]) / 3; 85 | _t_vertex[t][1] = (a[1] + b[1] + c[1]) / 3; 86 | } 87 | } 88 | } 89 | /** 90 | * Construct a DualMesh from a Delaunator object, without any 91 | * additional boundary regions. 92 | */ 93 | static fromDelaunator(points, delaunator) { 94 | return new _TriangleMesh({ 95 | numBoundaryRegions: 0, 96 | numSolidSides: delaunator.triangles.length, 97 | _r_vertex: points, 98 | _triangles: delaunator.triangles, 99 | _halfedges: delaunator.halfedges 100 | }); 101 | } 102 | r_x(r) { 103 | return this._r_vertex[r][0]; 104 | } 105 | r_y(r) { 106 | return this._r_vertex[r][1]; 107 | } 108 | t_x(r) { 109 | return this._t_vertex[r][0]; 110 | } 111 | t_y(r) { 112 | return this._t_vertex[r][1]; 113 | } 114 | r_pos(out, r) { 115 | out.length = 2; 116 | out[0] = this.r_x(r); 117 | out[1] = this.r_y(r); 118 | return out; 119 | } 120 | t_pos(out, t) { 121 | out.length = 2; 122 | out[0] = this.t_x(t); 123 | out[1] = this.t_y(t); 124 | return out; 125 | } 126 | s_begin_r(s) { 127 | return this._triangles[s]; 128 | } 129 | s_end_r(s) { 130 | return this._triangles[_TriangleMesh.s_next_s(s)]; 131 | } 132 | s_inner_t(s) { 133 | return _TriangleMesh.s_to_t(s); 134 | } 135 | s_outer_t(s) { 136 | return _TriangleMesh.s_to_t(this._halfedges[s]); 137 | } 138 | s_next_s(s) { 139 | return _TriangleMesh.s_next_s(s); 140 | } 141 | s_prev_s(s) { 142 | return _TriangleMesh.s_prev_s(s); 143 | } 144 | s_opposite_s(s) { 145 | return this._halfedges[s]; 146 | } 147 | t_circulate_s(out_s, t) { 148 | out_s.length = 3; 149 | for (let i = 0; i < 3; i++) { 150 | out_s[i] = 3 * t + i; 151 | } 152 | return out_s; 153 | } 154 | t_circulate_r(out_r, t) { 155 | out_r.length = 3; 156 | for (let i = 0; i < 3; i++) { 157 | out_r[i] = this._triangles[3 * t + i]; 158 | } 159 | return out_r; 160 | } 161 | t_circulate_t(out_t, t) { 162 | out_t.length = 3; 163 | for (let i = 0; i < 3; i++) { 164 | out_t[i] = this.s_outer_t(3 * t + i); 165 | } 166 | return out_t; 167 | } 168 | r_circulate_s(out_s, r) { 169 | const s0 = this._r_in_s[r]; 170 | let incoming = s0; 171 | out_s.length = 0; 172 | do { 173 | out_s.push(this._halfedges[incoming]); 174 | let outgoing = _TriangleMesh.s_next_s(incoming); 175 | incoming = this._halfedges[outgoing]; 176 | } while (incoming !== -1 && incoming !== s0); 177 | return out_s; 178 | } 179 | r_circulate_r(out_r, r) { 180 | const s0 = this._r_in_s[r]; 181 | let incoming = s0; 182 | out_r.length = 0; 183 | do { 184 | out_r.push(this.s_begin_r(incoming)); 185 | let outgoing = _TriangleMesh.s_next_s(incoming); 186 | incoming = this._halfedges[outgoing]; 187 | } while (incoming !== -1 && incoming !== s0); 188 | return out_r; 189 | } 190 | r_circulate_t(out_t, r) { 191 | const s0 = this._r_in_s[r]; 192 | let incoming = s0; 193 | out_t.length = 0; 194 | do { 195 | out_t.push(_TriangleMesh.s_to_t(incoming)); 196 | let outgoing = _TriangleMesh.s_next_s(incoming); 197 | incoming = this._halfedges[outgoing]; 198 | } while (incoming !== -1 && incoming !== s0); 199 | return out_t; 200 | } 201 | ghost_r() { 202 | return this.numRegions - 1; 203 | } 204 | s_ghost(s) { 205 | return s >= this.numSolidSides; 206 | } 207 | r_ghost(r) { 208 | return r === this.numRegions - 1; 209 | } 210 | t_ghost(t) { 211 | return this.s_ghost(3 * t); 212 | } 213 | s_boundary(s) { 214 | return this.s_ghost(s) && s % 3 === 0; 215 | } 216 | r_boundary(r) { 217 | return r < this.numBoundaryRegions; 218 | } 219 | }; 220 | module.exports = TriangleMesh; 221 | } 222 | }); 223 | 224 | // node_modules/.pnpm/delaunator@4.0.1/node_modules/delaunator/delaunator.js 225 | var require_delaunator = __commonJS({ 226 | "node_modules/.pnpm/delaunator@4.0.1/node_modules/delaunator/delaunator.js"(exports, module) { 227 | (function(global, factory) { 228 | typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = global || self, global.Delaunator = factory()); 229 | })(exports, function() { 230 | "use strict"; 231 | var EPSILON = Math.pow(2, -52); 232 | var EDGE_STACK = new Uint32Array(512); 233 | var Delaunator = function Delaunator2(coords) { 234 | var n = coords.length >> 1; 235 | if (n > 0 && typeof coords[0] !== "number") { 236 | throw new Error("Expected coords to contain numbers."); 237 | } 238 | this.coords = coords; 239 | var maxTriangles = Math.max(2 * n - 5, 0); 240 | this._triangles = new Uint32Array(maxTriangles * 3); 241 | this._halfedges = new Int32Array(maxTriangles * 3); 242 | this._hashSize = Math.ceil(Math.sqrt(n)); 243 | this._hullPrev = new Uint32Array(n); 244 | this._hullNext = new Uint32Array(n); 245 | this._hullTri = new Uint32Array(n); 246 | this._hullHash = new Int32Array(this._hashSize).fill(-1); 247 | this._ids = new Uint32Array(n); 248 | this._dists = new Float64Array(n); 249 | this.update(); 250 | }; 251 | Delaunator.from = function from(points, getX, getY) { 252 | if (getX === void 0) getX = defaultGetX; 253 | if (getY === void 0) getY = defaultGetY; 254 | var n = points.length; 255 | var coords = new Float64Array(n * 2); 256 | for (var i = 0; i < n; i++) { 257 | var p = points[i]; 258 | coords[2 * i] = getX(p); 259 | coords[2 * i + 1] = getY(p); 260 | } 261 | return new Delaunator(coords); 262 | }; 263 | Delaunator.prototype.update = function update() { 264 | var ref = this; 265 | var coords = ref.coords; 266 | var hullPrev = ref._hullPrev; 267 | var hullNext = ref._hullNext; 268 | var hullTri = ref._hullTri; 269 | var hullHash = ref._hullHash; 270 | var n = coords.length >> 1; 271 | var minX = Infinity; 272 | var minY = Infinity; 273 | var maxX = -Infinity; 274 | var maxY = -Infinity; 275 | for (var i = 0; i < n; i++) { 276 | var x = coords[2 * i]; 277 | var y = coords[2 * i + 1]; 278 | if (x < minX) { 279 | minX = x; 280 | } 281 | if (y < minY) { 282 | minY = y; 283 | } 284 | if (x > maxX) { 285 | maxX = x; 286 | } 287 | if (y > maxY) { 288 | maxY = y; 289 | } 290 | this._ids[i] = i; 291 | } 292 | var cx = (minX + maxX) / 2; 293 | var cy = (minY + maxY) / 2; 294 | var minDist = Infinity; 295 | var i0, i1, i2; 296 | for (var i$1 = 0; i$1 < n; i$1++) { 297 | var d = dist(cx, cy, coords[2 * i$1], coords[2 * i$1 + 1]); 298 | if (d < minDist) { 299 | i0 = i$1; 300 | minDist = d; 301 | } 302 | } 303 | var i0x = coords[2 * i0]; 304 | var i0y = coords[2 * i0 + 1]; 305 | minDist = Infinity; 306 | for (var i$2 = 0; i$2 < n; i$2++) { 307 | if (i$2 === i0) { 308 | continue; 309 | } 310 | var d$1 = dist(i0x, i0y, coords[2 * i$2], coords[2 * i$2 + 1]); 311 | if (d$1 < minDist && d$1 > 0) { 312 | i1 = i$2; 313 | minDist = d$1; 314 | } 315 | } 316 | var i1x = coords[2 * i1]; 317 | var i1y = coords[2 * i1 + 1]; 318 | var minRadius = Infinity; 319 | for (var i$3 = 0; i$3 < n; i$3++) { 320 | if (i$3 === i0 || i$3 === i1) { 321 | continue; 322 | } 323 | var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i$3], coords[2 * i$3 + 1]); 324 | if (r < minRadius) { 325 | i2 = i$3; 326 | minRadius = r; 327 | } 328 | } 329 | var i2x = coords[2 * i2]; 330 | var i2y = coords[2 * i2 + 1]; 331 | if (minRadius === Infinity) { 332 | for (var i$4 = 0; i$4 < n; i$4++) { 333 | this._dists[i$4] = coords[2 * i$4] - coords[0] || coords[2 * i$4 + 1] - coords[1]; 334 | } 335 | quicksort(this._ids, this._dists, 0, n - 1); 336 | var hull = new Uint32Array(n); 337 | var j = 0; 338 | for (var i$5 = 0, d0 = -Infinity; i$5 < n; i$5++) { 339 | var id = this._ids[i$5]; 340 | if (this._dists[id] > d0) { 341 | hull[j++] = id; 342 | d0 = this._dists[id]; 343 | } 344 | } 345 | this.hull = hull.subarray(0, j); 346 | this.triangles = new Uint32Array(0); 347 | this.halfedges = new Uint32Array(0); 348 | return; 349 | } 350 | if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) { 351 | var i$6 = i1; 352 | var x$1 = i1x; 353 | var y$1 = i1y; 354 | i1 = i2; 355 | i1x = i2x; 356 | i1y = i2y; 357 | i2 = i$6; 358 | i2x = x$1; 359 | i2y = y$1; 360 | } 361 | var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); 362 | this._cx = center.x; 363 | this._cy = center.y; 364 | for (var i$7 = 0; i$7 < n; i$7++) { 365 | this._dists[i$7] = dist(coords[2 * i$7], coords[2 * i$7 + 1], center.x, center.y); 366 | } 367 | quicksort(this._ids, this._dists, 0, n - 1); 368 | this._hullStart = i0; 369 | var hullSize = 3; 370 | hullNext[i0] = hullPrev[i2] = i1; 371 | hullNext[i1] = hullPrev[i0] = i2; 372 | hullNext[i2] = hullPrev[i1] = i0; 373 | hullTri[i0] = 0; 374 | hullTri[i1] = 1; 375 | hullTri[i2] = 2; 376 | hullHash.fill(-1); 377 | hullHash[this._hashKey(i0x, i0y)] = i0; 378 | hullHash[this._hashKey(i1x, i1y)] = i1; 379 | hullHash[this._hashKey(i2x, i2y)] = i2; 380 | this.trianglesLen = 0; 381 | this._addTriangle(i0, i1, i2, -1, -1, -1); 382 | for (var k = 0, xp = void 0, yp = void 0; k < this._ids.length; k++) { 383 | var i$8 = this._ids[k]; 384 | var x$2 = coords[2 * i$8]; 385 | var y$2 = coords[2 * i$8 + 1]; 386 | if (k > 0 && Math.abs(x$2 - xp) <= EPSILON && Math.abs(y$2 - yp) <= EPSILON) { 387 | continue; 388 | } 389 | xp = x$2; 390 | yp = y$2; 391 | if (i$8 === i0 || i$8 === i1 || i$8 === i2) { 392 | continue; 393 | } 394 | var start = 0; 395 | for (var j$1 = 0, key = this._hashKey(x$2, y$2); j$1 < this._hashSize; j$1++) { 396 | start = hullHash[(key + j$1) % this._hashSize]; 397 | if (start !== -1 && start !== hullNext[start]) { 398 | break; 399 | } 400 | } 401 | start = hullPrev[start]; 402 | var e = start, q = void 0; 403 | while (q = hullNext[e], !orient(x$2, y$2, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) { 404 | e = q; 405 | if (e === start) { 406 | e = -1; 407 | break; 408 | } 409 | } 410 | if (e === -1) { 411 | continue; 412 | } 413 | var t = this._addTriangle(e, i$8, hullNext[e], -1, -1, hullTri[e]); 414 | hullTri[i$8] = this._legalize(t + 2); 415 | hullTri[e] = t; 416 | hullSize++; 417 | var n$1 = hullNext[e]; 418 | while (q = hullNext[n$1], orient(x$2, y$2, coords[2 * n$1], coords[2 * n$1 + 1], coords[2 * q], coords[2 * q + 1])) { 419 | t = this._addTriangle(n$1, i$8, q, hullTri[i$8], -1, hullTri[n$1]); 420 | hullTri[i$8] = this._legalize(t + 2); 421 | hullNext[n$1] = n$1; 422 | hullSize--; 423 | n$1 = q; 424 | } 425 | if (e === start) { 426 | while (q = hullPrev[e], orient(x$2, y$2, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) { 427 | t = this._addTriangle(q, i$8, e, -1, hullTri[e], hullTri[q]); 428 | this._legalize(t + 2); 429 | hullTri[q] = t; 430 | hullNext[e] = e; 431 | hullSize--; 432 | e = q; 433 | } 434 | } 435 | this._hullStart = hullPrev[i$8] = e; 436 | hullNext[e] = hullPrev[n$1] = i$8; 437 | hullNext[i$8] = n$1; 438 | hullHash[this._hashKey(x$2, y$2)] = i$8; 439 | hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e; 440 | } 441 | this.hull = new Uint32Array(hullSize); 442 | for (var i$9 = 0, e$1 = this._hullStart; i$9 < hullSize; i$9++) { 443 | this.hull[i$9] = e$1; 444 | e$1 = hullNext[e$1]; 445 | } 446 | this.triangles = this._triangles.subarray(0, this.trianglesLen); 447 | this.halfedges = this._halfedges.subarray(0, this.trianglesLen); 448 | }; 449 | Delaunator.prototype._hashKey = function _hashKey(x, y) { 450 | return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize; 451 | }; 452 | Delaunator.prototype._legalize = function _legalize(a) { 453 | var ref = this; 454 | var triangles = ref._triangles; 455 | var halfedges = ref._halfedges; 456 | var coords = ref.coords; 457 | var i = 0; 458 | var ar = 0; 459 | while (true) { 460 | var b = halfedges[a]; 461 | var a0 = a - a % 3; 462 | ar = a0 + (a + 2) % 3; 463 | if (b === -1) { 464 | if (i === 0) { 465 | break; 466 | } 467 | a = EDGE_STACK[--i]; 468 | continue; 469 | } 470 | var b0 = b - b % 3; 471 | var al = a0 + (a + 1) % 3; 472 | var bl = b0 + (b + 2) % 3; 473 | var p0 = triangles[ar]; 474 | var pr = triangles[a]; 475 | var pl = triangles[al]; 476 | var p1 = triangles[bl]; 477 | var illegal = inCircle( 478 | coords[2 * p0], 479 | coords[2 * p0 + 1], 480 | coords[2 * pr], 481 | coords[2 * pr + 1], 482 | coords[2 * pl], 483 | coords[2 * pl + 1], 484 | coords[2 * p1], 485 | coords[2 * p1 + 1] 486 | ); 487 | if (illegal) { 488 | triangles[a] = p1; 489 | triangles[b] = p0; 490 | var hbl = halfedges[bl]; 491 | if (hbl === -1) { 492 | var e = this._hullStart; 493 | do { 494 | if (this._hullTri[e] === bl) { 495 | this._hullTri[e] = a; 496 | break; 497 | } 498 | e = this._hullPrev[e]; 499 | } while (e !== this._hullStart); 500 | } 501 | this._link(a, hbl); 502 | this._link(b, halfedges[ar]); 503 | this._link(ar, bl); 504 | var br = b0 + (b + 1) % 3; 505 | if (i < EDGE_STACK.length) { 506 | EDGE_STACK[i++] = br; 507 | } 508 | } else { 509 | if (i === 0) { 510 | break; 511 | } 512 | a = EDGE_STACK[--i]; 513 | } 514 | } 515 | return ar; 516 | }; 517 | Delaunator.prototype._link = function _link(a, b) { 518 | this._halfedges[a] = b; 519 | if (b !== -1) { 520 | this._halfedges[b] = a; 521 | } 522 | }; 523 | Delaunator.prototype._addTriangle = function _addTriangle(i0, i1, i2, a, b, c) { 524 | var t = this.trianglesLen; 525 | this._triangles[t] = i0; 526 | this._triangles[t + 1] = i1; 527 | this._triangles[t + 2] = i2; 528 | this._link(t, a); 529 | this._link(t + 1, b); 530 | this._link(t + 2, c); 531 | this.trianglesLen += 3; 532 | return t; 533 | }; 534 | function pseudoAngle(dx, dy) { 535 | var p = dx / (Math.abs(dx) + Math.abs(dy)); 536 | return (dy > 0 ? 3 - p : 1 + p) / 4; 537 | } 538 | function dist(ax, ay, bx, by) { 539 | var dx = ax - bx; 540 | var dy = ay - by; 541 | return dx * dx + dy * dy; 542 | } 543 | function orientIfSure(px, py, rx, ry, qx, qy) { 544 | var l = (ry - py) * (qx - px); 545 | var r = (rx - px) * (qy - py); 546 | return Math.abs(l - r) >= 33306690738754716e-32 * Math.abs(l + r) ? l - r : 0; 547 | } 548 | function orient(rx, ry, qx, qy, px, py) { 549 | var sign = orientIfSure(px, py, rx, ry, qx, qy) || orientIfSure(rx, ry, qx, qy, px, py) || orientIfSure(qx, qy, px, py, rx, ry); 550 | return sign < 0; 551 | } 552 | function inCircle(ax, ay, bx, by, cx, cy, px, py) { 553 | var dx = ax - px; 554 | var dy = ay - py; 555 | var ex = bx - px; 556 | var ey = by - py; 557 | var fx = cx - px; 558 | var fy = cy - py; 559 | var ap = dx * dx + dy * dy; 560 | var bp = ex * ex + ey * ey; 561 | var cp = fx * fx + fy * fy; 562 | return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; 563 | } 564 | function circumradius(ax, ay, bx, by, cx, cy) { 565 | var dx = bx - ax; 566 | var dy = by - ay; 567 | var ex = cx - ax; 568 | var ey = cy - ay; 569 | var bl = dx * dx + dy * dy; 570 | var cl = ex * ex + ey * ey; 571 | var d = 0.5 / (dx * ey - dy * ex); 572 | var x = (ey * bl - dy * cl) * d; 573 | var y = (dx * cl - ex * bl) * d; 574 | return x * x + y * y; 575 | } 576 | function circumcenter(ax, ay, bx, by, cx, cy) { 577 | var dx = bx - ax; 578 | var dy = by - ay; 579 | var ex = cx - ax; 580 | var ey = cy - ay; 581 | var bl = dx * dx + dy * dy; 582 | var cl = ex * ex + ey * ey; 583 | var d = 0.5 / (dx * ey - dy * ex); 584 | var x = ax + (ey * bl - dy * cl) * d; 585 | var y = ay + (dx * cl - ex * bl) * d; 586 | return { x, y }; 587 | } 588 | function quicksort(ids, dists, left, right) { 589 | if (right - left <= 20) { 590 | for (var i = left + 1; i <= right; i++) { 591 | var temp = ids[i]; 592 | var tempDist = dists[temp]; 593 | var j = i - 1; 594 | while (j >= left && dists[ids[j]] > tempDist) { 595 | ids[j + 1] = ids[j--]; 596 | } 597 | ids[j + 1] = temp; 598 | } 599 | } else { 600 | var median = left + right >> 1; 601 | var i$1 = left + 1; 602 | var j$1 = right; 603 | swap(ids, median, i$1); 604 | if (dists[ids[left]] > dists[ids[right]]) { 605 | swap(ids, left, right); 606 | } 607 | if (dists[ids[i$1]] > dists[ids[right]]) { 608 | swap(ids, i$1, right); 609 | } 610 | if (dists[ids[left]] > dists[ids[i$1]]) { 611 | swap(ids, left, i$1); 612 | } 613 | var temp$1 = ids[i$1]; 614 | var tempDist$1 = dists[temp$1]; 615 | while (true) { 616 | do { 617 | i$1++; 618 | } while (dists[ids[i$1]] < tempDist$1); 619 | do { 620 | j$1--; 621 | } while (dists[ids[j$1]] > tempDist$1); 622 | if (j$1 < i$1) { 623 | break; 624 | } 625 | swap(ids, i$1, j$1); 626 | } 627 | ids[left + 1] = ids[j$1]; 628 | ids[j$1] = temp$1; 629 | if (right - i$1 + 1 >= j$1 - left) { 630 | quicksort(ids, dists, i$1, right); 631 | quicksort(ids, dists, left, j$1 - 1); 632 | } else { 633 | quicksort(ids, dists, left, j$1 - 1); 634 | quicksort(ids, dists, i$1, right); 635 | } 636 | } 637 | } 638 | function swap(arr, i, j) { 639 | var tmp = arr[i]; 640 | arr[i] = arr[j]; 641 | arr[j] = tmp; 642 | } 643 | function defaultGetX(p) { 644 | return p[0]; 645 | } 646 | function defaultGetY(p) { 647 | return p[1]; 648 | } 649 | return Delaunator; 650 | }); 651 | } 652 | }); 653 | 654 | // create.js 655 | var require_create = __commonJS({ 656 | "create.js"(exports, module) { 657 | "use strict"; 658 | var Delaunator = require_delaunator(); 659 | var TriangleMesh = require_dual_mesh(); 660 | function s_next_s(s) { 661 | return s % 3 == 2 ? s - 2 : s + 1; 662 | } 663 | function checkPointInequality({ _r_vertex, _triangles, _halfedges }) { 664 | } 665 | function checkTriangleInequality({ _r_vertex, _triangles, _halfedges }) { 666 | const badAngleLimit = 30; 667 | let summary = new Array(badAngleLimit).fill(0); 668 | let count = 0; 669 | for (let s = 0; s < _triangles.length; s++) { 670 | let r0 = _triangles[s], r1 = _triangles[s_next_s(s)], r2 = _triangles[s_next_s(s_next_s(s))]; 671 | let p0 = _r_vertex[r0], p1 = _r_vertex[r1], p2 = _r_vertex[r2]; 672 | let d0 = [p0[0] - p1[0], p0[1] - p1[1]]; 673 | let d2 = [p2[0] - p1[0], p2[1] - p1[1]]; 674 | let dotProduct = d0[0] * d2[0] + d0[1] + d2[1]; 675 | let angleDegrees = 180 / Math.PI * Math.acos(dotProduct); 676 | if (angleDegrees < badAngleLimit) { 677 | summary[angleDegrees | 0]++; 678 | count++; 679 | } 680 | } 681 | if (count > 0) { 682 | console.log(" bad angles:", summary.join(" ")); 683 | } 684 | } 685 | function checkMeshConnectivity({ _r_vertex, _triangles, _halfedges }) { 686 | let ghost_r = _r_vertex.length - 1, out_s = []; 687 | for (let s0 = 0; s0 < _triangles.length; s0++) { 688 | if (_halfedges[_halfedges[s0]] !== s0) { 689 | console.log(`FAIL _halfedges[_halfedges[${s0}]] !== ${s0}`); 690 | } 691 | let s = s0, count = 0; 692 | out_s.length = 0; 693 | do { 694 | count++; 695 | out_s.push(s); 696 | s = s_next_s(_halfedges[s]); 697 | if (count > 100 && _triangles[s0] !== ghost_r) { 698 | console.log(`FAIL to circulate around region with start side=${s0} from region ${_triangles[s0]} to ${_triangles[s_next_s(s0)]}, out_s=${out_s}`); 699 | break; 700 | } 701 | } while (s !== s0); 702 | } 703 | } 704 | function addBoundaryPoints(spacing, size) { 705 | let N = Math.ceil(size / spacing); 706 | let points = []; 707 | for (let i = 0; i <= N; i++) { 708 | let t = (i + 0.5) / (N + 1); 709 | let w = size * t; 710 | let offset = Math.pow(t - 0.5, 2); 711 | points.push([offset, w], [size - offset, w]); 712 | points.push([w, offset], [w, size - offset]); 713 | } 714 | return points; 715 | } 716 | function addGhostStructure({ _r_vertex, _triangles, _halfedges }) { 717 | const numSolidSides = _triangles.length; 718 | const ghost_r = _r_vertex.length; 719 | let numUnpairedSides = 0, firstUnpairedEdge = -1; 720 | let r_unpaired_s = []; 721 | for (let s = 0; s < numSolidSides; s++) { 722 | if (_halfedges[s] === -1) { 723 | numUnpairedSides++; 724 | r_unpaired_s[_triangles[s]] = s; 725 | firstUnpairedEdge = s; 726 | } 727 | } 728 | let r_newvertex = _r_vertex.concat([[500, 500]]); 729 | let s_newstart_r = new Int32Array(numSolidSides + 3 * numUnpairedSides); 730 | s_newstart_r.set(_triangles); 731 | let s_newopposite_s = new Int32Array(numSolidSides + 3 * numUnpairedSides); 732 | s_newopposite_s.set(_halfedges); 733 | for (let i = 0, s = firstUnpairedEdge; i < numUnpairedSides; i++, s = r_unpaired_s[s_newstart_r[s_next_s(s)]]) { 734 | let ghost_s = numSolidSides + 3 * i; 735 | s_newopposite_s[s] = ghost_s; 736 | s_newopposite_s[ghost_s] = s; 737 | s_newstart_r[ghost_s] = s_newstart_r[s_next_s(s)]; 738 | s_newstart_r[ghost_s + 1] = s_newstart_r[s]; 739 | s_newstart_r[ghost_s + 2] = ghost_r; 740 | let k = numSolidSides + (3 * i + 4) % (3 * numUnpairedSides); 741 | s_newopposite_s[ghost_s + 2] = k; 742 | s_newopposite_s[k] = ghost_s + 2; 743 | } 744 | return { 745 | numSolidSides, 746 | _r_vertex: r_newvertex, 747 | _triangles: s_newstart_r, 748 | _halfedges: s_newopposite_s 749 | }; 750 | } 751 | var MeshBuilder2 = class { 752 | /** If boundarySpacing > 0 there will be a boundary added around the 1000x1000 area */ 753 | constructor({ boundarySpacing = 0 } = {}) { 754 | let boundaryPoints = boundarySpacing > 0 ? addBoundaryPoints(boundarySpacing, 1e3) : []; 755 | this.points = boundaryPoints; 756 | this.numBoundaryRegions = boundaryPoints.length; 757 | } 758 | /** Points should be [x, y] */ 759 | addPoints(newPoints) { 760 | for (let p of newPoints) { 761 | this.points.push(p); 762 | } 763 | return this; 764 | } 765 | /** Points will be [x, y] */ 766 | getNonBoundaryPoints() { 767 | return this.points.slice(this.numBoundaryRegions); 768 | } 769 | /** (used for more advanced mixing of different mesh types) */ 770 | clearNonBoundaryPoints() { 771 | this.points.splice(this.numBoundaryRegions, this.points.length); 772 | return this; 773 | } 774 | /** Pass in the constructor from the poisson-disk-sampling module */ 775 | addPoisson(Poisson2, spacing, random = Math.random) { 776 | let generator = new Poisson2({ 777 | shape: [1e3, 1e3], 778 | minDistance: spacing 779 | }, random); 780 | this.points.forEach((p) => generator.addPoint(p)); 781 | this.points = generator.fill(); 782 | return this; 783 | } 784 | /** Build and return a TriangleMesh */ 785 | create(runChecks = false) { 786 | let delaunator = Delaunator.from(this.points); 787 | let graph = { 788 | _r_vertex: this.points, 789 | _triangles: delaunator.triangles, 790 | _halfedges: delaunator.halfedges 791 | }; 792 | if (runChecks) { 793 | checkPointInequality(graph); 794 | checkTriangleInequality(graph); 795 | } 796 | graph = addGhostStructure(graph); 797 | graph.numBoundaryRegions = this.numBoundaryRegions; 798 | if (runChecks) { 799 | checkMeshConnectivity(graph); 800 | } 801 | return new TriangleMesh(graph); 802 | } 803 | }; 804 | module.exports = MeshBuilder2; 805 | } 806 | }); 807 | 808 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/tiny-ndarray.js 809 | var require_tiny_ndarray = __commonJS({ 810 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/tiny-ndarray.js"(exports, module) { 811 | "use strict"; 812 | function tinyNDArrayOfInteger(gridShape) { 813 | var dimensions = gridShape.length, totalLength = 1, stride = new Array(dimensions), dimension; 814 | for (dimension = dimensions; dimension > 0; dimension--) { 815 | stride[dimension - 1] = totalLength; 816 | totalLength = totalLength * gridShape[dimension - 1]; 817 | } 818 | return { 819 | stride, 820 | data: new Uint32Array(totalLength) 821 | }; 822 | } 823 | function tinyNDArrayOfArray(gridShape) { 824 | var dimensions = gridShape.length, totalLength = 1, stride = new Array(dimensions), data = [], dimension, index; 825 | for (dimension = dimensions; dimension > 0; dimension--) { 826 | stride[dimension - 1] = totalLength; 827 | totalLength = totalLength * gridShape[dimension - 1]; 828 | } 829 | for (index = 0; index < totalLength; index++) { 830 | data.push([]); 831 | } 832 | return { 833 | stride, 834 | data 835 | }; 836 | } 837 | module.exports = { 838 | integer: tinyNDArrayOfInteger, 839 | array: tinyNDArrayOfArray 840 | }; 841 | } 842 | }); 843 | 844 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/sphere-random.js 845 | var require_sphere_random = __commonJS({ 846 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/sphere-random.js"(exports, module) { 847 | "use strict"; 848 | module.exports = sampleSphere; 849 | function sampleSphere(d, rng) { 850 | var v = new Array(d), d2 = Math.floor(d / 2) << 1, r2 = 0, rr, r, theta, h, i; 851 | for (i = 0; i < d2; i += 2) { 852 | rr = -2 * Math.log(rng()); 853 | r = Math.sqrt(rr); 854 | theta = 2 * Math.PI * rng(); 855 | r2 += rr; 856 | v[i] = r * Math.cos(theta); 857 | v[i + 1] = r * Math.sin(theta); 858 | } 859 | if (d % 2) { 860 | var x = Math.sqrt(-2 * Math.log(rng())) * Math.cos(2 * Math.PI * rng()); 861 | v[d - 1] = x; 862 | r2 += Math.pow(x, 2); 863 | } 864 | h = 1 / Math.sqrt(r2); 865 | for (i = 0; i < d; ++i) { 866 | v[i] *= h; 867 | } 868 | return v; 869 | } 870 | } 871 | }); 872 | 873 | // node_modules/.pnpm/moore@1.0.0/node_modules/moore/index.js 874 | var require_moore = __commonJS({ 875 | "node_modules/.pnpm/moore@1.0.0/node_modules/moore/index.js"(exports, module) { 876 | module.exports = function moore(range, dimensions) { 877 | range = range || 1; 878 | dimensions = dimensions || 2; 879 | var size = range * 2 + 1; 880 | var length = Math.pow(size, dimensions) - 1; 881 | var neighbors = new Array(length); 882 | for (var i = 0; i < length; i++) { 883 | var neighbor = neighbors[i] = new Array(dimensions); 884 | var index = i < length / 2 ? i : i + 1; 885 | for (var dimension = 1; dimension <= dimensions; dimension++) { 886 | var value = index % Math.pow(size, dimension); 887 | neighbor[dimension - 1] = value / Math.pow(size, dimension - 1) - range; 888 | index -= value; 889 | } 890 | } 891 | return neighbors; 892 | }; 893 | } 894 | }); 895 | 896 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/neighbourhood.js 897 | var require_neighbourhood = __commonJS({ 898 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/neighbourhood.js"(exports, module) { 899 | "use strict"; 900 | var moore = require_moore(); 901 | function getNeighbourhood(dimensionNumber) { 902 | var neighbourhood = moore(2, dimensionNumber), origin = [], dimension; 903 | neighbourhood = neighbourhood.filter(function(n) { 904 | var dist = 0; 905 | for (var d = 0; d < dimensionNumber; d++) { 906 | dist += Math.pow(Math.max(0, Math.abs(n[d]) - 1), 2); 907 | } 908 | return dist < dimensionNumber; 909 | }); 910 | for (dimension = 0; dimension < dimensionNumber; dimension++) { 911 | origin.push(0); 912 | } 913 | neighbourhood.push(origin); 914 | neighbourhood.sort(function(n1, n2) { 915 | var squareDist1 = 0, squareDist2 = 0, dimension2; 916 | for (dimension2 = 0; dimension2 < dimensionNumber; dimension2++) { 917 | squareDist1 += Math.pow(n1[dimension2], 2); 918 | squareDist2 += Math.pow(n2[dimension2], 2); 919 | } 920 | if (squareDist1 < squareDist2) { 921 | return -1; 922 | } else if (squareDist1 > squareDist2) { 923 | return 1; 924 | } else { 925 | return 0; 926 | } 927 | }); 928 | return neighbourhood; 929 | } 930 | var neighbourhoodCache = {}; 931 | function getNeighbourhoodMemoized(dimensionNumber) { 932 | if (!neighbourhoodCache[dimensionNumber]) { 933 | neighbourhoodCache[dimensionNumber] = getNeighbourhood(dimensionNumber); 934 | } 935 | return neighbourhoodCache[dimensionNumber]; 936 | } 937 | module.exports = getNeighbourhoodMemoized; 938 | } 939 | }); 940 | 941 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/implementations/fixed-density.js 942 | var require_fixed_density = __commonJS({ 943 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/implementations/fixed-density.js"(exports, module) { 944 | "use strict"; 945 | var tinyNDArray = require_tiny_ndarray().integer; 946 | var sphereRandom = require_sphere_random(); 947 | var getNeighbourhood = require_neighbourhood(); 948 | function squaredEuclideanDistance(point1, point2) { 949 | var result = 0, i = 0; 950 | for (; i < point1.length; i++) { 951 | result += Math.pow(point1[i] - point2[i], 2); 952 | } 953 | return result; 954 | } 955 | function FixedDensityPDS(options, rng) { 956 | if (typeof options.distanceFunction === "function") { 957 | throw new Error("PoissonDiskSampling: Tried to instantiate the fixed density implementation with a distanceFunction"); 958 | } 959 | this.shape = options.shape; 960 | this.minDistance = options.minDistance; 961 | this.maxDistance = options.maxDistance || options.minDistance * 2; 962 | this.maxTries = Math.ceil(Math.max(1, options.tries || 30)); 963 | this.rng = rng || Math.random; 964 | var maxShape = 0; 965 | for (var i = 0; i < this.shape.length; i++) { 966 | maxShape = Math.max(maxShape, this.shape[i]); 967 | } 968 | var floatPrecisionMitigation = Math.max(1, maxShape / 128 | 0); 969 | var epsilonDistance = 1e-14 * floatPrecisionMitigation; 970 | this.dimension = this.shape.length; 971 | this.squaredMinDistance = this.minDistance * this.minDistance; 972 | this.minDistancePlusEpsilon = this.minDistance + epsilonDistance; 973 | this.deltaDistance = Math.max(0, this.maxDistance - this.minDistancePlusEpsilon); 974 | this.cellSize = this.minDistance / Math.sqrt(this.dimension); 975 | this.neighbourhood = getNeighbourhood(this.dimension); 976 | this.currentPoint = null; 977 | this.processList = []; 978 | this.samplePoints = []; 979 | this.gridShape = []; 980 | for (var i = 0; i < this.dimension; i++) { 981 | this.gridShape.push(Math.ceil(this.shape[i] / this.cellSize)); 982 | } 983 | this.grid = tinyNDArray(this.gridShape); 984 | } 985 | FixedDensityPDS.prototype.shape = null; 986 | FixedDensityPDS.prototype.dimension = null; 987 | FixedDensityPDS.prototype.minDistance = null; 988 | FixedDensityPDS.prototype.maxDistance = null; 989 | FixedDensityPDS.prototype.minDistancePlusEpsilon = null; 990 | FixedDensityPDS.prototype.squaredMinDistance = null; 991 | FixedDensityPDS.prototype.deltaDistance = null; 992 | FixedDensityPDS.prototype.cellSize = null; 993 | FixedDensityPDS.prototype.maxTries = null; 994 | FixedDensityPDS.prototype.rng = null; 995 | FixedDensityPDS.prototype.neighbourhood = null; 996 | FixedDensityPDS.prototype.currentPoint = null; 997 | FixedDensityPDS.prototype.processList = null; 998 | FixedDensityPDS.prototype.samplePoints = null; 999 | FixedDensityPDS.prototype.gridShape = null; 1000 | FixedDensityPDS.prototype.grid = null; 1001 | FixedDensityPDS.prototype.addRandomPoint = function() { 1002 | var point = new Array(this.dimension); 1003 | for (var i = 0; i < this.dimension; i++) { 1004 | point[i] = this.rng() * this.shape[i]; 1005 | } 1006 | return this.directAddPoint(point); 1007 | }; 1008 | FixedDensityPDS.prototype.addPoint = function(point) { 1009 | var dimension, valid = true; 1010 | if (point.length === this.dimension) { 1011 | for (dimension = 0; dimension < this.dimension && valid; dimension++) { 1012 | valid = point[dimension] >= 0 && point[dimension] < this.shape[dimension]; 1013 | } 1014 | } else { 1015 | valid = false; 1016 | } 1017 | return valid ? this.directAddPoint(point) : null; 1018 | }; 1019 | FixedDensityPDS.prototype.directAddPoint = function(point) { 1020 | var internalArrayIndex = 0, stride = this.grid.stride, dimension; 1021 | this.processList.push(point); 1022 | this.samplePoints.push(point); 1023 | for (dimension = 0; dimension < this.dimension; dimension++) { 1024 | internalArrayIndex += (point[dimension] / this.cellSize | 0) * stride[dimension]; 1025 | } 1026 | this.grid.data[internalArrayIndex] = this.samplePoints.length; 1027 | return point; 1028 | }; 1029 | FixedDensityPDS.prototype.inNeighbourhood = function(point) { 1030 | var dimensionNumber = this.dimension, stride = this.grid.stride, neighbourIndex, internalArrayIndex, dimension, currentDimensionValue, existingPoint; 1031 | for (neighbourIndex = 0; neighbourIndex < this.neighbourhood.length; neighbourIndex++) { 1032 | internalArrayIndex = 0; 1033 | for (dimension = 0; dimension < dimensionNumber; dimension++) { 1034 | currentDimensionValue = (point[dimension] / this.cellSize | 0) + this.neighbourhood[neighbourIndex][dimension]; 1035 | if (currentDimensionValue < 0 || currentDimensionValue >= this.gridShape[dimension]) { 1036 | internalArrayIndex = -1; 1037 | break; 1038 | } 1039 | internalArrayIndex += currentDimensionValue * stride[dimension]; 1040 | } 1041 | if (internalArrayIndex !== -1 && this.grid.data[internalArrayIndex] !== 0) { 1042 | existingPoint = this.samplePoints[this.grid.data[internalArrayIndex] - 1]; 1043 | if (squaredEuclideanDistance(point, existingPoint) < this.squaredMinDistance) { 1044 | return true; 1045 | } 1046 | } 1047 | } 1048 | return false; 1049 | }; 1050 | FixedDensityPDS.prototype.next = function() { 1051 | var tries, angle, distance, currentPoint, newPoint, inShape, i; 1052 | while (this.processList.length > 0) { 1053 | if (this.currentPoint === null) { 1054 | this.currentPoint = this.processList.shift(); 1055 | } 1056 | currentPoint = this.currentPoint; 1057 | for (tries = 0; tries < this.maxTries; tries++) { 1058 | inShape = true; 1059 | distance = this.minDistancePlusEpsilon + this.deltaDistance * this.rng(); 1060 | if (this.dimension === 2) { 1061 | angle = this.rng() * Math.PI * 2; 1062 | newPoint = [ 1063 | Math.cos(angle), 1064 | Math.sin(angle) 1065 | ]; 1066 | } else { 1067 | newPoint = sphereRandom(this.dimension, this.rng); 1068 | } 1069 | for (i = 0; inShape && i < this.dimension; i++) { 1070 | newPoint[i] = currentPoint[i] + newPoint[i] * distance; 1071 | inShape = newPoint[i] >= 0 && newPoint[i] < this.shape[i]; 1072 | } 1073 | if (inShape && !this.inNeighbourhood(newPoint)) { 1074 | return this.directAddPoint(newPoint); 1075 | } 1076 | } 1077 | if (tries === this.maxTries) { 1078 | this.currentPoint = null; 1079 | } 1080 | } 1081 | return null; 1082 | }; 1083 | FixedDensityPDS.prototype.fill = function() { 1084 | if (this.samplePoints.length === 0) { 1085 | this.addRandomPoint(); 1086 | } 1087 | while (this.next()) { 1088 | } 1089 | return this.samplePoints; 1090 | }; 1091 | FixedDensityPDS.prototype.getAllPoints = function() { 1092 | return this.samplePoints; 1093 | }; 1094 | FixedDensityPDS.prototype.getAllPointsWithDistance = function() { 1095 | throw new Error("PoissonDiskSampling: getAllPointsWithDistance() is not available in fixed-density implementation"); 1096 | }; 1097 | FixedDensityPDS.prototype.reset = function() { 1098 | var gridData = this.grid.data, i = 0; 1099 | for (i = 0; i < gridData.length; i++) { 1100 | gridData[i] = 0; 1101 | } 1102 | this.samplePoints = []; 1103 | this.currentPoint = null; 1104 | this.processList.length = 0; 1105 | }; 1106 | module.exports = FixedDensityPDS; 1107 | } 1108 | }); 1109 | 1110 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/implementations/variable-density.js 1111 | var require_variable_density = __commonJS({ 1112 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/implementations/variable-density.js"(exports, module) { 1113 | "use strict"; 1114 | var tinyNDArray = require_tiny_ndarray().array; 1115 | var sphereRandom = require_sphere_random(); 1116 | var getNeighbourhood = require_neighbourhood(); 1117 | function euclideanDistance(point1, point2) { 1118 | var result = 0, i = 0; 1119 | for (; i < point1.length; i++) { 1120 | result += Math.pow(point1[i] - point2[i], 2); 1121 | } 1122 | return Math.sqrt(result); 1123 | } 1124 | function VariableDensityPDS(options, rng) { 1125 | if (typeof options.distanceFunction !== "function") { 1126 | throw new Error("PoissonDiskSampling: Tried to instantiate the variable density implementation without a distanceFunction"); 1127 | } 1128 | this.shape = options.shape; 1129 | this.minDistance = options.minDistance; 1130 | this.maxDistance = options.maxDistance || options.minDistance * 2; 1131 | this.maxTries = Math.ceil(Math.max(1, options.tries || 30)); 1132 | this.distanceFunction = options.distanceFunction; 1133 | this.bias = Math.max(0, Math.min(1, options.bias || 0)); 1134 | this.rng = rng || Math.random; 1135 | var maxShape = 0; 1136 | for (var i = 0; i < this.shape.length; i++) { 1137 | maxShape = Math.max(maxShape, this.shape[i]); 1138 | } 1139 | var floatPrecisionMitigation = Math.max(1, maxShape / 128 | 0); 1140 | var epsilonDistance = 1e-14 * floatPrecisionMitigation; 1141 | this.dimension = this.shape.length; 1142 | this.minDistancePlusEpsilon = this.minDistance + epsilonDistance; 1143 | this.deltaDistance = Math.max(0, this.maxDistance - this.minDistancePlusEpsilon); 1144 | this.cellSize = this.maxDistance / Math.sqrt(this.dimension); 1145 | this.neighbourhood = getNeighbourhood(this.dimension); 1146 | this.currentPoint = null; 1147 | this.currentDistance = 0; 1148 | this.processList = []; 1149 | this.samplePoints = []; 1150 | this.sampleDistance = []; 1151 | this.gridShape = []; 1152 | for (var i = 0; i < this.dimension; i++) { 1153 | this.gridShape.push(Math.ceil(this.shape[i] / this.cellSize)); 1154 | } 1155 | this.grid = tinyNDArray(this.gridShape); 1156 | } 1157 | VariableDensityPDS.prototype.shape = null; 1158 | VariableDensityPDS.prototype.dimension = null; 1159 | VariableDensityPDS.prototype.minDistance = null; 1160 | VariableDensityPDS.prototype.maxDistance = null; 1161 | VariableDensityPDS.prototype.minDistancePlusEpsilon = null; 1162 | VariableDensityPDS.prototype.deltaDistance = null; 1163 | VariableDensityPDS.prototype.cellSize = null; 1164 | VariableDensityPDS.prototype.maxTries = null; 1165 | VariableDensityPDS.prototype.distanceFunction = null; 1166 | VariableDensityPDS.prototype.bias = null; 1167 | VariableDensityPDS.prototype.rng = null; 1168 | VariableDensityPDS.prototype.neighbourhood = null; 1169 | VariableDensityPDS.prototype.currentPoint = null; 1170 | VariableDensityPDS.prototype.currentDistance = null; 1171 | VariableDensityPDS.prototype.processList = null; 1172 | VariableDensityPDS.prototype.samplePoints = null; 1173 | VariableDensityPDS.prototype.sampleDistance = null; 1174 | VariableDensityPDS.prototype.gridShape = null; 1175 | VariableDensityPDS.prototype.grid = null; 1176 | VariableDensityPDS.prototype.addRandomPoint = function() { 1177 | var point = new Array(this.dimension); 1178 | for (var i = 0; i < this.dimension; i++) { 1179 | point[i] = this.rng() * this.shape[i]; 1180 | } 1181 | return this.directAddPoint(point); 1182 | }; 1183 | VariableDensityPDS.prototype.addPoint = function(point) { 1184 | var dimension, valid = true; 1185 | if (point.length === this.dimension) { 1186 | for (dimension = 0; dimension < this.dimension && valid; dimension++) { 1187 | valid = point[dimension] >= 0 && point[dimension] < this.shape[dimension]; 1188 | } 1189 | } else { 1190 | valid = false; 1191 | } 1192 | return valid ? this.directAddPoint(point) : null; 1193 | }; 1194 | VariableDensityPDS.prototype.directAddPoint = function(point) { 1195 | var internalArrayIndex = 0, stride = this.grid.stride, pointIndex = this.samplePoints.length, dimension; 1196 | this.processList.push(pointIndex); 1197 | this.samplePoints.push(point); 1198 | this.sampleDistance.push(this.distanceFunction(point)); 1199 | for (dimension = 0; dimension < this.dimension; dimension++) { 1200 | internalArrayIndex += (point[dimension] / this.cellSize | 0) * stride[dimension]; 1201 | } 1202 | this.grid.data[internalArrayIndex].push(pointIndex); 1203 | return point; 1204 | }; 1205 | VariableDensityPDS.prototype.inNeighbourhood = function(point) { 1206 | var dimensionNumber = this.dimension, stride = this.grid.stride, neighbourIndex, internalArrayIndex, dimension, currentDimensionValue, existingPoint, existingPointDistance; 1207 | var pointDistance = this.distanceFunction(point); 1208 | for (neighbourIndex = 0; neighbourIndex < this.neighbourhood.length; neighbourIndex++) { 1209 | internalArrayIndex = 0; 1210 | for (dimension = 0; dimension < dimensionNumber; dimension++) { 1211 | currentDimensionValue = (point[dimension] / this.cellSize | 0) + this.neighbourhood[neighbourIndex][dimension]; 1212 | if (currentDimensionValue < 0 || currentDimensionValue >= this.gridShape[dimension]) { 1213 | internalArrayIndex = -1; 1214 | break; 1215 | } 1216 | internalArrayIndex += currentDimensionValue * stride[dimension]; 1217 | } 1218 | if (internalArrayIndex !== -1 && this.grid.data[internalArrayIndex].length > 0) { 1219 | for (var i = 0; i < this.grid.data[internalArrayIndex].length; i++) { 1220 | existingPoint = this.samplePoints[this.grid.data[internalArrayIndex][i]]; 1221 | existingPointDistance = this.sampleDistance[this.grid.data[internalArrayIndex][i]]; 1222 | var minDistance = Math.min(existingPointDistance, pointDistance); 1223 | var maxDistance = Math.max(existingPointDistance, pointDistance); 1224 | var dist = minDistance + (maxDistance - minDistance) * this.bias; 1225 | if (euclideanDistance(point, existingPoint) < this.minDistance + this.deltaDistance * dist) { 1226 | return true; 1227 | } 1228 | } 1229 | } 1230 | } 1231 | return false; 1232 | }; 1233 | VariableDensityPDS.prototype.next = function() { 1234 | var tries, angle, distance, currentPoint, currentDistance, newPoint, inShape, i; 1235 | while (this.processList.length > 0) { 1236 | if (this.currentPoint === null) { 1237 | var sampleIndex = this.processList.shift(); 1238 | this.currentPoint = this.samplePoints[sampleIndex]; 1239 | this.currentDistance = this.sampleDistance[sampleIndex]; 1240 | } 1241 | currentPoint = this.currentPoint; 1242 | currentDistance = this.currentDistance; 1243 | for (tries = 0; tries < this.maxTries; tries++) { 1244 | inShape = true; 1245 | distance = this.minDistancePlusEpsilon + this.deltaDistance * (currentDistance + (1 - currentDistance) * this.bias); 1246 | if (this.dimension === 2) { 1247 | angle = this.rng() * Math.PI * 2; 1248 | newPoint = [ 1249 | Math.cos(angle), 1250 | Math.sin(angle) 1251 | ]; 1252 | } else { 1253 | newPoint = sphereRandom(this.dimension, this.rng); 1254 | } 1255 | for (i = 0; inShape && i < this.dimension; i++) { 1256 | newPoint[i] = currentPoint[i] + newPoint[i] * distance; 1257 | inShape = newPoint[i] >= 0 && newPoint[i] < this.shape[i]; 1258 | } 1259 | if (inShape && !this.inNeighbourhood(newPoint)) { 1260 | return this.directAddPoint(newPoint); 1261 | } 1262 | } 1263 | if (tries === this.maxTries) { 1264 | this.currentPoint = null; 1265 | } 1266 | } 1267 | return null; 1268 | }; 1269 | VariableDensityPDS.prototype.fill = function() { 1270 | if (this.samplePoints.length === 0) { 1271 | this.addRandomPoint(); 1272 | } 1273 | while (this.next()) { 1274 | } 1275 | return this.samplePoints; 1276 | }; 1277 | VariableDensityPDS.prototype.getAllPoints = function() { 1278 | return this.samplePoints; 1279 | }; 1280 | VariableDensityPDS.prototype.getAllPointsWithDistance = function() { 1281 | var result = new Array(this.samplePoints.length), i = 0, dimension = 0, point; 1282 | for (i = 0; i < this.samplePoints.length; i++) { 1283 | point = new Array(this.dimension + 1); 1284 | for (dimension = 0; dimension < this.dimension; dimension++) { 1285 | point[dimension] = this.samplePoints[i][dimension]; 1286 | } 1287 | point[this.dimension] = this.sampleDistance[i]; 1288 | result[i] = point; 1289 | } 1290 | return result; 1291 | }; 1292 | VariableDensityPDS.prototype.reset = function() { 1293 | var gridData = this.grid.data, i = 0; 1294 | for (i = 0; i < gridData.length; i++) { 1295 | gridData[i] = []; 1296 | } 1297 | this.samplePoints = []; 1298 | this.currentPoint = null; 1299 | this.processList.length = 0; 1300 | }; 1301 | module.exports = VariableDensityPDS; 1302 | } 1303 | }); 1304 | 1305 | // node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/poisson-disk-sampling.js 1306 | var require_poisson_disk_sampling = __commonJS({ 1307 | "node_modules/.pnpm/poisson-disk-sampling@2.3.1/node_modules/poisson-disk-sampling/src/poisson-disk-sampling.js"(exports, module) { 1308 | "use strict"; 1309 | var FixedDensityPDS = require_fixed_density(); 1310 | var VariableDensityPDS = require_variable_density(); 1311 | function PoissonDiskSampling(options, rng) { 1312 | this.shape = options.shape; 1313 | if (typeof options.distanceFunction === "function") { 1314 | this.implementation = new VariableDensityPDS(options, rng); 1315 | } else { 1316 | this.implementation = new FixedDensityPDS(options, rng); 1317 | } 1318 | } 1319 | PoissonDiskSampling.prototype.implementation = null; 1320 | PoissonDiskSampling.prototype.addRandomPoint = function() { 1321 | return this.implementation.addRandomPoint(); 1322 | }; 1323 | PoissonDiskSampling.prototype.addPoint = function(point) { 1324 | return this.implementation.addPoint(point); 1325 | }; 1326 | PoissonDiskSampling.prototype.next = function() { 1327 | return this.implementation.next(); 1328 | }; 1329 | PoissonDiskSampling.prototype.fill = function() { 1330 | return this.implementation.fill(); 1331 | }; 1332 | PoissonDiskSampling.prototype.getAllPoints = function() { 1333 | return this.implementation.getAllPoints(); 1334 | }; 1335 | PoissonDiskSampling.prototype.getAllPointsWithDistance = function() { 1336 | return this.implementation.getAllPointsWithDistance(); 1337 | }; 1338 | PoissonDiskSampling.prototype.reset = function() { 1339 | this.implementation.reset(); 1340 | }; 1341 | module.exports = PoissonDiskSampling; 1342 | } 1343 | }); 1344 | 1345 | // docs/diagrams.js 1346 | var DualMesh = require_dual_mesh(); 1347 | var MeshBuilder = require_create(); 1348 | var Poisson = require_poisson_disk_sampling(); 1349 | var seeds1 = [ 1350 | [250, 30], 1351 | [100, 260], 1352 | [400, 260], 1353 | [550, 30] 1354 | ]; 1355 | var seeds2 = [ 1356 | [320, 170], 1357 | [220, 270], 1358 | [400, 270], 1359 | [530, 50], 1360 | [100, 80], 1361 | [300, 30], 1362 | [50, 220], 1363 | [550, 240] 1364 | ]; 1365 | var G0 = new MeshBuilder({ boundarySpacing: 75 }).addPoisson(Poisson, 50).create(); 1366 | var G1 = new MeshBuilder().addPoints(seeds1).create(); 1367 | var G2 = new MeshBuilder().addPoints(seeds2).create(); 1368 | function interpolate(p, q, t) { 1369 | return [p[0] * (1 - t) + q[0] * t, p[1] * (1 - t) + q[1] * t]; 1370 | } 1371 | function extrapolate_from_center(p, center) { 1372 | let dx = p[0] - center[0], dy = p[1] - center[1]; 1373 | return [center[0] + dx * 5, center[1] + dy * 5]; 1374 | } 1375 | Vue.component("a-label", { 1376 | props: ["at", "dx", "dy"], 1377 | template: '' 1378 | }); 1379 | Vue.component("a-side-black-edges", { 1380 | props: ["graph", "alpha"], 1381 | template: ` 1382 | 1383 | 1386 | 1387 | `, 1388 | methods: { 1389 | b_side: function(s) { 1390 | const alpha = this.alpha || 0; 1391 | let begin = this.graph.r_pos([], this.graph.s_begin_r(s)); 1392 | let end = this.graph.r_pos([], this.graph.s_end_r(s)); 1393 | if (this.graph.r_ghost(this.graph.s_begin_r(s))) { 1394 | begin = extrapolate_from_center(end, [300, 150]); 1395 | } else if (this.graph.r_ghost(this.graph.s_end_r(s))) { 1396 | end = extrapolate_from_center(begin, [300, 150]); 1397 | } 1398 | let center = this.graph.t_pos([], this.graph.s_inner_t(s)); 1399 | begin = interpolate(begin, center, alpha); 1400 | end = interpolate(end, center, alpha); 1401 | return `M ${begin} L ${end}`; 1402 | } 1403 | } 1404 | }); 1405 | Vue.component("a-side-white-edges", { 1406 | props: ["graph", "alpha"], 1407 | template: ` 1408 | 1409 | 1412 | 1413 | `, 1414 | methods: { 1415 | w_side: function(s) { 1416 | const alpha = this.alpha || 0; 1417 | let begin = this.graph.t_pos([], this.graph.s_inner_t(s)); 1418 | let end = this.graph.t_pos([], this.graph.s_outer_t(s)); 1419 | let center = this.graph.r_pos([], this.graph.s_begin_r(s)); 1420 | begin = interpolate(begin, center, alpha); 1421 | end = interpolate(end, center, alpha); 1422 | return `M ${begin} L ${end}`; 1423 | } 1424 | } 1425 | }); 1426 | Vue.component("a-side-labels", { 1427 | props: ["graph"], 1428 | template: ` 1429 | 1430 | 1436 | s{{s}} 1437 | 1438 | 1439 | `, 1440 | methods: { interpolate } 1441 | }); 1442 | Vue.component("a-region-points", { 1443 | props: ["graph", "hover", "radius"], 1444 | template: ` 1445 | 1446 | 1452 | 1453 | ` 1454 | }); 1455 | Vue.component("a-region-labels", { 1456 | props: ["graph"], 1457 | template: ` 1458 | 1459 | 1462 | r{{r}} 1463 | 1464 | 1465 | ` 1466 | }); 1467 | Vue.component("a-triangle-points", { 1468 | props: ["graph", "hover", "radius"], 1469 | template: ` 1470 | 1471 | 1477 | 1478 | ` 1479 | }); 1480 | Vue.component("a-triangle-labels", { 1481 | props: ["graph"], 1482 | template: ` 1483 | 1484 | 1488 | t{{t}} 1489 | 1490 | 1491 | ` 1492 | }); 1493 | function makeDiagram(selector, graph) { 1494 | new Vue({ 1495 | el: selector, 1496 | data: { 1497 | graph: Object.freeze(graph), 1498 | highlight: "" 1499 | }, 1500 | computed: { 1501 | highlightId: function() { 1502 | return parseInt(this.highlight.slice(1)); 1503 | } 1504 | }, 1505 | methods: { 1506 | hover: function(label) { 1507 | this.highlight = label; 1508 | }, 1509 | format_array: function(label, array) { 1510 | return array.map((x) => x === null || x < 0 ? "(null)" : label + x).join(" "); 1511 | } 1512 | } 1513 | }); 1514 | } 1515 | for (let diagram of document.querySelectorAll("div.diagram-g0")) { 1516 | makeDiagram(diagram, G0); 1517 | } 1518 | for (let diagram of document.querySelectorAll("div.diagram-g1")) { 1519 | makeDiagram(diagram, G1); 1520 | } 1521 | for (let diagram of document.querySelectorAll("div.diagram-g2")) { 1522 | makeDiagram(diagram, G2); 1523 | } 1524 | })(); 1525 | -------------------------------------------------------------------------------- /docs/diagrams.js: -------------------------------------------------------------------------------- 1 | /* 2 | * From https://github.com/redblobgames/dual-mesh 3 | * Copyright 2017 Red Blob Games 4 | * License: Apache v2.0 5 | */ 6 | 'use strict'; 7 | 8 | let DualMesh = require('../'); 9 | let MeshBuilder = require('../create'); 10 | let Poisson = require('poisson-disk-sampling'); 11 | 12 | const seeds1 = [ 13 | [250, 30], [100, 260], [400, 260], [550, 30] 14 | ]; 15 | 16 | const seeds2 = [ 17 | [320, 170], [220, 270], [400, 270], 18 | [530, 50], [100, 80], [300, 30], 19 | [50, 220], [550, 240], 20 | ]; 21 | 22 | let G0 = new MeshBuilder({boundarySpacing: 75}) 23 | .addPoisson(Poisson, 50) 24 | .create(); 25 | let G1 = new MeshBuilder() 26 | .addPoints(seeds1) 27 | .create(); 28 | let G2 = new MeshBuilder() 29 | .addPoints(seeds2) 30 | .create(); 31 | 32 | 33 | function interpolate(p, q, t) { 34 | return [p[0] * (1-t) + q[0] * t, p[1] * (1-t) + q[1] * t]; 35 | } 36 | 37 | function extrapolate_from_center(p, center) { 38 | let dx = p[0] - center[0], dy = p[1] - center[1]; 39 | return [center[0] + dx*5, center[1] + dy*5]; 40 | } 41 | 42 | /** Label placed near a reference point. */ 43 | Vue.component('a-label', { 44 | props: ['at', 'dx', 'dy'], 45 | template: '' 46 | }); 47 | 48 | Vue.component('a-side-black-edges', { 49 | props: ['graph', 'alpha'], 50 | template: ` 51 | 52 | 55 | 56 | `, 57 | methods: { 58 | b_side: function(s) { 59 | const alpha = this.alpha || 0.0; 60 | let begin = this.graph.r_pos([], this.graph.s_begin_r(s)); 61 | let end = this.graph.r_pos([], this.graph.s_end_r(s)); 62 | if (this.graph.r_ghost(this.graph.s_begin_r(s))) { 63 | begin = extrapolate_from_center(end, [300, 150]); 64 | } else if (this.graph.r_ghost(this.graph.s_end_r(s))) { 65 | end = extrapolate_from_center(begin, [300, 150]); 66 | } 67 | let center = this.graph.t_pos([], this.graph.s_inner_t(s)); 68 | begin = interpolate(begin, center, alpha); 69 | end = interpolate(end, center, alpha); 70 | return `M ${begin} L ${end}`; 71 | }, 72 | } 73 | }); 74 | 75 | Vue.component('a-side-white-edges', { 76 | props: ['graph', 'alpha'], 77 | template: ` 78 | 79 | 82 | 83 | `, 84 | methods: { 85 | w_side: function(s) { 86 | const alpha = this.alpha || 0.0; 87 | let begin = this.graph.t_pos([], this.graph.s_inner_t(s)); 88 | let end = this.graph.t_pos([], this.graph.s_outer_t(s)); 89 | let center = this.graph.r_pos([], this.graph.s_begin_r(s)); 90 | begin = interpolate(begin, center, alpha); 91 | end = interpolate(end, center, alpha); 92 | return `M ${begin} L ${end}`; 93 | }, 94 | } 95 | }); 96 | 97 | Vue.component('a-side-labels', { 98 | props: ['graph'], 99 | template: ` 100 | 101 | 107 | s{{s}} 108 | 109 | 110 | `, 111 | methods: {interpolate}, 112 | }); 113 | 114 | Vue.component('a-region-points', { 115 | props: ['graph', 'hover', 'radius'], 116 | template: ` 117 | 118 | 124 | 125 | `, 126 | }); 127 | 128 | Vue.component('a-region-labels', { 129 | props: ['graph'], 130 | template: ` 131 | 132 | 135 | r{{r}} 136 | 137 | 138 | `, 139 | }); 140 | 141 | Vue.component('a-triangle-points', { 142 | props: ['graph', 'hover', 'radius'], 143 | template: ` 144 | 145 | 151 | 152 | `, 153 | }); 154 | 155 | Vue.component('a-triangle-labels', { 156 | props: ['graph'], 157 | template: ` 158 | 159 | 163 | t{{t}} 164 | 165 | 166 | `, 167 | }); 168 | 169 | function makeDiagram(selector, graph) { 170 | new Vue({ 171 | el: selector, 172 | data: { 173 | graph: Object.freeze(graph), 174 | highlight: '', 175 | }, 176 | computed: { 177 | highlightId: function() { 178 | return parseInt(this.highlight.slice(1)); 179 | }, 180 | }, 181 | methods: { 182 | hover: function(label) { 183 | this.highlight = label; 184 | }, 185 | format_array: function(label, array) { 186 | return array.map((x) => (x === null || x < 0)? '(null)' : label+x).join(" "); 187 | }, 188 | } 189 | }); 190 | } 191 | 192 | for (let diagram of document.querySelectorAll("div.diagram-g0")) { 193 | makeDiagram(diagram, G0); 194 | } 195 | for (let diagram of document.querySelectorAll("div.diagram-g1")) { 196 | makeDiagram(diagram, G1); 197 | } 198 | for (let diagram of document.querySelectorAll("div.diagram-g2")) { 199 | makeDiagram(diagram, G2); 200 | } 201 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dual-mesh library 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

Dual-mesh library

15 |

@redblobgames's library for his map generation projects

16 |
17 |
18 | 19 |
20 |
21 |
Oct 2017
22 | 23 |

24 | License: Apache v2 25 |

26 | 27 |

28 | I'm using this library for my own projects; the interface isn't stable yet, so expect breaking changes in the future. 29 |

30 | 31 |

32 | For some of my map generation projects I've used an unstructured grid instead of regular grids to add variety and interestingness to the maps. I need a way to represent polygon regions (red points, outline in white) including their corners (blue points): 33 |

34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 |

44 | But I also sometimes need to visit a region's neighbors (red points, black connecting lines): 45 |

46 | 47 |
48 | 49 | 50 | 51 | 52 |
53 | 54 |

55 | Put together, these form a dual mesh structure that has both the polygons (white lines, blue points) and triangles (black lines, red points): 56 |

57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | 67 |

Structure

68 | 69 |

70 | Each element (region, side, triangle) has an integer index starting from 0. The sides are half edges, so there are two of them between each pair of regions. The sides index both between red points (black lines) and blue points (white lines); for each pair of red and blue points there are two side half-edges. For example with r0, r2, t0, t1, there are two side half-edges, s2 from r2 → r0 and s5 from r0 → r2. These two sides are called opposites. There are three sides per triangle. For example triangle t1 has sides s3, s4, s5. 71 |

72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 |

86 | Regions are polygons. Each region has N sides and N corners. For example region r0 has sides s0, s11, s8, s17, s14, and corners t0, t3, t2, t5, t4. 87 |

88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 101 |

102 | Lots of error-prone code is avoided by using sentinel values instead of nulls. In this case, the mesh will have opposites[s] == -1 at the boundaries of the map. Checking whether each side has an opposite (≥ 0) leads to error-prone code. Iterating around the vertices of a polygon loop can fail if some vertices are missing. Switching to a side's opposite can fail if there is no opposite. Finding a triangle's neighbors can fail if some triangles are off the edge of the map. The solution is to “wrap” the map around the back so that there are no more boundaries. 103 |

104 | 105 |

106 | The ghost elements are invisible elements of the dual mesh that provide the connectivity that nulls would complicate. Only the solid (non-ghost) elements are usually drawn, although it depends on context. The ghost region can be thought of as the “outside” of the map, or a region at “infinity” or the “back” of the map. Ghost triangles and sides connect the boundary of the map to the ghost region. Here are the additional ghost sides and vertices: 107 |

108 | 109 |
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 | 123 |

124 | The ghost elements eliminate the boundary from a structural point of view, but I still want a boundary in the generated maps. The boundary elements are those adjacent to the outside of the map (ghost region). In the mesh creation function the points are evenly spaced but that isn't necessary. 125 |

126 | 127 |

Operations

128 | 129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | 141 |
142 |
s_next_s(s), s_prev_s(s)
circulate around triangle (s0 → s1 → s2 → s0)
143 |
s_begin_r(s), s_end_r(s)
black edge endpoints (s0 → r0, r1)
144 |
s_inner_t(s), s_outer_t(s)
white edge endpoints (s0 → t0, t1)
145 |
s_opposite_s(s)
opposite of half-edge (s0 → s3; s3 → s0)
146 |
{t,r}_circulate_{s,r,t}(output, input)
fills neighbors and returns output; pass in [] to make a new array (t1 → r0, r2, r3; t1 → s3, s4, s5)
147 |
ghost_r()
the ghost r index (not shown)
148 |
{s,r,t}_ghost(input)
whether an element is a ghost
149 |
{s,r}_boundary(input)
whether an element is on the boundary
150 |
{r,t}_{x,y}(input)
coordinates of a region (triangle vertex) or triangle (region vertex)
151 |
152 | 153 |

Invariants

154 | 155 |

156 | If s2 === s_opposite_s(s1): 157 |

158 | 159 |
    160 |
  • s_opposite_s(s2) === s1
  • 161 |
  • s_begin_r(s1) === s_end_r(s2)
  • 162 |
  • s_begin_r(s2) === s_end_r(s1)
  • 163 |
  • s_inner_t(s1) === s_outer_t(s2)
  • 164 |
  • s_inner_t(s2) === s_outer_t(s1)
  • 165 |
166 | 167 |

168 | Properties of circulation: 169 |

170 | 171 |
    172 |
  • If s is returned by r_circulate_s(_, r), then s_begin_r(s) === r
  • 173 |
  • If s is returned by t_circulate_s(_, t), then s_inner_t(s) === t
  • 174 |
175 | 176 |

History

177 | 178 |

179 | For my 2010 polygon-map-generator project (Flash) I wrote a 180 | wrapper around the as3delaunay library that gave me access 181 | to the kinds of structures and operations I wanted to work 182 | with for polygon maps. For my 2017 map generator projects 183 | (Javascript) I wrote this wrapper around the delaunator 184 | library. See my blog post about centroid polygons and my 185 | blog post about the dual mesh data structure. This library 186 | is an evolution of that dual mesh data structure, with ghost 187 | elements and different names. 188 |

189 | 190 |

Source code

191 | 192 |

193 | Take a look at @redblobgames/dual-mesh, but at the moment I'm writing it only for myself and don't intend for others to use it. I do make breaking changes. 194 |

195 | 196 |
197 |
198 | 199 |
200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 |
224 | 226 |
227 |
228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | /* Diagrams */ 2 | 3 | text { text-anchor: middle; font-size: 16px; } 4 | .b-side { fill: none; stroke: black; stroke-width: 2.5px; marker-end: url(#arrowhead); } 5 | .w-side { fill: none; stroke: white; stroke-width: 2px; } 6 | .b-side.ghost { stroke-opacity: 0.0; } 7 | .w-side.ghost { stroke-opacity: 0.0; } 8 | .polygon { fill: none; stroke: white; stroke-width: 1.5px; } 9 | .s { color: hsl(120,50%,30%); fill: hsl(120,50%,30%); stroke: hsla(0,0%,0%,0.2); stroke-width: 0.5px; font-size: 14px; font-weight: bold; } 10 | .r { color: hsl(0,50%,50%); fill: hsl(0,50%,50%); fill-opacity: 0.8; stroke: black; stroke-width: 0.5px; font-weight: bold; } 11 | text.r { fill-opacity: 1.0; stroke: none; } 12 | .t { color: hsl(240,50%,50%); fill: hsl(240,50%,50%); stroke: white; stroke-width: 0.5px; font-weight: bold; } 13 | .t.ghost { fill-opacity: 0.0; stroke-opacity: 0.0; } 14 | text.t { stroke: none; } 15 | 16 | .show-ghosts .ghost { fill-opacity: 0.7; stroke-opacity: 0.3; } 17 | 18 | .diagram-g0 .w-side.ghost { stroke-opacity: 1.0; } 19 | .diagram-g0 .b-side { stroke-opacity: 0.3; } 20 | .diagram-g1 .b-side { marker-end: url(#arrowhead-black); } 21 | .diagram-g1 .w-side { marker-end: url(#arrowhead-white); } 22 | .diagram-g1 .ghost { marker-end: none; } 23 | 24 | /* Fonts */ 25 | 26 | body, text { 27 | font-family: "Source Sans Pro", "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", "Tahoma", sans-serif; 28 | font-size: 17px; 29 | } 30 | tt, code, kbd, samp, pre { 31 | font-family: "Source Code Pro", monospace, serif, "Segoe UI Symbol", "Symbol"; 32 | font-size: 13px; 33 | } 34 | h1, h2, h3, h4, h5, h6 { font-weight: 700; letter-spacing: -1px; } 35 | h1 { font-size: 43px; } 36 | h2 { font-size: 32px; } 37 | h3 { font-size: 24px; } 38 | h4, h5, h6 { font-size: 18px; } 39 | 40 | 41 | /* Layout */ 42 | 43 | * { 44 | box-sizing: border-box; 45 | } 46 | 47 | html { 48 | margin: 0; 49 | padding: 0; 50 | } 51 | body { 52 | margin: 0; 53 | padding: 0; 54 | line-height: 1.5; 55 | } 56 | 57 | h1, h2, h3, h4, h5, h6, p, ul, ol, dl, pre { margin: 10px 0 15px 0; } 58 | p, ul, ol, dl { text-align: justify; hyphens: auto; -webkit-hyphens: auto; } 59 | 60 | pre { 61 | width: 100%; 62 | padding: 10px; 63 | overflow: auto; 64 | } 65 | code { 66 | padding: 3px; 67 | margin: 0 3px; 68 | } 69 | header div, section div, footer div { 70 | max-width: 600px; margin: 0 auto; 71 | } 72 | header, section, footer { 73 | margin: 20px 0; 74 | } 75 | 76 | 77 | /* Colors */ 78 | 79 | body { color: hsl(0,0%,100%); background-color: hsl(0,0%,20%); } 80 | section { color: hsl(0,0%,20%); background-color: hsl(0,0%,95%); } 81 | header, footer { text-shadow: 0 0 5px black; } 82 | 83 | a { color: #fff; text-decoration: underline; } 84 | section a { color: hsl(240,50%,50%); text-decoration: none; } 85 | a:hover, a:focus { text-decoration: underline; } 86 | 87 | section { border-top: 1px solid white; border-bottom: 1px solid white; box-shadow: inset 0 0 10px hsla(0,0%,20%,0.2); } 88 | 89 | pre, svg:not(.plain) { 90 | background-color: hsl(0,0%,66%); 91 | border: 1px solid hsl(0,0%,80%); 92 | border-radius: 10px; 93 | box-shadow: inset 0 0 10px rgba(0,0,0,0.2); 94 | } 95 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare class TriangleMesh { 2 | constructor(partialMesh: any); 3 | static fromDelaunator(points: number[][], delaunator: any); 4 | update(points: number[], delaunator: any); 5 | 6 | numSides: number; 7 | numSolidSides: number; 8 | numRegions: number; 9 | numSolidRegions: number; 10 | numTriangles: number; 11 | numSolidTriangles: number; 12 | numBoundaryRegions: number; 13 | 14 | r_x(r: number): number; 15 | r_y(r: number): number; 16 | t_x(t: number): number; 17 | t_y(t: number): number; 18 | r_pos(out: number[], r: number): number[]; 19 | t_pos(out: number[], t: number): number[]; 20 | 21 | s_begin_r(s: number): number; 22 | s_end_r(s: number): number; 23 | 24 | s_inner_t(s: number): number; 25 | s_outer_t(s: number): number; 26 | 27 | s_next_s(s: number): number; 28 | s_prev_s(s: number): number; 29 | 30 | s_opposite_s(s: number): number; 31 | 32 | t_circulate_s(out_s: number[], t: number): number[]; 33 | t_circulate_r(out_s: number[], t: number): number[]; 34 | t_circulate_t(out_s: number[], t: number): number[]; 35 | r_circulate_s(out_s: number[], t: number): number[]; 36 | r_circulate_r(out_s: number[], t: number): number[]; 37 | r_circulate_t(out_s: number[], t: number): number[]; 38 | 39 | ghost_r(): number; 40 | s_ghost(s: number): boolean; 41 | r_ghost(r: number): boolean; 42 | t_ghost(t: number): boolean; 43 | s_boundary(s: number): boolean; 44 | r_boundary(s: number): boolean; 45 | 46 | /* Internals */ 47 | readonly _r_in_s: number[]; 48 | readonly _halfedges: number[]; 49 | readonly _triangles: number[]; 50 | } 51 | 52 | 53 | export = TriangleMesh; 54 | 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * From https://github.com/redblobgames/dual-mesh 3 | * Copyright 2017 Red Blob Games 4 | * License: Apache v2.0 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Represent a triangle-polygon dual mesh with: 11 | * - Regions (r) 12 | * - Sides (s) 13 | * - Triangles (t) 14 | * 15 | * Each element has an id: 16 | * - 0 <= r < numRegions 17 | * - 0 <= s < numSides 18 | * - 0 <= t < numTriangles 19 | * 20 | * Naming convention: x_name_y takes x (r, s, t) as input and produces 21 | * y (r, s, t) as output. If the output isn't a mesh index (r, s, t) 22 | * then the _y suffix is omitted. 23 | * 24 | * A side is directed. If two triangles t0, t1 are adjacent, there will 25 | * be two sides representing the boundary, one for t0 and one for t1. These 26 | * can be accessed with s_inner_t and s_outer_t. 27 | * 28 | * A side also represents the boundary between two regions. If two regions 29 | * r0, r1 are adjacent, there will be two sides representing the boundary, 30 | * s_begin_r and s_end_r. 31 | * 32 | * Each side will have a pair, accessed with s_opposite_s. 33 | * 34 | * If created using the functions in create.js, the mesh has no 35 | * boundaries; it wraps around the "back" using a "ghost" region. Some 36 | * regions are marked as the boundary; these are connected to the 37 | * ghost region. Ghost triangles and ghost sides connect these 38 | * boundary regions to the ghost region. Elements that aren't "ghost" 39 | * are called "solid". 40 | */ 41 | class TriangleMesh { 42 | static s_to_t(s) { return (s/3) | 0; } 43 | static s_prev_s(s) { return (s % 3 === 0) ? s+2 : s-1; } 44 | static s_next_s(s) { return (s % 3 === 2) ? s-2 : s+1; } 45 | 46 | /** 47 | * Constructor takes partial mesh information and fills in the rest; the 48 | * partial information is generated in create.js or in fromDelaunator. 49 | */ 50 | constructor ({numBoundaryRegions, numSolidSides, _r_vertex, _triangles, _halfedges}) { 51 | Object.assign(this, {numBoundaryRegions, numSolidSides, 52 | _r_vertex, _triangles, _halfedges}); 53 | this._t_vertex = []; 54 | this._update(); 55 | } 56 | 57 | /** 58 | * Update internal data structures from Delaunator 59 | */ 60 | update(points, delaunator) { 61 | this._r_vertex = points; 62 | this._triangles = delaunator.triangles; 63 | this._halfedges = delaunator.halfedges; 64 | this._update(); 65 | } 66 | 67 | /** 68 | * Update internal data structures to match the input mesh. 69 | * 70 | * Use if you have updated the triangles/halfedges with Delaunator 71 | * and want the dual mesh to match the updated data. Note that 72 | * this DOES not update boundary regions or ghost elements. 73 | */ 74 | _update() { 75 | let {_triangles, _halfedges, _r_vertex, _t_vertex} = this; 76 | 77 | this.numSides = _triangles.length; 78 | this.numRegions = _r_vertex.length; 79 | this.numSolidRegions = this.numRegions - 1; // TODO: only if there are ghosts 80 | this.numTriangles = this.numSides / 3; 81 | this.numSolidTriangles = this.numSolidSides / 3; 82 | 83 | if (this._t_vertex.length < this.numTriangles) { 84 | // Extend this array to be big enough 85 | const numOldTriangles = _t_vertex.length; 86 | const numNewTriangles = this.numTriangles - numOldTriangles; 87 | _t_vertex = _t_vertex.concat(new Array(numNewTriangles)); 88 | for (let t = numOldTriangles; t < this.numTriangles; t++) { 89 | _t_vertex[t] = [0, 0]; 90 | } 91 | this._t_vertex = _t_vertex; 92 | } 93 | 94 | // Construct an index for finding sides connected to a region 95 | this._r_in_s = new Int32Array(this.numRegions); 96 | for (let s = 0; s < _triangles.length; s++) { 97 | let endpoint = _triangles[TriangleMesh.s_next_s(s)]; 98 | if (this._r_in_s[endpoint] === 0 || _halfedges[s] === -1) { 99 | this._r_in_s[endpoint] = s; 100 | } 101 | } 102 | 103 | // Construct triangle coordinates 104 | for (let s = 0; s < _triangles.length; s += 3) { 105 | let t = s/3, 106 | a = _r_vertex[_triangles[s]], 107 | b = _r_vertex[_triangles[s+1]], 108 | c = _r_vertex[_triangles[s+2]]; 109 | if (this.s_ghost(s)) { 110 | // ghost triangle center is just outside the unpaired side 111 | let dx = b[0]-a[0], dy = b[1]-a[1]; 112 | let scale = 10 / Math.sqrt(dx*dx + dy*dy); // go 10units away from side 113 | _t_vertex[t][0] = 0.5 * (a[0] + b[0]) + dy*scale; 114 | _t_vertex[t][1] = 0.5 * (a[1] + b[1]) - dx*scale; 115 | } else { 116 | // solid triangle center is at the centroid 117 | _t_vertex[t][0] = (a[0] + b[0] + c[0])/3; 118 | _t_vertex[t][1] = (a[1] + b[1] + c[1])/3; 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * Construct a DualMesh from a Delaunator object, without any 125 | * additional boundary regions. 126 | */ 127 | static fromDelaunator(points, delaunator) { 128 | return new TriangleMesh({ 129 | numBoundaryRegions: 0, 130 | numSolidSides: delaunator.triangles.length, 131 | _r_vertex: points, 132 | _triangles: delaunator.triangles, 133 | _halfedges: delaunator.halfedges, 134 | }); 135 | } 136 | 137 | 138 | r_x(r) { return this._r_vertex[r][0]; } 139 | r_y(r) { return this._r_vertex[r][1]; } 140 | t_x(r) { return this._t_vertex[r][0]; } 141 | t_y(r) { return this._t_vertex[r][1]; } 142 | r_pos(out, r) { out.length = 2; out[0] = this.r_x(r); out[1] = this.r_y(r); return out; } 143 | t_pos(out, t) { out.length = 2; out[0] = this.t_x(t); out[1] = this.t_y(t); return out; } 144 | 145 | s_begin_r(s) { return this._triangles[s]; } 146 | s_end_r(s) { return this._triangles[TriangleMesh.s_next_s(s)]; } 147 | 148 | s_inner_t(s) { return TriangleMesh.s_to_t(s); } 149 | s_outer_t(s) { return TriangleMesh.s_to_t(this._halfedges[s]); } 150 | 151 | s_next_s(s) { return TriangleMesh.s_next_s(s); } 152 | s_prev_s(s) { return TriangleMesh.s_prev_s(s); } 153 | 154 | s_opposite_s(s) { return this._halfedges[s]; } 155 | 156 | t_circulate_s(out_s, t) { out_s.length = 3; for (let i = 0; i < 3; i++) { out_s[i] = 3*t + i; } return out_s; } 157 | t_circulate_r(out_r, t) { out_r.length = 3; for (let i = 0; i < 3; i++) { out_r[i] = this._triangles[3*t+i]; } return out_r; } 158 | t_circulate_t(out_t, t) { out_t.length = 3; for (let i = 0; i < 3; i++) { out_t[i] = this.s_outer_t(3*t+i); } return out_t; } 159 | 160 | r_circulate_s(out_s, r) { 161 | const s0 = this._r_in_s[r]; 162 | let incoming = s0; 163 | out_s.length = 0; 164 | do { 165 | out_s.push(this._halfedges[incoming]); 166 | let outgoing = TriangleMesh.s_next_s(incoming); 167 | incoming = this._halfedges[outgoing]; 168 | } while (incoming !== -1 && incoming !== s0); 169 | return out_s; 170 | } 171 | 172 | r_circulate_r(out_r, r) { 173 | const s0 = this._r_in_s[r]; 174 | let incoming = s0; 175 | out_r.length = 0; 176 | do { 177 | out_r.push(this.s_begin_r(incoming)); 178 | let outgoing = TriangleMesh.s_next_s(incoming); 179 | incoming = this._halfedges[outgoing]; 180 | } while (incoming !== -1 && incoming !== s0); 181 | return out_r; 182 | } 183 | 184 | r_circulate_t(out_t, r) { 185 | const s0 = this._r_in_s[r]; 186 | let incoming = s0; 187 | out_t.length = 0; 188 | do { 189 | out_t.push(TriangleMesh.s_to_t(incoming)); 190 | let outgoing = TriangleMesh.s_next_s(incoming); 191 | incoming = this._halfedges[outgoing]; 192 | } while (incoming !== -1 && incoming !== s0); 193 | return out_t; 194 | } 195 | 196 | ghost_r() { return this.numRegions - 1; } 197 | s_ghost(s) { return s >= this.numSolidSides; } 198 | r_ghost(r) { return r === this.numRegions - 1; } 199 | t_ghost(t) { return this.s_ghost(3 * t); } 200 | s_boundary(s) { return this.s_ghost(s) && (s % 3 === 0); } 201 | r_boundary(r) { return r < this.numBoundaryRegions; } 202 | } 203 | 204 | module.exports = TriangleMesh; 205 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@redblobgames/dual-mesh", 3 | "license": "Apache-2.0", 4 | "version": "2.1.1", 5 | "private": true, 6 | "main": "./index.js", 7 | "types": "./index.d.ts", 8 | "dependencies": { 9 | "delaunator": "^4.0.0" 10 | }, 11 | "devDependencies": { 12 | "poisson-disk-sampling": "^2", 13 | "tape": "*" 14 | }, 15 | "scripts": { 16 | "test": "node tests.js", 17 | "generate-docs": "esbuild --bundle docs/diagrams.js >docs/_bundle.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | delaunator: 12 | specifier: ^4.0.0 13 | version: 4.0.1 14 | devDependencies: 15 | poisson-disk-sampling: 16 | specifier: ^2 17 | version: 2.3.1 18 | tape: 19 | specifier: '*' 20 | version: 5.9.0 21 | 22 | packages: 23 | 24 | '@ljharb/resumer@0.1.3': 25 | resolution: {integrity: sha512-d+tsDgfkj9X5QTriqM4lKesCkMMJC3IrbPKHvayP00ELx2axdXvDfWkqjxrLXIzGcQzmj7VAUT1wopqARTvafw==} 26 | engines: {node: '>= 0.4'} 27 | 28 | '@ljharb/through@2.3.13': 29 | resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} 30 | engines: {node: '>= 0.4'} 31 | 32 | array-buffer-byte-length@1.0.1: 33 | resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} 34 | engines: {node: '>= 0.4'} 35 | 36 | array.prototype.every@1.1.6: 37 | resolution: {integrity: sha512-gNEqZD97w6bfQRNmHkFv7rNnGM+VWyHZT+h/rf9C+22owcXuENr66Lfo0phItpU5KoXW6Owb34q2+8MnSIZ57w==} 38 | engines: {node: '>= 0.4'} 39 | 40 | arraybuffer.prototype.slice@1.0.3: 41 | resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} 42 | engines: {node: '>= 0.4'} 43 | 44 | available-typed-arrays@1.0.7: 45 | resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 46 | engines: {node: '>= 0.4'} 47 | 48 | balanced-match@1.0.2: 49 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 50 | 51 | brace-expansion@1.1.11: 52 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 53 | 54 | call-bind@1.0.7: 55 | resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} 56 | engines: {node: '>= 0.4'} 57 | 58 | concat-map@0.0.1: 59 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 60 | 61 | data-view-buffer@1.0.1: 62 | resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} 63 | engines: {node: '>= 0.4'} 64 | 65 | data-view-byte-length@1.0.1: 66 | resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} 67 | engines: {node: '>= 0.4'} 68 | 69 | data-view-byte-offset@1.0.0: 70 | resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} 71 | engines: {node: '>= 0.4'} 72 | 73 | deep-equal@2.2.3: 74 | resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} 75 | engines: {node: '>= 0.4'} 76 | 77 | define-data-property@1.1.4: 78 | resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 79 | engines: {node: '>= 0.4'} 80 | 81 | define-properties@1.2.1: 82 | resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 83 | engines: {node: '>= 0.4'} 84 | 85 | defined@1.0.1: 86 | resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} 87 | 88 | delaunator@4.0.1: 89 | resolution: {integrity: sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==} 90 | 91 | dotignore@0.1.2: 92 | resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==} 93 | hasBin: true 94 | 95 | es-abstract@1.23.3: 96 | resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} 97 | engines: {node: '>= 0.4'} 98 | 99 | es-define-property@1.0.0: 100 | resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} 101 | engines: {node: '>= 0.4'} 102 | 103 | es-errors@1.3.0: 104 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 105 | engines: {node: '>= 0.4'} 106 | 107 | es-get-iterator@1.1.3: 108 | resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} 109 | 110 | es-object-atoms@1.0.0: 111 | resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} 112 | engines: {node: '>= 0.4'} 113 | 114 | es-set-tostringtag@2.0.3: 115 | resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} 116 | engines: {node: '>= 0.4'} 117 | 118 | es-to-primitive@1.2.1: 119 | resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 120 | engines: {node: '>= 0.4'} 121 | 122 | for-each@0.3.3: 123 | resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 124 | 125 | fs.realpath@1.0.0: 126 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 127 | 128 | function-bind@1.1.2: 129 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 130 | 131 | function.prototype.name@1.1.6: 132 | resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} 133 | engines: {node: '>= 0.4'} 134 | 135 | functions-have-names@1.2.3: 136 | resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 137 | 138 | get-intrinsic@1.2.4: 139 | resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} 140 | engines: {node: '>= 0.4'} 141 | 142 | get-package-type@0.1.0: 143 | resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} 144 | engines: {node: '>=8.0.0'} 145 | 146 | get-symbol-description@1.0.2: 147 | resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} 148 | engines: {node: '>= 0.4'} 149 | 150 | glob@7.2.3: 151 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 152 | deprecated: Glob versions prior to v9 are no longer supported 153 | 154 | globalthis@1.0.4: 155 | resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 156 | engines: {node: '>= 0.4'} 157 | 158 | gopd@1.0.1: 159 | resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 160 | 161 | has-bigints@1.0.2: 162 | resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 163 | 164 | has-dynamic-import@2.1.0: 165 | resolution: {integrity: sha512-su0anMkNEnJKZ/rB99jn3y6lV/J8Ro96hBJ28YAeVzj5rWxH+YL/AdCyiYYA1HDLV9YhmvqpWSJJj2KLo1MX6g==} 166 | engines: {node: '>= 0.4'} 167 | 168 | has-property-descriptors@1.0.2: 169 | resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 170 | 171 | has-proto@1.0.3: 172 | resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} 173 | engines: {node: '>= 0.4'} 174 | 175 | has-symbols@1.0.3: 176 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 177 | engines: {node: '>= 0.4'} 178 | 179 | has-tostringtag@1.0.2: 180 | resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 181 | engines: {node: '>= 0.4'} 182 | 183 | hasown@2.0.2: 184 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 185 | engines: {node: '>= 0.4'} 186 | 187 | inflight@1.0.6: 188 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 189 | deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. 190 | 191 | inherits@2.0.4: 192 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 193 | 194 | internal-slot@1.0.7: 195 | resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} 196 | engines: {node: '>= 0.4'} 197 | 198 | is-arguments@1.1.1: 199 | resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} 200 | engines: {node: '>= 0.4'} 201 | 202 | is-array-buffer@3.0.4: 203 | resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} 204 | engines: {node: '>= 0.4'} 205 | 206 | is-bigint@1.0.4: 207 | resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 208 | 209 | is-boolean-object@1.1.2: 210 | resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 211 | engines: {node: '>= 0.4'} 212 | 213 | is-callable@1.2.7: 214 | resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 215 | engines: {node: '>= 0.4'} 216 | 217 | is-core-module@2.15.1: 218 | resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} 219 | engines: {node: '>= 0.4'} 220 | 221 | is-data-view@1.0.1: 222 | resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} 223 | engines: {node: '>= 0.4'} 224 | 225 | is-date-object@1.0.5: 226 | resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 227 | engines: {node: '>= 0.4'} 228 | 229 | is-map@2.0.3: 230 | resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} 231 | engines: {node: '>= 0.4'} 232 | 233 | is-negative-zero@2.0.3: 234 | resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} 235 | engines: {node: '>= 0.4'} 236 | 237 | is-number-object@1.0.7: 238 | resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 239 | engines: {node: '>= 0.4'} 240 | 241 | is-regex@1.1.4: 242 | resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 243 | engines: {node: '>= 0.4'} 244 | 245 | is-set@2.0.3: 246 | resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 247 | engines: {node: '>= 0.4'} 248 | 249 | is-shared-array-buffer@1.0.3: 250 | resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} 251 | engines: {node: '>= 0.4'} 252 | 253 | is-string@1.0.7: 254 | resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 255 | engines: {node: '>= 0.4'} 256 | 257 | is-symbol@1.0.4: 258 | resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 259 | engines: {node: '>= 0.4'} 260 | 261 | is-typed-array@1.1.13: 262 | resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} 263 | engines: {node: '>= 0.4'} 264 | 265 | is-weakmap@2.0.2: 266 | resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} 267 | engines: {node: '>= 0.4'} 268 | 269 | is-weakref@1.0.2: 270 | resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 271 | 272 | is-weakset@2.0.3: 273 | resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} 274 | engines: {node: '>= 0.4'} 275 | 276 | isarray@2.0.5: 277 | resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 278 | 279 | minimatch@3.1.2: 280 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 281 | 282 | minimist@1.2.8: 283 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 284 | 285 | mock-property@1.1.0: 286 | resolution: {integrity: sha512-1/JjbLoGwv87xVsutkX0XJc0M0W4kb40cZl/K41xtTViBOD9JuFPKfyMNTrLJ/ivYAd0aPqu/vduamXO0emTFQ==} 287 | engines: {node: '>= 0.4'} 288 | 289 | moore@1.0.0: 290 | resolution: {integrity: sha512-xcsFo/jgtMuVaGePHod5TdSzxnRAQQ4wFpDmFuu34lHvx5sNMsioA84NW7iBWYZ10jHR/nyGaDkhunMJxqAzkw==} 291 | 292 | object-inspect@1.13.2: 293 | resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} 294 | engines: {node: '>= 0.4'} 295 | 296 | object-is@1.1.6: 297 | resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} 298 | engines: {node: '>= 0.4'} 299 | 300 | object-keys@1.1.1: 301 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 302 | engines: {node: '>= 0.4'} 303 | 304 | object.assign@4.1.5: 305 | resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} 306 | engines: {node: '>= 0.4'} 307 | 308 | once@1.4.0: 309 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 310 | 311 | path-is-absolute@1.0.1: 312 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 313 | engines: {node: '>=0.10.0'} 314 | 315 | path-parse@1.0.7: 316 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 317 | 318 | poisson-disk-sampling@2.3.1: 319 | resolution: {integrity: sha512-O3TzHR8IA+Do5zC7EgPdHLOYOpUJ6DikiTwqRqXdSPUhx1ZqfeH6PqAD86KKi+8Nq8vnL2navErsgURKTe089w==} 320 | 321 | possible-typed-array-names@1.0.0: 322 | resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} 323 | engines: {node: '>= 0.4'} 324 | 325 | regexp.prototype.flags@1.5.2: 326 | resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} 327 | engines: {node: '>= 0.4'} 328 | 329 | resolve@2.0.0-next.5: 330 | resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 331 | hasBin: true 332 | 333 | safe-array-concat@1.1.2: 334 | resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} 335 | engines: {node: '>=0.4'} 336 | 337 | safe-regex-test@1.0.3: 338 | resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} 339 | engines: {node: '>= 0.4'} 340 | 341 | set-function-length@1.2.2: 342 | resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 343 | engines: {node: '>= 0.4'} 344 | 345 | set-function-name@2.0.2: 346 | resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} 347 | engines: {node: '>= 0.4'} 348 | 349 | side-channel@1.0.6: 350 | resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} 351 | engines: {node: '>= 0.4'} 352 | 353 | stop-iteration-iterator@1.0.0: 354 | resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} 355 | engines: {node: '>= 0.4'} 356 | 357 | string.prototype.trim@1.2.9: 358 | resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} 359 | engines: {node: '>= 0.4'} 360 | 361 | string.prototype.trimend@1.0.8: 362 | resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} 363 | 364 | string.prototype.trimstart@1.0.8: 365 | resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 366 | engines: {node: '>= 0.4'} 367 | 368 | supports-preserve-symlinks-flag@1.0.0: 369 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 370 | engines: {node: '>= 0.4'} 371 | 372 | tape@5.9.0: 373 | resolution: {integrity: sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==} 374 | hasBin: true 375 | 376 | typed-array-buffer@1.0.2: 377 | resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} 378 | engines: {node: '>= 0.4'} 379 | 380 | typed-array-byte-length@1.0.1: 381 | resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} 382 | engines: {node: '>= 0.4'} 383 | 384 | typed-array-byte-offset@1.0.2: 385 | resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} 386 | engines: {node: '>= 0.4'} 387 | 388 | typed-array-length@1.0.6: 389 | resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} 390 | engines: {node: '>= 0.4'} 391 | 392 | unbox-primitive@1.0.2: 393 | resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 394 | 395 | which-boxed-primitive@1.0.2: 396 | resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 397 | 398 | which-collection@1.0.2: 399 | resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} 400 | engines: {node: '>= 0.4'} 401 | 402 | which-typed-array@1.1.15: 403 | resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} 404 | engines: {node: '>= 0.4'} 405 | 406 | wrappy@1.0.2: 407 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 408 | 409 | snapshots: 410 | 411 | '@ljharb/resumer@0.1.3': 412 | dependencies: 413 | '@ljharb/through': 2.3.13 414 | call-bind: 1.0.7 415 | 416 | '@ljharb/through@2.3.13': 417 | dependencies: 418 | call-bind: 1.0.7 419 | 420 | array-buffer-byte-length@1.0.1: 421 | dependencies: 422 | call-bind: 1.0.7 423 | is-array-buffer: 3.0.4 424 | 425 | array.prototype.every@1.1.6: 426 | dependencies: 427 | call-bind: 1.0.7 428 | define-properties: 1.2.1 429 | es-abstract: 1.23.3 430 | es-object-atoms: 1.0.0 431 | is-string: 1.0.7 432 | 433 | arraybuffer.prototype.slice@1.0.3: 434 | dependencies: 435 | array-buffer-byte-length: 1.0.1 436 | call-bind: 1.0.7 437 | define-properties: 1.2.1 438 | es-abstract: 1.23.3 439 | es-errors: 1.3.0 440 | get-intrinsic: 1.2.4 441 | is-array-buffer: 3.0.4 442 | is-shared-array-buffer: 1.0.3 443 | 444 | available-typed-arrays@1.0.7: 445 | dependencies: 446 | possible-typed-array-names: 1.0.0 447 | 448 | balanced-match@1.0.2: {} 449 | 450 | brace-expansion@1.1.11: 451 | dependencies: 452 | balanced-match: 1.0.2 453 | concat-map: 0.0.1 454 | 455 | call-bind@1.0.7: 456 | dependencies: 457 | es-define-property: 1.0.0 458 | es-errors: 1.3.0 459 | function-bind: 1.1.2 460 | get-intrinsic: 1.2.4 461 | set-function-length: 1.2.2 462 | 463 | concat-map@0.0.1: {} 464 | 465 | data-view-buffer@1.0.1: 466 | dependencies: 467 | call-bind: 1.0.7 468 | es-errors: 1.3.0 469 | is-data-view: 1.0.1 470 | 471 | data-view-byte-length@1.0.1: 472 | dependencies: 473 | call-bind: 1.0.7 474 | es-errors: 1.3.0 475 | is-data-view: 1.0.1 476 | 477 | data-view-byte-offset@1.0.0: 478 | dependencies: 479 | call-bind: 1.0.7 480 | es-errors: 1.3.0 481 | is-data-view: 1.0.1 482 | 483 | deep-equal@2.2.3: 484 | dependencies: 485 | array-buffer-byte-length: 1.0.1 486 | call-bind: 1.0.7 487 | es-get-iterator: 1.1.3 488 | get-intrinsic: 1.2.4 489 | is-arguments: 1.1.1 490 | is-array-buffer: 3.0.4 491 | is-date-object: 1.0.5 492 | is-regex: 1.1.4 493 | is-shared-array-buffer: 1.0.3 494 | isarray: 2.0.5 495 | object-is: 1.1.6 496 | object-keys: 1.1.1 497 | object.assign: 4.1.5 498 | regexp.prototype.flags: 1.5.2 499 | side-channel: 1.0.6 500 | which-boxed-primitive: 1.0.2 501 | which-collection: 1.0.2 502 | which-typed-array: 1.1.15 503 | 504 | define-data-property@1.1.4: 505 | dependencies: 506 | es-define-property: 1.0.0 507 | es-errors: 1.3.0 508 | gopd: 1.0.1 509 | 510 | define-properties@1.2.1: 511 | dependencies: 512 | define-data-property: 1.1.4 513 | has-property-descriptors: 1.0.2 514 | object-keys: 1.1.1 515 | 516 | defined@1.0.1: {} 517 | 518 | delaunator@4.0.1: {} 519 | 520 | dotignore@0.1.2: 521 | dependencies: 522 | minimatch: 3.1.2 523 | 524 | es-abstract@1.23.3: 525 | dependencies: 526 | array-buffer-byte-length: 1.0.1 527 | arraybuffer.prototype.slice: 1.0.3 528 | available-typed-arrays: 1.0.7 529 | call-bind: 1.0.7 530 | data-view-buffer: 1.0.1 531 | data-view-byte-length: 1.0.1 532 | data-view-byte-offset: 1.0.0 533 | es-define-property: 1.0.0 534 | es-errors: 1.3.0 535 | es-object-atoms: 1.0.0 536 | es-set-tostringtag: 2.0.3 537 | es-to-primitive: 1.2.1 538 | function.prototype.name: 1.1.6 539 | get-intrinsic: 1.2.4 540 | get-symbol-description: 1.0.2 541 | globalthis: 1.0.4 542 | gopd: 1.0.1 543 | has-property-descriptors: 1.0.2 544 | has-proto: 1.0.3 545 | has-symbols: 1.0.3 546 | hasown: 2.0.2 547 | internal-slot: 1.0.7 548 | is-array-buffer: 3.0.4 549 | is-callable: 1.2.7 550 | is-data-view: 1.0.1 551 | is-negative-zero: 2.0.3 552 | is-regex: 1.1.4 553 | is-shared-array-buffer: 1.0.3 554 | is-string: 1.0.7 555 | is-typed-array: 1.1.13 556 | is-weakref: 1.0.2 557 | object-inspect: 1.13.2 558 | object-keys: 1.1.1 559 | object.assign: 4.1.5 560 | regexp.prototype.flags: 1.5.2 561 | safe-array-concat: 1.1.2 562 | safe-regex-test: 1.0.3 563 | string.prototype.trim: 1.2.9 564 | string.prototype.trimend: 1.0.8 565 | string.prototype.trimstart: 1.0.8 566 | typed-array-buffer: 1.0.2 567 | typed-array-byte-length: 1.0.1 568 | typed-array-byte-offset: 1.0.2 569 | typed-array-length: 1.0.6 570 | unbox-primitive: 1.0.2 571 | which-typed-array: 1.1.15 572 | 573 | es-define-property@1.0.0: 574 | dependencies: 575 | get-intrinsic: 1.2.4 576 | 577 | es-errors@1.3.0: {} 578 | 579 | es-get-iterator@1.1.3: 580 | dependencies: 581 | call-bind: 1.0.7 582 | get-intrinsic: 1.2.4 583 | has-symbols: 1.0.3 584 | is-arguments: 1.1.1 585 | is-map: 2.0.3 586 | is-set: 2.0.3 587 | is-string: 1.0.7 588 | isarray: 2.0.5 589 | stop-iteration-iterator: 1.0.0 590 | 591 | es-object-atoms@1.0.0: 592 | dependencies: 593 | es-errors: 1.3.0 594 | 595 | es-set-tostringtag@2.0.3: 596 | dependencies: 597 | get-intrinsic: 1.2.4 598 | has-tostringtag: 1.0.2 599 | hasown: 2.0.2 600 | 601 | es-to-primitive@1.2.1: 602 | dependencies: 603 | is-callable: 1.2.7 604 | is-date-object: 1.0.5 605 | is-symbol: 1.0.4 606 | 607 | for-each@0.3.3: 608 | dependencies: 609 | is-callable: 1.2.7 610 | 611 | fs.realpath@1.0.0: {} 612 | 613 | function-bind@1.1.2: {} 614 | 615 | function.prototype.name@1.1.6: 616 | dependencies: 617 | call-bind: 1.0.7 618 | define-properties: 1.2.1 619 | es-abstract: 1.23.3 620 | functions-have-names: 1.2.3 621 | 622 | functions-have-names@1.2.3: {} 623 | 624 | get-intrinsic@1.2.4: 625 | dependencies: 626 | es-errors: 1.3.0 627 | function-bind: 1.1.2 628 | has-proto: 1.0.3 629 | has-symbols: 1.0.3 630 | hasown: 2.0.2 631 | 632 | get-package-type@0.1.0: {} 633 | 634 | get-symbol-description@1.0.2: 635 | dependencies: 636 | call-bind: 1.0.7 637 | es-errors: 1.3.0 638 | get-intrinsic: 1.2.4 639 | 640 | glob@7.2.3: 641 | dependencies: 642 | fs.realpath: 1.0.0 643 | inflight: 1.0.6 644 | inherits: 2.0.4 645 | minimatch: 3.1.2 646 | once: 1.4.0 647 | path-is-absolute: 1.0.1 648 | 649 | globalthis@1.0.4: 650 | dependencies: 651 | define-properties: 1.2.1 652 | gopd: 1.0.1 653 | 654 | gopd@1.0.1: 655 | dependencies: 656 | get-intrinsic: 1.2.4 657 | 658 | has-bigints@1.0.2: {} 659 | 660 | has-dynamic-import@2.1.0: 661 | dependencies: 662 | call-bind: 1.0.7 663 | get-intrinsic: 1.2.4 664 | 665 | has-property-descriptors@1.0.2: 666 | dependencies: 667 | es-define-property: 1.0.0 668 | 669 | has-proto@1.0.3: {} 670 | 671 | has-symbols@1.0.3: {} 672 | 673 | has-tostringtag@1.0.2: 674 | dependencies: 675 | has-symbols: 1.0.3 676 | 677 | hasown@2.0.2: 678 | dependencies: 679 | function-bind: 1.1.2 680 | 681 | inflight@1.0.6: 682 | dependencies: 683 | once: 1.4.0 684 | wrappy: 1.0.2 685 | 686 | inherits@2.0.4: {} 687 | 688 | internal-slot@1.0.7: 689 | dependencies: 690 | es-errors: 1.3.0 691 | hasown: 2.0.2 692 | side-channel: 1.0.6 693 | 694 | is-arguments@1.1.1: 695 | dependencies: 696 | call-bind: 1.0.7 697 | has-tostringtag: 1.0.2 698 | 699 | is-array-buffer@3.0.4: 700 | dependencies: 701 | call-bind: 1.0.7 702 | get-intrinsic: 1.2.4 703 | 704 | is-bigint@1.0.4: 705 | dependencies: 706 | has-bigints: 1.0.2 707 | 708 | is-boolean-object@1.1.2: 709 | dependencies: 710 | call-bind: 1.0.7 711 | has-tostringtag: 1.0.2 712 | 713 | is-callable@1.2.7: {} 714 | 715 | is-core-module@2.15.1: 716 | dependencies: 717 | hasown: 2.0.2 718 | 719 | is-data-view@1.0.1: 720 | dependencies: 721 | is-typed-array: 1.1.13 722 | 723 | is-date-object@1.0.5: 724 | dependencies: 725 | has-tostringtag: 1.0.2 726 | 727 | is-map@2.0.3: {} 728 | 729 | is-negative-zero@2.0.3: {} 730 | 731 | is-number-object@1.0.7: 732 | dependencies: 733 | has-tostringtag: 1.0.2 734 | 735 | is-regex@1.1.4: 736 | dependencies: 737 | call-bind: 1.0.7 738 | has-tostringtag: 1.0.2 739 | 740 | is-set@2.0.3: {} 741 | 742 | is-shared-array-buffer@1.0.3: 743 | dependencies: 744 | call-bind: 1.0.7 745 | 746 | is-string@1.0.7: 747 | dependencies: 748 | has-tostringtag: 1.0.2 749 | 750 | is-symbol@1.0.4: 751 | dependencies: 752 | has-symbols: 1.0.3 753 | 754 | is-typed-array@1.1.13: 755 | dependencies: 756 | which-typed-array: 1.1.15 757 | 758 | is-weakmap@2.0.2: {} 759 | 760 | is-weakref@1.0.2: 761 | dependencies: 762 | call-bind: 1.0.7 763 | 764 | is-weakset@2.0.3: 765 | dependencies: 766 | call-bind: 1.0.7 767 | get-intrinsic: 1.2.4 768 | 769 | isarray@2.0.5: {} 770 | 771 | minimatch@3.1.2: 772 | dependencies: 773 | brace-expansion: 1.1.11 774 | 775 | minimist@1.2.8: {} 776 | 777 | mock-property@1.1.0: 778 | dependencies: 779 | define-data-property: 1.1.4 780 | functions-have-names: 1.2.3 781 | gopd: 1.0.1 782 | has-property-descriptors: 1.0.2 783 | hasown: 2.0.2 784 | isarray: 2.0.5 785 | object-inspect: 1.13.2 786 | 787 | moore@1.0.0: {} 788 | 789 | object-inspect@1.13.2: {} 790 | 791 | object-is@1.1.6: 792 | dependencies: 793 | call-bind: 1.0.7 794 | define-properties: 1.2.1 795 | 796 | object-keys@1.1.1: {} 797 | 798 | object.assign@4.1.5: 799 | dependencies: 800 | call-bind: 1.0.7 801 | define-properties: 1.2.1 802 | has-symbols: 1.0.3 803 | object-keys: 1.1.1 804 | 805 | once@1.4.0: 806 | dependencies: 807 | wrappy: 1.0.2 808 | 809 | path-is-absolute@1.0.1: {} 810 | 811 | path-parse@1.0.7: {} 812 | 813 | poisson-disk-sampling@2.3.1: 814 | dependencies: 815 | moore: 1.0.0 816 | 817 | possible-typed-array-names@1.0.0: {} 818 | 819 | regexp.prototype.flags@1.5.2: 820 | dependencies: 821 | call-bind: 1.0.7 822 | define-properties: 1.2.1 823 | es-errors: 1.3.0 824 | set-function-name: 2.0.2 825 | 826 | resolve@2.0.0-next.5: 827 | dependencies: 828 | is-core-module: 2.15.1 829 | path-parse: 1.0.7 830 | supports-preserve-symlinks-flag: 1.0.0 831 | 832 | safe-array-concat@1.1.2: 833 | dependencies: 834 | call-bind: 1.0.7 835 | get-intrinsic: 1.2.4 836 | has-symbols: 1.0.3 837 | isarray: 2.0.5 838 | 839 | safe-regex-test@1.0.3: 840 | dependencies: 841 | call-bind: 1.0.7 842 | es-errors: 1.3.0 843 | is-regex: 1.1.4 844 | 845 | set-function-length@1.2.2: 846 | dependencies: 847 | define-data-property: 1.1.4 848 | es-errors: 1.3.0 849 | function-bind: 1.1.2 850 | get-intrinsic: 1.2.4 851 | gopd: 1.0.1 852 | has-property-descriptors: 1.0.2 853 | 854 | set-function-name@2.0.2: 855 | dependencies: 856 | define-data-property: 1.1.4 857 | es-errors: 1.3.0 858 | functions-have-names: 1.2.3 859 | has-property-descriptors: 1.0.2 860 | 861 | side-channel@1.0.6: 862 | dependencies: 863 | call-bind: 1.0.7 864 | es-errors: 1.3.0 865 | get-intrinsic: 1.2.4 866 | object-inspect: 1.13.2 867 | 868 | stop-iteration-iterator@1.0.0: 869 | dependencies: 870 | internal-slot: 1.0.7 871 | 872 | string.prototype.trim@1.2.9: 873 | dependencies: 874 | call-bind: 1.0.7 875 | define-properties: 1.2.1 876 | es-abstract: 1.23.3 877 | es-object-atoms: 1.0.0 878 | 879 | string.prototype.trimend@1.0.8: 880 | dependencies: 881 | call-bind: 1.0.7 882 | define-properties: 1.2.1 883 | es-object-atoms: 1.0.0 884 | 885 | string.prototype.trimstart@1.0.8: 886 | dependencies: 887 | call-bind: 1.0.7 888 | define-properties: 1.2.1 889 | es-object-atoms: 1.0.0 890 | 891 | supports-preserve-symlinks-flag@1.0.0: {} 892 | 893 | tape@5.9.0: 894 | dependencies: 895 | '@ljharb/resumer': 0.1.3 896 | '@ljharb/through': 2.3.13 897 | array.prototype.every: 1.1.6 898 | call-bind: 1.0.7 899 | deep-equal: 2.2.3 900 | defined: 1.0.1 901 | dotignore: 0.1.2 902 | for-each: 0.3.3 903 | get-package-type: 0.1.0 904 | glob: 7.2.3 905 | has-dynamic-import: 2.1.0 906 | hasown: 2.0.2 907 | inherits: 2.0.4 908 | is-regex: 1.1.4 909 | minimist: 1.2.8 910 | mock-property: 1.1.0 911 | object-inspect: 1.13.2 912 | object-is: 1.1.6 913 | object-keys: 1.1.1 914 | object.assign: 4.1.5 915 | resolve: 2.0.0-next.5 916 | string.prototype.trim: 1.2.9 917 | 918 | typed-array-buffer@1.0.2: 919 | dependencies: 920 | call-bind: 1.0.7 921 | es-errors: 1.3.0 922 | is-typed-array: 1.1.13 923 | 924 | typed-array-byte-length@1.0.1: 925 | dependencies: 926 | call-bind: 1.0.7 927 | for-each: 0.3.3 928 | gopd: 1.0.1 929 | has-proto: 1.0.3 930 | is-typed-array: 1.1.13 931 | 932 | typed-array-byte-offset@1.0.2: 933 | dependencies: 934 | available-typed-arrays: 1.0.7 935 | call-bind: 1.0.7 936 | for-each: 0.3.3 937 | gopd: 1.0.1 938 | has-proto: 1.0.3 939 | is-typed-array: 1.1.13 940 | 941 | typed-array-length@1.0.6: 942 | dependencies: 943 | call-bind: 1.0.7 944 | for-each: 0.3.3 945 | gopd: 1.0.1 946 | has-proto: 1.0.3 947 | is-typed-array: 1.1.13 948 | possible-typed-array-names: 1.0.0 949 | 950 | unbox-primitive@1.0.2: 951 | dependencies: 952 | call-bind: 1.0.7 953 | has-bigints: 1.0.2 954 | has-symbols: 1.0.3 955 | which-boxed-primitive: 1.0.2 956 | 957 | which-boxed-primitive@1.0.2: 958 | dependencies: 959 | is-bigint: 1.0.4 960 | is-boolean-object: 1.1.2 961 | is-number-object: 1.0.7 962 | is-string: 1.0.7 963 | is-symbol: 1.0.4 964 | 965 | which-collection@1.0.2: 966 | dependencies: 967 | is-map: 2.0.3 968 | is-set: 2.0.3 969 | is-weakmap: 2.0.2 970 | is-weakset: 2.0.3 971 | 972 | which-typed-array@1.1.15: 973 | dependencies: 974 | available-typed-arrays: 1.0.7 975 | call-bind: 1.0.7 976 | for-each: 0.3.3 977 | gopd: 1.0.1 978 | has-tostringtag: 1.0.2 979 | 980 | wrappy@1.0.2: {} 981 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * From https://github.com/redblobgames/dual-mesh 3 | * Copyright 2017 Red Blob Games 4 | * License: Apache v2.0 5 | */ 6 | 7 | 'use strict'; 8 | 9 | let tape = require('tape'); 10 | let Delaunator = require('delaunator'); 11 | let Poisson = require('poisson-disk-sampling'); 12 | let TriangleMesh = require('./'); 13 | let MeshBuilder = require('./create'); 14 | 15 | 16 | tape("structural invariants", function(test) { 17 | let mesh = new MeshBuilder({boundarySpacing: 450}) 18 | .addPoisson(Poisson, 450) 19 | .create(true); 20 | 21 | let s_out = []; 22 | for (let s1 = 0; s1 < mesh.numSides; s1++) { 23 | let s2 = mesh.s_opposite_s(s1); 24 | test.equal(mesh.s_opposite_s(s2), s1); 25 | test.equal(mesh.s_begin_r(s1), mesh.s_end_r(s2)); 26 | test.equal(mesh.s_inner_t(s1), mesh.s_outer_t(s2)); 27 | test.equal(mesh.s_begin_r(mesh.s_next_s(s1)), mesh.s_begin_r(s2)); 28 | } 29 | for (let r = 0; r < mesh.numRegions; r++) { 30 | mesh.r_circulate_s(s_out, r); 31 | for (let s of s_out) { 32 | test.equal(mesh.s_begin_r(s), r); 33 | } 34 | } 35 | for (let t = 0; t < mesh.numTriangles; t++) { 36 | mesh.t_circulate_s(s_out, t); 37 | for (let s of s_out) { 38 | test.equal(mesh.s_inner_t(s), t); 39 | } 40 | } 41 | 42 | test.end(); 43 | }); 44 | 45 | 46 | 47 | tape("delaunator: properly connected halfedges", function(t) { 48 | let points = [[122,270],[181,121],[195,852],[204,694],[273,525],[280,355],[31,946],[319,938],[33,625],[344,93],[369,793],[38,18],[426,539],[454,239],[503,51],[506,997],[516,661],[532,386],[619,889],[689,131],[730,511],[747,750],[760,285],[856,83],[88,479],[884,943],[927,696],[960,472],[992,253]]; 49 | points = points.map((p) => [p[0] + Math.random(), p[1]]); 50 | var d = Delaunator.from(points); 51 | for (var i = 0; i < d.halfedges.length; i++) { 52 | var i2 = d.halfedges[i]; 53 | if (i2 !== -1 && d.halfedges[i2] !== i) { 54 | t.fail('invalid halfedge connection'); 55 | } 56 | } 57 | t.pass('halfedges are valid'); 58 | t.end(); 59 | }); 60 | 61 | tape("delaunator: properly connected halfedges, random set", function(test) { 62 | // NOTE: this is not a great test because the input data is 63 | // different each time; need to switch to a deterministic random 64 | // number generator 65 | let generator = new Poisson({shape: [1000, 1000], minDistance: 50.0}); 66 | let points = generator.fill(); 67 | let delaunator = Delaunator.from(points); 68 | for (let e1 = 0; e1 < delaunator.halfedges.length; e1++) { 69 | var e2 = delaunator.halfedges[e1]; 70 | if (e2 !== -1 && delaunator.halfedges[e2] !== e1) { 71 | test.fail("invalid halfedge connection; data set was " + JSON.stringify(points)); 72 | } 73 | } 74 | test.pass("halfedges are valid"); 75 | test.end(); 76 | }); 77 | --------------------------------------------------------------------------------