Turtle Graphics

I have been working on a library to perform Turtle Graphics with animation so the user can see the image being ‘developed’ rather than just the final result. Here are a few examples.

These sketches have been embedded from the p5js Web Editor. The first two are looped but you can use the keyboard to pause the animation and change the turtle cursor in the last one.

I have created a detailed user guide explaining the library API that can see at
http://www.lagers.org.uk/tg/guide/guide.html

The library repository can be found on github. I will be publishing more information and further examples on github and the web editor.

If you create any interesting graphics with this library then post links here. I will be fascinated to see what you can do with it. :smile:

8 Likes

It’s not intuitively obvious how to install this library so we can use it.

Sorry about that. I will be putting more information on github and “how to install the library” will be a priority but in the meantime the easiest way is to create a p5js Web Editor account (if you don’t have one already) and then save the example into your account. After that simply edit the code in sketch.js to do your own thing. The library (minified) is in the file tg.min.js located in the example.

1 Like

Sorry to be dense, but where’s the ‘example’? I found the library on github, tg.min.js; is there any way for me to directly install it?

Addendum:

I tried adding library directly to my sketch but got this:

Addendum2:
I forgot to hook it up in index.html.

    <script src="sketch.js"></script>
    <script src="tg.min.js"></script>

After doing that I now get this:

Looks like p5.js got to Arrow first: ARROW
Maybe change it to ‘Arrw’?

Addedum3:
If I ‘fix’ ARROW then I got a problem with SQUARE and ROUND

After I ‘fix’ both of those I’m still left with this:
TG

And I don’t know how to get around that one.

1 Like

Maybe try loading the addon script tag above your sketch script tag? Otherwise your sketch is trying to access the addon’s code before it has been loaded.

@quark: any interest in adding your addon to the list of community addons on p5js.org? Libraries

2 Likes

Good idea; no more errors.

Yes switch these round. Always load the library before the sketch that uses it.

TG ‘hides’ the p5js reference to ARROW but this is a warning so will not stop the library from working. Since I am using instance mode you can use p.ARROW to access the p5js ARROW variable whereas ARROW accesses the TG arrow cursor.

Yes but I still have a few things to do first

  1. document the setup / installation process to enable using the library
  2. more documentation on the basic syntax of using TG for the first time user
  3. some simpler examples with p5js, probably using global mode. The examples here all use instance mode and some of the advanced features of the library, I created them to test different features of the library as I created it.
  4. finally I need to create a hires image for the library when I submit it :smile: - for me that will be the hardest part LOL.
1 Like

I’m unable to get the following to show any output. Did I leave something out?

let turtle = TG.getTurtle(400, 400);

function setup() {
  createCanvas(400, 400);
  background(220);
  turtle.pencolor("black");
  turtle.pensize(width = 2);
  turtle.turtle(DART);
  console.log(turtle);
}

function draw() {
  turtle.pd();
  turtle.forward(20).right(25).forward(55).start();
  turtle.showturtle();
}

Try this

let turtle = TG.getTurtle(400, 400);

function setup() {
  createCanvas(400, 400);
  turtle.pencolor("blue"); // balck is default so changed to blue
  turtle.turtle(DART); // could skip this because DART is the default
  turtle.xy(20, -180); // start the turtle near the top centre so we can see the whole graphic
  addTasks(turtle);
}

// Add some tasks to the turtle
function addTasks(t) {
  t.forward(20)
    .right(25)
    .forward(55)
    .start() // tell the turtle to start performing the tasks
    // showturtle() and st() do the same thing so I have used the shorter option
    .st()
    // Once these tasks ahve been completed the next line will add them
    // again. This will be repeated for ever since there is nothing to stop
    // the animation.
    .do(addTasks);
}

function draw() {
  background(220);
  // Draw the turtle graphic so its centre is at 200,200 on the screen
  turtle.draw(drawingContext, 200, 200);
}

Do not add tasks in the draw method because that is executed ~60 times per second so there is a risk means the tasks li

1 Like

That works. Looks like you’re initially drawing offscreen and then need to bring it over to user’s canvas.
Output:

One of the down sides to turtle graphics the way that we are using it is that the loop just keeps drawing. In the case of your demo the ‘DART’ never stops. That is not unique to your code but Natalie Freed’s example code published February 2013 had the same problem. The example starts out as a polygon and ‘morphs’ into a circle over time (see below). One way around this is to use noLoop() (Java mode) along with recursion so that the drawing routine iterates a certain number of times and then stops.

Natalie Freed example, immediate and delayed:
Immediate output:

Delayed output:

Quark Library Output Immediate vs. Delayed:
Immediate output (start of second pass):

Delayed output:

Alternate p5.js technique based on Natalie Freed’s work which adds recursion with noLoop() to avoid over drawing (not all of drawing functions were added to this demo):

//https://gist.githubusercontent.com/nataliefreed/8483050/raw/9e3f1d0f44bcb0c872762e4b984358d375e7b5fa/turtle.pde

let loc;
let orientation;

function drawPolygon(sideLength, degrees, numSides) {
  if (numSides > 0) {
    numSides -= 1;
    forward(sideLength);
    left(radians(degrees));
    drawPolygon(sideLength, degrees, numSides);
  }
}

