Home / Coracle / Articles / 'trails' versus 'tails'

'trails' versus 'tails'

  

Years ago (18 to 20 years!) I remember someone uploaded a sketch to the old Processing.org forums that used a technique to add trails to a moving object that was very simple and effective. With just a couple of lines of code your sketches suddenly had a cool new effect. Processing syntax:

void draw() {
  x++;
  if(x > width) x = 0;

  //Draw red circle:
  noStroke();
  fill(255, 0, 0);
  ellipse(x, height/2, 5, 5);

  //Overlay semi transparent rectangle
  fill(200, 200, 200, 25);
  rect(0, 0, width, height);
}
    

Trails

The trick is to simply not clear the canvas at the start of the draw iteration (most sketches have a background(colour) call as the first line in the draw loop to clear the canvas), and then draw a semi-transparent rectangle over the entire canvas at the end. This gives the canvas a memory of objects previous positions that fades over a second or so, depending on the transparency level.

This has been added to the Coracle API, call foreground(colour, alpha) at the end of the draw method:

override fun draw() {

    //draw moving objects
    ...

    foreground(0xf5f2f0, 0.28f)
}
    

Tails

Tails is the name I've given to the process of storing an objects previous position so a 'tail' can be drawn each frame. This was adapted from some very efficient code in a Processing example sketch called Storing Input. The benefit of explicity drawing the tail is you have more control over how it's rendered and you don't have that tell-tale blurry effect. The drawback is some minor added complexity, and for certain drawings, a massively increased number of draw operations.

var frame = 0

override fun draw() {
    background(backgroundColour)

    frame++

    //update and draw tadpoles:
    tadpoles.forEach{ tadpole ->
        tadpole.update().draw()
    }
}

inner class Tadpole(){

    var x = random(width)
    vay y = random(height)

    var tailLength = randomInt(10, 30)
    var tailX = FloatArray(tailLength)
    var tailY = FloatArray(tailLength)

    fun update(): Tadpole {
        
        //Update x,y - move the tadpole
        ...

        val tailIndex = frame % tailLength
        tailX[tailIndex] = x
        tailY[tailIndex] = y

        return this
    }

    fun draw() {
        repeat(tailLength){ tailIndex ->
            point(tailX[tailIndex], tailY[tailIndex])
        }
    }
}