luv

Workshop wiki

sky.lisp

luvcraft/sky.lisp

system luvcraft/core · 26 definitions · on GitHub

The sky environment: a small session clock and a keyframed day profile.

The environment is continuous and frame-wide: Lisp evaluates the clock and profile once per frame into sky-frame-parameters, and shaders evaluate spatially varying image mathematics from those uniform lanes. The clock is a session object with mutable runtime identity worth inspecting; the profile is ordinary data. There is deliberately no sky class hierarchy until more than one actual sky model exists.

in-package#:luvcraft
defclasssky-clock
day-fraction:initarg:day-fraction:initform
/6.524.0
:typereal:quantity
:quantity:day-fraction:unit:one
:accessorsky-clock-day-fraction:documentation

Time of day in [0,1): 0 midnight, 0.25 sunrise, 0.5 noon. The game starts at half past six in the morning.

rate:initarg:rate:initform
/1.01200.0
:typereal:quantity
:quantity:sky-cycle-rate:unit:hertz
:accessorsky-clock-rate:documentation"Day fractions per real second; the default day is 20 minutes."
paused-p:initarg:paused-p:initformnil:accessorsky-clock-paused-p
pinned-day-fraction:initarg:pinned-day-fraction:initformnil:type
ornullreal
:quantity
:quantity:day-fraction:unit:one
:accessorsky-clock-pinned-day-fraction:documentation"When set, evaluation reads this fixed time for deterministic capture."
:documentation

Mutable time-of-day state owned by a luvcraft session.

SLY can pause it, set a time, change its rate, or pin it without restarting.

defunadvance-sky-clock
clockseconds

Advance clock by a bounded frame delta unless it is paused.

unless
sky-clock-paused-pclock
setf
sky-clock-day-fractionclock
mod
+
sky-clock-day-fractionclock
*
sky-clock-rateclock
seconds
1.0
clock
defunsky-clock-hour
clock

clock's time of day in hours, 0 to 24.

*24.0
sky-clock-day-fractionclock
defun
hourclock

Set clock's time of day to hour, wrapping around midnight.

setf
sky-clock-day-fractionclock
mod
/hour24.0
1.0
hour
defunsky-clock-minutes-per-day
clock

How many real minutes one of clock's days lasts.

/1.0
*60.0
sky-clock-rateclock
defun
minutesclock
setf
sky-clock-rateclock
/1.0
*60.0minutes
minutes
defunsky-clock-current-day-fraction
clock
or
sky-clock-pinned-day-fractionclock
sky-clock-day-fractionclock

A profile is a cyclic sequence of keyframes over the day fraction. Colours are 3-element single-float vectors; scalars are single floats.

luv.arithmetic.records:define-quantity-struct
sky-keyframe
:constructormake-sky-keyframe
day-fraction0.0:typesingle-float:quantity
:quantity:day-fraction:unit:one
zenith-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
horizon-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
sun-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
ambient-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
fog-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
sun-angular-width0.006:typesingle-float:quantity
:quantity:sun-angular-width:unit:radian
exposure1.0:typesingle-float:quantity
:quantity:exposure:unit:one
cloudiness0.5:typesingle-float:quantity
:quantity:cloudiness:unit:one
fog-near0.0:typesingle-float:quantity
:quantity:view-distance:unit:cell
fog-far180.0:typesingle-float:quantity
:quantity:view-distance:unit:cell
defstruct
sky-profile
:constructor%make-sky-profile
keyframes
:typesimple-vector
defunmake-sky-profile
keyframes

Make a profile from keyframes, sorted cyclically by day fraction.

%make-sky-profile:keyframes
sort
coercekeyframes'simple-vector
#'<:key#'sky-keyframe-day-fraction
defunsky-lerp
fromtoamount
+from
*
-tofrom
amount
defunsky-lerp-color
fromtoamount
map'
simple-vector3
lambda
ab
coerce
sky-lerpabamount
'single-float
fromto
defunsky-smoothstep
edge0edge1value
let
normalized
max0.0
min1.0
/
-valueedge0
-edge1edge0
*normalizednormalized
-3.0
*2.0normalized
defunsky-profile-keyframes-around

The keyframes bracketing day-fraction and the progress between them.

