luv

Workshop wiki

voxel-light.lisp

luft/voxel-light.lisp

system luft · 34 definitions · on GitHub

luft -- sparse colored voxel light

Local light is an immutable derived field over cells. Its semantic boundary is RGB4, while its physical storage is a sparse set of 8^3 u16 pages. A brightest-first packed frontier computes the componentwise-max fixpoint without allocating coordinate objects in the hot loop.

in-package#:luft
declaim
optimize
speed3
safety2
debug1
defconstant+voxel-light-direct-page-limit+262144"Largest bounded page directory retained directly by a light field."
deftypevoxel-light
'
unsigned-byte12
deftypevoxel-light-page
'
simple-array
unsigned-byte16
512
defunpack-voxel-light
redgreenblue

Pack three local-light levels in the range 0..15 as RGB4.

check-typered
integer015
check-typegreen
integer015
check-typeblue
integer015
logiorred
ashgreen4
ashblue8
defstruct
voxel-light-field
:constructor%make-voxel-light-field
domainpagesdirect-px-pagesy-pagespage-countrevisionvisitspushesstale-pops
:copiernil
domain:typeworld-domain:read-onlyt
pagesnil:read-onlyt
direct-pnil:typeboolean:read-onlyt
x-pages0:type
integer0*
:read-onlyt
y-pages0:type
integer0*
:read-onlyt
page-count0:type
integer0*
:read-onlyt
revision0:type
integer0*
:read-onlyt
visits0:type
integer0*
:read-onlyt
pushes0:type
integer0*
:read-onlyt
stale-pops0:type
integer0*
:read-onlyt
defun%voxel-light-page-key
xyz

X/Y have at most 17 bits and Z has 8. After the 8-cell page shift, 14 + 14 + 5 bits fit comfortably in a fixnum on supported hosts.

defun%voxel-light-page-index
xyz
logior
logandz7
ash
logandy7
3
ash
logandx7
6
defun%voxel-light-directory-index
x-pagesy-pagesxyz
declare
ignorex-pages
+
ashz-3
*32
+
ashy-3
*y-pages
ashx-3
defun%voxel-light-page-at
pagesdirect-px-pagesy-pagesxyz
ifdirect-p
aref
thesimple-vectorpages
gethash
thehash-tablepages
defun%voxel-light-at-coordinates
pagesdirect-px-pagesy-pagesxyz
defun%make-voxel-light-page-storage
domain
let*
directory-count
*x-pagesy-pages32
direct-p
values
ifdirect-p
make-arraydirectory-count:initial-elementnil
make-hash-table:test#'eql
direct-px-pagesy-pages
defunvoxel-light-at
fieldxyz

Return field's RGB4 value at an in-domain cell, or zero outside it.

check-typefieldvoxel-light-field
let
domain
voxel-light-field-domainfield
if
and
typepx'
integer0*
typepy'
integer0*
typepz'
integer0*
%voxel-light-at-coordinates
voxel-light-field-pagesfield
voxel-light-field-direct-pfield
voxel-light-field-x-pagesfield
voxel-light-field-y-pagesfield
xyz
0
defunvoxel-light-at-site
fieldcell
check-typefieldvoxel-light-field
checked-site
voxel-light-field-domainfield
cell
unless
error"Voxel light is defined on cells, not site ~S."cell
%voxel-light-at-coordinates
voxel-light-field-pagesfield
voxel-light-field-direct-pfield
voxel-light-field-x-pagesfield
voxel-light-field-y-pagesfield
defunvoxel-light-at-lattice-point
fieldxyz

Sample light at a cubical lattice point by joining its incident cells.

This canonical point-owned value is shared by every bevel primitive meeting there, so independently emitted face, band, and fan instances remain lit continuously.

check-typefieldvoxel-light-field
let
answer0
declare
dolist
dx'
-10
dolist
dy'
-10
dolist
dz'
-10
answer
defun%voxel-light-lattice-key
xyz

Z lattice coordinates need nine bits and a maximum-size horizontal domain's far boundary is the inclusive coordinate 2^17, so each horizontal lane reserves eighteen bits.

logiorz
ashy9
ashx27
defunvoxel-light-at-mesh-point
fieldtick-xtick-ytick-z&optionallattice-cache

Trilinearly sample field at an exact luft mesh-tick position.

The result remains RGB4. Sampling is a pure function of world position, so separate face, band, fan, and attachment instances agree at shared vertices, including the width-four medial limit where distinct site owners coincide. When supplied, lattice-cache memoizes canonical cubical-corner readings across many mesh points in one derived render population.

