Abstract Multi Turtle Shapes

Draws abstract shapes using a list of turtles where each turtle moves towards another turtle in the list.

Log in to post a comment.

// You can find the Turtle API reference here: https://turtletoy.net/syntax

// Line color
Canvas.setpenopacity(-0.25);

// Canvas is 200x200
const canvas_size = 200;

// Number of turtles
const turtles = 200; // min=1, max=500, step=1

// Offset between turtle and follower
const follower_offset = 3; // min=1, max=10, step=1

// When initially positioning turtles, the length increment on each point in the spiral
const spiral_length_increment = 5; // min=0, max=100, step=1

// When initially positioning turtles, the degree increment on each point in the spiral
const spiral_degree_increment = 15; // min=1, max=360, step=1

// Number of generations to draw
const generations = 400; // min=1, max=1000, step=5

// Number of generations to skip before putting the pen down
const skip_generations = 40; // min=0, max=1000, step=5

// Step length when moving towards next turtle
const step_length = 18; // min=1, max=100, step=1

// How much to change the step length on each generation
const step_increment = -1; // min=-10, max=10, step=1

// Create and position the turtles
const turtle_list = new Array(turtles);
for (i=0; i<turtles; i++) {
    const turtle = new Turtle();
    
    let position = pointAt(0, 0, radians(spiral_degree_increment * i), (spiral_length_increment * i));

    turtle.penup();
    turtle.goto(position[0], position[1]);

    turtle_list[i] = turtle;
}

// Connect each turtle to the target turtle it is following
for (i=0; i<turtles; i++) {
    let target_index = i+follower_offset;
    
    if (target_index >= turtles) {
      target_index -= turtles;
    }
    turtle_list[i].target = turtle_list[target_index];
}

let generation_index = 0;
let phase = 0;
let turtle_index = 0;
let current_step_length = step_length;

// The walk function will be called until it returns false.
function walk(i) {
    const turtle = turtle_list[turtle_index];

    if (phase == 0) {
        turtle.angle = angle_to(turtle.x(), turtle.y(), turtle.target.x(), turtle.target.y());
    } else if (phase == 1) {
        let position = pointAt(turtle.x(), turtle.y(), turtle.angle, current_step_length);

        if (generation_index >= skip_generations) {
            turtle.pendown();
        }

        turtle.goto(position[0], position[1]);
    }

    if (turtle_index >= (turtles - 1)) {
        turtle_index = 0;
        phase++;
    } else {
        turtle_index++;
    }
    
    if (phase > 1) {
        phase = 0;
        generation_index++;

        current_step_length += step_increment;
        if (current_step_length < 1) {
            current_step_length = 1;
        }
    }
    
    return generation_index < generations;
}

function radians(angle) {
    return angle * (Math.PI / 180);
}

function pointAt(x, y, radians, distance) {
    let new_x = x + (distance * Math.cos(radians));
    let new_y = y + (distance * Math.sin(radians));
    
    let clipped_x = new_x % (canvas_size / 2);
    let clipped_y = new_y % (canvas_size / 2);
    
    return [clipped_x, clipped_y];
}

function angle_to(x1, y1, x2, y2) {
    atan_radians = Math.atan2(y2 - y1, x2 - x1);

    if (atan_radians >= 0) {
        return atan_radians;
    } else {
        return Math.PI * 2 + atan_radians;
    }
}