Compare commits

..

5 Commits

Author SHA1 Message Date
Mike Lynch
44d3e0da8a Restored the original 120-cell with inscribed 5-cell 2026-02-11 12:02:26 +11:00
Mike Lynch
9ade532860 Added new rainbow colour scheme 2026-02-11 11:56:48 +11:00
Mike Lynch
5458b2c6a6 TODO update 2026-02-11 10:29:06 +11:00
Mike Lynch
b07b8d4a5b Removing console.log statements from taperedLink 2026-02-11 10:28:47 +11:00
Mike Lynch
f40d182192 Fooling around with different colour schemes 2026-02-11 10:28:21 +11:00
6 changed files with 108 additions and 67 deletions

23
TODO.md
View File

@ -1,24 +1,13 @@
# FOURD-JS # FOURD-JS
To-do: ## 120-cell 5-cell
* render a basic wireframe (vertices and edges) We can label the 5-cells by using the 600-cell layers - might be more interesting
- cylinder and spheres Try colouring the nodes according to the 120-cell standard way and the links
- group these in a different set of 5 colours by the new mapping
-- DONE For a subset of 24 5-cells, the vertices seem to be grouped in clusters on the 120-cells - each cluster has one of each of the 5 labels from the 120 cell
Write a script which checks this for each set
* algorithm for 4d -> 3d transform
--DONE
* 4D shape -> 3D projection -> wireframe -> three.js
--DONE
Next:
how to do this efficiently so that we can rotate the shape in 4d and have
this animate in 3d?

View File

@ -1,7 +1,14 @@
import ColorScheme from 'color-scheme'; import ColorScheme from 'color-scheme';
import Color from 'color'; import Color from 'color';
export const get_colours = (basis) => { export const get_colours = (basis, n) => {
const colours = get_colours_spectrum(basis, n);
return colours;
}
const get_colours_tetrade = (basis, n) => {
// this always returns what the scheme has so it ignores n
const basis_c = Color(basis); const basis_c = Color(basis);
const hslb = basis_c.hsl(); const hslb = basis_c.hsl();
const hue = hslb['color'][0]; const hue = hslb['color'][0];
@ -10,13 +17,29 @@ export const get_colours = (basis) => {
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 colours = scheme.colors();
colours.reverse(); //colours.reverse();
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);
return resaturated; return resaturated;
} }
const get_colours_spectrum = (basis, n) => {
// this returns n colours evenly spaced by hue
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 hsl = [];
for( let i = 0; i < n; i++ ) {
const h = (hue + i * 360 / n) % 360;
hsl.push(Color.hsl(h, saturation, luminance));
}
return hsl.map((hslc) => hslc.rgbNumber());
}
// basic colours where 0 = blue // basic colours where 0 = blue
// 1 - dark blue // 1 - dark blue
// 2 - white // 2 - white

1
gui.js
View File