check-typefieldvoxel-light-field
labels
axis
tick
multiple-value-bind
lowremainder
valueslowremainder
lattice-light
xyz
iflattice-cache
let
multiple-value-bind
lightpresent-p
gethashkeylattice-cache
ifpresent-plight
setf
gethashkeylattice-cache
multiple-value-bind
x0x1wx0w
axistick-x
multiple-value-bind
y0y1wy0w
axistick-y
multiple-value-bind
z0z1wz0w
axistick-z
let
red0
green0
blue0
dotimes
dx2
dotimes
dy2
dotimes
dz2
let*
weight
*
if
zeropdx
x0wx1w
if
zeropdy
y0wy1w
if
zeropdz
z0wz1w
light
lattice-light
+x0dx
+y0dy
+z0dz
flet
pack-voxel-light
rounded-lanered
rounded-lanegreen
rounded-laneblue
defunmake-voxel-light-source

Pack an in-domain positive cell site and its RGB4 source value.

check-typecellsite
unless
error"A voxel-light source needs a positive cell site, got ~S."cell
logiorcell
ashlight48
defun%voxel-light-source-cell
source
ldb
byte480
source
defun%material-opacity
material-cellsopacity-tablecell
multiple-value-bind
materialpresent-p
gethashcellmaterial-cells
ifpresent-p
if
<material
lengthopacity-table
arefopacity-tablematerial
error"Material index ~D has no voxel-light opacity."material
0
defunsolve-voxel-light
domainmaterial-cellsopacity-tablesources&key
revision0

Compute immutable colored local light with a packed brightest-first frontier.

material-cells maps positive cell sites to dense material indices. opacity-table maps those indices to entered-cell loss in 0..15. Every ordinary step additionally costs one level. sources are values from make-voxel-light-source; a source illuminates its own cell regardless of that cell's opacity. Concurrent colors and sources meet by componentwise maximum.

check-typedomainworld-domain
check-typematerial-cellshash-table
check-typeopacity-table
vector
unsigned-byte8
check-typerevision
integer0*
multiple-value-bind
pagesdirect-px-pagesy-pages
let
buckets
make-array16
visits0
pushes0
stale-pops0
page-count0
queued0
current-priority0
dotimes
level16
setf
arefbucketslevel
make-array64:element-type'
unsigned-byte64
:adjustablet:fill-pointer0
labels
light-at
xyz
%voxel-light-at-coordinatespagesdirect-px-pagesy-pagesxyz
page-for-write
xyz
or
%voxel-light-page-atpagesdirect-px-pagesy-pagesxyz
let
page
make-array+voxel-light-page-cell-count+:element-type'
unsigned-byte16
:initial-element0
ifdirect-p
setf
aref
thesimple-vectorpages
page
setf
gethash
thehash-tablepages
page
incfpage-count
page
admit
cellxyzproposed
let*
old
light-atxyz
unless
=oldjoined
setf
aref
thevoxel-light-page
page-for-writexyz
joined
let
priority
vector-push-extend
logiorcell
ashjoined48
arefbucketspriority
setfcurrent-priority
maxcurrent-prioritypriority
incfqueued
incfpushes
drain-neighbor
xyzlightdxdydz
let
target-x
+xdx
target-y
+ydy
target-z
+zdz
when
and
<=0target-x
<target-xx-limit
<=0target-y
<target-yy-limit
<=0target-z
<target-z+top-z+
let*
target
make-sitedomaintarget-xtarget-ytarget-z+cell-extent+1
loss
1+
%material-opacitymaterial-cellsopacity-tabletarget
unless
zeropcandidate
admittargettarget-xtarget-ytarget-zcandidate
mapnilsources

rgb joins can retain an old bright lane while improving a dim one. That may re-admit a value to a bucket brighter than the source being drained, so CURRENT-PRIORITY is dynamic rather than a one-way scan.

loopwhile
pluspqueued
do
loopwhile
zerop
fill-pointer
arefbucketscurrent-priority
do
decfcurrent-priority
let*
decfqueued
if
/=light
light-atxyz
incfstale-pops
progn
incfvisits
drain-neighborxyzlight-100
drain-neighborxyzlight100
drain-neighborxyzlight0-10
drain-neighborxyzlight010
drain-neighborxyzlight00-1
drain-neighborxyzlight001
%make-voxel-light-fielddomainpagesdirect-px-pagesy-pagespage-countrevisionvisitspushesstale-pops