# Help

underconstruction.gif

When you first visited Bauble, you were greeted by an interactive tutorial. You can restore it by deleting your script (just clear out the text field) and then refreshing the page. Make sure to save a backup of your script if you’ve done something interesting!

Some general notes:

- Argument order is usually not significant; only the types of values
are significant. Argument order
*is*significant when a function takes multiple arguments of the same type, such as`torus`

– the major radius must come before the minor radius. - Probably the most useful functions in order are
`move`

,`union`

,`rotate`

, the shape primitives,`color`

, and`shade`

.

All of the examples on this page are interactive. To prevent annoying scroll hijacking, you have to click on a canvas to focus it before you can zoom.

# Function reference

- Shapes
- Spatial operations
- Hybrid spatial/surface operations
- Repetition
- Operations on color
- Meta-operations
- General helpers
- GLSL functions ported to Janet
- Janet functions ported to GLSL
- Functions that are the same
- Color constants

# Shapes

A quick list of all shape primitives:

`sphere`

`sphere`

is the simplest of all primitives. It has only
one parameter: the radius.

`box`

`box`

can take a single `float`

dimension or a
`vec3`

.

The dimensions you supply to `box`

are twice the size that
the box appears. Think of them like a radius: a box of “width 50” will
range from `p.x = -50`

to `p.x = 50`

.

`:r`

to round the edges.

`cylinder`

The second argument is twice the height of the cylinder, which is centered at the origin.

`:r`

to round the edges.

`ellipsoid`

Not an exact distance field! This is an approximation that produces better results than scaling a sphere, but that is still only an approximation.

`line`

The two `vec3`

arguments are the start and end points. An
optional `float`

argument is the radius of the line (default
`0`

).

`torus`

`half-space`

This one is very hard to show with images, because it just looks like a flat-colored plane of infinite extent. Instead, it’s easier to understand when used as an input to a boolean operation:

The first argument is a signed axis, and the second optional argument is the position along that axis.

`cone`

Unlike most other shapes, which are centered at the origin, the cone rests at the origin, and the height you pass it is the cone’s actual height, not one half of its height. It’s still the extent of the cone in a single axis, like other shapes, but unlike most shapes the cone does not extend in both directions.

`:r`

to round the edges.

`ground`

`ground`

should not be used in actual art, but it’s useful
for debugging. It’s kind of a “`(half-space :-y)`

that you
can see through,” so that you can add a ground to see shadows, and still
move the camera around to view the underside of your shape.

It is not an actual distance field (the distance depends on the position of the camera), so it will not work well with morphs, boolean operations, or ambient occlusion. It’s really just for quickly looking at shadows.

# Spatial operations

`move`

Can take a `vec3`

elements or named `:x`

,
`:y`

, and `:z`

float arguments. You can mix and
match these forms, and the computed sum will be used.

`move`

is also a great way to distort shapes.

`rotate`

Takes named `:x`

, `:y`

, and `:z`

arguments; the final rotation is the combination of each rotation in
order. There is no vector form because the order of the rotations is
significant: `(rotate :x 1 :y 1)`

is not the same as
`(rotate :y 1 :y x)`

.

You can also pass `:pivot`

, with a `vec3`

that
will form the point around which rotation happens.

You can also optionally supply scale arguments, which will multiply
the rotations *following* that scale argument by a fixed amount.
Valid scales are `:pi`

, `:tau`

, or
`:deg`

. You can change scale mid-rotation, for example by
saying `(rotate :deg :x 45 :tau :y 0.125)`

. This is
equivalent to `(rotate :x tau/8 :y tau/8)`

.

`scale`

Can take a single `float`

, a `vec3`

, or named
`:x`

`:y`

`:z`

parameters. Like `move`

, you can mix and match these forms
and the final product will be used as the scale factor for each
axis.

If you scale by different amounts across different axes, the distance
field produced will be an underestimate. This means that Bauble can
still raymarch it accurately without the use of `slow`

, but soft shadows and boolean
operations will be inaccurate.

`offset`

Name will probably change.

Good way to apply texture to a shape, although if your expression is
expensive you should use `bounded-offset`

instead.

`onion`

`float`

argument is half of the thickness. The shape will
be both inset and outset by this amount.

`distort`

This allows you to supply an arbitrary expression that will replace
`p`

. You can use this to deform space in ways that are not
possible with any of the built-in operators.

This is by its nature a bizarre operation that can be used to do anything, including implement any of the other spatial distortion operations, so examples are not very informative.

`mirror`

Interior distances will have discontinuities when a shape crosses the axis.

You can specify multiple axes to mirror along. The optional
`:r`

argument makes this a smooth mirror.

`reflect`

Like mirror, but doesn’t create a copy. Can take multiple axes.

`mirror-plane`

TODO

`mirror-space`

TODO

`symmetry`

It’s hard to describe this one. It mirrors space across every axis and also flips it across every axis of rotation. You end up with only a tiny sliver of useful space that you can put a shape in, so it’s only really suitable for abstract things.

`flip`

Rotates the shape 90° around a signed axis (so `:x`

rotates counter-clockwise, `:-x`

rotates clockwise). More
efficient than `rotate`

.

`twist`

`float`

argument is the rate of rotation, in radians per
unit distance.

This is equivalent to a rotation around an axis that varies with
`p.(axis)`

, but it might be slightly more efficent.

Does not produce a correct distance field.

`swirl`

`float`

argument is the rate of rotation, in radians per
unit distance.

