TECH DETAILS

Matrix Earth - Day 2

Advanced Level

Table of Contents

01 / Three.js Setup

Three.js abstracts WebGL complexity. We need a scene (container), camera (viewpoint), and renderer (draws to canvas).

// Import Three.js from CDN import * as THREE from 'three'; // Create the scene - container for all objects const scene = new THREE.Scene(); // Perspective camera: FOV, aspect ratio, near/far planes const camera = new THREE.PerspectiveCamera( 60, // Field of view (degrees) window.innerWidth / window.innerHeight, // Aspect ratio 0.1, // Near clipping plane 1000 // Far clipping plane ); camera.position.z = 3; // Move camera back to see the globe // WebGL renderer with antialiasing and transparency const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true // Transparent background }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);

02 / Fibonacci Sphere Distribution

To distribute points evenly on a sphere, we use the Fibonacci spiral. Random placement creates clusters; Fibonacci ensures uniform coverage.

function fibonacciSphere(numPoints, radius) { const points = []; const phi = Math.PI * (3 - Math.sqrt(5)); // Golden angle ~2.4 rad for (let i = 0; i < numPoints; i++) { // Y goes from 1 to -1 (top to bottom of sphere) const y = 1 - (i / (numPoints - 1)) * 2; // Radius at this height (Pythagorean theorem) const radiusAtY = Math.sqrt(1 - y * y); // Golden angle increment const theta = phi * i; // Convert to Cartesian coordinates const x = Math.cos(theta) * radiusAtY; const z = Math.sin(theta) * radiusAtY; points.push(new THREE.Vector3( x * radius, y * radius, z * radius )); } return points; }
Why Golden Angle?

The golden angle (137.5deg) is irrational, so spiral points never align perfectly - creating optimal spacing without clustering.

03 / Custom GLSL Shaders

GLSL shaders run on the GPU, enabling effects like the Matrix falling characters. Vertex shader positions geometry; fragment shader colors pixels.

Vertex Shader

// Runs once per vertex varying vec2 vUv; // Pass UV coords to fragment varying float vElevation; // Pass height for coloring void main() { vUv = uv; // Project vertex to screen space gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); // Calculate elevation for continent coloring vElevation = position.y; }

Fragment Shader (Matrix Effect)

uniform float uTime; // Animated time value varying vec2 vUv; void main() { // Create falling effect based on time float fall = fract(vUv.y + uTime * 0.3); // Fade out at bottom of "fall" float alpha = smoothstep(0.0, 0.5, fall); // Matrix green with glow vec3 color = vec3(0.0, 1.0, 0.0) * (0.5 + 0.5 * alpha); gl_FragColor = vec4(color, alpha); }

04 / Continent Detection

We use a simplified polygon-based approach to detect if a point is on land or water, based on latitude/longitude.

// Simplified continent boundaries (lat/lon polygons) const continents = { northAmerica: [ { lat: 70, lon: -170 }, { lat: 70, lon: -50 }, { lat: 15, lon: -80 }, // ... more points ], // ... other continents }; function isOnLand(lat, lon) { for (const continent of Object.values(continents)) { if (pointInPolygon(lat, lon, continent)) { return true; } } return false; } // Ray casting algorithm for point-in-polygon test function pointInPolygon(lat, lon, polygon) { let inside = false; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const yi = polygon[i].lat, xi = polygon[i].lon; const yj = polygon[j].lat, xj = polygon[j].lon; if (((yi > lat) !== (yj > lat)) && (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi)) { inside = !inside; } } return inside; }

05 / Atmosphere Glow Effect

The glow is created with a slightly larger sphere using a custom shader that fades based on view angle.

// Atmosphere mesh - slightly larger than globe const atmosphereGeometry = new THREE.SphereGeometry(1.15, 64, 64); const atmosphereMaterial = new THREE.ShaderMaterial({ vertexShader: atmosphereVertexShader, fragmentShader: ` varying vec3 vNormal; void main() { // Intensity based on view angle (Fresnel-like) float intensity = pow(0.7 - dot(vNormal, vec3(0, 0, 1)), 2.0); gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0) * intensity; } `, blending: THREE.AdditiveBlending, // Glow adds to background side: THREE.BackSide, // Render inside of sphere transparent: true });
Fresnel Effect

The pow(0.7 - dot(normal, viewDir), 2.0) creates edge glow - surfaces facing away from the camera appear brighter.

06 / Animation & Controls

The globe auto-rotates and responds to user interaction (drag to rotate manually).

let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; // Animation loop function animate() { requestAnimationFrame(animate); // Auto-rotation when not dragging if (!isDragging) { globe.rotation.y += 0.002; } // Update shader time uniform for Matrix effect material.uniforms.uTime.value = performance.now() / 1000; renderer.render(scene, camera); } // Mouse drag controls canvas.addEventListener('mousedown', () => isDragging = true); canvas.addEventListener('mouseup', () => isDragging = false); canvas.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - previousMousePosition.x; const deltaY = e.clientY - previousMousePosition.y; globe.rotation.y += deltaX * 0.005; globe.rotation.x += deltaY * 0.005; previousMousePosition = { x: e.clientX, y: e.clientY }; });