├── .gitignore ├── LICENSE.md ├── README.md ├── example.png ├── package-lock.json ├── package.json ├── rollup.config.js ├── src └── index.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Tero Parviainen 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D Triangle Tessellation 2 | 3 | A JavaScript (TypeScript) implementation of the triangle tessellation algorithm from [the OpenGL specification for tessellation shaders](https://www.khronos.org/opengl/wiki/Tessellation). 4 | 5 | Takes the three endpoints of a single triangle and the level(s) of depth to which to subdivide it, and returns a collection of vertices and faces that tessellate the triangle to that depth. 6 | 7 | The tessellation depth is controlled separately for each triangle edge. This allows neighbouring triangles to have matching subdivisions for their shared edge to prevent gaps in the tessellated triangle mesh. Just use the same weight for the shared edge. 8 | 9 | ![Example tessellation](example.png?raw=true "Example tessellation") 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install @teropa/triangle-tessellation 15 | ``` 16 | 17 | ES Modules, CommonJS modules, and an UMD build are all provided. 18 | 19 | ## Usage 20 | 21 | ```js 22 | import { tessellateTriangle } from "@teropa/triangle-tessellation"; 23 | 24 | let { vertices, faces } = tessellateTriangle( 25 | triangle, 26 | outerLevel1, 27 | outerLevel2, 28 | outerLevel3, 29 | innerLevel 30 | ); 31 | ``` 32 | 33 | The arguments are: 34 | 35 | - `triangle` - the triangle you wish to tessellate, expressed as a 3x3 numeric array, of three 3D points. This could be in either cartesian or barycentric coordinates. 36 | - `outerLevel1` - the outer tessellation depth for the first edge of the triangle 37 | - `outerLevel2` - the outer tessellation depth for the second edge of the triangle 38 | - `outerLevel3` - the outer tessellation depth for the third edge of the triangle 39 | - `innerLevel` - the inner tessellation depth for the triangle 40 | 41 | The return value is an object of two properties, together representing an indexed mesh geometry suitable for 3D rendering: 42 | 43 | - `vertices` - an array of points 44 | - `faces` - an array of triangle faces, expressed as indexes into the `vertices` array. The faces will be in counterclockwise winding order based on the normal of the input triangle. 45 | 46 | ## Example 47 | 48 | ```js 49 | import { tessellateTriangle } from "@teropa/triangle-tessellation"; 50 | 51 | let myTriangle = [ 52 | [1, 0, 0], 53 | [0, 1, 0], 54 | [0, 0, 1], 55 | ]; 56 | 57 | let { vertices, faces } = tessellateTriangle(myTriangle, 2, 3, 4, 3); 58 | 59 | console.log(vertices); 60 | /* 61 | [ 62 | [1, 0, 0], 63 | [0, 1, 0], 64 | [0, 0, 1], 65 | [0, 0.5, 0.5], 66 | [0.3333333333333333, 0, 0.6666666666666667], 67 | [0.6666666666666666, 0, 0.33333333333333337], 68 | [0.3333333333333333, 0.3333333333333333, 0.3333333333333333] 69 | ] 70 | */ 71 | 72 | console.log(faces); 73 | /* 74 | [ 75 | [0, 1, 6], 76 | [1, 3, 6], 77 | [3, 2, 6], 78 | [2, 4, 6], 79 | [4, 5, 6], 80 | [5, 0, 6] 81 | ] 82 | */ 83 | ``` 84 | 85 | ## Links 86 | 87 | - [OpenGL tessellation shader spec](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_tessellation_shader.txt). This library implements the section "2.X.2.1, Triangle Tessellation" 88 | - [Tessellation in the OpenGL Wiki](https://www.khronos.org/opengl/wiki/Tessellation) 89 | - [A useful Stack Overflow answer](https://stackoverflow.com/a/37648093) 90 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teropa/triangle-tessellation/310c57601a31a3eebb958f3ebfa50ca18b9fdd41/example.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@teropa/triangle-tessellation", 3 | "version": "1.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@rollup/pluginutils": { 8 | "version": "3.1.0", 9 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", 10 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", 11 | "dev": true, 12 | "requires": { 13 | "@types/estree": "0.0.39", 14 | "estree-walker": "^1.0.1", 15 | "picomatch": "^2.2.2" 16 | }, 17 | "dependencies": { 18 | "@types/estree": { 19 | "version": "0.0.39", 20 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 21 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 22 | "dev": true 23 | }, 24 | "estree-walker": { 25 | "version": "1.0.1", 26 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 27 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", 28 | "dev": true 29 | } 30 | } 31 | }, 32 | "@types/estree": { 33 | "version": "0.0.50", 34 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", 35 | "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", 36 | "dev": true 37 | }, 38 | "@types/node": { 39 | "version": "16.4.14", 40 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.14.tgz", 41 | "integrity": "sha512-GZpnVRNtv7sHDXIFncsERt+qvj4rzAgRQtnvzk3Z7OVNtThD2dHXYCMDNc80D5mv4JE278qo8biZCwcmkbdpqw==", 42 | "dev": true 43 | }, 44 | "@types/resolve": { 45 | "version": "0.0.8", 46 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 47 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", 48 | "dev": true, 49 | "requires": { 50 | "@types/node": "*" 51 | } 52 | }, 53 | "commondir": { 54 | "version": "1.0.1", 55 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 56 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", 57 | "dev": true 58 | }, 59 | "estree-walker": { 60 | "version": "0.6.1", 61 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 62 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 63 | "dev": true 64 | }, 65 | "find-cache-dir": { 66 | "version": "3.3.1", 67 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", 68 | "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", 69 | "dev": true, 70 | "requires": { 71 | "commondir": "^1.0.1", 72 | "make-dir": "^3.0.2", 73 | "pkg-dir": "^4.1.0" 74 | } 75 | }, 76 | "find-up": { 77 | "version": "4.1.0", 78 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 79 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 80 | "dev": true, 81 | "requires": { 82 | "locate-path": "^5.0.0", 83 | "path-exists": "^4.0.0" 84 | } 85 | }, 86 | "fs-extra": { 87 | "version": "8.1.0", 88 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 89 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 90 | "dev": true, 91 | "requires": { 92 | "graceful-fs": "^4.2.0", 93 | "jsonfile": "^4.0.0", 94 | "universalify": "^0.1.0" 95 | } 96 | }, 97 | "fsevents": { 98 | "version": "2.3.2", 99 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 100 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 101 | "dev": true, 102 | "optional": true 103 | }, 104 | "graceful-fs": { 105 | "version": "4.2.8", 106 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", 107 | "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", 108 | "dev": true 109 | }, 110 | "is-module": { 111 | "version": "1.0.0", 112 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 113 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 114 | "dev": true 115 | }, 116 | "is-reference": { 117 | "version": "1.2.1", 118 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 119 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", 120 | "dev": true, 121 | "requires": { 122 | "@types/estree": "*" 123 | } 124 | }, 125 | "jsonfile": { 126 | "version": "4.0.0", 127 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 128 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 129 | "dev": true, 130 | "requires": { 131 | "graceful-fs": "^4.1.6" 132 | } 133 | }, 134 | "locate-path": { 135 | "version": "5.0.0", 136 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 137 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 138 | "dev": true, 139 | "requires": { 140 | "p-locate": "^4.1.0" 141 | } 142 | }, 143 | "magic-string": { 144 | "version": "0.25.7", 145 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", 146 | "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", 147 | "dev": true, 148 | "requires": { 149 | "sourcemap-codec": "^1.4.4" 150 | } 151 | }, 152 | "make-dir": { 153 | "version": "3.1.0", 154 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 155 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 156 | "dev": true, 157 | "requires": { 158 | "semver": "^6.0.0" 159 | }, 160 | "dependencies": { 161 | "semver": { 162 | "version": "6.3.0", 163 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 164 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 165 | "dev": true 166 | } 167 | } 168 | }, 169 | "p-limit": { 170 | "version": "2.3.0", 171 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 172 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 173 | "dev": true, 174 | "requires": { 175 | "p-try": "^2.0.0" 176 | } 177 | }, 178 | "p-locate": { 179 | "version": "4.1.0", 180 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 181 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 182 | "dev": true, 183 | "requires": { 184 | "p-limit": "^2.2.0" 185 | } 186 | }, 187 | "p-try": { 188 | "version": "2.2.0", 189 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 190 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 191 | "dev": true 192 | }, 193 | "path-exists": { 194 | "version": "4.0.0", 195 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 196 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 197 | "dev": true 198 | }, 199 | "path-parse": { 200 | "version": "1.0.7", 201 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 202 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 203 | "dev": true 204 | }, 205 | "picomatch": { 206 | "version": "2.3.0", 207 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 208 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", 209 | "dev": true 210 | }, 211 | "pkg-dir": { 212 | "version": "4.2.0", 213 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 214 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 215 | "dev": true, 216 | "requires": { 217 | "find-up": "^4.0.0" 218 | } 219 | }, 220 | "resolve": { 221 | "version": "1.17.0", 222 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", 223 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", 224 | "dev": true, 225 | "requires": { 226 | "path-parse": "^1.0.6" 227 | } 228 | }, 229 | "rollup": { 230 | "version": "2.56.2", 231 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.2.tgz", 232 | "integrity": "sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ==", 233 | "dev": true, 234 | "requires": { 235 | "fsevents": "~2.3.2" 236 | } 237 | }, 238 | "rollup-plugin-commonjs": { 239 | "version": "10.1.0", 240 | "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", 241 | "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", 242 | "dev": true, 243 | "requires": { 244 | "estree-walker": "^0.6.1", 245 | "is-reference": "^1.1.2", 246 | "magic-string": "^0.25.2", 247 | "resolve": "^1.11.0", 248 | "rollup-pluginutils": "^2.8.1" 249 | } 250 | }, 251 | "rollup-plugin-node-resolve": { 252 | "version": "5.2.0", 253 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", 254 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", 255 | "dev": true, 256 | "requires": { 257 | "@types/resolve": "0.0.8", 258 | "builtin-modules": "^3.1.0", 259 | "is-module": "^1.0.0", 260 | "resolve": "^1.11.1", 261 | "rollup-pluginutils": "^2.8.1" 262 | }, 263 | "dependencies": { 264 | "builtin-modules": { 265 | "version": "3.2.0", 266 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", 267 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", 268 | "dev": true 269 | } 270 | } 271 | }, 272 | "rollup-plugin-typescript2": { 273 | "version": "0.27.3", 274 | "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", 275 | "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", 276 | "dev": true, 277 | "requires": { 278 | "@rollup/pluginutils": "^3.1.0", 279 | "find-cache-dir": "^3.3.1", 280 | "fs-extra": "8.1.0", 281 | "resolve": "1.17.0", 282 | "tslib": "2.0.1" 283 | }, 284 | "dependencies": { 285 | "tslib": { 286 | "version": "2.0.1", 287 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", 288 | "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", 289 | "dev": true 290 | } 291 | } 292 | }, 293 | "rollup-pluginutils": { 294 | "version": "2.8.2", 295 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 296 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 297 | "dev": true, 298 | "requires": { 299 | "estree-walker": "^0.6.1" 300 | } 301 | }, 302 | "sourcemap-codec": { 303 | "version": "1.4.8", 304 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 305 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 306 | "dev": true 307 | }, 308 | "typescript": { 309 | "version": "3.8.3", 310 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", 311 | "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", 312 | "dev": true 313 | }, 314 | "universalify": { 315 | "version": "0.1.2", 316 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 317 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 318 | "dev": true 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@teropa/triangle-tessellation", 3 | "version": "1.1.0", 4 | "description": "A simple implementation of the OpenGL triangle tessellation algorithm", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "browser": "dist/index.umd.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist/**/*" 11 | ], 12 | "scripts": { 13 | "build": "rollup -c", 14 | "dev": "rollup -c -w" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/teropa/triangle-tessellation.git" 19 | }, 20 | "keywords": [], 21 | "author": "Tero Parviainen ", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/teropa/triangle-tessellation/issues" 25 | }, 26 | "homepage": "https://github.com/teropa/triangle-tessellation", 27 | "devDependencies": { 28 | "rollup": "^2.8.1", 29 | "rollup-plugin-commonjs": "^10.1.0", 30 | "rollup-plugin-node-resolve": "^5.2.0", 31 | "typescript": "^3.8.3", 32 | "rollup-plugin-typescript2": "^0.27.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-node-resolve"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import typescript from "rollup-plugin-typescript2"; 4 | import pkg from "./package.json"; 5 | 6 | export default [ 7 | { 8 | input: "src/index.ts", 9 | output: { 10 | name: "index", 11 | file: pkg.browser, 12 | format: "umd", 13 | }, 14 | plugins: [resolve(), commonjs(), typescript()], 15 | }, 16 | { 17 | input: "src/index.ts", 18 | plugins: [typescript()], 19 | output: [ 20 | { file: pkg.main, format: "cjs" }, 21 | { file: pkg.module, format: "es" }, 22 | ], 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type Vector3 = [number, number, number]; 2 | export type Face3 = [number, number, number]; 3 | export type Geometry3 = { vertices: Vector3[]; faces: Face3[] }; 4 | 5 | export function tessellateTriangle( 6 | triangle: [Vector3, Vector3, Vector3], 7 | outerLevelUV: number, 8 | outerLevelVW: number, 9 | outerLevelWU: number, 10 | innerLevel: number, 11 | ): Geometry3 { 12 | let [u, v, w] = triangle; 13 | if (outerLevelUV === 1 && outerLevelVW === 1 && outerLevelWU === 1 && innerLevel === 1) { 14 | return { 15 | vertices: [u, v, w], 16 | faces: [[0, 1, 2]], 17 | }; 18 | } 19 | 20 | if (innerLevel === 1) { 21 | innerLevel = 1 + 1e-6; 22 | } 23 | 24 | // Normal vector of the plane formed by the triangle 25 | let planeNormal = normalizeV3(crossV3(subV3(v, u), subV3(w, u))); 26 | 27 | // Start by subdividing the outer edges according to the inner tessellation level 28 | let rings: Vector3[][][] = [ 29 | [ 30 | subdivideEdge(u, v, Math.ceil(innerLevel)), 31 | subdivideEdge(v, w, Math.ceil(innerLevel)), 32 | subdivideEdge(w, u, Math.ceil(innerLevel)), 33 | ], 34 | ]; 35 | 36 | // Generate inner rings until there are no more edge subdivisions 37 | while (rings[rings.length - 1][0].length > 3) { 38 | rings.push(generateInnerRing(rings[rings.length - 1])); 39 | } 40 | 41 | // Now replace the outermost ring with one that uses the outer subdivisions 42 | rings[0][0] = subdivideEdge(u, v, Math.ceil(outerLevelUV)); 43 | rings[0][1] = subdivideEdge(v, w, Math.ceil(outerLevelVW)); 44 | rings[0][2] = subdivideEdge(w, u, Math.ceil(outerLevelWU)); 45 | 46 | // Generate vertex array and generate version of ring data structurewith indexes 47 | let vertices: Vector3[] = []; 48 | let ringVertexIndices: number[][][] = []; 49 | for (let [ringUVPoints, ringVWPoints, ringWUPoints] of rings) { 50 | let ringUVIndices: number[] = []; 51 | let ringVWIndices: number[] = []; 52 | let ringWUIndices: number[] = []; 53 | 54 | vertices.push(ringUVPoints[0]); 55 | ringUVIndices[0] = vertices.length - 1; 56 | ringWUIndices[ringWUPoints.length - 1] = vertices.length - 1; 57 | 58 | vertices.push(ringVWPoints[0]); 59 | ringVWIndices[0] = vertices.length - 1; 60 | ringUVIndices[ringUVPoints.length - 1] = vertices.length - 1; 61 | 62 | vertices.push(ringWUPoints[0]); 63 | ringWUIndices[0] = vertices.length - 1; 64 | ringVWIndices[ringVWPoints.length - 1] = vertices.length - 1; 65 | 66 | for (let i = 1; i < ringUVPoints.length - 1; i++) { 67 | vertices.push(ringUVPoints[i]); 68 | ringUVIndices[i] = vertices.length - 1; 69 | } 70 | for (let i = 1; i < ringVWPoints.length - 1; i++) { 71 | vertices.push(ringVWPoints[i]); 72 | ringVWIndices[i] = vertices.length - 1; 73 | } 74 | for (let i = 1; i < ringWUPoints.length - 1; i++) { 75 | vertices.push(ringWUPoints[i]); 76 | ringWUIndices[i] = vertices.length - 1; 77 | } 78 | 79 | ringVertexIndices.push([ringUVIndices, ringVWIndices, ringWUIndices]); 80 | } 81 | 82 | // Generate faces to fill each consecutive ring pair 83 | let faces: Face3[] = []; 84 | for (let i = 0; i < rings.length - 1; i++) { 85 | let [outerRingUVVertexIndices, outerRingVWVertexIndices, outerRingWUVertexIndices] = ringVertexIndices[i]; 86 | let [innerRingUVVertexIndices, innerRingVWVertexIndices, innerRingWUVertexIndices] = ringVertexIndices[i + 1]; 87 | 88 | generateFacesBetween(outerRingUVVertexIndices, innerRingUVVertexIndices, vertices, planeNormal, faces); 89 | generateFacesBetween(outerRingVWVertexIndices, innerRingVWVertexIndices, vertices, planeNormal, faces); 90 | generateFacesBetween(outerRingWUVertexIndices, innerRingWUVertexIndices, vertices, planeNormal, faces); 91 | } 92 | 93 | // Generate triangles to fill the innermost ring 94 | let [innermostUVVertexIndices, innermostVWVertexIndices, innermostWUVertexIndices] = ringVertexIndices[ 95 | rings.length - 1 96 | ]; 97 | if ( 98 | innermostUVVertexIndices.length === 2 && 99 | innermostVWVertexIndices.length === 2 && 100 | innermostWUVertexIndices.length === 2 101 | ) { 102 | // Innermost ring has no subdivisions; use it as a triangle as-is 103 | faces.push(buildFace(innermostUVVertexIndices[0], innermostVWVertexIndices[0], innermostWUVertexIndices[0], vertices, planeNormal)); 104 | } else { 105 | // Innermost triagle is subdivided, generate faces between all the points on the ring and the centerpoint 106 | let centerVertex = multiplyScalarV3(addV3(addV3(u, v), w), 1 / 3); 107 | vertices.push(centerVertex); 108 | let centerVertexIdx = vertices.length - 1; 109 | for (let i = 0; i < innermostUVVertexIndices.length - 1; i++) { 110 | faces.push(buildFace(innermostUVVertexIndices[i], innermostUVVertexIndices[i + 1], centerVertexIdx, vertices, planeNormal)); 111 | } 112 | for (let i = 0; i < innermostVWVertexIndices.length - 1; i++) { 113 | faces.push(buildFace(innermostVWVertexIndices[i], innermostVWVertexIndices[i + 1], centerVertexIdx, vertices, planeNormal)); 114 | } 115 | for (let i = 0; i < innermostWUVertexIndices.length - 1; i++) { 116 | faces.push(buildFace(innermostWUVertexIndices[i], innermostWUVertexIndices[i + 1], centerVertexIdx, vertices, planeNormal)); 117 | } 118 | } 119 | 120 | return { vertices, faces }; 121 | } 122 | 123 | function subdivideEdge(a: Vector3, b: Vector3, subdivision: number) { 124 | let aToB = subV3(b, a); 125 | let aToBUnit = normalizeV3(aToB); 126 | let subdivisionLength = lengthV3(aToB) / subdivision; 127 | let result = [a]; 128 | for (let i = 0; i < subdivision - 1; i++) { 129 | result.push(addV3(a, multiplyScalarV3(aToBUnit, subdivisionLength * (i + 1)))); 130 | } 131 | result.push(b); 132 | return result; 133 | } 134 | 135 | function projectPointV3(pt: Vector3, a: Vector3, b: Vector3) { 136 | let ab = subV3(b, a); 137 | let apt = subV3(pt, a); 138 | let t = dotV3(ab, apt) / dotV3(ab, ab); 139 | return addV3(multiplyScalarV3(ab, t), a); 140 | } 141 | 142 | function generateInnerRing(outerRing: Vector3[][]): Vector3[][] { 143 | let [outerUVPoints, outerVWPoints, outerWUPoints] = outerRing; 144 | let outerU = outerUVPoints[0]; 145 | let outerV = outerVWPoints[0]; 146 | let outerW = outerWUPoints[0]; 147 | 148 | // Outer edge vectors 149 | let outerUV = subV3(outerV, outerU); 150 | let outerVW = subV3(outerW, outerV); 151 | let outerWU = subV3(outerU, outerW); 152 | 153 | // Normal vector of the plane formed by the triangle 154 | let planeNormal = normalizeV3(crossV3(outerUV, outerVW)); 155 | 156 | // Edge normal vectors 157 | let outerUVNormal = normalizeV3(crossV3(outerUV, planeNormal)); 158 | let outerVWNormal = normalizeV3(crossV3(outerVW, planeNormal)); 159 | let outerWUNormal = normalizeV3(crossV3(outerWU, planeNormal)); 160 | 161 | // Vertices on each subdivided outer edge closest to the vertices of the original outer triangle 162 | let uV = outerUVPoints[1]; 163 | let uW = outerWUPoints[outerWUPoints.length - 2]; 164 | let vW = outerVWPoints[1]; 165 | let vU = outerUVPoints[outerUVPoints.length - 2]; 166 | let wU = outerWUPoints[1]; 167 | let wV = outerVWPoints[outerVWPoints.length - 2]; 168 | 169 | // Vertices of the inner triangle 170 | let innerU = intersectLines(uV, outerUVNormal, uW, outerWUNormal)!; 171 | let innerV = intersectLines(vW, outerVWNormal, vU, outerUVNormal)!; 172 | let innerW = intersectLines(wU, outerWUNormal, wV, outerVWNormal)!; 173 | 174 | // Project the rest of the subdivided edge points onto the edges of this inner triangle... 175 | let innerUVPoints = [innerU]; 176 | for (let i = 2; i < outerUVPoints.length - 2; i++) { 177 | innerUVPoints.push(projectPointV3(outerUVPoints[i], innerU, innerV)); 178 | } 179 | innerUVPoints.push(innerV); 180 | 181 | let innerVWPoints = [innerV]; 182 | for (let i = 2; i < outerVWPoints.length - 2; i++) { 183 | innerVWPoints.push(projectPointV3(outerVWPoints[i], innerV, innerW)); 184 | } 185 | innerVWPoints.push(innerW); 186 | 187 | let innerWUPoints = [innerW]; 188 | for (let i = 2; i < outerWUPoints.length - 2; i++) { 189 | innerWUPoints.push(projectPointV3(outerWUPoints[i], innerW, innerU)); 190 | } 191 | innerWUPoints.push(innerU); 192 | 193 | return [innerUVPoints, innerVWPoints, innerWUPoints]; 194 | } 195 | 196 | // https://math.stackexchange.com/a/271366 197 | function intersectLines(c: Vector3, e: Vector3, d: Vector3, f: Vector3) { 198 | let g = subV3(d, c); 199 | let h = crossV3(f, g); 200 | let hLength = lengthV3(h); 201 | if (hLength === 0) return null; 202 | let k = crossV3(f, e); 203 | let kLength = lengthV3(k); 204 | if (kLength === 0) return null; 205 | 206 | let i = multiplyScalarV3(e, hLength / kLength); 207 | 208 | if (lengthV3(crossV3(h, k)) > 1e-6) { 209 | return addV3(c, i); 210 | } else { 211 | return subV3(c, i); 212 | } 213 | } 214 | 215 | function generateFacesBetween(outerVertexIndices: number[], innerVertexIndices: number[], vertices: Vector3[], normal: Vector3, faces: Face3[]) { 216 | let outerEdge = true; 217 | let outerIdx = 0; 218 | let innerIdx = 0; 219 | while (outerIdx < outerVertexIndices.length - 1 || innerIdx < innerVertexIndices.length - 1) { 220 | if (outerEdge && outerIdx < outerVertexIndices.length - 1) { 221 | faces.push(buildFace(outerVertexIndices[outerIdx], outerVertexIndices[outerIdx + 1], innerVertexIndices[innerIdx], vertices, normal)); 222 | outerIdx++; 223 | } else if (!outerEdge && innerIdx < innerVertexIndices.length - 1) { 224 | faces.push(buildFace(innerVertexIndices[innerIdx], innerVertexIndices[innerIdx + 1], outerVertexIndices[outerIdx], vertices, normal)); 225 | innerIdx++; 226 | } 227 | outerEdge = !outerEdge; 228 | } 229 | } 230 | 231 | function buildFace(a: number, b: number, c: number, vertices: Vector3[], normal: Vector3): Face3 { 232 | if (isCCW(vertices[a], vertices[b], vertices[c], normal)) { 233 | return [a, b, c]; 234 | } else { 235 | return [a, c, b]; 236 | } 237 | } 238 | 239 | function isCCW(a: Vector3, b: Vector3, c: Vector3, normal: Vector3) { 240 | let triNormal = crossV3(subV3(b, a), subV3(c, a)); 241 | return dotV3(triNormal, normal) > 0; 242 | } 243 | 244 | function addV3(a: Vector3, b: Vector3): Vector3 { 245 | return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; 246 | } 247 | 248 | function subV3(a: Vector3, b: Vector3): Vector3 { 249 | return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; 250 | } 251 | 252 | function multiplyScalarV3(a: Vector3, m: number): Vector3 { 253 | return [a[0] * m, a[1] * m, a[2] * m]; 254 | } 255 | 256 | function lengthV3(a: Vector3): number { 257 | return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); 258 | } 259 | 260 | function normalizeV3(a: Vector3): Vector3 { 261 | let length = lengthV3(a); 262 | return [a[0] / length, a[1] / length, a[2] / length]; 263 | } 264 | 265 | function dotV3(a: Vector3, b: Vector3): number { 266 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; 267 | } 268 | 269 | function crossV3(a: Vector3, b: Vector3): Vector3 { 270 | return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; 271 | } 272 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "module": "ES2015", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "declaration": true, 9 | "outDir": "./lib/esm" 10 | }, 11 | "include": ["./src"] 12 | } 13 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@rollup/pluginutils@^3.0.8": 6 | version "3.0.10" 7 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.10.tgz#a659b9025920378494cd8f8c59fbf9b3a50d5f12" 8 | integrity sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw== 9 | dependencies: 10 | "@types/estree" "0.0.39" 11 | estree-walker "^1.0.1" 12 | picomatch "^2.2.2" 13 | 14 | "@types/estree@0.0.39": 15 | version "0.0.39" 16 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 17 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 18 | 19 | "@types/node@*": 20 | version "13.13.5" 21 | resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.5.tgz#96ec3b0afafd64a4ccea9107b75bf8489f0e5765" 22 | integrity sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g== 23 | 24 | "@types/resolve@0.0.8": 25 | version "0.0.8" 26 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" 27 | integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== 28 | dependencies: 29 | "@types/node" "*" 30 | 31 | builtin-modules@^3.1.0: 32 | version "3.1.0" 33 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" 34 | integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== 35 | 36 | commondir@^1.0.1: 37 | version "1.0.1" 38 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 39 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= 40 | 41 | estree-walker@^0.6.1: 42 | version "0.6.1" 43 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" 44 | integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== 45 | 46 | estree-walker@^1.0.1: 47 | version "1.0.1" 48 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 49 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 50 | 51 | find-cache-dir@^3.3.1: 52 | version "3.3.1" 53 | resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" 54 | integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== 55 | dependencies: 56 | commondir "^1.0.1" 57 | make-dir "^3.0.2" 58 | pkg-dir "^4.1.0" 59 | 60 | find-up@^4.0.0: 61 | version "4.1.0" 62 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 63 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 64 | dependencies: 65 | locate-path "^5.0.0" 66 | path-exists "^4.0.0" 67 | 68 | fs-extra@8.1.0: 69 | version "8.1.0" 70 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 71 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 72 | dependencies: 73 | graceful-fs "^4.2.0" 74 | jsonfile "^4.0.0" 75 | universalify "^0.1.0" 76 | 77 | fsevents@~2.1.2: 78 | version "2.1.3" 79 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" 80 | integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== 81 | 82 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 83 | version "4.2.4" 84 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 85 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 86 | 87 | is-module@^1.0.0: 88 | version "1.0.0" 89 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 90 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 91 | 92 | is-reference@^1.1.2: 93 | version "1.1.4" 94 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" 95 | integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw== 96 | dependencies: 97 | "@types/estree" "0.0.39" 98 | 99 | jsonfile@^4.0.0: 100 | version "4.0.0" 101 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 102 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 103 | optionalDependencies: 104 | graceful-fs "^4.1.6" 105 | 106 | locate-path@^5.0.0: 107 | version "5.0.0" 108 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 109 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 110 | dependencies: 111 | p-locate "^4.1.0" 112 | 113 | magic-string@^0.25.2: 114 | version "0.25.7" 115 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 116 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 117 | dependencies: 118 | sourcemap-codec "^1.4.4" 119 | 120 | make-dir@^3.0.2: 121 | version "3.1.0" 122 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 123 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 124 | dependencies: 125 | semver "^6.0.0" 126 | 127 | p-limit@^2.2.0: 128 | version "2.3.0" 129 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 130 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 131 | dependencies: 132 | p-try "^2.0.0" 133 | 134 | p-locate@^4.1.0: 135 | version "4.1.0" 136 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 137 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 138 | dependencies: 139 | p-limit "^2.2.0" 140 | 141 | p-try@^2.0.0: 142 | version "2.2.0" 143 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 144 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 145 | 146 | path-exists@^4.0.0: 147 | version "4.0.0" 148 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 149 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 150 | 151 | path-parse@^1.0.6: 152 | version "1.0.7" 153 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 154 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 155 | 156 | picomatch@^2.2.2: 157 | version "2.2.2" 158 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 159 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 160 | 161 | pkg-dir@^4.1.0: 162 | version "4.2.0" 163 | resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" 164 | integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== 165 | dependencies: 166 | find-up "^4.0.0" 167 | 168 | resolve@1.15.1: 169 | version "1.15.1" 170 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" 171 | integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== 172 | dependencies: 173 | path-parse "^1.0.6" 174 | 175 | resolve@^1.11.0, resolve@^1.11.1: 176 | version "1.17.0" 177 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" 178 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== 179 | dependencies: 180 | path-parse "^1.0.6" 181 | 182 | rollup-plugin-commonjs@^10.1.0: 183 | version "10.1.0" 184 | resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" 185 | integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== 186 | dependencies: 187 | estree-walker "^0.6.1" 188 | is-reference "^1.1.2" 189 | magic-string "^0.25.2" 190 | resolve "^1.11.0" 191 | rollup-pluginutils "^2.8.1" 192 | 193 | rollup-plugin-node-resolve@^5.2.0: 194 | version "5.2.0" 195 | resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523" 196 | integrity sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw== 197 | dependencies: 198 | "@types/resolve" "0.0.8" 199 | builtin-modules "^3.1.0" 200 | is-module "^1.0.0" 201 | resolve "^1.11.1" 202 | rollup-pluginutils "^2.8.1" 203 | 204 | rollup-plugin-typescript2@^0.27.0: 205 | version "0.27.0" 206 | resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.0.tgz#95ff96f9e07d5000a9d2df4d76b548f9a1f83511" 207 | integrity sha512-SRKG/Canve3cxBsqhY1apIBznqnX9X/WU3Lrq3XSwmTmFqccj3+//logLXFEmp+PYFNllSVng+f4zjqRTPKNkA== 208 | dependencies: 209 | "@rollup/pluginutils" "^3.0.8" 210 | find-cache-dir "^3.3.1" 211 | fs-extra "8.1.0" 212 | resolve "1.15.1" 213 | tslib "1.11.1" 214 | 215 | rollup-pluginutils@^2.8.1: 216 | version "2.8.2" 217 | resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" 218 | integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== 219 | dependencies: 220 | estree-walker "^0.6.1" 221 | 222 | rollup@^2.8.1: 223 | version "2.8.1" 224 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.8.1.tgz#bb88f06c00f588437cb397fb933589e0878f3b46" 225 | integrity sha512-j4aRMIpr0LT5JogYQ0tkznjgKglI11fipTAUgpkhB14imdnlOfD30LDk1cRX2GWaFI0+i8ryW+k8+heVwcoyug== 226 | optionalDependencies: 227 | fsevents "~2.1.2" 228 | 229 | semver@^6.0.0: 230 | version "6.3.0" 231 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 232 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 233 | 234 | sourcemap-codec@^1.4.4: 235 | version "1.4.8" 236 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 237 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 238 | 239 | tslib@1.11.1: 240 | version "1.11.1" 241 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" 242 | integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== 243 | 244 | typescript@^3.8.3: 245 | version "3.8.3" 246 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" 247 | integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== 248 | 249 | universalify@^0.1.0: 250 | version "0.1.2" 251 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 252 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 253 | --------------------------------------------------------------------------------