let*
keyframes
sky-profile-keyframesprofile
count
lengthkeyframes
when
zeropcount
error"Sky profile has no keyframes."
if
=count1
values
arefkeyframes0
arefkeyframes0
0.0
loopforindexbelowcountfornext-index=
mod
1+index
count
forstart=
arefkeyframesindex
forend=
arefkeyframesnext-index
forstart-time=
sky-keyframe-day-fractionstart
forend-time=
sky-keyframe-day-fractionend
forspan=
mod
-end-timestart-time
1.0
foroffset=
mod
-day-fractionstart-time
1.0
when
and
pluspspan
<offsetspan
return
valuesstartend
/offsetspan
finally

day-fraction coincides with a keyframe boundary.

cl:return
values
arefkeyframes0
arefkeyframes0
0.0

One evaluated frame of environment state. CPU trigonometry is right for a single sun direction; per-pixel sky shaping stays in the shader.

luv.arithmetic.records:define-quantity-struct
sky-frame-parameters
:constructor%make-sky-frame-parameters
day-fraction0.0:typesingle-float:quantity
:quantity:day-fraction:unit:one
sun-direction
make-vec30.01.00.0
:typevec3:quantity
:quantity:world-direction:unit:one:tensor-order1
day-factor1.0:typesingle-float:quantity
:quantity:day-factor:unit:one
sun-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
sun-angular-width0.006:typesingle-float:quantity
:quantity:sun-angular-width:unit:radian
zenith-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
horizon-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
ambient-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
fog-color
0.00.00.0
:type
simple-vector3
:quantity
:quantity:linear-rgb:unit:one:tensor-order1
exposure1.0:typesingle-float:quantity
:quantity:exposure:unit:one
cloudiness0.5:typesingle-float:quantity
:quantity:cloudiness:unit:one
fog-near0.0:typesingle-float:quantity
:quantity:view-distance:unit:cell
fog-far180.0:typesingle-float:quantity
:quantity:view-distance:unit:cell

The shader and CPU side share the definition in luvcraft.QUANTITIES. This binding checks its ordinary single-float ABI against the actual sky-frame slots once; calls below receive and return unwrapped numbers.

defparameter*sky-fog-view-distance-declaration*
luv.arithmetic:make-represented-value-declaration:representation-type'single-float:quantity-specification
luv.arithmetic:make-declared-quantity-specification'
:quantity:view-distance:unit:cell
:source-form'
view-distance:typesingle-float:quantity
:quantity:view-distance:unit:cell
defparameter*sky-fog-amount-declaration*
luv.arithmetic:make-represented-value-declaration:representation-type'single-float:quantity-specification:source-form'
fog-amount:typesingle-float:quantity
:quantity:fog-amount:unit:one
defparameter*sky-fog-amount-realization*
luv.arithmetic.lisp:make-lisp-arithmetic-realization'luvcraft.arithmetic:fog-amount-at-view-distance:parameter-representation-types'
single-floatsingle-floatsingle-float
:result-representation-type'single-float
defunsky-fog-amount-at-distance

Return production fog attenuation at view-distance for evaluated sky.

check-typeview-distancesingle-float
funcall*sky-fog-amount-function*view-distance
sky-frame-parameters-fog-nearsky
sky-frame-parameters-fog-farsky
defparameter*sky-sun-orbit-tilt*0.44"How far the solar orbit leans out of the world X/Y plane."
define-knobsun-orbit-tilt
:group:sun:quantity
:quantity:sun-orbit-tilt:unit:one
:minimum0.0:maximum1.0:step0.02
*sky-sun-orbit-tilt*
defunsky-sun-orbit-axis

The axis the sun revolves around: world Z, because the orbit is a circle in the X/Y plane displaced along Z.

Anything which needs a stable reference direction transverse to the sun should use this rather than world up. The sun's angle to this axis is the same at every hour, so a basis built from it is uniformly well conditioned; a basis built from world up degenerates toward the sun's own direction as the sun climbs.

make-vec30.00.01.0
defunsky-sun-direction

The unit sun direction: rising at 0.25, zenith-adjacent at 0.5.

The orbit tilts slightly out of the X/Y plane so light stays directional even at noon.

let*
x
coerce'single-float
y
coerce'single-float
make-vec3
coerce'single-float
defunsky-frame-parameters
clockprofile

