Tuesday, 29 October 2019

Solandra Hands-On Tutorial & Emergent Behaviour In Insects

This month's London meetup has two themes, a hands on tutorial on Solandra, a modern opinionated javascript framework for creative coding, and a talk on emergent behaviour in insects.


An overview of Solandra, including its rationale, documentation and examples, is here: [link].

A video of the talk on insects is here: [link], and slides here: [link].


Solandra Principles

James Porter is an experienced technologist and an algorithmic artist. Over time he became frustrated with the limitations and API design choices of Processing and explored other options. He looked at Quil, a Clojure wrapper for JVM Processing, but found it unsatisfactory. He then explored Kotlin, a more modern language that runs on the JVM. His journey ultimately led him to develop Solandra, a javascript framework that, importantly, runs in a web browser.


His experience of both coding and creative frameworks in particular, informed the principles he wanted his new framework to adhere to. You can read more here, but notable examples are:

  • coordinates independent of pixel size, width is always 1.0, height depends on aspect ratio
  • simple data structure for 2-d coordinates, usable in the form [x. y]
  • TypeScript to support coding via autocomplete and type checking
  • control flow / iteration over common drawing-level abstractions eg tiling, partitions, exploded shapes
  • rethink counter-intuitive API orthodoxy eg for bezier curves
  • minimal dependences and quick low-friction compile cycle
  • support agility to experiment and try new ideas

James has really thought about the most common things that developers do with frameworks like p5js but aren't as simple as they should be. A good example is Solandra providing a very simple time counter, avoiding the need to calculate it indirectly.

James consciously developed this framework to meet the needs of experienced coders, and didn't design for onboarding newcomers to coding.


Solandra Illustrative Examples

James led a hands-on tutorial working through key concepts. He used codesandbox.io a hosted environment with Solandra set up ready for use.

James provides several tutorials on his website (link), but here we'll look at a small number of examples that illustrate key design concepts for Solandra.

For the following illustrative examples, you can use Jame's codesandbox environment and modify the code as required: link.

On first loading, you should see the following page with sample code running:


We can edit the code, and it is automatically complied from typescript to javascript and executed, with the results appearing on the right.

All Solandra code implements a sketch function, the contents of which decide what is drawn or animated.


const sketch = (s: SCanvas) => {
  // write code here
}


You can see the syntax uses modern javascript for conciseness The object s is of type SCanvas, and is the context in which we change colours, draw shapes, and so on.

Let's illustrate by setting the background colour. This is done via the s object.


export const sketch = (s: SCanvas) => {
  // set background
  s.background(60, 80, 80);
}


You should see a light yellow coloured background.


This simple example illustrates who we operate on the canvas via the s object.

Let's draw a simple shape, a circle. In Soldandra, shapes are objects which we have the power to manipulate. We can choose to draw them, they aren't drawn by default.


export const sketch = (s: SCanvas) => {
  // set background
  s.background(60, 80, 80);

  // create circle
  const mycircle = new Circle({at: [0.5, 0.5], r: 0.2});
  // draw it
  s.draw(mycircle);
};


You can see we're first creating a new circle object and calling it mycircle. The parameters are concisely expressed and intuitive, the circle is centred on the canvas at (0.5, 0.5) and has a radius of 0.2. You should see a nice circle like this:


Very often we don't need to keep the object around for further operations so it is common to create the shape and draw it immediately, like this:


export const sketch = (s: SCanvas) => {
  // set background
  s.background(60, 80, 80);
  // draw filled circle
  s.fill( new Circle({at: [0.5, 0.5], r: 0.2}) );
};


You can see we've used s.fill() instead of s.draw() which draws a filled circle instead of the outline.


A very common task is to move over the canvas in regular steps and do something at those points. This is a basis for many works of algorithmic art. James provides a convenience function for iterating over one dimensional and two dimensional grids.

It is easiest to see the code:


export const sketch = (s: SCanvas) => {
  // Hue, saturation and lightness (alpha)
  s.background(60, 80, 80);

  s.forTiling({ n: 7, type: "square", margin: 0.1 }, (pt, [d], c, i) => {
    s.setFillColor(i*5, 80, 40, 0.4);
    s.fill(new Circle({ at: c, r: 0.05 }));
  });

};


Here the forTiling() function takes intuitive parameters, the number of grid subdivisions, the type of tiles, and size of the margin around the edge of the canvas. In return it creates variables which provide the position of each tile, its dimensions, its centre and an overall count. You can see we're using the count i to set a fill colour, and then drawing a circle at the centre of each imaginary tile.


Such iterators with callbacks that fill in useful variables are a key design element of James' Solandra. It is useful to think how much more effortful the code to achieve this tiling pattern in plain p5.js would be.

James has done a lot of thinking about bezier curves, which are intuitive to create interactively, in vector drawing tools for example, but are more difficult to code. Solandra makes it easier to imagine curves and translate that vision to code, by focussing on what's intuitive - the key points and the curvature of the curve between those points.

