Basic tails

Essentially a Perlin noise flow field

import coracle.*
import kotlin.math.cos
import kotlin.math.sin

class BasicTails: Drawing() {
    val w = 450
    val h = 450
    val boids = Array(350){ Boid(random(w), random(h))}
    val boidColour = 0x000000
    val worldColour = 0xf5f2f0

    var epochElapsed = 0
    var epochLength = 800

    var minNoiseScale = 0.025f
    var maxNoiseScale = 0.06f
    var noiseScale = random(minNoiseScale, maxNoiseScale)

    var frame = 0

    override fun setup() {
        size(w, h)

        stroke(boidColour)
        fill(boidColour, 0.85f)
    }

    override fun draw() {
        frame++
        background(worldColour)

        boids.forEach { boid ->
            boid
                .iterate()
                .checkBounds()
                .draw()
        }

        epochElapsed++
        if(epochElapsed >= epochLength){
            Perlin.newSeed()
            noiseScale = random(minNoiseScale, maxNoiseScale)
            epochElapsed = 0
        }
    }

    inner class Boid(x: Float, y: Float): Vector(x, y) {

        var age = 0
        var deathAge = random(100, 340)

        var velocity = Vector(0f, 0f)
        var maxSpeed = 1.7f

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

        fun iterate(): Boid {
            val a = TAU * Perlin.noise(x * noiseScale, y * noiseScale)
            var direction = direction(Vector( x + (cos(a)).toFloat(), y + (sin(a) ).toFloat()))
            direction *= 0.35f

            velocity += direction

            velocity.limit(maxSpeed)

            x += velocity.x
            y += velocity.y

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

            age++

            return this
        }

        override fun draw(){
            repeat(tailLength){ tailIndex ->
                stroke(boidColour, 0.4f)
                point(tailX[tailIndex], tailY[tailIndex])

                if(tailIndex == tailLength - 1){
                    noStroke()
                    circle(x, y, 1)
                }
            }

        }

        fun checkBounds(): Boid {
            if(age >= deathAge || x < 0 || x > width || y < 0 || y > height ){
                x = random(width)
                y = random(height)
                tailLength = randomInt(10, 30)
                tailX = FloatArray(tailLength)
                tailY = FloatArray(tailLength)
                age = 0
                deathAge = random(100, 340)
            }
            return this
        }
    }
}