luv

Workshop wiki

studio.lisp

luft/render/studio.lisp

system luft/render · 115 definitions · on GitHub

in-package#:luft.render
defvar*viewer*nil
defparameter*inspection-ink-p*t"Whether a ray hit gets a blueprint reticle and local triangle-edge lens."
defparameter*inspection-reach*600.0"How far, in cells, the atelier's pointer ray may inspect."
defparameter*projection*:isometric

Either :PERSPECTIVE or :ISOMETRIC.

An isometric picture has no vanishing point, so two chamfers the same width are the same width on screen wherever they sit. That is what makes it the projection to judge a shape rule in.

defparameter*isometric-height*18.0"How many world units of height an isometric frame spans."
defparameter*character-time*nil

Animation clock, in seconds, to hold the traveler at, or NIL to run free.

A character plate needs the same pose every time it is rendered. The viewer otherwise derives the clock from its own frame counter, which no capture can predict.

defconstant+orthographic-near+-4096.0
defconstant+orthographic-far+4096.0
defclassfly-camera
position:initarg:position:accessorcamera-position
yaw:initarg:yaw:initform0.0:accessorcamera-yaw
pitch:initarg:pitch:initform0.0:accessorcamera-pitch
field-of-view:initarg:field-of-view:initform
*70.0
/pi180
:accessorcamera-field-of-view
defclassviewer-mode
defclassisometric-walk-mode
:documentation

Absolute-pointer luft play: hover terrain, click a route, scroll zoom.

defclassorbit-mode
:documentation

The original relative-pointer atelier orbit and direct keyboard mode.

defgenericviewer-mode-inspection-p
mode
:documentation

Whether mode continuously points into luft terrain.

defclasssite-inspection
source:initarg:source:readersite-inspection-source
site:initarg:site:readersite-inspection-site
cell:initarg:cell:readersite-inspection-cell
point:initarg:point:readersite-inspection-point
distance:initarg:distance:readersite-inspection-distance
star-mask:initarg:star-mask:readersite-inspection-star-mask
stock:initarg:stock:readersite-inspection-stock
:documentation

One retained semantic ray hit, suitable for sparse inspection.

luft sites remain packed integers in dense products. This object exists only at the atelier boundary where a person has selected one site.

defmethodprint-object
inspectionsite-inspection
stream
print-unreadable-object
inspectionstream:typet
let
site
site-inspection-siteinspection
formatstream"(~D ~D ~D) extent ~3,'0B ~:[-~;+~] at ~,2F"
site-inspection-distanceinspection
defunmake-fly-camera
&key
yaw2.2455373
pitch-0.5165006
field-of-view0.9599311
make-instance'fly-camera:positionposition:yawyaw:pitchpitch:field-of-viewfield-of-view
defunreset-viewer-camera

Return viewer to the sanctuary spawn and its following isometric view.

whenviewer
let
camera
viewer-cameraviewer
player
viewer-playerviewer
setf2.2455373-0.51650060.9599311*projection*:isometric

The player owns the frame now: keep the playable view close enough to read footsteps and terrain relief without losing the next turn of the route.

*isometric-height*
ifplayer18.064.0
ifplayer
let
setf
viewer-playerviewer
spawn

Reset is also a camera move: do not briefly reveal the old outside perch when the spawn happens to be indoors.

setf
camera-positioncamera
when
viewer-rendererviewer
setf
renderer-history-valid-p
viewer-rendererviewer
nil
viewer
defuncamera-basis
camera
let*
yaw
pitch
forward
vec3:make-vec3
*
cosyaw
cospitch
*
sinyaw
cospitch
sinpitch
right
up
vec3:vec3-crossrightforward
valuesrightupforward
defunadd-scaled-directions
origin&restdirection-scales
let
x
vec3:vec3-xorigin
y
vec3:vec3-yorigin
z
vec3:vec3-zorigin
loopfor
directionscale
ondirection-scalesby#'cddrdo
incfx
*
vec3:vec3-xdirection
scale
incfy
*
vec3:vec3-ydirection
scale
incfz
*
vec3:vec3-zdirection
scale
defgenericinspection-face-stock
sourceface
:documentation

Return the atelier stock at face in source.

defmethodinspection-face-stock
sourcescene
face
defunray-axis-crossings
positiondirection

Return grid step, first crossing distance, and crossing interval.

cond
pluspdirection
values1
/direction
minuspdirection
values-1
/
-direction
t
values0most-positive-single-floatmost-positive-single-float
defunray-entry-face
solidcell-xcell-ycell-zaxisstep

Return the outward face through which a ray entered cell along axis.

let*
anchor-x
+cell-x
if
and
eqaxis:x
minuspstep
10
anchor-y
+cell-y
if
and
eqaxis:y
minuspstep
10
anchor-z
+cell-z
if
and
eqaxis:z
minuspstep
10
geometry
luft:make-sitedomainanchor-xanchor-yanchor-zextent1
occupancy
luft:orient-face-outwarddomaingeometryoccupancy
defunmake-site-inspection
sourcefacecell-xcell-ycell-zpointdistance
let*
occupancy
cell
luft:make-sitedomaincell-xcell-ycell-zluft:+cell-extent+1
make-instance'site-inspection:sourcesource:siteface:cellcell:pointpoint:distancedistance:star-mask:stock
defunraycast-site
sourceorigindirection&key
max-distance*inspection-reach*

