Merge branch 'feature-120-cell-index'
commit
6c6402bad9
|
@ -0,0 +1,137 @@
|
|||
// trying to go from faces to dodecahedra
|
||||
|
||||
|
||||
function shared_vertices(f1, f2) {
|
||||
return f1.nodes.filter((f) => f2.nodes.includes(f));
|
||||
}
|
||||
|
||||
|
||||
function adjacent_faces(f1, f2) {
|
||||
// adjacent faces which share an edge, not just a vertex
|
||||
const intersect = shared_vertices(f1, f2);
|
||||
if( intersect.length < 2 ) {
|
||||
return false;
|
||||
}
|
||||
if( intersect.length > 2 ) {
|
||||
console.log(`warning: faces ${f1.id} and ${f2.id} have too many common vertices`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function find_adjacent_faces(faces, face) {
|
||||
const neighbours = faces.filter((f) => f.id !== face.id && adjacent_faces(f, face));
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function find_dodeca_mutuals(faces, f1, f2) {
|
||||
// for any two adjacent faces, find their common neighbours where
|
||||
// all three share exactly one vertex (this, I think, guarantees that
|
||||
// all are on the same dodecahedron)
|
||||
|
||||
const n1 = find_adjacent_faces(faces, f1);
|
||||
const n2 = find_adjacent_faces(faces, f2);
|
||||
const common = n1.filter((f1) => n2.filter((f2) => f1.id === f2.id).length > 0 );
|
||||
// there's one extra here - the third which has two nodes in common with
|
||||
// both f1 and f2 - filter it out
|
||||
const mutuals = common.filter((cf) => {
|
||||
const shared = cf.nodes.filter((n) => f1.nodes.includes(n) && f2.nodes.includes(n));
|
||||
return shared.length === 1
|
||||
});
|
||||
return mutuals;
|
||||
}
|
||||
|
||||
function find_dodeca_next(faces, dodeca, f1, f2) {
|
||||
// of a pair of mutuals, return the one we haven't already got
|
||||
const m = find_dodeca_mutuals(faces, f1, f2);
|
||||
if( dodeca.filter((f) => f.id === m[0].id ).length > 0 ) {
|
||||
m.shift();
|
||||
}
|
||||
return m[0];
|
||||
}
|
||||
|
||||
// from any two mutual faces, return all the faces in their dodecahedron
|
||||
|
||||
function make_dodecahedron(faces, f1, f2) {
|
||||
const dodecahedron = [ f1, f2 ];
|
||||
|
||||
// take f1 as the 'center', get the other four around it from f2
|
||||
const fs = find_dodeca_mutuals(faces, f1, f2);
|
||||
const f3 = fs[0];
|
||||
const f6 = fs[1];
|
||||
dodecahedron.push(f3);
|
||||
const f4 = find_dodeca_next(faces, dodecahedron, f1, f3);
|
||||
dodecahedron.push(f4);
|
||||
const f5 = find_dodeca_next(faces, dodecahedron, f1, f4);
|
||||
dodecahedron.push(f5);
|
||||
dodecahedron.push(f6);
|
||||
|
||||
// get the next ring
|
||||
|
||||
const f7 = find_dodeca_next(faces, dodecahedron, f6, f2);
|
||||
dodecahedron.push(f7);
|
||||
const f8 = find_dodeca_next(faces, dodecahedron, f2, f3);
|
||||
dodecahedron.push(f8);
|
||||
const f9 = find_dodeca_next(faces, dodecahedron, f3, f4);
|
||||
dodecahedron.push(f9);
|
||||
const f10 = find_dodeca_next(faces, dodecahedron, f4, f5);
|
||||
dodecahedron.push(f10);
|
||||
const f11 = find_dodeca_next(faces, dodecahedron, f5, f6);
|
||||
dodecahedron.push(f11);
|
||||
|
||||
// get the last
|
||||
|
||||
const f12 = find_dodeca_next(faces, dodecahedron, f7, f8);
|
||||
dodecahedron.push(f12);
|
||||
|
||||
return dodecahedron;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
// first face's two dodecahedra
|
||||
|
||||
function find_edge_neighbours(faces, face) {
|
||||
const n1 = face.nodes[0];
|
||||
const n2 = face.nodes[1];
|
||||
return faces.filter((f) => f.id !== face.id && f.nodes.includes(n1) && f.nodes.includes(n2));
|
||||
}
|
||||
|
||||
|
||||
// each face is in two dodecahedra: this returns them both
|
||||
|
||||
function face_to_dodecahedra(faces, f) {
|
||||
const edge_friends = find_edge_neighbours(faces, f);
|
||||
const d1 = make_dodecahedron(faces, f, edge_friends[0]);
|
||||
const d2 = make_dodecahedron(faces, f, edge_friends[1]);
|
||||
return [ d1, d2 ];
|
||||
}
|
||||
|
||||
// brute-force calculation of all dodecahedra
|
||||
|
||||
function dd_fingerprint(dodecahedron) {
|
||||
const ids = dodecahedron.map((face) => face.id);
|
||||
ids.sort()
|
||||
return ids.join(',');
|
||||
}
|
||||
|
||||
export function make_120cell_dodecahedra(faces) {
|
||||
const dodecas = [];
|
||||
const seen = {};
|
||||
for( const face of faces ) {
|
||||
const dds = face_to_dodecahedra(faces, face);
|
||||
for( const dd of dds ) {
|
||||
const fp = dd_fingerprint(dd);
|
||||
if( ! (fp in seen) ) {
|
||||
console.log(`added dodeca ${fp}`);
|
||||
dodecas.push(dd);
|
||||
seen[fp] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dodecas;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# Indexing manually ideas
|
||||
|
||||
- a list of all the faces (and maybe dodecahedra) which lets me assign
|
||||
labels to them and lights up the interface
|
||||
|
||||
- where faces / vertices are repeated in the table, assigning a vertex in one
|
||||
spot changes it everywhere else
|
||||
|
||||
|
||||
|
||||
separately - take the links generated by this codebase and apply them to
|
||||
the d3.js forcemap code I wrote for the 24-cell
|
|
@ -1,4 +1,43 @@
|
|||
|
||||
Steps forward -
|
||||
|
||||
1. algorithm which, given a face, finds the two dodecahedra it belongs to
|
||||
|
||||
2. using this, generate a list of all 120 dodecahedra:
|
||||
|
||||
[ a b c d e f g h i j k l m n o p q r s t ] <- 20 vertices
|
||||
|
||||
Check that each vertex appears in four of these
|
||||
|
||||
Then - either manually start labelling them, or build an interface to help
|
||||
with the manual labelling
|
||||
|
||||
|
||||
|
||||
1.
|
||||
|
||||
For a face: there are five edges, and ten other faces sharing an edge.
|
||||
|
||||
These edges are in two sets: one for each dodecahedron. The sets are defined
|
||||
by them sharing vertices which aren't in the first face.
|
||||
|
||||
Go around a set of five, by pairs: for each pair, find the other neighbour -
|
||||
|
||||
this gives the next five faces.
|
||||
|
||||
There's only one face left, which is defined by the shared other vertices of
|
||||
the last five.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// old shit below that didn't work VVVV
|
||||
|
||||
|
||||
Chords: 1.74806 - the 120-cell has 7200 chords of this length
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,13 +6,15 @@ const HYPERPLANE = 2.0;
|
|||
|
||||
class FourDShape extends THREE.Group {
|
||||
|
||||
constructor(node_ms, link_ms, structure) {
|
||||
constructor(node_ms, link_ms, face_ms, structure) {
|
||||
super();
|
||||
this.node_ms = node_ms;
|
||||
this.link_ms = link_ms;
|
||||
this.face_ms = face_ms;
|
||||
this.nodes4 = structure.nodes;
|
||||
this.nodes3 = {};
|
||||
this.links = structure.links;
|
||||
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;
|
||||
|
@ -71,6 +73,32 @@ class FourDShape extends THREE.Group {
|
|||
link.object.children[0].rotation.x = Math.PI / 2.0;
|
||||
}
|
||||
|
||||
|
||||
setFaceGeometry(face, geometry) {
|
||||
const values = [];
|
||||
for( const f of face.nodes ) {
|
||||
const v3 = this.nodes3[f].v3;
|
||||
values.push(v3.x);
|
||||
values.push(v3.y);
|
||||
values.push(v3.z);
|
||||
}
|
||||
const v3 = this.nodes3[face.nodes[0]].v3;
|
||||
values.push(v3.x);
|
||||
values.push(v3.y);
|
||||
values.push(v3.z);
|
||||
const vertices = new Float32Array(values);
|
||||
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
|
||||
}
|
||||
|
||||
makeFace(material, face) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
this.setFaceGeometry(face, geometry)
|
||||
const mesh = new THREE.Mesh( geometry, material );
|
||||
this.add(mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
fourDtoV3(x, y, z, w, rotations) {
|
||||
const v4 = new THREE.Vector4(x, y, z, w);
|
||||
for ( const m4 of rotations ) {
|
||||
|
@ -94,6 +122,10 @@ class FourDShape extends THREE.Group {
|
|||
const material = this.getMaterial(l, this.link_ms);
|
||||
l.object = this.makeLink(material, l);
|
||||
}
|
||||
for( const f of this.faces ) {
|
||||
const material = this.getMaterial(f, this.face_ms);
|
||||
f.object = this.makeFace(material, f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,6 +141,10 @@ class FourDShape extends THREE.Group {
|
|||
for( const l of this.links ) {
|
||||
this.updateLink(l);
|
||||
}
|
||||
|
||||
for( const f of this.faces ) {
|
||||
this.setFaceGeometry(f, f.object.geometry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,366 +1,4 @@
|
|||
|
||||
// Utilities for generating sets of coordinates based on
|
||||
// permutations, even permutations and changes of sign.
|
||||
// Based on https://www.qfbox.info/epermute
|
||||
|
||||
|
||||
const THREE =require('three');
|
||||
|
||||
function pandita(a) {
|
||||
const n = a.length;
|
||||
for( let k = n - 2; k >= 0; k-- ) {
|
||||
if( a[k] < a[k + 1] ) {
|
||||
for( let l = n - 1; l >= 0; l-- ) {
|
||||
if( a[k] < a[l] ) {
|
||||
const tmp = a[k];
|
||||
a[k] = a[l];
|
||||
a[l] = tmp;
|
||||
const revtail = a.slice(k + 1);
|
||||
revtail.reverse();
|
||||
for( let i = 0; i < revtail.length; i++ ) {
|
||||
a[k + 1 + i] = revtail[i];
|
||||
}
|
||||
return Math.floor(revtail.length / 2) + 1;
|
||||
}
|
||||
}
|
||||
console.log("Shouldn't get here");
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function permutations_old(a) {
|
||||
a.sort();
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( running ) {
|
||||
const s = pandita(a);
|
||||
if( s ) {
|
||||
ps.push([...a]);
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
function permutations(a) {
|
||||
a.sort();
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( pandita(a) > 0 ) {
|
||||
ps.push([...a]);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
|
||||
function permutations_even(a) {
|
||||
a.sort();
|
||||
let parity = 'even';
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( running ) {
|
||||
const s = pandita(a);
|
||||
if( s ) {
|
||||
if( parity === 'even' ) {
|
||||
if( s % 2 === 1 ) {
|
||||
parity = 'odd';
|
||||
}
|
||||
} else {
|
||||
if( s % 2 === 1 ) {
|
||||
parity = 'even';
|
||||
}
|
||||
}
|
||||
if( parity === 'even' ) {
|
||||
ps.push([...a]);
|
||||
}
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
// for a given permutation, say [ 1, 1, 0, 0 ], return all
|
||||
// of the valid changes of sign, so:
|
||||
// [ [1, 1, 0, 0 ], [ -1, 1, 0, 0 ], [ 1, -1, 0, 0 ], [-1, -1, 0, 0 ]]
|
||||
// ie don't do it on the zeros
|
||||
|
||||
function expand_sign(a, label) {
|
||||
const expanded = [];
|
||||
const exv = a.map((v) => v ? [ -v, v ] : [ 0 ]);
|
||||
for( const xv of exv[0] ) {
|
||||
for( const yv of exv[1] ) {
|
||||
for( const zv of exv[2] ) {
|
||||
for( const wv of exv[3] ) {
|
||||
expanded.push({label: label, x: xv, y:yv, z:zv, w:wv});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
|
||||
function coordinates(a, id0=1, even=false) {
|
||||
const ps = even ? permutations_even(a) : permutations(a);
|
||||
const coords = [];
|
||||
for( const p of ps ) {
|
||||
const expanded = expand_sign(p, 0);
|
||||
coords.push(...expanded);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function index_nodes(nodes, scale) {
|
||||
let i = 1;
|
||||
for( const n of nodes ) {
|
||||
n["id"] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function scale_nodes(nodes, scale) {
|
||||
for( const n of nodes ) {
|
||||
for( const a of [ 'x', 'y', 'z', 'w' ] ) {
|
||||
n[a] = scale * n[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function auto_detect_edges(nodes, neighbours, debug=false) {
|
||||
const seen = {};
|
||||
const nnodes = nodes.length;
|
||||
const links = [];
|
||||
let id = 1;
|
||||
for( const n1 of nodes ) {
|
||||
const d2 = [];
|
||||
for( const n2 of nodes ) {
|
||||
d2.push({ d2: dist2(n1, n2), id: n2.id });
|
||||
}
|
||||
d2.sort((a, b) => a.d2 - b.d2);
|
||||
const closest = d2.slice(1, neighbours + 1);
|
||||
if( debug ) {
|
||||
console.log(`closest = ${closest.length}`);
|
||||
console.log(closest);
|
||||
}
|
||||
for( const e of closest ) {
|
||||
const ids = [ n1.id, e.id ];
|
||||
ids.sort();
|
||||
const fp = ids.join(',');
|
||||
if( !seen[fp] ) {
|
||||
seen[fp] = true;
|
||||
links.push({ id: id, label: 0, source: n1.id, target: e.id });
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( debug ) {
|
||||
console.log(`Found ${links.length} edges`)
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
// too small and simple to calculate
|
||||
|
||||
const cell5 = () => {
|
||||
const r5 = Math.sqrt(5);
|
||||
const r2 = Math.sqrt(2) / 2;
|
||||
return {
|
||||
nodes: [
|
||||
{id:1, x: r2, y: r2, z: r2, w: -r2 / r5 },
|
||||
{id:2, x: r2, y: -r2, z: -r2, w: -r2 / r5 },
|
||||
{id:3, x: -r2, y: r2, z: -r2, w: -r2 / r5 },
|
||||
{id:4, x: -r2, y: -r2, z: r2, w: -r2 / r5 },
|
||||
{id:5, x: 0, y: 0, z: 0, w: 4 * r2 / r5 },
|
||||
],
|
||||
links: [
|
||||
{ id:1, source:1, target: 2},
|
||||
{ id:2, source:1, target: 3},
|
||||
{ id:3, source:1, target: 4},
|
||||
{ id:4, source:1, target: 5},
|
||||
{ id:5, source:2, target: 3},
|
||||
{ id:6, source:2, target: 4},
|
||||
{ id:7, source:2, target: 5},
|
||||
{ id:8, source:3, target: 4},
|
||||
{ id:9, source:3, target: 5},
|
||||
{ id:10, source:4, target: 5},
|
||||
],
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const cell16 = () => {
|
||||
let nodes = coordinates([1, 1, 1, 1], 0);
|
||||
nodes = nodes.filter((n) => n.x * n.y * n.z * n.w > 0);
|
||||
index_nodes(nodes);
|
||||
scale_nodes(nodes, 0.75);
|
||||
const links = auto_detect_edges(nodes, 6);
|
||||
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
const tesseract = () => {
|
||||
const nodes = coordinates([1, 1, 1, 1], 0);
|
||||
index_nodes(nodes);
|
||||
scale_nodes(nodes, Math.sqrt(2) / 2);
|
||||
const links = auto_detect_edges(nodes, 4);
|
||||
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const cell24 = () => {
|
||||
const nodes = coordinates([0, 0, 1, 1], 0);
|
||||
index_nodes(nodes);
|
||||
const links = auto_detect_edges(nodes, 6);
|
||||
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function make_120cell_vertices() {
|
||||
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 nodes = [
|
||||
coordinates([0, 0, 2, 2], 0),
|
||||
coordinates([1, 1, 1, r5], 0),
|
||||
coordinates([phi, phi, phi, phi2inv], 0),
|
||||
coordinates([phiinv, phiinv, phiinv, phi2], 0),
|
||||
|
||||
coordinates([phi2, phi2inv, 1, 0], 0, true),
|
||||
coordinates([r5, phiinv, phi, 0], 0, true),
|
||||
coordinates([2, 1, phi, phiinv], 0, true),
|
||||
].flat();
|
||||
index_nodes(nodes);
|
||||
// scale_nodes(nodes, 0.5);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
const cell120 = () => {
|
||||
const nodes = make_120cell_vertices();
|
||||
const links = auto_detect_edges(nodes, 4);
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function make_600cell_vertices() {
|
||||
const phi = 0.5 * (1 + Math.sqrt(5));
|
||||
|
||||
const nodes = [
|
||||
coordinates([0, 0, 0, 2], 0),
|
||||
coordinates([1, 1, 1, 1], 1),
|
||||
|
||||
coordinates([phi, 1, 1 / phi, 0], 1, true)
|
||||
].flat();
|
||||
|
||||
index_nodes(nodes);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
function find_by_chord(nodesid, n, d) {
|
||||
const EPSILON = 0.02;
|
||||
return Object.keys(nodesid).filter((n1) => {
|
||||
const d2 = dist2(nodesid[n1], nodesid[n]);
|
||||
return Math.abs(d2 - d ** 2) < EPSILON;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function has_chord(n1, n2, d) {
|
||||
const d2 = dist2(n1, n2);
|
||||
const EPSILON = 0.01;
|
||||
return Math.abs(d2 - d ** 2) < EPSILON;
|
||||
}
|
||||
|
||||
|
||||
function find_all_chords(nodes) {
|
||||
const chords = {};
|
||||
for( let i = 0; i < nodes.length - 1; i++ ) {
|
||||
for( let j = i + 1; j < nodes.length; j++ ) {
|
||||
const n1 = nodes[i];
|
||||
const n2 = nodes[j];
|
||||
const chord = Math.sqrt(dist2(n1, n2)).toFixed(5);
|
||||
if( !(chord in chords) ) {
|
||||
chords[chord] = [];
|
||||
}
|
||||
chords[chord].push([n1, n2]);
|
||||
}
|
||||
}
|
||||
return chords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const cell600 = () => {
|
||||
const nodes = make_600cell_vertices();
|
||||
const links = auto_detect_edges(nodes, 12);
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.08,
|
||||
link_size: 0.02
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bad stuff
|
||||
|
||||
function find_chords(chords, n) {
|
||||
return chords.filter((c) => c[0].id === n.id || c[1].id === n.id);
|
||||
|
@ -621,14 +259,36 @@ function nice_icosa(nodes, icosa) {
|
|||
}
|
||||
|
||||
|
||||
const nodes = make_120cell_vertices();
|
||||
// const chords = find_all_chords(nodes);
|
||||
// const chord3 = chords["1.74806"]; // these are edges of the 600-cells;
|
||||
|
||||
//const pairs60 = neighbour_angles(chord3, nodes[0], "60.000");
|
||||
//const icosas = partition_nodes(pairs60);
|
||||
|
||||
make_120_partition(nodes, nodes[0])
|
||||
function find_by_chord(nodesid, n, d) {
|
||||
const EPSILON = 0.02;
|
||||
return Object.keys(nodesid).filter((n1) => {
|
||||
const d2 = dist2(nodesid[n1], nodesid[n]);
|
||||
return Math.abs(d2 - d ** 2) < EPSILON;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function has_chord(n1, n2, d) {
|
||||
const d2 = dist2(n1, n2);
|
||||
const EPSILON = 0.01;
|
||||
return Math.abs(d2 - d ** 2) < EPSILON;
|
||||
}
|
||||
|
||||
|
||||
function find_all_chords(nodes) {
|
||||
const chords = {};
|
||||
for( let i = 0; i < nodes.length - 1; i++ ) {
|
||||
for( let j = i + 1; j < nodes.length; j++ ) {
|
||||
const n1 = nodes[i];
|
||||
const n2 = nodes[j];
|
||||
const chord = Math.sqrt(dist2(n1, n2)).toFixed(5);
|
||||
if( !(chord in chords) ) {
|
||||
chords[chord] = [];
|
||||
}
|
||||
chords[chord].push([n1, n2]);
|
||||
}
|
||||
}
|
||||
return chords;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// New approach with tetrahedral coloring
|
||||
|
||||
function find_edges(links, nid) {
|
||||
return links.filter((l) => l.source === nid || l.target === nid );
|
||||
}
|
||||
|
||||
|
||||
function find_adjacent(links, nid) {
|
||||
return find_edges(links, nid).map((l) => {
|
||||
if( l.source === nid ) {
|
||||
return l.target;
|
||||
} else {
|
||||
return l.source;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function iterate_graph(nodes, links, n, fn) {
|
||||
const queue = [];
|
||||
const seen = {};
|
||||
const nodes_id = {};
|
||||
nodes.map((n) => nodes_id[n.id] = n);
|
||||
|
||||
queue.push(n.id);
|
||||
seen[n.id] = true;
|
||||
fn(n);
|
||||
|
||||
while( queue.length > 0 ) {
|
||||
const v = queue.shift();
|
||||
find_adjacent(links, v).map((aid) => {
|
||||
if( !(aid in seen) ) {
|
||||
seen[aid] = true;
|
||||
queue.push(aid);
|
||||
fn(nodes_id[aid]);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// stupid tetrahedral labelling
|
||||
// keeps getting stuck
|
||||
|
||||
|
||||
function naive_label_120cell(nodes, links, n) {
|
||||
const nodes_id = {};
|
||||
nodes.map((n) => nodes_id[n.id] = n);
|
||||
iterate_graph(nodes, links, nodes[0], (n) => {
|
||||
const cols = new Set();
|
||||
const nbors = find_adjacent(links, n.id);
|
||||
for( const nb of nbors ) {
|
||||
if( nodes_id[nb].label > 0 ) {
|
||||
cols.add(nodes_id[nb].label);
|
||||
}
|
||||
for( const nb2 of find_adjacent(links, nb) ) {
|
||||
if( nb2 !== n.id && nodes_id[nb].label > 0 ) {
|
||||
cols.add(nodes_id[nb2].label);
|
||||
}
|
||||
}
|
||||
}
|
||||
const pcols = [ 1, 2, 3, 4, 5 ].filter((c) => !cols.has(c));
|
||||
if( pcols.length < 1 ) {
|
||||
console.log(`Got stuck, no options at ${n.id}`);
|
||||
return false;
|
||||
} else {
|
||||
n.label = pcols[0];
|
||||
console.log(`found ${pcols.length} colors for node ${n.id}`);
|
||||
console.log(`applied ${pcols[0]} to node ${n.id}`);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
16
main.js
16
main.js
|
@ -8,6 +8,8 @@ import { FourDGUI } from './gui.js';
|
|||
import { FourDShape } from './fourDShape.js';
|
||||
import { get_colours } from './colours.js';
|
||||
|
||||
const FACE_OPACITY = 0.3;
|
||||
|
||||
// scene, lights and camera
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
|
@ -40,6 +42,17 @@ const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c
|
|||
|
||||
const link_ms = [ material ];
|
||||
|
||||
|
||||
const face_ms = [
|
||||
new THREE.MeshLambertMaterial( { color: 0x44ff44 } )
|
||||
];
|
||||
|
||||
for( const face_m of face_ms ) {
|
||||
face_m.transparent = true;
|
||||
face_m.opacity = FACE_OPACITY;
|
||||
}
|
||||
|
||||
|
||||
const STRUCTURES = {
|
||||
'5-cell': POLYTOPES.cell5(),
|
||||
'16-cell': POLYTOPES.cell16(),
|
||||
|
@ -55,7 +68,8 @@ function createShape(name) {
|
|||
if( shape ) {
|
||||
scene.remove(shape);
|
||||
}
|
||||
shape = new FourDShape(node_ms, link_ms, STRUCTURES[name]);
|
||||
console.log(STRUCTURES[name]);
|
||||
shape = new FourDShape(node_ms, link_ms, face_ms, STRUCTURES[name]);
|
||||
scene.add(shape);
|
||||
}
|
||||
|
||||
|
|
162
polytopes.js
162
polytopes.js
|
@ -1,6 +1,7 @@
|
|||
|
||||
import * as PERMUTE from './permute.js';
|
||||
|
||||
import * as DODECAHEDRA from './dodecahedra.js';
|
||||
|
||||
function index_nodes(nodes, scale) {
|
||||
let i = 1;
|
||||
for( const n of nodes ) {
|
||||
|
@ -171,9 +172,82 @@ export const cell24 = () => {
|
|||
}
|
||||
|
||||
|
||||
// notes on coherent indexing
|
||||
// see table in https://en.wikipedia.org/wiki/120-cell - maybe adapt the
|
||||
// unit radius table
|
||||
// face detection for the 120-cell
|
||||
|
||||
// NOTE: all of these return node ids, not nodes
|
||||
|
||||
// return all the links which connect to a node
|
||||
|
||||
function nodes_links(links, nodeid) {
|
||||
return links.filter((l) => l.source === nodeid || l.target === nodeid);
|
||||
}
|
||||
|
||||
// filter to remove a link to a given id from a set of links
|
||||
|
||||
function not_to_this(link, nodeid) {
|
||||
return !(link.source === nodeid || link.target === nodeid);
|
||||
}
|
||||
|
||||
// given nodes n1, n2, return all neighbours of n2 which are not n1
|
||||
|
||||
function unmutuals(links, n1id, n2id) {
|
||||
const nlinks = nodes_links(links, n2id).filter((l) => not_to_this(l, n1id));
|
||||
return nlinks.map((l) => {
|
||||
if( l.source === n2id ) {
|
||||
return l.target;
|
||||
} else {
|
||||
return l.source;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function fingerprint(ids) {
|
||||
const sids = [...ids];
|
||||
sids.sort();
|
||||
return sids.join(',');
|
||||
}
|
||||
|
||||
|
||||
|
||||
function auto_120cell_faces(links) {
|
||||
const faces = [];
|
||||
const seen = {};
|
||||
let id = 1;
|
||||
for( const edge of links ) {
|
||||
const v1 = edge.source;
|
||||
const v2 = edge.target;
|
||||
const n1 = unmutuals(links, v2, v1);
|
||||
const n2 = unmutuals(links, v1, v2);
|
||||
const shared = [];
|
||||
for( const a of n1 ) {
|
||||
const an = unmutuals(links, v1, a);
|
||||
for( const d of n2 ) {
|
||||
const dn = unmutuals(links, v2, d);
|
||||
for( const x of an ) {
|
||||
for( const y of dn ) {
|
||||
if( x == y ) {
|
||||
shared.push([v1, a, x, d, v2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( shared.length !== 3 ) {
|
||||
console.log(`Bad shared faces for ${edge.id} ${v1} ${v2}`);
|
||||
}
|
||||
for( const face of shared ) {
|
||||
const fp = fingerprint(face);
|
||||
if( !seen[fp] ) {
|
||||
faces.push({ id: id, nodes: face });
|
||||
id++;
|
||||
seen[fp] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return faces;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function make_120cell_vertices() {
|
||||
|
@ -185,13 +259,13 @@ function make_120cell_vertices() {
|
|||
|
||||
const nodes = [
|
||||
PERMUTE.coordinates([0, 0, 2, 2], 0),
|
||||
PERMUTE.coordinates([1, 1, 1, r5], 1),
|
||||
PERMUTE.coordinates([phi, phi, phi, phi2inv], 2),
|
||||
PERMUTE.coordinates([phiinv, phiinv, phiinv, phi2], 3),
|
||||
PERMUTE.coordinates([1, 1, 1, r5], 0),
|
||||
PERMUTE.coordinates([phi, phi, phi, phi2inv], 0),
|
||||
PERMUTE.coordinates([phiinv, phiinv, phiinv, phi2], 0),
|
||||
|
||||
PERMUTE.coordinates([phi2, phi2inv, 1, 0], 4, true),
|
||||
PERMUTE.coordinates([r5, phiinv, phi, 0], 5, true),
|
||||
PERMUTE.coordinates([2, 1, phi, phiinv], 6, true),
|
||||
PERMUTE.coordinates([phi2, phi2inv, 1, 0], 0, true),
|
||||
PERMUTE.coordinates([r5, phiinv, phi, 0], 0, true),
|
||||
PERMUTE.coordinates([2, 1, phi, phiinv], 0, true),
|
||||
].flat();
|
||||
index_nodes(nodes);
|
||||
scale_nodes(nodes, 0.5);
|
||||
|
@ -206,17 +280,79 @@ function label_nodes(nodes, ids, label) {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function label_faces_120cell(nodes, faces, cfaces, label) {
|
||||
const ns = new Set();
|
||||
console.log(`label faces from ${cfaces}`);
|
||||
for( const fid of cfaces ) {
|
||||
const face = faces.filter((f)=> f.id === fid );
|
||||
if( face.length > 0 ) {
|
||||
for ( const nid of face[0].nodes ) {
|
||||
ns.add(nid);
|
||||
}
|
||||
}
|
||||
}
|
||||
label_nodes(nodes, Array.from(ns), label);
|
||||
}
|
||||
|
||||
|
||||
function manual_label_120cell(nodes, links) {
|
||||
|
||||
const faces = auto_120cell_faces(links);
|
||||
const dodecas = DODECAHEDRA.DODECAHEDRA;
|
||||
//const cfaces = [ 1, 2, 4, 145, 169 ];
|
||||
|
||||
let colour = 1;
|
||||
for( const dd of dodecas ) {
|
||||
label_faces_120cell(nodes, faces, dd, colour);
|
||||
colour++;
|
||||
if( colour > 8 ) {
|
||||
colour = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// label_faces_120cell(nodes, faces, [
|
||||
// 1, 2, 4, 169, 626,
|
||||
// 145, 149, 553, 173, 171,
|
||||
// 147, 554
|
||||
// ], 2);
|
||||
|
||||
// label_faces_120cell(nodes, faces, [
|
||||
// 1, 5, 3, 193, 641,
|
||||
// 217, 221, 565, 197, 195,
|
||||
// 219, 566
|
||||
// ], 3);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const cell120 = () => {
|
||||
const nodes = make_120cell_vertices();
|
||||
const links = auto_detect_edges(nodes, 4);
|
||||
|
||||
manual_label_120cell(nodes, links);
|
||||
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,5 +542,3 @@ export const cell600 = () => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
|
||||
//testbed for playing with stuff in node repl
|
||||
|
||||
const THREE =require('three');
|
||||
|
||||
function pandita(a) {
|
||||
const n = a.length;
|
||||
for( let k = n - 2; k >= 0; k-- ) {
|
||||
if( a[k] < a[k + 1] ) {
|
||||
for( let l = n - 1; l >= 0; l-- ) {
|
||||
if( a[k] < a[l] ) {
|
||||
const tmp = a[k];
|
||||
a[k] = a[l];
|
||||
a[l] = tmp;
|
||||
const revtail = a.slice(k + 1);
|
||||
revtail.reverse();
|
||||
for( let i = 0; i < revtail.length; i++ ) {
|
||||
a[k + 1 + i] = revtail[i];
|
||||
}
|
||||
return Math.floor(revtail.length / 2) + 1;
|
||||
}
|
||||
}
|
||||
console.log("Shouldn't get here");
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function permutations_old(a) {
|
||||
a.sort();
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( running ) {
|
||||
const s = pandita(a);
|
||||
if( s ) {
|
||||
ps.push([...a]);
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
function permutations(a) {
|
||||
a.sort();
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( pandita(a) > 0 ) {
|
||||
ps.push([...a]);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
|
||||
function permutations_even(a) {
|
||||
a.sort();
|
||||
let parity = 'even';
|
||||
const ps = [ [...a] ];
|
||||
let running = true;
|
||||
while( running ) {
|
||||
const s = pandita(a);
|
||||
if( s ) {
|
||||
if( parity === 'even' ) {
|
||||
if( s % 2 === 1 ) {
|
||||
parity = 'odd';
|
||||
}
|
||||
} else {
|
||||
if( s % 2 === 1 ) {
|
||||
parity = 'even';
|
||||
}
|
||||
}
|
||||
if( parity === 'even' ) {
|
||||
ps.push([...a]);
|
||||
}
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
// for a given permutation, say [ 1, 1, 0, 0 ], return all
|
||||
// of the valid changes of sign, so:
|
||||
// [ [1, 1, 0, 0 ], [ -1, 1, 0, 0 ], [ 1, -1, 0, 0 ], [-1, -1, 0, 0 ]]
|
||||
// ie don't do it on the zeros
|
||||
|
||||
function expand_sign(a, label) {
|
||||
const expanded = [];
|
||||
const exv = a.map((v) => v ? [ -v, v ] : [ 0 ]);
|
||||
for( const xv of exv[0] ) {
|
||||
for( const yv of exv[1] ) {
|
||||
for( const zv of exv[2] ) {
|
||||
for( const wv of exv[3] ) {
|
||||
expanded.push({label: label, x: xv, y:yv, z:zv, w:wv});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
|
||||
function coordinates(a, id0=1, even=false) {
|
||||
const ps = even ? permutations_even(a) : permutations(a);
|
||||
const coords = [];
|
||||
for( const p of ps ) {
|
||||
const expanded = expand_sign(p, 0);
|
||||
coords.push(...expanded);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function index_nodes(nodes, scale) {
|
||||
let i = 1;
|
||||
for( const n of nodes ) {
|
||||
n["id"] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function scale_nodes(nodes, scale) {
|
||||
for( const n of nodes ) {
|
||||
for( const a of [ 'x', 'y', 'z', 'w' ] ) {
|
||||
n[a] = scale * n[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function auto_detect_edges(nodes, neighbours, debug=false) {
|
||||
const seen = {};
|
||||
const nnodes = nodes.length;
|
||||
const links = [];
|
||||
let id = 1;
|
||||
for( const n1 of nodes ) {
|
||||
const d2 = [];
|
||||
for( const n2 of nodes ) {
|
||||
d2.push({ d2: dist2(n1, n2), id: n2.id });
|
||||
}
|
||||
d2.sort((a, b) => a.d2 - b.d2);
|
||||
const closest = d2.slice(1, neighbours + 1);
|
||||
if( debug ) {
|
||||
console.log(`closest = ${closest.length}`);
|
||||
console.log(closest);
|
||||
}
|
||||
for( const e of closest ) {
|
||||
const ids = [ n1.id, e.id ];
|
||||
ids.sort();
|
||||
const fp = ids.join(',');
|
||||
if( !seen[fp] ) {
|
||||
seen[fp] = true;
|
||||
links.push({ id: id, label: 0, source: n1.id, target: e.id });
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( debug ) {
|
||||
console.log(`Found ${links.length} edges`)
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function make_120cell_vertices() {
|
||||
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 nodes = [
|
||||
coordinates([0, 0, 2, 2], 0),
|
||||
coordinates([1, 1, 1, r5], 0),
|
||||
coordinates([phi, phi, phi, phi2inv], 0),
|
||||
coordinates([phiinv, phiinv, phiinv, phi2], 0),
|
||||
|
||||
coordinates([phi2, phi2inv, 1, 0], 0, true),
|
||||
coordinates([r5, phiinv, phi, 0], 0, true),
|
||||
coordinates([2, 1, phi, phiinv], 0, true),
|
||||
].flat();
|
||||
index_nodes(nodes);
|
||||
// scale_nodes(nodes, 0.5);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// face detection for the 120-cell
|
||||
|
||||
// NOTE: all of these return node ids, not nodes
|
||||
|
||||
// return all the links which connect to a node
|
||||
|
||||
function nodes_links(links, nodeid) {
|
||||
return links.filter((l) => l.source === nodeid || l.target === nodeid);
|
||||
}
|
||||
|
||||
// filter to remove a link to a given id from a set of links
|
||||
|
||||
function not_to_this(link, nodeid) {
|
||||
return !(link.source === nodeid || link.target === nodeid);
|
||||
}
|
||||
|
||||
// given nodes n1, n2, return all neighbours of n2 which are not n1
|
||||
|
||||
function unmutuals(links, n1id, n2id) {
|
||||
const nlinks = nodes_links(links, n2id).filter((l) => not_to_this(l, n1id));
|
||||
return nlinks.map((l) => {
|
||||
if( l.source === n2id ) {
|
||||
return l.target;
|
||||
} else {
|
||||
return l.source;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function fingerprint(ids) {
|
||||
const sids = [...ids];
|
||||
sids.sort();
|
||||
return sids.join(',');
|
||||
}
|
||||
|
||||
|
||||
|
||||
function auto_120cell_faces(links) {
|
||||
const faces = [];
|
||||
const seen = {};
|
||||
let id = 1;
|
||||
for( const edge of links ) {
|
||||
const v1 = edge.source;
|
||||
const v2 = edge.target;
|
||||
const n1 = unmutuals(links, v2, v1);
|
||||
const n2 = unmutuals(links, v1, v2);
|
||||
const shared = [];
|
||||
for( const a of n1 ) {
|
||||
const an = unmutuals(links, v1, a);
|
||||
for( const d of n2 ) {
|
||||
const dn = unmutuals(links, v2, d);
|
||||
for( const x of an ) {
|
||||
for( const y of dn ) {
|
||||
if( x == y ) {
|
||||
shared.push([v1, a, x, d, v2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( shared.length !== 3 ) {
|
||||
console.log(`Bad shared faces for ${edge.id} ${v1} ${v2}`);
|
||||
}
|
||||
for( const face of shared ) {
|
||||
const fp = fingerprint(face);
|
||||
if( !seen[fp] ) {
|
||||
faces.push({ id: id, edge: edge.id, v1: edge.source, v2: edge.target, fingerprint: fp, nodes: face });
|
||||
id++;
|
||||
seen[fp] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return faces;
|
||||
}
|
||||
|
||||
|
||||
// trying to go from faces to dodecahedra
|
||||
|
||||
function shared_vertices(f1, f2) {
|
||||
return f1.nodes.filter((f) => f2.nodes.includes(f));
|
||||
}
|
||||
|
||||
|
||||
function adjacent_faces(f1, f2) {
|
||||
// adjacent faces which share an edge, not just a vertex
|
||||
const intersect = shared_vertices(f1, f2);
|
||||
if( intersect.length < 2 ) {
|
||||
return false;
|
||||
}
|
||||
if( intersect.length > 2 ) {
|
||||
console.log(`warning: faces ${f1.id} and ${f2.id} have too many common vertices`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function find_adjacent_faces(faces, face) {
|
||||
const neighbours = faces.filter((f) => f.id !== face.id && adjacent_faces(f, face));
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function find_dodeca_mutuals(faces, f1, f2) {
|
||||
// for any two adjacent faces, find their common neighbours where
|
||||
// all three share exactly one vertex (this, I think, guarantees that
|
||||
// all are on the same dodecahedron)
|
||||
|
||||
const n1 = find_adjacent_faces(faces, f1);
|
||||
const n2 = find_adjacent_faces(faces, f2);
|
||||
const common = n1.filter((f1) => n2.filter((f2) => f1.id === f2.id).length > 0 );
|
||||
// there's one extra here - the third which has two nodes in common with
|
||||
// both f1 and f2 - filter it out
|
||||
const mutuals = common.filter((cf) => {
|
||||
const shared = cf.nodes.filter((n) => f1.nodes.includes(n) && f2.nodes.includes(n));
|
||||
return shared.length === 1
|
||||
});
|
||||
return mutuals;
|
||||
}
|
||||
|
||||
function find_dodeca_next(faces, dodeca, f1, f2) {
|
||||
// of a pair of mutuals, return the one we haven't already got
|
||||
const m = find_dodeca_mutuals(faces, f1, f2);
|
||||
if( dodeca.filter((f) => f.id === m[0].id ).length > 0 ) {
|
||||
m.shift();
|
||||
}
|
||||
return m[0];
|
||||
}
|
||||
|
||||
// from any two mutual faces, return all the faces in their dodecahedron
|
||||
|
||||
function make_dodecahedron(faces, f1, f2) {
|
||||
const dodecahedron = [ f1, f2 ];
|
||||
|
||||
// take f1 as the 'center', get the other four around it from f2
|
||||
const fs = find_dodeca_mutuals(faces, f1, f2);
|
||||
const f3 = fs[0];
|
||||
const f6 = fs[1];
|
||||
dodecahedron.push(f3);
|
||||
const f4 = find_dodeca_next(faces, dodecahedron, f1, f3);
|
||||
dodecahedron.push(f4);
|
||||
const f5 = find_dodeca_next(faces, dodecahedron, f1, f4);
|
||||
dodecahedron.push(f5);
|
||||
dodecahedron.push(f6);
|
||||
|
||||
// get the next ring
|
||||
|
||||
const f7 = find_dodeca_next(faces, dodecahedron, f6, f2);
|
||||
dodecahedron.push(f7);
|
||||
const f8 = find_dodeca_next(faces, dodecahedron, f2, f3);
|
||||
dodecahedron.push(f8);
|
||||
const f9 = find_dodeca_next(faces, dodecahedron, f3, f4);
|
||||
dodecahedron.push(f9);
|
||||
const f10 = find_dodeca_next(faces, dodecahedron, f4, f5);
|
||||
dodecahedron.push(f10);
|
||||
const f11 = find_dodeca_next(faces, dodecahedron, f5, f6);
|
||||
dodecahedron.push(f11);
|
||||
|
||||
// get the last
|
||||
|
||||
const f12 = find_dodeca_next(faces, dodecahedron, f7, f8);
|
||||
dodecahedron.push(f12);
|
||||
|
||||
return dodecahedron;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
// first face's two dodecahedra
|
||||
|
||||
function find_edge_neighbours(faces, face) {
|
||||
const n1 = face.nodes[0];
|
||||
const n2 = face.nodes[1];
|
||||
return faces.filter((f) => f.id !== face.id && f.nodes.includes(n1) && f.nodes.includes(n2));
|
||||
}
|
||||
|
||||
|
||||
// each face is in two dodecahedra: this returns them both
|
||||
|
||||
function face_to_dodecahedra(faces, f) {
|
||||
const edge_friends = find_edge_neighbours(faces, f);
|
||||
const d1 = make_dodecahedron(faces, f, edge_friends[0]);
|
||||
const d2 = make_dodecahedron(faces, f, edge_friends[1]);
|
||||
return [ d1, d2 ];
|
||||
}
|
||||
|
||||
// brute-force calculation of all dodecahedra
|
||||
|
||||
function dd_fingerprint(dodecahedron) {
|
||||
const ids = dodecahedron.map((face) => face.id);
|
||||
ids.sort()
|
||||
return ids.join(',');
|
||||
}
|
||||
|
||||
function make_120cell_cells(faces) {
|
||||
const dodecas = [];
|
||||
const seen = {};
|
||||
for( const face of faces ) {
|
||||
const dds = face_to_dodecahedra(faces, face);
|
||||
for( const dd of dds ) {
|
||||
const fp = dd_fingerprint(dd);
|
||||
if( ! (fp in seen) ) {
|
||||
//console.log(`added dodeca ${fp}`);
|
||||
dodecas.push(dd);
|
||||
seen[fp] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dodecas;
|
||||
}
|
||||
|
||||
|
||||
const cell120 = () => {
|
||||
const nodes = make_120cell_vertices();
|
||||
const links = auto_detect_edges(nodes, 4);
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
geometry: {
|
||||
node_size: 0.02,
|
||||
link_size: 0.02
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const nodes = make_120cell_vertices();
|
||||
const links = auto_detect_edges(nodes, 4);
|
||||
const faces = auto_120cell_faces(links);
|
||||
const dodecas = make_120cell_cells(faces);
|
||||
|
||||
const ddfaces = dodecas.map((dd) => dd.map((f) => f.id));
|
||||
|
||||
console.log(JSON.stringify(ddfaces));
|
||||
|
||||
// for( const dodeca of dodecas ) {
|
||||
// console.log(dodeca.map((f) => f.id)
|
||||
// }
|
||||
|
Loading…
Reference in New Issue