Compare commits

...

58 Commits

Author SHA1 Message Date
Mike Lynch 0ada3fce6f A few more ux refinements, and added the ability to hide captions 2024-04-26 18:31:27 +10:00
Mike Lynch 3f83bde533 Very cursed but entertaining bug as I try to get links scaling well 2024-04-26 16:57:21 +10:00
Mike Lynch 6f4d4cc633 Link foreshortening works, but updating the geometry of every edge
is making large objects like the 120-cell noticeably stuttery
2024-04-26 11:42:37 +10:00
Mike Lynch fb9c78d82f Fixed some scale problems 2024-04-26 08:59:26 +10:00
Mike Lynch e478abe7c6 Scaled the ends of the links so that they have w-perspective 2024-04-26 07:34:27 +10:00
Mike Lynch f79a90e0d9 Added the rest of the regular 3-d polyhedra 2024-04-25 12:38:40 +10:00
Mike Lynch 78ebb381ee Played around with the hyperplane and zoom so that it all looks
better with unit radius normalisation
2024-04-25 11:25:01 +10:00
Mike Lynch f99901f1b0 Normalised dodecahedron to unit radius 2024-04-25 11:07:21 +10:00
Mike Lynch 39fe6e5e40 Normalised 120-cell to unit radius 2024-04-25 11:05:23 +10:00
Mike Lynch 836e0d5ab6 Normalised 600-cell and snub 24-cell to unit radius 2024-04-25 11:03:11 +10:00
Mike Lynch 0be8c47608 Normalised 24-cell to unit radius 2024-04-25 11:00:14 +10:00
Mike Lynch 5e31403420 Normalised tesseract to unit radius 2024-04-25 10:58:41 +10:00
Mike Lynch aba20124db Normalised 5-cell and 16-cell to unit radius 2024-04-25 10:57:03 +10:00
Mike Lynch 1ec7955861 Merge branch 'feature-node-foreshortening' 2024-04-14 16:10:23 +10:00
Mike Lynch 1e5db22c25 scale factor for node foreshortening 2024-04-14 16:08:46 +10:00
Mike Lynch 680f9997f9 Added descriptions for each of the shapes 2024-04-14 16:05:17 +10:00
Mike Lynch cab5878ac8 Added node size scaling with w-foreshortening - looks kind of goofy 2024-04-09 15:05:39 +10:00
Mike Lynch b22ac6546d Node saturation now matches the basis colour saturation too 2024-04-07 12:38:06 +10:00
Mike Lynch eafc906210 Using HSL to derive the colour scheme 2024-04-07 11:53:33 +10:00
Mike Lynch 3d64c73a5e Fixed bug introduced when reverting the simplified rotation UU 2024-04-07 11:37:21 +10:00
Mike Lynch bc9e86d918 Revert "Simplified rotation ui"
This reverts commit 6b0c5cf97e.
2024-04-07 11:30:52 +10:00
bombinans 01a12bfe2a Merge pull request 'Added model of snub 24-cell' (#13) from feature-snub-24-cell into main
Reviewed-on: #13
2024-04-06 22:11:46 +00:00
Mike Lynch 9d303afaa0 Added model of snub 24-cell 2024-04-07 08:10:26 +10:00
bombinans 973d1d944b Merge pull request '600-cell-layers' (#12) from 600-cell-layers into main
Reviewed-on: #12
2023-11-04 05:35:59 +00:00
Mike Lynch 777b36d048 Finished layered view of 600-cell, refactored cellindex and moved all
of the reference data there.
2023-11-04 16:30:46 +11:00
Mike Lynch 6c360fcafd Start of code to build tetrahedra from 600-cell 2023-11-03 12:57:40 +11:00
Mike Lynch 7f35056ab8 Made default thickness and nodesize a bit bigger 2023-11-03 10:38:35 +11:00
bombinans 1b9f4478a8 Merge pull request 'feature-more-ui-tweaks' (#11) from feature-more-ui-tweaks into main
Reviewed-on: #11
2023-11-02 22:59:07 +00:00
Mike Lynch bef56b211b Updated lil-gui to 0.19.0 so options control doesn't jump to the end
when you select a different shape
2023-11-03 09:56:19 +11:00
Mike Lynch e805c64de6 Added zoom control 2023-11-03 09:55:44 +11:00
bombinans 029b14f53a Merge pull request 'Automatically shifts the camera distance to match the hyperplane.' (#10) from feature-improve-hyperplane into main
Reviewed-on: #10
2023-11-02 07:41:43 +00:00
Mike Lynch d9e7ae716b Automatically shifts the camera distance to match the hyperplane.
Put back 120-cell as the default.
2023-11-02 18:40:37 +11:00
Mike Lynch 25a2c5ebe6 Added a manual chunks setting to stop Vite complaining on build 2023-11-02 16:52:40 +11:00
bombinans 5494660d76 Merge pull request 'bugfix-layered-links' (#9) from bugfix-layered-links into main
Reviewed-on: #9
2023-11-02 05:37:33 +00:00
Mike Lynch 46dfb808f4 Added better layer names 2023-11-02 16:36:18 +11:00
Mike Lynch e7cb1856e0 Added a flag to turn off link2opacity for layered 120-cell 2023-11-02 16:26:48 +11:00
bombinans c281c4569a Merge pull request 'feature-120-cell-layers' (#7) from feature-120-cell-layers into main
Reviewed-on: #7
2023-11-02 03:15:51 +00:00
Mike Lynch c883bd7406 Cleaned up some redundant old stuff 2023-11-02 14:13:45 +11:00
Mike Lynch 6f1c5a58e6 Finished layer-by-layer visualisation of 120-cell 2023-11-02 14:10:13 +11:00
Mike Lynch 7323935a1f Added layered 120-cell 2023-11-01 12:52:59 +11:00
Mike Lynch 6d9610a1c6 options menu gets sensible default when shape changes 2023-11-01 11:57:46 +11:00
Mike Lynch c481d24f3c Refactored dodecahedron 2023-11-01 11:52:48 +11:00
Mike Lynch 029e6f3161 Added 600-cell and 120-cell 2023-11-01 11:49:42 +11:00
Mike Lynch 4bf38858ea Added 24-cell 2023-11-01 11:43:54 +11:00
Mike Lynch 50214adbe3 Reinstated 5-cell and 16-cell 2023-11-01 11:40:23 +11:00
Mike Lynch 71c6aa62ac Removed old inscribed tesseract 2023-11-01 11:15:18 +11:00
Mike Lynch 76f463ae03 Added refactored tesseract 2023-11-01 11:14:57 +11:00
Mike Lynch 42d1871a9e Improved option defaults and URL params 2023-11-01 10:47:46 +11:00
Mike Lynch 203850ba39 First draft of refactored optional / visible nodes and links 2023-11-01 10:22:26 +11:00
Mike Lynch 06c1b074b3 Started generalising / unifying inscribed links and layers as different
ways of picking out parts of a structure
2023-10-27 17:32:44 +11:00
Mike Lynch 2f59c0b3a5 Added a callback to set link and node visibility based on label at
the render stage
2023-10-27 12:45:46 +11:00
Mike Lynch 944416f92b Visualisations of combinations of layers 2023-10-27 10:42:35 +11:00
Mike Lynch 8c256629b1 Added a new partition of the 120-cell into layers 2023-10-27 10:18:54 +11:00
Mike Lynch fb1d6ddd45 Removed logging 2023-10-18 11:22:24 +11:00
Mike Lynch 1f6c35c7b8 Refactored label120cell.js so that it works a bit better on the repl 2023-10-18 11:22:13 +11:00
Mike Lynch 88049ca891 renamed testbed.js to label120cell.js 2023-10-18 10:21:28 +11:00
bombinans bf8356b0a2 Merge pull request 'feature-update-link' (#3) from feature-update-link into main
Reviewed-on: #3
2023-09-30 02:53:16 +00:00
Mike Lynch e539f4f3af Changed source link to go to Gitea 2023-09-30 12:48:34 +10:00
13 changed files with 1434 additions and 513 deletions

View File

@ -1 +1,188 @@
export const INDEX = {"1": [ 27,38,48,49,61,68,74,87,95,98,105,120, 126,131,140,149,156,165,174,179,185,200,207,210,218,223,226,231,234,239,241,248,252,253,258,263,265,272,274,279,284,285,289,296,300,301,306,311,313,320,324,325,331,334,339,342,347,350,356,357,362,367,369,376,378,383,388,389,393,400,403,406,413,414,419,420,425,427,438,440,444,448,449,453,458,460,469,471,473,474,487,488,490,494,499,503,511,512,513,514,525,527,530,532,539,543,546,550,555,558,563,566,572,573,580,581,585,592,593,600],"2":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,28,30,31,34,35,37,40,41,44,46,47,50,51,53,56,57,60,62,63,66,67,69,72,73,76,78,79,82,83,85,88,90,91,93,96,97,100,102,103,106,107,109,112,113,116,118,119,122,123,125,128,129,132,134,135,138,139,141,144,145,148,150,151,154,155,157,160,161,164,166,167,170,171,173,176,177,180,182,183,186,187,189,192,193,196,198,199,202,203,205,208,209,212,214,215],"3":[26,39,45,52,64,65,75,86,94,99,108,117,127,130,137,152,153,168,175,178,188,197,206,211,219,222,227,230,235,238,244,245,251,254,257,264,268,269,273,280,283,286,292,293,299,302,305,312,316,317,321,328,330,335,338,343,348,349,355,358,363,366,370,375,377,384,385,392,394,399,404,405,415,416,417,418,426,428,437,439,441,445,452,456,457,459,470,472,475,476,485,486,491,495,498,502,509,510,515,516,526,528,529,531,538,542,547,551,554,559,562,567,569,576,577,584,588,589,596,597],"4":[32,33,43,54,58,71,77,84,92,101,110,115,121,136,143,146,159,162,169,184,190,195,204,213,220,221,228,229,236,237,242,247,249,256,260,261,266,271,276,277,281,288,290,295,297,304,308,309,315,318,322,327,329,336,340,341,346,351,354,359,361,368,371,374,379,382,387,390,396,397,401,408,409,410,423,424,430,432,433,435,443,447,450,454,461,463,466,468,477,478,483,484,489,493,500,504,507,508,517,518,522,524,533,535,540,544,545,549,553,560,561,568,570,575,578,583,587,590,595,598],"5":[29,36,42,55,59,70,80,81,89,104,111,114,124,133,142,147,158,163,172,181,191,194,201,216,217,224,225,232,233,240,243,246,250,255,259,262,267,270,275,278,282,287,291,294,298,303,307,310,314,319,323,326,332,333,337,344,345,352,353,360,364,365,372,373,380,381,386,391,395,398,402,407,411,412,421,422,429,431,434,436,442,446,451,455,462,464,465,467,479,480,481,482,492,496,497,501,505,506,519,520,521,523,534,536,537,541,548,552,556,557,564,565,571,574,579,582,586,591,594,599]};
export const LAYER_NAMES = {
0: "North pole",
1: "Arctic circle",
2: "North temperate",
3: "Tropic of Cancer",
4: "Equator",
5: "Tropic of Capricorn",
6: "South temperate",
7: "Antarctic circle (all)"
};
export const INDEX120 = {
"1": [
27,38,48,49,61,68,74,87,95,98,105,120, 126,131,140,149,156,165,174,
179,185,200,207,210,218,223,226,231,234,239,241,248,252,253,258,263,
265,272,274,279,284,285,289,296,300,301,306,311,313,320,324,325,331,
334,339,342,347,350,356,357,362,367,369,376,378,383,388,389,393,400,
403,406,413,414,419,420,425,427,438,440,444,448,449,453,458,460,469,
471,473,474,487,488,490,494,499,503,511,512,513,514,525,527,530,532,
539,543,546,550,555,558,563,566,572,573,580,581,585,592,593,600
],
"2":[
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,28,
30,31,34,35,37,40,41,44,46,47,50,51,53,56,57,60,62,63,66,67,69,72,73,
76,78,79,82,83,85,88,90,91,93,96,97,100,102,103,106,107,109,112,113,
116,118,119,122,123,125,128,129,132,134,135,138,139,141,144,145,148,
150,151,154,155,157,160,161,164,166,167,170,171,173,176,177,180,182,
183,186,187,189,192,193,196,198,199,202,203,205,208,209,212,214,215
],
"3":[
26,39,45,52,64,65,75,86,94,99,108,117,127,130,137,152,153,168,175,
178,188,197,206,211,219,222,227,230,235,238,244,245,251,254,257,264,
268,269,273,280,283,286,292,293,299,302,305,312,316,317,321,328,330,
335,338,343,348,349,355,358,363,366,370,375,377,384,385,392,394,399,
404,405,415,416,417,418,426,428,437,439,441,445,452,456,457,459,470,
472,475,476,485,486,491,495,498,502,509,510,515,516,526,528,529,531,
538,542,547,551,554,559,562,567,569,576,577,584,588,589,596,597
],
"4":[
32,33,43,54,58,71,77,84,92,101,110,115,121,136,143,146,159,162,169,
184,190,195,204,213,220,221,228,229,236,237,242,247,249,256,260,261,
266,271,276,277,281,288,290,295,297,304,308,309,315,318,322,327,329,
336,340,341,346,351,354,359,361,368,371,374,379,382,387,390,396,397,
401,408,409,410,423,424,430,432,433,435,443,447,450,454,461,463,466,
468,477,478,483,484,489,493,500,504,507,508,517,518,522,524,533,535,
540,544,545,549,553,560,561,568,570,575,578,583,587,590,595,598
],
"5":[
29,36,42,55,59,70,80,81,89,104,111,114,124,133,142,147,158,163,172,
181,191,194,201,216,217,224,225,232,233,240,243,246,250,255,259,262,
267,270,275,278,282,287,291,294,298,303,307,310,314,319,323,326,332,
333,337,344,345,352,353,360,364,365,372,373,380,381,386,391,395,398,
402,407,411,412,421,422,429,431,434,436,442,446,451,455,462,464,465,
467,479,480,481,482,492,496,497,501,505,506,519,520,521,523,534,536,
537,541,548,552,556,557,564,565,571,574,579,582,586,591,594,599
]
};
export const LAYERS120 = {
"0": [154,266,158,222,218,250,254,162,268,156,160,252,256,166,270,272,
164,220,224,168],
"1": [2,318,314,30,414,510,362,26,506,410,338,458,462,110,
106,6,350,346,34,482,418,474,330,442,450,98,90,14,
364,28,508,476,394,570,572,124,122,478,348,32,480,
512,396,576,574,126,128,8,352,38,422,486,334,454,
446,94,102,514,366,518,342,470,466,114,118,16,368,
36,484,516,398,580,578,130,132,412,316,420,332,452,
444,92,100,4,320,416,340,464,460,108,112,40,488,424,
336,448,456,104,96,520,400,582,584,136,134,344,468,472,120,116],
"2":[246,242,42,170,426,50,434,178,226,322,46,430,174,54,182,438,326,
230,78,558,526,62,494,542,590,142,74,554,386,300,298,202,290,206,
522,58,538,490,138,586,236,234,186,60,540,188,282,378,66,546,194,
68,196,548,382,286,498,82,562,530,146,594,76,556,204,80,208,560,
388,292,588,44,428,492,140,524,64,544,380,238,190,240,192,284,
592,496,432,48,528,144,70,198,550,72,552,200,288,384,502,534,566,
86,598,150,390,302,210,304,214,294,84,212,564,88,568,216,296,392,
596,500,436,52,532,148,324,244,172,248,180,228,176,56,440,184,232,
328,504,536,152,600],
"3":[369,273,275,371,17,353,257,261,357,9,401,305,306,402,21,307,308,
403,22,404,259,263,355,11,359,277,279,373,19,375,309,310,405,23,
406,258,262,354,10,358,274,276,370,18,372,356,260,264,360,12,407,
311,312,408,24,374,278,280,376,20],"4":[229,325,173,45,429,493,
141,525,589,289,385,557,77,205,201,137,521,553,73,585,285,381,
193,65,545,593,145,497,529,225,321,433,49,177,139,587,491,281,377,
539,59,187,283,379,189,61,541,197,287,199,237,239,191,293,389,213,
149,533,565,85,597,295,391,211,147,531,563,83,595,227,323,171,43,
427,523,151,503,535,231,327,439,55,183,599,383,551,71,87,215,567,
169,241,245,181,489,425,41,185,233,235,195,537,57,591,543,63,495,
143,549,69,501,179,499,435,51,175,431,47,527,203,291,387,555,75,
207,299,297,559,79,437,53,209,561,81,301,303,547,67,243,247],
"5":[457,337,461,105,109,469,341,465,117,113,89,97,441,449,329,93,445,
101,453,333,573,395,125,127,575,121,569,393,123,571,451,331,443,
99,91,129,577,131,579,397,107,459,111,339,463,95,447,335,103,455,
133,135,581,583,399,115,119,467,471,343
],
"6":[1,417,313,409,413,317,421,5,475,345,473,481,349,483,33,513,507,
13,363,511,7,487,351,485,37,517,15,519,367,515,3,423,319,415,39,
25,505,509,477,29,411,27,479,347,31,365,419,35,315,361
],
"7":[217,153,221,157,265,161,165,269,249,253,251,255,267,159,155,
163,219,271,223,167]
};
// Schoute's partition via https://arxiv.org/abs/1010.4353
export const PARTITION600 = {
"2,0,0,0": 1,
"0,2,0,0": 1,
"0,0,2,0": 1,
"0,0,0,2": 1,
"1,1,1,1": 1,
"1,1,-1,-1": 1,
"1,-1,1,-1": 1,
"1,-1,-1,1": 1,
"1,-1,-1,-1": 1,
"1,-1,1,1": 1,
"1,1,-1,1": 1,
"1,1,1,-1": 1,
"k,0,-t,-1": 2,
"0,k,1,-t": 2,
"t,-1,k,0": 2,
"1,t,0,k": 2,
"t,k,0,-1": 2,
"1,0,k,t": 2,
"k,-t,-1,0": 2,
"0,1,-t,k": 2,
"1,k,t,0": 2,
"t,0,-1,k": 2,
"0,t,-k,-1": 2,
"k,-1,0,-t": 2,
"t,0,1,k": 3,
"0,t,-k,1": 3,
"1,-k,-t,0": 3,
"k,1,0,-t": 3,
"0,k,1,t": 3,
"t,1,-k,0": 3,
"k,0,t,-1": 3,
"1,-t,0,k": 3,
"t,-k,0,-1": 3,
"0,1,-t,-k": 3,
"1,0,-k,t": 3,
"k,t,1,0": 3,
"t,0,-1,-k": 4,
"0,t,k,-1": 4,
"1,-k,t,0": 4,
"k,1,0,t": 4,
"t,1,k,0": 4,
"0,k,-1,-t": 4,
"1,-t,0,-k": 4,
"k,0,-t,1": 4,
"0,1,t,k": 4,
"t,-k,0,1": 4,
"k,t,-1,0": 4,
"1,0,k,-t": 4,
"k,0,t,1": 5,
"0,k,-1,t": 5,
"t,-1,-k,0": 5,
"1,t,0,-k": 5,
"1,0,-k,-t": 5,
"t,k,0,1": 5,
"0,1,t,-k": 5,
"k,-t,1,0": 5,
"t,0,1,-k": 5,
"1,k,-t,0": 5,
"k,-1,0,t": 5,
"0,t,k,1": 5
};
export const LAYERS600 = {
"0":[2,42,46,50,54,106,110,44,48,52,56,108,112],
"1":[10,12,14,16,18,20,22,24,34,36,38,40,98,102,100,104,90,92,94,96],
"2":[26,58,74,28,76,30,60,32,62,78,80,64],
"3":[3,4,5,6,7,8,65,113,81,66,114,82,67,115,83,68,116,84,69,117,85,70,118,86,71,119,87,72,120,88],
"4":[25,29,27,31,57,61,59,63,73,75,77,79],
"5":[97,101,99,103,33,35,37,39,89,91,93,95,9,11,13,15,17,19,21,23],
"6":[41,105,49,43,51,45,107,47,109,53,55,111],
"7":[1]
};

View File

@ -1,14 +1,20 @@
import ColorScheme from 'color-scheme';
import Color from 'color';
export const get_colours = (basis) => {
const basis_c = Color(basis);
const hslb = basis_c.hsl();
const hue = hslb['color'][0];
const saturation = hslb['color'][1];
const luminance = hslb['color'][2];
const scheme = new ColorScheme;
const hexbasis = basis.toString(16).padStart(6, "0");
scheme.from_hex(hexbasis).scheme("tetrade").variation("hard").distance(0.5);
const colours = scheme.colors().map((cs) => parseInt('0x' + cs));
const set = colours.slice(1, 6);
set.reverse();
set.unshift(colours[0]);
return set;
scheme.from_hue(hue).scheme("tetrade").distance(0.75);
const colours = scheme.colors().slice(1, 9);
colours.reverse();
const hsl = colours.map((c) => Color("#" + c).hsl());
const resaturated = hsl.map((hslc) => hslc.saturationl(saturation).rgbNumber());
resaturated.unshift(basis);
return resaturated;
}
// basic colours where 0 = blue

View File

@ -2,7 +2,7 @@ import * as THREE from 'three';
const HYPERPLANE = 2.0;
const W_FORESHORTENING = 0.04;
class FourDShape extends THREE.Group {
@ -15,11 +15,10 @@ class FourDShape extends THREE.Group {
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;
this.link_scale = 1;
this.hyperplane = HYPERPLANE;
this.foreshortening = W_FORESHORTENING;
this.initShapes();
}
@ -35,7 +34,7 @@ class FourDShape extends THREE.Group {
}
}
makeNode(material, v3) {
makeNode(material, v3, scale) {
const geometry = new THREE.SphereGeometry(this.node_size);
const sphere = new THREE.Mesh(geometry, material);
sphere.position.copy(v3);
@ -44,33 +43,45 @@ class FourDShape extends THREE.Group {
}
makeLink(material, link) {
const n1 = this.nodes3[link.source].v3;
const n2 = this.nodes3[link.target].v3;
const length = n1.distanceTo(n2);
const n1 = this.nodes3[link.source];
const n2 = this.nodes3[link.target];
const s1 = n1.scale;
const s2 = n2.scale;
const length = n1.v3.distanceTo(n2.v3);
const centre = new THREE.Vector3();
centre.lerpVectors(n1, n2, 0.5);
const geometry = new THREE.CylinderGeometry(this.link_size, this.link_size, 1);
centre.lerpVectors(n1.v3, n2.v3, 0.5);
const geometry = new THREE.CylinderGeometry(
this.link_scale * s2, this.link_scale * s1, 1,
16, 1, true
);
const cyl = new THREE.Mesh(geometry, material);
const edge = new THREE.Group();
edge.add(cyl);
edge.position.copy(centre);
edge.scale.copy(new THREE.Vector3(1, 1, length));
edge.lookAt(n2);
edge.lookAt(n2.v3);
cyl.rotation.x = Math.PI / 2.0;
this.add(edge);
return edge;
}
updateLink(link) {
const n1 = this.nodes3[link.source].v3;
const n2 = this.nodes3[link.target].v3;
const length = n1.distanceTo(n2);
updateLink(link, links_show) {
const n1 = this.nodes3[link.source];
const n2 = this.nodes3[link.target];
const s1 = n1.scale;
const s2 = n2.scale;
const length = n1.v3.distanceTo(n2.v3);
const centre = new THREE.Vector3();
centre.lerpVectors(n1, n2, 0.5);
link.object.scale.copy(new THREE.Vector3(this.link_scale, this.link_scale, length));
centre.lerpVectors(n1.v3, n2.v3, 0.5);
// take the average of the ends as the thickness - as a workaround,
// because I haven't worked out how to reshape tapered links without
// having to reassign a new geometry to every link
const link_mean = this.link_scale * (s1 + s2) * 0.5;
link.object.scale.copy(new THREE.Vector3(link_mean, link_mean, length));
link.object.position.copy(centre);
link.object.lookAt(n2);
link.object.lookAt(n2.v3);
link.object.children[0].rotation.x = Math.PI / 2.0;
link.object.visible = (!links_show || link.label in links_show);
}
@ -99,23 +110,42 @@ class FourDShape extends THREE.Group {
}
fourDtoV3(x, y, z, w, rotations) {
fourDtoV3_old(x, y, z, w, rotations) {
const v4 = new THREE.Vector4(x, y, z, w);
for ( const m4 of rotations ) {
v4.applyMatrix4(m4);
}
const k = this.hyperplane / (this.hyperplane + v4.w);
const k = this.fourDscale(v4.w);
return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
}
fourDscale(w) {
return this.hyperplane / ( this.hyperplane + w );
}
fourDrotate(x, y, z, w, rotations) {
const v4 = new THREE.Vector4(x, y, z, w);
for ( const m4 of rotations ) {
v4.applyMatrix4(m4);
}
return v4;
}
fourDtoV3(v4) {
const k = this.fourDscale(v4.w);
return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
}
initShapes() {
for( const n of this.nodes4 ) {
const v3 = this.fourDtoV3(n.x, n.y, n.z, n.w, []);
const k = this.fourDscale(n.w);
const v3 = new THREE.Vector3(n.x * k, n.y * k, n.z * k);
const material = this.getMaterial(n, this.node_ms);
this.nodes3[n.id] = {
v3: v3,
object: this.makeNode(material, v3)
scale: k,
label: n.label,
object: this.makeNode(material, v3, k)
};
}
for( const l of this.links ) {
@ -129,17 +159,22 @@ class FourDShape extends THREE.Group {
}
render3(rotations) {
render3(rotations, nodes_show, links_show) {
this.scalev3 = new THREE.Vector3(this.node_scale, this.node_scale, this.node_scale);
for( const n of this.nodes4 ) {
const v3 = this.fourDtoV3(n.x, n.y, n.z, n.w, rotations);
const v4 = this.fourDrotate(n.x, n.y, n.z, n.w, rotations);
const k = this.fourDscale(v4.w);
const v3 = new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
const s4 = k * this.node_scale * this.foreshortening;
const s3 = new THREE.Vector3(s4, s4, s4);
this.nodes3[n.id].v3 = v3;
this.nodes3[n.id].scale = k * this.foreshortening;
this.nodes3[n.id].object.position.copy(v3);
this.nodes3[n.id].object.scale.copy(this.scalev3);
this.nodes3[n.id].object.scale.copy(s3);
this.nodes3[n.id].object.visible = ( !nodes_show || n.label in nodes_show );
}
for( const l of this.links ) {
this.updateLink(l);
this.updateLink(l, links_show);
}
for( const f of this.faces ) {
@ -147,7 +182,6 @@ class FourDShape extends THREE.Group {
}
}
}
export { FourDShape };

97
gui.js
View File

@ -1,20 +1,26 @@
import { GUI } from 'lil-gui';
const DEFAULTS = {
thickness: 0.25,
nodesize: 1.25,
linkopacity: 0.5,
link2opacity: 0.5,
nodesize: 0.25,
nodeopacity: 1,
linksize: 0.2,
linkopacity: 0.75,
link2opacity: 0.75,
shape: '120-cell',
option: 'none',
visibility: 5,
inscribed: false,
inscribe_all: false,
color: 0x3293a9,
background: 0xd4d4d4,
hyperplane: 2,
rotation: 'rigid',
hyperplane: 0.93,
zoom: 1,
xRotate: 'YW',
yRotate: 'XW',
dtheta: 0,
damping: false,
captions: true,
dpsi: 0,
}
@ -22,50 +28,77 @@ const DEFAULTS = {
class FourDGUI {
constructor(changeShape, setColor, setBackground, setLinkOpacity) {
constructor(shapes, changeShape, setColor, setBackground, setNodeOpacity,setLinkOpacity, setVisibility, showDocs) {
this.gui = new GUI();
const SHAPE_NAMES = shapes.map((s) => s.name);
this.parseLinkParams();
const guiObj = this;
this.params = {
shape: this.link['shape'],
option: this.link['option'],
inscribed: this.link['inscribed'],
inscribe_all: this.link['inscribe_all'],
thickness: this.link['thickness'],
linksize: this.link['linksize'],
linkopacity: this.link['linkopacity'],
link2opacity: this.link['linkopacity'],
nodesize: this.link['nodesize'],
nodeopacity: this.link['nodeopacity'],
depth: this.link['depth'],
color: this.link['color'],
background: this.link['background'],
hyperplane: this.link['hyperplane'],
rotation: this.link['rotation'],
zoom: this.link['zoom'],
xRotate: this.link['xRotate'],
yRotate: this.link['yRotate'],
damping: false,
captions: true,
dtheta: this.link['dtheta'],
dpsi: this.link['dpsi'],
"copy link": function () { guiObj.copyUrl() }
};
this.gui.add(this.params, 'shape',
[ 'dodecahedron', '5-cell', '16-cell', 'tesseract',
'24-cell', '600-cell', '120-cell' ]
).onChange(changeShape)
this.gui.add(this.params, 'inscribed').onChange(changeShape);
this.gui.add(this.params, 'inscribe_all').onChange(changeShape);
this.gui.add(this.params, 'hyperplane', 1.5, 2.25);
this.gui.add(this.params, 'thickness', 0.1, 2);
let options_ctrl;
this.gui.add(this.params, 'shape', SHAPE_NAMES).onChange((shape) => {
const options = this.getShapeOptions(shapes, shape);
options_ctrl = options_ctrl.options(options).onChange((option) => {
setVisibility(option)
});
options_ctrl.setValue(options[0])
changeShape(shape)
});
const options = this.getShapeOptions(shapes, this.params['shape']);
options_ctrl = this.gui.add(this.params, 'option').options(options).onChange((option) => {
setVisibility(option)
});
this.gui.add(this.params, 'hyperplane', 0.5, 1 / 0.8);
this.gui.add(this.params, 'zoom', 0.1, 2.0);
this.gui.add(this.params, 'nodesize', 0, 1);
this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(setNodeOpacity);
this.gui.add(this.params, 'linksize', 0, 1);
this.gui.add(this.params, 'linkopacity', 0, 1).onChange(
(v) => setLinkOpacity(v, true)
);
this.gui.add(this.params, 'link2opacity', 0, 1).onChange(
(v) => setLinkOpacity(v, false)
);
this.gui.add(this.params, 'nodesize', 0.1, 4);
this.gui.addColor(this.params, 'color').onChange(setColor);
this.gui.addColor(this.params, 'background').onChange(setBackground);
this.gui.add(this.params, 'rotation', [ 'rigid', 'tumbling', 'inside-out', 'axisymmetrical' ]);
this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]);
this.gui.add(this.params, 'yRotate', [ 'XZ', 'XY', 'XW' ]);
this.gui.add(this.params, 'captions').onChange(showDocs);
this.gui.add(this.params, 'damping');
this.gui.add(this.params, 'copy link');
}
}
getShapeOptions(shapes, shape) {
const spec = shapes.filter((s) => s.name === shape);
if( spec && spec[0].options ) {
return spec[0].options.map((o) => o.name);
} else {
return [];
}
}
numParam(param, parser) {
@ -95,7 +128,7 @@ class FourDGUI {
const guiObj = this;
this.urlParams = this.linkUrl.searchParams;
for( const param of [ "shape", "rotation" ]) {
for( const param of [ "shape", "xRotate", "yRotate", "option" ]) {
const value = this.urlParams.get(param);
if( value ) {
this.link[param] = value;
@ -107,10 +140,12 @@ class FourDGUI {
this.link[param] = ( this.urlParams.get(param) === 'y' );
}
this.link['hyperplane'] = this.numParam('hyperplane', parseFloat);
this.link['thickness'] = this.numParam('thickness', parseFloat);
this.link['zoom'] = this.numParam('zoom', parseFloat);
this.link['linksize'] = this.numParam('linksize', parseFloat);
this.link['linkopacity'] = this.numParam('linkopacity', parseFloat);
this.link['link2opacity'] = this.numParam('link2opacity', parseFloat);
this.link['nodesize'] = this.numParam('nodesize', parseFloat);
this.link['nodeopacity'] = this.numParam('nodeopacity', parseFloat);
this.link['color'] = this.numParam('color', (s) => guiObj.stringToHex(s));
this.link['background'] = this.numParam('background', (s) => guiObj.stringToHex(s));
this.link['dpsi'] = this.numParam('dpsi', parseFloat);
@ -121,16 +156,20 @@ class FourDGUI {
copyUrl() {
const url = new URL(this.linkUrl.origin + this.linkUrl.pathname);
url.searchParams.append("shape", this.params.shape);
url.searchParams.append("option", this.params.option);
url.searchParams.append("inscribed", this.params.inscribed ? 'y': 'n');
url.searchParams.append("inscribe_all", this.params.inscribe_all ? 'y': 'n');
url.searchParams.append("thickness", this.params.thickness.toString());
url.searchParams.append("linksize", this.params.linksize.toString());
url.searchParams.append("nodesize", this.params.nodesize.toString());
url.searchParams.append("linkopacity", this.params.thickness.toString());
url.searchParams.append("link2opacity", this.params.nodesize.toString());
url.searchParams.append("nodeopacity", this.params.nodesize.toString());
url.searchParams.append("linkopacity", this.params.nodeopacity.toString());
url.searchParams.append("link2opacity", this.params.link2opacity.toString());
url.searchParams.append("color", this.hexToString(this.params.color));
url.searchParams.append("background", this.hexToString(this.params.background));
url.searchParams.append("hyperplane", this.params.hyperplane.toString());
url.searchParams.append("rotation", this.params.rotation);
url.searchParams.append("zoom", this.params.zoom.toString());
url.searchParams.append("xRotate", this.params.xRotate);
url.searchParams.append("yRotate", this.params.yRotate);
url.searchParams.append("dtheta", this.params.dtheta.toString());
url.searchParams.append("dpsi", this.params.dpsi.toString());
this.copyTextToClipboard(url);
@ -178,4 +217,4 @@ class FourDGUI {
}
export { FourDGUI, DEFAULTS };
export { FourDGUI, DEFAULTS };

View File

@ -5,6 +5,15 @@
<title>FourD</title>
<style>
body { margin: 0; }
div#description {
position: fixed;
top: 0;
left: 0;
width: 20%;
z-index: 2;
font-family: sans-serif;
padding: 1em;
}
div#info {
position: fixed;
bottom:0;
@ -16,7 +25,8 @@
</head>
<body>
<script type="module" src="/main.js"></script>
<div id="description"></div>
<div id="info">by <a target="_blank" href="https://mikelynch.org/">Mike Lynch</a> -
<a target="_blank" href="https://github.com/spikelynch/fourdjs">source</a></div>
<a target="_blank" href="https://git.tilde.town/bombinans/fourdjs">source</a></div>
</body>
</html>

View File

@ -1,7 +1,8 @@
//testbed for playing with stuff in node repl
// code for generating the 120-cell labels
// has some overlap with permute - FIXME
const THREE =require('three');
const THREE = require('three');
function pandita(a) {
const n = a.length;
@ -133,7 +134,7 @@ 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) {
export function auto_detect_edges(nodes, neighbours, debug=false) {
const seen = {};
const nnodes = nodes.length;
const links = [];
@ -168,7 +169,7 @@ function auto_detect_edges(nodes, neighbours, debug=false) {
function make_120cell_vertices() {
export function make_120cell_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5));
const r5 = Math.sqrt(5);
const phi2 = phi * phi;
@ -230,7 +231,7 @@ function fingerprint(ids) {
function auto_120cell_faces(links) {
export function auto_120cell_faces(links) {
const faces = [];
const seen = {};
let id = 1;
@ -486,7 +487,7 @@ function colour_next_dodeca_maybe(nodes, links, faces, colours, dd, nextf, nextd
const nextvs = dodecahedron_vertices(nextdd);
// get the initial colour permutations from the existing labels;
const p = [];
for( i = 0; i < 5; i ++ ) {
for( let i = 0; i < 5; i ++ ) {
p[i] = colours[nextvs[i]];
}
const nlabels = colour_dodecahedron_from_face(nextdd, p);
@ -529,6 +530,33 @@ function meridian(nodes, links, faces, startf, startn, dir=11, max=10) {
}
function meridian_bump(nodes, links, faces, startf, startn, bumpdir=6) {
const o = face_plus_to_dodecahedron(faces, startf, startn);
const dir = 11;
const max = 10;
const colours = colour_dodecahedron_from_face(o, [ 1, 2, 3, 4, 5 ] );
const dds = follow_meridian(nodes, links, faces, colours, o, dir, max);
const dd4 = dds[4];
const nextf = dd4[bumpdir];
const bump = follow_face_to_dodeca(faces, dd4, nextf);
const ncolours = colour_next_dodeca_maybe(nodes, links, faces, colours, dd4, nextf, bump);
add_colours(colours, ncolours);
const labels = { 1: [], 2:[], 3:[], 4:[], 5:[] };
for( const vstr in colours ) {
labels[colours[vstr]].push(Number(vstr));
}
return { dodecahedra: dds, labels: labels };
}
function all_meridians(nodes, links, faces, startf, startn) {
const o = face_plus_to_dodecahedron(faces, startf, startn);
@ -668,8 +696,9 @@ function arctic(nodes, links, faces, startf, startn, max) {
// this is the final one that works for the whole 120-cell
function arctic_two(nodes, links, faces, startf, startn) {
export function label_120cell(nodes, links, faces, startf, startn) {
const pole = face_plus_to_dodecahedron(faces, startf, startn);
const dds = [ pole ];
@ -688,10 +717,6 @@ function arctic_two(nodes, links, faces, startf, startn) {
seen[dd_fingerprint(nextdd)] = true;
}
// go around all of the arctic circle and grow all faces
// 1, 12, 20, 12, 30 = 75
// 0 1 13, 33, 45
for( const a of dds.slice(1, 13) ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
@ -722,6 +747,8 @@ function arctic_two(nodes, links, faces, startf, startn) {
}
// the 30 equatorials?
for( const a of dds.slice(13, 46) ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour(
@ -750,7 +777,10 @@ function arctic_two(nodes, links, faces, startf, startn) {
}
}
// this should get the rest or explode!
for( const a of dds ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour(
@ -778,6 +808,124 @@ function arctic_two(nodes, links, faces, startf, startn) {
export function cell120_layers(nodes, links, faces, startf, startn, max_layer) {
const pole = face_plus_to_dodecahedron(faces, startf, startn);
const dds = [ pole ];
const dd_families = { "0": [ pole ] }
const seen = {};
seen[dd_fingerprint(pole)] = true;
const colours = colour_dodecahedron_from_face(dds[0], [ 1, 2, 3, 4, 5 ] );
const vs = dodecahedron_vertices(dds[0]);
// arctic
if( max_layer > 0 ) {
dd_families["1"] = [];
for( const face of pole ) {
const [ nextdd, ncolours ] = follow_and_colour(
nodes, links, faces, colours, pole, face
);
add_colours(colours, ncolours);
dds.push(nextdd);
dd_families["1"].push(nextdd);
seen[dd_fingerprint(nextdd)] = true;
}
}
// subarctic - interstitial
if( max_layer > 1 ) {
dd_families["2"] = [];
for( const a of dd_families["1"] ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour(
nodes, links, faces, colours, a, a[i]
);
const fp = dd_fingerprint(nextdd);
if( !(fp in seen) ) {
add_colours(colours, ncolours);
dds.push(nextdd);
dd_families["2"].push(nextdd);
seen[fp] = true;
}
}
}
}
// tropic of cancer
if( max_layer > 2 ) {
dd_families["3"] = [];
for( const a of dd_families["1"] ) {
const [ nextdd, ncolours ] = follow_and_colour(
nodes, links, faces, colours, a, a[11]
);
const fp = dd_fingerprint(nextdd);
if( !(fp in seen) ) {
add_colours(colours, ncolours);
dds.push(nextdd);
dd_families["3"].push(nextdd);
seen[fp] = true;
}
}
}
if( max_layer > 3 ) {
// equator
dd_families["4"] = [];
for( const a of dds.slice(13, 46) ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour(
nodes, links, faces, colours, a, a[i]
);
const fp = dd_fingerprint(nextdd);
if( !(fp in seen) ) {
add_colours(colours, ncolours);
dd_families["4"].push(nextdd);
dds.push(nextdd);
seen[fp] = true;
}
}
}
}
if( max_layer > 4 ) {
dd_families["5"] = [];
for( const a of dd_families["4"] ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour(
nodes, links, faces, colours, a, a[i]
);
const fp = dd_fingerprint(nextdd);
if( !(fp in seen) ) {
add_colours(colours, ncolours);
dds.push(nextdd);
dd_families["5"].push(nextdd);
seen[fp] = true;
}
}
}
}
const labels = { 1: [], 2:[], 3:[], 4:[], 5:[] };
for( const vstr in colours ) {
labels[colours[vstr]].push(Number(vstr));
}
return { dodecahedra: dds, labels: labels, families: dd_families };
}
// 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
@ -815,7 +963,6 @@ function make_120cell_cells(faces) {
for( const dd of dds ) {
const fp = dd_fingerprint(dd);
if( ! (fp in seen) ) {
//console.log(`added dodeca ${fp}`);
const d = {
id: i,
faces: dd,
@ -895,7 +1042,7 @@ function meridian_label_120cell(nodes) {
//label_nodes(nodes, [313], 6);
}
1
function check_120cell_nodes(nodes) {
nodes.map((n) => {
const vs = find_adjacent_labels(nodes, links, n.id);
@ -927,9 +1074,120 @@ function make_dodecahedron_vertices() {
}
// const nodes = make_120cell_vertices();
// const links = auto_detect_edges(nodes, 4);
// const faces = auto_120cell_faces(links);
// this one does the coherent indexing / partition into 600-cells
export function make_labelled_120cell() {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
const faces = auto_120cell_faces(links);
const labelled = label_120cell(nodes, links, faces, faces[0], 341);
return labelled;
}
// calculate the w-distance of a dodecahedron's centroid
export function dd_w_distance(nodes, dd) {
const vertices = new Set();
dd.map((f) => f.nodes.map((n) => vertices.add(n)));
let w = 0;
for( const nid of vertices ) {
const node = node_by_id(nodes, nid);
w += node.w;
}
return w / 20;
}
export function sort_dds_w(nodes, dds) {
dds.sort((a, b) => {
return dd_w_distance(nodes, a) - dd_w_distance(nodes, b)
})
}
export function find_antipode_dd(nodes, links, dd) {
}
// notes because I'm too sick to continue working on this today
// the vertices which aren't counted by the layers are showing
// up in the visualisation because they are labelled "0" by default -
// for this to work better there needs to be a value which is never
// displayed.
// in the current layer algorithm, layer '5' (the one after the equator)
// is too greedy
export function make_layered_120cell(max_layer) {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
const faces = auto_120cell_faces(links);
const labelled = label_120cell(nodes, links, faces, faces[628], 250, max_layer);
// get layers from sorted w-distance order
const dds = labelled.dodecahedra;
dds.sort((a, b) => dd_w_distance(nodes, b) - dd_w_distance(nodes, a));
const LAYERS = [
[ "0", 1 ],
[ "1", 12 ],
[ "2", 20 ],
[ "3", 12 ],
[ "4", 30 ],
[ "5", 12 ],
[ "6", 20 ],
[ "7", 12 ],
[ "8", 1]
];
const layer_dds = labelled["families"];
const vertices_layers = {};
const seen = {};
let i = 0;
for( const layer of LAYERS ) {
const label = layer[0];
const n = layer[1];
vertices_layers[label] = [];
console.log(`Layer ${label} starting at ${i}`);
for( const dd of dds.slice(i, i + n) ) {
console.log(dd_w_distance(nodes, dd));
for( const face of dd ) {
for( const n of face.nodes ) {
if( !seen[n] ) {
vertices_layers[label].push(n);
seen[n] = true;
}
}
}
}
i += n;
}
return JSON.stringify(vertices_layers);
}
export function make_meridians(bumpdir) {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
const faces = auto_120cell_faces(links);
const mbs ={}
for( const bumpdir of [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] ) {
mbs[bumpdir] = meridian_bump(nodes, links, faces, faces[0], 341, bumpdir)
}
return mbs;
//
}
// console.log("Calculating 120-cell colours")

173
layer600cell.js 100644
View File

@ -0,0 +1,173 @@
import * as POLYTOPES from './polytopes.js';
// face detection for the 600-cell
export function nodes_links(links, nodeid) {
return links.filter((l) => l.source === nodeid || l.target === nodeid);
}
export function linked(links, n1, n2) {
const ls = nodes_links(nodes_links(links, n1), n2);
if( ls.length ) {
return ls[0]
} else {
return false;
}
}
function fingerprint(ids) {
const sids = [...ids];
sids.sort();
return sids.join(',');
}
export function make_600cell() {
const nodes = POLYTOPES.make_600cell_vertices();
const links = POLYTOPES.auto_detect_edges(nodes, 12);
return {
nodes: nodes,
links: links
}
}
export function link_to_tetras(nodes, links, link) {
const n1 = link.source;
const n2 = link.target;
const nl1 = nodes_links(links, n1).filter((l) => l.id !== link.id);
const nl2 = nodes_links(links, n2).filter((l) => l.id !== link.id);
const p1 = new Set();
const p = new Set();
for( const nl of nl1 ) {
if( nl.source !== n1 ) {
p1.add(nl.source);
}
if( nl.target !== n1 ) {
p1.add(nl.target);
}
}
for( const nl of nl2 ) {
if( nl.source !== n2 && p1.has(nl.source) ) {
p.add(nl.source);
}
if( nl.target !== n2 && p1.has(nl.target) ) {
p.add(nl.target);
}
}
const lp = Array.from(p);
const seen = {};
const tetras = [];
for( const p1 of lp ) {
for( const p2 of lp ) {
if( p1 != p2 ) {
if( linked(links, p1, p2) ) {
const fp = fingerprint([n1, n2, p1, p2]);
if( !seen[fp] ) {
seen[fp] = true;
tetras.push({fingerprint: fp, nodes: [n1, n2, p1, p2]})
}
}
}
}
}
return tetras;
}
export function auto_600cell_cells(nodes, links) {
const seen = {};
const tetras = [];
links.map((link) => {
link_to_tetras(nodes, links, link).map((lt) => {
if( !seen[lt.fingerprint] ) {
seen[lt.fingerprint] = true;
tetras.push(lt.nodes);
}
})
});
return tetras;
}
function node_by_id(nodes, nid) {
const ns = nodes.filter((n) => n.id === nid);
return ns[0];
}
export function tetra_w(nodes, tetra) {
let w = 0;
for( const nid of tetra ) {
const node = node_by_id(nodes, nid);
w += node.w;
}
return w / 4;
}
export function sorted_600cells() {
const cell600 = make_600cell();
const tetras = auto_600cell_cells(cell600.nodes, cell600.links);
const layers = tetras.map((t) => { return { "nodes": t, w: tetra_w(cell600.nodes, t) } });
layers.sort((a, b) => b.w - a.w);
return layers;
}
// const cell600 = make_600cell();
// const layers = sorted_600cells(cell600.nodes, cell600.links);
// for( const cell of layers ) {
// // const fp = fingerprint(cell.nodes);
// console.log(`${cell.w} ${cell.nodes}`);
// }
export function make_layered_600cell() {
const tetras = sorted_600cells()
const LAYERS = [
[ "00", 20 ],
[ "01", 20 ],
[ "02", 30 ],
[ "03", 60 ],
[ "04", 60 ],
[ "05", 60 ],
[ "06", 20 ],
[ "07", 60 ],
[ "08", 20 ],
[ "09", 60 ],
[ "10", 60 ],
[ "11", 60 ],
[ "12", 30 ],
[ "13", 20 ],
[ "14", 20 ]
];
const vertices = {};
const seen = {};
let i = 0;
for( const layer of LAYERS ) {
const label = layer[0];
const n = layer[1];
vertices[label] = [];
console.log(`Layer ${label} starting at ${i}`);
for( const t of tetras.slice(i, i + n) ) {
console.log(t);
for( const n of t.nodes ) {
if( !seen[n] ) {
vertices[label].push(n);
seen[n] = true;
}
}
}
i += n;
}
return JSON.stringify(vertices);
}

148
main.js
View File

@ -3,12 +3,13 @@ import * as THREE from 'three';
import * as POLYTOPES from './polytopes.js';
import { get_rotation } from './rotation.js';
import { rotfn } from './rotation.js';
import { FourDGUI, DEFAULTS } from './gui.js';
import { FourDShape } from './fourDShape.js';
import { get_colours } from './colours.js';
const FACE_OPACITY = 0.3;
const CAMERA_K = 5;
// scene, lights and camera
@ -23,7 +24,10 @@ scene.add(light);
const amblight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(amblight);
camera.position.z = 4;
camera.position.set(0, 0, CAMERA_K / 2);
camera.lookAt(0, 0, 0);
//camera.position.z = 4;
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
@ -43,11 +47,19 @@ material.opacity = 0.5;
const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c}));
const link_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c}));
node_ms.map((m) => {
m.transparent = true;
m.opacity = 1.0;
}
);
link_ms.map((m) => {
m.transparent = true;
m.opacity = 0.5;
}
)
);
const face_ms = [
new THREE.MeshLambertMaterial( { color: 0x44ff44 } )
@ -59,51 +71,46 @@ for( const face_m of face_ms ) {
}
const STRUCTURES = {
'5-cell': POLYTOPES.cell5(),
'16-cell': POLYTOPES.cell16(),
'tesseract': POLYTOPES.tesseract(),
'24-cell': POLYTOPES.cell24(),
'dodecahedron': POLYTOPES.dodecahedron(),
'120-cell': POLYTOPES.cell120(),
'600-cell': POLYTOPES.cell600(),
};
const STRUCTURES = POLYTOPES.build_all();
const INSCRIBED = {
'tesseract': POLYTOPES.tesseract_inscribed(),
'24-cell': POLYTOPES.cell24_inscribed(),
'120-cell': POLYTOPES.cell120_inscribed(),
'600-cell': POLYTOPES.cell600_inscribed(),
'dodecahedron': POLYTOPES.dodecahedron_inscribed(),
};
const STRUCTURES_BY_NAME = {};
const ALL_INSCRIBED = {
'tesseract': POLYTOPES.tesseract_all_inscribed(),
'24-cell': POLYTOPES.cell24_all_inscribed(),
'120-cell': POLYTOPES.cell120_all_inscribed(),
'600-cell': POLYTOPES.cell600_all_inscribed(),
'dodecahedron': POLYTOPES.dodecahedron_all_inscribed(),
}
STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s);
let shape = false;
let structure = false;
let node_show = [];
let link_show = [];
function createShape(name, inscribed, all) {
function createShape(name, option) {
if( shape ) {
scene.remove(shape);
}
let structure = STRUCTURES[name];
if( inscribed ) {
if( name in INSCRIBED ) {
if( all ) {
structure = ALL_INSCRIBED[name];
} else {
structure = INSCRIBED[name];
}
}
}
structure = STRUCTURES_BY_NAME[name];
shape = new FourDShape(node_ms, link_ms, face_ms, structure);
scene.add(shape);
setVisibility(option ? option : structure.options[0].name);
}
function displayDocs(name) {
const docdiv = document.getElementById("description");
const description = STRUCTURES_BY_NAME[name].description;
if( description ) {
docdiv.innerHTML =`<p>${name}</p><p>${description}</p>`;
} else {
docdiv.innerHTML =`<p>${name}</p>`;
}
}
function showDocs(visible) {
console.log(`showDocs ${visible}`);
const docdiv = document.getElementById("description");
if( visible ) {
docdiv.style.display = '';
} else {
docdiv.style.display = 'none';
}
}
// initialise gui and read params from URL
@ -125,23 +132,51 @@ function setBackground(c) {
}
function setLinkOpacity(o, primary) {
if( primary ) {
link_ms[0].opacity = o;
if( structure.nolink2opacity ) {
link_ms.map((lm) => lm.opacity = o);
} else {
for( const lm of link_ms.slice(1) ) {
lm.opacity = o;
if( primary ) {
link_ms[0].opacity = o;
} else {
link_ms.slice(1).map((lm) => lm.opacity = o);
}
}
}
let gui; //
function changeShape() {
console.log("change shape!")
createShape(gui.params.shape, gui.params.inscribed, gui.params.inscribe_all);
function setNodeOpacity(o) {
node_ms.map((nm) => nm.opacity = o);
}
gui = new FourDGUI(changeShape, setColors, setBackground, setLinkOpacity);
let gui;
function changeShape() {
createShape(gui.params.shape);
displayDocs(gui.params.shape);
}
function setVisibility(option_name) {
const option = structure.options.filter((o) => o.name === option_name);
if( option.length ) {
node_show = option[0].nodes;
link_show = option[0].links;
} else {
console.log(`Error: option '${option_name}' not found`);
}
}
gui = new FourDGUI(
STRUCTURES,
changeShape,
setColors,
setBackground,
setNodeOpacity,
setLinkOpacity,
setVisibility,
showDocs
);
// these are here to pick up colour settings from the URL params
setColors(gui.params.color);
@ -184,7 +219,8 @@ renderer.domElement.addEventListener("pointerup", (event) => {
dragging = false;
})
createShape(gui.params.shape, gui.params.inscribed, gui.params.inscribe_all);
createShape(gui.params.shape, gui.params.option);
displayDocs(gui.params.shape);
function animate() {
requestAnimationFrame( animate );
@ -198,12 +234,18 @@ function animate() {
}
}
const rotations = get_rotation(gui.params.rotation, theta, psi);
const rotations = [
rotfn[gui.params.xRotate](theta),
rotfn[gui.params.yRotate](psi)
];
shape.hyperplane = 1 / gui.params.hyperplane;
camera.position.set(0, 0, gui.params.zoom * CAMERA_K * gui.params.hyperplane);
shape.hyperplane = gui.params.hyperplane;
shape.link_scale = gui.params.thickness;
shape.node_scale = gui.params.nodesize;
shape.render3(rotations);
shape.link_scale = gui.params.linksize * gui.params.nodesize * 0.5;
shape.render3(rotations, node_show, link_show);
renderer.render( scene, camera );
}

270
package-lock.json generated
View File

@ -4,9 +4,11 @@
"requires": true,
"packages": {
"": {
"name": "fourdjs",
"dependencies": {
"color": "^4.2.3",
"color-scheme": "^1.0.1",
"lil-gui": "^0.18.2",
"lil-gui": "^0.19.0",
"three": "^0.154.0"
},
"devDependencies": {
@ -14,9 +16,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.15.tgz",
"integrity": "sha512-wlkQBWb79/jeEEoRmrxt/yhn5T1lU236OCNpnfRzaCJHZ/5gf82uYx1qmADTBWE0AR/v7FiozE1auk2riyQd3w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
@ -30,9 +32,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.15.tgz",
"integrity": "sha512-NI/gnWcMl2kXt1HJKOn2H69SYn4YNheKo6NZt1hyfKWdMbaGadxjZIkcj4Gjk/WPxnbFXs9/3HjGHaknCqjrww==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
@ -46,9 +48,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.15.tgz",
"integrity": "sha512-FM9NQamSaEm/IZIhegF76aiLnng1kEsZl2eve/emxDeReVfRuRNmvT28l6hoFD9TsCxpK+i4v8LPpEj74T7yjA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
@ -62,9 +64,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.15.tgz",
"integrity": "sha512-XmrFwEOYauKte9QjS6hz60FpOCnw4zaPAb7XV7O4lx1r39XjJhTN7ZpXqJh4sN6q60zbP6QwAVVA8N/wUyBH/w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
@ -78,9 +80,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.15.tgz",
"integrity": "sha512-bMqBmpw1e//7Fh5GLetSZaeo9zSC4/CMtrVFdj+bqKPGJuKyfNJ5Nf2m3LknKZTS+Q4oyPiON+v3eaJ59sLB5A==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
@ -94,9 +96,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.15.tgz",
"integrity": "sha512-LoTK5N3bOmNI9zVLCeTgnk5Rk0WdUTrr9dyDAQGVMrNTh9EAPuNwSTCgaKOKiDpverOa0htPcO9NwslSE5xuLA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
@ -110,9 +112,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.15.tgz",
"integrity": "sha512-62jX5n30VzgrjAjOk5orYeHFq6sqjvsIj1QesXvn5OZtdt5Gdj0vUNJy9NIpjfdNdqr76jjtzBJKf+h2uzYuTQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
@ -126,9 +128,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.15.tgz",
"integrity": "sha512-dT4URUv6ir45ZkBqhwZwyFV6cH61k8MttIwhThp2BGiVtagYvCToF+Bggyx2VI57RG4Fbt21f9TmXaYx0DeUJg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
@ -142,9 +144,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.15.tgz",
"integrity": "sha512-BWncQeuWDgYv0jTNzJjaNgleduV4tMbQjmk/zpPh/lUdMcNEAxy+jvneDJ6RJkrqloG7tB9S9rCrtfk/kuplsQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
@ -158,9 +160,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.15.tgz",
"integrity": "sha512-JPXORvgHRHITqfms1dWT/GbEY89u848dC08o0yK3fNskhp0t2TuNUnsrrSgOdH28ceb1hJuwyr8R/1RnyPwocw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
@ -174,9 +176,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.15.tgz",
"integrity": "sha512-kArPI0DopjJCEplsVj/H+2Qgzz7vdFSacHNsgoAKpPS6W/Ndh8Oe24HRDQ5QCu4jHgN6XOtfFfLpRx3TXv/mEg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
@ -190,9 +192,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.15.tgz",
"integrity": "sha512-b/tmngUfO02E00c1XnNTw/0DmloKjb6XQeqxaYuzGwHe0fHVgx5/D6CWi+XH1DvkszjBUkK9BX7n1ARTOst59w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
@ -206,9 +208,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.15.tgz",
"integrity": "sha512-KXPY69MWw79QJkyvUYb2ex/OgnN/8N/Aw5UDPlgoRtoEfcBqfeLodPr42UojV3NdkoO4u10NXQdamWm1YEzSKw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
@ -222,9 +224,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.15.tgz",
"integrity": "sha512-komK3NEAeeGRnvFEjX1SfVg6EmkfIi5aKzevdvJqMydYr9N+pRQK0PGJXk+bhoPZwOUgLO4l99FZmLGk/L1jWg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
@ -238,9 +240,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.15.tgz",
"integrity": "sha512-632T5Ts6gQ2WiMLWRRyeflPAm44u2E/s/TJvn+BP6M5mnHSk93cieaypj3VSMYO2ePTCRqAFXtuYi1yv8uZJNA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
@ -254,9 +256,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.15.tgz",
"integrity": "sha512-MsHtX0NgvRHsoOtYkuxyk4Vkmvk3PLRWfA4okK7c+6dT0Fu4SUqXAr9y4Q3d8vUf1VWWb6YutpL4XNe400iQ1g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
@ -270,9 +272,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.15.tgz",
"integrity": "sha512-djST6s+jQiwxMIVQ5rlt24JFIAr4uwUnzceuFL7BQT4CbrRtqBPueS4GjXSiIpmwVri1Icj/9pFRJ7/aScvT+A==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
@ -286,9 +288,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.15.tgz",
"integrity": "sha512-naeRhUIvhsgeounjkF5mvrNAVMGAm6EJWiabskeE5yOeBbLp7T89tAEw0j5Jm/CZAwyLe3c67zyCWH6fsBLCpw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
@ -302,9 +304,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.15.tgz",
"integrity": "sha512-qkT2+WxyKbNIKV1AEhI8QiSIgTHMcRctzSaa/I3kVgMS5dl3fOeoqkb7pW76KwxHoriImhx7Mg3TwN/auMDsyQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
@ -318,9 +320,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.15.tgz",
"integrity": "sha512-HC4/feP+pB2Vb+cMPUjAnFyERs+HJN7E6KaeBlFdBv799MhD+aPJlfi/yk36SED58J9TPwI8MAcVpJgej4ud0A==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
@ -334,9 +336,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.15.tgz",
"integrity": "sha512-ovjwoRXI+gf52EVF60u9sSDj7myPixPxqzD5CmkEUmvs+W9Xd0iqISVBQn8xcx4ciIaIVlWCuTbYDOXOnOL44Q==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
@ -350,9 +352,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.15.tgz",
"integrity": "sha512-imUxH9a3WJARyAvrG7srLyiK73XdX83NXQkjKvQ+7vPh3ZxoLrzvPkQKKw2DwZ+RV2ZB6vBfNHP8XScAmQC3aA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
@ -365,15 +367,52 @@
"node": ">=12"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-scheme": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/color-scheme/-/color-scheme-1.0.1.tgz",
"integrity": "sha512-4x+ya6+z6g9DaTFSfVzTZc8TSjxHuDT40NB43N3XPUkQlF6uujhwH8aeMeq8HBgoQQog/vrYgJ16mt/eVTRXwQ=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/esbuild": {
"version": "0.18.15",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.15.tgz",
"integrity": "sha512-3WOOLhrvuTGPRzQPU6waSDWrDTnQriia72McWcn6UCi43GhCHrXH4S59hKMeez+IITmdUuUyvbU9JIp+t3xlPQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -383,34 +422,34 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.15",
"@esbuild/android-arm64": "0.18.15",
"@esbuild/android-x64": "0.18.15",
"@esbuild/darwin-arm64": "0.18.15",
"@esbuild/darwin-x64": "0.18.15",
"@esbuild/freebsd-arm64": "0.18.15",
"@esbuild/freebsd-x64": "0.18.15",
"@esbuild/linux-arm": "0.18.15",
"@esbuild/linux-arm64": "0.18.15",
"@esbuild/linux-ia32": "0.18.15",
"@esbuild/linux-loong64": "0.18.15",
"@esbuild/linux-mips64el": "0.18.15",
"@esbuild/linux-ppc64": "0.18.15",
"@esbuild/linux-riscv64": "0.18.15",
"@esbuild/linux-s390x": "0.18.15",
"@esbuild/linux-x64": "0.18.15",
"@esbuild/netbsd-x64": "0.18.15",
"@esbuild/openbsd-x64": "0.18.15",
"@esbuild/sunos-x64": "0.18.15",
"@esbuild/win32-arm64": "0.18.15",
"@esbuild/win32-ia32": "0.18.15",
"@esbuild/win32-x64": "0.18.15"
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
@ -421,10 +460,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/lil-gui": {
"version": "0.18.2",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.18.2.tgz",
"integrity": "sha512-DgdrLy3/KGC0PiQLKgOcJMPItP4xY4iWgJ9+91Zaxfr8GCTmMps05QS9w9jW7yspILlbscbquwjOwxmWnSx5Uw=="
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.19.0.tgz",
"integrity": "sha512-02/Z7rPng3GXWFwkQVj1hQaJYo2fIEYctqe0ima5uI/N2HEagB9ZGCQKkVWr3UuKfTr0arto3Q9prTB8sxtJJw=="
},
"node_modules/nanoid": {
"version": "3.3.6",
@ -451,9 +495,9 @@
"dev": true
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@ -479,9 +523,9 @@
}
},
"node_modules/rollup": {
"version": "3.26.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz",
"integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==",
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@ -494,6 +538,14 @@
"fsevents": "~2.3.2"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@ -509,14 +561,14 @@
"integrity": "sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug=="
},
"node_modules/vite": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.6.tgz",
"integrity": "sha512-EY6Mm8vJ++S3D4tNAckaZfw3JwG3wa794Vt70M6cNJ6NxT87yhq7EC8Rcap3ahyHdo8AhCmV9PTk+vG1HiYn1A==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.26",
"rollup": "^3.25.2"
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"

View File

@ -1,7 +1,8 @@
{
"dependencies": {
"color": "^4.2.3",
"color-scheme": "^1.0.1",
"lil-gui": "^0.18.2",
"lil-gui": "^0.19.0",
"three": "^0.154.0"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
import * as PERMUTE from './permute.js';
import * as CELL120 from './cellindex.js';
import * as CELLINDEX from './cellindex.js';
function index_nodes(nodes, scale) {
let i = 1;
@ -22,7 +22,7 @@ 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) {
export function auto_detect_edges(nodes, neighbours, debug=false) {
const seen = {};
const nnodes = nodes.length;
const links = [];
@ -58,15 +58,15 @@ function auto_detect_edges(nodes, neighbours, debug=false) {
// too small and simple to calculate
export const cell5 = () => {
const r5 = Math.sqrt(5);
const r2 = Math.sqrt(2) / 2;
const c1 = Math.sqrt(5) / 4;
return {
name: '5-cell',
nodes: [
{id:1, label: 1, x: r2, y: r2, z: r2, w: -r2 / r5 },
{id:2, label: 2, x: r2, y: -r2, z: -r2, w: -r2 / r5 },
{id:3, label: 3, x: -r2, y: r2, z: -r2, w: -r2 / r5 },
{id:4, label: 4, x: -r2, y: -r2, z: r2, w: -r2 / r5 },
{id:5, label: 5, x: 0, y: 0, z: 0, w: 4 * r2 / r5 },
{id:1, label: 1, x: c1, y: c1, z: c1, w: -0.25 },
{id:2, label: 2, x: c1, y: -c1, z: -c1, w: -0.25 },
{id:3, label: 3, x: -c1, y: c1, z: -c1, w: -0.25 },
{id:4, label: 4, x: -c1, y: -c1, z: c1, w: -0.25 },
{id:5, label: 5, x: 0, y: 0, z: 0, w: 1 },
],
links: [
{ id:1, source:1, target: 2},
@ -80,10 +80,12 @@ export const cell5 = () => {
{ id:9, source:3, target: 5},
{ id:10, source:4, target: 5},
],
geometry: {
node_size: 0.02,
link_size: 0.02
}
options: [ { name: '--' }],
description: `Five tetrahedra joined at ten faces with three
tetrahedra around each edge. The 5-cell is the simplest regular
four-D polytope and the four-dimensional analogue of the tetrahedron.
A corresponding polytope, or simplex, exists for every n-dimensional
space.`,
};
};
@ -102,16 +104,18 @@ export const cell16 = () => {
nodes[1].label = 4;
index_nodes(nodes);
scale_nodes(nodes, 0.75);
scale_nodes(nodes, 0.5);
const links = auto_detect_edges(nodes, 6);
return {
name: '16-cell',
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
}
options: [ { name: '--' }],
description: `Sixteen tetrahedra joined at 32 faces with four
tetrahedra around each edge. The 16-cell is the four-dimensional
analogue of the octahedron and is dual to the tesseract. Every
n-dimensional space has a corresponding polytope in this family.`,
};
};
@ -129,41 +133,37 @@ export const tesseract = () => {
}
}
scale_nodes(nodes, Math.sqrt(2) / 2);
scale_nodes(nodes, 0.5);
const links = auto_detect_edges(nodes, 4);
links.map((l) => { l.label = 0 });
return {
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
}
};
}
const tesseract_some_inscribed = (ps) => {
const t = tesseract();
const i_links = [];
for( const p of ps ) {
const nodes16 = t.nodes.filter((n) => n.label === p);
for( const p of [ 1, 2 ] ) {
const nodes16 = nodes.filter((n) => n.label === p);
const links16 = auto_detect_edges(nodes16, 6);
links16.map((l) => l.label = p);
i_links.push(...links16);
links.push(...links16);
}
t.links.push(...i_links);
return t;
return {
name: 'Tesseract',
nodes: nodes,
links: links,
options: [
{ name: 'none', links: [ 0 ] },
{ name: 'one 16-cell', links: [ 0, 1 ] },
{ name: 'both 16-cells', links: [ 0, 1, 2 ] },
],
description: `The most well-known four-dimensional shape, the
tesseract is analogous to the cube, and is constructed by placing two
cubes in parallel hyperplanes and joining their corresponding
vertices. It consists of eight cubes joined at 32 face with three
cubes around each edge, and is dual to the 16-cell. Every
n-dimensional space has a cube analogue or measure polytope.`,
};
}
export const tesseract_inscribed = () => tesseract_some_inscribed([1]);
export const tesseract_all_inscribed = () => tesseract_some_inscribed([1,2]);
const CELL24_INDEXING = {
x: { y: 1, z: 3, w: 2 },
y: { z: 2, w: 3 },
@ -185,9 +185,17 @@ export const cell24 = () => {
n.label = CELL24_INDEXING[axes[0]][axes[1]];
}
scale_nodes(nodes, Math.sqrt(2) / 2);
index_nodes(nodes);
const links = auto_detect_edges(nodes, 8);
links.map((l) => l.label = 0);
for( const p of [ 1, 2, 3 ] ) {
const nodes16 = nodes.filter((n) => n.label === p);
const links16 = auto_detect_edges(nodes16, 6);
links16.map((l) => l.label = p);
links.push(...links16);
}
// links.map((l) => {
// const ls = [ l.source, l.target ].map((nid) => node_by_id(nodes, nid).label);
// for ( const c of [1, 2, 3] ) {
@ -198,35 +206,23 @@ export const cell24 = () => {
// });
return {
name: '24-cell',
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
}
base: {},
options: [
{ name: 'none', links: [ 0 ] },
{ name: 'one 16-cell', links: [ 0, 1 ] },
{ name: 'three 16-cells', links: [ 0, 1, 2, 3 ] }
],
description: `A unique object without an exact analogue in higher
or lower dimensions, the 24-cell is made of twenty-four octahedra
joined at 96 faces, with three around each edge. The 24-cell is
self-dual.`,
};
}
const cell24_some_inscribed = (ps) => {
const t = cell24();
const i_links = [];
for( const p of ps ) {
const nodes16 = t.nodes.filter((n) => n.label === p);
const links16 = auto_detect_edges(nodes16, 6);
links16.map((l) => l.label = p);
i_links.push(...links16);
}
t.links.push(...i_links);
return t;
}
export const cell24_inscribed = () => cell24_some_inscribed([1]);
export const cell24_all_inscribed = () => cell24_some_inscribed([1,2,3]);
@ -308,7 +304,7 @@ function auto_120cell_faces(links) {
function make_120cell_vertices() {
export function make_120cell_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5));
const r5 = Math.sqrt(5);
const phi2 = phi * phi;
@ -326,12 +322,14 @@ function make_120cell_vertices() {
PERMUTE.coordinates([2, 1, phi, phiinv], 0, true),
].flat();
index_nodes(nodes);
scale_nodes(nodes, 0.5);
scale_nodes(nodes, 0.25 * Math.sqrt(2));
return nodes;
}
function label_nodes(nodes, ids, label) {
nodes.filter((n) => ids.includes(n.id)).map((n) => n.label = label);
}
@ -339,16 +337,8 @@ 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 ) {
@ -361,33 +351,6 @@ function label_faces_120cell(nodes, faces, cfaces, label) {
}
function basic_auto_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;
}
}
}
function label_120cell(nodes) {
for( const cstr in CELL120.INDEX ) {
label_nodes(nodes, CELL120.INDEX[cstr], Number(cstr));
}
}
function link_labels(nodes, link) {
const n1 = nodes.filter((n) => n.id === link.source);
const n2 = nodes.filter((n) => n.id === link.target);
@ -395,124 +358,89 @@ function link_labels(nodes, link) {
}
// version of the 120-cell where nodes are partitioned by
// layer and the links follow that
export const cell120 = () => {
export const cell120_layered = (max) => {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
label_120cell(nodes);
nodes.map((n) => n.label = 9); // make all invisible by default
for (const cstr in CELLINDEX.LAYERS120 ) {
label_nodes(nodes, CELLINDEX.LAYERS120[cstr], Number(cstr));
}
links.map((l) => {
const labels = link_labels(nodes, l);
if( labels[0] >= labels[1] ) {
l.label = labels[0];
} else {
l.label = labels[1];
}
});
const options = [];
const layers = [];
for( const i of [ 0, 1, 2, 3, 4, 5, 6, 7 ] ) {
layers.push(i);
options.push({
name: CELLINDEX.LAYER_NAMES[i],
links: [...layers],
nodes: [...layers]
})
}
return {
name: '120-cell layered',
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
},
nolink2opacity: true,
options: options,
description: `This version of the 120-cell lets you explore its
structure by building each layer from the 'north pole' onwards.`,
}
}
const cell120_some_inscribed = (ps) => {
export const cell120_inscribed = () => {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
label_120cell(nodes);
for( const cstr in CELLINDEX.INDEX120 ) {
label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr));
}
const all_links = links;
all_links.map((l) => l.label = 0);
links.map((l) => l.label = 0);
for( const p of ps) {
for( const p of [ 1, 2, 3, 4, 5 ]) {
const nodes600 = nodes.filter((n) => n.label === p);
const links600 = auto_detect_edges(nodes600, 12);
links600.map((l) => l.label = p);
all_links.push(...links600);
links.push(...links600);
}
return {
name: '120-cell',
nodes: nodes,
links: all_links,
geometry: {
node_size: 0.02,
link_size: 0.02
},
links: links,
options: [
{ name: "none", links: [ 0 ]},
{ name: "one inscribed 600-cell", links: [ 0, 1 ] },
{ name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] }
],
description: `The 120-cell is the four-dimensional analogue of the
dodecahedron, and consists of 120 dodecahedra joined at 720 faces,
with three dodecahedra around each edge. It is dual to the 600-cell,
and five 600-cells can be inscribed in its vertices.`,
}
}
export const cell120_inscribed = () => cell120_some_inscribed([1]);
export const cell120_all_inscribed = () => cell120_some_inscribed([1,2,3,4,5]);
// Schoute's partition via https://arxiv.org/abs/1010.4353
const partition600 = {
"2,0,0,0": 1,
"0,2,0,0": 1,
"0,0,2,0": 1,
"0,0,0,2": 1,
"1,1,1,1": 1,
"1,1,-1,-1": 1,
"1,-1,1,-1": 1,
"1,-1,-1,1": 1,
"1,-1,-1,-1": 1,
"1,-1,1,1": 1,
"1,1,-1,1": 1,
"1,1,1,-1": 1,
"k,0,-t,-1": 2,
"0,k,1,-t": 2,
"t,-1,k,0": 2,
"1,t,0,k": 2,
"t,k,0,-1": 2,
"1,0,k,t": 2,
"k,-t,-1,0": 2,
"0,1,-t,k": 2,
"1,k,t,0": 2,
"t,0,-1,k": 2,
"0,t,-k,-1": 2,
"k,-1,0,-t": 2,
"t,0,1,k": 3,
"0,t,-k,1": 3,
"1,-k,-t,0": 3,
"k,1,0,-t": 3,
"0,k,1,t": 3,
"t,1,-k,0": 3,
"k,0,t,-1": 3,
"1,-t,0,k": 3,
"t,-k,0,-1": 3,
"0,1,-t,-k": 3,
"1,0,-k,t": 3,
"k,t,1,0": 3,
"t,0,-1,-k": 4,
"0,t,k,-1": 4,
"1,-k,t,0": 4,
"k,1,0,t": 4,
"t,1,k,0": 4,
"0,k,-1,-t": 4,
"1,-t,0,-k": 4,
"k,0,-t,1": 4,
"0,1,t,k": 4,
"t,-k,0,1": 4,
"k,t,-1,0": 4,
"1,0,k,-t": 4,
"k,0,t,1": 5,
"0,k,-1,t": 5,
"t,-1,-k,0": 5,
"1,t,0,-k": 5,
"1,0,-k,-t": 5,
"t,k,0,1": 5,
"0,1,t,-k": 5,
"k,-t,1,0": 5,
"t,0,1,-k": 5,
"1,k,-t,0": 5,
"k,-1,0,t": 5,
"0,t,k,1": 5
};
function partition_coord(i, coords, invert) {
@ -553,7 +481,7 @@ function map_coord(i, coords, values) {
}
function make_600cell_vertices() {
export function make_600cell_vertices() {
const coords = {
0: '0',
1: '1',
@ -577,7 +505,7 @@ function make_600cell_vertices() {
].flat();
for( const n of nodes ) {
n.label = label_vertex(n, coords, partition600);
n.label = label_vertex(n, coords, CELLINDEX.PARTITION600);
}
for( const n of nodes ) {
@ -588,7 +516,7 @@ function make_600cell_vertices() {
index_nodes(nodes);
scale_nodes(nodes, 0.75);
scale_nodes(nodes, 0.5);
return nodes;
}
@ -602,7 +530,6 @@ function get_node(nodes, id) {
}
function audit_link_labels(nodes, links) {
console.log("Link audit");
for( const l of links ) {
const n1 = get_node(nodes, l.source);
const n2 = get_node(nodes, l.target);
@ -618,45 +545,107 @@ export const cell600 = () => {
const nodes = make_600cell_vertices();
const links = auto_detect_edges(nodes, 12);
return {
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
}
}
}
links.map((l) => l.label = 0);
const cell600_some_inscribed = (ps) => {
const nodes = make_600cell_vertices();
const links = auto_detect_edges(nodes, 12);
const all_links = links;
all_links.map((l) => l.label = 0);
for( const p of ps) {
for( const p of [1, 2, 3, 4, 5]) {
const nodes24 = nodes.filter((n) => n.label === p);
const links24 = auto_detect_edges(nodes24, 8);
links24.map((l) => l.label = p);
all_links.push(...links24);
links.push(...links24);
}
return {
name: '600-cell',
nodes: nodes,
links: all_links,
geometry: {
node_size: 0.02,
link_size: 0.02
},
links: links,
options: [
{ name: "none", links: [ 0 ]},
{ name: "one 24-cell", links: [ 0, 1 ] },
{ name: "five 24-cells", links: [ 0, 1, 2, 3, 4, 5 ] }
],
description: `The 600-cell is the four-dimensional analogue of the
icosahedron, and consists of 600 tetrahedra joined at 1200 faces
with five tetrahedra around each edge. It is dual to the 120-cell.
Its 120 vertices can be partitioned into five sets which form the
vertices of five inscribed 24-cells.`,
}
}
export const cell600_inscribed = () => cell600_some_inscribed([1]);
export const cell600_all_inscribed = () => cell600_some_inscribed([1,2,3,4,5]);
export const cell600_layered = () => {
const nodes = make_600cell_vertices();
const links = auto_detect_edges(nodes, 12);
nodes.map((n) => n.label = 9); // make all invisible by default
for (const cstr in CELLINDEX.LAYERS600 ) {
label_nodes(nodes, CELLINDEX.LAYERS600[cstr], Number(cstr));
}
links.map((l) => {
const labels = link_labels(nodes, l);
if( labels[0] >= labels[1] ) {
l.label = labels[0];
} else {
l.label = labels[1];
}
});
const options = [];
const layers = [];
for( const i of [ 0, 1, 2, 3, 4, 5, 6, 7 ] ) {
layers.push(i);
options.push({
name: CELLINDEX.LAYER_NAMES[i],
links: [...layers],
nodes: [...layers]
})
}
return {
name: '600-cell layered',
nodes: nodes,
links: links,
nolink2opacity: true,
options: options,
description: `This version of the 600-cell lets you explore its
structure by building each layer from the 'north pole' onwards.`,
}
}
export const snub24cell = () => {
const nodes600 = make_600cell_vertices();
const links600 = auto_detect_edges(nodes600, 12);
const nodes = nodes600.filter((n) => n.label != 1);
const links = links600.filter((l) => {
const sn = node_by_id(nodes, l.source);
const tn = node_by_id(nodes, l.target);
return sn && tn;
});
links.map((l) => l.label = 0);
return {
name: 'Snub 24-cell',
nodes: nodes,
links: links,
options: [ { name: "--" } ],
description: `The snub 24-cell is a semiregular polytope which
connects the 24-cell with the 600-cell. It consists of 24 icosahedra
and 120 tetrahedra, and is constructed by removing one of the
five inscribed 24-cells from a 600-cell.`
}
}
@ -690,6 +679,7 @@ function make_dodecahedron_vertices() {
{ x: -phi, y: phiinv, z:0, w: 0 , label: 4},
{ x: -phi, y: -phiinv, z:0, w: 0 , label: 2},
];
scale_nodes(nodes, 1 / Math.sqrt(3));
index_nodes(nodes);
return nodes;
}
@ -697,41 +687,180 @@ function make_dodecahedron_vertices() {
export const dodecahedron = () => {
const nodes = make_dodecahedron_vertices();
const links = auto_detect_edges(nodes, 3);
links.map((l) => l.label = 0);
return {
nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
}
}
}
const dodecahedron_some_inscribed = (ps) => {
const nodes = make_dodecahedron_vertices();
const links = auto_detect_edges(nodes, 3);
const all_links = links;
all_links.map((l) => l.label = 0);
for( const p of ps) {
for( const p of [ 1, 2, 3, 4, 5 ]) {
const tetran = nodes.filter((n) => n.label === p);
const tetral = auto_detect_edges(tetran, 3);
tetral.map((l) => l.label = p);
all_links.push(...tetral);
links.push(...tetral);
}
return {
name: 'Dodecahedron',
nodes: nodes,
links: all_links,
geometry: {
node_size: 0.02,
link_size: 0.02
},
links: links,
options: [
{ name: "none", links: [ 0 ]},
{ name: "one tetrahedron", links: [ 0, 1 ] },
{ name: "five tetrahedra", links: [ 0, 1, 2, 3, 4, 5 ] }
],
description: `The dodecahedron is a three-dimensional polyhedron
which is included here so that you can see the partition of its
vertices into five interlocked tetrahedra. This structure is the
basis for the partition of the 120-cell's vertices into five
600-cells.`
}
}
export const dodecahedron_inscribed = () => dodecahedron_some_inscribed([1]);
export const dodecahedron_all_inscribed = () => dodecahedron_some_inscribed([1,2,3,4,5]);
export const tetrahedron = () => {
const r2 = Math.sqrt(2);
const r3 = Math.sqrt(3);
return {
name: 'Tetrahedron',
nodes: [
{id:1, label: 1, x: 2 * r2 / 3, y: 0, z: -1/3, w: 0 },
{id:2, label: 2, x: -r2 / 3, y: r2 / r3, z: -1/3, w: 0 },
{id:3, label: 3, x: -r2 / 3, y: -r2 / r3, z: -1/3, w: 0 },
{id:4, label: 4, x: 0, y: 0, z: 1, w: 0 },
],
links: [
{ id:1, source:1, target: 2},
{ id:2, source:1, target: 3},
{ id:3, source:1, target: 4},
{ id:4, source:2, target: 3},
{ id:5, source:2, target: 4},
{ id:6, source:3, target: 4},
],
options: [ { name: '--' }],
description: `The simplest three-dimensional polytope, consisting of four triangles joined at six edges. The 5-cell is its four-dimensional analogue.`,
};
};
export const octahedron = () => {
const nodes = [
{id: 1, label: 1, x: 1, y: 0, z: 0, w: 0},
{id: 2, label: 1, x: -1, y: 0, z: 0, w: 0},
{id: 3, label: 2, x: 0, y: 1, z: 0, w: 0},
{id: 4, label: 2, x: 0, y: -1, z: 0, w: 0},
{id: 5, label: 3, x: 0, y: 0, z: 1, w: 0},
{id: 6, label: 3, x: 0, y: 0, z: -1, w: 0},
];
const links = [
{id:1, source: 1, target: 3},
{id:2, source: 1, target: 4},
{id:3, source: 1, target: 5},
{id:4, source: 1, target: 6},
{id:5, source: 2, target: 3},
{id:6, source: 2, target: 4},
{id:7, source: 2, target: 5},
{id:8, source: 2, target: 6},
{id:9, source: 3, target: 5},
{id:10, source: 3, target: 6},
{id:11, source: 4, target: 5},
{id:12, source: 4, target: 6},
]
links.map((l) => { l.label = 0 });
return {
name: 'Octahedron',
nodes: nodes,
links: links,
options: [ { name: '--' }],
description: `The three-dimensional cross-polytope, the 16-cell is its four-dimensional analogue.`,
};
}
export const cube = () => {
const nodes = [
{id: 1, label: 1, x: 1, y: 1, z: 1, w: 0},
{id: 2, label: 2, x: -1, y: 1, z: 1, w: 0},
{id: 3, label: 2, x: 1, y: -1, z: 1, w: 0},
{id: 4, label: 1, x: -1, y: -1, z: 1, w: 0},
{id: 5, label: 2, x: 1, y: 1, z: -1, w: 0},
{id: 6, label: 1, x: -1, y: 1, z: -1, w: 0},
{id: 7, label: 1, x: 1, y: -1, z: -1, w: 0},
{id: 8, label: 2, x: -1, y: -1, z: -1, w: 0},
];
scale_nodes(nodes, 1/Math.sqrt(3));
const links = auto_detect_edges(nodes, 3);
links.map((l) => { l.label = 0 });
return {
name: 'Cube',
nodes: nodes,
links: links,
options: [ { name: '--' }],
description: `The three-dimensional measure polytope, the tesseract is its four-dimensional analogue.`,
};
}
function make_icosahedron_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5));
const nodes = [
{ x: 0, y: 1, z: phi, w: 0, label: 1 },
{ x: 0, y: -1, z: phi, w: 0, label: 1 },
{ x: 0, y: 1, z: -phi, w: 0, label: 1 },
{ x: 0, y: -1, z: -phi, w: 0, label: 1 },
{ x: 1, y: phi, z: 0, w: 0, label: 2 },
{ x: -1, y: phi, z: 0, w: 0, label: 2 },
{ x: 1, y: -phi, z: 0, w: 0, label: 2 },
{ x: -1, y: -phi, z: 0, w: 0, label: 2 },
{ x: phi, y: 0, z: 1, w: 0, label: 3},
{ x: phi, y: 0, z: -1, w: 0, label: 3},
{ x: -phi, y: 0, z: 1, w: 0, label: 3},
{ x: -phi, y: 0, z: -1, w: 0, label: 3},
];
scale_nodes(nodes, 1/Math.sqrt((5 + Math.sqrt(5)) / 2));
index_nodes(nodes);
return nodes;
}
export const icosahedron = () => {
const nodes = make_icosahedron_vertices();
const links = auto_detect_edges(nodes, 5);
links.map((l) => l.label = 0);
return {
name: 'Icosahedron',
nodes: nodes,
links: links,
options: [
{ name: "--"},
],
description: `The icosahedron is a twenty-sided polyhedron and is dual to the dodecahedron. Its four-dimensional analogue is the 600-cell.`
}
}
export const build_all = () => {
return [
tetrahedron(),
octahedron(),
cube(),
icosahedron(),
dodecahedron(),
cell5(),
cell16(),
tesseract(),
cell24(),
snub24cell(),
cell600(),
cell600_layered(),
cell120_inscribed(),
cell120_layered()
];
}
export const radii = (shape) => {
return shape.nodes.map(n => Math.sqrt(n.x * n.x + n.y * n.y + n.z * n.z + n.w * n.w))
}

View File

@ -81,24 +81,5 @@ export const rotfn = {
ZW: rotZW,
};
const rotMode = {
'rigid': [ rotYW, rotXW ],
'tumbling': [ rotYW, rotXZ ],
'inside-out': [ rotYW, rotXY ],
'axisymmetrical': [ rotZW, rotXY ]
};
export const get_rotation = (mode, theta, psi) => {
const fns = rotMode[mode];
return [ fns[0](theta), fns[1](psi) ];
}
// [
// rotfn[gui.params.xRotate](theta),
// rotfn[gui.params.yRotate](psi)
// ];

View File

@ -3,5 +3,14 @@
import { defineConfig, loadEnv } from 'vite';
export default defineConfig({
base: '/fourjs/'
base: '/fourjs/',
build: {
rollupOptions: {
output: {
manualChunks: {
threejs: [ 'three' ]
}
}
}
}
})