luv

Workshop wiki

gazebo.lisp

luvcraft/birthday/gazebo.lisp

system luvcraft/birthday · 9 definitions · on GitHub

The gazebo: a big beautiful pavilion at the heart of the party.

Contract: (build-birthday-gazebo world &key center-x center-z ground-y radius) builds the gazebo out of block edits (luvcraft:edit-block-at inside one luvcraft::with-world-change-transaction; the caller relights) and returns a plist describing the interesting anchor points for the other decorations: (:floor-y N ; the y of the floor slab blocks :roof-apex (x y z) ; above the finial, where a balloon ; bundle ties :post-tops ((x y z) ...) ; topmost wood block of each post, ; for balloon strings :cake (x y z)) ; center block of the cake's base tier

Construction is octagons all the way up. One membership predicate -- a square chamfered at |dx|+|dz| <= round(R*sqrt2) -- draws the floor slab, the railing ring, and every roof tier, so the plan stays concentric by construction. The eight posts stand where the octagon's vertices land: computed from the circumradius R/cos(pi/8) at the eight half-step angles and rounded onto the grid, rather than placed by hand, so a different RADIUS keeps its corners true.

Vertically: a one-block plinth lifts the floor above the meadow (stone brick rim, planks field, cobblestone pads under the posts); five-block wood posts carry a stack of slate octagons that starts one cell wider than the floor -- a real eave -- and shrinks by one and two alternately to a cap, crowned with a glowing crystal finial. Crystal garland lights hang under the eave at the rim-edge midpoints, so the covered interior glows warm instead of going dark under the solid roof. Two flat sides (toward -z and +z) are left entirely open between their posts, with a sandstone path through the rim and a sandstone step out onto the grass, so a four-year-old can run straight in, past the cake, and out the other side.

Blocks are the existing palette only: stone bricks and cobblestone for the base, planks for floor and railings, wood for posts, slate for the tiered roof, crystal for the finial and garland lights, snow for the cake's frosting.

in-package#:luvcraft.birthday

The octagon vocabulary

defunoctagon-diagonal-limit
radius

The |dx|+|dz| bound that chamfers a square of half-width radius into a near-regular octagon.

round
*radius
sqrt2d0
defunoctagon-rim-p
dxdzradius

True on the one-cell boundary ring of the filled octagon.

defunmap-octagon
functionradius

Call function with (dx dz rim-p) over every cell of the filled octagon.

loopfordxfrom
-radius
toradiusdo
loopfordzfrom
-radius
toradiuswhendo
funcallfunctiondxdz
octagon-rim-pdxdzradius
defunoctagon-vertex-offsets
radius

The eight vertex cells of the octagon, as (dx dz) pairs.

The vertices of the ideal octagon sit on the circumradius R/cos(pi/8) at the eight half-step angles; rounding those puts each post exactly on the rim ring where the flat sides meet the chamfers.

let
circumradius
/radius
cos
/pi8
loopforkbelow8forangle=
*
1+
*2k
/pi8
collect
list
round
*circumradius
round
*circumradius
defunoctagon-edge-midpoint-offsets
radius

One cell near the middle of each of the eight rim edges.

The axis-aligned midpoints land on the rim directly; the diagonal ones round to just outside the chamfer, so walk inward until the octagon holds the cell -- one step, onto the row under the eave.

loopforkbelow8forangle=
*k
/pi4
collect
let
dx
round
*radius
dz
round
*radius
loopuntildo
decfdx
decfdz
listdxdz
defungazebo-roof-radii
radius

Tier radii from eave to cap: one wider than the floor, then shrinking by one and two alternately so the silhouette curves instead of coning.

let
radiinil
r
1+radius
drop1
loopwhile
pluspr
do
pushrradii
decfrdrop
setfdrop
if
=drop1
21
nreverseradii

Reading the ground

defunprobe-gazebo-ground
worldcenter-xcenter-zradius

The highest solid surface y among five columns around the center.

Taking the maximum keeps the plinth proud of a gently uneven clearing instead of half-buried in it. Absent chunks read as air, so a wholly unmaterialized site errors here, by name, rather than as a mysterious chunk-not-resident mid-build.

let*
shape
luvcraft::voxel-space-chunk-shape
luvcraft::block-world-spaceworld
top
1-
*2
luvcraft::chunk-shape-heightshape
half
ceilingradius2
groundnil
dolist
offset
list
list00
listhalf0
list
-half
0
list0half
list0
-half
loopwithx=
+center-x
firstoffset
withz=
+center-z
secondoffset
foryfromtopdownto0whendo
setfground
ifground
maxgroundy
y
orground
error"No solid ground near (~D, ~D); is the terrain resident?"center-xcenter-z