function setup() {
  createCanvas(400, 400);
  strokeWeight(3);
  loc = createVector(width / 2 + 100, height / 2 + 50);
  orientation = radians(90);
  noLoop();
}

function draw() {
  background(220);
  drawPolygon(100, 360 / 8, 8);
}

// Turtle Utility Functions by Natalie Freed
function forward(pixels) {
  start = loc;
  end = p5.Vector.add(loc, polar(pixels, orientation));
  line(start.x, start.y, end.x, end.y);
  loc = end;
}

//calculate new orientation
function left(theta) {
  orientation += theta;
}

//converts an angle and radius into a vector
//negative y for left handed coordinate system
function polar(r, theta) {
  return new p5.Vector(r * cos(theta), r * sin(-theta)); 
}

Output:

“Global Mode” is p5js’ standard; and therefore the absolute majority code using that mode, including p5js tutorials!

As a workaround, you could simply re-use p5js’ ARROW constant instead of defining your own.

Or rename it in order to avoid crashing w/ p5js’ ARROW.

1 Like

Not surprising since there is nothing in the code to stop the turtle. I have modified the sketch (see below) so the turtle stops after 10 seconds.

Using noLoop() to stop the turtle will not work as expected. In TG the turtle processes the tasks independently of drawing the graphic to the display. So although noLoop() halts the display is does not stop the turtle from updating itself behind the scenes. TG provides the stop() task which halts the turtle update but allows the draw method to loop.

No need it seems that Processing can distiinguish between the two ARROWs. In the sketch below the turtle now uses its own ARROW cursor and we can change the sketch mouse cursor between the p5js ARROW and CROSS cursor with the a and c keys.

NOTE: TG is written in pure Javascript and is not dependent on any other library including p5js. I am using p5js because it provides a simple framework to create examples.

Good point I will change the examples to use global mode :smile:

let turtle = TG.getTurtle(400, 400);
const DURATION = 10000;

function setup() {
  createCanvas(400, 400);
  cursor(CROSS);
  turtle.pencolor("blue"); // balck is default so changed to blue
  turtle.turtle(ARROW); // could skip this because DART is the default
  turtle.xy(20, -180); // start the turtle near the top centre so we can see the whole graphic
  addTasks(turtle);
  console.log(millis());
}

// Add some tasks to the turtle
function addTasks(t) {
  if(millis() > DURATION){
    t.stop();
  }
  else {
  t.forward(20)
    .right(25)
    .forward(55)
    .start() // tell the turtle to start performing the tasks
    // showturtle() and st() do the same thing so I have used the shorter option
    .st()
    // Once these tasks ahve been completed the next line will add them
    // again. This will be repeated for ever since there is nothing to stop
    // the animation.
    .do(addTasks);
  }
}

function draw() {
  background(220);
  // Draw the turtle graphic so its centre is at 200,200 on the screen
  turtle.draw(drawingContext, 200, 200);
}

function keyTyped() {
  if (key == "a") cursor(ARROW);
  if (key == "c") cursor(CROSS);
}
2 Likes

Just a little peeve of mine:

If we don’t intend a variable to be re-assigned later, prefer keyword const over let or var:
const turtle = TG.getTurtle(400, 400);

JS’ keyword const is very similar to Java’s final btW. :coffee:

1 Like

Yes I know after all TG uses const 63 times :grin:

I agree in principle but I am not the original author, my post was an edit of code posted by another.

BTW let should always be used in preference to var where possible - a pet peeve of mine :grinning:

2 Likes

It’s a moot point, but the code came from guide.html:

If it’s really that critical the guide should be edited.

Sorry about that, the code was mine so if anyone is at fault it was me. :innocent:

When and where to use const versus let is not important in this discussion or in these example sketches. The choice depends on the code context and is outside the scope of this discussion so we should leave it there.

I have taken onboard @GoToLoop’s point about instance mode and have changed the examples to use global mode.

2 Likes

I’m curious why you chose to use offscreen drawing vs. drawing directly on to the user’s canvas?

In Processing and p5js one of the big choices is whether to use background inside the draw() function. If you are creating a single static image over a period of time then you can get away without background. I have seen some lovely images created over multiple frames because the background has not been erased.

The only problem with this it is restrictive especially when trying to do animation. The alternative is to use background() to clear the display but then recreate a new frame image from scratch.

If I did this in TG then I would have to redo all the turtle’s previous tasks before drawing the update. This is too much processing so what I do is store the results of every completed task on an offscreen buffer then on each frame copy this to the main display. Tasks such as forward(100) that are only partly completed i.e. being animated are then drawn on the main screen.

The benefits are

  1. it is a less CPU intensive algorithm
  2. the turtle graphic can be positioned anywhere on the display and even be changed during program exhibition
  3. makes it easier to manipulate multiple turtles independently
  4. more compatible with other libraries that require the display to be cleared each frame. This is the case with all the libraries I have ever used.
2 Likes

This recursion:

Can be replaced with a simple for() loop:

function drawPolygon(sideLength, degrees, numSides) 
  {
  for(let i = 0; i<numSides; i++) 
    {
    forward(sideLength);
    left(radians(degrees));
    }
  }

:)

1 Like