Return the first outward luft face met by a continuous lattice ray.

Tied edge and corner crossings advance together, so a ray never reports a cell it merely touches. The returned site-inspection is the one sparse object boundary over the packed chain and dense face records.

let*
direction
x
floor
vec3:vec3-xorigin
y
floor
vec3:vec3-yorigin
z
floor
vec3:vec3-zorigin
step-xstep-ystep-znext-xnext-ynext-zdelta-xdelta-ydelta-z
entry-axisnil
entry-step0
multiple-value-setq
step-xnext-xdelta-x
ray-axis-crossings
vec3:vec3-xorigin
vec3:vec3-xdirection
multiple-value-setq
step-ynext-ydelta-y
ray-axis-crossings
vec3:vec3-yorigin
vec3:vec3-ydirection
multiple-value-setq
step-znext-zdelta-z
ray-axis-crossings
vec3:vec3-zorigin
vec3:vec3-zdirection
loop
when
whenentry-axis
let*
face
ray-entry-facesolidxyzentry-axisentry-step
point
whenface
let
next
minnext-xnext-ynext-z
when
>nextmax-distance
setfdistancenextentry-axisnil

Remember one carrier face for tied crossings, choosing the axis most aligned with the ray. All tied cells still advance together.

flet
remember
axisstepcomponent
when
or
nullentry-axis
>
abscomponent
abs
vec3:vec3-componentdirectionentry-axis
setfentry-axisaxisentry-stepstep
when
<=next-x
+next1.0e-6
incfxstep-x
incfnext-xdelta-x
remember:xstep-x
vec3:vec3-xdirection
when
<=next-y
+next1.0e-6
incfystep-y
incfnext-ydelta-y
remember:ystep-y
vec3:vec3-ydirection
when
<=next-z
+next1.0e-6
incfzstep-z
incfnext-zdelta-z
remember:zstep-z
vec3:vec3-zdirection
defunconstrain-viewer-follow-camera

Keep a following camera on the traveler's side of sanctuary geometry.

The player can pass through a gate while the preferred isometric perch is still outside the rampart. Cast from the look-at point to the *actual* smoothed camera position, rather than the ideal perch: this preserves the soft follow but never lets a wall sit between the eye and the hermit.

let
player
viewer-playerviewer
whenplayer
let*
player-position
walking-player-positionplayer
heading-x
walking-player-heading-xplayer
heading-y
walking-player-heading-yplayer

Match follow-walking-player's look-ahead exactly. This is the point the player-owned frame promises to keep visible.

aim-x
+
vec3:vec3-xplayer-position
*2.4heading-x
aim-y
+
vec3:vec3-yplayer-position
*2.4heading-y
aim-z
+
vec3:vec3-zplayer-position
1.45
aim
vec3:make-vec3aim-xaim-yaim-z
camera-position
camera-position
viewer-cameraviewer
dx
-
vec3:vec3-xcamera-position
aim-x
dy
-
vec3:vec3-ycamera-position
aim-y
dz
-
vec3:vec3-zcamera-position
aim-z
distance
sqrt
+
*dxdx
*dydy
*dzdz
when
let
inspection

The sanctuary is finite while the pleasant isometric perch can look out across its edge. An off-map ray is simply no occluder, never a reason to park the frame.

handler-case
raycast-site
viewer-sourceviewer
aim:max-distancedistance
error
nil
wheninspection

Leave enough room for the near plane and an over-the- shoulder silhouette. The camera therefore tucks into the room smoothly instead of landing on the wall itself.

let*
safe-distance
max0.75
-
site-inspection-distanceinspection
0.40
scale
/safe-distancedistance
setf
vec3:vec3-xcamera-position
+aim-x
vec3:vec3-ycamera-position
+aim-y
vec3:vec3-zcamera-position
+aim-z
viewer
defunprojection-lane

The four projection coefficients and the homogeneous-divisor selector.

Both projections use the same three rows: clip X and Y are the view coordinates scaled, and clip Z is an affine function of view depth. The perspective divisor is the view depth and the isometric divisor is one, so the selector is the whole of the difference.

let
aspect
/
coercewidth'single-float
height
ecase*projection*
:perspective
let
focal
values
/focalaspect
focal
/far
-farnear
/
-
*farnear
-farnear
1.0
:isometric
let
values
/1.0
*halfaspect
/1.0half
/1.0
-farnear
/
-near
-farnear
0.0
defstruct
frame-view
:constructor%make-frame-view

One immutable camera sample shared by geometry and temporal motion.

positionrightupforwardprojectiondivisorjitter
defunhalton
indexbase
loopwithfraction=
/1.0base
withvalue=0.0while
pluspindex
do
incfvalue
*fraction
modindexbase
setfindex
floorindexbase
fraction
/fractionbase
finally
return
coercevalue'single-float
defuntemporal-jitter
frame-indexwidthheight

Sample the eight-position Halton(2,3) sequence in clip coordinates.

