roguestar-gl-0.4.0.1: src/Models/Sky.hs
{-# LANGUAGE OverloadedStrings #-}
module Models.Sky
(SkyInfo(..),default_sky,
SunInfo(..),default_sun,
sunInfoOf,
makeSky,
makeSun,
skyAbsorbtionFilter,
sunVector,
sunColor,
LightingConfiguration(..),
lightingConfiguration,
ambientSkyRadiation)
where
import RSAGL.Math
import RSAGL.Extras.Sky
import RSAGL.Extras.ColorPhysics
import RSAGL.Modeling
import RSAGL.Modeling.Noise
import Scene
import Data.Monoid
import RSAGL.Types
import qualified Data.ByteString as B
data SkyInfo = SkyInfo {
sky_info_biome :: B.ByteString,
sky_info_degrees_after_midnight :: Integer,
sky_info_degrees_latitude :: Integer,
sky_info_degrees_axial_tilt :: Integer,
-- | indicates current season, where 0 degrees is summer in northern hemisphere (positive latitude)
-- given positive axial tilt, and 180 degrees is winter.
sky_info_degrees_orbital :: Integer,
sky_info_solar_kelvins :: Integer }
deriving (Eq,Ord,Show)
data SunInfo = SunInfo {
-- | on a logarithmic scale base 1.01, i.e. 5 means multiply the size by 1.01**5
sun_info_size_adjustment :: Integer,
sun_info_kelvins :: Integer }
deriving (Eq,Ord,Show)
default_sky :: SkyInfo
default_sky = SkyInfo {
sky_info_biome = "oceanbiome",
sky_info_degrees_after_midnight = 0,
sky_info_degrees_latitude = 100,
sky_info_degrees_axial_tilt = 0,
sky_info_degrees_orbital = 90,
sky_info_solar_kelvins = 5800 }
default_sun :: SunInfo
default_sun = sunInfoOf default_sky
-- | generates a 'SunInfo' from a 'SkyInfo'
sunInfoOf :: SkyInfo -> SunInfo
sunInfoOf sky_info = SunInfo {
sun_info_size_adjustment = abs (sky_info_degrees_latitude sky_info) + (fst $ biomeAtmosphere $ sky_info_biome sky_info),
sun_info_kelvins = sky_info_solar_kelvins sky_info }
medium_atmosphere :: Atmosphere
medium_atmosphere = [
AtmosphereLayer Air 0.75 9.0e-4,
AtmosphereLayer Vapor 0.25 2.5e-4,
AtmosphereLayer (Dust $ gray 0.5) 0.01 1.0e-4 ]
thin_atmosphere :: Atmosphere
thin_atmosphere = [
AtmosphereLayer Air 0.075 9.0e-5,
AtmosphereLayer Vapor 0.025 2.5e-5 ]
thick_atmosphere :: Atmosphere
thick_atmosphere = [
AtmosphereLayer Air 1.5 9.0e-4,
AtmosphereLayer Vapor 1.0 2.5e-4,
AtmosphereLayer (Dust $ gray 0.5) 0.02 1.0e-4 ]
arid_atmosphere :: Atmosphere
arid_atmosphere = [
AtmosphereLayer Air 0.05 2.5e-3,
AtmosphereLayer (Dust $ rust) 0.1 1.0e-3 ]
biomeAtmosphere :: B.ByteString -> (Integer,Atmosphere)
biomeAtmosphere "rockbiome" = (0,arid_atmosphere)
biomeAtmosphere "icyrockbiome" = (-100,thin_atmosphere)
biomeAtmosphere "grasslandbiome" = (35,medium_atmosphere)
biomeAtmosphere "tundrabiome" = (-75,thin_atmosphere)
biomeAtmosphere "desertbiome" = (100,arid_atmosphere)
biomeAtmosphere "oceanbiome" = (5,medium_atmosphere)
biomeAtmosphere "mountainbiome" = (-15,thin_atmosphere)
biomeAtmosphere "swampbiome" = (35,thick_atmosphere)
biomeAtmosphere _ = (0,[])
-- | 'sunVectorOf' indicates vector pointing at the sun.
sunVector :: SkyInfo -> Vector3D
sunVector sky_info =
rotate (Vector3D 1 0 0) (fromDegrees $ (realToFrac $ sky_info_degrees_latitude sky_info) +
(cosine $ fromDegrees $ realToFrac $ sky_info_degrees_orbital sky_info) *
(realToFrac $ sky_info_degrees_axial_tilt sky_info)) $
rotate (Vector3D 0 0 1) (fromDegrees $ realToFrac $ sky_info_degrees_after_midnight sky_info) $
Vector3D 0 (-1) 0
-- | Apparent temperature of a color in kelvins.
temperatureColor :: Integer -> RGB
temperatureColor kelvins = lerpBetweenClamped (770,realToFrac kelvins,1060)
(gray 0,maximizeRGB $ blackBodyRGB $ realToFrac kelvins)
-- | Apparent color of light comming from the sun.
sunColor :: SunInfo -> RGB
sunColor sun_info = temperatureColor (sun_info_kelvins sun_info)
-- | The size of a very ordinary sun-like star as seen from a very temperate climate.
base_star_size :: RSdouble
base_star_size = 0.1
-- | Radius of the sun at a standard distance eye-to-center of 10 units.
sunSize :: SunInfo -> RSdouble
sunSize sun_info = base_star_size * (5800^2 / (realToFrac $ sun_info_kelvins sun_info ^2)) * 1.01 ** (realToFrac $ sun_info_size_adjustment sun_info)
-- | 'makeSky' generates a sky sphere.
makeSky :: SkyInfo -> Modeling ()
makeSky sky_info = model $
do hilly_silhouette
model $
do let v = sunVector sky_info
skyHemisphere origin_point_3d (Vector3D 0 1 0) 5.0
affine $ scale (Vector3D 2 1 2)
material $ atmosphereScatteringMaterial (snd $ biomeAtmosphere $ sky_info_biome sky_info)
[(v,maximizeRGB $ blackBodyRGB $ realToFrac $ sky_info_solar_kelvins sky_info)]
(dynamicSkyFilter 0.05 0.5)
-- | Implements absorbtion of light sources passing through the sky sphere. In particular, this turns off all lights
-- inside 'scene_layer_sky_sphere'.
skyAbsorbtionFilter :: SkyInfo -> LightSourceLayerTransform
skyAbsorbtionFilter sky_info = LightSourceLayerTransform $ \entering_layer originating_layer ls -> let v = direction ls in
case () of
() | entering_layer == scene_layer_sky_sphere || isNoLight ls -> NoLight
() | originating_layer <= scene_layer_sky_sphere || entering_layer > scene_layer_sky_sphere || originating_layer <= entering_layer -> ls
() | originating_layer == scene_layer_distant && entering_layer == scene_layer_orbit -> mapLightSource (mapBoth $ scaleRGB $ sunlightFadeFactor (fromDegrees 30) v) ls
() | entering_layer == scene_layer_far_sky -> sunFade (fromDegrees 10) v ls
() | entering_layer == scene_layer_clouds -> sunFade (fromDegrees 5) v ls
() | entering_layer == scene_layer_near_sky -> sunFade (fromDegrees 2) v ls
() | otherwise -> sunFade (fromDegrees 0) v ls
where absorbtion v = filterRGB $ (absorbtionFilter . absorbtionFilter) $ atmosphereAbsorbtion (snd $ biomeAtmosphere $ sky_info_biome sky_info) (Point3D 0 1 0) v
direction (PointLight { lightsource_position = p }) = vectorToFrom p origin_point_3d
direction (DirectionalLight { lightsource_direction = d }) = d
direction NoLight = Vector3D 0 1 0
sunFade tolerance v = mapLightSource (mapBoth (absorbtion v) `mappend` mapBoth (scaleRGB $ sunlightFadeFactor tolerance v))
-- | The amount of fade of the sun based on falling below the horizon.
sunlightFadeFactor :: Angle -> Vector3D -> RSdouble
sunlightFadeFactor tolerance v = max 0 $ lerpBetweenClamped (85,toDegrees $ angleBetween v (Vector3D 0 1 0),95+toDegrees tolerance) (1.0,0.0)
-- | Information about the lighting environment. All values are between 0 and 1, indicating a relative scale compared to the normal, full brightness.
data LightingConfiguration = LightingConfiguration {
-- | Apparent brightness of the sun. This will fade to zero along the horizon and equal zero at night.
lighting_sunlight,
-- | Apparent brightness of ambient sky radiation. This will fade to black more slowly than 'lighting_sunlight', lingering after the sun has set.
lighting_skylight,
-- | Apparent brightness of the nightlight. This is a blue light with heavy ambient component that simulates human night vision.
lighting_nightlight,
-- | Brightness of artificial lights. Typically all artificial lights intended for nighttime illumination should be scaled based on this value.
lighting_artificial :: RSdouble }
lightingConfiguration :: SkyInfo -> LightingConfiguration
lightingConfiguration sky_info = result
where result = LightingConfiguration {
lighting_sunlight = sunlightFadeFactor (fromDegrees 0) (sunVector sky_info),
lighting_skylight = sunlightFadeFactor (fromDegrees 10) (sunVector sky_info),
lighting_nightlight = max 0 $ 1.0 - lighting_sunlight result - lighting_skylight result,
lighting_artificial = min 1 $ max 0 $ 1.0 - lighting_sunlight result - (lighting_nightlight result/4) + (1000 / realToFrac (sky_info_solar_kelvins sky_info))^3 }
-- | Get the color of the ambient sky radiation by sampling a very small number vectors into the sky.
ambientSkyRadiation :: SkyInfo -> RGB
ambientSkyRadiation sky_info = abstractAverage $ map (atmosphereScattering atmosphere [sun_info] (Point3D 0 1 0)) test_vectors
where atmosphere = snd $ biomeAtmosphere $ sky_info_biome sky_info
sun_info = (sunVector sky_info,sunColor $ sunInfoOf sky_info)
test_vectors = map vectorNormalize $
do x <- [1,0,-1]
y <- [1,0,-1]
return $ Vector3D x 1 y
-- 'makeSun' generates a perspectiveSphere of the sun.
makeSun :: SunInfo -> Modeling ()
makeSun sun_info = model $
do let size = sunSize sun_info
let temp = sun_info_kelvins sun_info
let temperaturePattern t = pattern (cloudy (fromInteger $ temp + sun_info_size_adjustment sun_info) base_star_size)
[(0.0,pure $ temperatureColor $ t + 700),(0.5,pure $ temperatureColor t),(1.0,pure $ temperatureColor $ t - 700)]
perspectiveSphere (Point3D 0 (-10) 0) size origin_point_3d
material $
do pigment $ pure $ gray 0
emissive $ pattern (spherical (Point3D 0 (size-10) 0) size) [(0.0,temperaturePattern temp),
(0.5,temperaturePattern $ temp - 200),
(0.75,temperaturePattern $ temp - 500),
(0.9,temperaturePattern $ temp - 800),
(1.0,temperaturePattern $ temp - 1000)]
hilly_silhouette :: Modeling ()
hilly_silhouette = model $
do heightDisc (0,0) 8 (\(x,z) -> perlinNoise (Point3D x 0 z) - 6.9 + distanceBetween origin_point_3d (Point3D x 0 z))
affine $ scale (Vector3D 1 0.2 1)
material $ pigment $ pure blackbody
disregardSurfaceNormals