import React, { Suspense, useMemo, useEffect } from 'react';
import { Canvas, useThree } from '@react-three/fiber';
import { OrbitControls, Sky, Stars, Text } from '@react-three/drei';
import { EffectComposer, Bloom } from '@react-three/postprocessing';
import UltimateLensFlare from './lensflare/LensFlare';
import ForecastPortals from './ForecastPortals';
import { getWeatherConditionType, shouldShowSun } from '../services/weatherService';
import * as THREE from 'three';
import WeatherVisualization from './WeatherVisualization';
// Component to handle scene background
const SceneBackground = ({ backgroundColor }) => {
const { scene } = useThree();
useEffect(() => {
scene.background = new THREE.Color(backgroundColor);
}, [scene, backgroundColor]);
return null;
};
// Lens flare visibility logic - uses shared weather service utility
const useLensFlareVisibility = (weatherData, isNight) => {
return React.useMemo(() => {
if (isNight || !weatherData) return false;
return shouldShowSun(weatherData);
}, [isNight, weatherData]);
};
// Post-processing effects with Ultimate Lens Flare - only render when needed
const PostProcessingEffects = ({ showLensFlare, isPortalMode = false }) => {
// Define main scene lens flare values
const mainSceneDefaults = {
positionX: 0,
positionY: 5,
positionZ: 0,
opacity: 1.00,
glareSize: 1.68,
starPoints: 2,
animated: false,
followMouse: false,
anamorphic: false,
colorGain: '#38150b',
flareSpeed: 0.10,
flareShape: 0.81,
flareSize: 1.68,
secondaryGhosts: true,
ghostScale: 0.03,
aditionalStreaks: true,
starBurst: false,
haloScale: 3.88,
};
// Define portal mode lens flare values
const portalModeDefaults = {
positionX: 0,
positionY: 3,
positionZ: 0,
opacity: 1.00,
glareSize: 1.68,
starPoints: 2,
animated: false,
followMouse: false,
anamorphic: false,
colorGain: '#38150b',
flareSpeed: 0.10,
flareShape: 0.81,
flareSize: 1.68,
secondaryGhosts: true,
ghostScale: 0.03,
aditionalStreaks: true,
starBurst: false,
haloScale: 3.88,
};
// Use appropriate defaults based on portal mode
const lensFlareSettings = isPortalMode ? portalModeDefaults : mainSceneDefaults;
// Define bloom values for different modes
const mainSceneBloom = {
bloomIntensity: 0.3,
bloomThreshold: 0.9,
};
const portalModeBloom = {
bloomIntensity: 0.97,
bloomThreshold: 0.85,
};
const bloomSettings = isPortalMode ? portalModeBloom : mainSceneBloom;
if (!showLensFlare) return null;
return (
);
};
const Scene3D = ({ weatherData, isLoading, onPortalModeChange, onSetExitPortalFunction, onPortalWeatherDataChange }) => {
const [portalMode, setPortalMode] = React.useState(false);
const [portalWeatherData, setPortalWeatherData] = React.useState(null);
const exitPortal = () => {
setPortalMode(false);
setPortalWeatherData(null);
onPortalModeChange?.(false);
onPortalWeatherDataChange?.(null);
};
const handlePortalStateChange = (isPortalActive, dayData) => {
setPortalMode(isPortalActive);
onPortalModeChange?.(isPortalActive);
if (isPortalActive && dayData) {
// Create weather data for the portal day
const portalData = {
current: {
temp_f: dayData.day.maxtemp_f,
condition: dayData.day.condition,
is_day: 1,
humidity: dayData.day.avghumidity,
wind_mph: dayData.day.maxwind_mph,
feelslike_f: dayData.day.maxtemp_f, // Approximate feels like temp
vis_miles: 10, // Default visibility
},
location: {
name: weatherData?.location?.name || 'Unknown',
region: weatherData?.location?.region || '',
localtime: dayData.date + 'T12:00'
}
};
setPortalWeatherData(portalData);
onPortalWeatherDataChange?.(portalData);
} else {
setPortalWeatherData(null);
onPortalWeatherDataChange?.(null);
}
};
// Provide exit function to parent
React.useEffect(() => {
onSetExitPortalFunction?.(() => exitPortal);
}, [onSetExitPortalFunction]);
const getTimeOfDay = () => {
if (!weatherData?.location?.localtime) return 'day';
const localTime = weatherData.location.localtime;
const currentHour = new Date(localTime).getHours();
if (currentHour >= 19 || currentHour <= 6) return 'night';
if (currentHour >= 6 && currentHour < 8) return 'dawn';
if (currentHour >= 17 && currentHour < 19) return 'dusk';
return 'day';
};
const isNightTime = () => {
if (!weatherData?.location?.localtime) return false;
const localTime = weatherData.location.localtime;
const currentHour = new Date(localTime).getHours();
return currentHour >= 19 || currentHour <= 6;
};
// Calculate sun position based on time and location
const sunPosition = useMemo(() => {
if (!weatherData?.location) {
// Default position for day/night - ensure sun is visible during day
const hour = new Date().getHours();
if (hour >= 6 && hour <= 18) {
// Daytime - put sun high in sky
const dayProgress = (hour - 6) / 12; // 0 to 1 from sunrise to sunset
const angle = dayProgress * Math.PI; // 0 to π
return [
Math.sin(angle) * 50,
Math.cos(angle) * 50 + 25, // Keep sun above horizon
30
];
} else {
// Nighttime - sun below horizon
return [0, -50, 0];
}
}
const { lat, lon, localtime } = weatherData.location;
const date = new Date(localtime);
const hour = date.getHours() + date.getMinutes() / 60;
if (hour >= 6 && hour <= 18) {
// Daytime positioning
const dayProgress = (hour - 6) / 12; // 0 at sunrise, 1 at sunset
const sunAngle = dayProgress * Math.PI; // 0 to π
const distance = 100;
const elevation = Math.sin(sunAngle) * 0.7 + 0.3;
return [
Math.sin(sunAngle - Math.PI/2) * distance * 0.8,
elevation * distance,
Math.cos(sunAngle - Math.PI/2) * distance * 0.3
];
} else {
// Nighttime - moon position
return [0, -30, 50];
}
}, [weatherData?.location?.lat, weatherData?.location?.lon, weatherData?.location?.localtime]);
const isNight = isNightTime();
const timeOfDay = getTimeOfDay();
const showLensFlare = useLensFlareVisibility(weatherData, isNight);
const showPortalLensFlare = useLensFlareVisibility(portalWeatherData, false);
//Sky colors
const getBackgroundColor = () => {
if (isNight) return '#0A1428';
// Dawn/dusk specific colors
if (timeOfDay === 'dawn') return '#2D1B3D';
if (timeOfDay === 'dusk') return '#3D2914';
if (!weatherData?.current?.condition) return '#0D7FDB';
const condition = weatherData.current.condition.text.toLowerCase();
if (condition.includes('storm')) return '#263238';
if (condition.includes('rain') || condition.includes('overcast')) return '#546E7A';
if (condition.includes('cloudy')) return '#1E88E5';
return '#0D7FDB';
};
// Component to handle mobile responsive text inside Canvas
const ResponsiveText = ({ isNight, isLoading }) => {
const { viewport } = useThree();
const isMobile = viewport.width < 6;
const textScale = isMobile ? 0.7 : 1;
const textPosition = isMobile ? [0, -0.6, 0] : [0, -2.1, 0];
if (isLoading) return null;
return (
THREE DAY FORECAST
);
};
return (
);
};
export default Scene3D;