let
sample
1+
modframe-index8
vector
coerce'single-float
coerce'single-float
defuncapture-frame-view
camerawidthheightjitter
multiple-value-bind
rightupforward
let
near
far
multiple-value-bind
pxpypzpwdivisor
%make-frame-view:position
let
position
camera-positioncamera
vec3:make-vec3
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
:rightright:upup:forwardforward:projection
vectorpxpypzpw
:divisordivisor:jitterjitter
defuncamera-uniform-data
viewpreviousinspection-parametersink-strengthplayer&optional
flet
lane
vectorfourth
list
vec3:vec3-xvector
vec3:vec3-yvector
vec3:vec3-zvector
fourth
multiple-value-bind
characterprevious-charactercharacter-directionballprevious-ball
ifplayer
values'
0.00.00.00.0
'
0.00.00.00.0
'
0.01.00.01.0
'
0.00.00.00.0
'
0.00.00.00.0
make-array108:element-type'single-float:initial-contents
mapcar
lambda
value
coercevalue'single-float
append
lane
frame-view-positionview
0.0
lane
frame-view-rightview
0.0
lane
frame-view-upview
0.0
lane
frame-view-forwardview
0.0
coerce
frame-view-projectionview
'list
list*wireframe*
frame-view-divisorview
ink-strength
lane
frame-view-positionprevious
0.0
lane
frame-view-rightprevious
0.0
lane
frame-view-upprevious
0.0
lane
frame-view-forwardprevious
0.0
coerce
frame-view-projectionprevious
'list

MetalFX receives the current sample offset separately and its default motion-vector contract is unjittered. Retain the previous lane shape for ABI stability, but only the current two components participate in rasterization.

list
aref
frame-view-jitterview
0
aref
frame-view-jitterview
1
aref
frame-view-jitterprevious
0
aref
frame-view-jitterprevious
1
coerceinspection-parameters'list
character
light-uniform-data*light*
frame-view-positionview
exposure
previous-charactercharacter-directionballprevious-ball
defunviewer-logical-extent
let
canvas
viewer-canvasviewer
list
canvas-widthcanvas
canvas-heightcanvas
defunviewer-pointer-position
let
if
viewer-pointer-captured-pviewer
values
/
firstextent
2.0
/
secondextent
2.0
values
or
viewer-pointer-xviewer
/
firstextent
2.0
or
viewer-pointer-yviewer
/
secondextent
2.0
defunviewer-pointer-ray

Return the world-space origin and direction through viewer's pointer.

let*
camera
viewer-cameraviewer
width
firstextent
projection
frame-view-projectionview
multiple-value-bind
pointer-xpointer-y
let*
ndc-x
-
*2.0
/pointer-xwidth
1.0
ndc-y
-1.0
*2.0
/pointer-yheight
right-scale
/ndc-x
arefprojection0
up-scale
/ndc-y
arefprojection1
if
eq*projection*:perspective
values
camera-positioncamera
vec3:vec3-normalize
add-scaled-directions
frame-view-forwardview
frame-view-rightview
right-scale
frame-view-upview
up-scale
values
add-scaled-directions
camera-positioncamera
frame-view-rightview
right-scale
frame-view-upview
up-scale
frame-view-forwardview
defunsame-inspected-site-p
leftright
or
eqleftright
andleftright
=
site-inspection-siteleft
site-inspection-siteright
defunupdate-viewer-inspection
multiple-value-bind
origindirection
let*
inspection
handler-case
raycast-site
viewer-sourceviewer
origindirection
changed-p
not
same-inspected-site-pinspection
viewer-inspectionviewer

Retain the exact current distance and point even while the semantic site remains the same; repaint McCLIM only at semantic boundaries.

setf
viewer-inspectionviewer
inspection
inspection
defunviewer-inspection-parameters
viewerextent
let
multiple-value-bind
pointer-xpointer-y
vector
coerce
/pointer-x
firstlogical-extent
'single-float
coerce
/pointer-y
secondlogical-extent
'single-float
coerce
/
firstextent
'single-float
coerce
/
secondextent
'single-float
defunprepare-viewer-frame-renderer

Publish frame-boundary instruments, then borrow viewer's renderer.

An instrument operation may transactionally replace the complete renderer cohort and retire the previous one. No frame may borrow that previous cohort before the operation boundary, or it would encode through resources which the same canvas thread has just retired.

Inspector repaint is semantic and therefore sparse. Shader definitions are independently live, so the static retained stream still refreshes its direct compositor here, before encode-renderer-frame opens a pass.

when
and
viewer-inspector-pviewer
viewer-inspector-mirrorviewer
or
viewer-rendererviewer
error"LUFT viewer has no renderer after frame-boundary publication."
defunencode-viewer-frame
viewerencodersurface-textureextent&key
inspector-p
viewer-inspector-pviewer
let*
surface-view
render-extent
width
firstrender-extent
height
secondrender-extent
jitter
if
renderer-temporal-prenderer
0.00.0
previous
or
renderer-previous-viewrenderer
view
player
viewer-playerviewer
player-p
andplayer
typep
viewer-sourceviewer
'scene
scene-player-p
viewer-sourceviewer

Instrument state is now immutable for this frame. Encoding below only replays prepared GPU commands against the renderer borrowed afterward.

encode-renderer-framerendererencodersurface-viewextent
camera-uniform-dataviewprevious
if1.00.0
andplayer-pplayer
viewer-bevel-widthviewer
renderer-exposurerenderer
:jitterjitter:viewview:player-pplayer-p

Dense lattice dots are a close-study diagnostic, not a terrain view: streamed chunks can each contain more than a million markers. The shader-backed construction lines remain controlled by *WIREFRAME*.

