/* 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 ── // This is the SINGLE SOURCE OF TRUTH for terrain elevation. // Used by BOTH the terrain mesh AND all object placement. const TERRAIN_R = 260; 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.3); let y = 0; y += Math.sin(x * 0.05) * Math.cos(z * 0.04) * 1.3 * h; y += Math.sin(x * 0.1 + 2) * Math.cos(z * 0.08 + 1) * 0.5 * h; y += Math.sin(x * 0.02 + 5) * Math.cos(z * 0.025 + 3) * 1.0 * h; y -= edge * edge * 25; return y; } // Place on terrain — EVERY object uses this function onTerrain(x: number, z: number): THREE.Vector3 { return new THREE.Vector3(x, terrainY(x, z), z); } // ── CATENARY CABLE ── // Real catenary droop: cosh-based sag between two attachment points function catenarySag(t: number): number { // t: 0→1 along cable. Returns droop factor: 0 at ends, -1 at center const u = t * 2 - 1; // -1 → 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 (approximate) for natural spacing ── function poissonDisc( count: number, radius: number, bounds: number, seed: number, exclusions: THREE.Vector3[] = [], exRadius = 12, ): THREE.Vector3[] { const pts: THREE.Vector3[] = []; const attempts = count * 12; 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.75) 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 — all computed from algorithms // ═══════════════════════════════════════════════════════════════ // Step 1: Place hub substations at fixed strategic positions const HUBS = [onTerrain(-6, 14), onTerrain(50, -2)]; // Step 2: Place cities at the periphery const CITY_DATA = [ { city: onTerrain(100, -55), sub: onTerrain(84, -46) }, { city: onTerrain(96, 58), sub: onTerrain(82, 50) }, { city: onTerrain(-4, -102), sub: onTerrain(-3, -86) }, ]; // Step 3: Wind farm zones — use noise to find suitable areas type WindFarm = { sub: THREE.Vector3; turbines: THREE.Vector3[]; chains: THREE.Vector3[][]; // turbine→turbine→sub rows }; 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) * 2; const lz = (r - (rows - 1) / 2) * spacing + (sr(seed + r * 10 + c + 50) - 0.5) * 2; 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); // Chain: each row connects turbine→turbine→sub const chains: THREE.Vector3[][] = grid.map(row => [...row, sub]); return { sub, turbines, chains }; } const WIND_FARMS: WindFarm[] = [ placeWindFarm(-82, 62, 3, 2, 14, 32, -10, 5, 101), placeWindFarm(16, 90, 3, 2, 13, -22, -16, 12, 201), placeWindFarm(-50, -22, 2, 3, 12, 20, 12, -8, 301), placeWindFarm(70, 52, 3, 2, 12, -24, -14, 3, 401), placeWindFarm(-100, -8, 2, 2, 14, 22, 8, 0, 501), placeWindFarm(90, -64, 3, 2, 12, -26, 10, -6, 601), placeWindFarm(-60, 92, 2, 2, 13, 18, -16, 18, 701), placeWindFarm(44, -84, 2, 3, 12, -18, 20, 0, 801), ]; // Step 4: Solar parks with subs at edge type SolarPark = { pos: THREE.Vector3; sub: THREE.Vector3; rows: number; cols: number }; const SOLAR_PARKS: SolarPark[] = [ { pos: onTerrain(-114, -56), sub: onTerrain(-98, -48), rows: 6, cols: 8 }, { pos: onTerrain(-104, -72), sub: onTerrain(-98, -48), rows: 5, cols: 7 }, { pos: onTerrain(100, 18), sub: onTerrain(88, 12), rows: 5, cols: 7 }, { pos: onTerrain(-48, -70), sub: onTerrain(-40, -58), rows: 4, cols: 6 }, { pos: onTerrain(58, -20), sub: onTerrain(52, -14), rows: 5, cols: 6 }, { pos: onTerrain(-84, 34), sub: onTerrain(-74, 30), rows: 4, cols: 5 }, ]; // 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); } }); // Step 5: Collect all gen 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]; // Step 6: HSVL tower routes const TWR_H = 9; 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) / 22)); } 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))); }); // Step 7: All cable pairs 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]), ]; // Step 8: All infra for exclusion 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), ]; // Step 9: Poisson-disc distributed forests const FOREST_POSITIONS = poissonDisc(55, 18, 140, 5000, ALL_INFRA, 14); // ═══════════════════════════════════════════════════════════════ // TERRAIN MESH — uses the same terrainY() function // ═══════════════════════════════════════════════════════════════ const Terrain = () => { const geometry = useMemo(() => { const geo = new THREE.CircleGeometry(TERRAIN_R, 200); 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 ( ); }; // ═══════════════════════════════════════════════════════════════ // COMPONENTS // ═══════════════════════════════════════════════════════════════ 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) => ( )) )} ); const TH = 6, BLade = 3; const WindTurbine = ({ position, seed = 0 }: { position: THREE.Vector3; seed?: number }) => { const bladesRef = useRef(null); const speed = 1.8 + sr(seed * 7) * 1.2; useFrame((s) => { if (bladesRef.current) bladesRef.current.rotation.z = s.clock.elapsedTime * speed; }); return ( {[0, 1, 2].map(i => ( ))} ); }; const Substation = ({ position, size = 'small' }: { position: THREE.Vector3; size?: 'small' | 'large' }) => { const s = size === 'large' ? 1.3 : 0.8; const pulseRef = useRef(null); useFrame((st) => { if (pulseRef.current) pulseRef.current.scale.setScalar(1 + Math.sin(st.clock.elapsedTime * 2.5) * 0.15); }); return ( {[[-0.8 * s, -0.5 * s], [0.8 * s, -0.5 * s], [0, 0.6 * s]].map(([x, z], i) => ( ))} ); }; const City = ({ position, seed = 0 }: { position: THREE.Vector3; seed?: number }) => { const buildings = useMemo(() => { const items = []; const count = 10 + Math.floor(sr(seed * 3) * 8); for (let i = 0; i < count; i++) { const angle = sr(seed * 100 + i * 17) * Math.PI * 2; const radius = 0.5 + sr(seed * 100 + i * 29) * 6; const height = 1 + sr(seed * 100 + i * 37) * 4; items.push({ x: Math.cos(angle) * radius, z: Math.sin(angle) * radius, height, width: 0.4 + sr(seed * 100 + i * 43) * 0.8, depth: 0.4 + sr(seed * 100 + i * 47) * 0.6, rows: Math.max(1, Math.floor(height / 0.8)), }); } return items; }, [seed]); return ( {buildings.map((b, i) => ( {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.25) return null; const color = sr(seed * 2000 + i * 50 + row + col) > 0.45 ? '#ffdd44' : '#aaddff'; const xP = wc > 1 ? (col / (wc - 1) - 0.5) * (b.width * 0.7) : 0; return ( ); }); })} ))} ); }; // ═══════════════════════════════════════════════════════════════ // HOCHSPANNUNGSMAST — tapered pylon // ═══════════════════════════════════════════════════════════════ const TransmissionTower = ({ position }: { position: THREE.Vector3 }) => ( {[-1.6, 1.6].map((xOff, i) => ( ))} ); // ═══════════════════════════════════════════════════════════════ // CABLE NETWORK — all use catenary algorithm // ═══════════════════════════════════════════════════════════════ const CableNetwork = () => { // Pre-compute all cable point arrays 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, 2.5, 20), color: '#6699cc', width: 1.5, opacity: 0.4, }); } }); // Distribution cables: solar→sub, sub→city (elevated) DIST_CABLES.forEach(([from, to]) => { const a = from.clone(); a.y += 5; const b = to.clone(); b.y += 5; result.push({ points: catenaryPoints(a, b, 1.5, 16), color: '#55cc88', width: 1.2, opacity: 0.35, }); }); // Wind farm internal chains: ground-level cables 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.3; const b = chain[i].clone(); b.y += 0.3; result.push({ points: catenaryPoints(a, b, 0.3, 8), color: '#30ff70', width: 1, opacity: 0.2, }); } }); }); return result; }, []); // Towers at intermediate route points 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 — Poisson-disc distributed, terrain-aware // ═══════════════════════════════════════════════════════════════ 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++) { // Use noise for natural clumping const angle = sr(seed * 200 + i * 11) * Math.PI * 2; const radius = sr(seed * 200 + i * 23) * 14; const wx = position.x + Math.cos(angle) * radius; const wz = position.z + Math.sin(angle) * radius; // Skip if near infrastructure if (ALL_INFRA.some(p => Math.hypot(wx - p.x, wz - p.z) < 10)) continue; // Skip if too far from terrain center if (Math.sqrt(wx * wx + wz * wz) > TERRAIN_R * 0.72) continue; const localY = terrainY(wx, wz) - position.y; // height relative to parent group const trunkH = 0.4 + sr(seed * 200 + i * 31) * 1; const canopyH = 0.6 + sr(seed * 200 + i * 37) * 1.5; const canopyR = 0.2 + sr(seed * 200 + i * 43) * 0.4; 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.2 + sr(seed * 200 + i * 41) * 0.3, }); } return items; }, [seed, count, position]); return ( {trees.map((t, i) => ( 0.6 ? '#15aa45' : t.shade > 0.3 ? '#1a9050' : '#10804a'} transparent opacity={t.brightness} toneMapped={false} /> 0.5 ? '#20cc55' : '#18bb50'} transparent opacity={t.brightness * 0.7} toneMapped={false} /> ))} ); }; // ═══════════════════════════════════════════════════════════════ // ENERGY PARTICLES — flow along catenary cables // ═══════════════════════════════════════════════════════════════ const EnergyParticles = () => { const meshRef = useRef(null); const dummy = useMemo(() => new THREE.Object3D(), []); // Build all curves from cable segments for particle paths 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, 2.5, 12))); } }); // Distribution DIST_CABLES.forEach(([from, to]) => { const a = from.clone(); a.y += 5; const b = to.clone(); b.y += 5; all.push(new THREE.CatmullRomCurve3(catenaryPoints(a, b, 1.5, 10))); }); // 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.3; const b = chain[i].clone(); b.y += 0.3; all.push(new THREE.CatmullRomCurve3(catenaryPoints(a, b, 0.3, 6))); } }); }); return all; }, []); const perCurve = 4; 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.3 + sr(c * 100 + p * 7) * 0.35, 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); dummy.scale.setScalar(0.5 + Math.sin(state.clock.elapsedTime * 6 + d.phase) * 0.3); dummy.updateMatrix(); meshRef.current!.setMatrixAt(i, dummy.matrix); }); meshRef.current.instanceMatrix.needsUpdate = true; }); return ( ); }; // ═══════════════════════════════════════════════════════════════ // AMBIENT MOTES // ═══════════════════════════════════════════════════════════════ const AmbientMotes = () => { const meshRef = useRef(null); const count = 250; const dummy = useMemo(() => new THREE.Object3D(), []); const motes = useMemo(() => Array.from({ length: count }, (_, i) => ({ x: (sr(i * 7 + 1) - 0.5) * 220, y: 3 + sr(i * 13 + 2) * 18, z: (sr(i * 19 + 3) - 0.5) * 220, speed: 0.1 + sr(i * 23 + 4) * 0.3, 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) * 3, m.y + Math.sin(state.clock.elapsedTime * m.speed * 0.7 + m.phase) * 2, m.z + Math.cos(state.clock.elapsedTime * m.speed + m.phase) * 3); dummy.scale.setScalar(0.3 + Math.sin(state.clock.elapsedTime * 2 + m.phase) * 0.2); dummy.updateMatrix(); meshRef.current!.setMatrixAt(i, dummy.matrix); }); meshRef.current.instanceMatrix.needsUpdate = true; }); return ( ); }; // ═══════════════════════════════════════════════════════════════ // SCENE // ═══════════════════════════════════════════════════════════════ const Scene = () => ( <> {/* 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) => )} {/* All cables with catenary physics */} {/* Forests — Poisson-disc placed */} {FOREST_POSITIONS.map((pos, i) => ( ))} {/* Energy flow */} ); // ═══════════════════════════════════════════════════════════════ 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 (
); }