Merge pull request 'feature-120-cell-more-inscriptions' (#24) from feature-120-cell-more-inscriptions into main

Reviewed-on: #24
This commit is contained in:
bombinans 2026-01-01 07:27:01 +00:00
commit db7e2c41b2
11 changed files with 1469 additions and 34 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
## v1.1 - 1/1/2025
The 120-cell now includes a visualisation of its inscribed 5-cells, which honestly
looks like less of a mess than I expected it to.
## v1.0 - 16/11/2025 ## v1.0 - 16/11/2025
It's been [two years](https://mikelynch.org/2023/Sep/02/120-cell/)</a> since It's been [two years](https://mikelynch.org/2023/Sep/02/120-cell/)</a> since

26
NOTES.md Normal file
View File

@ -0,0 +1,26 @@
# NOTES
New approach for the 5-cells:
Pick a tetrahedron of an inscribed 600-cell with vertices A, B, C, D
This gives pairs of vertices:
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)

View File

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

View File

@ -14,6 +14,7 @@ export const get_colours = (basis) => {
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);
console.log(resaturated);
return resaturated; return resaturated;
} }

1
explore_120 Normal file
View File

@ -0,0 +1 @@

492
explore_120cell.js Normal file
View File

@ -0,0 +1,492 @@
import * as POLYTOPES from './polytopes.js';
// exploring more inscriptions of the 120-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;
}
}
function fingerprint(ids) {
const sids = [...ids];
sids.sort();
return sids.join(',');
}
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);
}
export function make_120cell() {
const nodes = POLYTOPES.make_120cell_vertices();
const links = POLYTOPES.auto_detect_edges(nodes, 4);
return {
nodes: nodes,
links: links
}
}
function round_dist(raw) {
return Math.floor(raw * 100000) / 100000;
}
export function distance_groups(cell120) {
// get list of other nodes by distance
// sort them and dump them out
const dists = {};
cell120.nodes.map((n) => {
const draw = dist(cell120.nodes[0], n);
const dtrunc = round_dist(draw);
if( !(dtrunc in dists) ) {
dists[dtrunc] = [];
}
dists[dtrunc].push(n);
});
return dists;
}
function distance_group(cell120, n0, chord) {
const nodes = []
cell120.nodes.map((n) => {
const d = round_dist(dist(n0, n));
if( d == chord ) {
nodes.push(n);
}
});
// filter and return those whose chord is also the same
const equidistant = [];
for( const n1 of nodes ) {
for( const n2 of nodes ) {
if( n2.id > n1.id ) {
if( round_dist(dist(n1, n2)) == chord ) {
equidistant.push([n1, n2]);
}
}
}
}
return equidistant;
}
export function chord_survey() {
const cell120 = POLYTOPES.cell120_inscribed();
const dgroups = distance_groups(cell120);
const dists = Object.keys(dgroups);
dists.sort();
for( const d of dists ) {
const g0 = dgroups[d][0];
dgroups[d].map((g) => {
console.log(`${g0.id}-${g.id}: ${round_dist(dist(g0, g))}`);
});
}
}
function overlap(c1, c2) {
for( const l in c1 ) {
if( c1[l] === c2[l] ) {
return true;
}
}
return false;
}
function c5match(c1, c2) {
for( const l in c1 ) {
if( c1[l] != c2[l] ) {
return false;
}
}
return true;
}
export function gather_5cells(cell120) {
const CHORD5 = round_dist(Math.sqrt(2.5));
const bins = [];
const all = [];
cell120.nodes.filter((n) => n.label === 1).map((n) => {
const cells = [ ];
const g = distance_group(cell120, n, CHORD5);
for( const pair of g ) {
let seen = false;
for( const cell of cells ) {
const c = Object.values(cell);
if( c.includes(pair[0].id) || c.includes(pair[1].id) ) {
if( !c.includes(pair[0].id) ) {
cell[pair[0].label] = pair[0].id;
}
if( !c.includes(pair[1].id) ) {
cell[pair[1].label] = pair[1].id;
}
seen = true;
break;
}
}
if( !seen ) {
const cell = {};
cell[1]= n.id;
cell[pair[0].label] = pair[0].id;
cell[pair[1].label] = pair[1].id;
cells.push(cell);
}
}
all.push(...cells);
});
return all;
}
function audit_5cells(cells) {
// this verifies that for each label (a 600-cell set), each of its
// vertices is in exactly 7 5-cells. It checks out.
['1','2','3','4','5'].map((l) => {
const sets = {};
for( const cell of cells ) {
const lv = cell[l];
if( !(lv in sets) ) {
sets[lv] = [];
}
sets[lv].push(cell);
}
for( const lv in sets ) {
const ok = ( sets[lv].length === 7 ) ? 'ok' : 'miss';
console.log(`${l},${lv},${sets[lv].length},${ok}`);
}
});
}
function try_120_5_cells_fails(cell120, cells, l) {
// iterate over every vertex in the 600-cell defined by label l,
// get all 7 5-cells including that vertex, and add them if they are
// disjoint with what we already have
// this always runs out of disjoint nodes early
const vertices = cell120.nodes.filter((n) => n.label === l);
const cellset = [];
for( const v of vertices ) {
console.log(`Vertex ${v.id}`);
const vcells = cells.filter((c) => c[l] === v.id);
const overlap_any = (cs, c) => {
for( const seen of cs ) {
if( overlap(seen, c) ) {
console.log("overlap");
console.log(c);
return true;
}
}
return false;
}
const disjoint = vcells.filter((c) => ! overlap_any(cellset, c));
console.log(`Found ${disjoint.length} disjoint cells`);
if( disjoint.length > 0 ) {
cellset.push(choice(disjoint));
}
}
console.log(`Found total of ${cellset.length} disjoint cells`);
//console.log(cellset);
}
function overlap_any(cs, c) {
for( const seen of cs ) {
if( overlap(seen, c) ) {
return true;
}
}
return false;
}
function explore_disjoint(cell120, all5, l) {
const a = all5[0];
const overlaps = all5.filter((c) => overlap(c, a));
console.log(a);
console.log(overlaps.length);
console.log(overlaps);
}
// select a five-cell from a starting vertex v
// find a neighbor of v vn on its 600 cell, find all of the 5-cells which include
// vn. Then see if we can find any from that set which are similiar neighbours to
// the other four vertices in the first 5-cell
// the idea is that the 600-cells are a guide to finding the right subset of
// 5-cells
function neighbours600(cell120, vid) {
const v = cell120.nodes.filter((node) => node.id === vid)[0];
const label = v.label;
const links = cell120.links.filter((l) => {
return l.label === v.label && (l.source === v.id || l.target == v.id );
});
const nodes = links.map((l) => {
if( l.source === v.id ) {
return l.target;
} else {
return l.source;
}
});
return nodes;
}
function cell120node(cell120, nid) {
return cell120.nodes.filter((n) => n.id === nid)[0];
}
function node_dist(cell120, aid, bid) {
const a = cell120node(cell120, aid);
const b = cell120node(cell120, bid);
return dist(a, b);
}
function print_row(v1, v2, p, v5) {
console.log(`${v1.id},${v2.id},${p},${v5[1]},${v5[2]},${v5[3]},${v5[4]},${v5[5]}`);
}
// for a pair of vertices which are on the same inscribed 600 cell,
// this returns all 7 pairs of 5-cells which contain v1 and v2 and
// which are also evenly spaced (ie every pair of vertices on the
// same 600-cell is one edge apart)
function find_adjoining_5cells(cell120, all5, v1, v2) {
const DIST600 = round_dist(node_dist(cell120, v1.id, v2.id));
const v15s = all5.filter((c5) => c5[v1.label] === v1.id);
const v25s = all5.filter((c5) => c5[v2.label] === v2.id);
let p = 0;
const c5pairs = [];
for( const v5a of v15s ) {
for( const v5b of v25s ) {
let match = true;
const d = {};
for( const label in v5a ) {
d[label] = round_dist(node_dist(cell120, v5a[label], v5b[label]));
if( d[label] != DIST600 ) {
match = false;
}
}
if( match ) {
c5pairs.push([ v5a, v5b ]);
}
}
}
return c5pairs;
}
function tetras(cell120, v) {
// given a vertex v, find all of the 600-cell tetras it's on
const n600s = neighbours600(cell120, v.id);
// need to find all sets of three neighbours which are neighbours: there
// should be 20 of these because they're faces of an icosahedron
const tetras = new Set;
for( const v2id of n600s ) {
// find mutual neighbours of the first two
const n2600s = neighbours600(cell120, v2id);
const mutuals = n2600s.filter((nid) => {
return nid != v2id && nid != v.id && n600s.includes(nid)
});
for( const nm of mutuals ) {
const nnms = neighbours600(cell120, nm);
const mutuals2 = nnms.filter((nid) => {
return nid != nm && nid != v2id && nid != v.id && mutuals.includes(nid)
});
for( const m2 of mutuals2 ) {
const t = [ v.id, v2id, nm, m2 ];
t.sort((a, b) => a - b);
const tstr = t.join(',');
tetras.add(tstr);
}
}
}
const tarray = [];
for( const t of tetras ) {
const ta = t.split(',').map((v) => Number(v));
tarray.push(ta);
}
return tarray;
}
function vertices(hedra) {
const v = new Set;
for ( const h of hedra) {
for( const p of h ) {
v.add(p);
}
}
return Array.from(v);
}
function str5cell(c5) {
return ["1","2","3","4","5"].map((l) => String(c5[l]).padStart(3, '0')).join('-');
}
function tetra_sets(cell120, all5, tetra) {
// given a tetrahedron on a 600-cell, find the sets of adjacent 5-cells on
// all of the pairs
// this is ass-backwards. Need to find tetras on the other 4 vertices of a 5-cell
const vs = tetra.map((tid) => cell120node(cell120, tid));
const pairs = [[0,1], [0,2], [0, 3], [1, 2], [1, 3], [2, 3]];
for( const p of pairs ) {
const v1 = vs[p[0]];
const v2 = vs[p[1]];
const c5pairs = find_adjoining_5cells(cell120, all5, v1, v2);
console.log(v1.id, v2.id);
console.log(c5pairs.map((p) => str5cell(p[0]) + " " + str5cell(p[1])));
}
}
function cell5_neighbourhoods(cell120, all5, c5) {
const neighbours = {}
for( const l in c5 ) {
const v = cell120node(cell120, c5[l]);
neighbours[l] = vertices(tetras(cell120, v));
}
// now take the set of all 5-cells and filter it to only those whose vertices
// are in the neighour sets. On first inspection there are 13?
const n5cells = all5.filter((c5) => {
for( const l in c5 ) {
if( ! neighbours[l].includes(c5[l]) ) {
return false;
}
}
return true;
});
return n5cells;
}
function cell5_tetras(cell120, all5, c5) {
const nb = cell5_neighbourhoods(cell120, all5, c5);
const v1 = cell120node(cell120, c5["1"]);
const ts = tetras(cell120, v1);
const c5s = [];
for( const t of ts ) {
const nt = nb.filter((n) => {
for( const l in n ) {
if( t.includes(n[l]) ) {
return true;
}
}
return false
});
for( const nc5 of nt ) {
const exact = c5s.filter((c) => c5match(c, nc5));
if( exact.length === 0 ) {
const o = c5s.filter((c) => overlap(c, nc5));
if( o.length > 0 ) {
console.log("Overlap", c5, o);
} else {
c5s.push(nc5);
}
}
}
}
return c5s;
}
function coherent_5cells_r(cell120, all5, c5s, c50) {
// Find next set of c5s, see if there are any we haven't seen,
// recurse into those ones
const c5ns = cell5_tetras(cell120, all5, c50);
const c5unseen = c5ns.filter((c5) => {
const matched = c5s.filter((c5b) => c5match(c5b, c5));
return matched.length === 0;
});
for( const c5u of c5unseen ) {
c5s.push(c5u);
}
for( const c5u of c5unseen ) {
coherent_5cells_r(cell120, all5, c5s, c5u);
}
}
function coherent_5cells(cell120, all5) {
// pick a starting point, collect coherent 5_cells, continue till
// there aren't any new ones
const c5set = [];
let c5 = all5[0];
const c5s = [];
coherent_5cells_r(cell120, all5, c5s, c5);
return c5s;
}
const cell120 = POLYTOPES.cell120_inscribed();
const all5 = gather_5cells(cell120);
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
const vertex_check = {};
for( const c5 of celli ) {
for( const l in c5 ) {
const v = c5[l];
if( v in vertex_check ) {
console.log(`Double count vertex ${v}`);
}
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));

