import React, { useEffect, useRef, useState } from 'react'; import { useFrame, useThree } from '@react-three/fiber'; import { PerspectiveCamera } from '@react-three/drei'; import { EffectComposer, ToneMapping } from '@react-three/postprocessing'; import { ToneMappingMode } from 'postprocessing'; import * as THREE from 'three'; import { AerialPerspective, Atmosphere } from '@takram/three-atmosphere/r3f'; import { Clouds, CloudLayer } from '@takram/three-clouds/r3f'; import { TilesPlugin, TilesRenderer } from '3d-tiles-renderer/r3f'; import { GLTFExtensionsPlugin, GoogleCloudAuthPlugin, TileCompressionPlugin, TilesFadePlugin, UpdateOnChangePlugin, } from '3d-tiles-renderer/plugins'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { FollowCamera } from './controls/CameraRig'; import { worldState } from './worldState'; const TIME_SCALE = 100; const INITIAL_DATE = new Date('2024-06-21T12:00:00'); const WEATHER_INTERVAL = 5 * 60 * 1000; const WEATHER_PRESETS = [ { name: 'Clear', coverage: 0.05, layers: [ { channel: 'r', altitude: 2000, height: 500, densityScale: 0.05 }, { channel: 'b', altitude: 7500, height: 500, densityScale: 0.05 }, ] }, { name: 'Sunny', coverage: 0.2, layers: [ { channel: 'r', altitude: 1500, height: 500, densityScale: 0.3 }, { channel: 'b', altitude: 7500, height: 500, densityScale: 0.15 }, ] }, { name: 'Cloudy', coverage: 0.4, layers: [ { channel: 'r', altitude: 1500, height: 500, densityScale: 0.4 }, { channel: 'g', altitude: 2000, height: 800, densityScale: 0.3 }, ] } ]; const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); function GoogleMaps3DTiles() { const apiKey = import.meta.env.VITE_GOOGLE_MAP_API_KEY; if (!apiKey) return null; return ( ); } export default function AtmosphereScene({ timeScale: timeScaleProp, overrideDate, overrideTimeScale }) { const { gl } = useThree(); const sunRef = useRef(); const atmosphereRef = useRef(); const dateRef = useRef(new Date(INITIAL_DATE)); const savedDateRef = useRef(null); const timeScaleRef = useRef(timeScaleProp ?? TIME_SCALE); // Override date for burst effect const hasBurstRef = useRef(false); useEffect(() => { if (overrideDate) { hasBurstRef.current = true; dateRef.current = new Date(overrideDate); } else if (hasBurstRef.current) { hasBurstRef.current = false; // Burst ended: set to sunrise dateRef.current = new Date('2024-06-22T10:00:00'); } }, [overrideDate]); timeScaleRef.current = overrideTimeScale ?? timeScaleProp ?? TIME_SCALE; const [weather, setWeather] = useState(WEATHER_PRESETS[1]); useEffect(() => { gl.toneMapping = THREE.NoToneMapping; gl.toneMappingExposure = 10.0; }, [gl]); useEffect(() => { const interval = setInterval(() => { setWeather(prev => { const others = WEATHER_PRESETS.filter(w => w.name !== prev.name); return others[Math.floor(Math.random() * others.length)]; }); }, WEATHER_INTERVAL); return () => clearInterval(interval); }, []); useFrame((_, delta) => { const currentDate = dateRef.current; currentDate.setTime(currentDate.getTime() + delta * timeScaleRef.current * 1000); worldState.currentHour = currentDate.getHours() + currentDate.getMinutes() / 60; if (atmosphereRef.current) { atmosphereRef.current.updateByDate(currentDate); const sunDirection = atmosphereRef.current.sunDirection; if (sunRef.current && sunDirection) { sunRef.current.position.copy(sunDirection); sunRef.current.intensity = sunDirection.y < -0.1 ? 0.1 : 3.0; } } }); return ( <> {weather.layers.map((layer, i) => ( ))} ); }