The build

defunbuild-birthday-gazebo
world&key
center-x0
center-z0
ground-y
radius7

Build the party gazebo centered on center-x, center-z and return its anchor plist (see the file header). ground-y is the y of the surface block to stand on; when NIL the terrain is probed for it. All edits go through the world's source overlay in one change transaction; the caller relights.

let*
ground
orground-y
probe-gazebo-groundworldcenter-xcenter-zradius
floor-y
1+ground
post-height5
roof-base-y
+floor-ypost-height1
roof-radii

The flat side at |dz| = radius spans the cells between two posts; everything strictly between them is entrance.

opening-half
flet
place
luvcraft:edit-block-atblock-kindworld
+center-xdx
y
+center-zdz
air-p
dxydz
multiple-value-bind
blockstate
luvcraft:world-block-atworld
+center-xdx
y
+center-zdz
and
nullblock
eqstate:resident
occupied-p
dxydz
and
luvcraft:world-block-atworld
+center-xdx
y
+center-zdz
t
post-p
dxdz
member
listdxdz
posts:test#'equal
path-p
dxdz
and
<=
-radius2
absdz
radius
entrance-p
dxdz
and
=
absdz
radius
<=
absdx
opening-half
luvcraft::with-world-change-transaction
world

Clear the interior air band first, so a stray shrub or terrain bump inside the footprint does not end up furnishing the party.

map-octagon
lambda
dxdzrim-p
declare
ignorerim-p
loopforyfrom
1+floor-y
belowroof-base-ywhen
occupied-pdxydz
do
placenildxydz
radius

The plinth: stone brick rim, planks field, sandstone where the path runs through, cobblestone pads under the posts, and a cobblestone underpinning wherever the rim overhangs a dip.

map-octagon
lambda
dxdzrim-p
place
cond
post-pdxdz
luvcraft::*cobblestone-block*
path-pdxdz
luvcraft::*sandstone-block*
rim-pluvcraft::*stone-bricks-block*
tluvcraft::*planks-block*
dxfloor-ydz
whenrim-p
loopforyfrom
1-floor-y
downto
-floor-y4
while
air-pdxydz
do
placeluvcraft::*cobblestone-block*dxydz
radius

Sandstone steps: a flush landing two cells deep outside each entrance, underpinned where the meadow falls away.

dolist
side'
-11
loopfordzfrom
1+radius
to
+radius2
do
loopfordxfrom-1to1do
placeluvcraft::*sandstone-block*dxfloor-y
loopforyfrom
1-floor-y
downto
-floor-y3
while
air-pdxy
do
placeluvcraft::*sandstone-block*dxy

Posts and railings. Railings run one block high along the rim between posts; the two entrance sides stay completely open.

dolist
postposts
loopforyfrom
1+floor-y
to
+floor-ypost-height
do
placeluvcraft::*wood-block*
firstpost
y
secondpost
map-octagon
lambda
dxdzrim-p
when
andrim-p
not
post-pdxdz
not
entrance-pdxdz
placeluvcraft::*planks-block*dx
1+floor-y
dz
radius

The roof: filled slate octagons from the eave up, so there is no hole to rain through, then the crystal finial on the cap.

loopfortier-radiusinroof-radiiforyfromroof-base-ydo
map-octagon
lambda
dxdzrim-p
declare
ignorerim-p
placeluvcraft::*slate-block*dxydz
tier-radius
placeluvcraft:*crystal-block*0
+roof-base-y
lengthroof-radii
0

Garland lights tucked under the eave: a crystal hanging from the overhang at each rim-edge midpoint, clear of the posts.

dolist
offset
unless
post-p
firstoffset
secondoffset
placeluvcraft:*crystal-block*
firstoffset
1-roof-base-y
secondoffset

The cake on its table: a low 3x3 planks table, a full 3x3 snow base tier (a clean white box reads as frosting; a cut-corner tier read as a grey lump), a single snow top tier, one crystal candle whose glow lights the party.

loopfordxfrom-1to1do
loopfordzfrom-1to1do
placeluvcraft::*planks-block*dx
1+floor-y
dz
placeluvcraft::*snow-block*dx
+floor-y2
dz
placeluvcraft::*snow-block*0
+floor-y3
0
placeluvcraft:*crystal-block*0
+floor-y4
0
list:floor-yfloor-y:roof-apex
listcenter-x
+roof-base-y
lengthroof-radii
1
center-z
:post-tops
loopfor
dxdz
inpostscollect
list
+center-xdx
+floor-ypost-height
+center-zdz
:cake
listcenter-x
+floor-y2
center-z