diff --git a/cell120.js b/cell120.js new file mode 100644 index 0000000..ffe39dc --- /dev/null +++ b/cell120.js @@ -0,0 +1,137 @@ +// trying to go from faces to dodecahedra + + +function shared_vertices(f1, f2) { + return f1.nodes.filter((f) => f2.nodes.includes(f)); +} + + +function adjacent_faces(f1, f2) { + // adjacent faces which share an edge, not just a vertex + const intersect = shared_vertices(f1, f2); + if( intersect.length < 2 ) { + return false; + } + if( intersect.length > 2 ) { + console.log(`warning: faces ${f1.id} and ${f2.id} have too many common vertices`); + } + return true; +} + + +function find_adjacent_faces(faces, face) { + const neighbours = faces.filter((f) => f.id !== face.id && adjacent_faces(f, face)); + return neighbours; +} + + + + +function find_dodeca_mutuals(faces, f1, f2) { + // for any two adjacent faces, find their common neighbours where + // all three share exactly one vertex (this, I think, guarantees that + // all are on the same dodecahedron) + + const n1 = find_adjacent_faces(faces, f1); + const n2 = find_adjacent_faces(faces, f2); + const common = n1.filter((f1) => n2.filter((f2) => f1.id === f2.id).length > 0 ); + // there's one extra here - the third which has two nodes in common with + // both f1 and f2 - filter it out + const mutuals = common.filter((cf) => { + const shared = cf.nodes.filter((n) => f1.nodes.includes(n) && f2.nodes.includes(n)); + return shared.length === 1 + }); + return mutuals; +} + +function find_dodeca_next(faces, dodeca, f1, f2) { + // of a pair of mutuals, return the one we haven't already got + const m = find_dodeca_mutuals(faces, f1, f2); + if( dodeca.filter((f) => f.id === m[0].id ).length > 0 ) { + m.shift(); + } + return m[0]; +} + +// from any two mutual faces, return all the faces in their dodecahedron + +function make_dodecahedron(faces, f1, f2) { + const dodecahedron = [ f1, f2 ]; + + // take f1 as the 'center', get the other four around it from f2 + const fs = find_dodeca_mutuals(faces, f1, f2); + const f3 = fs[0]; + const f6 = fs[1]; + dodecahedron.push(f3); + const f4 = find_dodeca_next(faces, dodecahedron, f1, f3); + dodecahedron.push(f4); + const f5 = find_dodeca_next(faces, dodecahedron, f1, f4); + dodecahedron.push(f5); + dodecahedron.push(f6); + + // get the next ring + + const f7 = find_dodeca_next(faces, dodecahedron, f6, f2); + dodecahedron.push(f7); + const f8 = find_dodeca_next(faces, dodecahedron, f2, f3); + dodecahedron.push(f8); + const f9 = find_dodeca_next(faces, dodecahedron, f3, f4); + dodecahedron.push(f9); + const f10 = find_dodeca_next(faces, dodecahedron, f4, f5); + dodecahedron.push(f10); + const f11 = find_dodeca_next(faces, dodecahedron, f5, f6); + dodecahedron.push(f11); + + // get the last + + const f12 = find_dodeca_next(faces, dodecahedron, f7, f8); + dodecahedron.push(f12); + + return dodecahedron; +} + + +// for a face, pick an edge, and then find the other two faces which +// share this edge. These can be used as the starting points for the +// first face's two dodecahedra + +function find_edge_neighbours(faces, face) { + const n1 = face.nodes[0]; + const n2 = face.nodes[1]; + return faces.filter((f) => f.id !== face.id && f.nodes.includes(n1) && f.nodes.includes(n2)); +} + + +// each face is in two dodecahedra: this returns them both + +function face_to_dodecahedra(faces, f) { + const edge_friends = find_edge_neighbours(faces, f); + const d1 = make_dodecahedron(faces, f, edge_friends[0]); + const d2 = make_dodecahedron(faces, f, edge_friends[1]); + return [ d1, d2 ]; +} + +// brute-force calculation of all dodecahedra + +function dd_fingerprint(dodecahedron) { + const ids = dodecahedron.map((face) => face.id); + ids.sort() + return ids.join(','); +} + +export function make_120cell_dodecahedra(faces) { + const dodecas = []; + const seen = {}; + for( const face of faces ) { + const dds = face_to_dodecahedra(faces, face); + for( const dd of dds ) { + const fp = dd_fingerprint(dd); + if( ! (fp in seen) ) { + console.log(`added dodeca ${fp}`); + dodecas.push(dd); + seen[fp] = 1; + } + } + } + return dodecas; +} diff --git a/docs/manual_index_ideas.md b/docs/manual_index_ideas.md new file mode 100644 index 0000000..7fa1cf0 --- /dev/null +++ b/docs/manual_index_ideas.md @@ -0,0 +1,12 @@ +# Indexing manually ideas + +- a list of all the faces (and maybe dodecahedra) which lets me assign + labels to them and lights up the interface + +- where faces / vertices are repeated in the table, assigning a vertex in one + spot changes it everywhere else + + + +separately - take the links generated by this codebase and apply them to +the d3.js forcemap code I wrote for the 24-cell \ No newline at end of file diff --git a/docs/notes_120_cell.md b/docs/notes_120_cell.md index 1022d42..bfcce99 100644 --- a/docs/notes_120_cell.md +++ b/docs/notes_120_cell.md @@ -1,4 +1,43 @@ +Steps forward - + +1. algorithm which, given a face, finds the two dodecahedra it belongs to + +2. using this, generate a list of all 120 dodecahedra: + +[ a b c d e f g h i j k l m n o p q r s t ] <- 20 vertices + +Check that each vertex appears in four of these + +Then - either manually start labelling them, or build an interface to help +with the manual labelling + + + +1. + +For a face: there are five edges, and ten other faces sharing an edge. + +These edges are in two sets: one for each dodecahedron. The sets are defined +by them sharing vertices which aren't in the first face. + +Go around a set of five, by pairs: for each pair, find the other neighbour - + +this gives the next five faces. + +There's only one face left, which is defined by the shared other vertices of +the last five. + + + + + + + + + +/// old shit below that didn't work VVVV + Chords: 1.74806 - the 120-cell has 7200 chords of this length diff --git a/dodecahedra.js b/dodecahedra.js new file mode 100644 index 0000000..3226c3d --- /dev/null +++ b/dodecahedra.js @@ -0,0 +1,1682 @@ +export const DODECAHEDRA = [ + [ + 1, + 4, + 2, + 145, + 626, + 169, + 173, + 553, + 149, + 147, + 171, + 554 + ], + [ + 1, + 5, + 3, + 193, + 641, + 217, + 221, + 565, + 197, + 195, + 219, + 566 + ], + [ + 2, + 6, + 3, + 529, + 244, + 242, + 649, + 290, + 292, + 530, + 241, + 289 + ], + [ + 4, + 6, + 5, + 541, + 268, + 266, + 659, + 314, + 316, + 542, + 265, + 313 + ], + [ + 7, + 10, + 8, + 151, + 629, + 175, + 179, + 556, + 155, + 153, + 177, + 557 + ], + [ + 7, + 11, + 9, + 199, + 643, + 223, + 227, + 568, + 203, + 201, + 225, + 569 + ], + [ + 8, + 12, + 9, + 532, + 250, + 248, + 652, + 296, + 298, + 533, + 247, + 295 + ], + [ + 10, + 12, + 11, + 544, + 274, + 272, + 661, + 320, + 322, + 545, + 271, + 319 + ], + [ + 13, + 16, + 14, + 157, + 632, + 181, + 185, + 559, + 161, + 159, + 183, + 560 + ], + [ + 13, + 17, + 15, + 205, + 645, + 229, + 233, + 571, + 209, + 207, + 231, + 572 + ], + [ + 14, + 18, + 15, + 535, + 256, + 254, + 654, + 302, + 304, + 536, + 253, + 301 + ], + [ + 16, + 18, + 17, + 547, + 280, + 278, + 662, + 326, + 328, + 548, + 277, + 325 + ], + [ + 19, + 22, + 20, + 163, + 634, + 187, + 191, + 562, + 167, + 165, + 189, + 563 + ], + [ + 19, + 23, + 21, + 211, + 646, + 235, + 239, + 574, + 215, + 213, + 237, + 575 + ], + [ + 20, + 24, + 21, + 538, + 262, + 260, + 657, + 308, + 310, + 539, + 259, + 307 + ], + [ + 22, + 24, + 23, + 550, + 286, + 284, + 664, + 332, + 334, + 551, + 283, + 331 + ], + [ + 25, + 28, + 26, + 146, + 625, + 194, + 196, + 531, + 148, + 145, + 193, + 529 + ], + [ + 25, + 29, + 27, + 158, + 631, + 206, + 208, + 537, + 160, + 157, + 205, + 535 + ], + [ + 26, + 30, + 27, + 577, + 341, + 337, + 674, + 349, + 353, + 578, + 339, + 351 + ], + [ + 28, + 30, + 29, + 589, + 389, + 385, + 689, + 397, + 401, + 590, + 387, + 399 + ], + [ + 31, + 34, + 32, + 152, + 628, + 200, + 202, + 534, + 154, + 151, + 199, + 532 + ], + [ + 31, + 35, + 33, + 164, + 633, + 212, + 214, + 540, + 166, + 163, + 211, + 538 + ], + [ + 32, + 36, + 33, + 580, + 347, + 343, + 677, + 355, + 359, + 581, + 345, + 357 + ], + [ + 34, + 36, + 35, + 592, + 395, + 391, + 691, + 403, + 407, + 593, + 393, + 405 + ], + [ + 37, + 40, + 38, + 170, + 635, + 218, + 220, + 543, + 172, + 169, + 217, + 541 + ], + [ + 37, + 41, + 39, + 182, + 639, + 230, + 232, + 549, + 184, + 181, + 229, + 547 + ], + [ + 38, + 42, + 39, + 583, + 365, + 361, + 682, + 373, + 377, + 584, + 363, + 375 + ], + [ + 40, + 42, + 41, + 595, + 413, + 409, + 693, + 421, + 425, + 596, + 411, + 423 + ], + [ + 43, + 46, + 44, + 176, + 637, + 224, + 226, + 546, + 178, + 175, + 223, + 544 + ], + [ + 43, + 47, + 45, + 188, + 640, + 236, + 238, + 552, + 190, + 187, + 235, + 550 + ], + [ + 44, + 48, + 45, + 586, + 371, + 367, + 685, + 379, + 383, + 587, + 369, + 381 + ], + [ + 46, + 48, + 47, + 598, + 419, + 415, + 695, + 427, + 431, + 599, + 417, + 429 + ], + [ + 49, + 52, + 50, + 241, + 651, + 247, + 251, + 601, + 245, + 243, + 249, + 602 + ], + [ + 49, + 53, + 51, + 289, + 666, + 295, + 299, + 613, + 293, + 291, + 297, + 614 + ], + [ + 50, + 54, + 51, + 530, + 340, + 338, + 673, + 386, + 388, + 531, + 337, + 385 + ], + [ + 52, + 54, + 53, + 533, + 346, + 344, + 676, + 392, + 394, + 534, + 343, + 391 + ], + [ + 55, + 58, + 56, + 253, + 656, + 259, + 263, + 604, + 257, + 255, + 261, + 605 + ], + [ + 55, + 59, + 57, + 301, + 669, + 307, + 311, + 616, + 305, + 303, + 309, + 617 + ], + [ + 56, + 60, + 57, + 536, + 352, + 350, + 678, + 398, + 400, + 537, + 349, + 397 + ], + [ + 58, + 60, + 59, + 539, + 358, + 356, + 680, + 404, + 406, + 540, + 355, + 403 + ], + [ + 61, + 64, + 62, + 265, + 660, + 271, + 275, + 607, + 269, + 267, + 273, + 608 + ], + [ + 61, + 65, + 63, + 313, + 671, + 319, + 323, + 619, + 317, + 315, + 321, + 620 + ], + [ + 62, + 66, + 63, + 542, + 364, + 362, + 681, + 410, + 412, + 543, + 361, + 409 + ], + [ + 64, + 66, + 65, + 545, + 370, + 368, + 684, + 416, + 418, + 546, + 367, + 415 + ], + [ + 67, + 70, + 68, + 277, + 663, + 283, + 287, + 610, + 281, + 279, + 285, + 611 + ], + [ + 67, + 71, + 69, + 325, + 672, + 331, + 335, + 622, + 329, + 327, + 333, + 623 + ], + [ + 68, + 72, + 69, + 548, + 376, + 374, + 686, + 422, + 424, + 549, + 373, + 421 + ], + [ + 70, + 72, + 71, + 551, + 382, + 380, + 688, + 428, + 430, + 552, + 379, + 427 + ], + [ + 73, + 76, + 74, + 147, + 627, + 159, + 162, + 579, + 150, + 146, + 158, + 577 + ], + [ + 73, + 77, + 75, + 171, + 636, + 183, + 186, + 585, + 174, + 170, + 182, + 583 + ], + [ + 74, + 78, + 75, + 554, + 436, + 434, + 697, + 458, + 460, + 555, + 433, + 457 + ], + [ + 76, + 78, + 77, + 560, + 448, + 446, + 702, + 470, + 472, + 561, + 445, + 469 + ], + [ + 79, + 82, + 80, + 153, + 630, + 165, + 168, + 582, + 156, + 152, + 164, + 580 + ], + [ + 79, + 83, + 81, + 177, + 638, + 189, + 192, + 588, + 180, + 176, + 188, + 586 + ], + [ + 80, + 84, + 81, + 557, + 442, + 440, + 700, + 464, + 466, + 558, + 439, + 463 + ], + [ + 82, + 84, + 83, + 563, + 454, + 452, + 704, + 476, + 478, + 564, + 451, + 475 + ], + [ + 85, + 88, + 86, + 195, + 642, + 207, + 210, + 591, + 198, + 194, + 206, + 589 + ], + [ + 85, + 89, + 87, + 219, + 647, + 231, + 234, + 597, + 222, + 218, + 230, + 595 + ], + [ + 86, + 90, + 87, + 566, + 484, + 482, + 709, + 506, + 508, + 567, + 481, + 505 + ], + [ + 88, + 90, + 89, + 572, + 496, + 494, + 714, + 518, + 520, + 573, + 493, + 517 + ], + [ + 91, + 94, + 92, + 201, + 644, + 213, + 216, + 594, + 204, + 200, + 212, + 592 + ], + [ + 91, + 95, + 93, + 225, + 648, + 237, + 240, + 600, + 228, + 224, + 236, + 598 + ], + [ + 92, + 96, + 93, + 569, + 490, + 488, + 712, + 512, + 514, + 570, + 487, + 511 + ], + [ + 94, + 96, + 95, + 575, + 502, + 500, + 716, + 524, + 526, + 576, + 499, + 523 + ], + [ + 97, + 100, + 98, + 243, + 650, + 267, + 270, + 555, + 246, + 242, + 266, + 553 + ], + [ + 97, + 101, + 99, + 249, + 653, + 273, + 276, + 558, + 252, + 248, + 272, + 556 + ], + [ + 98, + 102, + 99, + 602, + 437, + 433, + 699, + 439, + 443, + 603, + 435, + 441 + ], + [ + 100, + 102, + 101, + 608, + 461, + 457, + 706, + 463, + 467, + 609, + 459, + 465 + ], + [ + 103, + 106, + 104, + 255, + 655, + 279, + 282, + 561, + 258, + 254, + 278, + 559 + ], + [ + 103, + 107, + 105, + 261, + 658, + 285, + 288, + 564, + 264, + 260, + 284, + 562 + ], + [ + 104, + 108, + 105, + 605, + 449, + 445, + 703, + 451, + 455, + 606, + 447, + 453 + ], + [ + 106, + 108, + 107, + 611, + 473, + 469, + 708, + 475, + 479, + 612, + 471, + 477 + ], + [ + 109, + 112, + 110, + 291, + 665, + 315, + 318, + 567, + 294, + 290, + 314, + 565 + ], + [ + 109, + 113, + 111, + 297, + 667, + 321, + 324, + 570, + 300, + 296, + 320, + 568 + ], + [ + 110, + 114, + 111, + 614, + 485, + 481, + 711, + 487, + 491, + 615, + 483, + 489 + ], + [ + 112, + 114, + 113, + 620, + 509, + 505, + 718, + 511, + 515, + 621, + 507, + 513 + ], + [ + 115, + 118, + 116, + 303, + 668, + 327, + 330, + 573, + 306, + 302, + 326, + 571 + ], + [ + 115, + 119, + 117, + 309, + 670, + 333, + 336, + 576, + 312, + 308, + 332, + 574 + ], + [ + 116, + 120, + 117, + 617, + 497, + 493, + 715, + 499, + 503, + 618, + 495, + 501 + ], + [ + 118, + 120, + 119, + 623, + 521, + 517, + 720, + 523, + 527, + 624, + 519, + 525 + ], + [ + 121, + 124, + 122, + 339, + 675, + 345, + 348, + 603, + 342, + 338, + 344, + 601 + ], + [ + 121, + 125, + 123, + 351, + 679, + 357, + 360, + 606, + 354, + 350, + 356, + 604 + ], + [ + 122, + 126, + 123, + 578, + 438, + 435, + 698, + 447, + 450, + 579, + 434, + 446 + ], + [ + 124, + 126, + 125, + 581, + 444, + 441, + 701, + 453, + 456, + 582, + 440, + 452 + ], + [ + 127, + 130, + 128, + 363, + 683, + 369, + 372, + 609, + 366, + 362, + 368, + 607 + ], + [ + 127, + 131, + 129, + 375, + 687, + 381, + 384, + 612, + 378, + 374, + 380, + 610 + ], + [ + 128, + 132, + 129, + 584, + 462, + 459, + 705, + 471, + 474, + 585, + 458, + 470 + ], + [ + 130, + 132, + 131, + 587, + 468, + 465, + 707, + 477, + 480, + 588, + 464, + 476 + ], + [ + 133, + 136, + 134, + 387, + 690, + 393, + 396, + 615, + 390, + 386, + 392, + 613 + ], + [ + 133, + 137, + 135, + 399, + 692, + 405, + 408, + 618, + 402, + 398, + 404, + 616 + ], + [ + 134, + 138, + 135, + 590, + 486, + 483, + 710, + 495, + 498, + 591, + 482, + 494 + ], + [ + 136, + 138, + 137, + 593, + 492, + 489, + 713, + 501, + 504, + 594, + 488, + 500 + ], + [ + 139, + 142, + 140, + 411, + 694, + 417, + 420, + 621, + 414, + 410, + 416, + 619 + ], + [ + 139, + 143, + 141, + 423, + 696, + 429, + 432, + 624, + 426, + 422, + 428, + 622 + ], + [ + 140, + 144, + 141, + 596, + 510, + 507, + 717, + 519, + 522, + 597, + 506, + 518 + ], + [ + 142, + 144, + 143, + 599, + 516, + 513, + 719, + 525, + 528, + 600, + 512, + 524 + ], + [ + 148, + 150, + 149, + 244, + 340, + 341, + 438, + 436, + 246, + 245, + 342, + 437 + ], + [ + 154, + 156, + 155, + 250, + 346, + 347, + 444, + 442, + 252, + 251, + 348, + 443 + ], + [ + 160, + 162, + 161, + 256, + 352, + 353, + 450, + 448, + 258, + 257, + 354, + 449 + ], + [ + 166, + 168, + 167, + 262, + 358, + 359, + 456, + 454, + 264, + 263, + 360, + 455 + ], + [ + 172, + 174, + 173, + 268, + 364, + 365, + 462, + 460, + 270, + 269, + 366, + 461 + ], + [ + 178, + 180, + 179, + 274, + 370, + 371, + 468, + 466, + 276, + 275, + 372, + 467 + ], + [ + 184, + 186, + 185, + 280, + 376, + 377, + 474, + 472, + 282, + 281, + 378, + 473 + ], + [ + 190, + 192, + 191, + 286, + 382, + 383, + 480, + 478, + 288, + 287, + 384, + 479 + ], + [ + 196, + 198, + 197, + 292, + 388, + 389, + 486, + 484, + 294, + 293, + 390, + 485 + ], + [ + 202, + 204, + 203, + 298, + 394, + 395, + 492, + 490, + 300, + 299, + 396, + 491 + ], + [ + 208, + 210, + 209, + 304, + 400, + 401, + 498, + 496, + 306, + 305, + 402, + 497 + ], + [ + 214, + 216, + 215, + 310, + 406, + 407, + 504, + 502, + 312, + 311, + 408, + 503 + ], + [ + 220, + 222, + 221, + 316, + 412, + 413, + 510, + 508, + 318, + 317, + 414, + 509 + ], + [ + 226, + 228, + 227, + 322, + 418, + 419, + 516, + 514, + 324, + 323, + 420, + 515 + ], + [ + 232, + 234, + 233, + 328, + 424, + 425, + 522, + 520, + 330, + 329, + 426, + 521 + ], + [ + 238, + 240, + 239, + 334, + 430, + 431, + 528, + 526, + 336, + 335, + 432, + 527 + ], + [ + 625, + 627, + 626, + 641, + 642, + 631, + 632, + 636, + 635, + 647, + 645, + 639 + ], + [ + 628, + 630, + 629, + 643, + 644, + 633, + 634, + 638, + 637, + 648, + 646, + 640 + ], + [ + 649, + 651, + 650, + 659, + 665, + 666, + 652, + 653, + 660, + 671, + 667, + 661 + ], + [ + 654, + 656, + 655, + 662, + 668, + 669, + 657, + 658, + 663, + 672, + 670, + 664 + ], + [ + 673, + 675, + 674, + 689, + 690, + 676, + 677, + 679, + 678, + 692, + 691, + 680 + ], + [ + 681, + 683, + 682, + 693, + 694, + 684, + 685, + 687, + 686, + 696, + 695, + 688 + ], + [ + 697, + 699, + 698, + 702, + 705, + 706, + 700, + 701, + 703, + 708, + 707, + 704 + ], + [ + 709, + 711, + 710, + 714, + 717, + 718, + 712, + 713, + 715, + 720, + 719, + 716 + ] +]; \ No newline at end of file diff --git a/fourDShape.js b/fourDShape.js index c760d3f..32cfb78 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -6,13 +6,15 @@ const HYPERPLANE = 2.0; class FourDShape extends THREE.Group { - constructor(node_ms, link_ms, structure) { + constructor(node_ms, link_ms, face_ms, structure) { super(); this.node_ms = node_ms; this.link_ms = link_ms; + this.face_ms = face_ms; this.nodes4 = structure.nodes; this.nodes3 = {}; this.links = structure.links; + this.faces = ( "faces" in structure ) ? structure.faces : []; this.node_size = structure.geometry.node_size; this.link_size = structure.geometry.link_size; this.node_scale = 1; @@ -71,6 +73,32 @@ class FourDShape extends THREE.Group { link.object.children[0].rotation.x = Math.PI / 2.0; } + + setFaceGeometry(face, geometry) { + const values = []; + for( const f of face.nodes ) { + const v3 = this.nodes3[f].v3; + values.push(v3.x); + values.push(v3.y); + values.push(v3.z); + } + const v3 = this.nodes3[face.nodes[0]].v3; + values.push(v3.x); + values.push(v3.y); + values.push(v3.z); + const vertices = new Float32Array(values); + geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + } + + makeFace(material, face) { + const geometry = new THREE.BufferGeometry(); + this.setFaceGeometry(face, geometry) + const mesh = new THREE.Mesh( geometry, material ); + this.add(mesh); + return mesh; + } + + fourDtoV3(x, y, z, w, rotations) { const v4 = new THREE.Vector4(x, y, z, w); for ( const m4 of rotations ) { @@ -94,6 +122,10 @@ class FourDShape extends THREE.Group { const material = this.getMaterial(l, this.link_ms); l.object = this.makeLink(material, l); } + for( const f of this.faces ) { + const material = this.getMaterial(f, this.face_ms); + f.object = this.makeFace(material, f); + } } @@ -109,9 +141,13 @@ class FourDShape extends THREE.Group { for( const l of this.links ) { this.updateLink(l); } + + for( const f of this.faces ) { + this.setFaceGeometry(f, f.object.geometry); + } } } -export { FourDShape }; \ No newline at end of file +export { FourDShape }; diff --git a/permute_testbed.js b/indexing_attempts/complicated_vectors.js similarity index 52% rename from permute_testbed.js rename to indexing_attempts/complicated_vectors.js index ff716be..8fc552a 100644 --- a/permute_testbed.js +++ b/indexing_attempts/complicated_vectors.js @@ -1,366 +1,4 @@ - -// Utilities for generating sets of coordinates based on -// permutations, even permutations and changes of sign. -// Based on https://www.qfbox.info/epermute - - -const THREE =require('three'); - -function pandita(a) { - const n = a.length; - for( let k = n - 2; k >= 0; k-- ) { - if( a[k] < a[k + 1] ) { - for( let l = n - 1; l >= 0; l-- ) { - if( a[k] < a[l] ) { - const tmp = a[k]; - a[k] = a[l]; - a[l] = tmp; - const revtail = a.slice(k + 1); - revtail.reverse(); - for( let i = 0; i < revtail.length; i++ ) { - a[k + 1 + i] = revtail[i]; - } - return Math.floor(revtail.length / 2) + 1; - } - } - console.log("Shouldn't get here"); - process.exit(); - } - } - return false; -} - -function permutations_old(a) { - a.sort(); - const ps = [ [...a] ]; - let running = true; - while( running ) { - const s = pandita(a); - if( s ) { - ps.push([...a]); - } else { - running = false; - } - } - return ps; -} - -function permutations(a) { - a.sort(); - const ps = [ [...a] ]; - let running = true; - while( pandita(a) > 0 ) { - ps.push([...a]); - } - return ps; -} - - -function permutations_even(a) { - a.sort(); - let parity = 'even'; - const ps = [ [...a] ]; - let running = true; - while( running ) { - const s = pandita(a); - if( s ) { - if( parity === 'even' ) { - if( s % 2 === 1 ) { - parity = 'odd'; - } - } else { - if( s % 2 === 1 ) { - parity = 'even'; - } - } - if( parity === 'even' ) { - ps.push([...a]); - } - } else { - running = false; - } - } - return ps; -} - -// for a given permutation, say [ 1, 1, 0, 0 ], return all -// of the valid changes of sign, so: -// [ [1, 1, 0, 0 ], [ -1, 1, 0, 0 ], [ 1, -1, 0, 0 ], [-1, -1, 0, 0 ]] -// ie don't do it on the zeros - -function expand_sign(a, label) { - const expanded = []; - const exv = a.map((v) => v ? [ -v, v ] : [ 0 ]); - for( const xv of exv[0] ) { - for( const yv of exv[1] ) { - for( const zv of exv[2] ) { - for( const wv of exv[3] ) { - expanded.push({label: label, x: xv, y:yv, z:zv, w:wv}); - } - } - } - } - return expanded; -} - - -function coordinates(a, id0=1, even=false) { - const ps = even ? permutations_even(a) : permutations(a); - const coords = []; - for( const p of ps ) { - const expanded = expand_sign(p, 0); - coords.push(...expanded); - } - return coords; -} - - - -function index_nodes(nodes, scale) { - let i = 1; - for( const n of nodes ) { - n["id"] = i; - i++; - } -} - -function scale_nodes(nodes, scale) { - for( const n of nodes ) { - for( const a of [ 'x', 'y', 'z', 'w' ] ) { - n[a] = scale * n[a]; - } - } -} - -function dist2(n1, n2) { - return (n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2; -} - -function auto_detect_edges(nodes, neighbours, debug=false) { - const seen = {}; - const nnodes = nodes.length; - const links = []; - let id = 1; - for( const n1 of nodes ) { - const d2 = []; - for( const n2 of nodes ) { - d2.push({ d2: dist2(n1, n2), id: n2.id }); - } - d2.sort((a, b) => a.d2 - b.d2); - const closest = d2.slice(1, neighbours + 1); - if( debug ) { - console.log(`closest = ${closest.length}`); - console.log(closest); - } - for( const e of closest ) { - const ids = [ n1.id, e.id ]; - ids.sort(); - const fp = ids.join(','); - if( !seen[fp] ) { - seen[fp] = true; - links.push({ id: id, label: 0, source: n1.id, target: e.id }); - id++; - } - } - } - if( debug ) { - console.log(`Found ${links.length} edges`) - } - return links; -} - -// too small and simple to calculate - -const cell5 = () => { - const r5 = Math.sqrt(5); - const r2 = Math.sqrt(2) / 2; - return { - nodes: [ - {id:1, x: r2, y: r2, z: r2, w: -r2 / r5 }, - {id:2, x: r2, y: -r2, z: -r2, w: -r2 / r5 }, - {id:3, x: -r2, y: r2, z: -r2, w: -r2 / r5 }, - {id:4, x: -r2, y: -r2, z: r2, w: -r2 / r5 }, - {id:5, x: 0, y: 0, z: 0, w: 4 * r2 / r5 }, - ], - links: [ - { id:1, source:1, target: 2}, - { id:2, source:1, target: 3}, - { id:3, source:1, target: 4}, - { id:4, source:1, target: 5}, - { id:5, source:2, target: 3}, - { id:6, source:2, target: 4}, - { id:7, source:2, target: 5}, - { id:8, source:3, target: 4}, - { id:9, source:3, target: 5}, - { id:10, source:4, target: 5}, - ], - geometry: { - node_size: 0.02, - link_size: 0.02 - } - }; -}; - - -const cell16 = () => { - let nodes = coordinates([1, 1, 1, 1], 0); - nodes = nodes.filter((n) => n.x * n.y * n.z * n.w > 0); - index_nodes(nodes); - scale_nodes(nodes, 0.75); - const links = auto_detect_edges(nodes, 6); - - return { - nodes: nodes, - links: links, - geometry: { - node_size: 0.02, - link_size: 0.02 - } - }; -}; - - - -const tesseract = () => { - const nodes = coordinates([1, 1, 1, 1], 0); - index_nodes(nodes); - scale_nodes(nodes, Math.sqrt(2) / 2); - const links = auto_detect_edges(nodes, 4); - - return { - nodes: nodes, - links: links, - geometry: { - node_size: 0.02, - link_size: 0.02 - } - }; -} - - - - -const cell24 = () => { - const nodes = coordinates([0, 0, 1, 1], 0); - index_nodes(nodes); - const links = auto_detect_edges(nodes, 6); - - return { - nodes: nodes, - links: links, - geometry: { - node_size: 0.02, - link_size: 0.02 - } - }; -} - - - - -function make_120cell_vertices() { - const phi = 0.5 * (1 + Math.sqrt(5)); - const r5 = Math.sqrt(5); - const phi2 = phi * phi; - const phiinv = 1 / phi; - const phi2inv = 1 / phi2; - - const nodes = [ - coordinates([0, 0, 2, 2], 0), - coordinates([1, 1, 1, r5], 0), - coordinates([phi, phi, phi, phi2inv], 0), - coordinates([phiinv, phiinv, phiinv, phi2], 0), - - coordinates([phi2, phi2inv, 1, 0], 0, true), - coordinates([r5, phiinv, phi, 0], 0, true), - coordinates([2, 1, phi, phiinv], 0, true), - ].flat(); - index_nodes(nodes); -// scale_nodes(nodes, 0.5); - return nodes; -} - - -const cell120 = () => { - const nodes = make_120cell_vertices(); - const links = auto_detect_edges(nodes, 4); - return { - nodes: nodes, - links: links, - geometry: { - node_size: 0.02, - link_size: 0.02 - } - - } -} - - - - -function make_600cell_vertices() { - const phi = 0.5 * (1 + Math.sqrt(5)); - - const nodes = [ - coordinates([0, 0, 0, 2], 0), - coordinates([1, 1, 1, 1], 1), - - coordinates([phi, 1, 1 / phi, 0], 1, true) - ].flat(); - - index_nodes(nodes); - return nodes; -} - - -function find_by_chord(nodesid, n, d) { - const EPSILON = 0.02; - return Object.keys(nodesid).filter((n1) => { - const d2 = dist2(nodesid[n1], nodesid[n]); - return Math.abs(d2 - d ** 2) < EPSILON; - }); -} - - -function has_chord(n1, n2, d) { - const d2 = dist2(n1, n2); - const EPSILON = 0.01; - return Math.abs(d2 - d ** 2) < EPSILON; -} - - -function find_all_chords(nodes) { - const chords = {}; - for( let i = 0; i < nodes.length - 1; i++ ) { - for( let j = i + 1; j < nodes.length; j++ ) { - const n1 = nodes[i]; - const n2 = nodes[j]; - const chord = Math.sqrt(dist2(n1, n2)).toFixed(5); - if( !(chord in chords) ) { - chords[chord] = []; - } - chords[chord].push([n1, n2]); - } - } - return chords; -} - - - - -const cell600 = () => { - const nodes = make_600cell_vertices(); - const links = auto_detect_edges(nodes, 12); - return { - nodes: nodes, - links: links, - geometry: { - node_size: 0.08, - link_size: 0.02 - } - } -} - +// bad stuff function find_chords(chords, n) { return chords.filter((c) => c[0].id === n.id || c[1].id === n.id); @@ -621,14 +259,36 @@ function nice_icosa(nodes, icosa) { } -const nodes = make_120cell_vertices(); -// const chords = find_all_chords(nodes); -// const chord3 = chords["1.74806"]; // these are edges of the 600-cells; - -//const pairs60 = neighbour_angles(chord3, nodes[0], "60.000"); -//const icosas = partition_nodes(pairs60); - -make_120_partition(nodes, nodes[0]) +function find_by_chord(nodesid, n, d) { + const EPSILON = 0.02; + return Object.keys(nodesid).filter((n1) => { + const d2 = dist2(nodesid[n1], nodesid[n]); + return Math.abs(d2 - d ** 2) < EPSILON; + }); +} +function has_chord(n1, n2, d) { + const d2 = dist2(n1, n2); + const EPSILON = 0.01; + return Math.abs(d2 - d ** 2) < EPSILON; +} + + +function find_all_chords(nodes) { + const chords = {}; + for( let i = 0; i < nodes.length - 1; i++ ) { + for( let j = i + 1; j < nodes.length; j++ ) { + const n1 = nodes[i]; + const n2 = nodes[j]; + const chord = Math.sqrt(dist2(n1, n2)).toFixed(5); + if( !(chord in chords) ) { + chords[chord] = []; + } + chords[chord].push([n1, n2]); + } + } + return chords; +} + diff --git a/indexing_attempts/graph_traversal.js b/indexing_attempts/graph_traversal.js new file mode 100644 index 0000000..e7b6424 --- /dev/null +++ b/indexing_attempts/graph_traversal.js @@ -0,0 +1,78 @@ +// New approach with tetrahedral coloring + +function find_edges(links, nid) { + return links.filter((l) => l.source === nid || l.target === nid ); +} + + +function find_adjacent(links, nid) { + return find_edges(links, nid).map((l) => { + if( l.source === nid ) { + return l.target; + } else { + return l.source; + } + }); +} + +function iterate_graph(nodes, links, n, fn) { + const queue = []; + const seen = {}; + const nodes_id = {}; + nodes.map((n) => nodes_id[n.id] = n); + + queue.push(n.id); + seen[n.id] = true; + fn(n); + + while( queue.length > 0 ) { + const v = queue.shift(); + find_adjacent(links, v).map((aid) => { + if( !(aid in seen) ) { + seen[aid] = true; + queue.push(aid); + fn(nodes_id[aid]); + } + }) + } +} + + + + +// stupid tetrahedral labelling +// keeps getting stuck + + +function naive_label_120cell(nodes, links, n) { + const nodes_id = {}; + nodes.map((n) => nodes_id[n.id] = n); + iterate_graph(nodes, links, nodes[0], (n) => { + const cols = new Set(); + const nbors = find_adjacent(links, n.id); + for( const nb of nbors ) { + if( nodes_id[nb].label > 0 ) { + cols.add(nodes_id[nb].label); + } + for( const nb2 of find_adjacent(links, nb) ) { + if( nb2 !== n.id && nodes_id[nb].label > 0 ) { + cols.add(nodes_id[nb2].label); + } + } + } + const pcols = [ 1, 2, 3, 4, 5 ].filter((c) => !cols.has(c)); + if( pcols.length < 1 ) { + console.log(`Got stuck, no options at ${n.id}`); + return false; + } else { + n.label = pcols[0]; + console.log(`found ${pcols.length} colors for node ${n.id}`); + console.log(`applied ${pcols[0]} to node ${n.id}`); + return true; + } + }); +} + + + + diff --git a/main.js b/main.js index a3725e9..dc6c80f 100644 --- a/main.js +++ b/main.js @@ -8,6 +8,8 @@ import { FourDGUI } from './gui.js'; import { FourDShape } from './fourDShape.js'; import { get_colours } from './colours.js'; +const FACE_OPACITY = 0.3; + // scene, lights and camera const scene = new THREE.Scene(); @@ -40,6 +42,17 @@ const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c const link_ms = [ material ]; + +const face_ms = [ + new THREE.MeshLambertMaterial( { color: 0x44ff44 } ) + ]; + +for( const face_m of face_ms ) { + face_m.transparent = true; + face_m.opacity = FACE_OPACITY; +} + + const STRUCTURES = { '5-cell': POLYTOPES.cell5(), '16-cell': POLYTOPES.cell16(), @@ -55,7 +68,8 @@ function createShape(name) { if( shape ) { scene.remove(shape); } - shape = new FourDShape(node_ms, link_ms, STRUCTURES[name]); + console.log(STRUCTURES[name]); + shape = new FourDShape(node_ms, link_ms, face_ms, STRUCTURES[name]); scene.add(shape); } diff --git a/polytopes.js b/polytopes.js index 09aa393..01900c0 100644 --- a/polytopes.js +++ b/polytopes.js @@ -1,6 +1,7 @@ - import * as PERMUTE from './permute.js'; +import * as DODECAHEDRA from './dodecahedra.js'; + function index_nodes(nodes, scale) { let i = 1; for( const n of nodes ) { @@ -171,9 +172,82 @@ export const cell24 = () => { } -// notes on coherent indexing -// see table in https://en.wikipedia.org/wiki/120-cell - maybe adapt the -// unit radius table +// face detection for the 120-cell + +// NOTE: all of these return node ids, not nodes + +// return all the links which connect to a node + +function nodes_links(links, nodeid) { + return links.filter((l) => l.source === nodeid || l.target === nodeid); +} + +// filter to remove a link to a given id from a set of links + +function not_to_this(link, nodeid) { + return !(link.source === nodeid || link.target === nodeid); +} + +// given nodes n1, n2, return all neighbours of n2 which are not n1 + +function unmutuals(links, n1id, n2id) { + const nlinks = nodes_links(links, n2id).filter((l) => not_to_this(l, n1id)); + return nlinks.map((l) => { + if( l.source === n2id ) { + return l.target; + } else { + return l.source; + } + }) +} + + +function fingerprint(ids) { + const sids = [...ids]; + sids.sort(); + return sids.join(','); +} + + + +function auto_120cell_faces(links) { + const faces = []; + const seen = {}; + let id = 1; + for( const edge of links ) { + const v1 = edge.source; + const v2 = edge.target; + const n1 = unmutuals(links, v2, v1); + const n2 = unmutuals(links, v1, v2); + const shared = []; + for( const a of n1 ) { + const an = unmutuals(links, v1, a); + for( const d of n2 ) { + const dn = unmutuals(links, v2, d); + for( const x of an ) { + for( const y of dn ) { + if( x == y ) { + shared.push([v1, a, x, d, v2]) + } + } + } + } + } + if( shared.length !== 3 ) { + console.log(`Bad shared faces for ${edge.id} ${v1} ${v2}`); + } + for( const face of shared ) { + const fp = fingerprint(face); + if( !seen[fp] ) { + faces.push({ id: id, nodes: face }); + id++; + seen[fp] = true; + } + } + } + return faces; +} + function make_120cell_vertices() { @@ -185,13 +259,13 @@ function make_120cell_vertices() { const nodes = [ PERMUTE.coordinates([0, 0, 2, 2], 0), - PERMUTE.coordinates([1, 1, 1, r5], 1), - PERMUTE.coordinates([phi, phi, phi, phi2inv], 2), - PERMUTE.coordinates([phiinv, phiinv, phiinv, phi2], 3), + PERMUTE.coordinates([1, 1, 1, r5], 0), + PERMUTE.coordinates([phi, phi, phi, phi2inv], 0), + PERMUTE.coordinates([phiinv, phiinv, phiinv, phi2], 0), - PERMUTE.coordinates([phi2, phi2inv, 1, 0], 4, true), - PERMUTE.coordinates([r5, phiinv, phi, 0], 5, true), - PERMUTE.coordinates([2, 1, phi, phiinv], 6, true), + PERMUTE.coordinates([phi2, phi2inv, 1, 0], 0, true), + PERMUTE.coordinates([r5, phiinv, phi, 0], 0, true), + PERMUTE.coordinates([2, 1, phi, phiinv], 0, true), ].flat(); index_nodes(nodes); scale_nodes(nodes, 0.5); @@ -206,17 +280,79 @@ function label_nodes(nodes, ids, label) { + + + + + + + + +function label_faces_120cell(nodes, faces, cfaces, label) { + const ns = new Set(); + console.log(`label faces from ${cfaces}`); + for( const fid of cfaces ) { + const face = faces.filter((f)=> f.id === fid ); + if( face.length > 0 ) { + for ( const nid of face[0].nodes ) { + ns.add(nid); + } + } + } + label_nodes(nodes, Array.from(ns), label); +} + + +function manual_label_120cell(nodes, links) { + + const faces = auto_120cell_faces(links); + const dodecas = DODECAHEDRA.DODECAHEDRA; + //const cfaces = [ 1, 2, 4, 145, 169 ]; + + let colour = 1; + for( const dd of dodecas ) { + label_faces_120cell(nodes, faces, dd, colour); + colour++; + if( colour > 8 ) { + colour = 1; + } + } + +// label_faces_120cell(nodes, faces, [ +// 1, 2, 4, 169, 626, +// 145, 149, 553, 173, 171, +// 147, 554 +// ], 2); + +// label_faces_120cell(nodes, faces, [ +// 1, 5, 3, 193, 641, +// 217, 221, 565, 197, 195, +// 219, 566 +// ], 3); + +} + + + + + + + + + export const cell120 = () => { const nodes = make_120cell_vertices(); const links = auto_detect_edges(nodes, 4); + + manual_label_120cell(nodes, links); + return { nodes: nodes, links: links, geometry: { node_size: 0.02, link_size: 0.02 - } - + }, } } @@ -406,5 +542,3 @@ export const cell600 = () => { } - - diff --git a/testbed.js b/testbed.js new file mode 100644 index 0000000..08cba2f --- /dev/null +++ b/testbed.js @@ -0,0 +1,440 @@ + +//testbed for playing with stuff in node repl + +const THREE =require('three'); + +function pandita(a) { + const n = a.length; + for( let k = n - 2; k >= 0; k-- ) { + if( a[k] < a[k + 1] ) { + for( let l = n - 1; l >= 0; l-- ) { + if( a[k] < a[l] ) { + const tmp = a[k]; + a[k] = a[l]; + a[l] = tmp; + const revtail = a.slice(k + 1); + revtail.reverse(); + for( let i = 0; i < revtail.length; i++ ) { + a[k + 1 + i] = revtail[i]; + } + return Math.floor(revtail.length / 2) + 1; + } + } + console.log("Shouldn't get here"); + process.exit(); + } + } + return false; +} + +function permutations_old(a) { + a.sort(); + const ps = [ [...a] ]; + let running = true; + while( running ) { + const s = pandita(a); + if( s ) { + ps.push([...a]); + } else { + running = false; + } + } + return ps; +} + +function permutations(a) { + a.sort(); + const ps = [ [...a] ]; + let running = true; + while( pandita(a) > 0 ) { + ps.push([...a]); + } + return ps; +} + + +function permutations_even(a) { + a.sort(); + let parity = 'even'; + const ps = [ [...a] ]; + let running = true; + while( running ) { + const s = pandita(a); + if( s ) { + if( parity === 'even' ) { + if( s % 2 === 1 ) { + parity = 'odd'; + } + } else { + if( s % 2 === 1 ) { + parity = 'even'; + } + } + if( parity === 'even' ) { + ps.push([...a]); + } + } else { + running = false; + } + } + return ps; +} + +// for a given permutation, say [ 1, 1, 0, 0 ], return all +// of the valid changes of sign, so: +// [ [1, 1, 0, 0 ], [ -1, 1, 0, 0 ], [ 1, -1, 0, 0 ], [-1, -1, 0, 0 ]] +// ie don't do it on the zeros + +function expand_sign(a, label) { + const expanded = []; + const exv = a.map((v) => v ? [ -v, v ] : [ 0 ]); + for( const xv of exv[0] ) { + for( const yv of exv[1] ) { + for( const zv of exv[2] ) { + for( const wv of exv[3] ) { + expanded.push({label: label, x: xv, y:yv, z:zv, w:wv}); + } + } + } + } + return expanded; +} + + +function coordinates(a, id0=1, even=false) { + const ps = even ? permutations_even(a) : permutations(a); + const coords = []; + for( const p of ps ) { + const expanded = expand_sign(p, 0); + coords.push(...expanded); + } + return coords; +} + + + +function index_nodes(nodes, scale) { + let i = 1; + for( const n of nodes ) { + n["id"] = i; + i++; + } +} + +function scale_nodes(nodes, scale) { + for( const n of nodes ) { + for( const a of [ 'x', 'y', 'z', 'w' ] ) { + n[a] = scale * n[a]; + } + } +} + +function dist2(n1, n2) { + return (n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2; +} + +function auto_detect_edges(nodes, neighbours, debug=false) { + const seen = {}; + const nnodes = nodes.length; + const links = []; + let id = 1; + for( const n1 of nodes ) { + const d2 = []; + for( const n2 of nodes ) { + d2.push({ d2: dist2(n1, n2), id: n2.id }); + } + d2.sort((a, b) => a.d2 - b.d2); + const closest = d2.slice(1, neighbours + 1); + if( debug ) { + console.log(`closest = ${closest.length}`); + console.log(closest); + } + for( const e of closest ) { + const ids = [ n1.id, e.id ]; + ids.sort(); + const fp = ids.join(','); + if( !seen[fp] ) { + seen[fp] = true; + links.push({ id: id, label: 0, source: n1.id, target: e.id }); + id++; + } + } + } + if( debug ) { + console.log(`Found ${links.length} edges`) + } + return links; +} + + + +function make_120cell_vertices() { + const phi = 0.5 * (1 + Math.sqrt(5)); + const r5 = Math.sqrt(5); + const phi2 = phi * phi; + const phiinv = 1 / phi; + const phi2inv = 1 / phi2; + + const nodes = [ + coordinates([0, 0, 2, 2], 0), + coordinates([1, 1, 1, r5], 0), + coordinates([phi, phi, phi, phi2inv], 0), + coordinates([phiinv, phiinv, phiinv, phi2], 0), + + coordinates([phi2, phi2inv, 1, 0], 0, true), + coordinates([r5, phiinv, phi, 0], 0, true), + coordinates([2, 1, phi, phiinv], 0, true), + ].flat(); + index_nodes(nodes); +// scale_nodes(nodes, 0.5); + return nodes; +} + + + +// face detection for the 120-cell + +// NOTE: all of these return node ids, not nodes + +// return all the links which connect to a node + +function nodes_links(links, nodeid) { + return links.filter((l) => l.source === nodeid || l.target === nodeid); +} + +// filter to remove a link to a given id from a set of links + +function not_to_this(link, nodeid) { + return !(link.source === nodeid || link.target === nodeid); +} + +// given nodes n1, n2, return all neighbours of n2 which are not n1 + +function unmutuals(links, n1id, n2id) { + const nlinks = nodes_links(links, n2id).filter((l) => not_to_this(l, n1id)); + return nlinks.map((l) => { + if( l.source === n2id ) { + return l.target; + } else { + return l.source; + } + }) +} + + +function fingerprint(ids) { + const sids = [...ids]; + sids.sort(); + return sids.join(','); +} + + + +function auto_120cell_faces(links) { + const faces = []; + const seen = {}; + let id = 1; + for( const edge of links ) { + const v1 = edge.source; + const v2 = edge.target; + const n1 = unmutuals(links, v2, v1); + const n2 = unmutuals(links, v1, v2); + const shared = []; + for( const a of n1 ) { + const an = unmutuals(links, v1, a); + for( const d of n2 ) { + const dn = unmutuals(links, v2, d); + for( const x of an ) { + for( const y of dn ) { + if( x == y ) { + shared.push([v1, a, x, d, v2]) + } + } + } + } + } + if( shared.length !== 3 ) { + console.log(`Bad shared faces for ${edge.id} ${v1} ${v2}`); + } + for( const face of shared ) { + const fp = fingerprint(face); + if( !seen[fp] ) { + faces.push({ id: id, edge: edge.id, v1: edge.source, v2: edge.target, fingerprint: fp, nodes: face }); + id++; + seen[fp] = true; + } + } + } + return faces; +} + + +// trying to go from faces to dodecahedra + +function shared_vertices(f1, f2) { + return f1.nodes.filter((f) => f2.nodes.includes(f)); +} + + +function adjacent_faces(f1, f2) { + // adjacent faces which share an edge, not just a vertex + const intersect = shared_vertices(f1, f2); + if( intersect.length < 2 ) { + return false; + } + if( intersect.length > 2 ) { + console.log(`warning: faces ${f1.id} and ${f2.id} have too many common vertices`); + } + return true; +} + + +function find_adjacent_faces(faces, face) { + const neighbours = faces.filter((f) => f.id !== face.id && adjacent_faces(f, face)); + return neighbours; +} + + + + +function find_dodeca_mutuals(faces, f1, f2) { + // for any two adjacent faces, find their common neighbours where + // all three share exactly one vertex (this, I think, guarantees that + // all are on the same dodecahedron) + + const n1 = find_adjacent_faces(faces, f1); + const n2 = find_adjacent_faces(faces, f2); + const common = n1.filter((f1) => n2.filter((f2) => f1.id === f2.id).length > 0 ); + // there's one extra here - the third which has two nodes in common with + // both f1 and f2 - filter it out + const mutuals = common.filter((cf) => { + const shared = cf.nodes.filter((n) => f1.nodes.includes(n) && f2.nodes.includes(n)); + return shared.length === 1 + }); + return mutuals; +} + +function find_dodeca_next(faces, dodeca, f1, f2) { + // of a pair of mutuals, return the one we haven't already got + const m = find_dodeca_mutuals(faces, f1, f2); + if( dodeca.filter((f) => f.id === m[0].id ).length > 0 ) { + m.shift(); + } + return m[0]; +} + +// from any two mutual faces, return all the faces in their dodecahedron + +function make_dodecahedron(faces, f1, f2) { + const dodecahedron = [ f1, f2 ]; + + // take f1 as the 'center', get the other four around it from f2 + const fs = find_dodeca_mutuals(faces, f1, f2); + const f3 = fs[0]; + const f6 = fs[1]; + dodecahedron.push(f3); + const f4 = find_dodeca_next(faces, dodecahedron, f1, f3); + dodecahedron.push(f4); + const f5 = find_dodeca_next(faces, dodecahedron, f1, f4); + dodecahedron.push(f5); + dodecahedron.push(f6); + + // get the next ring + + const f7 = find_dodeca_next(faces, dodecahedron, f6, f2); + dodecahedron.push(f7); + const f8 = find_dodeca_next(faces, dodecahedron, f2, f3); + dodecahedron.push(f8); + const f9 = find_dodeca_next(faces, dodecahedron, f3, f4); + dodecahedron.push(f9); + const f10 = find_dodeca_next(faces, dodecahedron, f4, f5); + dodecahedron.push(f10); + const f11 = find_dodeca_next(faces, dodecahedron, f5, f6); + dodecahedron.push(f11); + + // get the last + + const f12 = find_dodeca_next(faces, dodecahedron, f7, f8); + dodecahedron.push(f12); + + return dodecahedron; +} + + +// for a face, pick an edge, and then find the other two faces which +// share this edge. These can be used as the starting points for the +// first face's two dodecahedra + +function find_edge_neighbours(faces, face) { + const n1 = face.nodes[0]; + const n2 = face.nodes[1]; + return faces.filter((f) => f.id !== face.id && f.nodes.includes(n1) && f.nodes.includes(n2)); +} + + +// each face is in two dodecahedra: this returns them both + +function face_to_dodecahedra(faces, f) { + const edge_friends = find_edge_neighbours(faces, f); + const d1 = make_dodecahedron(faces, f, edge_friends[0]); + const d2 = make_dodecahedron(faces, f, edge_friends[1]); + return [ d1, d2 ]; +} + +// brute-force calculation of all dodecahedra + +function dd_fingerprint(dodecahedron) { + const ids = dodecahedron.map((face) => face.id); + ids.sort() + return ids.join(','); +} + +function make_120cell_cells(faces) { + const dodecas = []; + const seen = {}; + for( const face of faces ) { + const dds = face_to_dodecahedra(faces, face); + for( const dd of dds ) { + const fp = dd_fingerprint(dd); + if( ! (fp in seen) ) { + //console.log(`added dodeca ${fp}`); + dodecas.push(dd); + seen[fp] = 1; + } + } + } + return dodecas; +} + + +const cell120 = () => { + const nodes = make_120cell_vertices(); + const links = auto_detect_edges(nodes, 4); + return { + nodes: nodes, + links: links, + geometry: { + node_size: 0.02, + link_size: 0.02 + } + + } +} + + + + + +const nodes = make_120cell_vertices(); +const links = auto_detect_edges(nodes, 4); +const faces = auto_120cell_faces(links); +const dodecas = make_120cell_cells(faces); + +const ddfaces = dodecas.map((dd) => dd.map((f) => f.id)); + +console.log(JSON.stringify(ddfaces)); + +// for( const dodeca of dodecas ) { +// console.log(dodeca.map((f) => f.id) +// } +