c2.js is a JavaScript library for creative coding based on computational geometry, physics simulation, evolutionary algorithm and other modules.

… from the website.

This library does not have a content delivery network (CDN), so to use in my blog, the first thing I will need to do is download the c2.min.js file and move it to the /etc/scripts folder of my blog.

I can now load it into my blog page by referencing it in my markdown like this:

<script src='/etc/scripts/c2.min.js'></script>

Once the library is loaded, we have access to the c2.Renderer class, which accepts a canvas element as an argument, and subsequently acts as an interface via which we can draw geometric primitives to the canvas:

<canvas id=c2_example_0></canvas>

<script type=module>

    // getting the canvas element
    let cnv = document.getElementById ('c2_example_0')

    // instantiating a new c2.Renderer object
    let renderer = new c2.Renderer (cnv)

    // calculating width and height
    const width = cnv.parentNode.scrollWidth
    const height = width * 9 / 16

    // setting the size of the canvas
    // via the renderer object
    renderer.size (width, height)

    // setting the background colour
    renderer.background ('turquoise')

    // no outline
    renderer.stroke (false)

    // fill colour
    renderer.fill (`hotpink`)

    // draw circle 
    renderer.circle (width / 4, height / 2, 50)

    // for easier trig:
    const TAU = Math.PI * 2

    // for convenience
    const mid = new c2.Vector (width / 2, height / 2)

    // to nudge the triangle down some arbitrary amount
    const tri_off = new c2.Vector (0, 17)

    // making a new Array with length = 3
    const tri_corners = Array (3)

        // filling the array with undefined
        // this step is necessary for the
        // next step to work
        .fill ()

        // .map will replace each element with the
        // return value of the function we pass in
        .map ((e, i) => {

            // each point to be 66 pixels
            // out from the centre
            const vec = new c2.Vector (0, 66)

                // we rotate 3 times to the circle
                // offsetting i by 0.5 to point the
                // triangle directly upwards
                .rotate (TAU * (i + 0.5) / 3)

                // moving the triangle to
                // the centre of the canvas
                .add (mid)

                // nudging it down a bit
                .add (tri_off)

            // returning the vector places it
            // in the array
            return vec

    // using the spread operator to give 
    // the contents of the tri_corners array
    // to the .triangle method, as arguments
    renderer.triangle (...tri_corners)

    // drawing a square
    renderer.rect (width * 3 / 4 - 50, height / 2 - 50, 100, 100)

Note that c2 has a Vector class, which is similar to the p5 Vector class, but with one important differnce. In c2, instance methods, such as .add () and .rotate () return a new vector object, rather than simply transforming the vector the method was called on.

A useful pattern to know looks like this:

const vec = new c2.Vector (0, 66)
    .rotate (TAU * (i + 0.5) / 3)
    .add (mid)
    .add (tri_off)

Here we are creating a new c2.Vector object and assigning it to the variable vec. However, before it is assigned to vec, the .rotate () method is called on it, which returns a new, rotated, vector. However, before this new rotated vector is assigned to vec, the .add () method is called on it, returning a new, translated vector, on which .add () is called one more time, returning another new translated vector, which is finally assigned to vec.

So in total four vectors were created, of which only the final vector was then passed to the assignment procedure =, and stored in the variable vec.

This block of code uses this type of pattern twice:

const tri_corners = Array (3)
    .fill ()
    .map ((e, i) => {
        const vec = new c2.Vector (0, 66)
            .rotate (TAU * (i + 0.5) / 3)
            .add (mid)
            .add (tri_off)
        return vec

The array is created with Array (), but before it is passed to the assignment procedure on the left, a .fill () method is called, which returns an array, on which the .map () method is called. Finally once the .map () function has exectuted, an array full of vectors is returned to the assignment procedure and stored in the variable tri_corners.

You can read about .fill () here, and about .map () here, and watch about them both, here.

Also note my use of the spread operator ... which allows me to pass the contents of an array into a function, as discrete arguments:

renderer.triangle (...tri_corners)

You can read about the spread operator here, or watch about here.


c2 ships with a c2.Random class, which instantiates an object capable being a linear congruential generator.

Click to start / stop animation:

<canvas id='c2.Random'></canvas>

<script type=module>

    // get the canvas element
    const cnv = document.getElementById (`c2.Random`)

    // create an instance of the c2.Renderer
    // passing in the canvas element
    const renderer = new c2.Renderer (cnv)

    // calculating and setting the size of the canvas
    const width  = cnv.parentNode.scrollWidth
    const height = width * 9 / 16
    renderer.size (width, height)

    // we will build the rand_colour function
    // with a c2.Random object:
    const rand = new c2.Random ()

    // giving the Random object a 'seed'
    // will swap to using LCG method
    const rand_seed = Math.random () * Number.MAX_SAFE_INTEGER
    rand.seed (rand_seed)

    // defining a function
    function rand_colour () {

        // calling the .next () method on the rand object
        // passing in 360 to be the maximum value
        // cutting off the decimal points with Math.floor ()
        const h = Math.floor (rand.next (360))

        // returning a string housing the random value for hue
        return `hsl(${ h }, 100%, 50%)`

    // this is the size of the "pixel" 
    // we will use for the static
    const l = cnv.width / 256

    // draw function
    renderer.draw (() => {

        // no outlines
        renderer.stroke (false)

        // go down the canvas
        for (let y = 0; y < cnv.height; y+= l) {

            // go across the canvas
            for (let x = 0; x < cnv.width; x+= l) {

                // get a random fill colour
                renderer.fill (rand_colour ())

                // draw a square there
                // of size l
                renderer.rect (x, y, l, l)

    // the .loop method on the renderer
    // sets the ._loop property of the renderer
    renderer.loop (false)

    // assigning to the .onclick a method that
    // toggles the state of the ._loop property
    cnv.onclick = e => renderer.loop (!renderer._loop)

Bubble friends

In this example we will make some pink bubbles that are perhaps a little over-eager to be friends with your cursor. Much of my code is inspired by this example, which uses this code.

We will first define a class called Bubble. As we want the object we are defining to exhibit many of the properties and methods of an existing class, c2.Circle, we can use the keyword extends to automatically give our Bubble all the properties and methods already contained in the c2.Circle class. This is a common concept in object oriented programming, called inheritance. You can learn about the extends keyword here, and here.

This is particularly useful for us, as we want to define custom behavour for our object (we want it to gravitate towards the pointer), but we also want to use c2.Circle.intersection () method to find the points the bubbles overlap each other. You can find some rudimentary documentation for the .intersection () method of the Circle class here.

// declaring a Bubble class
// which inherits from c2.Circle
class Bubble extends c2.Circle {

    // the constructor takes 5 arguments
    // position, velocity, acceleration vectors
    // a value for radius, and the renderer
    constructor (pos, vel, acc, rad, renderer) {

        // super is a keyword that calls
        // the constructor of the super class
        // c2.Circle in this case
        super (pos, rad)

        // assigns the vectors passed in
        // for velocity and acceleration
        this.v = vel
        this.a = acc

        // stores a reference to the renderer
        // on the object, for drawing to
        this.renderer = renderer

    update () {

        // the physics engine
        // .add () returns a vector in c2
        // add acceleration to velocity
        this.v = this.v.add (this.a)

            // .limit () to keep the velocity
            // in a cute range
            .limit (5)
        // add velocity to position
        this.p = this.p.add (this.v)

        // bounce off the left
        if (this.p.x < 0) {
            this.p.x = 0
            this.v.x *= -1

        // bounce off the right
        if (this.p.x > this.renderer.width) {
            this.p.x = this.renderer.width
            this.v.x *= -1

        // bounce off the top
        if (this.p.y < 0) {
            this.p.y = 0
            this.v.y *= -1

        // bounce off the bottom
        if (this.p.y > this.renderer.height) {
            this.p.y = this.renderer.height
            this.v.y *= -1

    // method to draw the object
    display () {

        // no outline
        this.renderer.stroke (false)

        // pink fill
        this.renderer.fill (`hotpink`)

        // pass the object itself to 
        // the .circle method on the 
        // renderer to draw
        this.renderer.circle (this)

Click on the canvas to toggle animation:.

<canvas id='bubble friends'></canvas>

<script type=module>

    // get the canvas element
    const cnv = document.getElementById (`bubble friends`)

    // create an instance of the c2.Renderer
    // passing in the canvas element
    const renderer = new c2.Renderer (cnv)

    // calculating and setting the size of the canvas
    const width  = cnv.parentNode.scrollWidth
    const height = width * 9 / 16
    renderer.size (width, height)
    renderer.background (`turquoise`)

    // pause the animation
    renderer.loop (false)

    // clicking on the canvas toggles the animation
    cnv.onclick = () => renderer.loop (!renderer._loop)

    // instantiate a c2.Random object
    const rand = new c2.Random ()

    // pass a random value to the rand object to seed
    // linear congruential generator mode
    const rand_seed = Math.random () * Number.MAX_SAFE_INTEGER
    rand.seed (rand_seed)

    // instantiating a c2.Vector to house 
    // the pointer coordinates.  Giving an
    // arbitrary starting position
    const pointer = new c2.Vector (width / 2, height / 2)

    // using the .onpointermove onevent listener
    // on the document object to keep track of 
    // where the mouse is
    document.onpointermove = e => {

        // using the .page properties of the pointerEvent
        // and the .offset properties of the canvas
        // to calculate position of the mouse
        // in relation to the canvas
        pointer.x = e.pageX - cnv.offsetLeft
        pointer.y = e.pageY - cnv.offsetTop

    // storing 12 Bubble objects, with random position
    // vectors, and a radius of 50, in an array
    // assigned to the variable 'bubbles'
    const bubbles = Array (12).fill ().map ((e, i) => {
        const x = rand.next (width)
        const y = rand.next (height)
        const p_vec = new c2.Vector (x, y)
        const v_vec = new c2.Vector (0, 0)
        const a_vec = new c2.Vector (0, 0)
        return new Bubble (p_vec, v_vec, a_vec, 50, renderer)

    // passing an anonymous function in to the .draw method
    renderer.draw (() => {

        // clear the canvas
        renderer.clear ()

        // for each Bubble in bubbles
        bubbles.forEach (b => {

            // create the vector which points to the cursor
            // from the bubble, by subtracting the bubble's
            // position vector from the pointer vector
            const to_pointer = pointer.sub (b.p)

            // create a vector which simulates 
            // a gravity-like force towards the cursor
            // .normalize () sets the magnitude to 1
            const grav = to_pointer.normalize ()

                // using inverse square law
                // tinkered with the numerator
                // until the result was sane / cute
                .mult (5000 / to_pointer.magSq ())

                // limited the maximum gravity
                // to preserve sanity / cuteness
                .limit (0.1)

            // set the acceleration vector of the bubble
            // to be the grav vector we just made
            b.a = grav

            // call .update () on the bubble
            b.update ()

            // display the bubble
            b.display ()

        // go through the bubbles array again
        bubbles.forEach ((b, i) => {

            // go through the remaining bubbles
            // which have not yet been iterated over
            for (let j = i + 1; j < bubbles.length; j++) {

                // pass those bubbles to the original bubble's
                // .intersection () method, which returns
                // an array of points, we are assigning to p
                let p = b.intersection (bubbles[j])

                // if p is not empty
                if (p != null) {

                    // make the line turquoise
                    renderer.stroke (`turquoise`)

                    // make the line 2 pixels thick
                    renderer.lineWidth (2);

                    // make a line between the two 
                    // points in the array
                    renderer.line (p[0].x, p[0].y, p[1].x, p[1].y)