Compare commits

..

No commits in common. "92c0fa6c27d079242958cac0173fde08c5e5911b" and "1bf3187f33ba035a1339001790f1d398ccc56323" have entirely different histories.

12 changed files with 106 additions and 2379 deletions

View File

@ -1,87 +0,0 @@
# 600-cell mapping
These are the nodes from a 120-cell which are on one of its inscribed 600-cells,
sorted into layers (just the first half because the second half mirror these ones)
Start: [ 27 ]
0: [ 27 ]
0.618:
[
223, 253, 265, 331,
339, 393, 419, 427,
473, 511, 539, 555
]
1
[
95, 105, 131, 140, 165, 179,
185, 207, 258, 274, 306,
313, 347, 367, 449, 471,
499, 527, 573, 585
]
1.175
[
231, 285, 289, 324,
378, 388, 413, 425,
487, 513, 543, 563
]
1.414
[
48, 49, 61, 68, 74, 87, 234,
239, 241, 248, 300, 301, 356, 357,
369, 376, 403, 406, 444, 453, 460,
469, 490, 503, 525, 532, 572, 581,
592, 593
]
## Manual mapping progress
Starting from 27 on the inscribed 600 cell and 1 on the primary 600 cell, here are
the two arctic circles
Pole: 27: 1
Arctic circle:
419: 41
223: 49
253: 45
331: 53
427: 109
339: 105
511: 51
265: 107
473: 111
539: 55
555: 43
393: 47
Next: for each face on this icosahedron, find the other vertex - these are the next 20
367
131
499
179
471
165
449
258
274
95
347
313
185
140
527
573
105
585
306
207
Then - for

View File

@ -1,12 +1,6 @@
CHANGELOG CHANGELOG
========= =========
## v1.3 - 7/2/2026
Went to inordinate lengths to apply the partition of the 600-cell (into five
24-cells) to the 5-cell inscription in the 120-cell, so that they could be coloured
in a way which reveals some of that symmetry.
## v1.2 - 18/1/2026 ## v1.2 - 18/1/2026
Added a second visualisation of the 120-cell's 5-cells without the 120-cell links Added a second visualisation of the 120-cell's 5-cells without the 120-cell links

View File

@ -1,15 +1,26 @@
# NOTES # NOTES
## Labelling the inscribed 600-cells in a 120-cell New approach for the 5-cells:
I want to apply the partition of the 600-cell into five 24-cells to the inscribed Pick a tetrahedron of an inscribed 600-cell with vertices A, B, C, D
600-cells in the 120-cell, so that I can use this partition to colour the inscribed
5-cells - since each 5-cell has a vertex in each of the 600-cells, getting a This gives pairs of vertices:
partition for just one will be enough.
AB
AC
AD
BC
BD
CD
Each of these gives rise to seven pairs of 5-cells which are on neighboring vertices
of the 5 600-cells.
Try enumerating these and inspecting them to find one or more coherent sets of four
5-cells which lie on one tetrahedron from each of the 600-cells.
(I expect there to be more than one, like how there are two ways to partition the
120-cell vertices into 600-cells)
The challenge is that the 600-cells in the 120-cell are rotated differently to the
coordinates for the original 600-cell. And I don't have enough maths to line them up.
What about - given one of the inscribed 600-cell, find all of the 24-cells? (there
are 25 possible ones so sorting them out will be a pain)

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,8 @@ export const get_colours = (basis) => {
const luminance = hslb['color'][2]; const luminance = hslb['color'][2];
const scheme = new ColorScheme; const scheme = new ColorScheme;
scheme.from_hue(hue).scheme("tetrade").distance(0.75); scheme.from_hue(hue).scheme("tetrade").distance(0.75);
const colours = scheme.colors(); const scolours = scheme.colors();
colours.reverse(); const colours = [ ...scolours, ...scolours, ...scolours, ...scolours, ...scolours, ...scolours, ...scolours, ...scolours ];
const hsl = colours.map((c) => Color("#" + c).hsl()); const hsl = colours.map((c) => Color("#" + c).hsl());
const resaturated = hsl.map((hslc) => hslc.saturationl(saturation).rgbNumber()); const resaturated = hsl.map((hslc) => hslc.saturationl(saturation).rgbNumber());
resaturated.unshift(basis); resaturated.unshift(basis);

View File

@ -452,103 +452,41 @@ function coherent_5cells(cell120, all5) {
function coherent_all() {
const cell120 = POLYTOPES.cell120_inscribed();
const all5 = gather_5cells(cell120);
const c5s = coherent_5cells(cell120, all5); const cell120 = POLYTOPES.cell120_inscribed();
const all5 = gather_5cells(cell120);
const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l])); const c5s = coherent_5cells(cell120, all5);
const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l]));
// check it because I don't believe it yet // check it because I don't believe it yet
const vertex_check = {}; const vertex_check = {};
for( const c5 of celli ) { for( const c5 of celli ) {
for( const l in c5 ) { for( const l in c5 ) {
const v = c5[l]; const v = c5[l];
if( v in vertex_check ) { if( v in vertex_check ) {
console.log(`Double count vertex ${v}`); console.log(`Double count vertex ${v}`);
}
vertex_check[v] = 1;
} }
vertex_check[v] = 1;
} }
for( let i = 1; i < 601; i++ ) {
if( !vertex_check[i] ) {
console.log(`v ${i} missing`);
}
}
const idict = {};
for( let i = 1; i < 121; i++ ) {
idict[i] = celli[i - 1];
}
console.log(JSON.stringify(idict, null, 2));
} }
for( let i = 1; i < 601; i++ ) {
if( !vertex_check[i] ) {
function coherent_one_set() { console.log(`v ${i} missing`);
const cell120 = POLYTOPES.cell120_inscribed();
const all5 = gather_5cells(cell120);
const c5ns = cell5_tetras(cell120, all5, all5[0]);
const celli = c5ns.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l]));
const idict = {};
for( let i = 0; i < celli.length; i++ ) {
idict[i + 1] = celli[i];
}
console.log(JSON.stringify(idict, null, 2));
}
function cell120_csv() {
const cell120 = POLYTOPES.cell120_inscribed();
const coords = [ 'x', 'y', 'z', 'w' ];
console.log("id,label,x,y,z,w,zeroes");
for( const n of cell120.nodes ) {
const zc = coords.filter((c) => n[c] === 0);
console.log(`${n.id},${n.label},${n.x},${n.y},${n.z},${n.w},${zc.length}`);
} }
} }
function cell600_links(cell600, n) { const idict = {};
const links = cell600.links.filter((l) => l.source === n.id || l.target === n.id); for( let i = 1; i < 121; i++ ) {
const nbors = links.map((l) => { idict[i] = celli[i - 1];
if( l.source === n.id ) {
return l.target;
} else {
return l.source;
}
});
return nbors.sort((a, b) => a - b);
} }
console.log(JSON.stringify(idict, null, 2));
function cell600_csv() {
const cell600 = POLYTOPES.cell600();
console.log("id,label,x,y,z,w,d");
const n0 = cell600.nodes[0];
for( const n of cell600.nodes ) {
const d = dist(n0, n);
const nbors = cell600_links(cell600, n);
const nids = nbors.join(',');
console.log(`${n.id},${n.label},${n.x},${n.y},${n.z},${n.w},${d},${nids}`);
}
}
cell600_csv();

