/* eslint-disable react/no-unknown-property */ 'use client'; import React, { useRef, useMemo, useState, useEffect } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; import { OrbitControls, PerspectiveCamera, Stars, Line } from '@react-three/drei'; import { EffectComposer, Bloom } from '@react-three/postprocessing'; import * as THREE from 'three'; // ═══════════════════════════════════════════════════════════════ // CORE ALGORITHMS // ═══════════════════════════════════════════════════════════════ // Deterministic hash function sr(seed: number) { const x = Math.sin(seed * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); } // ── TERRAIN HEIGHT FUNCTION ── // SINGLE SOURCE OF TRUTH for terrain elevation. const TERRAIN_R = 400; const TERRAIN_CURVATURE = 0.00012; // planetary curvature function terrainY(x: number, z: number): number { const dist = Math.sqrt(x * x + z * z); const edge = Math.min(1, dist / TERRAIN_R); const h = Math.max(0, 1 - edge * 1.2); // Planetary curvature — drops off like the surface of a sphere const curvature = -dist * dist * TERRAIN_CURVATURE; // Multi-octave noise for rolling hills let y = 0; y += Math.sin(x * 0.025 + 1.2) * Math.cos(z * 0.02 + 0.8) * 3.5 * h; y += Math.sin(x * 0.05 + 3.0) * Math.cos(z * 0.04 + 2.0) * 1.8 * h; y += Math.sin(x * 0.1 + 5.0) * Math.cos(z * 0.08 + 4.0) * 0.6 * h; y += Math.sin(x * 0.015) * Math.cos(z * 0.018 + 6.0) * 4.0 * h; // Edge drop-off y -= edge * edge * 40; return y + curvature; } function onTerrain(x: number, z: number): THREE.Vector3 { return new THREE.Vector3(x, terrainY(x, z), z); } // ── CATENARY CABLE ── function catenarySag(t: number): number { const u = t * 2 - 1; const cEdge = Math.cosh(2.5); return -(cEdge - Math.cosh(u * 2.5)) / (cEdge - 1); } function catenaryPoints( p1: THREE.Vector3, p2: THREE.Vector3, sag: number, segments: number = 20, ): THREE.Vector3[] { const pts: THREE.Vector3[] = []; for (let i = 0; i <= segments; i++) { const t = i / segments; pts.push(new THREE.Vector3( p1.x + (p2.x - p1.x) * t, p1.y + (p2.y - p1.y) * t + catenarySag(t) * sag, p1.z + (p2.z - p1.z) * t, )); } return pts; } // ── POISSON DISC SAMPLING ── function poissonDisc( count: number, radius: number, bounds: number, seed: number, exclusions: THREE.Vector3[] = [], exRadius = 18, ): THREE.Vector3[] { const pts: THREE.Vector3[] = []; const attempts = count * 15; for (let i = 0; i < attempts && pts.length < count; i++) { const x = (sr(seed + i * 17) - 0.5) * bounds * 2; const z = (sr(seed + i * 23 + 1000) - 0.5) * bounds * 2; if (Math.sqrt(x * x + z * z) > TERRAIN_R * 0.7) continue; if (pts.some(p => Math.hypot(x - p.x, z - p.z) < radius)) continue; if (exclusions.some(p => Math.hypot(x - p.x, z - p.z) < exRadius)) continue; pts.push(onTerrain(x, z)); } return pts; } function nearest(pt: THREE.Vector3, list: THREE.Vector3[]): THREE.Vector3 { let best = list[0], bestD = Infinity; for (const c of list) { const d = Math.hypot(pt.x - c.x, pt.z - c.z); if (d < bestD) { bestD = d; best = c; } } return best; } // ═══════════════════════════════════════════════════════════════ // PROCEDURAL WORLD // ═══════════════════════════════════════════════════════════════ // Hub substations — central distribution points const HUBS = [onTerrain(-10, 20), onTerrain(60, -5)]; // Cities at the periphery with their substations const CITY_DATA = [ { city: onTerrain(150, -80), sub: onTerrain(125, -65) }, { city: onTerrain(140, 85), sub: onTerrain(115, 70) }, { city: onTerrain(-15, -150), sub: onTerrain(-12, -125) }, { city: onTerrain(-140, 40), sub: onTerrain(-115, 35) }, { city: onTerrain(30, 160), sub: onTerrain(28, 135) }, ]; // Wind farm generation type WindFarm = { sub: THREE.Vector3; turbines: THREE.Vector3[]; chains: THREE.Vector3[][]; }; function placeWindFarm( cx: number, cz: number, cols: number, rows: number, spacing: number, subOffX: number, subOffZ: number, rotDeg: number, seed: number, ): WindFarm { const rad = (rotDeg * Math.PI) / 180; const co = Math.cos(rad), si = Math.sin(rad); const grid: THREE.Vector3[][] = []; const turbines: THREE.Vector3[] = []; for (let r = 0; r < rows; r++) { const row: THREE.Vector3[] = []; for (let c = 0; c < cols; c++) { const lx = (c - (cols - 1) / 2) * spacing + (sr(seed + r * 10 + c) - 0.5) * 3; const lz = (r - (rows - 1) / 2) * spacing + (sr(seed + r * 10 + c + 50) - 0.5) * 3; const wx = cx + lx * co - lz * si; const wz = cz + lx * si + lz * co; const pos = onTerrain(wx, wz); turbines.push(pos); row.push(pos); } grid.push(row); } const sub = onTerrain(cx + subOffX, cz + subOffZ); const chains: THREE.Vector3[][] = grid.map(row => [...row, sub]); return { sub, turbines, chains }; } const WIND_FARMS: WindFarm[] = [ placeWindFarm(-120, 90, 4, 3, 16, 40, -15, 5, 101), placeWindFarm(25, 130, 3, 3, 15, -28, -20, 12, 201), placeWindFarm(-75, -35, 3, 3, 14, 28, 18, -8, 301), placeWindFarm(100, 75, 4, 2, 14, -30, -18, 3, 401), placeWindFarm(-150, -15, 3, 2, 16, 28, 10, 0, 501), placeWindFarm(130, -90, 3, 3, 14, -32, 15, -6, 601), placeWindFarm(-90, 140, 3, 2, 15, 24, -20, 18, 701), placeWindFarm(65, -120, 3, 3, 14, -22, 25, 0, 801), placeWindFarm(-40, 60, 2, 3, 14, 20, -16, 10, 901), placeWindFarm(170, 20, 3, 2, 15, -30, 12, -3, 1001), ]; // Solar parks with substations at edge type SolarPark = { pos: THREE.Vector3; sub: THREE.Vector3; rows: number; cols: number }; const SOLAR_PARKS: SolarPark[] = [ { pos: onTerrain(-170, -80), sub: onTerrain(-145, -70), rows: 7, cols: 10 }, { pos: onTerrain(-155, -105), sub: onTerrain(-145, -70), rows: 6, cols: 8 }, { pos: onTerrain(145, 28), sub: onTerrain(130, 20), rows: 6, cols: 9 }, { pos: onTerrain(-70, -100), sub: onTerrain(-58, -85), rows: 5, cols: 7 }, { pos: onTerrain(85, -30), sub: onTerrain(75, -20), rows: 6, cols: 8 }, { pos: onTerrain(-125, 50), sub: onTerrain(-110, 42), rows: 5, cols: 6 }, { pos: onTerrain(50, -170), sub: onTerrain(48, -148), rows: 5, cols: 7 }, { pos: onTerrain(-30, -160), sub: onTerrain(-25, -138), rows: 6, cols: 8 }, ]; // Deduplicated solar subs const solarSubSet = new Set(); const SOLAR_SUBS: THREE.Vector3[] = []; SOLAR_PARKS.forEach(sp => { const k = `${sp.sub.x.toFixed(1)},${sp.sub.z.toFixed(1)}`; if (!solarSubSet.has(k)) { solarSubSet.add(k); SOLAR_SUBS.push(sp.sub); } }); // Collect all generation subs and auto-route to hubs const ALL_GEN_SUBS = [...WIND_FARMS.map(f => f.sub), ...SOLAR_SUBS]; const ALL_CITY_SUBS = CITY_DATA.map(c => c.sub); const ALL_SUBS = [...ALL_GEN_SUBS, ...HUBS, ...ALL_CITY_SUBS]; // Tower routing const TWR_H = 12; function lerpRoute(from: THREE.Vector3, to: THREE.Vector3, n: number): THREE.Vector3[] { const pts = [from]; for (let i = 1; i <= n; i++) { const t = i / (n + 1); pts.push(onTerrain(from.x + (to.x - from.x) * t, from.z + (to.z - from.z) * t)); } pts.push(to); return pts; } function autoTowerCount(a: THREE.Vector3, b: THREE.Vector3) { return Math.max(2, Math.floor(Math.hypot(a.x - b.x, a.z - b.z) / 28)); } const HSVL_ROUTES: THREE.Vector3[][] = []; ALL_GEN_SUBS.forEach(sub => { const hub = nearest(sub, HUBS); HSVL_ROUTES.push(lerpRoute(sub, hub, autoTowerCount(sub, hub))); }); HSVL_ROUTES.push(lerpRoute(HUBS[0], HUBS[1], autoTowerCount(HUBS[0], HUBS[1]))); CITY_DATA.forEach(cd => { const hub = nearest(cd.sub, HUBS); HSVL_ROUTES.push(lerpRoute(hub, cd.sub, autoTowerCount(hub, cd.sub))); }); // Distribution cables: solar→sub, sub→city const DIST_CABLES: [THREE.Vector3, THREE.Vector3][] = [ ...SOLAR_PARKS.map(sp => [sp.pos, sp.sub] as [THREE.Vector3, THREE.Vector3]), ...CITY_DATA.map(cd => [cd.sub, cd.city] as [THREE.Vector3, THREE.Vector3]), ]; // All infrastructure for exclusion zones const ALL_INFRA: THREE.Vector3[] = [ ...ALL_SUBS, ...CITY_DATA.map(c => c.city), ...WIND_FARMS.flatMap(f => f.turbines), ...SOLAR_PARKS.map(s => s.pos), ]; // Forest positions via Poisson disc const FOREST_POSITIONS = poissonDisc(80, 22, 220, 5000, ALL_INFRA, 18); // ═══════════════════════════════════════════════════════════════ // TERRAIN MESH // ═══════════════════════════════════════════════════════════════ const Terrain = () => { const geometry = useMemo(() => { const geo = new THREE.CircleGeometry(TERRAIN_R, 256); const pos = geo.attributes.position; for (let i = 0; i < pos.count; i++) { const x = pos.getX(i); const z = -pos.getY(i); pos.setX(i, x); pos.setY(i, terrainY(x, z)); pos.setZ(i, z); } geo.computeVertexNormals(); return geo; }, []); return ( {/* Subtle grid glow on terrain */} ); }; // ═══════════════════════════════════════════════════════════════ // COMPONENTS // ═══════════════════════════════════════════════════════════════ // ── SOLAR FIELD ── const SolarField = ({ position, rows = 5, cols = 7 }: { position: THREE.Vector3; rows?: number; cols?: number }) => ( {Array.from({ length: rows }).map((_, r) => Array.from({ length: cols }).map((_, c) => ( {/* Post */} {/* Panel */} {/* Grid lines on panel */} {/* Subtle blue reflection shine */} )) )} ); // ── WIND TURBINE ── const TURBINE_H = 8; const BLADE_L = 4; const WindTurbine = ({ position, seed = 0 }: { position: THREE.Vector3; seed?: number }) => { const bladesRef = useRef(null); const speed = 1.5 + sr(seed * 7) * 1.5; useFrame((s) => { if (bladesRef.current) bladesRef.current.rotation.z = s.clock.elapsedTime * speed; }); return ( {/* Tower — tapered */} {/* Nacelle */} {/* Rotor hub + blades */} {[0, 1, 2].map(i => ( ))} ); }; // ── SUBSTATION ── const Substation = ({ position, size = 'small' }: { position: THREE.Vector3; size?: 'small' | 'large' }) => { const s = size === 'large' ? 1.5 : 0.9; const pulseRef = useRef(null); useFrame((st) => { if (pulseRef.current) { const pulse = 1 + Math.sin(st.clock.elapsedTime * 3) * 0.2; pulseRef.current.scale.setScalar(pulse); } }); return ( {/* Fenced area */} {/* Ground pad */} {/* Transformers */} {[[-1 * s, -0.6 * s], [1 * s, -0.6 * s], [0, 0.7 * s]].map(([x, z], i) => ( {/* Wireframe overlay */} {/* Insulator */} ))} {/* Bus bar */} {/* Pulsing energy indicator */} ); }; // ── CITY ── const City = ({ position, seed = 0 }: { position: THREE.Vector3; seed?: number }) => { const buildings = useMemo(() => { const items = []; const count = 14 + Math.floor(sr(seed * 3) * 10); for (let i = 0; i < count; i++) { const angle = sr(seed * 100 + i * 17) * Math.PI * 2; const radius = 0.8 + sr(seed * 100 + i * 29) * 8; const height = 1.2 + sr(seed * 100 + i * 37) * 5; items.push({ x: Math.cos(angle) * radius, z: Math.sin(angle) * radius, height, width: 0.5 + sr(seed * 100 + i * 43) * 1, depth: 0.5 + sr(seed * 100 + i * 47) * 0.8, rows: Math.max(1, Math.floor(height / 0.8)), }); } return items; }, [seed]); return ( {buildings.map((b, i) => ( {/* Building body */} {/* Building edge glow */} {/* Windows — glowing */} {Array.from({ length: b.rows }).map((_, row) => { const wc = Math.max(1, Math.floor(b.width / 0.35)); return Array.from({ length: wc }).map((_, col) => { if (sr(seed * 1000 + i * 100 + row * 10 + col) < 0.2) return null; const isWarm = sr(seed * 2000 + i * 50 + row + col) > 0.4; const color = isWarm ? '#ffcc33' : '#88ccff'; const intensity = 0.6 + sr(seed * 3000 + i * 30 + row * 5 + col) * 0.4; const xP = wc > 1 ? (col / (wc - 1) - 0.5) * (b.width * 0.7) : 0; return ( ); }); })} {/* Rooftop light */} ))} {/* City ambient glow */} ); }; // ═══════════════════════════════════════════════════════════════ // TRANSMISSION TOWER — lattice-style pylon // ═══════════════════════════════════════════════════════════════ const TransmissionTower = ({ position }: { position: THREE.Vector3 }) => ( {/* Main tower body — tapered */} {/* Wireframe lattice overlay */} {/* Top cross-arm */} {/* Middle cross-arm */} {/* Insulator tips */} {[-2, -0.8, 0.8, 2].map((xOff, i) => ( ))} {/* Aviation light */} ); // ═══════════════════════════════════════════════════════════════ // CABLE NETWORK // ═══════════════════════════════════════════════════════════════ const CableNetwork = () => { const cables = useMemo(() => { const result: { points: THREE.Vector3[]; color: string; width: number; opacity: number }[] = []; // HSVL cables: tower-top to tower-top with catenary sag HSVL_ROUTES.forEach(route => { for (let i = 1; i < route.length; i++) { const a = route[i - 1].clone(); a.y += TWR_H; const b = route[i].clone(); b.y += TWR_H; result.push({ points: catenaryPoints(a, b, 3, 24), color: '#5599cc', width: 1.5, opacity: 0.35, }); } }); // Distribution cables DIST_CABLES.forEach(([from, to]) => { const a = from.clone(); a.y += 6; const b = to.clone(); b.y += 6; result.push({ points: catenaryPoints(a, b, 2, 18), color: '#44bb88', width: 1.2, opacity: 0.3, }); }); // Wind farm internal chains WIND_FARMS.forEach(farm => { farm.chains.forEach(chain => { for (let i = 1; i < chain.length; i++) { const a = chain[i - 1].clone(); a.y += 0.4; const b = chain[i].clone(); b.y += 0.4; result.push({ points: catenaryPoints(a, b, 0.4, 10), color: '#30ee70', width: 0.8, opacity: 0.18, }); } }); }); return result; }, []); const towers = useMemo(() => { const pts: THREE.Vector3[] = []; HSVL_ROUTES.forEach(route => { for (let i = 1; i < route.length - 1; i++) pts.push(route[i]); }); return pts; }, []); return ( {towers.map((pos, i) => )} {cables.map((c, i) => ( ))} ); }; // ═══════════════════════════════════════════════════════════════ // FOREST // ═══════════════════════════════════════════════════════════════ const Forest = ({ position, seed = 0, count = 30 }: { position: THREE.Vector3; seed?: number; count?: number }) => { const trees = useMemo(() => { const items = []; for (let i = 0; i < count; i++) { const angle = sr(seed * 200 + i * 11) * Math.PI * 2; const radius = sr(seed * 200 + i * 23) * 18; const wx = position.x + Math.cos(angle) * radius; const wz = position.z + Math.sin(angle) * radius; if (ALL_INFRA.some(p => Math.hypot(wx - p.x, wz - p.z) < 12)) continue; if (Math.sqrt(wx * wx + wz * wz) > TERRAIN_R * 0.68) continue; const localY = terrainY(wx, wz) - position.y; const trunkH = 0.5 + sr(seed * 200 + i * 31) * 1.2; const canopyH = 0.8 + sr(seed * 200 + i * 37) * 2; const canopyR = 0.25 + sr(seed * 200 + i * 43) * 0.5; const shade = sr(seed * 200 + i * 47); items.push({ x: Math.cos(angle) * radius, y: localY, z: Math.sin(angle) * radius, trunkH, canopyH, canopyR, shade, brightness: 0.15 + sr(seed * 200 + i * 41) * 0.25, }); } return items; }, [seed, count, position]); return ( {trees.map((t, i) => ( {/* Trunk */} {/* Lower canopy */} 0.6 ? '#12aa40' : t.shade > 0.3 ? '#159050' : '#0d7545'} transparent opacity={t.brightness} toneMapped={false} /> {/* Upper canopy layer */} 0.5 ? '#1cc855' : '#15b048'} transparent opacity={t.brightness * 0.6} toneMapped={false} /> ))} ); }; // ═══════════════════════════════════════════════════════════════ // ENERGY PARTICLES — flow along all cables // ═══════════════════════════════════════════════════════════════ const EnergyParticles = () => { const meshRef = useRef(null); const dummy = useMemo(() => new THREE.Object3D(), []); const curves = useMemo(() => { const all: THREE.CatmullRomCurve3[] = []; // HSVL HSVL_ROUTES.forEach(route => { for (let i = 1; i < route.length; i++) { const a = route[i - 1].clone(); a.y += TWR_H; const b = route[i].clone(); b.y += TWR_H; all.push(new THREE.CatmullRomCurve3(catenaryPoints(a, b, 3, 14))); } }); // Distribution DIST_CABLES.forEach(([from, to]) => { const a = from.clone(); a.y += 6; const b = to.clone(); b.y += 6; all.push(new THREE.CatmullRomCurve3(catenaryPoints(a, b, 2, 12))); }); // Farm chains WIND_FARMS.forEach(farm => { farm.chains.forEach(chain => { for (let i = 1; i < chain.length; i++) { const a = chain[i - 1].clone(); a.y += 0.4; const b = chain[i].clone(); b.y += 0.4; all.push(new THREE.CatmullRomCurve3(catenaryPoints(a, b, 0.4, 8))); } }); }); return all; }, []); const perCurve = 5; const total = curves.length * perCurve; const data = useMemo(() => { const items = []; for (let c = 0; c < curves.length; c++) for (let p = 0; p < perCurve; p++) items.push({ curve: c, t: p / perCurve, speed: 0.25 + sr(c * 100 + p * 7) * 0.4, phase: sr(c * 100 + p * 13) * Math.PI * 2, }); return items; }, [curves]); useFrame((state, delta) => { if (!meshRef.current) return; data.forEach((d, i) => { d.t += delta * d.speed; if (d.t > 1) d.t -= 1; const p = curves[d.curve].getPointAt(d.t); dummy.position.copy(p); const pulse = 0.4 + Math.sin(state.clock.elapsedTime * 8 + d.phase) * 0.35; dummy.scale.setScalar(pulse); dummy.updateMatrix(); meshRef.current!.setMatrixAt(i, dummy.matrix); }); meshRef.current.instanceMatrix.needsUpdate = true; }); return ( ); }; // ═══════════════════════════════════════════════════════════════ // AMBIENT ENERGY MOTES // ═══════════════════════════════════════════════════════════════ const AmbientMotes = () => { const meshRef = useRef(null); const count = 350; const dummy = useMemo(() => new THREE.Object3D(), []); const motes = useMemo(() => Array.from({ length: count }, (_, i) => ({ x: (sr(i * 7 + 1) - 0.5) * 320, y: 4 + sr(i * 13 + 2) * 25, z: (sr(i * 19 + 3) - 0.5) * 320, speed: 0.08 + sr(i * 23 + 4) * 0.25, phase: sr(i * 29 + 5) * Math.PI * 2, })), []); useFrame((state) => { if (!meshRef.current) return; motes.forEach((m, i) => { dummy.position.set( m.x + Math.sin(state.clock.elapsedTime * m.speed + m.phase) * 4, m.y + Math.sin(state.clock.elapsedTime * m.speed * 0.6 + m.phase) * 3, m.z + Math.cos(state.clock.elapsedTime * m.speed + m.phase) * 4, ); const s = 0.25 + Math.sin(state.clock.elapsedTime * 2.5 + m.phase) * 0.2; dummy.scale.setScalar(s); dummy.updateMatrix(); meshRef.current!.setMatrixAt(i, dummy.matrix); }); meshRef.current.instanceMatrix.needsUpdate = true; }); return ( ); }; // ═══════════════════════════════════════════════════════════════ // GROUND FOG PARTICLES — atmospheric haze near terrain // ═══════════════════════════════════════════════════════════════ const GroundFog = () => { const meshRef = useRef(null); const count = 120; const dummy = useMemo(() => new THREE.Object3D(), []); const particles = useMemo(() => Array.from({ length: count }, (_, i) => { const x = (sr(i * 31 + 100) - 0.5) * TERRAIN_R * 1.6; const z = (sr(i * 37 + 200) - 0.5) * TERRAIN_R * 1.6; return { x, y: terrainY(x, z) + 0.5 + sr(i * 43 + 300) * 3, z, scale: 8 + sr(i * 47 + 400) * 15, speed: 0.02 + sr(i * 53 + 500) * 0.04, phase: sr(i * 59 + 600) * Math.PI * 2, }; }), []); useFrame((state) => { if (!meshRef.current) return; particles.forEach((p, i) => { dummy.position.set( p.x + Math.sin(state.clock.elapsedTime * p.speed + p.phase) * 5, p.y, p.z + Math.cos(state.clock.elapsedTime * p.speed + p.phase) * 5, ); dummy.scale.setScalar(p.scale); dummy.updateMatrix(); meshRef.current!.setMatrixAt(i, dummy.matrix); }); meshRef.current.instanceMatrix.needsUpdate = true; }); return ( ); }; // ═══════════════════════════════════════════════════════════════ // SCENE // ═══════════════════════════════════════════════════════════════ const Scene = () => ( <> {/* Lighting */} {/* Starfield */} {/* Atmospheric fog */} {/* Solar parks */} {SOLAR_PARKS.map((sp, i) => ( ))} {/* Wind farms */} {WIND_FARMS.flatMap((farm, fi) => farm.turbines.map((pos, ti) => ( )) )} {/* Substations */} {WIND_FARMS.map((f, i) => )} {SOLAR_SUBS.map((s, i) => )} {HUBS.map((h, i) => )} {CITY_DATA.map((cd, i) => )} {/* Cities */} {CITY_DATA.map((cd, i) => )} {/* Cable network with towers */} {/* Forests */} {FOREST_POSITIONS.map((pos, i) => ( ))} {/* Energy flow */} {/* Post-processing */} ); // ═══════════════════════════════════════════════════════════════ export default function HeroIllustration() { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); // eslint-disable-line react-hooks/set-state-in-effect if (!mounted) return null; return (
{/* Cinematic vignette overlay */}
{/* Bottom fade into page */}
); }