:construction-p:overlay-encoder
and
lambda
pass
wheninspector-p
mcluv:encode-direct-gpu-mirror
viewer-inspector-compositorviewer
passsurface-texture

Instruments are ordered low-to-high so modal tools render last.

encode-viewer-instrumentsviewerpasssurface-textureextent
clim:define-command-tableluft-window
clim:define-command-tableluft-window-release
clim:define-command-tableluft-atelier
clim:define-command-tableluft-atelier-release
defclasssite-inspector-pane
clim:application-pane
clim:define-presentation-typeluft-site
defunsite-extent-label
with-output-to-string
stream
dolist
axis'
:x:y:z
when
write-char
char
symbol-nameaxis
0
stream
defuninspector-row
streamylabelvalue&keypresentation
clim:draw-text*streamlabel18y:align-y:center:text-size12:ink
clim:make-rgb-color0.480.520.55
flet
draw
clim:draw-text*streamvalue148y:align-y:center:text-size12:ink
clim:make-rgb-color0.860.880.84
ifpresentation
clim:with-output-as-presentation
streampresentation'luft-site
defunbevel-width-label
bevel-width
let
divisor
gcdbevel-widthluft:+mesh-cell-size+
formatnil"~D/~D"
/bevel-widthdivisor
defunviewer-bevel-label
if
viewer-bevel-profileviewer
"mixed"
bevel-width-label
viewer-bevel-widthviewer
defunnext-bevel-width
bevel-width
casebevel-width
12
24
otherwise1
defundisplay-site-inspector
viewerstream

Draw viewer's current sparse ray hit as McCLIM presentations.

clim:draw-rectangle*stream00+site-inspector-width++site-inspector-height+:ink
clim:make-rgb-color0.0250.0700.090
clim:draw-text*stream
formatnil"LUFT · BEVEL ~A · C LINES ~A"
if"ON""OFF"
1825:align-y:center:text-size14:text-face:bold:ink
clim:make-rgb-color0.420.910.94
let
inspection
viewer-inspectionviewer
if
nullinspection
progn
clim:draw-text*stream"point at the boundary"1860:align-y:center:text-size13:ink
clim:make-rgb-color0.580.620.62
clim:draw-text*stream"escape releases input; click locks it"1884:align-y:center:text-size11:ink
clim:make-rgb-color0.400.440.45
let*
site
site-inspection-siteinspection
cell
site-inspection-cellinspection
point
site-inspection-pointinspection
star-mask
site-inspection-star-maskinspection
inspector-rowstream57"site":presentationsite
inspector-rowstream80"extent"
inspector-rowstream103"orientation"
inspector-rowstream126"solid cell":presentationcell
inspector-rowstream149"distance"
formatnil"~,3F cells"
site-inspection-distanceinspection
inspector-rowstream172"world hit"
formatnil"~,2F ~,2F ~,2F"
vec3:vec3-xpoint
vec3:vec3-ypoint
vec3:vec3-zpoint
inspector-rowstream195"stock"
formatnil"~D"
site-inspection-stockinspection
inspector-rowstream218"cell-corner star"
formatnil"#x~2,'0X"star-mask
inspector-rowstream241"star topology"
if"singular · split sheets""regular"
inspector-rowstream264"spike junction"
handler-case
formatnil"~D regular sheet~:P"
error
"covered junction · next spike"
defunrefresh-viewer-inspector
let
pane
and
viewer-inspector-pviewer
ignore-errors
clim:find-pane-namedviewer'inspector
whenpane
let
mirror
clim:sheet-direct-mirror
clim:frame-top-level-sheetviewer
setf
viewer-inspector-mirrorviewer
mirror
mcluv:call-with-gpu-mirror-sheet-repaintmirrorpane
lambda
clim:redisplay-frame-paneviewerpane:force-pt

Application panes may follow their output cursor even without visible scroll bars; this fixed HUD stays pinned at its origin.

clim:scroll-extentpane00
viewer
defunviewer-inspector-state
viewerextent
declare
ignoreextent
when
viewer-inspector-mirrorviewer
let*
width
firstlogical-extent
height
secondlogical-extent
margin14.0
center-x
-1.0
/
*2.0margin
width
half-width
center-y
-1.0
/
*2.0margin
height
half-height
make-array12:element-type'single-float:initial-contents
mapcar
lambda
value
coercevalue'single-float
listcenter-xcenter-y0.01.0half-width0.00.00.00.0half-height0.00.0
clim:define-application-frameviewer
clim:standard-application-framecanvas-event-handler
canvas:initarg:canvas:initformnil:readerviewer-canvas
context:initarg:context:initformnil:readerviewer-context
device:initarg:device:initformnil:readerviewer-device
source:initarg:source:initform:accessorviewer-source
renderer:initarg:renderer:initformnil:accessorviewer-renderer
production-system:initarg:production-system:initformnil:accessorviewer-production-system
bevel-width:initarg:bevel-width:initform2:accessorviewer-bevel-width
bevel-profile:initarg:bevel-profile:initformnil:accessorviewer-bevel-profile
camera:initarg:camera:initform:readerviewer-camera
player:initarg:player:initform:accessorviewer-player
mode:initarg:mode:initform
make-instance'isometric-walk-mode
:accessorviewer-mode
surface-views:initform
make-hash-table:test#'eql
:readerviewer-surface-views
controls:initform
make-hash-table:test#'eq
:readerviewer-controls
pointer-captured-p:initformnil:accessorviewer-pointer-captured-p
pointer-x:initformnil:accessorviewer-pointer-x
pointer-y:initformnil:accessorviewer-pointer-y
inspection:initformnil:accessorviewer-inspection
inspector-p:initarg:inspector-p:initformnil:accessorviewer-inspector-p
inspector-mirror:initformnil:accessorviewer-inspector-mirror
inspector-compositor:initformnil:accessorviewer-inspector-compositor
last-timestamp:initformnil:accessorviewer-last-timestamp
speed:initarg:speed:initform4.0:accessorviewer-speed
sensitivity:initarg:sensitivity:initform0.0032:accessorviewer-sensitivity
shader-diagnostic:initformnil:accessorviewer-shader-diagnostic
running-p:initformt:accessorviewer-running-p
stop-controller:initarg:stop-controller:initform
make-stop-controller:name"LUFT viewer"
:readerviewer-stop-controller

