192 lines
6.4 KiB
TypeScript
192 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo, useRef } from "react";
|
|
import { useFrame } from "@react-three/fiber";
|
|
import * as THREE from "three";
|
|
import { COLORS } from "./constants";
|
|
import { damp, lerp, seeded, smoothstep } from "./math";
|
|
|
|
type Props = {
|
|
progress: React.RefObject<number>;
|
|
count?: number;
|
|
reduced?: boolean;
|
|
};
|
|
|
|
const CYAN = new THREE.Color(COLORS.cyan);
|
|
const RED = new THREE.Color(COLORS.red);
|
|
const GREEN = new THREE.Color(COLORS.green);
|
|
|
|
// Generate deterministic cluster centers matching routes.ts
|
|
const clusterCount = 5;
|
|
const clusterCenters = Array.from({ length: clusterCount }, (_, c) => {
|
|
const baseAngle = (c / clusterCount) * Math.PI * 2 + 0.3;
|
|
const radius = 7 + seeded(c * 7 + 1) * 3;
|
|
const cx = Math.cos(baseAngle) * radius;
|
|
const cz = Math.sin(baseAngle) * radius;
|
|
return new THREE.Vector3(cx, 0.1, cz);
|
|
});
|
|
|
|
function HologramCity({ progress, reduced = false }: Props) {
|
|
const mainWarehouseRef = useRef<THREE.Group>(null);
|
|
const radarRefs = useRef<(THREE.Group | null)[]>([]);
|
|
const corridorMats = useRef<(THREE.LineBasicMaterial | null)[]>([]);
|
|
const zoneMats = useRef<(THREE.MeshBasicMaterial | null)[]>([]);
|
|
const eased = useRef(0);
|
|
|
|
// Pre-generate corridor geometries (Warehouse [0, 0, 0] to Cluster Centers)
|
|
const corridorGeometries = useMemo(() => {
|
|
return clusterCenters.map((center) => {
|
|
const pts = [
|
|
new THREE.Vector3(0, 0.15, 0),
|
|
new THREE.Vector3(center.x * 0.5, 0.6, center.z * 0.5), // arch up slightly in the middle
|
|
new THREE.Vector3(center.x, 0.15, center.z),
|
|
];
|
|
return new THREE.CatmullRomCurve3(pts).getPoints(24);
|
|
});
|
|
}, []);
|
|
|
|
useFrame((state, dt) => {
|
|
const p = progress.current ?? 0;
|
|
eased.current = damp(eased.current, p, 3, dt);
|
|
const e = eased.current;
|
|
const t = state.clock.elapsedTime;
|
|
|
|
// Animate Main Warehouse central beacon rotation
|
|
if (mainWarehouseRef.current) {
|
|
mainWarehouseRef.current.position.y = Math.sin(t * 1.2) * 0.04;
|
|
}
|
|
|
|
// Rotate Radar dishes on top of the Regional Hubs
|
|
radarRefs.current.forEach((radar, idx) => {
|
|
if (radar) {
|
|
radar.rotation.y = t * (0.8 + idx * 0.2);
|
|
}
|
|
});
|
|
|
|
// Animate corridor line opacity and color:
|
|
// Under unoptimized states (chaos/scan) they are faint or red.
|
|
// As optimization settles, they light up with active cyan/green.
|
|
corridorMats.current.forEach((mat, idx) => {
|
|
if (mat) {
|
|
const activeColor = idx % 2 === 0 ? CYAN : GREEN;
|
|
const colorFactor = smoothstep(0.45, 0.8, e);
|
|
mat.color.copy(RED).lerp(activeColor, colorFactor);
|
|
mat.opacity = lerp(0.12, 0.7, colorFactor) * (0.8 + Math.sin(t * 4 - idx * 0.5) * 0.2);
|
|
}
|
|
});
|
|
|
|
// Animate ground delivery zone circles
|
|
zoneMats.current.forEach((mat, idx) => {
|
|
if (mat) {
|
|
const activeColor = idx % 2 === 0 ? CYAN : GREEN;
|
|
const colorFactor = smoothstep(0.5, 0.82, e);
|
|
mat.color.copy(RED).lerp(activeColor, colorFactor);
|
|
mat.opacity = lerp(0.06, 0.22, colorFactor) * (0.85 + Math.sin(t * 3 - idx) * 0.15);
|
|
}
|
|
});
|
|
});
|
|
|
|
return (
|
|
<group>
|
|
{/* Sleek Dark Cyber Ground Grid */}
|
|
<gridHelper
|
|
args={[60, 48, COLORS.cyan, COLORS.cyan]}
|
|
position={[0, -0.01, 0]}
|
|
>
|
|
<lineBasicMaterial
|
|
attach="material"
|
|
color={COLORS.cyan}
|
|
transparent
|
|
opacity={0.05}
|
|
/>
|
|
</gridHelper>
|
|
|
|
{/* Primary Transport Corridors (Warehouse to Cluster Hubs) */}
|
|
{corridorGeometries.map((points, i) => (
|
|
<line key={`corr${i}`}>
|
|
<bufferGeometry>
|
|
<float32BufferAttribute
|
|
attach="attributes-position"
|
|
args={[new Float32Array(points.flatMap(p => [p.x, p.y, p.z])), 3]}
|
|
/>
|
|
</bufferGeometry>
|
|
<lineBasicMaterial
|
|
ref={(el) => {
|
|
corridorMats.current[i] = el;
|
|
}}
|
|
transparent
|
|
opacity={0.15}
|
|
depthWrite={false}
|
|
/>
|
|
</line>
|
|
))}
|
|
|
|
{/* CENTRAL LOGISTICS ASSET: Main Warehouse Hub */}
|
|
<group ref={mainWarehouseRef} position={[0, 0, 0]}>
|
|
{/* Core foundation structure */}
|
|
<mesh position={[0, 0.35, 0]}>
|
|
<boxGeometry args={[2.2, 0.7, 1.8]} />
|
|
<meshBasicMaterial color={COLORS.ink} />
|
|
</mesh>
|
|
<mesh position={[0, 0.35, 0]}>
|
|
<boxGeometry args={[2.2, 0.7, 1.8]} />
|
|
<meshBasicMaterial color={COLORS.cyan} wireframe transparent opacity={0.65} />
|
|
</mesh>
|
|
|
|
{/* Loading Docks */}
|
|
{[-0.6, 0, 0.6].map((offset, i) => (
|
|
<mesh key={i} position={[offset, 0.18, 0.91]}>
|
|
<boxGeometry args={[0.3, 0.35, 0.06]} />
|
|
<meshBasicMaterial color={COLORS.cyan} transparent opacity={0.8} />
|
|
</mesh>
|
|
))}
|
|
|
|
{/* Upper Level / Control Deck */}
|
|
<mesh position={[0, 0.85, 0]}>
|
|
<boxGeometry args={[1.2, 0.3, 1.0]} />
|
|
<meshBasicMaterial color={COLORS.cyan} transparent opacity={0.15} />
|
|
</mesh>
|
|
<mesh position={[0, 0.85, 0]}>
|
|
<boxGeometry args={[1.2, 0.3, 1.0]} />
|
|
<meshBasicMaterial color={COLORS.cyan} wireframe transparent opacity={0.9} />
|
|
</mesh>
|
|
|
|
{/* Rooftop Solar Panels */}
|
|
{[-0.4, 0.4].map((offset, i) => (
|
|
<mesh key={i} position={[offset, 0.71, -0.4]} rotation={[0.15, 0, 0]}>
|
|
<boxGeometry args={[0.5, 0.03, 0.6]} />
|
|
<meshBasicMaterial color="#1e293b" />
|
|
</mesh>
|
|
))}
|
|
|
|
{/* Central Communications Array / Tower */}
|
|
<group position={[0, 1.0, 0]}>
|
|
{/* Main rod */}
|
|
<mesh position={[0, 0.4, 0]}>
|
|
<cylinderGeometry args={[0.03, 0.03, 0.8, 8]} />
|
|
<meshBasicMaterial color={COLORS.cyan} />
|
|
</mesh>
|
|
{/* Glowing Beacon */}
|
|
<mesh position={[0, 0.8, 0]}>
|
|
<sphereGeometry args={[0.12, 16, 16]} />
|
|
<meshBasicMaterial color="#ffffff" />
|
|
</mesh>
|
|
</group>
|
|
|
|
{/* Pulsing ground base glow */}
|
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>
|
|
<ringGeometry args={[1.8, 2.2, 32]} />
|
|
<meshBasicMaterial
|
|
color={COLORS.cyan}
|
|
transparent
|
|
opacity={0.3}
|
|
blending={THREE.AdditiveBlending}
|
|
/>
|
|
</mesh>
|
|
</group>
|
|
</group>
|
|
);
|
|
}
|
|
|
|
export default React.memo(HologramCity);
|