@scene
A 3D model built from shapes, raymarched on the GPU with WebGL into an image. It is written in the same language as @shape, with shapes in three dimensions.
Syntax
- @scene(<body>)
- @scene<width>x<height>(<body>)
The body is a list of shapes, each written name { … }, and rows such as view: 35 25.
Digits after the name set the image size in pixels (@scene800 is square).
Without them the image takes the size of the element.
The camera fits the model to the image, so only its proportions matter. The model is lit from above and has no background of its own.
@grid: 1 / 60vmin;
background: @scene(
box { size: 1; round: .1; fill: #f06 }
view: 35 25;
); Shapes
Each shape takes its own sizes, listed in the table below.
Shapes at the top of the body are drawn together, and fill gives each one a color:
@grid: 1 / 60vmin;
background: @scene(
cylinder { r: .3; height: .8; fill: #fc0 }
cone { r: .4 0; height: .6; move: 0 .7 0; fill: #f06 }
view: 0 20;
); Moving shapes
move: x y z shifts a shape, with y pointing up.
rotate turns it counterclockwise, in degrees, around z, or around the axis written before the angle:
rotate: x 90, or rotate: 1 1 0 45 for any direction.
scale takes one factor, or one per axis.
They run in the order written, as in @shape.
@ functions in the body run for each cell, so every cell can draw its own model:
@grid: 4 / 60vmin / #111;
background: @scene(
box {
size: .6;
round: .06;
rotate: 1 1 1 @r(360);
fill: @p(#f06, #fc0, #0cf, #fff);
}
); Combining shapes
union, subtract and intersect join the shapes inside them.
subtract takes the rest away from the first one.
A rounded cube with three holes through it:
@grid: 1 / 60vmin;
background: @scene(
subtract {
intersect {
box { size: 1 }
sphere { r: .68 }
}
cylinder { r: .3; height: 2 }
cylinder { r: .3; height: 2; rotate: x 90 }
cylinder { r: .3; height: 2; rotate: z 90 }
}
view: 35 25;
); smooth rounds the joins with a curve of that radius, and blends the colors where the shapes meet.
The shapes in a group take its fill unless they have their own:
@grid: 1 / 60vmin;
background: @scene(
union {
smooth: .15;
fill: #fc0;
sphere { r: .5 }
sphere { r: .35; move: .55 .3 0; fill: #f06 }
}
); Names and copies
name#id keeps a shape for later instead of drawing it, and #id draws it, with any rows given there added.
name*N repeats a shape, and name*XxY repeats it in a grid.
Each copy sees n from 1, the count N, and its column and row as nx and ny:
@grid: 1 / 60vmin;
background: @scene(
box#leg { size: .06 .5 .06 }
box { size: 1 .06 .7; move: 0 .28 0; round: .02 }
#leg*2x2 { move: nx * .84 - 1.26, 0, ny * .54 - .81 }
view: 35 25;
); @grid: 1 / 60vmin;
background: @scene(
sphere*12 {
r: .12;
move: .8 0 0;
rotate: y n * 30;
fill: hsl(n / N, .7, .6);
}
view: 0 30;
); From an outline
polygon takes a @shape body as its outline, holes and all, and gives it a depth.
The outline is in the units of @shape, where the box is 2 across.
points: x y, … lists the corners instead:
@grid: 1 / 60vmin;
background: @scene(
polygon { heart; depth: .3; fill: #e33 }
view: 30 20;
); lathe turns the right half of an outline around y:
@grid: 1 / 60vmin;
background: @scene(
lathe { vase; fill: #4a8 }
view: 0 20;
); Expressions
Values are written in the math of @pattern.
p is the point in the shape's own space, t the time in seconds,
and rand() differs from copy to copy.
fill may be an expression in p that gives a color:
@grid: 1 / 60vmin;
background: @scene(
torus { r: .5 .2; fill: mix(#f06, #0cf, p.x + .5) }
view: 20 50;
); sdf { d: … } draws any shape from its signed distance, written in p:
@grid: 1 / 60vmin;
background: @scene(
sdf {
d: length(p) - .6
+ .04 * sin(20 * p.x) * sin(20 * p.y) * sin(20 * p.z);
}
); Bending space
twist: a turns a shape a degrees for each unit up y.
mirror: x z keeps the shape and its reflections across those axes,
and repeat: x y z repeats it that far apart along each axis, 0 for none:
@grid: 1 / 60vmin;
background: @scene(
box { size: .5 1.5 .5; twist: 90; fill: #0cf }
view: 30 15;
); Camera
view: turn tilt turns the camera around y and tilts it down, in degrees.
perspective: d sets its distance, 2 by default, and none gives a parallel view:
@grid: 1 / 60vmin;
background: @scene(
box*5x5 {
size: .3;
round: .03;
move: nx * .4 - 1.2, 0, ny * .4 - 1.2;
fill: hsl(n / N, .6, .6);
}
view: 45 35;
perspective: none;
); Motion
A value that reads t makes the scene move, and view: 60t spins it in place.
The camera is fitted to everywhere the model goes, so it holds still while the model or the view moves.
Put a moving scene in @content, where it draws a live <canvas>.
As a background, every frame is encoded into a new image, which is much slower.
@grid: 1 / 60vmin;
@content: @scene(
box { size: .8; round: .08; rotate: 1 1 0 45; fill: #fc0 }
view: 60t 20;
); Reference
| Shape | Sizes |
|---|---|
sphere | r |
ellipsoid | r: x y z |
box | size, one value or x y z; round |
cylinder | r, height; round |
cone | r: bottom top, height |
torus | r: ring tube |
capsule | from: x y z, to: x y z, r, or r: start end |
polygon | a @shape body or points: x y, …; depth, round |
lathe | a @shape body or points: x y, … |
plane | none; everything below y = 0, to cut with in a group |
sdf | d, a distance written in p |
| Row | Meaning |
|---|---|
move | offset x y z, with y pointing up |
rotate | counterclockwise rotation in degrees around z, x, y, or an axis x y z before the angle |
scale | one factor, or one per axis; a negative one mirrors |
grow | makes the shape bigger by this much |
frame | keeps a shell this thick inside the surface |
round | rounds the edges of a box, cylinder or polygon, keeping its outer size |
twist | degrees of turn for each unit up y |
mirror | the axes to reflect across, x, y, z |
repeat | spacing x y z of endless copies, 0 for none |
fill | a color, or an expression in p that gives one |
smooth | on a group: the radius of the curve where its shapes meet |
view | at the top: turn tilt of the camera, in degrees |
perspective | at the top: the camera distance, 2 by default, or none |
| name | any other name declares a variable for the rows and shapes of its block |
move, rotate, scale, grow, frame, mirror,
repeat and twist run in the order written; for every other row the last one wins.
A size or row a shape does not take is reported as a warning, which is how a misspelled name shows.