Compare commits

..

No commits in common. "main" and "feature-120-cell-index" have entirely different histories.

13 changed files with 392 additions and 1644 deletions

View File

@ -1,188 +1 @@
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,29 +1,12 @@
import ColorScheme from 'color-scheme'; import ColorScheme from 'color-scheme';
import Color from 'color';
export const get_colours = (basis) => { 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 scheme = new ColorScheme;
scheme.from_hue(hue).scheme("tetrade").distance(0.75); const hexbasis = basis.toString(16).padStart(6, "0");
const colours = scheme.colors().slice(1, 9); scheme.from_hex(hexbasis).scheme("tetrade").variation("hard").distance(0.5);
colours.reverse(); return scheme.colors().map((cs) => parseInt('0x' + cs));
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
// 1 - dark blue
// 2 - white
// 3 - light cyan
// 4 - light orange
// 5 - dark orange
export const get_plain_colours = (basis) => { export const get_plain_colours = (basis) => {
return [ return [
basis, basis,

View File

@ -238,7 +238,3 @@ do the rest by hand
113 37 53 93 113 37 53 93
Another idea - look at colouring the vertices of a dodecahedron according to
the four compound tetrahedra and see if this can be repeated automatically to
neighbouring cells

View File

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

148
gui.js
View File

@ -1,107 +1,52 @@
import { GUI } from 'lil-gui'; import { GUI } from 'lil-gui';
const DEFAULTS = { const DEFAULT_SHAPE = '120-cell';
nodesize: 0.25, const DEFAULT_COLOR = 0x3293a9;
nodeopacity: 1, const DEFAULT_BG = 0x808080;
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: 0.93,
zoom: 1,
xRotate: 'YW',
yRotate: 'XW',
dtheta: 0,
damping: false,
captions: true,
dpsi: 0,
}
class FourDGUI { class FourDGUI {
constructor(shapes, changeShape, setColor, setBackground, setNodeOpacity,setLinkOpacity, setVisibility, showDocs) { constructor(createShape, setColor, setBackground) {
this.gui = new GUI(); this.gui = new GUI();
const SHAPE_NAMES = shapes.map((s) => s.name);
this.parseLinkParams(); this.parseLinkParams();
const guiObj = this; const guiObj = this;
console.log(this.link);
this.params = { this.params = {
shape: this.link['shape'], shape: this.link['shape'] || DEFAULT_SHAPE,
option: this.link['option'], thickness: this.link['thickness'] || 1,
inscribed: this.link['inscribed'], nodesize: this.link['nodesize'] || 2,
inscribe_all: this.link['inscribe_all'], color: this.link['color'] || DEFAULT_COLOR,
linksize: this.link['linksize'], background: this.link['background'] || DEFAULT_BG,
linkopacity: this.link['linkopacity'], hyperplane: this.link['hyperplane'] || 2,
link2opacity: this.link['linkopacity'], xRotate: this.link['xRotate'] || 'YW',
nodesize: this.link['nodesize'], yRotate: this.link['yRotate'] || 'XZ',
nodeopacity: this.link['nodeopacity'],
depth: this.link['depth'],
color: this.link['color'],
background: this.link['background'],
hyperplane: this.link['hyperplane'],
zoom: this.link['zoom'],
xRotate: this.link['xRotate'],
yRotate: this.link['yRotate'],
damping: false, damping: false,
captions: true, dtheta: this.link['dtheta'] || 0,
dtheta: this.link['dtheta'], dpsi: this.link['dpsi'] || 0,
dpsi: this.link['dpsi'],
"copy link": function () { guiObj.copyUrl() } "copy link": function () { guiObj.copyUrl() }
}; };
let options_ctrl;
this.gui.add(this.params, 'shape', SHAPE_NAMES).onChange((shape) => { this.gui.add(this.params, 'shape',
const options = this.getShapeOptions(shapes, shape); [ '5-cell', '16-cell', 'tesseract', '24-cell', '600-cell', '120-cell' ]
options_ctrl = options_ctrl.options(options).onChange((option) => { ).onChange(createShape)
setVisibility(option)
}); this.gui.add(this.params, 'hyperplane', 1.5, 3);
options_ctrl.setValue(options[0]) this.gui.add(this.params, 'thickness', 0.1, 4);
changeShape(shape) this.gui.add(this.params, 'nodesize', 0, 5);
});
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.addColor(this.params, 'color').onChange(setColor); this.gui.addColor(this.params, 'color').onChange(setColor);
this.gui.addColor(this.params, 'background').onChange(setBackground); this.gui.addColor(this.params, 'background').onChange(setBackground);
this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]); this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]);
this.gui.add(this.params, 'yRotate', [ 'XZ', 'XY', 'XW' ]); 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, 'damping');
this.gui.add(this.params, 'copy link'); 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, dft) {
numParam(param, parser) {
const value = this.urlParams.get(param); const value = this.urlParams.get(param);
if( value ) { if( value ) {
const n = parser(value); const n = parser(value);
@ -109,7 +54,7 @@ class FourDGUI {
return n; return n;
} }
} }
return DEFAULTS[param]; return dft;
} }
stringToHex(cstr) { stringToHex(cstr) {
@ -125,49 +70,38 @@ class FourDGUI {
parseLinkParams() { parseLinkParams() {
this.linkUrl = new URL(window.location.toLocaleString()); this.linkUrl = new URL(window.location.toLocaleString());
this.link = {}; this.link = {};
const guiObj = this;
this.urlParams = this.linkUrl.searchParams; this.urlParams = this.linkUrl.searchParams;
for( const param of [ "shape", "xRotate", "yRotate", "option" ]) { for( const param of [ "shape", "xRotate", "yRotate" ]) {
const value = this.urlParams.get(param); const value = this.urlParams.get(param);
if( value ) { if( value ) {
this.link[param] = value; this.link[param] = value;
} else {
this.link[param] = DEFAULTS[param];
} }
} }
for( const param of [ "inscribed", "inscribe_all"] ) { const guiObj = this;
this.link[param] = ( this.urlParams.get(param) === 'y' ); this.link['hyperplane'] = this.numParam('hyperplane', parseFloat, 2);
} this.link['thickness'] = this.numParam('thickness', parseFloat, 1);
this.link['hyperplane'] = this.numParam('hyperplane', parseFloat); this.link['nodesize'] = this.numParam('nodesize', parseFloat, 1);
this.link['zoom'] = this.numParam('zoom', parseFloat); this.link['color'] = this.numParam(
this.link['linksize'] = this.numParam('linksize', parseFloat); 'color', (s) => guiObj.stringToHex(s), DEFAULT_COLOR
this.link['linkopacity'] = this.numParam('linkopacity', parseFloat); );
this.link['link2opacity'] = this.numParam('link2opacity', parseFloat); this.link['background'] = this.numParam(
this.link['nodesize'] = this.numParam('nodesize', parseFloat); 'background', (s) => guiObj.stringToHex(s), DEFAULT_BG
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, 0);
this.link['dpsi'] = this.numParam('dpsi', parseFloat); this.link['dtheta'] = this.numParam('dtheta', parseFloat, 0);
this.link['dtheta'] = this.numParam('dtheta', parseFloat);
} }
copyUrl() { copyUrl() {
const url = new URL(this.linkUrl.origin + this.linkUrl.pathname); const url = new URL(this.linkUrl.origin + this.linkUrl.pathname);
url.searchParams.append("shape", this.params.shape); url.searchParams.append("shape", this.params.shape);
url.searchParams.append("option", this.params.option); url.searchParams.append("thickness", this.params.thickness.toString());
url.searchParams.append("inscribed", this.params.inscribed ? 'y': 'n');
url.searchParams.append("inscribe_all", this.params.inscribe_all ? 'y': 'n');
url.searchParams.append("linksize", this.params.linksize.toString());
url.searchParams.append("nodesize", this.params.nodesize.toString()); url.searchParams.append("nodesize", 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("color", this.hexToString(this.params.color));
url.searchParams.append("background", this.hexToString(this.params.background)); url.searchParams.append("background", this.hexToString(this.params.background));
url.searchParams.append("hyperplane", this.params.hyperplane.toString()); url.searchParams.append("hyperplane", this.params.hyperplane.toString());
url.searchParams.append("zoom", this.params.zoom.toString());
url.searchParams.append("xRotate", this.params.xRotate); url.searchParams.append("xRotate", this.params.xRotate);
url.searchParams.append("yRotate", this.params.yRotate); url.searchParams.append("yRotate", this.params.yRotate);
url.searchParams.append("dtheta", this.params.dtheta.toString()); url.searchParams.append("dtheta", this.params.dtheta.toString());
@ -217,4 +151,4 @@ class FourDGUI {
} }
export { FourDGUI, DEFAULTS }; export { FourDGUI };

View File

@ -5,28 +5,9 @@
<title>FourD</title> <title>FourD</title>
<style> <style>
body { margin: 0; } 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;
right: 0;
z-index: 2;
border:0.5em;
font-family: sans-serif }
</style> </style>
</head> </head>
<body> <body>
<script type="module" src="/main.js"></script> <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://git.tilde.town/bombinans/fourdjs">source</a></div>
</body> </body>
</html> </html>

View File

@ -1,173 +0,0 @@
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);
}

135
main.js
View File

@ -4,12 +4,11 @@ import * as THREE from 'three';
import * as POLYTOPES from './polytopes.js'; import * as POLYTOPES from './polytopes.js';
import { rotfn } from './rotation.js'; import { rotfn } from './rotation.js';
import { FourDGUI, DEFAULTS } from './gui.js'; import { FourDGUI } from './gui.js';
import { FourDShape } from './fourDShape.js'; import { FourDShape } from './fourDShape.js';
import { get_colours } from './colours.js'; import { get_colours } from './colours.js';
const FACE_OPACITY = 0.3; const FACE_OPACITY = 0.3;
const CAMERA_K = 5;
// scene, lights and camera // scene, lights and camera
@ -24,10 +23,7 @@ scene.add(light);
const amblight = new THREE.AmbientLight(0xffffff, 0.5); const amblight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(amblight); scene.add(amblight);
camera.position.set(0, 0, CAMERA_K / 2); camera.position.z = 4;
camera.lookAt(0, 0, 0);
//camera.position.z = 4;
const renderer = new THREE.WebGLRenderer({antialias: true}); const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setSize( window.innerWidth, window.innerHeight );
@ -35,30 +31,23 @@ document.body.appendChild( renderer.domElement );
// set up colours and materials for gui callbacks // set up colours and materials for gui callbacks
scene.background = new THREE.Color(DEFAULTS.background); scene.background = new THREE.Color(0x808080);
const material = new THREE.MeshStandardMaterial({ color: DEFAULTS.color }); const material = new THREE.MeshStandardMaterial({ color: 0x3293a9 });
const node_colours = get_colours(DEFAULTS.color); const node_colours = get_colours(0x3293a9);
console.log(node_colours);
material.transparent = true; material.transparent = true;
material.opacity = 0.5; material.opacity = 0.5;
const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c})); 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) => { node_ms[0].transparent = true;
m.transparent = true; node_ms[0].opacity = 0.0;
m.opacity = 1.0;
}
);
link_ms.map((m) => {
m.transparent = true;
m.opacity = 0.5;
}
);
const link_ms = [ material ];
const face_ms = [ const face_ms = [
@ -71,46 +60,24 @@ for( const face_m of face_ms ) {
} }
const STRUCTURES = POLYTOPES.build_all(); const STRUCTURES = {
'5-cell': POLYTOPES.cell5(),
const STRUCTURES_BY_NAME = {}; '16-cell': POLYTOPES.cell16(),
'tesseract': POLYTOPES.tesseract(),
STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s); '24-cell': POLYTOPES.cell24(),
'120-cell': POLYTOPES.cell120(),
'600-cell': POLYTOPES.cell600()
};
let shape = false; let shape = false;
let structure = false;
let node_show = [];
let link_show = [];
function createShape(name) {
function createShape(name, option) {
if( shape ) { if( shape ) {
scene.remove(shape); scene.remove(shape);
} }
structure = STRUCTURES_BY_NAME[name]; console.log(STRUCTURES[name]);
shape = new FourDShape(node_ms, link_ms, face_ms, structure); shape = new FourDShape(node_ms, link_ms, face_ms, STRUCTURES[name]);
scene.add(shape); 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 // initialise gui and read params from URL
@ -122,7 +89,6 @@ function setColors(c) {
const nc = get_colours(c); const nc = get_colours(c);
for( let i = 0; i < node_ms.length; i++ ) { for( let i = 0; i < node_ms.length; i++ ) {
node_ms[i].color = new THREE.Color(nc[i]); node_ms[i].color = new THREE.Color(nc[i]);
link_ms[i].color = new THREE.Color(nc[i]);
} }
material.color = new THREE.Color(c); material.color = new THREE.Color(c);
} }
@ -131,52 +97,8 @@ function setBackground(c) {
scene.background = new THREE.Color(c) scene.background = new THREE.Color(c)
} }
function setLinkOpacity(o, primary) {
if( structure.nolink2opacity ) {
link_ms.map((lm) => lm.opacity = o);
} else {
if( primary ) {
link_ms[0].opacity = o;
} else {
link_ms.slice(1).map((lm) => lm.opacity = o);
}
}
}
function setNodeOpacity(o) { const gui = new FourDGUI(createShape, setColors, setBackground);
node_ms.map((nm) => nm.opacity = o);
}
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 // these are here to pick up colour settings from the URL params
setColors(gui.params.color); setColors(gui.params.color);
@ -219,8 +141,7 @@ renderer.domElement.addEventListener("pointerup", (event) => {
dragging = false; dragging = false;
}) })
createShape(gui.params.shape, gui.params.option); createShape(gui.params.shape);
displayDocs(gui.params.shape);
function animate() { function animate() {
requestAnimationFrame( animate ); requestAnimationFrame( animate );
@ -238,14 +159,10 @@ function animate() {
rotfn[gui.params.xRotate](theta), rotfn[gui.params.xRotate](theta),
rotfn[gui.params.yRotate](psi) rotfn[gui.params.yRotate](psi)
]; ];
shape.hyperplane = 1 / gui.params.hyperplane; shape.hyperplane = gui.params.hyperplane;
camera.position.set(0, 0, gui.params.zoom * CAMERA_K * gui.params.hyperplane); shape.link_scale = gui.params.thickness;
shape.node_scale = gui.params.thickness * (gui.params.nodesize + 1);
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 ); renderer.render( scene, camera );
} }