The frame is the application and the inspector is its first pane. Its command table inherits every input phase so McCLIM considers each command executable; event dispatch still chooses one phase explicitly.

:command-table
:panes
inspector:application:display-function'display-site-inspector:scroll-barsnil:default-text-style
clim:make-text-style:sans-serifnil:normal
:layouts
default
clim:horizontallyinspector
:menu-barnil
defunviewer-surface-view
viewersurface

Return viewer's texture view for the current presentation slot.

let*
context
viewer-contextviewer
views
viewer-surface-viewsviewer
view
gethashkeyviews
when
andview
not
eqsurface
gpu-texture-view-textureview

Metal returns a fresh borrowed wrapper when it revisits a drawable. Keep the stable slot but never let its view retain the old wrapper.

setfviewnil
orview
setf
gethashkeyviews
create
viewer-deviceviewer
make-texture-view-descriptor:texturesurface
defunrelease-viewer-surface-views
with-release-report
maphash
lambda
keyview
declare
ignorekey
releasing:surface-view
viewer-surface-viewsviewer
clrhash
viewer-surface-viewsviewer
values
defunrelease-viewer-surface-view
viewersurface

Release only viewer's cached view of surface, when one exists.

let*
context
viewer-contextviewer
views
viewer-surface-viewsviewer
multiple-value-bind
viewpresent-p
gethashkeyviews
whenpresent-p
whenview
remhashkeyviews
values
defunviewer-control-active-p
viewerdirection
gethashdirection
viewer-controlsviewer
defunset-viewer-control
viewerdirectionactive-p
ifactive-p
progn
when
viewer-playerviewer
setf
gethashdirection
viewer-controlsviewer
t
remhashdirection
viewer-controlsviewer
viewer
defunclear-viewer-controls
clrhash
viewer-controlsviewer
viewer
defunadvance-viewer-camera
viewertimestamp
let*
last
viewer-last-timestampviewer
dt
iflast
min0.1
max0.0
-timestamplast
0.0
camera
viewer-cameraviewer
step
*dt
viewer-speedviewer
setf
viewer-last-timestampviewer
timestamp
if
viewer-playerviewer
let
when
and
zeropforward
zeropright
multiple-value-setq
forwardrightmaximum-distance
advance-walking-player
viewer-playerviewer
viewer-sourceviewer
camera
orforward0.0
orright0.0
dt:maximum-distancemaximum-distance

The first timestamp establishes the follow pose immediately. Subsequent zero-duration samples preserve it; in Common Lisp a numeric zero is true, so DT alone cannot express that distinction.

follow-walking-playercamera
viewer-playerviewer
:seconds
andlastdt
multiple-value-bind
rightupforward
flet
move
directionamount
let
position
camera-positioncamera
setf
camera-positioncamera
when
moveforwardstep
when
moveforward
defunadvance-viewer-streaming

Drain completed meshes and admit the next mock residency change.

let
source
viewer-sourceviewer
production-system
viewer-production-systemviewer
when
and
typepsource'streaming-scene
production-system
drain-streaming-scene-productionsource
viewer-rendererviewer
production-system

One complete neighborhood cohort crosses the owner boundary before another residency change can invalidate it.

when
and
null
streaming-scene-cohortsource
null
streaming-scene-removalssource
incf
streaming-scene-frame-countersource
when
>=
streaming-scene-frame-countersource
streaming-scene-frames-per-loadsource
setf
streaming-scene-frame-countersource
0
let
position
camera-position
viewer-cameraviewer
retarget-streaming-scenesourceproduction-system
viewer-bevel-widthviewer
vec3:vec3-xposition
vec3:vec3-yposition
defunrender-viewer-frame
viewertimestamp
declare
ignoretimestamp
when
viewer-running-pviewer

Source callbacks only advance revisions. Compilation and complete cohort publication happen here, before this frame borrows the renderer.

present-canvas-frame
viewer-contextviewer
lambda
surface-textureencoderpresentation-time
let
extent
canvas-extent
viewer-contextviewer
encode-viewer-frameviewerencodersurface-textureextent
defunviewer-command-viewer

Return the luft application receiving the current McCLIM command.

clim:*application-frame*
defunrequest-viewer-quit

Begin an orderly stop once and return true when this call began it.

Native input closes capture admission without waiting. The worker below drains the capture beside this thread before crossing its frame barrier.