@ -13,6 +13,7 @@ const DEFAULTS = {
inscribed: false, inscribed: false,
inscribe_all: false, inscribe_all: false,
colour: 0x3293a9, colour: 0x3293a9,
colour_n: 5,
background: 0xd4d4d4, background: 0xd4d4d4,
hyperplane: 0.93, hyperplane: 0.93,
zoom: 1, zoom: 1,

40
main.js
View File

@ -66,9 +66,20 @@ 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 STRUCTURES = POLYTOPES.build_all();
const STRUCTURES_BY_NAME = {};
STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s);
const max_ms = Math.max(...STRUCTURES.map((s) => s.colour_n));
// make enough materials for the largest value of colour_n
const node_colours = get_colours(DEFAULTS.colour, max_ms);
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}));
@ -91,17 +102,11 @@ const face_ms = [
]; ];
for( const face_m of face_ms ) { for( const face_m of face_ms ) {
face_m.transparent = true; face_m.transparent = true;
face_m.opacity = FACE_OPACITY; face_m.opacity = FACE_OPACITY;
} }
const STRUCTURES = POLYTOPES.build_all();
const STRUCTURES_BY_NAME = {};
STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s);
let shape = false; let shape = false;
let structure = false; let structure = false;
let node_show = []; let node_show = [];
@ -162,17 +167,20 @@ relnotes.addEventListener('click', releaseNotes);
// 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 setColours(c) {
const nc = get_colours(c); if( structure ) {
for( let i = 0; i < node_ms.length; i++ ) { const nc = get_colours(c, structure.colour_n);
node_ms[i].color = new THREE.Color(nc[i]); for( let i = 0; i < node_ms.length; i++ ) {
link_ms[i].color = new THREE.Color(nc[i]); node_ms[i].color = new THREE.Color(nc[i]);
} link_ms[i].color = new THREE.Color(nc[i]);
if( shape ) { }
// taperedLink.set_color updates according to the link index if( shape ) {
shape.links.map((l) => l.object.set_colour(nc)); // taperedLink.set_color updates according to the link index
shape.links.map((l) => l.object.set_colour(nc));
}
} }
} }
function setBackground(c) { function setBackground(c) {
scene.background = new THREE.Color(c) scene.background = new THREE.Color(c)
} }

View File

