/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "simulationenvironment.h" #include "simulationsounds.h" #include "Globals.h" #include "Timer.h" namespace simulation { world_environment Environment; } // simulation void world_environment::toggle_daylight() { Global.FakeLight = !Global.FakeLight; if( Global.FakeLight ) { // for fake daylight enter fixed hour time( 10, 30, 0 ); } else { // local clock based calculation time(); } } // calculates current season of the year based on set simulation date void world_environment::compute_season( int const Yearday ) const { using dayseasonpair = std::pair; std::vector seasonsequence { { 65, "winter:" }, { 158, "spring:" }, { 252, "summer:" }, { 341, "autumn:" }, { 366, "winter:" } }; auto const lookup = std::lower_bound( std::begin( seasonsequence ), std::end( seasonsequence ), clamp( Yearday, 1, seasonsequence.back().first ), []( dayseasonpair const &Left, const int Right ) { return Left.first < Right; } ); Global.Season = lookup->second; // season can affect the weather so if it changes, re-calculate weather as well compute_weather(); } // calculates current weather void world_environment::compute_weather() const { Global.Weather = ( Global.Overcast <= 0.25 ? "clear:" : Global.Overcast <= 1.0 ? "cloudy:" : ( Global.Season != "winter:" ? "rain:" : "snow:" ) ); } void world_environment::init() { m_sun.init(); m_moon.init(); m_stars.init(); m_clouds.Init(); m_precipitation.init(); { auto const rainsoundoverride { simulation::Sound_overrides.find( "weather.rainsound:" ) }; m_rainsound.deserialize( ( rainsoundoverride != simulation::Sound_overrides.end() ? rainsoundoverride->second : "rain-sound-loop" ), sound_type::single ); } m_wind = basic_wind{ static_cast( Random( 0, 360 ) ), static_cast( Random( -5, 5 ) ), static_cast( Random( -2, 4 ) ), static_cast( Random( -1, 1 ) ), static_cast( Random( 5, 20 ) ), {} }; } void world_environment::update() { // move celestial bodies... m_sun.update(); m_moon.update(); // ...determine source of key light and adjust global state accordingly... // diffuse (sun) intensity goes down after twilight, and reaches minimum 18 degrees below horizon float twilightfactor = clamp( -m_sun.getAngle(), 0.0f, 18.0f ) / 18.0f; // NOTE: sun light receives extra padding to prevent moon from kicking in too soon auto const sunlightlevel = m_sun.getIntensity() + 0.05f * ( 1.f - twilightfactor ); auto const moonlightlevel = m_moon.getIntensity() * 0.65f; // scaled down by arbitrary factor, it's pretty bright otherwise // ...update skydome to match the current sun position as well... // twilight factor can be reset later down, so we do it here while it's still reflecting state of the sun // turbidity varies from 2-3 during the day based on overcast, 3-4 after sunset to deal with sunlight bleeding too much into the sky from below horizon m_skydome.SetTurbidity( 2.f + clamp( Global.Overcast, 0.f, 1.f ) + interpolate( 0.f, 1.f, clamp( twilightfactor * 1.5f, 0.f, 1.f ) ) ); m_skydome.SetOvercastFactor( Global.Overcast ); m_skydome.Update( m_sun.getDirection() ); float keylightintensity; glm::vec3 keylightcolor; if( moonlightlevel > sunlightlevel ) { // rare situations when the moon is brighter than the sun, typically at night Global.SunAngle = m_moon.getAngle(); Global.DayLight.position = m_moon.getDirection(); Global.DayLight.direction = -1.0f * m_moon.getDirection(); keylightintensity = moonlightlevel; m_lightintensity = 0.35f; // if the moon is up, it overrides the twilight twilightfactor = 0.0f; keylightcolor = glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 202.0f / 255.0f ); } else { // regular situation with sun as the key light Global.SunAngle = m_sun.getAngle(); Global.DayLight.position = m_sun.getDirection(); Global.DayLight.direction = -1.0f * m_sun.getDirection(); keylightintensity = sunlightlevel; m_lightintensity = 1.0f; // include 'golden hour' effect in twilight lighting float const duskfactor = 1.0f - clamp( Global.SunAngle, 0.0f, 18.0f ) / 18.0f; keylightcolor = interpolate( glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), duskfactor ); } // ...retrieve current sky colour and brightness... auto const skydomecolour = m_skydome.GetAverageColor(); auto const skydomehsv = colors::RGBtoHSV( skydomecolour ); // sun strength is reduced by overcast level keylightintensity *= ( 1.0f - std::min( 1.f, Global.Overcast ) * 0.65f ); // intensity combines intensity of the sun and the light reflected by the sky dome // it'd be more technically correct to have just the intensity of the sun here, // but whether it'd _look_ better is something to be tested auto const intensity = std::min( 1.15f * ( 0.05f + keylightintensity + skydomehsv.z ), 1.25f ); // the impact of sun component is reduced proportionally to overcast level, as overcast increases role of ambient light auto const diffuselevel = interpolate( keylightintensity, intensity * ( 1.0f - twilightfactor ), 1.0f - std::min( 1.f, Global.Overcast ) * 0.75f ); // ...update light colours and intensity. keylightcolor = keylightcolor * diffuselevel; Global.DayLight.diffuse = glm::vec4( keylightcolor, Global.DayLight.diffuse.a ); Global.DayLight.specular = glm::vec4( keylightcolor * 0.85f, diffuselevel ); // tonal impact of skydome color is inversely proportional to how high the sun is above the horizon // (this is pure conjecture, aimed more to 'look right' than be accurate) float const ambienttone = clamp( 1.0f - ( Global.SunAngle / 90.0f ), 0.0f, 1.0f ); Global.DayLight.ambient[ 0 ] = interpolate( skydomehsv.z, skydomecolour.r, ambienttone ); Global.DayLight.ambient[ 1 ] = interpolate( skydomehsv.z, skydomecolour.g, ambienttone ); Global.DayLight.ambient[ 2 ] = interpolate( skydomehsv.z, skydomecolour.b, ambienttone ); Global.fLuminance = intensity; // update the fog. setting it to match the average colour of the sky dome is cheap // but quite effective way to make the distant items blend with background better // NOTE: base brightness calculation provides scaled up value, so we bring it back to 'real' one here Global.FogColor = m_skydome.GetAverageHorizonColor(); // weather-related simulation factors Global.FrictionWeatherFactor = ( Global.Weather == "rain:" ? 0.85f : Global.Weather == "snow:" ? 0.75f : 1.0f ); Global.Period = ( m_sun.getAngle() > -12.0f ? "day:" : "night:" ); if( ( true == ( FreeFlyModeFlag || Global.CabWindowOpen ) ) && ( Global.Weather == "rain:" ) ) { if( m_rainsound.is_combined() ) { m_rainsound.pitch( Global.Overcast - 1.0 ); } m_rainsound .gain( m_rainsound.m_amplitudeoffset + m_rainsound.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { m_rainsound.stop(); } update_wind(); } void world_environment::update_precipitation() { m_precipitation.update(); } void world_environment::update_wind() { auto const timedelta{ static_cast( Timer::GetDeltaTime() ) }; m_wind.change_time -= timedelta; if( m_wind.change_time < 0 ) { m_wind.change_time = Random( 5, 15 ); m_wind.velocity_change = Random( -0.2, 0.2 ); if( Random() < 0.05 ) { // changes in wind direction should be less frequent than changes in wind speed // TBD, TODO: configuration-driven direction change frequency m_wind.azimuth_change = Random( -5, 5 ); } else { // keep direction change periods short, to avoid too drastic changes in direction m_wind.azimuth_change = 0.0; } } // TBD, TODO: wind configuration m_wind.azimuth = clamp_circular( m_wind.azimuth + m_wind.azimuth_change * timedelta ); // HACK: negative part of range allows for some quiet periods, without active wind m_wind.velocity = clamp( m_wind.velocity + m_wind.velocity_change * timedelta, -2, 4 ); // convert to force vector auto const polarangle { glm::radians( 90.f ) }; // theta auto const azimuthalangle{ glm::radians( m_wind.azimuth ) }; // phi // convert spherical coordinates to opengl coordinates m_wind.vector = std::max( 0.f, m_wind.velocity ) * glm::vec3( std::sin( polarangle ) * std::sin( azimuthalangle ) * -1, std::cos( polarangle ), std::sin( polarangle ) * std::cos( azimuthalangle ) ); } void world_environment::time( int const Hour, int const Minute, int const Second ) { m_sun.setTime( Hour, Minute, Second ); } //---------------------------------------------------------------------------