setf
viewer-running-pviewer
nil

A native close and a command both run on the canvas thread. Reserve the one teardown owner here, but run it beside that thread so its synchronous frame-boundary barrier can complete before close-canvas ends the loop.

nth-value0
request-controlled-stop
viewer-stop-controllerviewer
:thread-name"LUFT viewer quit"
clim:define-command
com-start-moving:command-tableluft-atelier:name"Start Moving"
direction'keyword:prompt"direction"
clim:define-command
com-stop-moving:command-tableluft-atelier-release:name"Stop Moving"
direction'keyword:prompt"direction"
clim:define-command
com-reset-view:command-tableluft-atelier:name"Reset View":keystroke
:r
clim:define-command
com-jump:command-tableluft-atelier:name"Jump":keystroke
:space
let
player
clim:define-command
com-toggle-construction-lines:command-tableluft-atelier:name"Toggle Construction Lines":keystroke
:c
let
setf*wireframe*
if0.01.0
when
viewer-rendererviewer
setf
renderer-history-valid-p
viewer-rendererviewer
nil
clim:define-command
com-toggle-bevel-width:command-tableluft-atelier:name"Toggle Bevel Width":keystroke
:b
let*
profile
viewer-bevel-profileviewer
bevel-width
viewer-bevel-widthviewer
cond
profile
refresh-viewer-rendererviewer:solid
viewer-sourceviewer
:bevel-width1:bevel-profilenil
=bevel-width4
refresh-viewer-rendererviewer:solid
viewer-sourceviewer
:bevel-width4:bevel-profile
t
refresh-viewer-rendererviewer:solid
viewer-sourceviewer
:bevel-width
next-bevel-widthbevel-width
:bevel-profilenil
clim:define-command
com-release-pointer:command-tableluft-window:name"Release Pointer":keystroke
:escape
let*
when
viewer-pointer-captured-pviewer
setf
viewer-pointer-captured-pviewer
nil
clim:define-command
com-toggle-fullscreen:command-tableluft-window:name"Toggle Fullscreen":keystroke
:f11
defunset-viewer-mode
viewermode

Install mode and make its pointer ownership immediately true on screen.

check-typemodeviewer-mode
when
viewer-pointer-captured-pviewer
setf
viewer-pointer-captured-pviewer
nil
viewer
clim:define-command
com-toggle-viewer-mode:command-tableluft-window:name"Toggle Interaction Mode":keystroke
:m
clim:define-command
com-quit:command-tableluft-window:name"Quit":keystroke
#\q:control
defparameter+viewer-movement-keys+'
:w:forward
:up:forward
:s:backward
:down:backward
:a:left
:left:left
:d:right
:right:right
"Physical keys whose press and release urge the active controller."
defuninstall-viewer-movement-commands

Install the atelier's held controls into their press and release tables.

dolist
destructuring-bind
keydirection
binding
let
gesture
listkey:any

Definition reloads replace the live vocabulary instead of stacking another item at the same gesture coordinate.

dolist
clim:remove-keystroke-from-command-tabletablegesture:errorpnil
flet
command-item
command
let
directiondirection
lambda
gesturenumeric-argument
declare
ignoregesturenumeric-argument
listcommanddirection
clim:add-keystroke-to-command-table'luft-ateliergesture:function
command-item'com-start-moving
:errorpnil
clim:add-keystroke-to-command-table'luft-atelier-releasegesture:function
command-item'com-stop-moving
:errorpnil
values
defgenericviewer-key-event-tables
event
:documentation

Return the window and atelier tables for key event.

defunviewer-key-command
viewerevent

Return the named McCLIM command viewer binds to key event, or NIL.

multiple-value-bind
windowatelier

Window commands are global application controls: fullscreen, Tracy, quit, and pointer release remain available after Escape. Unmodified atelier/gameplay commands still require captured input; modified tools such as M-x deliberately remain global as well.

or
mcluv:canvas-key-event-commandviewerevent:command-tablewindow
when
or
viewer-pointer-captured-pviewer
intersection'
:control:meta:super
canvas-key-event-modifiersevent
mcluv:canvas-key-event-commandviewerevent:command-tableatelier
defmethodhandle-canvas-event
declare
ignorecanvas
unless
canvas-key-event-repeat-pevent
let
whencommand
clim:execute-frame-commandviewercommand
nil
defmethodhandle-canvas-event
declare
ignorecanvas
let
whencommand
clim:execute-frame-commandviewercommand
nil
defmethodhandle-canvas-event
setf
viewer-pointer-xviewer
canvas-pointer-event-xevent
viewer-pointer-yviewer
canvas-pointer-event-yevent
nil
defmethodhandle-viewer-mode-pointer-press
declare
ignoremodecanvas
let
player
viewer-playerviewer
whenplayer
case
canvas-pointer-event-buttonevent
:left
let
wheninspection
let
site
site-inspection-siteinspection
cell
site-inspection-cellinspection

Only an upward horizontal surface promises a standable destination. Walls remain useful inspection targets but do not turn into surprising roof teleports.

