Creating sketches with Solandra for the Amimetic website

I wanted to create a few 'sketches' with my open source, TypeScript-first creative coding library Solandra both as a demo and for something interesting to look at on my website. Here is their source code and explanations.

1

This shows off a couple of Solandra's features. palettePreset returns a nice palette of colours in an array.

Then the main code converts a basic shape to a path then uses a bunch of Solandra's powerful path manipulation functions to create a bunch of interesting shapes which can then be coloured by the palette.

const cols = palettePreset('natural', 12)
s.background(350, 30, 80)

new RegularPolygon({ at: s.meta.center, r: 0.35, n: 6 }).path
  .exploded()
  .flatMap((p) => p.exploded())
  .flatMap((p) => p.exploded({ scale: 1.2 }))
  .forEach((p, i) => {
    s.setFillColor(...cols[i % cols.length])
    s.fill(p)
  })

2

This one uses some HTML canvas capabilities but as nicely exposed by Solandra. We can fill a linear gradient with an arbitrary clipping (which with Solandra we can do via a function passed to withClipping).

s.background(210, 70, 10)

s.withClipping(new Circle({ at: s.meta.center, r: 0.4 }), () => {
  s.backgroundGradient(
    new LinearGradient({
      from: [0.5, 0],
      to: [0.5, s.meta.bottom],
      colors: [
        [0, { h: 210, s: 80, l: 40 }],
        [1, { h: 190, s: 60, l: 60 }],
      ],
    }),
  )

  s.forPoissonDiskPoints({ minDist: 0.05 }, (pt, i) => {
    if (perlin2(...v.scale(pt, 4)) > 0.05) {
      s.setFillColor(210, 10, 90, 0.4)
      s.fill(new RegularPolygon({ at: pt, r: 0.06, n: 6, a: s.randomAngle() }))
    }
  })
})

3

Here we use the new forPoissonDiskPoints function to iterate over a bunch of points created by the Poisson disk algorithm.

We can colour nicely based on a palette using the perlin2 function to get a nice natural looking value.

const cols = palettePreset('autumnal', 20)
const c = (v: number) => cols[Math.floor(((1 + v) * cols.length) / 2)]

s.background(40, 70, 80)
s.forPoissonDiskPoints({ minDist: 0.05 }, (pt, i) => {
  const value = perlin2(...pt)
  s.setFillColor(...c(value))
  s.fill(new Circle({ at: pt, r: 0.02 }))
})

4

This is one of the longer examples but shows how we can layer several arbitrary drawings to create something interesting. It was adapted from one of the many examples in the Solandra docs.

const { bottom, right } = s.meta
s.backgroundGradient(
  new LinearGradient({
    from: [0, 0],
    to: [0, bottom],
    colors: [
      [0, { h: 215, s: 80, l: 60 }],
      [1, { h: 215, s: 80, l: 20 }],
    ],
  }),
)

s.times(40, () => {
  s.setFillColor(s.uniformRandomInt({ from: 180, to: 225 }), 80, 40, 0.3)
  s.fill(
    new Rect({
      at: [0, s.gaussian({ mean: bottom * 0.6, sd: bottom * 0.15 })],
      w: right,
      h: bottom * 0.1,
    }),
  )
})
s.lineWidth = 0.005
s.times(20, () => {
  s.setStrokeColor(0, 0, 100, 0.2)
  const y = s.gaussian({ mean: bottom * 0.8, sd: bottom * 0.1 })
  s.draw(new Line([0, y], [right, y]))
})

5

One of Solandra's nicest features is an original take on how to draw curves, with an approach, that unlike standard Bezier curves:

  • allows for the shape to remain the same as the start and end points change
  • has a more natural, human set of parameters: you set the 'curve size' or 'bulbousness' rather than control points.

Here we draw a bunch of curves between points with a variety of pseudo-random parameters.

s.backgroundFromSpec({ h: 30, s: 50, l: 90 })

const hr = palettePreset('rusty', 22)

const {
  bottom: b,
  right: r,
  center: [cX, cY],
} = s.meta
const u: Point2D = [cX - r / 3.5, cY - b / 3.5]
const v: Point2D = [cX + r / 3.5, cY + b / 3.5]

s.lineWidth = 0.02
s.range({ from: -10, to: 10, n: 20 }, (n) => {
  s.setStrokeColor(...hr[(n + 10) % 20])
  s.draw(
    Path.startAt(u).addCurveTo(v, {
      curveSize: n / 10,
      curveAngle: s.gaussian(),
      bulbousness: 1 + Math.cos(s.t / 2 + n),
    }),
  )
})