Function

@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

ShapeSizes
spherer
ellipsoidr: x y z
boxsize, one value or x y z; round
cylinderr, height; round
coner: bottom top, height
torusr: ring tube
capsulefrom: x y z, to: x y z, r, or r: start end
polygona @shape body or points: x y, …; depth, round
lathea @shape body or points: x y, …
planenone; everything below y = 0, to cut with in a group
sdfd, a distance written in p
RowMeaning
moveoffset x y z, with y pointing up
rotatecounterclockwise rotation in degrees around z, x, y, or an axis x y z before the angle
scaleone factor, or one per axis; a negative one mirrors
growmakes the shape bigger by this much
framekeeps a shell this thick inside the surface
roundrounds the edges of a box, cylinder or polygon, keeping its outer size
twistdegrees of turn for each unit up y
mirrorthe axes to reflect across, x, y, z
repeatspacing x y z of endless copies, 0 for none
filla color, or an expression in p that gives one
smoothon a group: the radius of the curve where its shapes meet
viewat the top: turn tilt of the camera, in degrees
perspectiveat the top: the camera distance, 2 by default, or none
nameany 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.

See also