Arrows pointing in all kinds of directions.
Log in to post a comment.
// Forked from "Double Circle Turnaround" by mcfly
// https://turtletoy.net/turtle/49150890a6
// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);
// Global code will be evaluated once.
const turtle = new Turtle();
// Adjustable variables.
const size = 8.5; // min=1, max=10, step=.1
const grow = -.2; // min=-3, max=3, step=.1
const rows = 15; // min=1, max=50, step=1
const columns = 15; // min=1, max=50, step=1
const maxAngle = 90; // min=0, max=360, step=45
const turns = 1; // min=0, max=5, step=1
const border = 4; // min=1, max=20, step=1
// Adjust some parameters so they fit the screen.
var len = size,
space = 5,
factor = 180 / (Math.max(columns,rows) * (len+space));
len *= factor;
space *= factor;
// Helper variables.
const width = columns * (len+space),
height = rows * (len+space);
// The walk function will be called until it returns false.
function walk(i) {
if(!(function() {
var row = Math.floor(i / columns);
column = i % columns;
if(row >= rows)
return false;
// Position.
turtle.jump(-width/2 + (column+.5) * (len+space),-height/2 + (row+.5) * (len+space));
// Calculate rotation.
let x = column - columns/2,
y = row - rows/2,
angle = Math.atan2(y,x) * 180 / Math.PI,
distance = Math.sqrt(x*x + y*y) / (columns/2),
rotation = Math.max(0,1 - distance) * maxAngle + turns*angle;
// Rotate.
turtle.setheading(rotation);
// Drawn maple leaf.
traceSvgPath("M17.5,10L10,17.5V14H3V6h7V2.5L17.5,10z",10,10,.1*len*(1+grow*Math.max(0,1-distance)));
return true;
})()) {
finish();
return false;
}
return true;
}
// Evaluated once at the end.
function finish() {
// Cut the frame last.
const frameWidth = width + 2 * border,
frameHeight = height + 2 * border;
turtle.jump(-frameWidth/2,-frameHeight/2);
turtle.setheading(0);
turtle.pendown();
turtle.forward(frameWidth);
turtle.right(90);
turtle.forward(frameHeight);
turtle.right(90);
turtle.forward(frameWidth);
turtle.right(90);
turtle.forward(frameHeight);
}
// Trace the provided "d" attribute of an SVG <path> element with the turtle,
// starting at the current position (and heading). An offset and scaling factor
// may be applied (offset first, scale second). Curves will become straight
// lines. The position and heading of the turtle will be preserved, then pen
// will be up.
function traceSvgPath(path,offsetX = 0,offsetY = 0,scale = 1) {
const pos = turtle.pos(),
heading = turtle.heading();
angle = (dx,dy) => Math.atan2(dy,dx) * 180 / Math.PI,
length = (dx,dy) => Math.sqrt(dx*dx + dy*dy);
turtle.penup();
var x = 0,
y = 0,
a = 0,
startX = 0,
startY = 0,
ta;
parseSvgPath(path).forEach((cmd,i) => {
// Transform commands into line commands.
cmd = {
H : ["L",cmd[1],y/(scale || 1) + offsetY],
h : ["l",cmd[1],0],
V : ["L",x/(scale || 1) + offsetX,cmd[1]],
v : ["l",0,cmd[1]],
Z : ["L",startX,startY],
z : ["L",startX,startY],
C : ["L",cmd[5],cmd[6]],
c : ["l",cmd[1]+cmd[3]+cmd[5],cmd[2]+cmd[4]+cmd[6]],
S : ["L",cmd[3],cmd[4]],
s : ["l",cmd[1]+cmd[3],cmd[2]+cmd[4]],
Q : ["L",cmd[3],cmd[4]],
q : ["l",cmd[1]+cmd[3],cmd[2]+cmd[4]],
T : ["L",cmd[1],cmd[2]],
t : ["l",cmd[1],cmd[2]],
A : ["L",cmd[6],cmd[7]],
a : ["l",cmd[6],cmd[7]],
}[cmd[0]] || cmd;
switch(cmd[0]) {
case "M":
case "L":
cmd[1] -= offsetX;
cmd[2] -= offsetY;
cmd[1] *= scale;
cmd[2] *= scale;
ta = angle(cmd[1]-x,cmd[2]-y);
turtle.right(ta - a);
if(cmd[0] === "L")
turtle.pendown();
turtle.forward(length(cmd[1]-x,cmd[2]-y));
if(cmd[0] === "L")
turtle.penup();
x = cmd[1];
y = cmd[2];
a = ta;
break;
case "m":
case "l":
cmd[1] *= scale;
cmd[2] *= scale;
ta = angle(cmd[1],cmd[2]);
turtle.right(ta - a);
if(cmd[0] === "l")
turtle.pendown();
turtle.forward(length(cmd[1],cmd[2]));
if(cmd[0] === "l")
turtle.penup();
x += cmd[1];
y += cmd[2];
a = ta;
break;
}
if(!i) {
startX = x/(scale || 1) + offsetX;
startY = y/(scale || 1) + offsetY;
}
});
turtle.jump(pos);
turtle.setheading(heading);
}
// Taken from https://github.com/jkroso/parse-svg-path and adjusted somewhat.
const cmdLength = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0};
function parseSvgPath(path) {
var data = [];
path.replace(/([astvzqmhlc])([^astvzqmhlc]*)/ig, function(_, command, args){
var type = command.toLowerCase();
args = (args.match(/-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig) || []).map(Number);
// overloaded moveTo
if(type == 'm' && args.length > 2) {
data.push([command].concat(args.splice(0, 2)));
type = 'l';
command = command == 'm' ? 'l' : 'L';
}
while(true) {
if(args.length == cmdLength[type]) {
args.unshift(command);
return data.push(args);
}
if(args.length < cmdLength[type]) throw new Error('malformed path data')
data.push([command].concat(args.splice(0, cmdLength[type])));
}
});
return data;
}