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) => (
))}
>
);
}