@ -89,16 +89,16 @@ export const cell5 = () => {
{id:5, label: 5, x: 0, y: 0, z: 0, w: 1 }, {id:5, label: 5, x: 0, y: 0, z: 0, w: 1 },
], ],
links: [ links: [
{ id:1, source:1, target: 2}, { id:1, source:1, target: 2, label:0},
{ id:2, source:1, target: 3}, { id:2, source:1, target: 3, label:0},
{ id:3, source:1, target: 4}, { id:3, source:1, target: 4, label:0},
{ id:4, source:1, target: 5}, { id:4, source:1, target: 5, label:0},
{ id:5, source:2, target: 3}, { id:5, source:2, target: 3, label:0},
{ id:6, source:2, target: 4}, { id:6, source:2, target: 4, label:0},
{ id:7, source:2, target: 5}, { id:7, source:2, target: 5, label:0},
{ id:8, source:3, target: 4}, { id:8, source:3, target: 4, label:0},
{ id:9, source:3, target: 5}, { id:9, source:3, target: 5, label:0},
{ id:10, source:4, target: 5}, { id:10, source:4, target: 5, label:0},
], ],
options: [ { name: '--' }], options: [ { name: '--' }],
description: `Five tetrahedra joined at ten faces with three description: `Five tetrahedra joined at ten faces with three
@ -126,6 +126,7 @@ export const cell16 = () => {
index_nodes(nodes); index_nodes(nodes);
scale_nodes(nodes, 0.5); scale_nodes(nodes, 0.5);
const links = auto_detect_edges(nodes, 6); const links = auto_detect_edges(nodes, 6);
links.map((l) => l.label = 0);
return { return {
name: '16-cell', name: '16-cell',
@ -513,29 +514,40 @@ export const cell120_inscribed_cell5 = () => {
const nodes = make_120cell_vertices(); const nodes = make_120cell_vertices();
const links = auto_detect_edges(nodes, 4); 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); links.map((l) => l.label = 0);
const CELL5S = CELLINDEX.CELL120_CELL5; const CELL5S = CELLINDEX.CELL120_CELL5;
const nodesc5s = [];
const linksc5s = [];
const nids = [];
for( const c5 in CELL5S ) { for( const c5 in CELL5S ) {
const nodes5 = nodes.filter((n) => CELL5S[c5].nodes.includes(n.id)); const nodes5 = nodes.filter((n) => CELL5S[c5].nodes.includes(n.id));
const links5 = auto_detect_edges(nodes5, 5); const links5 = auto_detect_edges(nodes5, 5);
links5.map((l) => l.label = CELL5S[c5].label); links5.map((l) => l.label = CELL5S[c5].label);
links.push(...links5); nodes5.map((n) => n.label = CELL5S[c5].label);
nodes5.map((n) => n.label = CELL5S[c5].label); linksc5s.push(...links5);
nodesc5s.push(...nodes5);
nids.push(...nodes5.map((n) => n.id));
} }
const links120 = links.filter((l) => nids.includes(l.source) && nids.includes(l.target));
return { return {
name: '120-cell with 5-cells', name: '120-cell with 5-cells',
nodes: nodes, nodes: nodesc5s,
links: links, links: [...linksc5s, ...links120],
options: [ options: [
{ name: "all", links: [0, 1, 2, 3, 4, 5] }, { name: "all", links: [0, 1, 2, 3, 4, 5] },
{ name: "24", links: [0, 1 ] }, { name: "24", links: [0, 3 ] },
{ name: "48", links: [0, 1, 2 ] }, { name: "48", links: [0, 1, 2 ] },
{ name: "72", links: [0, 1, 2, 3 ] }, { name: "72", links: [0, 1, 2, 3 ] },
{ name: "96", links: [0, 1, 2, 3, 4 ] }, { name: "96", links: [0, 1, 2, 3, 4 ] },
{ name: "hide 1200-cell", links: [ 1, 2, 3, 4, 5 ] }, { name: "hide 120-cell links", links: [ 1, 2, 3, 4, 5 ] },
], ],
description: `The vertices of the 120-cell can also be partitioned 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 into 120 5-cells: each 5-cell has one vertex in each of the five
@ -806,10 +818,6 @@ export const cell600_layered = () => {
// recolour nodes according to 24-cell partition // 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 +826,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]
}) })
} }
@ -946,12 +954,12 @@ export const tetrahedron = () => {
{id:4, label: 4, x: 0, y: 0, z: 1, w: 0 }, {id:4, label: 4, x: 0, y: 0, z: 1, w: 0 },
], ],
links: [ links: [
{ id:1, source:1, target: 2}, { id:1, source:1, target: 2, label: 0},
{ id:2, source:1, target: 3}, { id:2, source:1, target: 3, label: 0},
{ id:3, source:1, target: 4}, { id:3, source:1, target: 4, label: 0},
{ id:4, source:2, target: 3}, { id:4, source:2, target: 3, label: 0},
{ id:5, source:2, target: 4}, { id:5, source:2, target: 4, label: 0},
{ id:6, source:3, target: 4}, { id:6, source:3, target: 4, label: 0},
], ],
options: [ { name: '--' }], options: [ { name: '--' }],
description: `The simplest three-dimensional polytope, consisting of four triangles joined at six edges. The 5-cell is its four-dimensional analogue.`, description: `The simplest three-dimensional polytope, consisting of four triangles joined at six edges. The 5-cell is its four-dimensional analogue.`,
@ -1062,7 +1070,7 @@ export const icosahedron = () => {
export const build_all = () => { export const build_all = () => {
return [ const shapes = [
tetrahedron(), tetrahedron(),
octahedron(), octahedron(),
cube(), cube(),
@ -1079,8 +1087,21 @@ export const build_all = () => {
cell120_inscribed_cell5(), cell120_inscribed_cell5(),
cell120_layered() cell120_layered()
]; ];
for( const shape of shapes ) {
shape.colour_n = count_labels(shape);
}
return shapes;
} }
export const count_labels = (shape) => {
const labels = {};
shape.nodes.map((n) => labels[n.label] = true);
shape.links.map((n) => labels[n.label] = true);
return Object.keys(labels).length;
};
export const radii = (shape) => { 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)) 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

@ -57,7 +57,6 @@ class TaperedLink extends THREE.Group {
} }
set_colour(colours) { set_colour(colours) {
console.log(`taperedLink.set_colour {this.colour_i} {colours[this.colour_i]}`);
this.material.color = new THREE.Color(colours[this.colour_i]); this.material.color = new THREE.Color(colours[this.colour_i]);
} }