Home / Coracle / Articles / '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);
}
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 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])
}
}
}