270
package-lock.json generated
View File

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

View File

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

View File

@ -1,6 +1,6 @@
import * as PERMUTE from './permute.js'; import * as PERMUTE from './permute.js';
import * as CELLINDEX from './cellindex.js'; import * as CELL120 from './cellindex.js';
function index_nodes(nodes, scale) { function index_nodes(nodes, scale) {
let i = 1; 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; return (n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2;
} }
export function auto_detect_edges(nodes, neighbours, debug=false) { function auto_detect_edges(nodes, neighbours, debug=false) {
const seen = {}; const seen = {};
const nnodes = nodes.length; const nnodes = nodes.length;
const links = []; const links = [];
@ -58,15 +58,15 @@ export function auto_detect_edges(nodes, neighbours, debug=false) {
// too small and simple to calculate // too small and simple to calculate
export const cell5 = () => { export const cell5 = () => {
const c1 = Math.sqrt(5) / 4; const r5 = Math.sqrt(5);
const r2 = Math.sqrt(2) / 2;
return { return {
name: '5-cell',
nodes: [ nodes: [
{id:1, label: 1, x: c1, y: c1, z: c1, w: -0.25 }, {id:1, label: 0, x: r2, y: r2, z: r2, w: -r2 / r5 },
{id:2, label: 2, x: c1, y: -c1, z: -c1, w: -0.25 }, {id:2, label: 1, x: r2, y: -r2, z: -r2, w: -r2 / r5 },
{id:3, label: 3, x: -c1, y: c1, z: -c1, w: -0.25 }, {id:3, label: 2, x: -r2, y: r2, z: -r2, w: -r2 / r5 },
{id:4, label: 4, x: -c1, y: -c1, z: c1, w: -0.25 }, {id:4, label: 3, x: -r2, y: -r2, z: r2, w: -r2 / r5 },
{id:5, label: 5, x: 0, y: 0, z: 0, w: 1 }, {id:5, label: 4, x: 0, y: 0, z: 0, w: 4 * r2 / r5 },
], ],
links: [ links: [
{ id:1, source:1, target: 2}, { id:1, source:1, target: 2},
@ -80,12 +80,10 @@ export const cell5 = () => {
{ id:9, source:3, target: 5}, { id:9, source:3, target: 5},
{ id:10, source:4, target: 5}, { id:10, source:4, target: 5},
], ],
options: [ { name: '--' }], geometry: {
description: `Five tetrahedra joined at ten faces with three node_size: 0.02,
tetrahedra around each edge. The 5-cell is the simplest regular link_size: 0.02
four-D polytope and the four-dimensional analogue of the tetrahedron. }
A corresponding polytope, or simplex, exists for every n-dimensional
space.`,
}; };
}; };
@ -94,28 +92,26 @@ export const cell16 = () => {
let nodes = PERMUTE.coordinates([1, 1, 1, 1], 0); let nodes = PERMUTE.coordinates([1, 1, 1, 1], 0);
nodes = nodes.filter((n) => n.x * n.y * n.z * n.w > 0); nodes = nodes.filter((n) => n.x * n.y * n.z * n.w > 0);
nodes[0].label = 1; nodes[0].label = 0;
nodes[3].label = 2; nodes[3].label = 1;
nodes[5].label = 3; nodes[5].label = 2;
nodes[6].label = 4; nodes[6].label = 3;
nodes[7].label = 1; nodes[7].label = 0;
nodes[4].label = 2; nodes[4].label = 1;
nodes[2].label = 3; nodes[2].label = 2;
nodes[1].label = 4; nodes[1].label = 3;
index_nodes(nodes); index_nodes(nodes);
scale_nodes(nodes, 0.5); scale_nodes(nodes, 0.75);
const links = auto_detect_edges(nodes, 6); const links = auto_detect_edges(nodes, 6);
return { return {
name: '16-cell',
nodes: nodes, nodes: nodes,
links: links, links: links,
options: [ { name: '--' }], geometry: {
description: `Sixteen tetrahedra joined at 32 faces with four node_size: 0.02,
tetrahedra around each edge. The 16-cell is the four-dimensional link_size: 0.02
analogue of the octahedron and is dual to the tesseract. Every }
n-dimensional space has a corresponding polytope in this family.`,
}; };
}; };
@ -127,55 +123,32 @@ export const tesseract = () => {
for( const n of nodes ) { for( const n of nodes ) {
if( n.x * n.y * n.z * n.w > 0 ) { if( n.x * n.y * n.z * n.w > 0 ) {
n.label = 2;
} else {
n.label = 1; n.label = 1;
} }
} }
scale_nodes(nodes, 0.5); scale_nodes(nodes, Math.sqrt(2) / 2);
const links = auto_detect_edges(nodes, 4); const links = auto_detect_edges(nodes, 4);
links.map((l) => { l.label = 0 });
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);
links.push(...links16);
}
return { return {
name: 'Tesseract',
nodes: nodes, nodes: nodes,
links: links, links: links,
options: [ geometry: {
{ name: 'none', links: [ 0 ] }, node_size: 0.02,
{ name: 'one 16-cell', links: [ 0, 1 ] }, link_size: 0.02
{ 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.`,
}; };
} }
const CELL24_INDEXING = { const CELL24_INDEXING = {
x: { y: 1, z: 3, w: 2 }, x: { y: 0, z: 2, w: 1 },
y: { z: 2, w: 3 }, y: { z: 1, w: 2 },
z: { w: 1 } z: { w: 0 }
}; };
function node_by_id(nodes, nid) {
const ns = nodes.filter((n) => n.id === nid);
return ns[0];
}
export const cell24 = () => { export const cell24 = () => {
const nodes = PERMUTE.coordinates([0, 0, 1, 1], 0); const nodes = PERMUTE.coordinates([0, 0, 1, 1], 0);
@ -185,47 +158,20 @@ export const cell24 = () => {
n.label = CELL24_INDEXING[axes[0]][axes[1]]; n.label = CELL24_INDEXING[axes[0]][axes[1]];
} }
scale_nodes(nodes, Math.sqrt(2) / 2);
index_nodes(nodes); index_nodes(nodes);
const links = auto_detect_edges(nodes, 8); 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] ) {
// if( ! ls.includes(c) ) {
// l.label = c
// }
// }
// });
return { return {
name: '24-cell',
nodes: nodes, nodes: nodes,
links: links, links: links,
base: {}, geometry: {
options: [ node_size: 0.02,
{ name: 'none', links: [ 0 ] }, link_size: 0.02
{ 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.`,
}; };
} }
// face detection for the 120-cell // face detection for the 120-cell
// NOTE: all of these return node ids, not nodes // NOTE: all of these return node ids, not nodes
@ -304,7 +250,7 @@ function auto_120cell_faces(links) {
export function make_120cell_vertices() { function make_120cell_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5)); const phi = 0.5 * (1 + Math.sqrt(5));
const r5 = Math.sqrt(5); const r5 = Math.sqrt(5);
const phi2 = phi * phi; const phi2 = phi * phi;
@ -322,14 +268,12 @@ export function make_120cell_vertices() {
PERMUTE.coordinates([2, 1, phi, phiinv], 0, true), PERMUTE.coordinates([2, 1, phi, phiinv], 0, true),
].flat(); ].flat();
index_nodes(nodes); index_nodes(nodes);
scale_nodes(nodes, 0.25 * Math.sqrt(2)); scale_nodes(nodes, 0.5);
return nodes; return nodes;
} }
function label_nodes(nodes, ids, label) { function label_nodes(nodes, ids, label) {
nodes.filter((n) => ids.includes(n.id)).map((n) => n.label = label); nodes.filter((n) => ids.includes(n.id)).map((n) => n.label = label);
} }
@ -337,8 +281,16 @@ function label_nodes(nodes, ids, label) {
function label_faces_120cell(nodes, faces, cfaces, label) { function label_faces_120cell(nodes, faces, cfaces, label) {
const ns = new Set(); const ns = new Set();
console.log(`label faces from ${cfaces}`);
for( const fid of cfaces ) { for( const fid of cfaces ) {
const face = faces.filter((f)=> f.id === fid ); const face = faces.filter((f)=> f.id === fid );
if( face.length > 0 ) { if( face.length > 0 ) {
@ -351,6 +303,45 @@ 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;
}
}
// 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);
}
function label_120cell(nodes) {
for( const cstr in CELL120.INDEX ) {
label_nodes(nodes, CELL120.INDEX[cstr], Number(cstr));
}
}
function link_labels(nodes, link) { function link_labels(nodes, link) {
const n1 = nodes.filter((n) => n.id === link.source); const n1 = nodes.filter((n) => n.id === link.source);
const n2 = nodes.filter((n) => n.id === link.target); const n2 = nodes.filter((n) => n.id === link.target);
@ -358,88 +349,92 @@ function link_labels(nodes, link) {
} }
// version of the 120-cell where nodes are partitioned by
// layer and the links follow that
export const cell120_layered = (max) => { export const cell120 = () => {
const nodes = make_120cell_vertices(); const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4); const links = auto_detect_edges(nodes, 4);
nodes.map((n) => n.label = 9); // make all invisible by default label_120cell(nodes);
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 { return {
name: '120-cell layered',
nodes: nodes, nodes: nodes,
links: links, links: links,
nolink2opacity: true, geometry: {
options: options, node_size: 0.02,
description: `This version of the 120-cell lets you explore its link_size: 0.02
structure by building each layer from the 'north pole' onwards.`, },
} }
} }
// 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,
export const cell120_inscribed = () => { "k,0,-t,-1": 2,
const nodes = make_120cell_vertices(); "0,k,1,-t": 2,
const links = auto_detect_edges(nodes, 4); "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,
for( const cstr in CELLINDEX.INDEX120 ) { "t,0,1,k": 3,
label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr)); "0,t,-k,1": 3,
} "1,-k,-t,0": 3,
"k,1,0,-t": 3,
links.map((l) => l.label = 0); "0,k,1,t": 3,
"t,1,-k,0": 3,
for( const p of [ 1, 2, 3, 4, 5 ]) { "k,0,t,-1": 3,
const nodes600 = nodes.filter((n) => n.label === p); "1,-t,0,k": 3,
const links600 = auto_detect_edges(nodes600, 12); "t,-k,0,-1": 3,
links600.map((l) => l.label = p); "0,1,-t,-k": 3,
links.push(...links600); "1,0,-k,t": 3,
} "k,t,1,0": 3,
return {
name: '120-cell',
nodes: nodes,
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.`,
}
}
"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
};
@ -481,7 +476,7 @@ function map_coord(i, coords, values) {
} }
export function make_600cell_vertices() { function make_600cell_vertices() {
const coords = { const coords = {
0: '0', 0: '0',
1: '1', 1: '1',
@ -505,7 +500,7 @@ export function make_600cell_vertices() {
].flat(); ].flat();
for( const n of nodes ) { for( const n of nodes ) {
n.label = label_vertex(n, coords, CELLINDEX.PARTITION600); n.label = label_vertex(n, coords, partition600) - 1;
} }
for( const n of nodes ) { for( const n of nodes ) {
@ -516,7 +511,7 @@ export function make_600cell_vertices() {
index_nodes(nodes); index_nodes(nodes);
scale_nodes(nodes, 0.5); scale_nodes(nodes, 0.75);
return nodes; return nodes;
} }
@ -530,6 +525,7 @@ function get_node(nodes, id) {
} }
function audit_link_labels(nodes, links) { function audit_link_labels(nodes, links) {
console.log("Link audit");
for( const l of links ) { for( const l of links ) {
const n1 = get_node(nodes, l.source); const n1 = get_node(nodes, l.source);
const n2 = get_node(nodes, l.target); const n2 = get_node(nodes, l.target);
@ -545,322 +541,14 @@ export const cell600 = () => {
const nodes = make_600cell_vertices(); const nodes = make_600cell_vertices();
const links = auto_detect_edges(nodes, 12); const links = auto_detect_edges(nodes, 12);
links.map((l) => l.label = 0);
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);
links.push(...links24);
}
return { return {
name: '600-cell',
nodes: nodes, nodes: nodes,
links: links, links: links,
options: [ geometry: {
{ name: "none", links: [ 0 ]}, node_size: 0.02,
{ name: "one 24-cell", links: [ 0, 1 ] }, link_size: 0.02
{ 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_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.`
}
}
function make_dodecahedron_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5));
const phiinv = 1 / phi;
const nodes = [
{ x: 1, y: 1, z: 1, w: 0, label: 4 },
{ x: 1, y: 1, z: -1, w: 0, label: 3 },
{ x: 1, y: -1, z: 1, w: 0, label: 3 },
{ x: 1, y: -1, z: -1, w: 0, label: 2 },
{ x: -1, y: 1, z: 1, w: 0, label: 3 },
{ x: -1, y: 1, z: -1, w: 0, label: 1 },
{ x: -1, y: -1, z: 1, w: 0, label: 5 },
{ x: -1, y: -1, z: -1, w: 0, label: 3 },
{ x: 0, y: phi, z: phiinv, w: 0, label: 5 },
{ x: 0, y: phi, z: -phiinv, w: 0 , label: 2 },
{ x: 0, y: -phi, z: phiinv, w: 0, label: 4 },
{ x: 0, y: -phi, z: -phiinv, w: 0 , label: 1 },
{ x: phiinv, y: 0, z: phi, w: 0 , label: 2},
{ x: phiinv, y: 0, z: -phi, w: 0 , label: 4},
{ x: -phiinv, y: 0, z: phi, w: 0 , label: 1},
{ x: -phiinv, y: 0, z: -phi, w: 0 , label: 5},
{ x: phi, y: phiinv, z:0, w: 0 , label: 1},
{ x: phi, y: -phiinv, z:0, w: 0 , label: 5},
{ 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;
}
export const dodecahedron = () => {
const nodes = make_dodecahedron_vertices();
const links = auto_detect_edges(nodes, 3);
links.map((l) => l.label = 0);
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);
links.push(...tetral);
}
return {
name: 'Dodecahedron',
nodes: nodes,
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 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

@ -1,6 +1,5 @@
// code for generating the 120-cell labels //testbed for playing with stuff in node repl
// has some overlap with permute - FIXME
const THREE =require('three'); const THREE =require('three');
@ -134,7 +133,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; return (n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2;
} }
export function auto_detect_edges(nodes, neighbours, debug=false) { function auto_detect_edges(nodes, neighbours, debug=false) {
const seen = {}; const seen = {};
const nnodes = nodes.length; const nnodes = nodes.length;
const links = []; const links = [];
@ -169,7 +168,7 @@ export function auto_detect_edges(nodes, neighbours, debug=false) {
export function make_120cell_vertices() { function make_120cell_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5)); const phi = 0.5 * (1 + Math.sqrt(5));
const r5 = Math.sqrt(5); const r5 = Math.sqrt(5);
const phi2 = phi * phi; const phi2 = phi * phi;
@ -231,7 +230,7 @@ function fingerprint(ids) {
export function auto_120cell_faces(links) { function auto_120cell_faces(links) {
const faces = []; const faces = [];
const seen = {}; const seen = {};
let id = 1; let id = 1;
@ -487,7 +486,7 @@ function colour_next_dodeca_maybe(nodes, links, faces, colours, dd, nextf, nextd
const nextvs = dodecahedron_vertices(nextdd); const nextvs = dodecahedron_vertices(nextdd);
// get the initial colour permutations from the existing labels; // get the initial colour permutations from the existing labels;
const p = []; const p = [];
for( let i = 0; i < 5; i ++ ) { for( i = 0; i < 5; i ++ ) {
p[i] = colours[nextvs[i]]; p[i] = colours[nextvs[i]];
} }
const nlabels = colour_dodecahedron_from_face(nextdd, p); const nlabels = colour_dodecahedron_from_face(nextdd, p);
@ -530,33 +529,6 @@ 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) { function all_meridians(nodes, links, faces, startf, startn) {
const o = face_plus_to_dodecahedron(faces, startf, startn); const o = face_plus_to_dodecahedron(faces, startf, startn);
@ -696,9 +668,8 @@ function arctic(nodes, links, faces, startf, startn, max) {
// this is the final one that works for the whole 120-cell
export function label_120cell(nodes, links, faces, startf, startn) { function arctic_two(nodes, links, faces, startf, startn) {
const pole = face_plus_to_dodecahedron(faces, startf, startn); const pole = face_plus_to_dodecahedron(faces, startf, startn);
const dds = [ pole ]; const dds = [ pole ];
@ -717,6 +688,10 @@ export function label_120cell(nodes, links, faces, startf, startn) {
seen[dd_fingerprint(nextdd)] = true; 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 a of dds.slice(1, 13) ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) { for( const i of [ 6, 7, 8, 9, 10 ] ) {
@ -747,8 +722,6 @@ export function label_120cell(nodes, links, faces, startf, startn) {
} }
// the 30 equatorials? // the 30 equatorials?
for( const a of dds.slice(13, 46) ) { for( const a of dds.slice(13, 46) ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) { for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour( const [ nextdd, ncolours ] = follow_and_colour(
@ -777,10 +750,7 @@ export function label_120cell(nodes, links, faces, startf, startn) {
} }
} }
// this should get the rest or explode! // this should get the rest or explode!
for( const a of dds ) { for( const a of dds ) {
for( const i of [ 6, 7, 8, 9, 10 ] ) { for( const i of [ 6, 7, 8, 9, 10 ] ) {
const [ nextdd, ncolours ] = follow_and_colour( const [ nextdd, ncolours ] = follow_and_colour(
@ -808,124 +778,6 @@ export function label_120cell(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 // 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 // share this edge. These can be used as the starting points for the
// first face's two dodecahedra // first face's two dodecahedra
@ -963,6 +815,7 @@ function make_120cell_cells(faces) {
for( const dd of dds ) { for( const dd of dds ) {
const fp = dd_fingerprint(dd); const fp = dd_fingerprint(dd);
if( ! (fp in seen) ) { if( ! (fp in seen) ) {
//console.log(`added dodeca ${fp}`);
const d = { const d = {
id: i, id: i,
faces: dd, faces: dd,
@ -1042,166 +895,18 @@ function meridian_label_120cell(nodes) {
//label_nodes(nodes, [313], 6); //label_nodes(nodes, [313], 6);
} }
1
function check_120cell_nodes(nodes) {
nodes.map((n) => {
const vs = find_adjacent_labels(nodes, links, n.id);
vs.push(n.label);
console.log(`Node ${n.id} and neighbours ${vs}`);
const fp = vs.sort().join(',');
if( fp !== "1,2,3,4,5" ) {
console.log(`Label error ${n.id} ${fp}`)
}
});
}
function make_dodecahedron_vertices() {
const phi = 0.5 * (1 + Math.sqrt(5));
const phiinv = 1 / phi;
const nodes = [
{ x: 1, y: 1, z: 1, w: 0 },
{ x: 1, y: 1, z: -1, w: 0 },
{ x: 1, y: -1, z: 1, w: 0 },
{ x: 1, y: -1, z: -1, w: 0 },
{ x: -1, y: 1, z: 1, w: 0 },
{ x: -1, y: 1, z: -1, w: 0 },
{ x: -1, y: -1, z: 1, w: 0 },
{ x: -1, y: -1, z: -1, w: 0 }
].flat();
scale_nodes(nodes, 0.5);
return nodes;
}
// this one does the coherent indexing / partition into 600-cells
export function make_labelled_120cell() {
const nodes = make_120cell_vertices(); const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4); const links = auto_detect_edges(nodes, 4);
const faces = auto_120cell_faces(links); 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) { const a2 = arctic_two(nodes, links, faces, faces[0], 341)
dds.sort((a, b) => {
return dd_w_distance(nodes, a) - dd_w_distance(nodes, b) console.log(`got ${a2.dodecahedra.length}`);
})
} console.log(JSON.stringify(a2.labels));
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")
// const a2 = arctic_two(nodes, links, faces, faces[0], 341)
// console.log(`got ${a2.dodecahedra.length}`);
// const labels = a2.labels;
// console.log("labelling nodes");
// for( const cstr in labels ) {
// label_nodes(nodes, labels[cstr], Number(cstr));
// }

View File

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