View File

@ -62,7 +62,7 @@ class FourDShape extends THREE.Group {
const s1 = this.link_scale * n1.scale; const s1 = this.link_scale * n1.scale;
const s2 = this.link_scale * n2.scale; const s2 = this.link_scale * n2.scale;
link.object.update(n1, n2, s1, s2); link.object.update(n1, n2, s1, s2);
link.object.visible = (!links_show || link.label in links_show); link.object.visible = (!links_show || links_show.includes(link.label));
} }
@ -143,7 +143,7 @@ class FourDShape extends THREE.Group {
this.nodes3[n.id].scale = k * this.foreshortening; 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(s3);
this.nodes3[n.id].object.visible = ( !nodes_show || n.label in nodes_show ); this.nodes3[n.id].object.visible = ( !nodes_show || nodes_show.includes(n.label) );
} }
for( const l of this.links ) { for( const l of this.links ) {
this.updateLink(l, links_show); this.updateLink(l, links_show);

53
gui.js
View File

@ -7,6 +7,7 @@ const DEFAULTS = {
linksize: 1.0, linksize: 1.0,
linkopacity: 0.75, linkopacity: 0.75,
shape: '120-cell', shape: '120-cell',
link2opacity: 0.75,
option: 'none', option: 'none',
visibility: 5, visibility: 5,
inscribed: false, inscribed: false,
@ -15,8 +16,8 @@ const DEFAULTS = {
background: 0xd4d4d4, background: 0xd4d4d4,
hyperplane: 0.93, hyperplane: 0.93,
zoom: 1, zoom: 1,
xRotate: 'YW', xRotate: 'YZ',
yRotate: 'XW', yRotate: 'XZ',
dtheta: 0, dtheta: 0,
damping: false, damping: false,
captions: true, captions: true,
@ -27,9 +28,10 @@ const DEFAULTS = {
class FourDGUI { class FourDGUI {
constructor(shapes, changeShape, setColor, setBackground, setNodeOpacity,setLinkOpacity, setVisibility, showDocs) { constructor(funcs) {
this.shapes = funcs.shapes;
this.gui = new GUI(); this.gui = new GUI();
const SHAPE_NAMES = shapes.map((s) => s.name); const SHAPE_NAMES = this.shapes.map((s) => s.name);
this.parseLinkParams(); this.parseLinkParams();
const guiObj = this; const guiObj = this;
@ -40,6 +42,7 @@ class FourDGUI {
inscribe_all: this.link['inscribe_all'], inscribe_all: this.link['inscribe_all'],
linksize: this.link['linksize'], linksize: this.link['linksize'],
linkopacity: this.link['linkopacity'], linkopacity: this.link['linkopacity'],
link2opacity: this.link['link2opacity'],
nodesize: this.link['nodesize'], nodesize: this.link['nodesize'],
nodeopacity: this.link['nodeopacity'], nodeopacity: this.link['nodeopacity'],
depth: this.link['depth'], depth: this.link['depth'],
@ -53,39 +56,52 @@ class FourDGUI {
captions: true, 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() },
}; };
if( funcs.extras ) {
for( const label in funcs.extras ) {
console.log(label);
console.log(funcs.extras[label]);
this.params[label] = funcs.extras[label];
}
}
let options_ctrl; let options_ctrl;
this.gui.add(this.params, 'shape', SHAPE_NAMES).onChange((shape) => { this.gui.add(this.params, 'shape', SHAPE_NAMES).onChange((shape) => {
const options = this.getShapeOptions(shapes, shape); const options = this.getShapeOptions(shape);
options_ctrl = options_ctrl.options(options).onChange((option) => { options_ctrl = options_ctrl.options(options).onChange((option) => {
setVisibility(option) funcs.setVisibility(option)
}); });
options_ctrl.setValue(options[0]) options_ctrl.setValue(options[0])
changeShape(shape) funcs.changeShape(shape)
}); });
const options = this.getShapeOptions(shapes, this.params['shape']); const options = this.getShapeOptions(this.params['shape']);
options_ctrl = this.gui.add(this.params, 'option').options(options).onChange((option) => { options_ctrl = this.gui.add(this.params, 'option').options(options).onChange((option) => {
setVisibility(option) funcs.setVisibility(option)
}); });
this.gui.add(this.params, 'hyperplane', 0.5, 1 / 0.8); 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, 'zoom', 0.1, 2.0);
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(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);
this.gui.add(this.params, 'linkopacity', 0, 1).onChange(setLinkOpacity); console.log(funcs.setLinkOpacity);
this.gui.addColor(this.params, 'color').onChange(setColor); this.gui.add(this.params, 'linkopacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, true));
this.gui.addColor(this.params, 'background').onChange(setBackground); this.gui.add(this.params, 'link2opacity', 0, 1).onChange((v) => funcs.setLinkOpacity(v, false));
this.gui.addColor(this.params, 'color').onChange(funcs.setColor);
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' ]);
this.gui.add(this.params, 'captions').onChange(showDocs); this.gui.add(this.params, 'captions').onChange(this.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');
if( funcs.extras ) {
for( const label in funcs.extras ) {
this.gui.add(this.params, label);
}
}
} }
getShapeOptions(shapes, shape) { getShapeOptions(shape) {
const spec = shapes.filter((s) => s.name === shape); const spec = this.shapes.filter((s) => s.name === shape);
if( spec && spec[0].options ) { if( spec && spec[0].options ) {
return spec[0].options.map((o) => o.name); return spec[0].options.map((o) => o.name);
} else { } else {
@ -136,6 +152,7 @@ class FourDGUI {
this.link['zoom'] = this.numParam('zoom', parseFloat); this.link['zoom'] = this.numParam('zoom', parseFloat);
this.link['linksize'] = this.numParam('linksize', 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['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['color'] = this.numParam('color', (s) => guiObj.stringToHex(s)); this.link['color'] = this.numParam('color', (s) => guiObj.stringToHex(s));

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.0</a> | <div id="info"><a href="#" id="show_notes">release 1.1</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>

27
main.js
View File

@ -68,6 +68,7 @@ link_ms.map((m) => {
} }
); );
console.log("link_ms", link_ms);
const face_ms = [ const face_ms = [
@ -168,7 +169,11 @@ function setBackground(c) {
function setLinkOpacity(o, primary) { function setLinkOpacity(o, primary) {
link_ms.map((lm) => lm.opacity = o); link_ms.map((lm) => lm.opacity = o);
if( shape ) { if( shape ) {
shape.links.map((l) => l.object.material.opacity = o); shape.links.map((l) => {
if( (primary && l.label == 0) || (!primary && l.label !== 0) ) {
l.object.material.opacity = o
}
});
} }
} }
@ -186,6 +191,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;
@ -197,14 +204,16 @@ function setVisibility(option_name) {
gui = new FourDGUI( gui = new FourDGUI(
STRUCTURES, {
changeShape, shapes: STRUCTURES,
setColors, changeShape: changeShape,
setBackground, setColors: setColors,
setNodeOpacity, setBackground: setBackground,
setLinkOpacity, setNodeOpacity: setNodeOpacity,
setVisibility, setLinkOpacity: setLinkOpacity,
showDocs setVisibility: setVisibility,
showDocs: 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

View File

@ -443,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,
@ -450,17 +459,44 @@ export const cell120_inscribed = () => {
options: [ options: [
{ 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_inscribed_cell5 = () => {
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 = 0);
return {
name: '120-cell-5-cell',
nodes: nodes,
links: links,
options: [
{ name: "5-cells", links: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] },
],
description: `The 120-cell with one of its 5-cells.`,
}
}
function partition_coord(i, coords, invert) { function partition_coord(i, coords, invert) {
@ -864,7 +900,6 @@ export const icosahedron = () => {
export const build_all = () => { export const build_all = () => {
return [ return [
linkTest(),
tetrahedron(), tetrahedron(),
octahedron(), octahedron(),
cube(), cube(),