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.
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.
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
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
document the setup / installation process to enable using the library
more documentation on the basic syntax of using TG for the first time user
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.
finally I need to create a hires image for the library when I submit it - for me that will be the hardest part LOL.
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
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:
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));
}
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
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);
}
Sorry about that, the code was mine so if anyone is at fault it was me.
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.
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
it is a less CPU intensive algorithm
the turtle graphic can be positioned anywhere on the display and even be changed during program exhibition
makes it easier to manipulate multiple turtles independently
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.