This is equivalent to a rotation around an axis that varies with
`(length p.(other axes))`

, but it might be slightly more
efficent.

Does not produce a correct distance field.

`bend`

This operation is weird and hard to explain and I will probably get rid of it.

Does not produce a correct distance field.

`map-distance`

Apply a function to a shape’s distance field. Can be used to do weird
things. Many operations, like `offset`

or `slow`

,
are simple transformations on the underlying distance field. For
example, `offset`

:

# Hybrid spatial/surface operations

The boolean operations `union`

, `intersect`

,
and `subtract`

can all take an optional `:r`

value, but note that this will make color calculations slower:
*all* surfaces will be evaluated, even those that do not
contribute at all to the final result. When used without
`:r`

, or with an `:r`

value of `0`

(as
evaluated on the CPU – any symbolic expression will cause Bauble to take
the slow branch), only the one relevant surface will be evaluated.

Because of this, you should prefer to apply surfaces *after*
smooth boolean operations, unless of course you are relying on the
surface blending. Even if it’s the same surface, Bauble will still
evaluate it multiple times! (This is a fixable deficiency in Bauble but
it is the way that it is right now.)

`union`

Union produces a correct distance field unless shapes overlap, in which case the interior distance field will be discontinuous.

`intersect`

Does not produce a correct distance field.

`subtract`

Does not produce a correct distance field.

`morph`

Produces incorrect distance fields when the amount is outside of the
range `[0, 1]`

.

# Repetition

`tile`

Tile divides space into rectangular regions.

The `vec3`

argument determines the size of each tile, with
`0`

meaning that no repetition takes place in that direction.
By default the shape will be repeatedly infinitely in every direction,
but the optional argument `:limit`

will clamp the
repetition.

`:limit`

must be a tuple of three positive integers; you
cannot write a dynamic expression for `:limit`

.

If you provide a function argument instead of a shape, your function
will be called with an expression for the current index of the tiling.
This allows you to produce different objects at each instance of the
tiling. The index will always be a `vec3`

, even if you are
not repeating in all three directions.

Indexes are integers, so the index of the element at the origin is
`[0 0 0]`

, the index to the right of that is
`[1 0 0]`

, etc.

You can also provide a shape *and* a function, in which case
the shape will be passed as the first argument to your function. By
appropriating a little bit of Janet convention, we call this argument
`$`

, for `$hape`

. This is useful for fitting
`tile`

into a pipeline:

### Asymmetry

The way `tile`

works is that, for each step of the
raymarch, it computes the current “slice” of space. Then it evaluates
*only* that one slice, and returns the nearest distance.

This means that, if the actual nearest shape is in a
*different* tile than the current one, this will produce an
invalid distance field.

Here you can see lots of artifacts around the edges of the boxes,
where rays overshoot their targets. The solution to this problem, if you
want to tile an asymmetric shape, is to first duplicate the shape a
small number of times (with `union`

) and then to tile that
array of shapes with overlap.

A future version of Bauble will have a helper to make this more convenient.

`radial`

Similar to `tile`

, but repeats radially. Requires an axis
and a count. Can optionally take a number that will determine how far to
outset the shape before repeating it (default `0`

).

Like `tile`

you can supply a mapping function (with a
shape) or a producing function (without a shape). The index will be an
integer in the range `[0, count)`

. As with `tile`

,
only a fraction of space is considered at once, so asymmetric shapes
will produce invalid distance fields.

# Operations on color

`shade`

`fresnel`

`cel?`

`resurface`

`map-color`

`color`

# Meta-operations

`bound`

`bounded`

`bounded-offset`

`slow`

`pivot`

# General helpers

`fork`

`spoon`

`remap+`

(and`sin+`

,`cos+`

,`perlin+`

)`hsv`

`hsl`

`rgb`

`hex-rgb`

`pi`

,`pi/2`

,`pi/3`

, etc`tau`

,`tau/2`

,`tau/3`

, etc`deg`

`tau*`

,`pi*`

,`tau/`

,`pi/`

`ss`

`hash`

functions`perlin`

# GLSL functions ported to Janet

`sign`

`clamp`

`step`

(note: argument order reversed)`smoothstep`

`distance`

`dot`

`mix`

`min`

(note: overloaded better)`max`

(note: overloaded better)`fract`

`normalize`

`sqrt`

`length`

/`vec-length`

`pow`

(note: overloaded better)

# Janet functions ported to GLSL

`sum`

`product`

`atan2`

(note: safer than`atan`

)

# Functions that are the same

`sin`

`cos`

`abs`

`round`

`floor`

`ceil`

`atan`

`mod`

# Color constants

`(def red (hsv (/ 0 6) 0.98 1))`

`(def orange (hsv (/ 0.25 6) 0.98 1))`

`(def yellow (hsv (/ 1 6) 0.98 1))`

`(def green (hsv (/ 2 6) 0.98 1))`

`(def cyan (hsv (/ 3 6) 0.98 1))`

`(def sky (hsv (/ 3.5 6) 0.98 1))`

`(def blue (hsv (/ 4 6) 0.98 1))`

`(def purple (hsv (/ 4.5 6) 0.98 1))`

`(def magenta (hsv (/ 5 6) 0.98 1))`

`(def hot-pink (hsv (/ 5.5 6) 0.98 1))`

`(def white [1 1 1])`

`(def light-gray [0.75 0.75 0.75])`

`(def gray [0.5 0.5 0.5])`

`(def dark-gray [0.25 0.25 0.25])`

`(def black [0.03 0.03 0.03])`