├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── changelog.md ├── package.json ├── quads.js ├── test.js ├── three.js └── types.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | index.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Greg Tatum 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `quads` - Geometry Tools 2 | 3 | This package is a collection of quad geometry creation and manipulation tools. They can be used agnostic to any given library as they only operate on simple arrays and objects. Please note that this package is an early release, and the APIs may stabilize over time. More rigorous testing is also in the works. 4 | 5 | ## Types 6 | 7 | 8 | 9 | ### SimplicialComplex 10 | 11 | This is a complicated math word that means an object with `{ positions, cells }`. The 12 | word `mesh` is used for convenience in this module, and `normals` are included with 13 | this object. 14 | 15 | ```javascript 16 | // A single quad oriented facing up. 17 | const mesh = { 18 | positions: [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]], 19 | normals: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]], 20 | cells: [[0, 1, 2, 3]] 21 | } 22 | ``` 23 | 24 | Additional attributes may be added for one's own applications. For example: 25 | 26 | ```javascript 27 | mesh.colors = mesh.positions.map(p => [0, p.y, 0]) 28 | ``` 29 | 30 | Type: [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) 31 | 32 | **Properties** 33 | 34 | - `positions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Position](#position)>** 35 | - `cells` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Cell](#cell)>** 36 | - `normals` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Normal](#normal)>** 37 | 38 | ### Position 39 | 40 | An array of 3 values representing a position [x, y, z]. 41 | 42 | Type: [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 43 | 44 | ### Cell 45 | 46 | In a simplicial complex, a cell is an array of of indices that refer to a position or 47 | some other attribute like normals. Quads have 4 indices, and this module uses the 48 | convention of `[a, b, c, d]` with clockwise winding order. 49 | 50 | b-------c 51 | | | 52 | | | 53 | a-------d 54 | 55 | Type: [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 56 | 57 | ### Normal 58 | 59 | An array of 3 values, [x, y, z] representing a surface normal. A valid normal has a 60 | length of 1. Normals are used for lighting calculation, and for knowing which way a 61 | surface is oriented in space. Many operation rely on valid normals. 62 | 63 | Type: [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 64 | 65 | ## API 66 | 67 | 68 | 69 | ### averageNormalForPosition 70 | 71 | Computes the average normal for a position given the connected cells. 72 | 73 | **Parameters** 74 | 75 | - `mesh` **SimplicialComplex** 76 | - `positionIndex` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 77 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** = \[] 78 | - `normalCache` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)?** A Map can be provided to cache intermediate normal computations. 79 | - `positionIndexToCells` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)?** A Map where positionIndex is mapped to its cell, used primarily internally. 80 | 81 | Returns **Normal** target 82 | 83 | ### clone 84 | 85 | Clones a cell. Returns the new cell. 86 | 87 | **Parameters** 88 | 89 | - `mesh` **SimplicialComplex** 90 | - `cell` **Cell** 91 | 92 | Returns **Cell** cloned cell 93 | 94 | ### computeCellCenter 95 | 96 | Computes the center of a single cell. 97 | 98 | **Parameters** 99 | 100 | - `mesh` **SimplicialComplex** 101 | - `cell` **Cell** 102 | 103 | Returns **Position** center 104 | 105 | ### computeCenterPositions 106 | 107 | Computes all of the centers of all the cells. 108 | 109 | **Parameters** 110 | 111 | - `mesh` **SimplicialComplex** 112 | 113 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Position>** centers 114 | 115 | ### computeNormals 116 | 117 | Updates all of the normals for all the positions using 118 | [#averageNormalForPosition](#averageNormalForPosition). If a normal doesn't exist, 119 | then it is created. 120 | 121 | **Parameters** 122 | 123 | - `mesh` **SimplicialComplex** 124 | 125 | Returns **SimplicialComplex** 126 | 127 | ### createBox 128 | 129 | Creates a quad box of the given dimensions. This box will render as a 130 | smoothed out box, as the normals are averaged. This is typically used for a 131 | starting place for subdividing or extrusion operations. If the 132 | `optionalMesh` object is passed, then the box will be created inside of 133 | that simplicial complex, otherwise a new mesh simplicial complex will be 134 | generated. 135 | 136 | **Parameters** 137 | 138 | - `x` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** = 1 139 | - `y` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** = 1 140 | - `z` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** = 1 141 | - `optionalMesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** 142 | 143 | Returns **SimplicialComplex** 144 | 145 | ### createBoxDisjoint 146 | 147 | Creates a quad box of the given dimensions, but with non-joined positions. 148 | This box renders as a flat shaded box. If the optionalMesh object is 149 | passed, then the box will be created inside of that simplicial complex, 150 | otherwise a new mesh simplicial complex will be generated. 151 | 152 | **Parameters** 153 | 154 | - `x` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 1** 155 | - `y` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 1** 156 | - `z` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 1** 157 | - `optionalMesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** 158 | 159 | Returns **SimplicialComplex** 160 | 161 | ### createQuad 162 | 163 | Create a quad with options. If the optionalMesh object is passed, then the 164 | quad will be created inside of that simplicial complex, otherwise a new 165 | mesh simplicial complex will be generated. Both the mesh simplicial 166 | complex and the created cell are returned in an object. 167 | 168 | **Parameters** 169 | 170 | - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 171 | - `mesh` **SimplicialComplex?= {}** 172 | 173 | **Examples** 174 | 175 | _Usage:_ 176 | 177 | ```javascript 178 | const {mesh, cell} = createQuad({ positions: [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]] }) 179 | const {mesh, cell} = createQuad({ w: 1, h: 1 }) 180 | const {mesh, cell} = createQuad() 181 | ``` 182 | 183 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** `{mesh, cell}` 184 | 185 | ### elementsFromQuads 186 | 187 | Returns an elements array using the given `ArrayType`, which can be used by WebGL. 188 | 189 | **Parameters** 190 | 191 | - `mesh` **SimplicialComplex** 192 | - `drawMode` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?= 'triangles'** 193 | - `ArrayType` **typeof?= Uint16Array** 194 | 195 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Elements using the given `ArrayType`, which can be used by WebGL. 196 | 197 | ### extrude 198 | 199 | Given a target cell, first inset it, then move it along the cell's normal 200 | outwards by a given distance. 201 | 202 | **Parameters** 203 | 204 | - `mesh` **SimplicialComplex** 205 | - `cell` **Cell** 206 | - `insetT` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Value ranged from `0` to `1`, defaults to `0` 207 | - `extrude` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Distance to extrude, defaults to `0` 208 | 209 | ### extrudeDisjoint 210 | 211 | Given a target cell, first inset it, then move it along the cell's normal 212 | outwards by a given distance, but all new geometry generated will not 213 | share positions. 214 | 215 | **Parameters** 216 | 217 | - `mesh` **SimplicialComplex** 218 | - `cell` **Cell** 219 | - `insetT` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** = 0, ranged from `0` (the edge) to `1` (the center). 220 | - `extrude` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** = 0, the distance to extrude out. 221 | 222 | ### flip 223 | 224 | Flip a cell's normal to point the other way. Returns the cell. 225 | 226 | **Parameters** 227 | 228 | - `mesh` **SimplicialComplex** 229 | - `cell` **Cell** 230 | 231 | Returns **Cell** The cell 232 | 233 | ### getCellFromEdge 234 | 235 | Find a cell given two position indices. Optionally provide a `previousCell` 236 | that will not be matched against. Returns the first cell that matches. 237 | 238 | **Parameters** 239 | 240 | - `mesh` **SimplicialComplex** 241 | - `positionIndexA` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 242 | - `positionIndexB` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 243 | - `previousCell` **Cell?** Optional will not be matched against 244 | 245 | Returns **Cell** 246 | 247 | ### getCellNormal 248 | 249 | Compute a cell's normal regardless of it's neighboring cells. 250 | 251 | **Parameters** 252 | 253 | - `mesh` **SimplicialComplex** 254 | - `cell` **Cell** 255 | - `target` **Normal?** **= \[]** 256 | 257 | Returns **Normal** The target normal. 258 | 259 | ### getCellsFromPositionIndex 260 | 261 | Given a position index, find any cells that include it. 262 | 263 | **Parameters** 264 | 265 | - `mesh` **SimplicialComplex** 266 | - `index` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 267 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Cell>?= \[]** 268 | 269 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Cell>** The target cells. 270 | 271 | ### getCenter 272 | 273 | Computes the center of a cell. 274 | 275 | **Parameters** 276 | 277 | - `mesh` **SimplicialComplex** 278 | - `cell` **Cell** 279 | - `target` **Position?= \[]** 280 | 281 | Returns **Position** center 282 | 283 | ### getLoop 284 | 285 | Gets a loop of cells. Given a single cell, start walking in both 286 | directions to select a loop. . 287 | 288 | **Parameters** 289 | 290 | - `mesh` **SimplicialComplex** 291 | - `cell` **Cell** 292 | - `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** can either be `"cells"`, `"positions"`, or `"normals"`. 293 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 294 | 295 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** an array according to the `type`. 296 | 297 | ### getNewGeometry 298 | 299 | Get all newly created geometry of the given type from whatever arbitrary 300 | operations were done on the mesh. This assumes new geometry was created 301 | and not destroyed. 302 | 303 | **Parameters** 304 | 305 | - `mesh` **SimplicialComplex** 306 | - `key` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 307 | - `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** 308 | 309 | **Examples** 310 | 311 | _Usage:_ 312 | 313 | ```javascript 314 | const extrudedCells = quad.getNewGeometry(mesh, "cells", () => { 315 | quad.extrude(mesh, tipCell, 0.5, 3) 316 | }); 317 | ``` 318 | 319 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 320 | 321 | ### inset 322 | 323 | Inset a cell some value between `0` (its edges) and `1` (its center). 324 | 325 | b----------c 326 | |\ q1 /| 327 | | \ / | 328 | | f----g | 329 | |q0| tC |q2| tc = targetCell 330 | | e----h | 331 | | / \ | 332 | |/ q3 \| 333 | a----------d 334 | 335 | **Parameters** 336 | 337 | - `mesh` **SimplicialComplex** 338 | - `cell` **Cell** 339 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1`, defaults to `0`. 340 | 341 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Cell>** cells `[q0, q1, q2, q3, targetCell]` 342 | 343 | ### insetDisjoint 344 | 345 | Inset a cell some value between `0` (its edges) and `1` (its center), but 346 | keep the new cells disjoint so they do not share any positions. 347 | 348 | bT----------cT 349 | bL \ qT / cR 350 | |\ \ / /| 351 | | \ fT----gT / | 352 | | fL fM----gM gR | 353 | |qL| | tC | |qR| tC = targetCell 354 | | eL eM----hM hR | 355 | | / eB----hB \ | 356 | |/ / \ \| 357 | aL / qB \ dR 358 | aB----------dB 359 | 360 | **Parameters** 361 | 362 | - `mesh` **SimplicialComplex** 363 | - `cell` **Cell** 364 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 0 - 1 (optional, default `0`) 365 | 366 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Cell>** cells `[qL, qT, qR, qB, targetCell]`. 367 | 368 | ### insetLoop 369 | 370 | Given a cell, walk a loop and inset the loop, where 0 is the inset being on 371 | the edge, and 1 the inset being in the enter. Setting opposite to true will 372 | make the cell walk the loop in the opposite direction, e.g. up/down rather 373 | than left/right. 374 | 375 | *----*----*----*----*----*----*----*----*----* 376 | | | | | | | | | | | 377 | | | |<---|----|----|----|--->| | | 378 | | | | | |cell| | | | | 379 | | | |<---|----|----|----|--->| | | 380 | | | | | | | | | | | 381 | *----*----*----*----*----*----*----*----*----* 382 | 383 | **Parameters** 384 | 385 | - `mesh` **SimplicialComplex** 386 | - `cell` **Cell** 387 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1` 388 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 389 | 390 | Returns **SimplicialComplex** 391 | 392 | ### mergePositions 393 | 394 | Combine all positions together and recompute the normals. 395 | 396 | **Parameters** 397 | 398 | - `mesh` **SimplicialComplex** 399 | 400 | Returns **SimplicialComplex** 401 | 402 | ### mirror 403 | 404 | Clone all existing geometry, and mirror it about the given axis. 405 | 406 | **Parameters** 407 | 408 | - `mesh` **SimplicialComplex** 409 | - `cells` **Cell** 410 | - `axis` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is either `0`, `1`, or `2`, which represents the `x`, `y`, and `z` axis respectively. 411 | 412 | ### splitHorizontal 413 | 414 | Split a cell horizontally. 415 | 416 | b--------c 417 | | | 418 | ab------cd 419 | | | 420 | a--------d 421 | 422 | **Parameters** 423 | 424 | - `mesh` **SimplicialComplex** 425 | - `cell` **Cell** 426 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1` 427 | - `targetCell` 428 | 429 | ### splitHorizontalDisjoint 430 | 431 | Split a cell horizontally into two new disconnected cells. 432 | 433 | b--------c 434 | | | 435 | ab1----cd1 436 | ab2----cd2 437 | | target | 438 | a--------d 439 | 440 | **Parameters** 441 | 442 | - `mesh` **SimplicialComplex** 443 | - `cell` **Cell** 444 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1` 445 | - `targetCell` 446 | 447 | ### splitLoop 448 | 449 | Given a cell, walk along the mesh in both directions and split the cell. 450 | 451 | *--------*--------*--------*--------*--------*--------*--------* 452 | | | | | | | | | 453 | * *<-------*--------*--cell--*--------*------->* * 454 | | | | | | | | | 455 | *--------*--------*--------*--------*--------*--------*--------* 456 | 457 | **Parameters** 458 | 459 | - `mesh` **SimplicialComplex** 460 | - `cell` **Cell** 461 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1` 462 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 463 | 464 | Returns **SimplicialComplex** 465 | 466 | ### splitVertical 467 | 468 | Split a cell horizontally. 469 | 470 | b---bc---c 471 | | | | 472 | | | | 473 | a---ad---d 474 | 475 | **Parameters** 476 | 477 | - `mesh` **SimplicialComplex** 478 | - `cell` **Cell** 479 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1`, defaults to `0.5`. 480 | - `targetCell` 481 | 482 | ### splitVerticalDisjoint 483 | 484 | Split a cell horizontally into two new disconnected cells. 485 | 486 | b---bc1 bc2---c 487 | | | | | 488 | | | | | 489 | a---ad1 ad2---d 490 | 491 | **Parameters** 492 | 493 | - `mesh` **SimplicialComplex** 494 | - `cell` **Cell** 495 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 0.5** Specifies where the split should be. Ranged from `0` to `1`, defaults to `0.5`. 496 | - `targetCell` 497 | 498 | ### subdivide 499 | 500 | Use catmull clark subdivision to smooth out the geometry. All normals will 501 | be recomputed. Under the hood this is a convenience function for the 502 | module [gl-catmull-clark](https://www.npmjs.com/package/gl-catmull-clark). 503 | 504 | **Parameters** 505 | 506 | - `mesh` **SimplicialComplex** 507 | - `subdivisions` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 508 | - `positions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Position>?= mesh.positions** 509 | - `cells` **Cell?= mesh.cells** 510 | 511 | Returns **SimplicialComplex** 512 | 513 | ### updateNormals 514 | 515 | Updates all of the normals for all the positions using 516 | [#averageNormalForPosition](#averageNormalForPosition). If a normal doesn't exist, 517 | then it is created. 518 | 519 | **Parameters** 520 | 521 | - `mesh` **SimplicialComplex** 522 | - `cell` **Cell** 523 | 524 | Returns **SimplicialComplex** 525 | 526 | ## Three 527 | 528 | 529 | 530 | ### splitVertical 531 | 532 | Split a cell horizontally. 533 | 534 | b---bc---c 535 | | | | 536 | | | | 537 | a---ad---d 538 | 539 | **Parameters** 540 | 541 | - `$0` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** quad 542 | - `$0.positions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 543 | - `$0.cells` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 544 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 545 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 546 | - `_ref` 547 | 548 | ### splitVerticalDisjoint 549 | 550 | Split a cell horizontally into two new disconnected cells. 551 | 552 | b---bc1 bc2---c 553 | | | | | 554 | | | | | 555 | a---ad1 ad2---d 556 | 557 | **Parameters** 558 | 559 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 560 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 561 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 562 | 563 | ### splitHorizontal 564 | 565 | Split a cell horizontally. 566 | 567 | b--------c 568 | | | 569 | ab------cd 570 | | | 571 | a--------d 572 | 573 | **Parameters** 574 | 575 | - `$0.positions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 576 | - `$0.cells` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 577 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 578 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 579 | - `_ref2` 580 | 581 | ### splitHorizontalDisjoint 582 | 583 | Split a cell horizontally into two new disconnected cells. 584 | 585 | b--------c 586 | | | 587 | ab1----cd1 588 | ab2----cd2 589 | | target | 590 | a--------d 591 | 592 | **Parameters** 593 | 594 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 595 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 596 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 597 | 598 | ### inset 599 | 600 | Inset a cell some value between `0` (its edges) and `1` (its center). 601 | 602 | b----------c 603 | |\ q1 /| 604 | | \ / | 605 | | f----g | 606 | |q0| tQ |q2| 607 | | e----h | 608 | | / \ | 609 | |/ q3 \| 610 | a----------d 611 | 612 | **Parameters** 613 | 614 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 615 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 616 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 617 | 618 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** cells `[q0, q1, q2, q3, tC]` where `tC` is the `targetCell`. 619 | 620 | ### extrude 621 | 622 | Given a target cell, first inset it, then move it along the cell's normal 623 | outwards by a given distance. 624 | 625 | **Parameters** 626 | 627 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 628 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 629 | - `insetT` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 630 | - `extrude` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 631 | 632 | ### averageNormalForPosition 633 | 634 | Computes the average normal for a position given the connected cells. 635 | 636 | **Parameters** 637 | 638 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 639 | - `positionIndex` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 640 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** = \[] 641 | - `normalCache` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)?** = new Map() can be provided to cache intermediate normal computations. 642 | - `positionIndexToCells` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 643 | 644 | Returns **any** the `targetNormal` 645 | 646 | ### insetDisjoint 647 | 648 | Inset a cell some value between `0` (its edges) and `1` (its center), but 649 | keep the new cells disjoint so they do not share any positions. 650 | 651 | bT----------cT 652 | bL \ qT / cR 653 | |\ \ / /| 654 | | \ fT----gT / | 655 | | fL fM----gM gR | 656 | |qL| | tC | |qR| tC = targetCell 657 | | eL eM----hM hR | 658 | | / eB----hB \ | 659 | |/ / \ \| 660 | aL / qB \ dR 661 | aB----------dB 662 | 663 | **Parameters** 664 | 665 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 666 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 667 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** **=0** 668 | 669 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** cells `[q0, q1, q2, q3, tC]` where `tC` is the `targetCell`. 670 | 671 | ### extrudeDisjoint 672 | 673 | Given a target cell, first inset it, then move it along the cell's normal 674 | outwards by a given distance, but all new geometry generated will not 675 | share positions. 676 | 677 | **Parameters** 678 | 679 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 680 | - `targetCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 681 | - `insetT` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 682 | - `extrude` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 683 | 684 | ### getCenter 685 | 686 | Computes the center of a cell 687 | 688 | **Parameters** 689 | 690 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 691 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 692 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 693 | 694 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** the targetPosition 695 | 696 | ### clone 697 | 698 | Clones a cell. Returns the new cell. 699 | 700 | **Parameters** 701 | 702 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 703 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 704 | 705 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** a new cell 706 | 707 | ### updateNormals 708 | 709 | Updates all of the normals for all the positions using 710 | [#averageNormalForPosition](#averageNormalForPosition). If a normal doesn't exist, 711 | then it is created. 712 | 713 | **Parameters** 714 | 715 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 716 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 717 | 718 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** mesh 719 | 720 | ### getCellNormal 721 | 722 | Compute a cell's normal regardless of it's neighboring cells. 723 | 724 | **Parameters** 725 | 726 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 727 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 728 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** **= \[]** 729 | 730 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** The target normal. 731 | 732 | ### getCellsFromPositionIndex 733 | 734 | Given a position index, find any cells that include it. 735 | 736 | **Parameters** 737 | 738 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 739 | - `index` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 740 | - `target` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 741 | 742 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** The target cells. 743 | 744 | ### flip 745 | 746 | Flip a cell's normal to point the other way. Returns the cell. 747 | 748 | **Parameters** 749 | 750 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 751 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 752 | 753 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** The cell 754 | 755 | ### createQuad 756 | 757 | Create a quad with options. If the optionalMesh object is passed, then the 758 | quad will be created inside of that simplicial complex, otherwise a new 759 | mesh simplicial complex will be generated. Both the mesh simplicial 760 | complex and the created cell are returned in an object. 761 | 762 | **Parameters** 763 | 764 | - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 765 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 766 | 767 | **Examples** 768 | 769 | _Usage:_ 770 | 771 | ```javascript 772 | const {mesh, cell} = createQuad({ positions: [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]] }) 773 | const {mesh, cell} = createQuad({ w: 1, h: 1 }) 774 | const {mesh, cell} = createQuad() 775 | ``` 776 | 777 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** `{mesh, cell}` 778 | 779 | ### createBoxDisjoint 780 | 781 | Creates a quad box of the given dimensions, but with non-joined positions. 782 | This box renders as a flat shaded box. If the optionalMesh object is 783 | passed, then the box will be created inside of that simplicial complex, 784 | otherwise a new mesh simplicial complex will be generated. 785 | 786 | **Parameters** 787 | 788 | - `x` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 789 | - `y` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 790 | - `z` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 791 | - `optionalMesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 792 | 793 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** a simplicial complex 794 | 795 | ### createBox 796 | 797 | Creates a quad box of the given dimensions. This box will render as a 798 | smoothed out box, as the normals are averaged. This is typically used for a 799 | starting place for subdividing or extrusion operations. If the 800 | `optionalMesh` object is passed, then the box will be created inside of 801 | that simplicial complex, otherwise a new mesh simplicial complex will be 802 | generated. 803 | 804 | **Parameters** 805 | 806 | - `x` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 807 | - `y` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 808 | - `z` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 809 | - `optionalMesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 810 | 811 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** a simplicial complex 812 | 813 | ### mergePositions 814 | 815 | Combine all positions together and recompute the normals. 816 | 817 | **Parameters** 818 | 819 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 820 | 821 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** mesh 822 | 823 | ### elementsFromQuads 824 | 825 | Returns an elements array using the given `ArrayType`, which can be used by WebGL. 826 | 827 | **Parameters** 828 | 829 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 830 | - `drawMode` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 831 | - `ArrayType` **typeof** 832 | 833 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Elements using the given `ArrayType`, which can be used by WebGL. 834 | 835 | ### computeNormals 836 | 837 | Updates all of the normals for all the positions using 838 | [#averageNormalForPosition](#averageNormalForPosition). If a normal doesn't exist, 839 | then it is created. 840 | 841 | **Parameters** 842 | 843 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 844 | 845 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** The mesh 846 | 847 | ### splitLoop 848 | 849 | Given a cell, walk along the mesh in both directions and split the cell. 850 | 851 | *--------*--------*--------*--------*--------*--------*--------* 852 | | | | | | | | | 853 | * *<-------*--------*--cell--*--------*------->* * 854 | | | | | | | | | 855 | *--------*--------*--------*--------*--------*--------*--------* 856 | 857 | **Parameters** 858 | 859 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 860 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 861 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 862 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 863 | 864 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** mesh 865 | 866 | ### getCellFromEdge 867 | 868 | Find a cell given two position indices. Optionally provide a `previousCell` 869 | that will not be matched against. Returns the first cell that matches. 870 | 871 | **Parameters** 872 | 873 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 874 | - `positionIndexA` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 875 | - `positionIndexB` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 876 | - `previousCell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Optional will not be matched against 877 | 878 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Elements using the given `ArrayType`, which can be used by WebGL. 879 | 880 | ### getNewGeometry 881 | 882 | Get all newly created geometry of the given type from whatever arbitrary 883 | operations were done on the mesh. This assumes new geometry was created 884 | and not destroyed. 885 | 886 | **Parameters** 887 | 888 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 889 | - `key` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 890 | - `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** 891 | 892 | **Examples** 893 | 894 | _Usage:_ 895 | 896 | ```javascript 897 | const extrudedCells = quad.getNewGeometry(mesh, "cells", () => { 898 | quad.extrude(mesh, tipCell, 0.5, 3) 899 | }); 900 | ``` 901 | 902 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 903 | 904 | ### subdivide 905 | 906 | Use catmull clark subdivision to smooth out the geometry. All normals will 907 | be recomputed. Under the hood this is a convenience function for the 908 | module [gl-catmull-clark](https://www.npmjs.com/package/gl-catmull-clark). 909 | 910 | **Parameters** 911 | 912 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 913 | - `subdivisions` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 914 | - `positions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 915 | - `cells` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 916 | 917 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** mesh 918 | 919 | ### computeCenterPositions 920 | 921 | Computes all of the centers of all the cells. 922 | 923 | **Parameters** 924 | 925 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 926 | 927 | Returns **any** A new array 928 | 929 | ### computeCellCenter 930 | 931 | Computes the center of a single cell. 932 | 933 | **Parameters** 934 | 935 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 936 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 937 | 938 | Returns **any** A new array 939 | 940 | ### insetLoop 941 | 942 | Given a cell, walk a loop and inset the loop, where 0 is the inset being on 943 | the edge, and 1 the inset being in the enter. Setting opposite to true will 944 | make the cell walk the loop in the opposite direction, e.g. up/down rather 945 | than left/right. 946 | 947 | *----*----*----*----*----*----*----*----*----* 948 | | | | | | | | | | | 949 | | | |<---|----|----|----|--->| | | 950 | | | | | |cell| | | | | 951 | | | |<---|----|----|----|--->| | | 952 | | | | | | | | | | | 953 | *----*----*----*----*----*----*----*----*----* 954 | 955 | **Parameters** 956 | 957 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 958 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 959 | - `t` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Specifies where the split should be. Ranged from `0` to `1` 960 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 961 | 962 | Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** mesh 963 | 964 | ### getLoop 965 | 966 | Gets a loop of cells. Given a single cell, start walking in both 967 | directions to select a loop. . 968 | 969 | **Parameters** 970 | 971 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 972 | - `cell` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 973 | - `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** can either be `"cells"`, `"positions"`, or `"normals"`. 974 | - `opposite` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** will walk in the opposite direction, e.g. up and down, versus left and right 975 | 976 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** an array according to the `type`. 977 | 978 | ### mirror 979 | 980 | Clone all existing geometry, and mirror it about the given axis. 981 | 982 | **Parameters** 983 | 984 | - `mesh` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** 985 | - `cells` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** 986 | - `axis` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is either `0`, `1`, or `2`, which represents the `x`, `y`, and `z` axis respectively. 987 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2017-02-09 4 | 5 | Added a cloneCells command. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quads", 3 | "version": "1.2.0", 4 | "description": "Quad geometry tools", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/gregtatum/quads" 9 | }, 10 | "scripts": { 11 | "test": "node test | faucet", 12 | "build": "babel quads.js > index.js", 13 | "prepublish": "npm run build", 14 | "docs": "npm run docs-source && npm run docs-types && npm run docs-three", 15 | "docs-source": "documentation readme --sort-order=alpha --section=API quads.js", 16 | "docs-types": "documentation readme --sort-order=source --section=Types types.js", 17 | "docs-three": "documentation readme --sort-order=source --section=Three three.js", 18 | "lint": "standard" 19 | }, 20 | "keywords": [ 21 | "webgl", 22 | "stackgl", 23 | "quads", 24 | "gl", 25 | "geometry", 26 | "model", 27 | "mesh" 28 | ], 29 | "author": "Greg Tatum", 30 | "license": "MIT", 31 | "dependencies": { 32 | "gl-catmull-clark": "^1.0.0", 33 | "gl-vec3": "^1.0.3" 34 | }, 35 | "devDependencies": { 36 | "babel-cli": "^6.22.2", 37 | "babel-preset-env": "^1.1.8", 38 | "documentation": "^4.0.0-beta.18", 39 | "faucet": "0.0.1", 40 | "tape": "^4.6.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /quads.js: -------------------------------------------------------------------------------- 1 | const vec3 = require('gl-vec3') 2 | const catmullClark = require('gl-catmull-clark') 3 | 4 | /** 5 | * Split a cell horizontally. 6 | * 7 | * ``` 8 | * b---bc---c 9 | * | | | 10 | * | | | 11 | * a---ad---d 12 | * ``` 13 | * 14 | * @param {SimplicialComplex} mesh 15 | * @param {Cell} cell 16 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1`, defaults to `0.5`. 17 | */ 18 | function splitVertical (mesh, targetCell, t = 0.5) { 19 | const {positions, cells} = mesh 20 | const [a, b, c, d] = targetCell 21 | const positionA = positions[a] 22 | const positionB = positions[b] 23 | const positionC = positions[c] 24 | const positionD = positions[d] 25 | const bcPosition = vec3.lerp([], positionB, positionC, t) 26 | const adPosition = vec3.lerp([], positionA, positionD, t) 27 | const bc = positions.length 28 | const ad = bc + 1 29 | positions[bc] = bcPosition 30 | positions[ad] = adPosition 31 | targetCell[2] = bc 32 | targetCell[3] = ad 33 | cells.push([ad, bc, c, d]) 34 | } 35 | 36 | /** 37 | * Split a cell horizontally into two new disconnected cells. 38 | * 39 | * ``` 40 | * b---bc1 bc2---c 41 | * | | | | 42 | * | | | | 43 | * a---ad1 ad2---d 44 | * ``` 45 | * 46 | * @param {SimplicialComplex} mesh 47 | * @param {Cell} cell 48 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1`, defaults to `0.5`. 49 | */ 50 | function splitVerticalDisjoint (mesh, targetCell, t = 0.5) { 51 | const {positions, cells, normals} = mesh 52 | const [a, b, c, d] = targetCell 53 | const bc1 = positions.length 54 | const ad1 = bc1 + 1 55 | const bc2 = bc1 + 2 56 | const ad2 = bc1 + 3 57 | 58 | // Add the positions 59 | const bcPosition = vec3.lerp([], positions[b], positions[c], t) 60 | const adPosition = vec3.lerp([], positions[a], positions[d], t) 61 | positions[bc1] = bcPosition 62 | positions[ad1] = adPosition 63 | positions[bc2] = bcPosition.slice() 64 | positions[ad2] = adPosition.slice() 65 | 66 | // Update the cells 67 | targetCell[2] = bc1 68 | targetCell[3] = ad1 69 | cells.push([ad2, bc2, c, d]) 70 | 71 | // Normals - assume that disjoint splits all share the same normal. 72 | const normal = normals[a] 73 | normals[ad1] = normal.slice() 74 | normals[ad2] = normal.slice() 75 | normals[bc1] = normal.slice() 76 | normals[bc2] = normal.slice() 77 | } 78 | 79 | /** 80 | * Split a cell horizontally. 81 | * 82 | * ``` 83 | * b--------c 84 | * | | 85 | * ab------cd 86 | * | | 87 | * a--------d 88 | * ``` 89 | * 90 | * @param {SimplicialComplex} mesh 91 | * @param {Cell} cell 92 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1` 93 | */ 94 | function splitHorizontal (mesh, targetCell, t = 0.5) { 95 | const {positions, cells} = mesh 96 | const [a, b, c, d] = targetCell 97 | const positionA = positions[a] 98 | const positionB = positions[b] 99 | const positionC = positions[c] 100 | const positionD = positions[d] 101 | const abPosition = vec3.lerp([], positionB, positionA, t) 102 | const cdPosition = vec3.lerp([], positionC, positionD, t) 103 | const ab = positions.length 104 | const cd = ab + 1 105 | positions[ab] = abPosition 106 | positions[cd] = cdPosition 107 | targetCell[1] = ab 108 | targetCell[2] = cd 109 | cells.push([ab, b, c, cd]) 110 | } 111 | 112 | /** 113 | * Split a cell horizontally into two new disconnected cells. 114 | * 115 | * ``` 116 | * b--------c 117 | * | | 118 | * ab1----cd1 119 | * ab2----cd2 120 | * | target | 121 | * a--------d 122 | * ``` 123 | * 124 | * @param {SimplicialComplex} mesh 125 | * @param {Cell} cell 126 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1` 127 | */ 128 | function splitHorizontalDisjoint (mesh, targetCell, t = 0.5) { 129 | const {positions, cells, normals} = mesh 130 | const [a, b, c, d] = targetCell 131 | const ab1 = positions.length 132 | const cd1 = ab1 + 1 133 | const ab2 = ab1 + 2 134 | const cd2 = ab1 + 3 135 | 136 | // Positions 137 | const abPosition = vec3.lerp([], positions[a], positions[b], t) 138 | const cdPosition = vec3.lerp([], positions[d], positions[c], t) 139 | positions[ab1] = abPosition 140 | positions[cd1] = cdPosition 141 | positions[ab2] = abPosition.slice() 142 | positions[cd2] = cdPosition.slice() 143 | 144 | // Cells 145 | targetCell[0] = ab1 146 | targetCell[3] = cd1 147 | cells.push([a, ab2, cd2, d]) 148 | 149 | // Normals - assume that disjoint splits all share the same normal. 150 | const normal = normals[a] 151 | normals[ab1] = normal.slice() 152 | normals[cd1] = normal.slice() 153 | normals[ab2] = normal.slice() 154 | normals[cd2] = normal.slice() 155 | } 156 | 157 | /** 158 | * Inset a cell some value between `0` (its edges) and `1` (its center). 159 | * 160 | * ``` 161 | * b----------c 162 | * |\ q1 /| 163 | * | \ / | 164 | * | f----g | 165 | * |q0| tC |q2| tc = targetCell 166 | * | e----h | 167 | * | / \ | 168 | * |/ q3 \| 169 | * a----------d 170 | * ``` 171 | * 172 | * @param {SimplicialComplex} mesh 173 | * @param {Cell} cell 174 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1`, defaults to `0`. 175 | * @returns {Cell[]} cells `[q0, q1, q2, q3, targetCell]` 176 | */ 177 | var inset = (() => { 178 | var center = [0, 0, 0] 179 | return function (mesh, targetCell, t = 0) { 180 | const {positions, cells, normals} = mesh 181 | const [a, b, c, d] = targetCell 182 | const e = positions.length 183 | const f = e + 1 184 | const g = f + 1 185 | const h = g + 1 186 | const positionA = positions[a] 187 | const positionB = positions[b] 188 | const positionC = positions[c] 189 | const positionD = positions[d] 190 | 191 | // Update positions 192 | center[0] = (positionA[0] + positionB[0] + positionC[0] + positionD[0]) / 4 193 | center[1] = (positionA[1] + positionB[1] + positionC[1] + positionD[1]) / 4 194 | center[2] = (positionA[2] + positionB[2] + positionC[2] + positionD[2]) / 4 195 | positions.push(vec3.lerp([], positionA, center, t)) 196 | positions.push(vec3.lerp([], positionB, center, t)) 197 | positions.push(vec3.lerp([], positionC, center, t)) 198 | positions.push(vec3.lerp([], positionD, center, t)) 199 | normals.push(normals[a].slice()) 200 | normals.push(normals[b].slice()) 201 | normals.push(normals[c].slice()) 202 | normals.push(normals[d].slice()) 203 | 204 | // Update cells 205 | targetCell[0] = e 206 | targetCell[1] = f 207 | targetCell[2] = g 208 | targetCell[3] = h 209 | const q0 = [a, b, f, e] 210 | const q1 = [f, b, c, g] 211 | const q2 = [h, g, c, d] 212 | const q3 = [a, e, h, d] 213 | cells.push(q0) 214 | cells.push(q1) 215 | cells.push(q2) 216 | cells.push(q3) 217 | return [q0, q1, q2, q3, targetCell] 218 | } 219 | })() 220 | 221 | /** 222 | * Given a target cell, first inset it, then move it along the cell's normal 223 | * outwards by a given distance. 224 | * 225 | * @param {SimplicialComplex} mesh 226 | * @param {Cell} cell 227 | * @param {Number} insetT Value ranged from `0` to `1`, defaults to `0` 228 | * @param {Number} extrude Distance to extrude, defaults to `0` 229 | */ 230 | var extrude = (() => { 231 | const toTranslate = [] 232 | const translation = [] 233 | const targetCellNormal = [] 234 | return function (mesh, targetCell, insetT = 0, extrude = 0) { 235 | const {positions, normals} = mesh 236 | const ring = inset(mesh, targetCell, insetT) 237 | const [qL, qT, qR, qB] = ring 238 | 239 | // Enumerate which positions to translate 240 | toTranslate[0] = targetCell[0] 241 | toTranslate[1] = targetCell[1] 242 | toTranslate[2] = targetCell[2] 243 | toTranslate[3] = targetCell[3] 244 | 245 | toTranslate[4] = qL[2] 246 | toTranslate[5] = qL[3] 247 | 248 | toTranslate[6] = qT[0] 249 | toTranslate[7] = qT[3] 250 | 251 | toTranslate[8] = qR[0] 252 | toTranslate[9] = qR[1] 253 | 254 | toTranslate[10] = qB[1] 255 | toTranslate[11] = qB[2] 256 | 257 | getCellNormal(mesh, targetCell, targetCellNormal) 258 | vec3.scale(translation, targetCellNormal, extrude) 259 | 260 | for (let i = 0; i < toTranslate.length; i++) { 261 | const position = positions[toTranslate[i]] 262 | vec3.add(position, position, translation) 263 | } 264 | 265 | // Update all of the affected normals by averaging a position's neighboring 266 | // cell's normals. This will create some intermediate allocations, that will 267 | // then be GCed. 268 | const normalCache = new Map() 269 | normalCache.set(targetCell, targetCellNormal) 270 | const [a, b, c, d] = targetCell 271 | const e = positions.length - 4 272 | const f = positions.length - 3 273 | const g = positions.length - 2 274 | const h = positions.length - 1 275 | averageNormalForPosition(mesh, a, normals[a], normalCache) 276 | averageNormalForPosition(mesh, b, normals[b], normalCache) 277 | averageNormalForPosition(mesh, c, normals[c], normalCache) 278 | averageNormalForPosition(mesh, d, normals[d], normalCache) 279 | averageNormalForPosition(mesh, e, normals[e], normalCache) 280 | averageNormalForPosition(mesh, f, normals[f], normalCache) 281 | averageNormalForPosition(mesh, g, normals[g], normalCache) 282 | averageNormalForPosition(mesh, h, normals[h], normalCache) 283 | } 284 | })() 285 | 286 | function _calculatePositionIndexToCells (mesh) { 287 | const toCells = new Map() 288 | for (let i = 0; i < mesh.cells.length; i++) { 289 | const cell = mesh.cells[i] 290 | for (let j = 0; j < cell.length; j++) { 291 | const index = cell[j] 292 | let arr = toCells.get(index) 293 | if (!arr) { 294 | arr = [] 295 | toCells.set(index, arr) 296 | } 297 | arr.push(cell) 298 | } 299 | } 300 | return toCells 301 | } 302 | 303 | /** 304 | * Computes the average normal for a position given the connected cells. 305 | * 306 | * @param {SimplicialComplex} mesh 307 | * @param {Number} positionIndex 308 | * @param {Array?} target = [] 309 | * @param {Map?} normalCache A Map can be provided to cache intermediate normal computations. 310 | * @param {Map?} positionIndexToCells A Map where positionIndex is mapped to its cell, used primarily internally. 311 | * @returns {Normal} target 312 | */ 313 | var averageNormalForPosition = (() => { 314 | const cellsCache = [] 315 | 316 | return function averageNormalForPosition (mesh, positionIndex, target = [], normalCache = new Map(), positionIndexToCells) { 317 | let cells 318 | if (positionIndexToCells) { 319 | cells = positionIndexToCells.get(positionIndex) 320 | } else { 321 | cells = getCellsFromPositionIndex(mesh, positionIndex, cellsCache) 322 | } 323 | vec3.set(target, 0, 0, 0) 324 | 325 | // Add neighboring cells' normals 326 | for (let i = 0; i < cells.length; i++) { 327 | const cell = cells[i] 328 | let normal 329 | if (normalCache) { 330 | normal = normalCache.get(cell) 331 | } 332 | if (!normal) { 333 | normal = getCellNormal(mesh, cell, []) 334 | if (normalCache) { 335 | normalCache.set(normal) 336 | } 337 | } 338 | vec3.add(target, target, normal) 339 | } 340 | vec3.normalize(target, target) 341 | 342 | // Clean out the cellsCache. 343 | while (cellsCache.length) { 344 | cellsCache.pop() 345 | } 346 | return target 347 | } 348 | })() 349 | 350 | /** 351 | * Inset a cell some value between `0` (its edges) and `1` (its center), but 352 | * keep the new cells disjoint so they do not share any positions. 353 | * 354 | * ``` 355 | * bT----------cT 356 | * bL \ qT / cR 357 | * |\ \ / /| 358 | * | \ fT----gT / | 359 | * | fL fM----gM gR | 360 | * |qL| | tC | |qR| tC = targetCell 361 | * | eL eM----hM hR | 362 | * | / eB----hB \ | 363 | * |/ / \ \| 364 | * aL / qB \ dR 365 | * aB----------dB 366 | * ``` 367 | * 368 | * @param {SimplicialComplex} mesh 369 | * @param {Cell} cell 370 | * @param {Number} [t=0] value between 0 - 1 371 | * @returns {Cell[]} cells `[qL, qT, qR, qB, targetCell]`. 372 | */ 373 | var insetDisjoint = (() => { 374 | var center = [0, 0, 0] 375 | return function (mesh, targetCell, t = 0) { 376 | const {positions, cells, normals} = mesh 377 | const [a, b, c, d] = targetCell 378 | const positionA = positions[a] 379 | const positionB = positions[b] 380 | const positionC = positions[c] 381 | const positionD = positions[d] 382 | 383 | // Calculate inset positions 384 | center[0] = (positionA[0] + positionB[0] + positionC[0] + positionD[0]) / 4 385 | center[1] = (positionA[1] + positionB[1] + positionC[1] + positionD[1]) / 4 386 | center[2] = (positionA[2] + positionB[2] + positionC[2] + positionD[2]) / 4 387 | const positionE = vec3.lerp([], positionA, center, t) 388 | const positionF = vec3.lerp([], positionB, center, t) 389 | const positionG = vec3.lerp([], positionC, center, t) 390 | const positionH = vec3.lerp([], positionD, center, t) 391 | 392 | // Assign indices 393 | const offset = positions.length 394 | const aB = offset 395 | const aL = a 396 | const bL = b 397 | const bT = offset + 1 398 | const cT = offset + 2 399 | const cR = c 400 | const dR = d 401 | const dB = offset + 3 402 | const eM = offset + 4 403 | const eB = offset + 5 404 | const eL = offset + 6 405 | const fM = offset + 7 406 | const fL = offset + 8 407 | const fT = offset + 9 408 | const gM = offset + 10 409 | const gT = offset + 11 410 | const gR = offset + 12 411 | const hM = offset + 13 412 | const hR = offset + 14 413 | const hB = offset + 15 414 | 415 | // Update cells 416 | targetCell[0] = eM 417 | targetCell[1] = fM 418 | targetCell[2] = gM 419 | targetCell[3] = hM 420 | const qL = [aL, bL, fL, eL] 421 | const qT = [fT, bT, cT, gT] 422 | const qR = [hR, gR, cR, dR] 423 | const qB = [aB, eB, hB, dB] 424 | cells.push(qL) 425 | cells.push(qT) 426 | cells.push(qR) 427 | cells.push(qB) 428 | 429 | // Update positions 430 | positions[aB] = positionA.slice() 431 | positions[aL] = positionA 432 | positions[bL] = positionB 433 | positions[bT] = positionB.slice() 434 | positions[cT] = positionC.slice() 435 | positions[cR] = positionC 436 | positions[dR] = positionD 437 | positions[dB] = positionD.slice() 438 | positions[eM] = positionE 439 | positions[eB] = positionE.slice() 440 | positions[eL] = positionE.slice() 441 | positions[fM] = positionF 442 | positions[fL] = positionF.slice() 443 | positions[fT] = positionF.slice() 444 | positions[gM] = positionG 445 | positions[gT] = positionG.slice() 446 | positions[gR] = positionG.slice() 447 | positions[hM] = positionH 448 | positions[hR] = positionH.slice() 449 | positions[hB] = positionH.slice() 450 | 451 | // Normals - assume that disjoint mesh all share the same normal. 452 | const normal = normals[a] 453 | normals[aB] = normal.slice() 454 | normals[aL] = normals[a] 455 | normals[bL] = normals[b] 456 | normals[bT] = normal.slice() 457 | normals[cT] = normal.slice() 458 | normals[cR] = normals[c] 459 | normals[dR] = normals[d] 460 | normals[dB] = normal.slice() 461 | normals[eM] = normal.slice() 462 | normals[eB] = normal.slice() 463 | normals[eL] = normal.slice() 464 | normals[fM] = normal.slice() 465 | normals[fL] = normal.slice() 466 | normals[fT] = normal.slice() 467 | normals[gM] = normal.slice() 468 | normals[gT] = normal.slice() 469 | normals[gR] = normal.slice() 470 | normals[hM] = normal.slice() 471 | normals[hR] = normal.slice() 472 | normals[hB] = normal.slice() 473 | 474 | return [qL, qT, qR, qB, targetCell] 475 | } 476 | })() 477 | 478 | /** 479 | * Given a target cell, first inset it, then move it along the cell's normal 480 | * outwards by a given distance, but all new geometry generated will not 481 | * share positions. 482 | * 483 | * @param {SimplicialComplex} mesh 484 | * @param {Cell} cell 485 | * @param {Number} insetT = 0, ranged from `0` (the edge) to `1` (the center). 486 | * @param {Number} extrude = 0, the distance to extrude out. 487 | */ 488 | var extrudeDisjoint = (() => { 489 | const toTranslate = [] 490 | const translation = [] 491 | return function (mesh, targetCell, insetT = 0, extrude = 0) { 492 | const {positions, normals} = mesh 493 | const ring = insetDisjoint(mesh, targetCell, insetT) 494 | const [qL, qT, qR, qB] = ring 495 | 496 | // Enumerate which positions to translate 497 | toTranslate[0] = targetCell[0] 498 | toTranslate[1] = targetCell[1] 499 | toTranslate[2] = targetCell[2] 500 | toTranslate[3] = targetCell[3] 501 | 502 | toTranslate[4] = qL[2] 503 | toTranslate[5] = qL[3] 504 | 505 | toTranslate[6] = qT[0] 506 | toTranslate[7] = qT[3] 507 | 508 | toTranslate[8] = qR[0] 509 | toTranslate[9] = qR[1] 510 | 511 | toTranslate[10] = qB[1] 512 | toTranslate[11] = qB[2] 513 | 514 | // Assume that disjoint cells all share the same normal. 515 | const targetCellNormal = normals[targetCell[0]] 516 | vec3.scale(translation, targetCellNormal, extrude) 517 | 518 | for (let i = 0; i < toTranslate.length; i++) { 519 | const position = positions[toTranslate[i]] 520 | vec3.add(position, position, translation) 521 | } 522 | 523 | // Calculate the normals for the translated rings. 524 | for (let i = 0; i < ring.length; i++) { 525 | updateNormals(mesh, ring[i]) 526 | } 527 | } 528 | })() 529 | 530 | /** 531 | * Computes the center of a cell. 532 | * 533 | * @param {SimplicialComplex} mesh 534 | * @param {Cell} cell 535 | * @param {Position} target 536 | * @returns {Position} center 537 | */ 538 | function getCenter (mesh, cell, target = []) { 539 | const a = mesh.positions[cell[0]] 540 | const b = mesh.positions[cell[1]] 541 | const c = mesh.positions[cell[2]] 542 | const d = mesh.positions[cell[3]] 543 | target[0] = (a[0] + b[0] + c[0] + d[0]) * 0.25 544 | target[1] = (a[1] + b[1] + c[1] + d[1]) * 0.25 545 | target[2] = (a[2] + b[2] + c[2] + d[2]) * 0.25 546 | return target 547 | } 548 | 549 | /** 550 | * Clones a cell. Returns the new cell. 551 | * 552 | * @param {SimplicialComplex} mesh 553 | * @param {Cell} cell 554 | * @returns {Cell} cloned cell 555 | */ 556 | function clone (mesh, cell) { 557 | const index = mesh.positions.length 558 | const clonedCell = [index, index + 1, index + 2, index + 3] 559 | mesh.cells.push(clonedCell) 560 | mesh.positions.push(mesh.positions[cell[0]].slice()) 561 | mesh.positions.push(mesh.positions[cell[1]].slice()) 562 | mesh.positions.push(mesh.positions[cell[2]].slice()) 563 | mesh.positions.push(mesh.positions[cell[3]].slice()) 564 | mesh.normals.push(mesh.normals[cell[0]].slice()) 565 | mesh.normals.push(mesh.normals[cell[1]].slice()) 566 | mesh.normals.push(mesh.normals[cell[2]].slice()) 567 | mesh.normals.push(mesh.normals[cell[3]].slice()) 568 | return clonedCell 569 | } 570 | 571 | /** 572 | * Clones a group of cells and their geometry. Use getNewGeometry to capture the newly 573 | * created geometry. 574 | * 575 | * @param {SimplicialComplex} mesh 576 | * @param {Cell[]} cells 577 | * @returns {SimplicialComplex} the original mesh. 578 | */ 579 | function cloneCells (mesh, cells) { 580 | // Get a list of the position indices used 581 | const positions = [] 582 | for (let i = 0; i < cells.length; i++) { 583 | const cell = cells[i] 584 | for (let j = 0; j < cell.length; j++) { 585 | const positionIndex = cell[j] 586 | positions[positionIndex] = positionIndex 587 | } 588 | } 589 | const indices = positions.filter(i => i !== undefined) 590 | 591 | // Clone the cells. 592 | const cellIndexOffset = mesh.positions.length 593 | const cellsLength = cells.length 594 | for (let i = 0; i < cellsLength; i++) { 595 | const cell = cells[i] 596 | mesh.cells.push( 597 | cell.map(cellIndex => indices.indexOf(cellIndex) + cellIndexOffset) 598 | ) 599 | } 600 | 601 | // Clone the positions. 602 | for (let i = 0; i < indices.length; i++) { 603 | mesh.positions.push(mesh.positions[indices[i]].slice()) 604 | } 605 | 606 | // Clone the normals. 607 | for (let i = 0; i < indices.length; i++) { 608 | mesh.normals.push(mesh.normals[indices[i]].slice()) 609 | } 610 | 611 | return mesh 612 | } 613 | 614 | /** 615 | * Updates all of the normals for all the positions using 616 | * {@link #averageNormalForPosition}. If a normal doesn't exist, 617 | * then it is created. 618 | * 619 | * @param {SimplicialComplex} mesh 620 | * @param {Cell} cell 621 | * @returns {SimplicialComplex} 622 | */ 623 | function updateNormals (mesh, cell) { 624 | let normal = mesh.normals[cell[0]] 625 | getCellNormal(mesh, cell, normal) 626 | vec3.copy(mesh.normals[cell[1]], normal) 627 | vec3.copy(mesh.normals[cell[2]], normal) 628 | vec3.copy(mesh.normals[cell[3]], normal) 629 | } 630 | 631 | var getCellNormal = (() => { 632 | const edgeA = [] 633 | const edgeB = [] 634 | /** 635 | * Compute a cell's normal regardless of it's neighboring cells. 636 | * 637 | * @param {SimplicialComplex} mesh 638 | * @param {Cell} cell 639 | * @param {Normal?} target **= []** 640 | * @returns {Normal} The target normal. 641 | */ 642 | return function getCellNormal (mesh, cell, target = []) { 643 | const positionA = mesh.positions[cell[0]] 644 | const positionB = mesh.positions[cell[1]] 645 | const positionC = mesh.positions[cell[2]] 646 | vec3.subtract(edgeA, positionB, positionA) 647 | vec3.subtract(edgeB, positionC, positionB) 648 | vec3.normalize(target, vec3.cross(target, edgeA, edgeB)) 649 | return target 650 | } 651 | })() 652 | 653 | /** 654 | * Given a position index, find any cells that include it. 655 | * 656 | * @param {SimplicialComplex} mesh 657 | * @param {Number} index 658 | * @param {Cell[]} target 659 | * @returns {Cell[]} The target cells. 660 | */ 661 | function getCellsFromPositionIndex (mesh, index, target = []) { 662 | for (let i = 0; i < mesh.cells.length; i++) { 663 | const cell = mesh.cells[i] 664 | if (cell.indexOf(index) >= 0) { 665 | target.push(cell) 666 | } 667 | } 668 | return target 669 | } 670 | 671 | /** 672 | * Flip a cell's normal to point the other way. Returns the cell. 673 | * 674 | * @param {SimplicialComplex} mesh 675 | * @param {Cell} cell 676 | * @returns {Cell} The cell 677 | */ 678 | function flip (mesh, cell) { 679 | const [a, b, c, d] = cell 680 | cell.reverse() 681 | const nA = mesh.normals[a] 682 | const nB = mesh.normals[b] 683 | const nC = mesh.normals[c] 684 | const nD = mesh.normals[d] 685 | vec3.scale(nA, nA, -1) 686 | vec3.scale(nB, nB, -1) 687 | vec3.scale(nC, nC, -1) 688 | vec3.scale(nD, nD, -1) 689 | return cell 690 | } 691 | 692 | /** 693 | * Create a quad with options. If the optionalMesh object is passed, then the 694 | * quad will be created inside of that simplicial complex, otherwise a new 695 | * mesh simplicial complex will be generated. Both the mesh simplicial 696 | * complex and the created cell are returned in an object. 697 | * 698 | * @example Usage: 699 | * 700 | * const {mesh, cell} = createQuad({ positions: [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]] }) 701 | * const {mesh, cell} = createQuad({ w: 1, h: 1 }) 702 | * const {mesh, cell} = createQuad() 703 | * 704 | * @param {Object} options 705 | * @param {SimplicialComplex} mesh 706 | * @returns {Object} `{mesh, cell}` 707 | */ 708 | function createQuad (options, mesh = {}) { 709 | if (!mesh.positions) { 710 | mesh.positions = [] 711 | } 712 | if (!mesh.normals) { 713 | mesh.normals = [] 714 | } 715 | if (!mesh.cells) { 716 | mesh.cells = [] 717 | } 718 | const index = mesh.positions.length 719 | let direction 720 | const cell = [ 721 | index, 722 | index + 1, 723 | index + 2, 724 | index + 3 725 | ] 726 | mesh.cells.push(cell) 727 | if (options && options.positions) { 728 | mesh.positions.push(options.positions[0]) 729 | mesh.positions.push(options.positions[1]) 730 | mesh.positions.push(options.positions[2]) 731 | mesh.positions.push(options.positions[3]) 732 | } else { 733 | let w, h 734 | if (options && options.w && options.h) { 735 | w = options.w / 2 736 | h = options.h / 2 737 | } else { 738 | w = 0.5 739 | h = 0.5 740 | } 741 | const facing = options && options.facing ? options.facing : 'y+' 742 | const axis = facing[0] 743 | direction = facing[1] 744 | switch (axis) { 745 | case 'x': 746 | mesh.positions.push([0, -w, -h]) 747 | mesh.positions.push([0, w, -h]) 748 | mesh.positions.push([0, w, h]) 749 | mesh.positions.push([0, -w, h]) 750 | break 751 | case 'y': 752 | mesh.positions.push([-w, 0, -h]) 753 | mesh.positions.push([-w, 0, h]) 754 | mesh.positions.push([w, 0, h]) 755 | mesh.positions.push([w, 0, -h]) 756 | break 757 | case 'z': 758 | mesh.positions.push([w, -h, 0]) 759 | mesh.positions.push([w, h, 0]) 760 | mesh.positions.push([-w, h, 0]) 761 | mesh.positions.push([-w, -h, 0]) 762 | break 763 | } 764 | } 765 | 766 | const normal = getCellNormal(mesh, cell, []) 767 | mesh.normals.push(normal) 768 | mesh.normals.push(normal.slice()) 769 | mesh.normals.push(normal.slice()) 770 | mesh.normals.push(normal.slice()) 771 | 772 | if (direction === '-') { 773 | flip(mesh, cell) 774 | } 775 | 776 | return {mesh, cell} 777 | } 778 | 779 | /** 780 | * Creates a quad box of the given dimensions, but with non-joined positions. 781 | * This box renders as a flat shaded box. If the optionalMesh object is 782 | * passed, then the box will be created inside of that simplicial complex, 783 | * otherwise a new mesh simplicial complex will be generated. 784 | * 785 | * @param {Number} x 786 | * @param {Number} y 787 | * @param {Number} z 788 | * @param {Object?} optionalMesh 789 | * @returns {SimplicialComplex} 790 | */ 791 | function createBoxDisjoint (x = 1, y = 1, z = 1, optionalMesh) { 792 | const {mesh, cell} = createQuad({w: x, h: z}, optionalMesh) 793 | mesh.positions.forEach(position => { 794 | position[1] -= y / 2 795 | }) 796 | clone(mesh, cell) 797 | flip(mesh, mesh.cells[1]) 798 | extrudeDisjoint(mesh, cell, 0, y) 799 | return mesh 800 | } 801 | 802 | /** 803 | * Creates a quad box of the given dimensions. This box will render as a 804 | * smoothed out box, as the normals are averaged. This is typically used for a 805 | * starting place for subdividing or extrusion operations. If the 806 | * `optionalMesh` object is passed, then the box will be created inside of 807 | * that simplicial complex, otherwise a new mesh simplicial complex will be 808 | * generated. 809 | * 810 | * @param {Number?} x = 1 811 | * @param {Number?} y = 1 812 | * @param {Number?} z = 1 813 | * @param {Object?} optionalMesh 814 | * @returns {SimplicialComplex} 815 | */ 816 | function createBox (x, y, z, optionalMesh) { 817 | return mergePositions(createBoxDisjoint(x, y, z, optionalMesh)) 818 | } 819 | 820 | /** 821 | * Combine all positions together and recompute the normals. 822 | * 823 | * @param {SimplicialComplex} mesh 824 | * @returns {SimplicialComplex} 825 | */ 826 | function mergePositions (mesh) { 827 | const {positions, normals, cells} = mesh 828 | // Go through each position. 829 | for (let aIndex = 0; aIndex < positions.length; aIndex++) { 830 | const a = positions[aIndex] 831 | 832 | // Compare this position to the rest of the position. 833 | for (let bIndex = aIndex + 1; bIndex < positions.length; bIndex++) { 834 | const b = positions[bIndex] 835 | 836 | // If the positions match, then remove "a" from positions. 837 | if (a[0] === b[0] && a[1] === b[1] && a[2] === b[2]) { 838 | // Update the cells to point to the bIndex. 839 | for (let k = 0; k < cells.length; k++) { 840 | const cell = cells[k] 841 | for (let l = 0; l < cell.length; l++) { 842 | const index = cell[l] 843 | if (index === aIndex) { 844 | cell[l] = bIndex - 1 845 | } else if (index > aIndex) { 846 | cell[l]-- 847 | } 848 | } 849 | } 850 | 851 | // Remove the position and continue 852 | positions.splice(aIndex, 1) 853 | normals.splice(aIndex, 1) 854 | aIndex-- 855 | break 856 | } 857 | } 858 | } 859 | 860 | const normalCache = new Map() 861 | for (let i = 0; i < positions.length; i++) { 862 | averageNormalForPosition(mesh, i, normals[i], normalCache) 863 | } 864 | return mesh 865 | } 866 | /** 867 | * Returns an elements array using the given `ArrayType`, which can be used by WebGL. 868 | * 869 | * @param {SimplicialComplex} mesh 870 | * @param {String} drawMode 871 | * @param {typeof} ArrayType 872 | * @returns {Array} Elements using the given `ArrayType`, which can be used by WebGL. 873 | */ 874 | function elementsFromQuads (mesh, drawMode = 'triangles', ArrayType = Uint16Array) { 875 | const countPerCell = drawMode === 'lines' ? 8 : 6 876 | const elements = new ArrayType(mesh.cells.length * countPerCell) 877 | 878 | if (drawMode === 'lines') { 879 | // lines 880 | for (let i = 0; i < mesh.cells.length; i++) { 881 | const [a, b, c, d] = mesh.cells[i] 882 | const offset = i * countPerCell 883 | // Lines 884 | elements[offset + 0] = a 885 | elements[offset + 1] = b 886 | 887 | elements[offset + 2] = b 888 | elements[offset + 3] = c 889 | 890 | elements[offset + 4] = c 891 | elements[offset + 5] = d 892 | 893 | elements[offset + 6] = d 894 | elements[offset + 7] = a 895 | } 896 | } else { 897 | for (let i = 0; i < mesh.cells.length; i++) { 898 | const offset = i * countPerCell 899 | const [a, b, c, d] = mesh.cells[i] 900 | // Triangle: 901 | elements[offset + 0] = a 902 | elements[offset + 1] = b 903 | elements[offset + 2] = c 904 | 905 | elements[offset + 3] = c 906 | elements[offset + 4] = d 907 | elements[offset + 5] = a 908 | } 909 | } 910 | return elements 911 | } 912 | 913 | /** 914 | * Updates all of the normals for all the positions using 915 | * {@link #averageNormalForPosition}. If a normal doesn't exist, 916 | * then it is created. 917 | * 918 | * @param {SimplicialComplex} mesh 919 | * @returns {SimplicialComplex} 920 | */ 921 | function computeNormals (mesh) { 922 | if (!mesh.normals) { 923 | mesh.normals = [] 924 | } 925 | const normalCache = new Map() 926 | const positionIndexToCells = _calculatePositionIndexToCells(mesh) 927 | for (let i = 0; i < mesh.positions.length; i++) { 928 | let normal = mesh.normals[i] 929 | if (!normal) { 930 | normal = [] 931 | mesh.normals[i] = normal 932 | } 933 | averageNormalForPosition(mesh, i, normal, normalCache, positionIndexToCells) 934 | } 935 | return mesh 936 | } 937 | 938 | /** 939 | * Given a cell, walk along the mesh in both directions and split the cell. 940 | * 941 | * ``` 942 | * *--------*--------*--------*--------*--------*--------*--------* 943 | * | | | | | | | | 944 | * * *<-------*--------*--cell--*--------*------->* * 945 | * | | | | | | | | 946 | * *--------*--------*--------*--------*--------*--------*--------* 947 | * ``` 948 | * 949 | * @param {SimplicialComplex} mesh 950 | * @param {Cell} cell 951 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1` 952 | * @param {Boolean} opposite - will walk in the opposite direction, e.g. up and down, versus left and right 953 | * @returns {SimplicialComplex} 954 | */ 955 | function splitLoop (mesh, cell, t = 0.5, opposite) { 956 | let cellIndexA, cellIndexB, cellIndexC, cellIndexD 957 | if (opposite) { 958 | cellIndexA = 1 959 | cellIndexB = 2 960 | cellIndexC = 3 961 | cellIndexD = 0 962 | } else { 963 | cellIndexA = 0 964 | cellIndexB = 1 965 | cellIndexC = 2 966 | cellIndexD = 3 967 | } 968 | 969 | const positionIndexLB = cell[cellIndexA] 970 | const positionIndexLT = cell[cellIndexB] 971 | const positionIndexMT = mesh.positions.length 972 | const positionIndexMB = mesh.positions.length + 1 973 | const positionIndexRT = cell[cellIndexC] 974 | const positionIndexRB = cell[cellIndexD] 975 | 976 | const positionA = vec3.lerp([], mesh.positions[positionIndexLT], mesh.positions[positionIndexRT], t) 977 | const positionB = vec3.lerp([], mesh.positions[positionIndexLB], mesh.positions[positionIndexRB], t) 978 | const normalA = vec3.lerp([], mesh.normals[positionIndexLT], mesh.normals[positionIndexRT], t) 979 | const normalB = vec3.lerp([], mesh.normals[positionIndexLB], mesh.normals[positionIndexRB], t) 980 | mesh.positions.push(positionA) 981 | mesh.positions.push(positionB) 982 | mesh.normals.push(vec3.normalize(normalA, normalA)) 983 | mesh.normals.push(vec3.normalize(normalB, normalB)) 984 | 985 | // Split cells 986 | const cellL = cell 987 | const cellR = [] 988 | mesh.cells.push(cellR) 989 | cellL[cellIndexC] = positionIndexMT 990 | cellL[cellIndexD] = positionIndexMB 991 | cellR[cellIndexA] = positionIndexMB 992 | cellR[cellIndexB] = positionIndexMT 993 | cellR[cellIndexC] = positionIndexRT 994 | cellR[cellIndexD] = positionIndexRB 995 | 996 | // Split by walking up and down from the cell, and then merge the last points if they 997 | // meet. 998 | const newPositionIndex = _walkAndSplitLoop(mesh, positionIndexLT, positionIndexMT, positionIndexRT, t) 999 | const didMerge = _mergePositionsIfEqual(mesh, newPositionIndex, positionIndexMB) 1000 | 1001 | if (!didMerge) { 1002 | _walkAndSplitLoop(mesh, positionIndexRB, positionIndexMB, positionIndexLB, 1 - t) 1003 | } 1004 | 1005 | return mesh 1006 | } 1007 | 1008 | function _mergePositionsIfEqual (mesh, positionIndexA, positionIndexB) { 1009 | const {positions, normals, cells} = mesh 1010 | if (positionIndexA >= 0 && positionIndexB >= 0) { 1011 | const positionA = positions[positionIndexA] 1012 | const positionB = positions[positionIndexB] 1013 | if ( 1014 | positionA[0] === positionB[0] && 1015 | positionA[1] === positionB[1] && 1016 | positionA[2] === positionB[2] 1017 | ) { 1018 | const positionIndexSaved = positionIndexA < positionIndexB 1019 | ? positionIndexA 1020 | : positionIndexB 1021 | const positionIndexDeleted = positionIndexA > positionIndexB 1022 | ? positionIndexA 1023 | : positionIndexB 1024 | 1025 | // Update the cells. 1026 | for (let k = 0; k < cells.length; k++) { 1027 | const cell = cells[k] 1028 | for (let l = 0; l < cell.length; l++) { 1029 | const positionIndex = cell[l] 1030 | if (positionIndex === positionIndexDeleted) { 1031 | cell[l] = positionIndexSaved 1032 | } else if (positionIndex > positionIndexDeleted) { 1033 | cell[l] = positionIndex - 1 1034 | } 1035 | } 1036 | } 1037 | 1038 | // Remove the position and continue 1039 | positions.splice(positionIndexDeleted, 1) 1040 | normals.splice(positionIndexDeleted, 1) 1041 | } 1042 | } 1043 | } 1044 | 1045 | /* 1046 | * Utility function to split mesh in a loop in a single direction, based off of the 1047 | * previously split quad's positions. The cell orientation is based off the previously 1048 | * split cell. 1049 | * 1050 | * LT----MT---RT 1051 | * | . | 1052 | * | . | <- split this cell 1053 | * | . | 1054 | * LB----MB---RB 1055 | * | | | 1056 | * | | | <- previous cell 1057 | * | | | 1058 | * *----*-----* 1059 | */ 1060 | function _walkAndSplitLoop (mesh, positionIndexLB, positionIndexMB, positionIndexRB, t) { 1061 | let newPositionIndex 1062 | while (true) { 1063 | const cell = getCellFromEdge(mesh, positionIndexLB, positionIndexRB) 1064 | if (!cell) { 1065 | break 1066 | } 1067 | const cellIndexA = cell.indexOf(positionIndexLB) 1068 | const cellIndexD = cell.indexOf(positionIndexRB) 1069 | const cellIndexB = (cellIndexA + 1) % 4 1070 | const cellIndexC = (cellIndexD + 3) % 4 1071 | 1072 | const positionIndexLT = cell[cellIndexB] 1073 | const positionIndexMT = mesh.positions.length 1074 | const positionIndexRT = cell[cellIndexC] 1075 | 1076 | // Create a new middle position at the opposite end 1077 | const position = vec3.lerp([], mesh.positions[positionIndexLT], mesh.positions[positionIndexRT], t) 1078 | const normal = vec3.lerp([], mesh.normals[positionIndexLT], mesh.normals[positionIndexRT], t) 1079 | vec3.normalize(normal, normal) 1080 | mesh.positions.push(position) 1081 | mesh.normals.push(normal) 1082 | 1083 | // Construct the split cells. 1084 | const cellL = cell 1085 | const cellR = [] 1086 | mesh.cells.push(cellR) 1087 | 1088 | cellL[cellIndexC] = positionIndexMT 1089 | cellL[cellIndexD] = positionIndexMB 1090 | 1091 | cellR[cellIndexA] = positionIndexMB 1092 | cellR[cellIndexB] = positionIndexMT 1093 | cellR[cellIndexC] = positionIndexRT 1094 | cellR[cellIndexD] = positionIndexRB 1095 | 1096 | // Modify the arguments to keep on walking. 1097 | positionIndexLB = positionIndexLT 1098 | positionIndexMB = positionIndexMT 1099 | positionIndexRB = positionIndexRT 1100 | 1101 | newPositionIndex = positionIndexMT 1102 | } 1103 | return newPositionIndex 1104 | } 1105 | 1106 | /** 1107 | * Find a cell given two position indices. Optionally provide a `previousCell` 1108 | * that will not be matched against. Returns the first cell that matches. 1109 | * 1110 | * @param {SimplicialComplex} mesh 1111 | * @param {Number} positionIndexA 1112 | * @param {Number} positionIndexB 1113 | * @param {Cell?} previousCell - Optional will not be matched against 1114 | * @returns {Cell} 1115 | */ 1116 | function getCellFromEdge (mesh, positionIndexA, positionIndexB, previousCell) { 1117 | return mesh.cells.find(cell => { 1118 | if (cell === previousCell) { 1119 | return false 1120 | } 1121 | const cellIndexA = cell.indexOf(positionIndexA) 1122 | if (cellIndexA >= 0) { 1123 | if ( 1124 | cell[(cellIndexA + 1) % 4] === positionIndexB || 1125 | cell[(cellIndexA + 3) % 4] === positionIndexB 1126 | ) { 1127 | return true 1128 | } 1129 | } 1130 | return false 1131 | }) 1132 | } 1133 | 1134 | /** 1135 | * Get all newly created geometry of the given type from whatever arbitrary 1136 | * operations were done on the mesh. This assumes new geometry was created 1137 | * and not destroyed. 1138 | * 1139 | * @example Usage: 1140 | * const extrudedCells = quad.getNewGeometry(mesh, "cells", () => { 1141 | * quad.extrude(mesh, tipCell, 0.5, 3) 1142 | * }); 1143 | * 1144 | * @param {SimplicialComplex} mesh 1145 | * @param {Number} key 1146 | * @param {Function} callback 1147 | * @returns {Array} 1148 | */ 1149 | function getNewGeometry (mesh, key, callback) { 1150 | const geometry = mesh[key] 1151 | let start = geometry.length 1152 | callback() 1153 | return geometry.slice(start, geometry.length) 1154 | } 1155 | 1156 | /** 1157 | * Use catmull clark subdivision to smooth out the geometry. All normals will 1158 | * be recomputed. Under the hood this is a convenience function for the 1159 | * module [gl-catmull-clark](https://www.npmjs.com/package/gl-catmull-clark). 1160 | * 1161 | * @param {SimplicialComplex} mesh 1162 | * @param {Number} subdivisions 1163 | * @param {Position[]} positions 1164 | * @param {Cell} cells 1165 | * @returns {SimplicialComplex} 1166 | */ 1167 | function subdivide (mesh, subdivisions, positions = mesh.positions, cells = mesh.cells) { 1168 | const result = catmullClark(positions, cells, subdivisions, false) 1169 | mesh.positions = result.positions 1170 | mesh.cells = result.cells 1171 | computeNormals(mesh) 1172 | return mesh 1173 | } 1174 | 1175 | /** 1176 | * Computes all of the centers of all the cells. 1177 | * 1178 | * @param {SimplicialComplex} mesh 1179 | * @returns {Position[]} centers 1180 | */ 1181 | function computeCenterPositions (mesh) { 1182 | return mesh.cells.map(cell => computeCellCenter(mesh, cell)) 1183 | } 1184 | 1185 | /** 1186 | * Computes the center of a single cell. 1187 | * 1188 | * @param {SimplicialComplex} mesh 1189 | * @param {Cell} cell 1190 | * @param {Position} targetPosition, defaults to a new array 1191 | * @returns {Position} center 1192 | */ 1193 | function computeCellCenter (mesh, cell, target = []) { 1194 | const [aI, bI, cI, dI] = cell 1195 | const { positions } = mesh 1196 | const a = positions[aI] 1197 | const b = positions[bI] 1198 | const c = positions[cI] 1199 | const d = positions[dI] 1200 | target[0] = (a[0] + b[0] + c[0] + d[0]) * 0.25 1201 | target[1] = (a[1] + b[1] + c[1] + d[1]) * 0.25 1202 | target[2] = (a[2] + b[2] + c[2] + d[2]) * 0.25 1203 | return target 1204 | } 1205 | 1206 | /** 1207 | * Given a cell, walk a loop and inset the loop, where 0 is the inset being on 1208 | * the edge, and 1 the inset being in the enter. Setting opposite to true will 1209 | * make the cell walk the loop in the opposite direction, e.g. up/down rather 1210 | * than left/right. 1211 | * 1212 | * ``` 1213 | * *----*----*----*----*----*----*----*----*----* 1214 | * | | | | | | | | | | 1215 | * | | |<---|----|----|----|--->| | | 1216 | * | | | | |cell| | | | | 1217 | * | | |<---|----|----|----|--->| | | 1218 | * | | | | | | | | | | 1219 | * *----*----*----*----*----*----*----*----*----* 1220 | * ``` 1221 | * 1222 | * @param {SimplicialComplex} mesh 1223 | * @param {Cell} cell 1224 | * @param {Number} t Specifies where the split should be. Ranged from `0` to `1` 1225 | * @param {Boolean} opposite - will walk in the opposite direction, e.g. up and down, versus left and right 1226 | * @returns {SimplicialComplex} 1227 | */ 1228 | function insetLoop (mesh, cell, t = 0.5, opposite) { 1229 | const tA = 1 - 0.5 * t 1230 | const tB = 0.5 * t + (1 - tA) * t 1231 | splitLoop(mesh, cell, tA, opposite) 1232 | splitLoop(mesh, cell, tB, opposite) 1233 | return mesh 1234 | } 1235 | 1236 | /** 1237 | * Gets a loop of cells. Given a single cell, start walking in both 1238 | * directions to select a loop. . 1239 | * 1240 | * @param {SimplicialComplex} mesh 1241 | * @param {Cell} cell 1242 | * @param {String} type - can either be `"cells"`, `"positions"`, or `"normals"`. 1243 | * @param {Boolean} opposite - will walk in the opposite direction, e.g. up and down, versus left and right 1244 | * @returns {Array} an array according to the `type`. 1245 | */ 1246 | function getLoop (mesh, cell, type, opposite) { 1247 | if (type === 'cells') { 1248 | return _getLoopCells(mesh, cell, opposite) 1249 | } 1250 | let positionIndexLB, positionIndexRB 1251 | if (opposite) { 1252 | positionIndexLB = cell[1] 1253 | positionIndexRB = cell[2] 1254 | } else { 1255 | positionIndexLB = cell[0] 1256 | positionIndexRB = cell[1] 1257 | } 1258 | 1259 | return [ 1260 | ..._getLoopOneDirection(mesh, cell, type, positionIndexLB, positionIndexRB), 1261 | ...cell.map(i => mesh[type][i]), 1262 | ..._getLoopOneDirection(mesh, cell, type, positionIndexRB, positionIndexLB).reverse() 1263 | ] 1264 | } 1265 | 1266 | function _getLoopCells (mesh, cell, opposite) { 1267 | let positionIndexLB, positionIndexRB 1268 | if (opposite) { 1269 | positionIndexLB = cell[1] 1270 | positionIndexRB = cell[2] 1271 | } else { 1272 | positionIndexLB = cell[0] 1273 | positionIndexRB = cell[1] 1274 | } 1275 | 1276 | return [ 1277 | ..._getLoopCellsOneDirection(mesh, cell, positionIndexLB, positionIndexRB), 1278 | cell, 1279 | ..._getLoopCellsOneDirection(mesh, cell, positionIndexRB, positionIndexLB).reverse() 1280 | ] 1281 | } 1282 | 1283 | function _getLoopCellsOneDirection (mesh, cell, indexA, indexB) { 1284 | const loop = [] 1285 | let positionIndexLB = indexA 1286 | let positionIndexRB = indexB 1287 | let neighborCell = cell 1288 | while (true) { 1289 | neighborCell = getCellFromEdge(mesh, positionIndexLB, positionIndexRB, neighborCell) 1290 | if (!neighborCell || neighborCell === cell) { 1291 | break 1292 | } 1293 | 1294 | loop.push(neighborCell) 1295 | 1296 | const cellIndexA = neighborCell.indexOf(positionIndexLB) 1297 | const cellIndexD = neighborCell.indexOf(positionIndexRB) 1298 | const cellIndexB = (cellIndexA + 1) % 4 1299 | const cellIndexC = (cellIndexD + 3) % 4 1300 | 1301 | // Modify the arguments to keep on walking. 1302 | positionIndexLB = neighborCell[cellIndexB] 1303 | positionIndexRB = neighborCell[cellIndexC] 1304 | } 1305 | return loop 1306 | } 1307 | 1308 | function _getLoopOneDirection (mesh, cell, type, indexA, indexB) { 1309 | const loop = [] 1310 | let positionIndexLB = indexA 1311 | let positionIndexRB = indexB 1312 | let neighborCell = cell 1313 | while (true) { 1314 | neighborCell = getCellFromEdge(mesh, positionIndexLB, positionIndexRB, neighborCell) 1315 | if (!neighborCell || neighborCell === cell) { 1316 | break 1317 | } 1318 | 1319 | const cellIndexA = neighborCell.indexOf(positionIndexLB) 1320 | const cellIndexD = neighborCell.indexOf(positionIndexRB) 1321 | const cellIndexB = (cellIndexA + 1) % 4 1322 | const cellIndexC = (cellIndexD + 3) % 4 1323 | 1324 | loop.push(mesh[type][neighborCell[cellIndexB]]) 1325 | loop.push(mesh[type][neighborCell[cellIndexC]]) 1326 | 1327 | // Modify the arguments to keep on walking. 1328 | positionIndexLB = neighborCell[cellIndexB] 1329 | positionIndexRB = neighborCell[cellIndexC] 1330 | } 1331 | return loop 1332 | } 1333 | 1334 | /** 1335 | * Clone all existing geometry, and mirror it about the given axis. 1336 | * 1337 | * @param {SimplicialComplex} mesh 1338 | * @param {Cell} cells 1339 | * @param {Number} axis - is either `0`, `1`, or `2`, which represents the `x`, `y`, and `z` axis respectively. 1340 | */ 1341 | function mirror (mesh, cells, axis) { 1342 | const mirrorMap = {} 1343 | 1344 | cells.forEach(cell => { 1345 | const mirrorCell = cell.map(positionIndex => { 1346 | let mirrorIndex = mirrorMap[positionIndex] 1347 | if (mirrorIndex === undefined) { 1348 | mirrorIndex = mesh.positions.length 1349 | mirrorMap[positionIndex] = mirrorIndex 1350 | const position = mesh.positions[positionIndex] 1351 | const normal = mesh.normals[positionIndex] 1352 | const mirrorPosition = position.slice() 1353 | const mirrorNormal = normal.slice() 1354 | mirrorPosition[axis] *= -1 1355 | mirrorNormal[axis] *= -1 1356 | mesh.positions.push(mirrorPosition) 1357 | mesh.normals.push(mirrorNormal) 1358 | } 1359 | return mirrorIndex 1360 | }) 1361 | mirrorCell.reverse() 1362 | mesh.cells.push(mirrorCell) 1363 | }) 1364 | 1365 | return mesh 1366 | } 1367 | 1368 | module.exports = { 1369 | averageNormalForPosition, 1370 | clone, 1371 | cloneCells, 1372 | computeCenterPositions, 1373 | computeCellCenter, 1374 | computeNormals, 1375 | createBox, 1376 | createBoxDisjoint, 1377 | createQuad, 1378 | elementsFromQuads, 1379 | extrude, 1380 | extrudeDisjoint, 1381 | flip, 1382 | getCellNormal, 1383 | getCellFromEdge, 1384 | getCellsFromPositionIndex, 1385 | getCenter, 1386 | getLoop, 1387 | getNewGeometry, 1388 | inset, 1389 | insetDisjoint, 1390 | insetLoop, 1391 | mergePositions, 1392 | mirror, 1393 | subdivide, 1394 | splitHorizontal, 1395 | splitHorizontalDisjoint, 1396 | splitLoop, 1397 | splitVertical, 1398 | splitVerticalDisjoint, 1399 | updateNormals 1400 | } 1401 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const quad = require('./quads') 3 | 4 | test('createQuad', t => { 5 | test('createQuad with w h', t => { 6 | const {mesh, cell} = quad.createQuad({w: 2, h: 6}) 7 | t.plan(2) 8 | t.deepLooseEqual(mesh, { 9 | positions: [[-1, 0, -3], [-1, 0, 3], [1, 0, 3], [1, 0, -3]], 10 | normals: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]], 11 | cells: [[0, 1, 2, 3]] 12 | }, 'A quad simplicial complex is created') 13 | 14 | t.deepLooseEqual(cell, [0, 1, 2, 3], 'The cell is also returned.') 15 | }) 16 | 17 | test('createQuad with positions', t => { 18 | const {mesh, cell} = quad.createQuad({positions: [[-1, 0, -3], [-1, 0, 3], [1, 0, 3], [1, 0, -3]]}) 19 | t.plan(2) 20 | t.deepLooseEqual(mesh, { 21 | positions: [[-1, 0, -3], [-1, 0, 3], [1, 0, 3], [1, 0, -3]], 22 | normals: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]], 23 | cells: [[0, 1, 2, 3]] 24 | }, 'A quad simplicial complex is created') 25 | 26 | t.deepLooseEqual(cell, [0, 1, 2, 3], 'The cell is also returned.') 27 | }) 28 | 29 | test('createQuad with nothing', t => { 30 | const {mesh, cell} = quad.createQuad() 31 | t.plan(2) 32 | t.deepLooseEqual(mesh, { 33 | positions: [[-0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, 0.5], [0.5, 0, -0.5]], 34 | normals: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]], 35 | cells: [[0, 1, 2, 3]] 36 | }, 'A quad simplicial complex is created') 37 | 38 | t.deepLooseEqual(cell, [0, 1, 2, 3], 'The cell is also returned.') 39 | }) 40 | 41 | test('createQuad with w h and facing a direction', t => { 42 | const {mesh, cell} = quad.createQuad({w: 2, h: 6, facing: 'z-'}) 43 | t.plan(2) 44 | t.deepLooseEqual(mesh, { 45 | positions: [[1, -3, 0], [1, 3, 0], [-1, 3, 0], [-1, -3, 0]], 46 | normals: [[0, 0, -1], [0, 0, -1], [0, 0, -1], [0, 0, -1]], 47 | cells: [[3, 2, 1, 0]] 48 | }, 'A quad simplicial complex is created') 49 | 50 | t.deepLooseEqual(cell, [3, 2, 1, 0], 'The cell is also returned.') 51 | }) 52 | 53 | test('createQuad normal facing is correct', t => { 54 | t.plan(6) 55 | const xp = quad.createQuad({facing: 'x+'}) 56 | t.deepLooseEqual(xp.mesh.normals[0], [1, 0, 0], 'x+ points up') 57 | const xm = quad.createQuad({facing: 'x-'}) 58 | t.deepLooseEqual(xm.mesh.normals[0], [-1, 0, 0], 'x- points down') 59 | const yp = quad.createQuad({facing: 'y+'}) 60 | t.deepLooseEqual(yp.mesh.normals[0], [0, 1, 0], 'y+ points up') 61 | const ym = quad.createQuad({facing: 'y-'}) 62 | t.deepLooseEqual(ym.mesh.normals[0], [0, -1, 0], 'y- points down') 63 | const zp = quad.createQuad({facing: 'z+'}) 64 | t.deepLooseEqual(zp.mesh.normals[0], [0, 0, 1], 'z+ points up') 65 | const zm = quad.createQuad({facing: 'z-'}) 66 | t.deepLooseEqual(zm.mesh.normals[0], [0, 0, -1], 'z- points down') 67 | }) 68 | t.end() 69 | }) 70 | 71 | test.skip('averageNormalForPosition', t => { 72 | const mesh = quad.createBox(1, 1, 1) 73 | quad.subdivide(mesh, 1) 74 | const index = 0 75 | 76 | t.deepLooseEqual( 77 | mesh.normals[index], 78 | [ -0.5773502691896258, 0.5773502691896258, -0.5773502691896258 ], 79 | 'Normal is how it was originally computed' 80 | ) 81 | 82 | mesh.positions[index][1] += 0.2 83 | 84 | t.deepLooseEqual( 85 | mesh.normals[index], 86 | [ -0.5773502691896258, 0.5773502691896258, -0.5773502691896258 ], 87 | 'New normal was averaged by its neighbors.' 88 | ) 89 | 90 | t.end() 91 | }) 92 | 93 | test('clone', t => { 94 | const mesh = quad.createBox(1, 1, 1) 95 | const cellToPositions = i => mesh.positions[i] 96 | t.equals(mesh.cells.length, 6, 'starts with 6 cells') 97 | t.equals(mesh.positions.length, 8, 'starts with 8 cells') 98 | // console.log(mesh) 99 | quad.clone(mesh, mesh.cells[0]) 100 | t.equals(mesh.cells.length, 7, '1 cell was added') 101 | t.equals(mesh.positions.length, 12, '4 positions was added') 102 | t.deepLooseEqual( 103 | mesh.cells[0].map(cellToPositions), 104 | mesh.cells[6].map(cellToPositions), 105 | 'The newly added cell was equal to the old one.' 106 | ) 107 | t.end() 108 | }) 109 | 110 | test('cloneCells', t => { 111 | const mesh = quad.createBox(1, 1, 1) 112 | const cellToPositions = i => mesh.positions[i] 113 | t.equals(mesh.cells.length, 6, 'starts with 6 cells') 114 | t.equals(mesh.positions.length, 8, 'starts with 8 cells') 115 | const twoConcurrentCells = mesh.cells.slice(1, 3) 116 | const clonedCells = quad.getNewGeometry(mesh, 'cells', () => { 117 | quad.cloneCells(mesh, twoConcurrentCells) 118 | }) 119 | t.equals(mesh.cells.length, 6 + 2, '2 cells were added') 120 | t.equals(mesh.positions.length, 8 + 6, '6 positions were added') 121 | t.deepLooseEqual( 122 | twoConcurrentCells.map(cellToPositions), 123 | clonedCells.map(cellToPositions), 124 | 'The newly added cells was equal to the old one.' 125 | ) 126 | t.end() 127 | }) 128 | 129 | test('computeCenterPositions', t => { 130 | const mesh = quad.createBox(1, 1, 1) 131 | const centers = quad.computeCenterPositions(mesh) 132 | t.deepLooseEqual(centers, [ 133 | [0, 0.5, 0], 134 | [0, -0.5, 0], 135 | [-0.5, 0, 0], 136 | [0, 0, 0.5], 137 | [0.5, 0, 0], 138 | [0, 0, -0.5] 139 | ], 'The centers of the cells are calculated.') 140 | t.end() 141 | }) 142 | 143 | test('computeCellCenter', t => { 144 | const mesh = quad.createBox(1, 1, 1) 145 | const topQuad = mesh.cells[0] 146 | const center = quad.computeCellCenter(mesh, topQuad) 147 | t.deepLooseEqual(center, [0, 0.5, 0]) 148 | t.end() 149 | }) 150 | 151 | test('computeNormals - connected', t => { 152 | const mesh = quad.createBox(1, 1, 1) 153 | delete mesh.normals 154 | quad.computeNormals(mesh) 155 | t.deepLooseEqual(mesh.normals, [ 156 | [-0.5773502691896258, -0.5773502691896258, -0.5773502691896258], 157 | [-0.5773502691896258, -0.5773502691896258, 0.5773502691896258], 158 | [0.5773502691896258, -0.5773502691896258, 0.5773502691896258], 159 | [0.5773502691896258, -0.5773502691896258, -0.5773502691896258], 160 | [-0.5773502691896258, 0.5773502691896258, -0.5773502691896258], 161 | [-0.5773502691896258, 0.5773502691896258, 0.5773502691896258], 162 | [0.5773502691896258, 0.5773502691896258, 0.5773502691896258], 163 | [0.5773502691896258, 0.5773502691896258, -0.5773502691896258] 164 | ], 'Calculates normals that are the average of the surrounding cells') 165 | t.end() 166 | }) 167 | 168 | test('computeNormals - disjoint', t => { 169 | const mesh = quad.createBoxDisjoint(1, 1, 1) 170 | delete mesh.normals 171 | quad.computeNormals(mesh) 172 | t.deepLooseEqual(mesh.normals, [ 173 | [-1, 0, 0], 174 | [-1, 0, 0], 175 | [1, 0, 0], 176 | [1, 0, 0], 177 | [0, -1, 0], 178 | [0, -1, 0], 179 | [0, -1, 0], 180 | [0, -1, 0], 181 | [0, 0, -1], 182 | [0, 0, 1], 183 | [0, 0, 1], 184 | [0, 0, -1], 185 | [0, 1, 0], 186 | [0, 0, -1], 187 | [-1, 0, 0], 188 | [0, 1, 0], 189 | [-1, 0, 0], 190 | [0, 0, 1], 191 | [0, 1, 0], 192 | [0, 0, 1], 193 | [1, 0, 0], 194 | [0, 1, 0], 195 | [1, 0, 0], 196 | [0, 0, -1] 197 | ], 'Calculates normals that are unique to the disjoint cell.') 198 | t.end() 199 | }) 200 | 201 | test('createBox', t => { 202 | const mesh = quad.createBox(1, 2, 3) 203 | t.deepLooseEqual(mesh, { 204 | positions: [ 205 | [-0.5, -1, -1.5], 206 | [-0.5, -1, 1.5], 207 | [0.5, -1, 1.5], 208 | [0.5, -1, -1.5], 209 | [-0.5, 1, -1.5], 210 | [-0.5, 1, 1.5], 211 | [0.5, 1, 1.5], 212 | [0.5, 1, -1.5]], 213 | normals: [ 214 | [-0.5773502691896258, -0.5773502691896258, -0.5773502691896258], 215 | [-0.5773502691896258, -0.5773502691896258, 0.5773502691896258], 216 | [0.5773502691896258, -0.5773502691896258, 0.5773502691896258], 217 | [0.5773502691896258, -0.5773502691896258, -0.5773502691896258], 218 | [-0.5773502691896258, 0.5773502691896258, -0.5773502691896258], 219 | [-0.5773502691896258, 0.5773502691896258, 0.5773502691896258], 220 | [0.5773502691896258, 0.5773502691896258, 0.5773502691896258], 221 | [0.5773502691896258, 0.5773502691896258, -0.5773502691896258] 222 | ], 223 | cells: [ 224 | [4, 5, 6, 7], 225 | [3, 2, 1, 0], 226 | [0, 1, 5, 4], 227 | [5, 1, 2, 6], 228 | [7, 6, 2, 3], 229 | [0, 4, 7, 3] 230 | ] 231 | }) 232 | t.end() 233 | }) 234 | 235 | test.skip('createBoxDisjoint', t => { 236 | 237 | }) 238 | 239 | test.skip('elementsFromQuads', t => { 240 | 241 | }) 242 | 243 | test.skip('extrude', t => { 244 | 245 | }) 246 | 247 | test.skip('extrudeDisjoint', t => { 248 | 249 | }) 250 | 251 | test.skip('flip', t => { 252 | 253 | }) 254 | 255 | test.skip('getCellNormal', t => { 256 | 257 | }) 258 | 259 | test.skip('getCellFromEdge', t => { 260 | 261 | }) 262 | 263 | test.skip('getCellsFromPositionIndex', t => { 264 | 265 | }) 266 | 267 | test.skip('getCenter', t => { 268 | 269 | }) 270 | 271 | test.skip('getLoop', t => { 272 | 273 | }) 274 | 275 | test.skip('getNewGeometry', t => { 276 | 277 | }) 278 | 279 | test.skip('inset', t => { 280 | 281 | }) 282 | 283 | test.skip('insetDisjoint', t => { 284 | 285 | }) 286 | 287 | test.skip('insetLoop', t => { 288 | 289 | }) 290 | 291 | test.skip('mergePositions', t => { 292 | 293 | }) 294 | 295 | test.skip('mirror', t => { 296 | 297 | }) 298 | 299 | test.skip('subdivide', t => { 300 | 301 | }) 302 | 303 | test.skip('splitHorizontal', t => { 304 | 305 | }) 306 | 307 | test.skip('splitHorizontalDisjoint', t => { 308 | 309 | }) 310 | 311 | test.skip('splitLoop', t => { 312 | 313 | }) 314 | 315 | test.skip('splitVertical', t => { 316 | 317 | }) 318 | 319 | test.skip('splitVerticalDisjoint', t => { 320 | 321 | }) 322 | 323 | test.skip('updateNormals', t => { 324 | 325 | }) 326 | -------------------------------------------------------------------------------- /three.js: -------------------------------------------------------------------------------- 1 | var quad = require('./index') 2 | 3 | module.exports = function callToBufferGeometry (quads, threeOrBufferGeometry, bufferAttribute) { 4 | // Get a good reference to BufferGeometry and BufferAttribute 5 | var THREE, BufferGeometry, BufferAttribute 6 | 7 | if (!threeOrBufferGeometry) { 8 | THREE = window.THREE 9 | } 10 | 11 | if ( 12 | typeof threeOrBufferGeometry === 'object' && 13 | typeof threeOrBufferGeometry.BufferGeometry === 'function' 14 | ) { 15 | THREE = threeOrBufferGeometry 16 | } 17 | 18 | if (THREE) { 19 | BufferGeometry = THREE.BufferGeometry 20 | BufferAttribute = THREE.BufferAttribute 21 | } else if ( 22 | typeof threeOrBufferGeometry === 'function' && 23 | typeof bufferAttribute === 'function' 24 | ) { 25 | BufferGeometry = threeOrBufferGeometry 26 | BufferAttribute = bufferAttribute 27 | } else { 28 | throw new Error( 29 | 'The second parameter must be either the THREE global, or the second and third ' + 30 | 'parameters must be THREE.BufferGeometry and THREE.BufferAttribute respectively.' 31 | ) 32 | } 33 | 34 | return toBufferGeometry(quads, BufferGeometry, BufferAttribute) 35 | } 36 | 37 | function toBufferGeometry (quads, BufferGeometry, BufferAttribute) { 38 | var geometry = new BufferGeometry() 39 | var positions = new Float32Array(quads.positions.length * 3) 40 | var normals = new Float32Array(quads.normals.length * 3) 41 | var indices = quad.elementsFromQuads(quads) 42 | 43 | for (let i = 0; i < quads.positions.length; i++) { 44 | const position = quads.positions[i] 45 | const normal = quads.normal[i] 46 | positions[i * 3] = position[0] 47 | positions[i * 3 + 1] = position[1] 48 | positions[i * 3 + 2] = position[2] 49 | normals[i * 3] = normal[0] 50 | normals[i * 3 + 1] = normal[1] 51 | normals[i * 3 + 2] = normal[2] 52 | } 53 | 54 | geometry.setIndex(new BufferAttribute(indices, 1)) 55 | geometry.addAttribute('position', new BufferAttribute(positions, 3)) 56 | geometry.addAttribute('normal', new BufferAttribute(normals, 3)) 57 | 58 | return geometry 59 | } 60 | -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a complicated math word that means an object with `{ positions, cells }`. The 3 | * word `mesh` is used for convenience in this module, and `normals` are included with 4 | * this object. 5 | * 6 | * ```javascript 7 | * // A single quad oriented facing up. 8 | * const mesh = { 9 | * positions: [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]], 10 | * normals: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]], 11 | * cells: [[0, 1, 2, 3]] 12 | * } 13 | * ``` 14 | * 15 | * Additional attributes may be added for one's own applications. For example: 16 | * 17 | * ```javascript 18 | * mesh.colors = mesh.positions.map(p => [0, p.y, 0]) 19 | * ``` 20 | * 21 | * @property {Position[]} positions 22 | * @property {Cell[]} cells 23 | * @property {Normal[]} normals 24 | * @typedef {Object} SimplicialComplex 25 | */ 26 | 27 | /** 28 | * An array of 3 values representing a position [x, y, z]. 29 | * @typedef {Array} Position 30 | */ 31 | 32 | /** 33 | * In a simplicial complex, a cell is an array of of indices that refer to a position or 34 | * some other attribute like normals. Quads have 4 indices, and this module uses the 35 | * convention of `[a, b, c, d]` with clockwise winding order. 36 | * 37 | * ``` 38 | * b-------c 39 | * | | 40 | * | | 41 | * a-------d 42 | * ``` 43 | * 44 | * @typedef {Array} Cell 45 | */ 46 | 47 | /** 48 | * An array of 3 values, [x, y, z] representing a surface normal. A valid normal has a 49 | * length of 1. Normals are used for lighting calculation, and for knowing which way a 50 | * surface is oriented in space. Many operation rely on valid normals. 51 | * @typedef {Array} Normal 52 | */ 53 | --------------------------------------------------------------------------------