Compare commits

..

1 Commits

Author SHA1 Message Date
Mike Lynch c8ce499b22 Added dodecahedron with compound of five cubes, refactored the
inscribed polytopes into one function
2023-09-16 17:05:07 +10:00
13 changed files with 552 additions and 1449 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,20 +1,14 @@
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(); const colours = scheme.colors().map((cs) => parseInt('0x' + cs));
const hsl = colours.map((c) => Color("#" + c).hsl()); const set = colours.slice(1, 6);
const resaturated = hsl.map((hslc) => hslc.saturationl(saturation).rgbNumber()); set.reverse();
resaturated.unshift(basis); set.unshift(colours[0]);
return resaturated; return set;
} }
// basic colours where 0 = blue // basic colours where 0 = blue

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 };

95
gui.js
View File

@ -1,26 +1,20 @@
import { GUI } from 'lil-gui'; import { GUI } from 'lil-gui';
const DEFAULTS = { const DEFAULTS = {
nodesize: 0.25, thickness: 0.25,
nodeopacity: 1, nodesize: 1.25,
linksize: 0.2, linkopacity: 0.5,
linkopacity: 0.75, link2opacity: 0.5,
link2opacity: 0.75, shape: 'five-cubes',
shape: '120-cell',
option: 'none',
visibility: 5,
inscribed: false, inscribed: false,
inscribe_all: false, inscribe_all: false,
color: 0x3293a9, color: 0x3293a9,
background: 0xd4d4d4, background: 0xd4d4d4,
hyperplane: 0.93, hyperplane: 2,
zoom: 1, rotation: 'rigid',
xRotate: 'YW',
yRotate: 'XW',
dtheta: 0, dtheta: 0,
damping: false,
captions: true,
dpsi: 0, dpsi: 0,
} }
@ -28,78 +22,51 @@ const DEFAULTS = {
class FourDGUI { class FourDGUI {
constructor(shapes, changeShape, setColor, setBackground, setNodeOpacity,setLinkOpacity, setVisibility, showDocs) { constructor(changeShape, setColor, setBackground, setLinkOpacity) {
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;
this.params = { this.params = {
shape: this.link['shape'], shape: this.link['shape'],
option: this.link['option'],
inscribed: this.link['inscribed'], inscribed: this.link['inscribed'],
inscribe_all: this.link['inscribe_all'], inscribe_all: this.link['inscribe_all'],
linksize: this.link['linksize'], thickness: this.link['thickness'],
linkopacity: this.link['linkopacity'], linkopacity: this.link['linkopacity'],
link2opacity: this.link['linkopacity'], link2opacity: this.link['linkopacity'],
nodesize: this.link['nodesize'], nodesize: this.link['nodesize'],
nodeopacity: this.link['nodeopacity'],
depth: this.link['depth'],
color: this.link['color'], color: this.link['color'],
background: this.link['background'], background: this.link['background'],
hyperplane: this.link['hyperplane'], hyperplane: this.link['hyperplane'],
zoom: this.link['zoom'], rotation: this.link['rotation'],
xRotate: this.link['xRotate'],
yRotate: this.link['yRotate'],
damping: false, damping: false,
captions: true,
dtheta: this.link['dtheta'], dtheta: this.link['dtheta'],
dpsi: this.link['dpsi'], 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); [ 'dodecahedron', 'five-cubes', '5-cell', '16-cell', 'tesseract',
options_ctrl = options_ctrl.options(options).onChange((option) => { '24-cell', '600-cell', '120-cell' ]
setVisibility(option) ).onChange(changeShape)
}); this.gui.add(this.params, 'inscribed').onChange(changeShape);
options_ctrl.setValue(options[0]) this.gui.add(this.params, 'inscribe_all').onChange(changeShape);
changeShape(shape) this.gui.add(this.params, 'hyperplane', 1.5, 2.25);
}); this.gui.add(this.params, 'thickness', 0.1, 2);
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( this.gui.add(this.params, 'linkopacity', 0, 1).onChange(
(v) => setLinkOpacity(v, true) (v) => setLinkOpacity(v, true)
); );
this.gui.add(this.params, 'link2opacity', 0, 1).onChange( this.gui.add(this.params, 'link2opacity', 0, 1).onChange(
(v) => setLinkOpacity(v, false) (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, '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, 'rotation', [ 'rigid', 'tumbling', 'inside-out', 'axisymmetrical' ]);
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) { numParam(param, parser) {
const value = this.urlParams.get(param); const value = this.urlParams.get(param);
@ -128,7 +95,7 @@ class FourDGUI {
const guiObj = this; 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", "rotation" ]) {
const value = this.urlParams.get(param); const value = this.urlParams.get(param);
if( value ) { if( value ) {
this.link[param] = value; this.link[param] = value;
@ -140,12 +107,10 @@ class FourDGUI {
this.link[param] = ( this.urlParams.get(param) === 'y' ); this.link[param] = ( this.urlParams.get(param) === 'y' );
} }
this.link['hyperplane'] = this.numParam('hyperplane', parseFloat); this.link['hyperplane'] = this.numParam('hyperplane', parseFloat);
this.link['zoom'] = this.numParam('zoom', parseFloat); this.link['thickness'] = this.numParam('thickness', parseFloat);
this.link['linksize'] = this.numParam('linksize', parseFloat);
this.link['linkopacity'] = this.numParam('linkopacity', parseFloat); this.link['linkopacity'] = this.numParam('linkopacity', parseFloat);
this.link['link2opacity'] = this.numParam('link2opacity', parseFloat); this.link['link2opacity'] = this.numParam('link2opacity', parseFloat);
this.link['nodesize'] = this.numParam('nodesize', 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['color'] = this.numParam('color', (s) => guiObj.stringToHex(s));
this.link['background'] = this.numParam('background', (s) => guiObj.stringToHex(s)); this.link['background'] = this.numParam('background', (s) => guiObj.stringToHex(s));
this.link['dpsi'] = this.numParam('dpsi', parseFloat); this.link['dpsi'] = this.numParam('dpsi', parseFloat);
@ -156,20 +121,16 @@ class FourDGUI {
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("inscribed", this.params.inscribed ? 'y': 'n'); url.searchParams.append("inscribed", this.params.inscribed ? 'y': 'n');
url.searchParams.append("inscribe_all", this.params.inscribe_all ? 'y': 'n'); url.searchParams.append("inscribe_all", this.params.inscribe_all ? 'y': 'n');
url.searchParams.append("linksize", this.params.linksize.toString()); url.searchParams.append("thickness", this.params.thickness.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.thickness.toString());
url.searchParams.append("linkopacity", this.params.nodeopacity.toString()); url.searchParams.append("link2opacity", this.params.nodesize.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("rotation", this.params.rotation);
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("dtheta", this.params.dtheta.toString());
url.searchParams.append("dpsi", this.params.dpsi.toString()); url.searchParams.append("dpsi", this.params.dpsi.toString());
this.copyTextToClipboard(url); this.copyTextToClipboard(url);

View File

@ -5,15 +5,6 @@
<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 { div#info {
position: fixed; position: fixed;
bottom:0; bottom:0;
@ -25,8 +16,7 @@
</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> - <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> <a target="_blank" href="https://github.com/spikelynch/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);
}

143
main.js
View File

@ -3,13 +3,12 @@ import * as THREE from 'three';
import * as POLYTOPES from './polytopes.js'; import * as POLYTOPES from './polytopes.js';
import { rotfn } from './rotation.js'; import { get_rotation } from './rotation.js';
import { FourDGUI, DEFAULTS } from './gui.js'; import { FourDGUI, DEFAULTS } 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 );
@ -47,19 +43,11 @@ 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})); 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) => { link_ms.map((m) => {
m.transparent = true; m.transparent = true;
m.opacity = 0.5; m.opacity = 0.5;
} }
); )
const face_ms = [ const face_ms = [
new THREE.MeshLambertMaterial( { color: 0x44ff44 } ) new THREE.MeshLambertMaterial( { color: 0x44ff44 } )
@ -71,46 +59,54 @@ for( const face_m of face_ms ) {
} }
const STRUCTURES = POLYTOPES.build_all(); const STRUCTURES = {
'5-cell': POLYTOPES.cell5(),
'16-cell': POLYTOPES.cell16(),
'tesseract': POLYTOPES.tesseract(),
'24-cell': POLYTOPES.cell24(),
'dodecahedron': POLYTOPES.dodecahedron(),
'five-cubes': POLYTOPES.five_cubes(),
'120-cell': POLYTOPES.cell120(),
'600-cell': POLYTOPES.cell600(),
};
const STRUCTURES_BY_NAME = {}; 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(),
'five-cubes': POLYTOPES.five_cubes_inscribed(),
};
STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s); 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(),
'five-cubes': POLYTOPES.five_cubes_all_inscribed(),
}
let shape = false; let shape = false;
let structure = false;
let node_show = [];
let link_show = [];
function createShape(name, inscribed, all) {
function createShape(name, option) {
if( shape ) { if( shape ) {
scene.remove(shape); scene.remove(shape);
} }
structure = STRUCTURES_BY_NAME[name]; let structure = STRUCTURES[name];
if( inscribed ) {
if( name in INSCRIBED ) {
if( all ) {
structure = ALL_INSCRIBED[name];
} else {
structure = INSCRIBED[name];
}
}
}
shape = new FourDShape(node_ms, link_ms, face_ms, structure); shape = new FourDShape(node_ms, link_ms, face_ms, structure);
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
@ -132,51 +128,23 @@ function setBackground(c) {
} }
function setLinkOpacity(o, primary) { function setLinkOpacity(o, primary) {
if( structure.nolink2opacity ) {
link_ms.map((lm) => lm.opacity = o);
} else {
if( primary ) { if( primary ) {
link_ms[0].opacity = o; link_ms[0].opacity = o;
} else { } else {
link_ms.slice(1).map((lm) => lm.opacity = o); for( const lm of link_ms.slice(1) ) {
lm.opacity = o;
} }
} }
} }
function setNodeOpacity(o) { let gui; //
node_ms.map((nm) => nm.opacity = o);
}
let gui;
function changeShape() { function changeShape() {
createShape(gui.params.shape); console.log("change shape!")
displayDocs(gui.params.shape); createShape(gui.params.shape, gui.params.inscribed, gui.params.inscribe_all);
} }
function setVisibility(option_name) { gui = new FourDGUI(changeShape, setColors, setBackground, setLinkOpacity);
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 +187,7 @@ renderer.domElement.addEventListener("pointerup", (event) => {
dragging = false; dragging = false;
}) })
createShape(gui.params.shape, gui.params.option); createShape(gui.params.shape, gui.params.inscribed, gui.params.inscribe_all);
displayDocs(gui.params.shape);
function animate() { function animate() {
requestAnimationFrame( animate ); requestAnimationFrame( animate );
@ -234,18 +201,12 @@ function animate() {
} }
} }
const rotations = [ const rotations = get_rotation(gui.params.rotation, theta, psi);
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.node_scale = gui.params.nodesize;
shape.link_scale = gui.params.linksize * gui.params.nodesize * 0.5; shape.render3(rotations);
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: 1, 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: 2, 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: 3, 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: 4, x: -r2, y: -r2, z: r2, w: -r2 / r5 },
{id:5, label: 5, x: 0, y: 0, z: 0, w: 1 }, {id:5, label: 5, 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.`,
}; };
}; };
@ -104,18 +102,16 @@ export const cell16 = () => {
nodes[1].label = 4; nodes[1].label = 4;
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.`,
}; };
}; };
@ -133,37 +129,41 @@ export const tesseract = () => {
} }
} }
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 tesseract_some_inscribed = (ps) => {
const t = tesseract();
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 tesseract_inscribed = () => tesseract_some_inscribed([1]);
export const tesseract_all_inscribed = () => tesseract_some_inscribed([1,2]);
const CELL24_INDEXING = { const CELL24_INDEXING = {
x: { y: 1, z: 3, w: 2 }, x: { y: 1, z: 3, w: 2 },
y: { z: 2, w: 3 }, y: { z: 2, w: 3 },
@ -185,17 +185,9 @@ 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) => { // links.map((l) => {
// const ls = [ l.source, l.target ].map((nid) => node_by_id(nodes, nid).label); // const ls = [ l.source, l.target ].map((nid) => node_by_id(nodes, nid).label);
// for ( const c of [1, 2, 3] ) { // for ( const c of [1, 2, 3] ) {
@ -206,23 +198,35 @@ export const cell24 = () => {
// }); // });
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.`,
}; };
} }
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]);
@ -304,7 +308,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 +326,14 @@ 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);
label_120cell(nodes);
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 +341,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 +363,33 @@ 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) { 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 +397,126 @@ 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 ) { return {
label_nodes(nodes, CELLINDEX.LAYERS120[cstr], Number(cstr)); nodes: nodes,
links: links,
geometry: {
node_size: 0.02,
link_size: 0.02
},
}
} }
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 inscribed_polytope = (vertex_fn, edges, inscr_edges, parts) => {
const layers = []; const nodes = vertex_fn();
const links = auto_detect_edges(nodes, edges);
for( const i of [ 0, 1, 2, 3, 4, 5, 6, 7 ] ) { const all_links = links;
layers.push(i); all_links.map((l) => l.label = 0);
options.push({
name: CELLINDEX.LAYER_NAMES[i], for( const p of parts ) {
links: [...layers], const nodes_in = nodes.filter((n) => n.label === p);
nodes: [...layers] const links_in = auto_detect_edges(nodes_in, inscr_edges);
}) links_in.map((l) => l.label = p);
all_links.push(...links_in);
} }
return { return {
name: '120-cell layered',
nodes: nodes, nodes: nodes,
links: links, links: all_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.`, },
} }
} }
export const cell120_inscribed = () => inscribed_polytope(
make_120cell_vertices, 4, 12, [1]
);
export const cell120_all_inscribed = () => inscribed_polytope(
make_120cell_vertices, 4, 12, [1,2,3,4,5]
);
export const cell120_inscribed = () => { // Schoute's partition via https://arxiv.org/abs/1010.4353
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
for( const cstr in CELLINDEX.INDEX120 ) { const partition600 = {
label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr));
}
links.map((l) => l.label = 0); "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,
for( const p of [ 1, 2, 3, 4, 5 ]) { "k,0,-t,-1": 2,
const nodes600 = nodes.filter((n) => n.label === p); "0,k,1,-t": 2,
const links600 = auto_detect_edges(nodes600, 12); "t,-1,k,0": 2,
links600.map((l) => l.label = p); "1,t,0,k": 2,
links.push(...links600); "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,
return { "t,0,1,k": 3,
name: '120-cell', "0,t,-k,1": 3,
nodes: nodes, "1,-k,-t,0": 3,
links: links, "k,1,0,-t": 3,
options: [ "0,k,1,t": 3,
{ name: "none", links: [ 0 ]}, "t,1,-k,0": 3,
{ name: "one inscribed 600-cell", links: [ 0, 1 ] }, "k,0,t,-1": 3,
{ name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] } "1,-t,0,k": 3,
], "t,-k,0,-1": 3,
description: `The 120-cell is the four-dimensional analogue of the "0,1,-t,-k": 3,
dodecahedron, and consists of 120 dodecahedra joined at 720 faces, "1,0,-k,t": 3,
with three dodecahedra around each edge. It is dual to the 600-cell, "k,t,1,0": 3,
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 +558,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 +582,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);
} }
for( const n of nodes ) { for( const n of nodes ) {
@ -516,7 +593,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 +607,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,108 +623,22 @@ 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_inscribed = () => inscribed_polytope(
export const cell600_layered = () => { make_600cell_vertices, 12, 8, [1]
const nodes = make_600cell_vertices(); );
const links = auto_detect_edges(nodes, 12); export const cell600_all_inscribed = () => inscribed_polytope(
make_600cell_vertices, 12, 8, [1,2,3,4,5]
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() { function make_dodecahedron_vertices() {
@ -679,7 +671,6 @@ function make_dodecahedron_vertices() {
{ x: -phi, y: phiinv, z:0, w: 0 , label: 4}, { x: -phi, y: phiinv, z:0, w: 0 , label: 4},
{ x: -phi, y: -phiinv, z:0, w: 0 , label: 2}, { x: -phi, y: -phiinv, z:0, w: 0 , label: 2},
]; ];
scale_nodes(nodes, 1 / Math.sqrt(3));
index_nodes(nodes); index_nodes(nodes);
return nodes; return nodes;
} }
@ -687,180 +678,65 @@ function make_dodecahedron_vertices() {
export const dodecahedron = () => { export const dodecahedron = () => {
const nodes = make_dodecahedron_vertices(); const nodes = make_dodecahedron_vertices();
const links = auto_detect_edges(nodes, 3); 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 { return {
name: 'Dodecahedron',
nodes: nodes, nodes: nodes,
links: links, links: links,
options: [ geometry: {
{ name: "none", links: [ 0 ]}, node_size: 0.02,
{ name: "one tetrahedron", links: [ 0, 1 ] }, link_size: 0.02
{ 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 = () => inscribed_polytope(
make_dodecahedron_vertices, 3, 3, [1]
);
export const dodecahedron_all_inscribed = () => inscribed_polytope(
make_dodecahedron_vertices, 3, 3, [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 = () => { // this can't be done with inscribed_polytope because each vertex
const nodes = [ // belongs to two cubes
{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 dodecahedron_five_cubes = (parts) => {
const nodes = make_dodecahedron_vertices();
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); const links = auto_detect_edges(nodes, 3);
links.map((l) => { l.label = 0 }); const all_links = links;
return { all_links.map((l) => l.label = 0);
name: 'Cube',
nodes: nodes, const CUBES = {
links: links, 1: [ 1, 2, 3, 4, 5, 6, 7, 8 ],
options: [ { name: '--' }], 2: [ 4, 5, 10, 11, 13, 16, 17, 20],
description: `The three-dimensional measure polytope, the tesseract is its four-dimensional analogue.`, 3: [ 2, 7, 9, 12, 13, 16, 18, 19],
4: [ 1, 8, 10, 11, 14, 15, 18, 19],
5: [ 3, 6, 9, 12, 14, 15, 17, 20]
}; };
if( parts.length > 0 ) {
// console.log(parts);
//console.log(parts.map(String));
//nodes.map((n) => n.label = 0);
for( const label of parts.map(String) ) {
console.log(`label = ${label}`);
const cube = nodes.filter((n) => CUBES[label].includes(n.id));
const cubel = auto_detect_edges(cube, 3);
cubel.map((l) => l.label = Number(label));
all_links.push(...cubel);
} }
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 { return {
name: 'Icosahedron',
nodes: nodes, nodes: nodes,
links: links, links: all_links,
options: [ geometry: {
{ name: "--"}, node_size: 0.02,
], link_size: 0.02
description: `The icosahedron is a twenty-sided polyhedron and is dual to the dodecahedron. Its four-dimensional analogue is the 600-cell.` }
} }
} }
export const five_cubes = () => dodecahedron_five_cubes([]);
export const five_cubes_inscribed = () => dodecahedron_five_cubes([1]);
export const five_cubes_all_inscribed = () => dodecahedron_five_cubes([1,2,3,4,5]);
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,5 +81,24 @@ export const rotfn = {
ZW: rotZW, 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

@ -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,7 +895,7 @@ function meridian_label_120cell(nodes) {
//label_nodes(nodes, [313], 6); //label_nodes(nodes, [313], 6);
} }
1
function check_120cell_nodes(nodes) { function check_120cell_nodes(nodes) {
nodes.map((n) => { nodes.map((n) => {
const vs = find_adjacent_labels(nodes, links, n.id); const vs = find_adjacent_labels(nodes, links, n.id);
@ -1060,134 +913,39 @@ function make_dodecahedron_vertices() {
const phiinv = 1 / phi; const phiinv = 1 / phi;
const nodes = [ const nodes = [
{ x: 1, y: 1, z: 1, w: 0 }, { x: 1, y: 1, z: 1, w: 0, label: 4 },
{ x: 1, y: 1, z: -1, w: 0 }, { x: 1, y: 1, z: -1, w: 0, label: 3 },
{ x: 1, y: -1, z: 1, w: 0 }, { x: 1, y: -1, z: 1, w: 0, label: 3 },
{ x: 1, y: -1, z: -1, w: 0 }, { x: 1, y: -1, z: -1, w: 0, label: 2 },
{ x: -1, y: 1, z: 1, w: 0 },
{ x: -1, y: 1, z: -1, w: 0 }, { x: -1, y: 1, z: 1, w: 0, label: 3 },
{ x: -1, y: -1, z: 1, w: 0 }, { x: -1, y: 1, z: -1, w: 0, label: 1 },
{ x: -1, y: -1, z: -1, w: 0 } { x: -1, y: -1, z: 1, w: 0, label: 5 },
].flat(); { x: -1, y: -1, z: -1, w: 0, label: 3 },
scale_nodes(nodes, 0.5);
{ 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},
];
index_nodes(nodes);
return nodes; return nodes;
} }
// this one does the coherent indexing / partition into 600-cells // const nodes = make_120cell_vertices();
// const links = auto_detect_edges(nodes, 4);
export function make_labelled_120cell() { // const faces = auto_120cell_faces(links);
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") // console.log("Calculating 120-cell colours")

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' ]
}
}
}
}
}) })