:right
multiple-value-bind
origindirection
throw-walking-player-ballplayerorigindirection
defmethodhandle-viewer-mode-pointer-press
declare
ignoremodeevent
unless
viewer-pointer-captured-pviewer
setf
viewer-pointer-captured-pviewer
t
when
viewer-playerviewer
multiple-value-bind
origindirection
throw-walking-player-ball
viewer-playerviewer
origindirection
defmethodhandle-canvas-event
declare
ignorecanvas
let
factor
expt1.10
-
canvas-pointer-event-scroll-yevent
if
eq*projection*:isometric
let
camera
viewer-cameraviewer
setf
max0.43633232
min1.7453293
nil
defmethodhandle-canvas-event
declare
ignorecanvas
setf
viewer-pointer-xviewer
canvas-pointer-event-xevent
viewer-pointer-yviewer
canvas-pointer-event-yevent
when
and
viewer-pointer-captured-pviewer
let
camera
viewer-cameraviewer
sensitivity
viewer-sensitivityviewer
decf
*
canvas-pointer-event-delta-xevent
sensitivity
setf
max-1.5
min1.5
-
*
canvas-pointer-event-delta-yevent
sensitivity
nil
defmethodhandle-canvas-event
declare
ignoreevent
when
viewer-pointer-captured-pviewer
setf
viewer-pointer-captured-pviewer
nil
nil
defmethodhandle-canvas-event
declare
ignoreviewercanvasevent
nil
defunstart-viewer
&key
bevel-width2
bevel-profilesurface-mesh
title"LUFT — click to walk · scroll to zoom · M orbit"
width1100
fullscreen-p
inspector-pnil
frames-per-second60

Open the indexed-instanced luft renderer as a McCLIM atelier.

bevel-profile enables the experimental material-selected width cohorts for a non-streaming scene. bevel-width remains the camera/inspection reference and the uniform fallback. surface-mesh supplies an already constructed diagnostic mesh while retaining solid as the semantic inspection source.

when
andbevel-profile
typepsolid'streaming-scene
error"Material bevel profiles do not yet support streaming scenes."
whensurface-mesh
check-typesurface-meshluft:surface-mesh
whenbevel-profile
error"Specify either SURFACE-MESH or BEVEL-PROFILE, not both."
when
typepsolid'streaming-scene
error"An explicit surface mesh requires a non-streaming scene."
let
canvas
make-sdl-canvas:titletitle:widthwidth:heightheight:visible-pnil:fullscreen-pfullscreen-p:high-pixel-density-pt:presentation-api
devicenil
viewer-statenil
renderer-source-valuesnil
renderer-source-revision0
production-systemnil
completed-pnil
unwind-protect-releasing
let*
device*
setfdevice
request-gpu-deviceprovider
make-device-descriptor:labeltitle
context
make-canvas-contextcanvasprovider

:copy-src drops the Metal framebuffer-only contract so capture-viewer-frame can read the drawable back.

make-canvas-configuration:devicedevice*:usage'
:render-attachment:copy-src
renderer*
multiple-value-bind
createdsource-valuesbeforeafter

If source moved during construction, BEFORE deliberately remains the installed attempt: the first frame sees the newer AFTER revision and transactionally rebuilds.

declare
ignoreafter
setfrenderercreatedrenderer-source-valuessource-valuesrenderer-source-revisionbefore
created
port
clim:find-port:server-path'
:luv-gpu
manager
or
first
clim-internals::frame-managersport
make-instance'mcluv:luv-frame-manager:portport
viewer
setfviewer-state
let
when
typepsolid'streaming-scene
setfproduction-system
clim:make-application-frame'viewer:frame-managermanager:enablet:canvascanvas:contextcontext:devicedevice*:rendererrenderer*:stop-controller:production-systemproduction-system:cameracamera:sourcesolid:player
and
typepsolid'scene
scene-player-psolid
:bevel-widthbevel-width:bevel-profilebevel-profile:inspector-pinspector-p
unless
typepsolid'streaming-scene
cond
surface-mesh
renderer-set-meshrenderer*0surface-mesh
bevel-profile
t
renderer-set-meshrenderer*0
make-render-meshsolid:bevel-widthbevel-width
attach-viewer-live-artifactviewerrenderer-source-valuesrenderer-source-revision
wheninspector-p
let*
mirror
viewer-inspector-mirrorviewer
compositor
make-instance'mcluv:direct-gpu-mirror-compositor:mirrormirror
setf
mcluv:mirror-compositormirror
compositor
viewer-inspector-compositorviewer
compositor

Realization painted before the compositor existed; publish only the inspector pane for direct final-pass replay.

The radio is application infrastructure and remains active while its old detailed panel is hidden. The compact shared status line is the default visible representation.

setf
make-cadence-clock
lambda
native-canvastimestamp
declare
ignorenative-canvas
:frames-per-secondframes-per-second
setf*viewer*viewercompleted-pt
viewer
unlesscompleted-p
whenviewer-state
releasing:instruments
whenproduction-system
releasing:production-system
if
andviewer-state
releasing:renderer-artifact
whendevice
releasing:device
destroydevice
defmethodluv:prepare-capture
when
eq:film
luv:capture-kindcapture
setf
luv:capture-client-statecapture
list:running-p
viewer-running-pviewer
viewer-running-pviewer
nil
viewer
defmethodluv:encode-capture-frame
encodertargetextent
encode-viewer-frameviewerencodertargetextent:inspector-p
luv:capture-optioncapture:inspector-p
and
eq:screenshot
luv:capture-kindcapture
viewer-inspector-pviewer
defmethodluv:cleanup-capture
let
target
luv:capture-targetcapture
canvas
viewer-canvasviewer
saved-state
luv:capture-client-statecapture
unwind-protect
when
request-canvas-framecanvas
lambda
timestamp
declare
ignoretimestamp

