import React, { useMemo, useRef, Suspense } from 'react'; import { useCurrentFrame, useVideoConfig, interpolate, Easing, AbsoluteFill } from 'remotion'; import { Canvas, useFrame, useLoader } from '@react-three/fiber'; import * as THREE from 'three'; import { TextureLoader } from 'three'; // ============================================================================= // COMPOSITION CONFIG (Required for auto-discovery) // ============================================================================= export const compositionConfig = { id: 'GlobeFlightArc', durationInSeconds: 5, fps: 30, width: 1920, height: 1080, }; // ============================================================================= // PRE-GENERATED DATA (computed once, NOT during render) // ============================================================================= const seededRandom = (seed: number): number => { const x = Math.sin(seed * 9999) * 10000; return x - Math.floor(x); }; // Generate starfield positions const STAR_COUNT = 2000; const starPositions = new Float32Array(STAR_COUNT * 3); const starColors = new Float32Array(STAR_COUNT * 3); for (let i = 0; i < STAR_COUNT; i++) { const radius = 50 + seededRandom(i) * 50; const theta = seededRandom(i + 1000) * Math.PI * 2; const phi = Math.acos(2 * seededRandom(i + 2000) - 1); starPositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); starPositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta); starPositions[i * 3 + 2] = radius * Math.cos(phi); const brightness = 0.5 + seededRandom(i + 3000) * 0.5; starColors[i * 3] = brightness; starColors[i * 3 + 1] = brightness; starColors[i * 3 + 2] = brightness + seededRandom(i + 4000) * 0.1; } // Flight arc: New York to London const FLIGHT_ARC = { from: { lat: 40.7128, lng: -74.006, name: 'New York' }, to: { lat: 51.5074, lng: -0.1278, name: 'London' }, }; // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= const latLngToVector3 = (lat: number, lng: number, radius: number): THREE.Vector3 => { const phi = (90 - lat) * (Math.PI / 180); const theta = (lng + 180) * (Math.PI / 180); return new THREE.Vector3( -radius * Math.sin(phi) * Math.cos(theta), radius * Math.cos(phi), radius * Math.sin(phi) * Math.sin(theta) ); }; const generateArcPoints = ( from: { lat: number; lng: number }, to: { lat: number; lng: number }, radius: number, segments: number = 100 ): THREE.Vector3[] => { const start = latLngToVector3(from.lat, from.lng, radius); const end = latLngToVector3(to.lat, to.lng, radius); // Calculate arc height based on distance const distance = start.distanceTo(end); const arcHeight = radius + distance * 0.3; // Midpoint elevated above the surface const mid = new THREE.Vector3() .addVectors(start, end) .multiplyScalar(0.5) .normalize() .multiplyScalar(arcHeight); // Create quadratic bezier curve const curve = new THREE.QuadraticBezierCurve3(start, mid, end); return curve.getPoints(segments); }; // ============================================================================= // 3D COMPONENTS // ============================================================================= // Starfield Background const Stars: React.FC = () => { const geometryRef = useRef(null); return ( ); }; // Earth Sphere const Earth: React.FC<{ rotation: number }> = ({ rotation }) => { const meshRef = useRef(null); const dayTexture = useLoader( TextureLoader, 'https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg' ); const bumpTexture = useLoader( TextureLoader, 'https://threejs.org/examples/textures/planets/earth_normal_2048.jpg' ); const specularTexture = useLoader( TextureLoader, 'https://threejs.org/examples/textures/planets/earth_specular_2048.jpg' ); return ( ); }; // Atmosphere Glow const Atmosphere: React.FC = () => { const shaderMaterial = useMemo(() => { return new THREE.ShaderMaterial({ uniforms: {}, vertexShader: ` varying vec3 vNormal; void main() { vNormal = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` varying vec3 vNormal; void main() { float intensity = pow(0.7 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0); gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity; } `, side: THREE.BackSide, blending: THREE.AdditiveBlending, transparent: true, }); }, []); return ( ); }; // Cloud Layer const Clouds: React.FC<{ rotation: number }> = ({ rotation }) => { const cloudTexture = useLoader( TextureLoader, 'https://threejs.org/examples/textures/planets/earth_clouds_1024.png' ); return ( ); }; // Animated Flight Arc const FlightArc: React.FC<{ progress: number; rotation: number }> = ({ progress, rotation }) => { const arcPoints = useMemo( () => generateArcPoints(FLIGHT_ARC.from, FLIGHT_ARC.to, 2, 100), [] ); // Number of points to show based on progress const visiblePoints = Math.floor(progress * arcPoints.length); const geometry = useMemo(() => { const geo = new THREE.BufferGeometry(); const positions = new Float32Array(Math.max(visiblePoints, 1) * 3); for (let i = 0; i < visiblePoints; i++) { positions[i * 3] = arcPoints[i].x; positions[i * 3 + 1] = arcPoints[i].y; positions[i * 3 + 2] = arcPoints[i].z; } geo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); return geo; }, [visiblePoints, arcPoints]); // Glowing trail head const headPosition = visiblePoints > 0 ? arcPoints[visiblePoints - 1] : arcPoints[0]; return ( {/* Main arc line */} {visiblePoints > 1 && ( )} {/* Glowing head of the arc */} {progress > 0 && progress < 1 && ( )} {/* Start marker (New York) */} {/* End marker (London) */} ); }; // ============================================================================= // SCENE COMPONENT // ============================================================================= const Scene: React.FC<{ frame: number; durationInFrames: number }> = ({ frame, durationInFrames }) => { // Globe rotation animation const rotation = interpolate( frame, [0, durationInFrames], [0.5, -0.8], { easing: Easing.inOut(Easing.cubic) } ); // Arc drawing progress (starts at 10%, completes at 80%) const arcProgress = interpolate( frame, [durationInFrames * 0.1, durationInFrames * 0.8], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic) } ); return ( <> {/* Lighting */} {/* Background */} {/* Earth + Clouds wrapped in Suspense so textures don't block the canvas */} {/* Flight Arc */} ); }; // ============================================================================= // MAIN COMPONENT // ============================================================================= const GlobeAnimation: React.FC = () => { const frame = useCurrentFrame(); const { fps, durationInFrames } = useVideoConfig(); return ( {/* UI Overlay */}
New York → London
); }; export default GlobeAnimation;