16
gui.js
View File

@ -6,13 +6,13 @@ const DEFAULTS = {
nodeopacity: 1, nodeopacity: 1,
linksize: 1.0, linksize: 1.0,
linkopacity: 0.75, linkopacity: 0.75,
shape: '600-cell', shape: '120-cell',
link2opacity: 0.75, link2opacity: 0.75,
option: 'none', option: 'none',
visibility: 5, visibility: 5,
inscribed: false, inscribed: false,
inscribe_all: false, inscribe_all: false,
colour: 0x3293a9, color: 0x3293a9,
background: 0xd4d4d4, background: 0xd4d4d4,
hyperplane: 0.93, hyperplane: 0.93,
zoom: 1, zoom: 1,
@ -46,7 +46,7 @@ class FourDGUI {
nodesize: this.link['nodesize'], nodesize: this.link['nodesize'],
nodeopacity: this.link['nodeopacity'], nodeopacity: this.link['nodeopacity'],
depth: this.link['depth'], depth: this.link['depth'],
colour: this.link['colour'], color: this.link['color'],
background: this.link['background'], background: this.link['background'],
hyperplane: this.link['hyperplane'], hyperplane: this.link['hyperplane'],
zoom: this.link['zoom'], zoom: this.link['zoom'],
@ -60,6 +60,8 @@ class FourDGUI {
}; };
if( funcs.extras ) { if( funcs.extras ) {
for( const label in funcs.extras ) { for( const label in funcs.extras ) {
console.log(label);
console.log(funcs.extras[label]);
this.params[label] = funcs.extras[label]; this.params[label] = funcs.extras[label];
} }
} }
@ -81,9 +83,10 @@ class FourDGUI {
this.gui.add(this.params, 'nodesize', 0, 1.5); this.gui.add(this.params, 'nodesize', 0, 1.5);
this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(funcs.setNodeOpacity); this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(funcs.setNodeOpacity);
this.gui.add(this.params, 'linksize', 0, 2); this.gui.add(this.params, 'linksize', 0, 2);
console.log(funcs.setLinkOpacity);
this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, true)); this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, true));
this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, false)); this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, false));
this.gui.addColor(this.params, 'colour').onChange(funcs.setColours); this.gui.addColor(this.params, 'color').onChange(funcs.setColor);
this.gui.addColor(this.params, 'background').onChange(funcs.setBackground); this.gui.addColor(this.params, 'background').onChange(funcs.setBackground);
this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]); this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]);
this.gui.add(this.params, 'yRotate', [ 'XZ', 'XY', 'XW' ]); this.gui.add(this.params, 'yRotate', [ 'XZ', 'XY', 'XW' ]);
@ -152,7 +155,7 @@ class FourDGUI {
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['nodeopacity'] = this.numParam('nodeopacity', parseFloat);
this.link['colour'] = this.numParam('colour', (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);
this.link['dtheta'] = this.numParam('dtheta', parseFloat); this.link['dtheta'] = this.numParam('dtheta', parseFloat);
@ -169,7 +172,7 @@ class FourDGUI {
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("nodeopacity", this.params.nodesize.toString());
url.searchParams.append("linkopacity", this.params.nodeopacity.toString()); url.searchParams.append("linkopacity", this.params.nodeopacity.toString());
url.searchParams.append("colour", this.hexToString(this.params.colour)); 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("zoom", this.params.zoom.toString());
@ -187,6 +190,7 @@ class FourDGUI {
return; return;
} }
navigator.clipboard.writeText(text).then(function() { navigator.clipboard.writeText(text).then(function() {
console.log('Async: Copying to clipboard was successful!');
}, function(err) { }, function(err) {
console.error('Async: Could not copy text: ', err); console.error('Async: Could not copy text: ', err);
}); });

View File

@ -36,7 +36,7 @@
<script type="module" src="/main.js"></script> <script type="module" src="/main.js"></script>
<div id="description"></div> <div id="description"></div>
<div id="release_notes"></div> <div id="release_notes"></div>
<div id="info"><a href="#" id="show_notes">release 1.3</a> | <div id="info"><a href="#" id="show_notes">release 1.2</a> |
by <a target="_blank" href="https://mikelynch.org/">Mike Lynch</a> | 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://git.tilde.town/bombinans/fourdjs">source</a></div>

View File

@ -1,409 +0,0 @@
import * as POLYTOPES from './polytopes.js';
import * as CELLINDEX from './cellindex.js';
// script to help me label the vertices of one of the inscribed 600-cells of a 120-cell
// with Schoute's partition (which is used to label the main 600-cell)
function choice(a) {
const r = Math.floor(Math.random() * a.length);
return a[r];
}
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;
}
}
export function dist(n1, n2) {
return Math.sqrt((n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2);
}
function round_dist(raw) {
return Math.floor(raw * 1000) / 1000;
}
export function make_one_600cell() {
const nodes = POLYTOPES.make_120cell_vertices();
const links = POLYTOPES.auto_detect_edges(nodes, 4);
for( const cstr in CELLINDEX.INDEX120 ) {
POLYTOPES.label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr));
}
links.map((l) => l.label = 0);
const nodes600 = nodes.filter((n) => n.label === 1);
const links600 = POLYTOPES.auto_detect_edges(nodes600, 12);
links600.map((l) => l.label = 1);
return {
nodes: nodes600,
links: links600
}
}
export function base_600cell() {
const nodes = POLYTOPES.make_600cell_vertices();
const links = POLYTOPES.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);
}
return {
nodes: nodes,
links: links,
};
}
export function distance_groups(shape) {
// get list of other nodes by distance
// sort them and dump them out
const dists = {};
shape.nodes.map((n) => {
const draw = dist(shape.nodes[0], n);
const dtrunc = round_dist(draw);
if( !(dtrunc in dists) ) {
dists[dtrunc] = [];
}
dists[dtrunc].push(n.id);
});
return dists;
}
export function insc600_layers(cell600) {
const layers = distance_groups(cell600);
/* const sorted = Object.keys(layers).sort((a,b) => a - b);
for( const d of sorted ) {
const ids = layers[d].map((n) => n.id);
console.log(`Layer at distance ${d}`);
console.log(ids);
} */
return layers;
}
export function neighbours(shape, nid) {
const links = shape.links.filter((l) => l.source === nid || l.target == nid );
const nodes = links.map((l) => {
if( l.source === nid ) {
return l.target;
} else {
return l.source;
}
});
return nodes;
}
export function neighbours_in_subset(shape, subset, nid) {
// shape = nodes, links
// subset = a list of ids
// n = an id
// returns all of n's neighbours which are in subset
const all_nbors = neighbours(shape, nid);
return all_nbors.filter((n) => subset.includes(n));
}
export function face_vertices(shape, f1, f2, f3) {
// for f1/f2/f3 forming a triangular face, return the two vertices of the
// adjacent tetrahedra
const n1 = neighbours(shape, f1).filter((n) => n !== f2 && n !== f3);
const n2 = neighbours(shape, f2).filter((n) => n !== f1 && n !== f3);
const n3 = neighbours(shape, f3).filter((n) => n !== f1 && n !== f2);
const ns = n1.filter((n) => n2.includes(n) && n3.includes(n));
return ns;
}
export function shared_neighbours(shape, nodes) {
let ns = shape.nodes.map((n) => n.id);
for( const n of nodes ) {
ns = neighbours_in_subset(shape, ns, n);
}
return ns;
}
export function layer_neighbours(cell600, layer) {
console.log("Layer neighbours");
for( const n of layer ) {
console.log(`n = ${n}`);
const nbors = neighbours_in_subset(cell600, layer, n);
console.log(` Vertex ${n} neighbours: ` + JSON.stringify(nbors));
}
}
const ARCTIC_I_FACES = [
[ 419, 223, 253 ],
[ 419, 253, 331 ],
[ 419, 331, 427 ],
[ 419, 427, 339 ],
[ 419, 339, 223 ],
[ 253, 223, 265 ],
[ 331, 253, 473 ],
[ 427, 331, 539 ],
[ 339, 427, 555 ],
[ 511, 339, 223 ],
[ 223, 511, 265 ],
[ 253, 265, 473 ],
[ 331, 473, 539 ],
[ 427, 539, 555 ],
[ 339, 555, 511 ],
[ 393, 265, 511 ],
[ 393, 473, 265 ],
[ 393, 539, 473 ],
[ 393, 555, 539 ],
[ 393, 555, 511 ]
];
export const ARCTIC_FACES = ARCTIC_I_FACES.map((f) => {
return f.map((nid) => CELLINDEX.CELL600_METAMAP[nid]);
});
export const TEMPERATE_PENTAGONS_I = [
[ 499, 179, 471, 367, 131 ],
[ 131, 367, 165, 313, 449 ],
[ 131, 449, 185, 258, 499 ],
[ 499, 258, 140, 274, 179 ],
[ 179, 274, 527, 95, 471 ],
[ 471, 95, 347, 165, 367 ],
[ 347, 573, 105, 313, 165 ],
[ 313, 105, 585, 185, 449 ],
[ 185, 585, 306, 140, 258 ],
[ 140, 306, 207, 527, 274 ],
[ 527, 207, 573, 347, 95 ],
[ 105, 573, 207, 306, 585 ],
];
export const TEMPERATE_PENTAGONS = TEMPERATE_PENTAGONS_I.map((f) => {
return f.map((nid) => CELLINDEX.CELL600_METAMAP[nid])
});
export function layer_two(cell600, centre, faces) {
for ( const face of faces ) {
const n2 = face_vertices(cell600, face[0], face[1], face[2]);
console.log(face, n2);
}
}
export function layer_three(shape, pentagons) {
for ( const pentagon of pentagons ) {
console.log(pentagon);
const s = shared_neighbours(shape, pentagon);
console.log(s);
console.log("\n");
}
}
export const TEMPERATE_APICES = [
563,
513,
285,
324,
231,
487,
413,
425,
378,
388,
543,
289,
];
// this one generates the mapping to the base 600 cell as well, unlike
// previous versions where I did the mapping by hand
export function equator(i600, b600, apices) {
const pairs = [];
// get all 30 of the edges on the temperate dodeca
for( let i = 0; i < 11; i++ ) {
for( let j = i + 1; j < 12; j++ ) {
const s = shared_neighbours(i600, [ apices[i], apices[j] ]);
if( s.length > 0 ) {
const e = s.filter((n) => !(n in CELLINDEX.CELL600_METAMAP));
pairs.push([apices[i], apices[j], e]);
}
}
}
const MAPPED = Object.values(CELLINDEX.CELL600_METAMAP);
const eq = {};
for( const pair of pairs ) {
const b1 = CELLINDEX.CELL600_METAMAP[pair[0]];
const b2 = CELLINDEX.CELL600_METAMAP[pair[1]];
const s = shared_neighbours(b600, [ b1, b2 ]);
const e = s.filter((n) => !MAPPED.includes(n));
if( e.length !== 1 ) {
console.log(`Bad value at ${pair}`);
} else {
eq[pair[2]] = e[0];
}
}
return eq;
}
export function antipode(shape, nid) {
const n0 = shape.nodes.filter((n) => n.id === nid)[0];
if( !n0 ) {
throw new Error(`antipodes error: couldn't find node ${nid} in shape`);
}
const dists = shape.nodes.map((n) => [ dist(n, n0), n ]);
dists.sort((a, b) => b[0] - a[0]);
return dists[0][1];
}
export function check_antipodes() {
const c600 = base_600cell();
const seen = {};
c600.nodes.map((n) => {
const a = antipode(c600, n.id);
if( !seen[a.id] && !seen[n.id] ) {
seen[a.id] = true;
seen[n.id] = true;
console.log(`${n.id} - ${n.label} / ${a.id} - ${a.label}`);
if( n.label !== a.label ) {
console.lot("MISMATCH");
}
}
});
}
export function meta600_label(b600, iid) {
const bid = CELLINDEX.CELL600_METAMAP[iid];
const bn = b600.nodes.filter((n) => bid === n.id);
return bn[0].label;
}
export function map_antipodes() {
const b600 = base_600cell();
const i600 = make_one_600cell();
const already = [];
const antimap = {};
for( const inid in CELLINDEX.CELL600_METAMAP ) {
const bnid = CELLINDEX.CELL600_METAMAP[inid];
const banti = antipode(b600, Number(bnid));
const ianti = antipode(i600, Number(inid));
if( CELLINDEX.CELL600_METAMAP[ianti.id] ) {
//console.log(`Anti ${ianti.id} is already mapped`);
already.push(ianti.id);
const l1 = meta600_label(b600, inid);
const l2 = meta600_label(b600, Number(ianti.id));
//console.log(`labels: ${l1} ${l2}`);
} else {
antimap[ianti.id] = banti.id;
}
}
console.log(JSON.stringify(antimap, null, 2));
}
export function check_metamap_completeness() {
const b600 = base_600cell();
const i600 = make_one_600cell();
const labels = {};
const bids = {};
const mm = CELLINDEX.CELL600_METAMAP;
for( const i of i600.nodes ) {
if( i.id in mm ) {
const ml = meta600_label(b600, i.id);
if( !(ml in labels) ) {
labels[ml] = [];
}
labels[ml].push(i.id);
bids[mm[i.id]] = 1;
} else {
console.log(`inscribed node ${i.id} is not in metamap`);
}
}
for( const b of b600.nodes ) {
if( !(b.id in bids) ) {
console.log(`base mode ${b.id} is not mapped`);
}
}
for ( const label in labels ) {
console.log(`label ${label} has ${labels[label].length} nodes`);
}
}
// this gives a mapping from cell-120-ids of one inscribed 600-cell to the
// metamap labels, which I can then [checks notes] use to colour the 5-cells.
export function metamap_to_labels() {
const b600 = base_600cell();
const i600 = make_one_600cell();
const mapping = {};
for( const inode of i600.nodes ) {
mapping[inode.id] = meta600_label(b600, inode.id);;
}
return mapping;
}
export function cell5_labels() {
const labels = metamap_to_labels();
// now build a dict of the 120 cell5s with the colours from the above
const cell5map = {};
const CELL5S = CELLINDEX.CELL120_CELL5.cell5s;
for( const c5i in CELL5S ) {
const n1 = CELL5S[c5i][0]; // label 1 node;
const ml = labels[n1];
cell5map[c5i] = ml;
}
return cell5map;
}
export function rebuild_cell5_index() {
const labels = metamap_to_labels();
const new_cell5s = {};
const CELL5S = CELLINDEX.CELL120_CELL5;
for( const c5i in CELL5S ) {
const n1 = CELL5S[c5i][0]; // label 1 node;
const ml = labels[n1];
new_cell5s[c5i] = {
nodes: CELL5S[c5i],
label: ml
}
}
return new_cell5s;
}
const nc5 = rebuild_cell5_index();
console.log(JSON.stringify(nc5, null, 4));