Evaluate clock against profile once for the coming frame.

let*

Day factor follows the sun's elevation with a soft twilight band, so direct light fades out while the sun crosses the horizon.

day-factor
sky-smoothstep-0.060.16
vec3-ysun-direction
multiple-value-bind
startendprogress
%make-sky-frame-parameters:day-fractionday-fraction:sun-directionsun-direction:day-factor
coerceday-factor'single-float
:sun-color
sky-lerp-color
sky-keyframe-sun-colorstart
sky-keyframe-sun-colorend
progress
:sun-angular-width
sky-lerp
sky-keyframe-sun-angular-widthstart
sky-keyframe-sun-angular-widthend
progress
:zenith-color
sky-lerp-color
sky-keyframe-zenith-colorstart
sky-keyframe-zenith-colorend
progress
:horizon-color
sky-lerp-color
sky-keyframe-horizon-colorstart
sky-keyframe-horizon-colorend
progress
:ambient-color
sky-lerp-color
sky-keyframe-ambient-colorstart
sky-keyframe-ambient-colorend
progress
:fog-color
sky-lerp-color
sky-keyframe-fog-colorstart
sky-keyframe-fog-colorend
progress
:exposure
sky-lerp
sky-keyframe-exposurestart
sky-keyframe-exposureend
progress
:cloudiness
coerce
sky-lerp
sky-keyframe-cloudinessstart
sky-keyframe-cloudinessend
progress
'single-float
:fog-near
sky-lerp
sky-keyframe-fog-nearstart
sky-keyframe-fog-nearend
progress
:fog-far
sky-lerp
sky-keyframe-fog-farstart
sky-keyframe-fog-farend
progress
defunmake-default-sky-profile

A clear-day profile whose noon matches the world's original literals.

flet
keyframe
timezenithhorizonsunambientfog&key
width0.006
make-sky-keyframe:day-fractiontime:cloudiness
coercecloudiness'single-float
:zenith-color
coerce
mapcar
lambda
c
coercec'single-float
zenith
'
simple-vector3
:horizon-color
coerce
mapcar
lambda
c
coercec'single-float
horizon
'
simple-vector3
:sun-color
coerce
mapcar
lambda
c
coercec'single-float
sun
'
simple-vector3
:ambient-color
coerce
mapcar
lambda
c
coercec'single-float
ambient
'
simple-vector3
:fog-color
coerce
mapcar
lambda
c
coercec'single-float
fog
'
simple-vector3
:sun-angular-width
coercewidth'single-float
make-sky-profile
list

midnight

keyframe0.00'
0.0060.0100.032
'
0.0200.0280.070
'
0.00.00.0
'
0.0550.0700.135
'
0.0180.0260.062
:cloudiness0.45

pre-dawn

keyframe0.21'
0.0220.0340.095
'
0.0900.0800.130
'
0.420.160.07
'
0.1000.1100.200
'
0.0700.0650.100
:width0.015:cloudiness0.50

sunrise

keyframe0.28'
0.070.190.52
'
0.460.420.56
'
1.550.720.34
'
0.320.310.42
'
0.500.380.36
:width0.015:cloudiness0.56

morning

keyframe0.38'
0.200.440.88
'
0.620.760.95
'
1.100.980.84
'
0.420.530.76
'
0.500.680.92
:cloudiness0.50

noon

keyframe0.50'
0.160.400.92
'
0.580.750.96
'
1.081.020.92
'
0.440.560.80
'
0.460.680.94
:cloudiness0.44

afternoon

keyframe0.62'
0.200.430.88
'
0.640.760.94
'
1.120.980.82
'
0.430.530.75
'
0.520.680.90
:cloudiness0.50

sunset

keyframe0.72'
0.090.170.45
'
0.480.400.50
'
1.650.620.24
'
0.320.280.36
'
0.540.340.30
:width0.015:cloudiness0.58

dusk

keyframe0.79'
0.0300.0450.115
'
0.1200.1050.150
'
0.350.130.06
'
0.1100.1100.200
'
0.0900.0800.115
:width0.015:cloudiness0.50

deep night

keyframe0.90'
0.0060.0100.032
'
0.0200.0280.070
'
0.00.00.0
'
0.0550.0700.135
'
0.0180.0260.062
:cloudiness0.45