148 lines
4.9 KiB
JavaScript
148 lines
4.9 KiB
JavaScript
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 (
|
|
<TilesRenderer url={`https://tile.googleapis.com/v1/3dtiles/root.json?key=${apiKey}`}>
|
|
<TilesPlugin plugin={GoogleCloudAuthPlugin} args={{ apiToken: apiKey }} />
|
|
<TilesPlugin plugin={GLTFExtensionsPlugin} dracoLoader={dracoLoader} />
|
|
<TilesPlugin plugin={TileCompressionPlugin} />
|
|
<TilesPlugin plugin={UpdateOnChangePlugin} />
|
|
<TilesPlugin plugin={TilesFadePlugin} />
|
|
</TilesRenderer>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<>
|
|
<PerspectiveCamera makeDefault near={10} far={10000000} fov={45} />
|
|
<FollowCamera />
|
|
<directionalLight ref={sunRef} position={[0, 1, 0]} intensity={3.0} castShadow />
|
|
<Atmosphere ref={atmosphereRef}>
|
|
<GoogleMaps3DTiles />
|
|
<EffectComposer multisampling={0} disableNormalPass={false}>
|
|
<Clouds disableDefaultLayers coverage={weather.coverage}>
|
|
{weather.layers.map((layer, i) => (
|
|
<CloudLayer key={i} channel={layer.channel} altitude={layer.altitude}
|
|
height={layer.height} densityScale={layer.densityScale} shapeAmount={0.5} />
|
|
))}
|
|
</Clouds>
|
|
<AerialPerspective sky />
|
|
<ToneMapping mode={ToneMappingMode.AGX} />
|
|
</EffectComposer>
|
|
</Atmosphere>
|
|
</>
|
|
);
|
|
}
|