The shared target is still alive. Evict only its cached view; normal drawable views remain warm.

when
and
eq:film
luv:capture-kindcapture
saved-state

Test and publication share the capture gate lock. If cleanup wins, a following stop request writes NIL afterward; if shutdown wins, cleanup cannot resurrect a terminal viewer.

call-if-application-captures-openviewer
lambda
setf
viewer-running-pviewer
getfsaved-state:running-p
values
defuncapture-viewer-frame
pathname&optional&key
inspector-p
viewer-inspector-pviewer

Render one native-resolution viewer frame offscreen into pathname.

inspector-p defaults to viewer's inspector setting. A source-defined evidence capture may override it when the subject is the geometry rather than the atelier UI.

luv:capture-application-screenshotviewer
merge-pathnamespathname
:label"LUFT screenshot":options
list:inspector-pinspector-p
defunfilm-viewer
viewerpathname&key
seconds8
frame-rate30
before-frame

Film viewer offscreen into an MP4 at pathname.

before-frame, when supplied, receives the frame index before streaming and rendering that frame. Recording is paced in real time so asynchronous chunk production gets the same opportunity to publish as it does in the window.

luv:capture-application-filmviewerpathname:secondsseconds:frame-rateframe-rate:before-framebefore-frame:progress-function
lambda
frameframe-count
when
zerop
modframeframe-rate
formatt"LUFT film: frame ~D / ~D~%"frameframe-count
force-output
:label"LUFT film":options'
:inspector-pnil
defunrefresh-viewer-renderer
&optional&key
bevel-width
andviewer
viewer-bevel-widthviewer
bevel-profile
andviewer
viewer-bevel-profileviewer

Rebuild viewer at bevel-width or its material bevel-profile.

whenviewer
when
andbevel-profile
typepsolid'streaming-scene
error"Material bevel profiles do not yet support streaming scenes."
luv::call-on-sdl-canvas-thread
viewer-canvasviewer
lambda
let*
bevel-width
orbevel-width
viewer-bevel-widthviewer
context
viewer-contextviewer
old
viewer-rendererviewer
old-production-system
viewer-production-systemviewer
candidate-renderernil
candidate-source-valuesnil
candidate-source-revision0
production-systemnil
was-running-p
viewer-running-pviewer
setf
viewer-running-pviewer
nil
unwind-protect
handler-case
let
renderer
multiple-value-bind
createdsource-valuesbeforeafter
unless
=beforeafter
whencreated
error'renderer-source-changed-during-build:beforebefore:afterafter
setfcandidate-renderercreatedcandidate-source-valuessource-valuescandidate-source-revisionbefore
created
cond
typepsolid'streaming-scene
setfproduction-system
t
renderer-set-meshrenderer0
make-render-meshsolid:bevel-widthbevel-width
setf
viewer-rendererviewer
renderer
viewer-production-systemviewer
production-system
viewer-sourceviewer
solid
viewer-playerviewer
and
typepsolid'scene
scene-player-psolid
viewer-bevel-widthviewer
bevel-width
viewer-bevel-profileviewer
bevel-profile
viewer-inspectionviewer
nil
candidate-renderernil
note-viewer-renderer-replacementviewercandidate-source-valuescandidate-source-revision
error
condition
whencandidate-renderer
destroy-renderercandidate-renderer
whenproduction-system
errorcondition
setf
viewer-running-pviewer
was-running-p
whenold-production-system
values
defunrefresh-viewer-shaders

Force a complete renderer-cohort rebuild on viewer's canvas thread.

values
defun%perform-viewer-stop

Release viewer after its stop controller granted this caller ownership.

A native request only closed admission. This sole off-canvas owner waits for the active capture to encode and evict its surface view before any renderer, canvas, or device release can begin.

setf
viewer-running-pviewer
nil
let
canvas
viewer-canvasviewer
unwind-protect-releasing
with-release-report
when
member'
:opening:open
when
viewer-pointer-captured-pviewer
releasing:pointer-capture
setf
viewer-pointer-captured-pviewer
nil
releasing:canvas-quiescence

A synchronous no-op after changing the clock is the frame boundary: no encoder still borrows application resources.

request-canvas-framecanvas
lambda
timestamp
declare
ignoretimestamp
when
viewer-production-systemviewer
releasing:production-system
setf
viewer-production-systemviewer
nil

The live artifact is the renderer cohort's sole owner. It detaches the renderer before native retirement and makes release terminal even when the backend reports a retirement failure.

unless
eq:disowned
clim:frame-stateviewer
releasing:inspector-frame
clim:destroy-frameviewer

Native window and GPU device are deliberately the final handles.

when
member'
:opening:open
when
viewer-deviceviewer
releasing:device
destroy
viewer-deviceviewer
releasing:viewer-publication
values
defunstop-viewer

Stop viewer exactly once and publish one result to every caller.

Native canvas-thread handlers use request-viewer-quit instead: a synchronous owner or waiter is rejected there because teardown crosses a frame boundary.

values
defunrun-standalone-viewer
let
unwind-protect
loopwhile
viewer-running-pviewer
do
sleep0.05