20
main.js
View File

@ -1,12 +1,6 @@
import * as THREE from 'three'; import * as THREE from 'three';
const RELEASE_NOTES = ` const RELEASE_NOTES = `
<p><b>v1.3 - 7/2/2026</b></p>
<p>Went to inordinate lengths to apply the partition of the 600-cell (into five
24-cells) to the 5-cell inscription in the 120-cell, so that they could be coloured
in a way which reveals some of that symmetry.</p>
<p><b>v1.2 - 18/1/2026</b></p> <p><b>v1.2 - 18/1/2026</b></p>
<p>Added a second visualisation of the 120-cell's 5-cells without the 120-cell links and with more colours added so you can get a sense of the individual 5-cells.</p> <p>Added a second visualisation of the 120-cell's 5-cells without the 120-cell links and with more colours added so you can get a sense of the individual 5-cells.</p>
@ -66,8 +60,7 @@ document.body.appendChild( renderer.domElement );
// set up colours and materials for gui callbacks // set up colours and materials for gui callbacks
scene.background = new THREE.Color(DEFAULTS.background); scene.background = new THREE.Color(DEFAULTS.background);
const node_colours = get_colours(DEFAULTS.colour); const node_colours = get_colours(DEFAULTS.color);
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}));
@ -84,6 +77,7 @@ link_ms.map((m) => {
} }
); );
console.log("link_ms", link_ms);
const face_ms = [ const face_ms = [
@ -161,7 +155,7 @@ relnotes.addEventListener('click', releaseNotes);
// callbacks to do things which are triggered by controls: reset the shape, // callbacks to do things which are triggered by controls: reset the shape,
// change the colors. Otherwise we just read stuff from gui.params. // change the colors. Otherwise we just read stuff from gui.params.
function setColours(c) { function setColors(c) {
const nc = get_colours(c); const nc = get_colours(c);
for( let i = 0; i < node_ms.length; i++ ) { for( let i = 0; i < node_ms.length; i++ ) {
node_ms[i].color = new THREE.Color(nc[i]); node_ms[i].color = new THREE.Color(nc[i]);
@ -169,7 +163,7 @@ function setColours(c) {
} }
if( shape ) { if( shape ) {
// taperedLink.set_color updates according to the link index // taperedLink.set_color updates according to the link index
shape.links.map((l) => l.object.set_colour(nc)); shape.links.map((l) => l.object.set_color(nc));
} }
} }
@ -206,6 +200,8 @@ function changeShape() {
} }
function setVisibility(option_name) { function setVisibility(option_name) {
console.log("setVisibility", option_name);
console.log(structure.options);
const option = structure.options.filter((o) => o.name === option_name); const option = structure.options.filter((o) => o.name === option_name);
if( option.length ) { if( option.length ) {
node_show = option[0].nodes; node_show = option[0].nodes;
@ -220,7 +216,7 @@ gui = new FourDGUI(
{ {
shapes: STRUCTURES, shapes: STRUCTURES,
changeShape: changeShape, changeShape: changeShape,
setColours: setColours, setColors: setColors,
setBackground: setBackground, setBackground: setBackground,
setNodeOpacity: setNodeOpacity, setNodeOpacity: setNodeOpacity,
setLinkOpacity: setLinkOpacity, setLinkOpacity: setLinkOpacity,
@ -230,7 +226,7 @@ gui = new FourDGUI(
); );
// these are here to pick up colour settings from the URL params // these are here to pick up colour settings from the URL params
setColours(gui.params.colour); setColors(gui.params.color);
setBackground(gui.params.background); setBackground(gui.params.background);
const dragK = 0.005; const dragK = 0.005;

View File

@ -216,7 +216,6 @@ export const cell24 = () => {
links16.map((l) => l.label = p); links16.map((l) => l.label = p);
links.push(...links16); links.push(...links16);
} }
// colour links `
// 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] ) {
@ -326,11 +325,11 @@ function auto_120cell_faces(links) {
export function make_120cell_vertices() { export 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;
const phiinv = 1 / phi; const phiinv = 1 / phi;
const phi2inv = 1 / phi2; const phi2inv = 1 / phi2;
const nodes = [ const nodes = [
PERMUTE.coordinates([0, 0, 2, 2], 0), PERMUTE.coordinates([0, 0, 2, 2], 0),
@ -349,39 +348,9 @@ export function make_120cell_vertices() {
export function make_120cell_vertices_alt() {
// unit radius version to make it easier to partition one of the 600-cells
const phi = 0.5 * (1 + Math.sqrt(5));
const r5 = Math.sqrt(5);
const phi2 = phi * phi;
const phiinv = 1 / phi;
const phi2inv = 1 / phi2;
const r8 = Math.sqrt(8);
const p8 = phi / r8;
const r58 = r5 / r8;
const ir8 = 1 / r8;
const ip8 = phiinv / r8;
const p28 = phi2 / r8;
const ip28 = phi2inv / r8;
const nodes = [
PERMUTE.coordinates([1, 0, 0, 0], 1),
PERMUTE.coordinates([0.5, 0.5, 0.5, 0.5], 1),
PERMUTE.coordinates([0, phiinv * 0.5, 0.5, phi * 0.5], 1, true),
PERMUTE.coordinates([p8, p8, p8, ip28], 0, true),
PERMUTE.coordinates([ir8, ir8, ir8, r58], 0, true),
PERMUTE.coordinates([ip8, ip8, ip8, p28], 0, true),
PERMUTE.coordinates([0, ip8, p8, r58], 0, true),
PERMUTE.coordinates([0, ip28, ir8, p28], 0, true),
PERMUTE.coordinates([ip8, ir8, p8, 2 * ir8], 0, true),
].flat();
index_nodes(nodes);
return nodes;
}
function label_nodes(nodes, ids, label) {
export 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);
} }
@ -474,6 +443,15 @@ export const cell120_inscribed = () => {
links.push(...links600); links.push(...links600);
} }
const CELL5S = CELLINDEX.CELL120_CELL5.cell5s;
for( const c5 in CELL5S ) {
const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id));
const links5 = auto_detect_edges(nodes5, 5);
links5.map((l) => l.label = 8);
links.push(...links5);
}
return { return {
name: '120-cell', name: '120-cell',
nodes: nodes, nodes: nodes,
@ -482,73 +460,22 @@ export const cell120_inscribed = () => {
{ name: "none", links: [ 0 ]}, { name: "none", links: [ 0 ]},
{ name: "one inscribed 600-cell", links: [ 0, 1 ] }, { name: "one inscribed 600-cell", links: [ 0, 1 ] },
{ name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] }, { name: "five inscribed 600-cells", links: [ 0, 1, 2, 3, 4, 5 ] },
], { name: "120 inscribed 5-cells", links: [ 0, 8 ] },
],
description: `The 120-cell is the four-dimensional analogue of the description: `The 120-cell is the four-dimensional analogue of the
dodecahedron, and consists of 120 dodecahedra joined at 720 faces, dodecahedron, and consists of 120 dodecahedra joined at 720 faces,
with three dodecahedra around each edge. It is dual to the 600-cell, with three dodecahedra around each edge. It is dual to the 600-cell,
and five 600-cells can be inscribed in its vertices.` , and five 600-cells can be inscribed in its vertices. The converse
of this allows 120 5-cells (each of which has one vertex in each
of the 5 600-cells) to be inscribed in the 120-cell.`,
} }
} }
export const cell120_alt = () => {
const nodes = make_120cell_vertices_alt();
const links = auto_detect_edges(nodes, 4);
links.map((l) => l.label = 0);
return {
name: '120-cell-alt',
nodes: nodes,
links: links,
options: [
{ name: "none", links: [ 0 ]},
],
description: `alt 120-cell`,
}
}
export const cell120_inscribed_cell5 = () => { export const cell120_inscribed_cell5 = () => {
const nodes = make_120cell_vertices(); const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4); const links = [];
links.map((l) => l.label = 0);
const CELL5S = CELLINDEX.CELL120_CELL5;
for( const c5 in CELL5S ) {
const nodes5 = nodes.filter((n) => CELL5S[c5].nodes.includes(n.id));
const links5 = auto_detect_edges(nodes5, 5);
links5.map((l) => l.label = CELL5S[c5].label);
links.push(...links5);
nodes5.map((n) => n.label = CELL5S[c5].label);
}
return {
name: '120-cell with 5-cells',
nodes: nodes,
links: links,
options: [
{ name: "all", links: [0, 1, 2, 3, 4, 5] },
{ name: "24", links: [0, 1 ] },
{ name: "48", links: [0, 1, 2 ] },
{ name: "72", links: [0, 1, 2, 3 ] },
{ name: "96", links: [0, 1, 2, 3, 4 ] },
{ name: "hide 1200-cell", links: [ 1, 2, 3, 4, 5 ] },
],
description: `The vertices of the 120-cell can also be partitioned
into 120 5-cells: each 5-cell has one vertex in each of the five
600-cells which can be inscribed in the 120-cell. The colours here
are taken from the partition of the inscribed 600-cells' vertices
into five 24-cells.`
}
}
export const cell120_inscribe_cell5_subset = () => {
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
for( const cstr in CELLINDEX.INDEX120 ) { for( const cstr in CELLINDEX.INDEX120 ) {
label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr)); label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr));
@ -556,101 +483,30 @@ export const cell120_inscribe_cell5_subset = () => {
links.map((l) => l.label = 0); links.map((l) => l.label = 0);
const CELL5S = CELLINDEX.CELL120_CELL5.some_cell5s; const CELL5S = CELLINDEX.CELL120_CELL5.cell5s;
const link5s = [];
const nodes_subset = [];
for( const c5 in CELL5S ) { for( const c5 in CELL5S ) {
const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id)); const nodes5 = nodes.filter((n) => CELL5S[c5].includes(n.id));
const cell5links = auto_detect_edges(nodes5, 5); const links5 = auto_detect_edges(nodes5, 5);
cell5links.map((l) => l.label = 8); links5.map((l) => l.label = Number(c5));
link5s.push(...cell5links); links.push(...links5);
for( const n5 of nodes5 ) {
nodes_subset.push(n5);
}
} }
// now add the links of the inscribed 600-cells which have the const show_links = Array.from({ length: 128 }, (_, i) => i);
// node subset in them
const link_subset = (l) => {
const source = nodes_subset.filter((n) => n.id === l.source);
const target = nodes_subset.filter((n) => n.id === l.target);
return source.length === 1 && target.length === 1;
};
for( const p of [ 1, 2, 3, 4, 5 ]) {
const nodes600 = nodes.filter((n) => n.label === p);
const links600 = auto_detect_edges(nodes600, 12);
links600.map((l) => l.label = p);
for( const link of links600 ) {
if( link_subset(link) ) {
link5s.push(link);
}
}
}
return { return {
name: '120-cell 5-cell subset', name: '120 5-cells',
nodes: nodes_subset,
links: link5s,
options: [
{ name: "none", links: [ 0, 1, 2, 3, 4, 5, 8 ]},
],
description: `Showing only thirteen of the inscribed 5-cells and the links between them on the inscribed 600-cells`,
}
}
export const cell120_test_metamap = () => {
const o600 = cell600();
const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4);
for( const cstr in CELLINDEX.INDEX120 ) {
label_nodes(nodes, CELLINDEX.INDEX120[cstr], Number(cstr));
}
links.map((l) => l.label = 6);
const mm = CELLINDEX.CELL600_METAMAP;
const mm_ids = Object.keys(mm).map((i) => Number(i));
console.log(mm_ids);
for( const p of [ 1 ]) {
const nodes600 = nodes.filter((n) => n.label === p);
const links600 = auto_detect_edges(nodes600, 12);
console.log(links600);
const mm_links = links600.filter((l) => {
//console.log(`${l.id} ${l.source} ${l.target}`);
return mm_ids.includes(l.source) && mm_ids.includes(l.target)
});
console.log(mm_links);
mm_links.map((l) => l.label = p);
links.push(...mm_links);
}
nodes.map((n) => {
if( mm_ids.includes(n.id) ) {
const mapid = mm[n.id];
const n600 = o600.nodes.filter((n) => n.id === mapid);
n.label = n600[0].label;
} else {
n.label = 6;
}
});
return {
name: '120-cell-metamap',
nodes: nodes, nodes: nodes,
links: links, links: links,
options: [ options: [
{ name: "none", links: [ 0 ]}, { name: "none", links: show_links},
{ name: "one inscribed 600-cell", links: [ 0, 1 ] }, ],
], description: `The 120 5-cells from the 120-cell, without the latter's links. This colouring is pretty arbitrary, being based on the algorithm which partitioned the nodes: a later version will have something that's based on the symmetries of the 600-cells which each of the 5-cells has its nodes in.`,
description: `foo`,
} }
} }
function partition_coord(i, coords, invert) { function partition_coord(i, coords, invert) {
const j = invert ? -i : i; const j = invert ? -i : i;
if( j >= 0 ) { if( j >= 0 ) {
@ -784,12 +640,7 @@ export const cell600_layered = () => {
const nodes = make_600cell_vertices(); const nodes = make_600cell_vertices();
const links = auto_detect_edges(nodes, 12); const links = auto_detect_edges(nodes, 12);
const plabels = {}; nodes.map((n) => n.label = 9); // make all invisible by default
nodes.map((n) => {
plabels[n.id] = n.label;
n.label = 9
}); // make all invisible by default
for (const cstr in CELLINDEX.LAYERS600 ) { for (const cstr in CELLINDEX.LAYERS600 ) {
label_nodes(nodes, CELLINDEX.LAYERS600[cstr], Number(cstr)); label_nodes(nodes, CELLINDEX.LAYERS600[cstr], Number(cstr));
@ -804,12 +655,6 @@ export const cell600_layered = () => {
} }
}); });
// recolour nodes according to 24-cell partition
nodes.map((n) => n.label = 8 + plabels[n.id]);
const node_c = [ 8, 9, 10, 11, 12, 13, 14, 15 ];
const options = []; const options = [];
const layers = []; const layers = [];
@ -818,7 +663,7 @@ export const cell600_layered = () => {
options.push({ options.push({
name: CELLINDEX.LAYER_NAMES[i], name: CELLINDEX.LAYER_NAMES[i],
links: [...layers], links: [...layers],
nodes: [...node_c, ...layers] nodes: [...layers]
}) })
} }

View File

@ -4,11 +4,11 @@ const EPSILON = 0.001;
class TaperedLink extends THREE.Group { class TaperedLink extends THREE.Group {
constructor(baseMaterial, colour_i, n1, n2, r1, r2) { constructor(baseMaterial, color_i, n1, n2, r1, r2) {
super(); super();
const geometry = new THREE.ConeGeometry( 1, 1, 16, true ); const geometry = new THREE.ConeGeometry( 1, 1, 16, true );
const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5);
this.colour_i = colour_i; this.color_i = color_i;
this.material = baseMaterial.clone(); this.material = baseMaterial.clone();
this.material.clippingPlanes = [ cplane ]; this.material.clippingPlanes = [ cplane ];
this.object = new THREE.Mesh( geometry, this.material ); this.object = new THREE.Mesh( geometry, this.material );
@ -53,12 +53,11 @@ class TaperedLink extends THREE.Group {
clipnorm, napex clipnorm, napex
); );
} }
set_colour(colours) { set_color(colors) {
console.log(`taperedLink.set_colour {this.colour_i} {colours[this.colour_i]}`); this.material.color = new THREE.Color(colors[this.color_i]);
this.material.color = new THREE.Color(colours[this.colour_i]);
} }
} }