Compare commits

...

4 Commits

Author SHA1 Message Date
Mike Lynch 1ec7955861 Merge branch 'feature-node-foreshortening' 2024-04-14 16:10:23 +10:00
Mike Lynch 1e5db22c25 scale factor for node foreshortening 2024-04-14 16:08:46 +10:00
Mike Lynch 680f9997f9 Added descriptions for each of the shapes 2024-04-14 16:05:17 +10:00
Mike Lynch cab5878ac8 Added node size scaling with w-foreshortening - looks kind of goofy 2024-04-09 15:05:39 +10:00
4 changed files with 105 additions and 20 deletions

View File

@ -2,7 +2,7 @@ import * as THREE from 'three';
const HYPERPLANE = 2.0; const HYPERPLANE = 2.0;
const NODE_FORESHORTENING = 0.4;
class FourDShape extends THREE.Group { class FourDShape extends THREE.Group {
@ -35,7 +35,7 @@ class FourDShape extends THREE.Group {
} }
} }
makeNode(material, v3) { makeNode(material, v3, scale) {
const geometry = new THREE.SphereGeometry(this.node_size); const geometry = new THREE.SphereGeometry(this.node_size);
const sphere = new THREE.Mesh(geometry, material); const sphere = new THREE.Mesh(geometry, material);
sphere.position.copy(v3); sphere.position.copy(v3);
@ -100,24 +100,41 @@ class FourDShape extends THREE.Group {
} }
fourDtoV3(x, y, z, w, rotations) { fourDtoV3_old(x, y, z, w, rotations) {
const v4 = new THREE.Vector4(x, y, z, w); const v4 = new THREE.Vector4(x, y, z, w);
for ( const m4 of rotations ) { for ( const m4 of rotations ) {
v4.applyMatrix4(m4); v4.applyMatrix4(m4);
} }
const k = this.hyperplane / (this.hyperplane + v4.w); const k = this.fourDscale(v4.w);
return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k); return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
} }
fourDscale(w) {
return this.hyperplane / ( this.hyperplane + w );
}
fourDrotate(x, y, z, w, rotations) {
const v4 = new THREE.Vector4(x, y, z, w);
for ( const m4 of rotations ) {
v4.applyMatrix4(m4);
}
return v4;
}
fourDtoV3(v4) {
const k = this.fourDscale(v4.w);
return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
}
initShapes() { initShapes() {
for( const n of this.nodes4 ) { for( const n of this.nodes4 ) {
const v3 = this.fourDtoV3(n.x, n.y, n.z, n.w, []); const k = this.fourDscale(n.w);
const v3 = new THREE.Vector3(n.x * k, n.y * k, n.z * k);
const material = this.getMaterial(n, this.node_ms); const material = this.getMaterial(n, this.node_ms);
this.nodes3[n.id] = { this.nodes3[n.id] = {
v3: v3, v3: v3,
label: n.label, label: n.label,
object: this.makeNode(material, v3) object: this.makeNode(material, v3, k)
}; };
} }
for( const l of this.links ) { for( const l of this.links ) {
@ -134,10 +151,14 @@ class FourDShape extends THREE.Group {
render3(rotations, nodes_show, links_show) { render3(rotations, nodes_show, links_show) {
this.scalev3 = new THREE.Vector3(this.node_scale, this.node_scale, this.node_scale); this.scalev3 = new THREE.Vector3(this.node_scale, this.node_scale, this.node_scale);
for( const n of this.nodes4 ) { for( const n of this.nodes4 ) {
const v3 = this.fourDtoV3(n.x, n.y, n.z, n.w, rotations); const v4 = this.fourDrotate(n.x, n.y, n.z, n.w, rotations);
const k = this.fourDscale(v4.w);
const v3 = new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k);
const s4 = k * this.node_scale * NODE_FORESHORTENING;
const s3 = new THREE.Vector3(s4, s4, s4);
this.nodes3[n.id].v3 = v3; this.nodes3[n.id].v3 = v3;
this.nodes3[n.id].object.position.copy(v3); this.nodes3[n.id].object.position.copy(v3);
this.nodes3[n.id].object.scale.copy(this.scalev3); 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 || n.label in nodes_show );
} }
for( const l of this.links ) { for( const l of this.links ) {

View File

@ -5,6 +5,15 @@
<title>FourD</title> <title>FourD</title>
<style> <style>
body { margin: 0; } body { margin: 0; }
div#description {
position: fixed;
top: 0;
left: 0;
width: 20%;
z-index: 2;
font-family: sans-serif;
padding: 1em;
}
div#info { div#info {
position: fixed; position: fixed;
bottom:0; bottom:0;
@ -16,6 +25,7 @@
</head> </head>
<body> <body>
<script type="module" src="/main.js"></script> <script type="module" src="/main.js"></script>
<div id="description"></div>
<div id="info">by <a target="_blank" href="https://mikelynch.org/">Mike Lynch</a> - <div id="info">by <a target="_blank" href="https://mikelynch.org/">Mike Lynch</a> -
<a target="_blank" href="https://git.tilde.town/bombinans/fourdjs">source</a></div> <a target="_blank" href="https://git.tilde.town/bombinans/fourdjs">source</a></div>
</body> </body>

14
main.js
View File

@ -85,6 +85,16 @@ function createShape(name, option) {
setVisibility(option ? option : structure.options[0].name); setVisibility(option ? option : structure.options[0].name);
} }
function displayDocs(name) {
const docdiv = document.getElementById("description");
const description = STRUCTURES_BY_NAME[name].description;
if( description ) {
docdiv.innerHTML =`<p>${name}</p><p>${description}</p>`;
} else {
docdiv.innerHTML =`<p>${name}</p>`;
}
}
// initialise gui and read params from URL // initialise gui and read params from URL
// callbacks to do things which are triggered by controls: reset the shape, // callbacks to do things which are triggered by controls: reset the shape,
@ -119,7 +129,8 @@ let gui;
function changeShape() { function changeShape() {
createShape(gui.params.shape); createShape(gui.params.shape);
displayDocs(gui.params.shape);
} }
function setVisibility(option_name) { function setVisibility(option_name) {
@ -184,6 +195,7 @@ renderer.domElement.addEventListener("pointerup", (event) => {
}) })
createShape(gui.params.shape, gui.params.option); createShape(gui.params.shape, gui.params.option);
displayDocs(gui.params.shape);
function animate() { function animate() {
requestAnimationFrame( animate ); requestAnimationFrame( animate );

View File

@ -85,7 +85,12 @@ export const cell5 = () => {
node_size: 0.02, node_size: 0.02,
link_size: 0.02 link_size: 0.02
}, },
options: [ { name: '--' }] options: [ { name: '--' }],
description: `Five tetrahedra joined at ten faces with three
tetrahedra around each edge. The 5-cell is the simplest regular
four-D polytope and the four-dimensional analogue of the tetrahedron.
A corresponding polytope, or simplex, exists for every n-dimensional
space.`,
}; };
}; };
@ -115,7 +120,11 @@ export const cell16 = () => {
node_size: 0.02, node_size: 0.02,
link_size: 0.02 link_size: 0.02
}, },
options: [ { name: '--' }] options: [ { name: '--' }],
description: `Sixteen tetrahedra joined at 32 faces with four
tetrahedra around each edge. The 16-cell is the four-dimensional
analogue of the octahedron and is dual to the tesseract. Every
n-dimensional space has a corresponding polytope in this family.`,
}; };
}; };
@ -146,7 +155,7 @@ export const tesseract = () => {
return { return {
name: 'tesseract', name: 'Tesseract',
nodes: nodes, nodes: nodes,
links: links, links: links,
geometry: { geometry: {
@ -158,6 +167,12 @@ export const tesseract = () => {
{ name: 'one 16-cell', links: [ 0, 1 ] }, { name: 'one 16-cell', links: [ 0, 1 ] },
{ name: 'both 16-cells', links: [ 0, 1, 2 ] }, { name: 'both 16-cells', links: [ 0, 1, 2 ] },
], ],
description: `The most well-known four-dimensional shape, the
tesseract is analogous to the cube, and is constructed by placing two
cubes in parallel hyperplanes and joining their corresponding
vertices. It consists of eight cubes joined at 32 face with three
cubes around each edge, and is dual to the 16-cell. Every
n-dimensional space has a cube analogue or measure polytope.`,
}; };
} }
@ -215,7 +230,11 @@ export const cell24 = () => {
{ name: 'none', links: [ 0 ] }, { name: 'none', links: [ 0 ] },
{ name: 'one 16-cell', links: [ 0, 1 ] }, { name: 'one 16-cell', links: [ 0, 1 ] },
{ name: 'three 16-cells', links: [ 0, 1, 2, 3 ] } { name: 'three 16-cells', links: [ 0, 1, 2, 3 ] }
] ],
description: `A unique object without an exact analogue in higher
or lower dimensions, the 24-cell is made of twenty-four octahedra
joined at 96 faces, with three around each edge. The 24-cell is
self-dual.`,
}; };
} }
@ -398,7 +417,9 @@ export const cell120_layered = (max) => {
link_size: 0.02 link_size: 0.02
}, },
nolink2opacity: true, nolink2opacity: true,
options: options options: options,
description: `This version of the 120-cell lets you explore its
structure by building each layer from the 'north pole' onwards.`,
} }
} }
@ -434,7 +455,11 @@ 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 ] }
] ],
description: `The 120-cell is the four-dimensional analogue of the
dodecahedron, and consists of 120 dodecahedra joined at 720 faces,
with three dodecahedra around each edge. It is dual to the 600-cell,
and five 600-cells can be inscribed in its vertices.`,
} }
} }
@ -565,7 +590,12 @@ export const cell600 = () => {
{ name: "none", links: [ 0 ]}, { name: "none", links: [ 0 ]},
{ name: "one 24-cell", links: [ 0, 1 ] }, { name: "one 24-cell", links: [ 0, 1 ] },
{ name: "five 24-cells", links: [ 0, 1, 2, 3, 4, 5 ] } { name: "five 24-cells", links: [ 0, 1, 2, 3, 4, 5 ] }
] ],
description: `The 600-cell is the four-dimensional analogue of the
icosahedron, and consists of 600 tetrahedra joined at 1200 faces
with five tetrahedra around each edge. It is dual to the 120-cell.
Its 120 vertices can be partitioned into five sets which form the
vertices of five inscribed 24-cells.`,
} }
} }
@ -610,7 +640,9 @@ export const cell600_layered = () => {
link_size: 0.02 link_size: 0.02
}, },
nolink2opacity: true, nolink2opacity: true,
options: options options: options,
description: `This version of the 600-cell lets you explore its
structure by building each layer from the 'north pole' onwards.`,
} }
@ -631,7 +663,7 @@ export const snub24cell = () => {
links.map((l) => l.label = 0); links.map((l) => l.label = 0);
return { return {
name: 'snub 24-cell', name: 'Snub 24-cell',
nodes: nodes, nodes: nodes,
links: links, links: links,
geometry: { geometry: {
@ -639,6 +671,11 @@ export const snub24cell = () => {
link_size: 0.02 link_size: 0.02
}, },
options: [ { name: "--" } ], options: [ { name: "--" } ],
description: `The snub 24-cell is a semiregular polytope which
connects the 24-cell with the 600-cell. It consists of 24 icosahedra
and 120 tetrahedra, and is constructed by removing one of the
five inscribed 24-cells from a 600-cell.`
} }
@ -695,7 +732,7 @@ export const dodecahedron = () => {
} }
return { return {
name: 'dodecahedron', name: 'Dodecahedron',
nodes: nodes, nodes: nodes,
links: links, links: links,
geometry: { geometry: {
@ -706,7 +743,12 @@ export const dodecahedron = () => {
{ name: "none", links: [ 0 ]}, { name: "none", links: [ 0 ]},
{ name: "one tetrahedron", links: [ 0, 1 ] }, { name: "one tetrahedron", links: [ 0, 1 ] },
{ name: "five tetrahedra", links: [ 0, 1, 2, 3, 4, 5 ] } { name: "five tetrahedra", links: [ 0, 1, 2, 3, 4, 5 ] }
] ],
description: `The dodecahedron is a three-dimensional polyhedron
which is included here so that you can see the partition of its
vertices into five interlocked tetrahedra. This structure is the
basis for the partition of the 120-cell's vertices into five
600-cells.`
} }
} }