← Mike Higgins Home
Custom Code Experiment •

ThreeJS: Luma

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js - wireframe + firing nodes</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body { margin: 0; overflow: hidden; background-color: black; }
			#info {
				position: fixed;
				bottom: 16px;
				right: 20px;
				color: rgba(220, 220, 240, 0.5);
				font-size: 14px;
				letter-spacing: 1px;
				pointer-events: none;
				z-index: 10;
				backdrop-filter: blur(4px);
				padding: 6px 16px;
				border-radius: 40px;
				background: rgba(20, 20, 30, 0.3);
				border: 0.5px solid rgba(255,255,255,0.1);
				font-family: Arial, Helvetica, sans-serif;
			}
			#info a { color: rgba(220, 220, 240, 0.8); text-decoration: none; }
		</style>
		<!-- CORS‑safe import map -->
		<script type="importmap">
			{
				"imports": {
					"three": "https://esm.sh/three@0.128.0",
					"three/addons/": "https://esm.sh/three@0.128.0/examples/jsm/"
				}
			}
		</script>
	</head>
	<body>
		<div id="container"></div>

		<script type="module">
			import * as THREE from 'three';

			let container;
			let camera, scene, renderer;
			let wireframe, points;
			let pointsPositions;
			let edgesPositions;
			let lineColorAttribute, pointColorAttribute;

			init();

			function init() {

				container = document.getElementById( 'container' );

				camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 1, 3500 );
				camera.position.z = 2750;

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0x050505 );
				scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );

				const triangles = 5000;
				const geometry = new THREE.BufferGeometry();

				const positions = [];
				const normals = [];
				const colors = [];

				const color = new THREE.Color();

				const n = 800, n2 = n / 2;
				const d = 12, d2 = d / 2;

				const pA = new THREE.Vector3();
				const pB = new THREE.Vector3();
				const pC = new THREE.Vector3();

				const cb = new THREE.Vector3();
				const ab = new THREE.Vector3();

				for ( let i = 0; i < triangles; i ++ ) {

					const x = Math.random() * n - n2;
					const y = Math.random() * n - n2;
					const z = Math.random() * n - n2;

					const ax = x + Math.random() * d - d2;
					const ay = y + Math.random() * d - d2;
					const az = z + Math.random() * d - d2;

					const bx = x + Math.random() * d - d2;
					const by = y + Math.random() * d - d2;
					const bz = z + Math.random() * d - d2;

					const cx = x + Math.random() * d - d2;
					const cy = y + Math.random() * d - d2;
					const cz = z + Math.random() * d - d2;

					positions.push( ax, ay, az );
					positions.push( bx, by, bz );
					positions.push( cx, cy, cz );

					pA.set( ax, ay, az );
					pB.set( bx, by, bz );
					pC.set( cx, cy, cz );

					cb.subVectors( pC, pB );
					ab.subVectors( pA, pB );
					cb.cross( ab );
					cb.normalize();

					const nx = cb.x;
					const ny = cb.y;
					const nz = cb.z;

					normals.push( nx * 32767, ny * 32767, nz * 32767 );
					normals.push( nx * 32767, ny * 32767, nz * 32767 );
					normals.push( nx * 32767, ny * 32767, nz * 32767 );

					const vx = ( x / n ) + 0.5;
					const vy = ( y / n ) + 0.5;
					const vz = ( z / n ) + 0.5;

					color.setRGB( vx, vy, vz );

					colors.push( color.r * 255, color.g * 255, color.b * 255 );
					colors.push( color.r * 255, color.g * 255, color.b * 255 );
					colors.push( color.r * 255, color.g * 255, color.b * 255 );

				}

				const positionAttribute = new THREE.Float32BufferAttribute( positions, 3 );
				const normalAttribute = new THREE.Int16BufferAttribute( normals, 3 );
				const colorAttribute = new THREE.Uint8BufferAttribute( colors, 3 );

				normalAttribute.normalized = true;
				colorAttribute.normalized = true;

				geometry.setAttribute( 'position', positionAttribute );
				geometry.setAttribute( 'normal', normalAttribute );
				geometry.setAttribute( 'color', colorAttribute );

				geometry.computeBoundingSphere();

				pointsPositions = positions;

				const edgesGeo = new THREE.EdgesGeometry( geometry );
				edgesPositions = edgesGeo.attributes.position.array.slice();

				const lineGeo = new THREE.BufferGeometry();
				lineGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( edgesPositions, 3 ) );

				const lineCount = edgesPositions.length / 3;
				const lineColors = [];
				for ( let i = 0; i < lineCount; i ++ ) {
					const ix = i * 3;
					const x = edgesPositions[ ix ];
					const y = edgesPositions[ ix + 1 ];
					const z = edgesPositions[ ix + 2 ];

					let hue = ( ( x / 800 ) * 0.5 + ( y / 800 ) * 0.3 + ( z / 800 ) * 0.2 + 1.0 ) % 1.0;

					const rgb = hslToRgb( hue, 0.8, 0.3 );
					lineColors.push( rgb[0], rgb[1], rgb[2] );
				}
				lineColorAttribute = new THREE.Float32BufferAttribute( lineColors, 3 );
				lineGeo.setAttribute( 'color', lineColorAttribute );

				const lineMat = new THREE.LineBasicMaterial( {
					vertexColors: true,
					linewidth: 1,
					depthTest: false
				} );
				wireframe = new THREE.LineSegments( lineGeo, lineMat );
				scene.add( wireframe );

				const pointsGeo = new THREE.BufferGeometry();
				pointsGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );

				const pointCount = positions.length / 3;
				const pointColors = [];
				for ( let i = 0; i < pointCount; i ++ ) {
					pointColors.push( 0.8, 0.5, 0.1 );
				}
				pointColorAttribute = new THREE.Float32BufferAttribute( pointColors, 3 );
				pointsGeo.setAttribute( 'color', pointColorAttribute );

				const pointsMat = new THREE.PointsMaterial( {
					size: 6.0,
					vertexColors: true,
					sizeAttenuation: true,
					transparent: true,
					opacity: 0.95
				} );
				points = new THREE.Points( pointsGeo, pointsMat );
				scene.add( points );

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setAnimationLoop( animate );
				container.appendChild( renderer.domElement );

				window.addEventListener( 'resize', onWindowResize );
			}

			function hslToRgb( h, s, l ) {
				let r, g, b;
				if ( s === 0 ) {
					r = g = b = l;
				} else {
					const hue2rgb = ( p, q, t ) => {
						if ( t < 0 ) t += 1;
						if ( t > 1 ) t -= 1;
						if ( t < 1/6 ) return p + ( q - p ) * 6 * t;
						if ( t < 1/2 ) return q;
						if ( t < 2/3 ) return p + ( q - p ) * ( 2/3 - t ) * 6;
						return p;
					};
					const q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s;
					const p = 2 * l - q;
					r = hue2rgb( p, q, h + 1/3 );
					g = hue2rgb( p, q, h );
					b = hue2rgb( p, q, h - 1/3 );
				}
				return [ r, g, b ];
			}

			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize( window.innerWidth, window.innerHeight );
			}

			function animate() {
				const time = Date.now() * 0.001;

				wireframe.rotation.x = time * 0.25;
				wireframe.rotation.y = time * 0.5;
				points.rotation.x = time * 0.25;
				points.rotation.y = time * 0.5;

				const lineColors = wireframe.geometry.attributes.color.array;
				const lPos = edgesPositions;
				const lCount = lPos.length / 3;

				const lineSpeed = 3.5;
				const lineFreq = 0.008;

				for ( let i = 0; i < lCount; i ++ ) {
					const ix = i * 3;
					const x = lPos[ ix ];
					const y = lPos[ ix + 1 ];
					const z = lPos[ ix + 2 ];

					const wave1 = Math.sin( time * lineSpeed + x * lineFreq + y * lineFreq * 0.7 + z * lineFreq * 1.1 );
					const wave2 = Math.cos( time * 2.2 + y * lineFreq * 1.3 + z * lineFreq * 0.9 );
					const wave3 = Math.sin( time * 1.4 + x * lineFreq * 1.2 + z * lineFreq * 0.8 );

					let t = ( wave1 * 0.6 + wave2 * 0.3 + wave3 * 0.2 + 1 ) / 2;
					t = Math.pow( t, 1.2 ) * 1.5;

					const r0 = lineColors[ ix ];
					const g0 = lineColors[ ix + 1 ];
					const b0 = lineColors[ ix + 2 ];

					const mix = Math.min( t, 1.0 );
					const r = r0 * (1 - mix) + 1.0 * mix;
					const g = g0 * (1 - mix) + 1.0 * mix;
					const b = b0 * (1 - mix) + 1.0 * mix;

					lineColors[ ix ] = r;
					lineColors[ ix + 1 ] = g;
					lineColors[ ix + 2 ] = b;
				}
				wireframe.geometry.attributes.color.needsUpdate = true;

				const pointColors = points.geometry.attributes.color.array;
				const pPos = pointsPositions;
				const pCount = pPos.length / 3;

				const pointSpeed = 2.8;
				const pointFreq = 0.006;

				for ( let i = 0; i < pCount; i ++ ) {
					const ix = i * 3;
					const x = pPos[ ix ];
					const y = pPos[ ix + 1 ];
					const z = pPos[ ix + 2 ];

					const wave1 = Math.sin( time * pointSpeed + x * pointFreq + y * pointFreq * 0.8 + z * pointFreq * 1.2 );
					const wave2 = Math.cos( time * 1.8 + y * pointFreq * 1.5 + z * pointFreq * 0.7 );
					const wave3 = Math.sin( time * 1.0 + x * pointFreq * 0.9 + z * pointFreq * 1.1 );

					let t = ( wave1 * 0.5 + wave2 * 0.3 + wave3 * 0.2 + 1 ) / 2;
					t = Math.pow( t, 1.8 ) * 1.5;

					const r0 = 0.8, g0 = 0.5, b0 = 0.1;
					const mix = Math.min( t, 1.0 );
					const r = r0 * (1 - mix) + 1.0 * mix;
					const g = g0 * (1 - mix) + 1.0 * mix;
					const b = b0 * (1 - mix) + 0.8 * mix;

					pointColors[ ix ] = r;
					pointColors[ ix + 1 ] = g;
					pointColors[ ix + 2 ] = b;
				}
				points.geometry.attributes.color.needsUpdate = true;

				renderer.render( scene, camera );
			}
		</script>
	</body>
</html>