The following code, taken from one of Jame's online sample sketches,  illustrates the construction of curves.


export const sketch = (s: SCanvas) => {
  // Hue, saturation and lightness (alpha)
  s.background(0, 0, 50);

  s.forTiling({ n: 12, margin: 0.1 }, ([x, y], [dX, dY]) => {
    s.setStrokeColor(20 + x * 40, 90 - 20 * y, 50)
    s.draw(
      Path.startAt([x, y + dY]).addCurveTo([x + dX, y + dY], {
        polarlity: s.randomPolarity(),
        curveSize: x * 2,
        curveAngle: x,
        bulbousness: y,
      })
    )
  })

};


We can see a tiling iterator dividing the canvas into a 12x12 grid. A curve, Path(), is started at a point (x, y+dY) and as the iterator moves along the grid, subsequent points (x+dX, y+dY) are added to it. Each segment has its own parameters like polarity, curve size, curve angle (approx asymmetric skew), and bulbousness around the endpoints.


You can see that as the curves progress to the right, the curve size increases. You can experiment by changing those curve parameters to see the effect on the curves.

One interesting area of flexibility is that shapes are objects before they are rendered. That means they can be operated upon or subject to filters. The following shows an example of this.


export const sketch = (s: SCanvas) => {
  s.background(0, 0, 60);
  s.setFillColor(0, 80, 40, 0.3);
  s.lineWidth = 0.01;

  const h = new RegularPolygon({at: s.meta.center, n: 8, r: 0.2});
  s.setStrokeColor(220, 80, 40, 0.2);
  s.draw(h);

  const h2 = h.path.segmented
  .flatMap(e => e.exploded({ scale: 0.8, magnitude: 1 }))
  .map(e => e.rotated(s.gaussian({ sd: 0.1 })))
  .forEach(e => {
    s.fill(e);
  })

  s.lineWidth = 0.003;
  s.setStrokeColor(270, 60, 40, 0.2);
  s.times(40, () => {
    const h3 = h.path.curvify(
      () => ({
        curveSize: 1+s.gaussian({ mean: 0.5, sd: 0.5 }),
        curveAngle: s.gaussian({ mean: 0.0, sd: 0.5 }),
      })
    )
    
    s.draw(h3);
  })
  

};


You can see that we first create a polygon h with 8 sides, and it is drawn as an outline. We then create a new shape h2 from h. We do this by converting the octagon into paths and segmenting the shape. This collection of segments is exploded with scaling and rotated by a small random amount. Each item is plotted as a filled shape.

Finally we create a new shape h3 from h and this time the paths are converted from lines to curves, with some randomness in their curve size and angle. This is actually done 40 times using a times() convenience function.


You can see the octagon, the segmented exploded shapes, as well as the curves created from the corners of the octagon.

We can simplify the code to create a design using only the curves.


export const sketch = (s: SCanvas) => {
  s.background(0, 0, 60);
  s.lineWidth = 0.01;

  const h = new RegularPolygon({at: s.meta.center, n: 8, r: 0.2});

  s.lineWidth = 0.002;
  s.setStrokeColor(270, 60, 20, 0.1);

  s.times(300, () => {
    const h3 = h.path.curvify(
      () => ({
        curveSize: 1.0+s.gaussian({ mean: 0.5, sd: 0.5 }),
        curveAngle: s.gaussian({ mean: 0.0, sd: 0.5 }),
      })
    )
    
    s.draw(h3);
  })
  
};


The results are pretty effective.



Emergent Behaviour in Insects

We also had a talk by an invited specialist in insect behaviour, Alison Rice, founder of Tira Eco, a biotech startup working to develop more natural and sustainable approaches to waste recycling.

Given how much of algorithmic art is modelling or simulating how nature works, whether is it crystalline grown or the flocking behaviour of boids, I thought it would be interested to hear directly from a scientist working with insects directly.

Her talk was fascinating and energetic, and generated a lot of interest and questions.


Her slides include videos showing how maggots appear to self-organise once their population density increases around food. It is understood this emergent behaviour optimises overall group energy efficiency.


The emergent geometric forms are fascinating!


Thoughts

It was particularly exciting to see someone challenge the orthodoxy around APIs and coding patterns and design a modern framework for creative coding, based on the actual experience of developers who had started to hit the limits of popular frameworks like Processing and p5js.

Personally, I think not enough is done to actually design programming languages and the metaphors and abstractions they give developers.

One of my own needs, and of many others based on queries and web analytics, is for a creative coding framework like p5js or Solandra to output a vector drawing, in SVG for example. Perhaps this is a future project for me!



I was really pleased that the group was excited by having Alison, a specialist in her field, bring her more direct experience and expertise of the natural world into a group which very often models that world only code.

Feedback strongly suggested we do that more often!


References


No comments:

Post a Comment