-
Notifications
You must be signed in to change notification settings - Fork 166
Day/night cycle implementation - how to get scene from globe ref? #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@micarner this is very cool! I would love for there to be an example like this in this repo, so when you're finished with cleaning up the code feel free to submit a PR to add it. 😃 About getting the camera angle, you can get there easily like this: const { lat, lng, altitude } = globeRef.current.pointOfView(); |
Thanks man! Yes that worked great. Yeah I'd love to add it once I get things set up. I've got it working but only 100% when the camera is at the equator. I need to doublecheck my math. |
Got it working. Was running smooth as butter until I tried to snag this short video. react-globe.day.night.mp4 |
Here is my final globe component for anyone else who needs to see it. It uses some hi res images I grabbed from here: https://planetpixelemporium.com/earth8081.html I will check out your examples and try to whip something up that fits that format for the PR 'use client'
import { useEffect, useRef, useState } from 'react';
import Globe from 'react-globe.gl';
import { Slider } from "@/components/ui/slider";
import * as THREE from 'three';
const dayNightShader = {
vertexShader: `
varying vec3 vNormal;
varying vec2 vUv;
void main() {
vNormal = normalize(normalMatrix * normal);
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
// Fragment Shader Adjustments
#define PI 3.141592653589793
uniform sampler2D dayTexture;
uniform sampler2D nightTexture;
uniform float time;
uniform vec3 globeRotation; // Contains (lat, lng, 0)
varying vec3 vNormal;
varying vec2 vUv;
void main() {
// Base sun angle from time (fixed in world space)
float sunAngle = time * 2.0 * PI;
vec3 sunDirection = vec3(cos(sunAngle), 0.0, sin(sunAngle));
// Convert to radians and invert rotations
vec3 rotation = globeRotation * PI / 180.0;
float invLat = -rotation.x; // Inverse latitude rotation
float invLon = rotation.y; // Inverse longitude rotation
// Correct rotation order: latitude (X) first, then longitude (Y)
mat3 rotX = mat3(
1, 0, 0,
0, cos(invLat), -sin(invLat),
0, sin(invLat), cos(invLat)
);
mat3 rotY = mat3(
cos(invLon), 0, sin(invLon),
0, 1, 0,
-sin(invLon), 0, cos(invLon)
);
// Apply inverse rotations in corrected order
vec3 rotatedSunDirection = rotX * rotY * sunDirection;
// Calculate intensity with rotated normals
float intensity = dot(normalize(vNormal), normalize(rotatedSunDirection));
// Mix textures
vec4 dayColor = texture2D(dayTexture, vUv);
vec4 nightColor = texture2D(nightTexture, vUv);
float blendFactor = smoothstep(-0.1, 0.1, intensity);
gl_FragColor = mix(nightColor, dayColor, blendFactor);
}
`,
};
interface Point {
lat: number;
lng: number;
label: string;
}
const EarthGlobe = () => {
const globeRef = useRef<any>();
const [globeMaterial, setGlobeMaterial] = useState<THREE.ShaderMaterial>();
const [globeRotation, setGlobeRotation] = useState({ lat: 0, lng: 0 });
const startTimeRef = useRef(performance.now());
const [cycleDuration, setCycleDuration] = useState(10); // Cycle duration in seconds
useEffect(() => {
const loader = new THREE.TextureLoader();
Promise.all([
loader.loadAsync('/globe/10k/day.jpg'),
loader.loadAsync('/globe/10k/night.jpg'),
]).then(([dayTexture, nightTexture]) => {
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
dayTexture: { value: dayTexture },
nightTexture: { value: nightTexture },
globeRotation: { value: new THREE.Vector3() }
},
vertexShader: dayNightShader.vertexShader,
fragmentShader: dayNightShader.fragmentShader,
side: THREE.FrontSide
});
setGlobeMaterial(material);
});
}, []);
// Update rotation and timeOfDay
useEffect(() => {
let animationFrameId: number;
const updateRotation = () => {
if (globeRef.current && globeMaterial) {
try {
// Update globe rotation
const { lat, lng } = globeRef.current.pointOfView();
setGlobeRotation({ lat, lng });
globeMaterial.uniforms.globeRotation.value.set(lat, lng, 0);
// Update day/night cycle
const currentTime = performance.now();
const elapsedTime = (currentTime - startTimeRef.current) / 1000; // Convert to seconds
const timeOfDay = (elapsedTime % cycleDuration) / cycleDuration;
globeMaterial.uniforms.time.value = timeOfDay;
globeMaterial.uniformsNeedUpdate = true;
} catch (error) {
console.error('Error updating rotation:', error);
}
}
animationFrameId = requestAnimationFrame(updateRotation);
};
updateRotation();
return () => cancelAnimationFrame(animationFrameId);
}, [globeMaterial, cycleDuration]);
const pointsData = [
{ lat: 37.7749, lng: -122.4194, label: 'San Francisco' },
{ lat: 40.7128, lng: -74.006, label: 'New York' },
];
return (
<div style={{
width: '100%',
height: '100vh',
position: 'relative',
backgroundImage: "url('https://raw.githubusercontent.com/vasturiano/three-globe/refs/heads/master/example/img/night-sky.png')"
}}>
<Globe
ref={globeRef}
width={window.innerWidth}
height={window.innerHeight}
backgroundColor="rgba(0, 0, 0, 0)"
globeMaterial={globeMaterial}
pointsData={pointsData}
pointLabel="label"
pointLat="lat"
pointLng="lng"
pointRadius={0.1}
pointColor={() => 'red'}
/>
{/* Slider for adjusting cycleDuration */}
<div style={{
position: 'absolute',
bottom: '20px',
right: '20px',
width: '200px',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
padding: '10px',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)'
}}>
<label htmlFor="cycle-duration-slider" style={{ display: 'block', marginBottom: '8px', fontSize: '14px', color: '#333' }}>
Cycle Duration (seconds): {cycleDuration}
</label>
<Slider
id="cycle-duration-slider"
value={[cycleDuration]}
min={5}
max={60}
step={1}
onValueChange={(value) => setCycleDuration(value[0])}
/>
</div>
</div>
);
};
export default EarthGlobe; |
Here is that PR :) |
Hey there, I have created a custom shader to blend day/night images. I have also been able to get it to rotate based on a 0 to 1 integer where it goes all the way around the globe. However, this is all relative to the camera and not the position of the globe. I am trying to set up a solution where it extracts the rotation of the globe, however I'm unable to get proper access to the globe object. I am using the ref property of the Globe component but when I check the ref, everything appears to be empty. Like the object structure is there, but the scene has no children or anything to dig into.
Is there a different way to go about this? Here is my custom Globe:
The text was updated successfully, but these errors were encountered: