Files
thrive/src/matrix.js
T

138 lines
5.0 KiB
JavaScript

import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js';
import { createStage } from './lib/stage.js';
import { makeTube } from './lib/edgeTubes.js';
// The vector equilibrium (cuboctahedron): the only form where every vertex is
// exactly as far from the centre as from its neighbours - Fuller's "zero point"
// of the isotropic vector matrix. Its 12 vertices plus centre are the 13 circles
// of Metatron's Cube, and 12 spheres kiss one central sphere on those points.
const { scene, colors, onUpdate } = createStage();
const params = {
scale: 3,
edges: true,
spokes: true,
fill: true,
spheres: false,
spin: false,
};
const group = new THREE.Group();
scene.add( group );
let parts = [];
let sphereRings = [];
function renderMatrix() {
for ( const part of parts ) group.remove( part );
parts = [];
sphereRings = [];
const a = params.scale;
// all permutations of (+-a, +-a, 0): the 12 cuboctahedron vertices
const vertices = [];
for ( const s1 of [ -1, 1 ] ) {
for ( const s2 of [ -1, 1 ] ) {
vertices.push( new THREE.Vector3( s1 * a, s2 * a, 0 ) );
vertices.push( new THREE.Vector3( s1 * a, 0, s2 * a ) );
vertices.push( new THREE.Vector3( 0, s1 * a, s2 * a ) );
}
}
const edgeLength = a * Math.SQRT2; // equals the distance from centre to vertex
const edgeMaterial = new THREE.MeshBasicMaterial( { color: colors.line } );
const spokeMaterial = new THREE.MeshBasicMaterial( { color: colors.line, opacity: 0.35, transparent: true } );
const edges = new THREE.Group();
for ( let i = 0; i < vertices.length; i++ ) {
for ( let j = i + 1; j < vertices.length; j++ ) {
if ( Math.abs( vertices[ i ].distanceTo( vertices[ j ] ) - edgeLength ) < 1e-6 ) {
edges.add( makeTube( vertices[ i ], vertices[ j ], edgeMaterial ) );
}
}
}
edges.visible = params.edges;
group.add( edges );
parts.push( edges );
// the 12 radial spokes are as long as the edges - that's the equilibrium
const spokes = new THREE.Group();
const centre = new THREE.Vector3( 0, 0, 0 );
for ( const vertex of vertices ) {
spokes.add( makeTube( centre, vertex, spokeMaterial, 0.02 ) );
}
spokes.visible = params.spokes;
group.add( spokes );
parts.push( spokes );
const fillMaterial = new THREE.MeshStandardMaterial( {
color: colors.fills.fire,
metalness: 0.7,
roughness: 0.3,
opacity: 0.8,
transparent: true,
} );
// the fill casts no shadow: only the edges and spokes project, as outlines
const fill = new THREE.Mesh( new ConvexGeometry( vertices ), fillMaterial );
fill.visible = params.fill;
group.add( fill );
parts.push( fill );
// 12 spheres of radius edge/2 kiss each other on the edges and all kiss the
// 13th sphere in the centre: closest packing of equal spheres
const sphereRadius = edgeLength / 2;
const sphereMaterial = new THREE.MeshStandardMaterial( {
color: colors.fills.water,
metalness: 0.7,
roughness: 0.3,
opacity: 0.8,
transparent: true,
} );
const spheres = new THREE.Group();
const sphereGeometry = new THREE.SphereGeometry( sphereRadius, 48, 48 );
const sphereRingGeometry = new THREE.TorusGeometry( sphereRadius, 0.025, 16, 96 );
const sphereRingMaterial = new THREE.MeshBasicMaterial( { color: colors.line, opacity: 0.5, transparent: true } );
// the spheres themselves cast nothing; each ring draws the outline and
// projects it onto the wall
for ( const point of [ centre, ...vertices ] ) {
const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
sphere.position.copy( point );
spheres.add( sphere );
const ring = new THREE.Mesh( sphereRingGeometry, sphereRingMaterial );
ring.rotation.y = Math.PI / 2;
ring.position.copy( point );
ring.castShadow = true;
spheres.add( ring );
sphereRings.push( ring );
}
spheres.visible = params.spheres;
group.add( spheres );
parts.push( spheres );
}
renderMatrix();
onUpdate( ( time ) => {
if ( params.spin ) group.rotation.y = time * 0.15;
// the outline rings counter the group's spin so they keep facing the wall
for ( const ring of sphereRings ) {
ring.rotation.y = Math.PI / 2 - group.rotation.y;
}
} );
const gui = new GUI();
if ( window.innerWidth < 700 ) gui.close();
gui.add( params, 'scale', 1, 4 ).step( 0.1 ).onChange( renderMatrix );
gui.add( params, 'edges' ).onChange( ( value ) => { parts[ 0 ].visible = value; } );
gui.add( params, 'spokes' ).onChange( ( value ) => { parts[ 1 ].visible = value; } );
gui.add( params, 'fill' ).onChange( ( value ) => { parts[ 2 ].visible = value; } );
gui.add( params, 'spheres' ).onChange( ( value ) => { parts[ 3 ].visible = value; } );
gui.add( params, 'spin' );