Unicorns

Spinning unicorn heads.

Log in to post a comment.

// Forked from "Spinning Arrows" by mcfly
// https://turtletoy.net/turtle/53ac464c88

// 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 = 1.8; // min=1, max=10, step=.1
const grow = 1.1; // min=-3, max=3, step=.1
const rows = 10; // min=1, max=50, step=1
const columns = 10; // 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 = 2; // 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 SVG.
        let unicorn = "M6708 5746 c575 -68 1120 -205 1727 -433 211 -80 332 -130 429 -180 l59 -30 -36 -22 c-125 -77 -349 -428 -560 -876 -119 -255 -141 -305 -134 -311 3 -3 65 19 137 50 231 99 479 174 700 211 130 22 384 30 492 16 171 -23 338 -82 451 -160 47 -32 141 -121 134 -127 -2 -2 -43 -15 -92 -29 -250 -73 -426 -227 -505 -443 -64 -175 -65 -188 -66 -737 -1 -276 -4 -539 -8 -585 -19 -225 -80 -432 -172 -591 -40 -69 -155 -213 -182 -228 -9 -5 -13 -13 -9 -18 12 -11 144 59 212 114 218 177 382 540 402 891 3 56 10 102 14 102 16 0 79 -128 113 -230 46 -139 60 -244 53 -394 -9 -191 -47 -337 -137 -521 -140 -288 -368 -521 -691 -710 l-34 -19 117 -8 c251 -17 550 -112 887 -283 166 -84 257 -139 386 -236 103 -76 208 -182 185 -186 -8 -1 -89 -7 -180 -13 -606 -39 -954 -151 -1127 -363 -66 -80 -107 -175 -166 -382 -65 -228 -92 -295 -163 -401 -110 -166 -289 -293 -556 -394 -49 -19 -88 -37 -88 -41 0 -4 28 -10 63 -14 346 -38 579 -149 677 -320 28 -49 67 -191 56 -202 -3 -3 -43 11 -89 31 -153 66 -222 79 -402 80 -141 0 -173 -3 -267 -27 -59 -14 -148 -42 -197 -61 -157 -61 -451 -215 -451 -236 0 -13 -2 -13 82 10 292 78 528 31 713 -142 36 -33 65 -65 65 -71 0 -6 -21 -8 -57 -4 -78 8 -240 -8 -330 -33 -95 -26 -216 -87 -290 -146 -32 -26 -110 -97 -173 -158 -254 -247 -385 -324 -626 -373 -92 -18 -418 -25 -476 -10 -45 12 -32 -5 39 -53 85 -57 244 -214 307 -303 108 -153 174 -346 163 -476 -6 -68 -14 -101 -26 -101 -3 0 -26 31 -51 68 -61 91 -191 219 -270 265 -106 61 -154 72 -320 71 -135 0 -158 -3 -345 -46 -292 -67 -333 -73 -505 -72 -170 1 -249 15 -410 73 -46 17 -85 31 -87 31 -9 0 0 -22 28 -63 53 -80 151 -140 353 -216 161 -61 224 -97 258 -151 26 -40 29 -52 25 -105 -2 -33 -9 -73 -16 -89 l-12 -30 -18 30 c-27 44 -61 71 -111 90 -38 14 -90 17 -335 19 -321 2 -388 11 -523 67 -184 76 -313 225 -426 491 -9 23 -96 -86 -142 -178 -67 -137 -79 -194 -78 -370 1 -114 7 -169 22 -230 11 -44 19 -81 18 -82 -7 -7 -120 34 -187 68 -169 85 -309 231 -407 425 -65 129 -130 355 -148 512 -3 34 -9 62 -13 62 -3 0 -19 -26 -35 -58 -18 -34 -62 -89 -110 -137 -90 -89 -183 -144 -341 -201 l-89 -32 -27 -67 c-75 -184 -199 -299 -399 -369 l-91 -31 -23 -65 c-69 -196 -187 -327 -357 -396 -67 -26 -69 -28 -123 -118 -68 -112 -169 -240 -239 -301 -112 -100 -251 -176 -347 -191 l-41 -7 7 54 c37 297 178 522 432 689 77 51 81 56 87 99 12 77 65 208 117 289 58 88 143 174 245 247 67 48 70 52 82 108 15 73 68 175 128 245 63 74 185 169 278 218 49 25 77 46 79 59 49 249 120 384 246 466 l25 16 -45 8 c-314 50 -602 167 -672 273 -39 59 -9 129 77 182 63 39 56 74 -49 225 -84 122 -118 189 -114 228 l3 31 60 -54 c125 -111 379 -249 491 -267 l36 -6 -40 65 c-159 259 -237 465 -312 818 -23 107 -58 244 -77 304 -184 572 -537 1034 -1194 1561 -440 353 -571 494 -586 628 -5 40 13 160 73 487 58 321 132 540 201 605 15 14 50 33 78 42 45 16 93 18 486 16 420 -2 439 -3 480 -23 106 -50 177 -155 254 -373 24 -68 34 -82 72 -108 164 -113 332 -132 808 -89 409 37 605 15 850 -94 87 -39 238 -144 328 -226 42 -39 77 -67 77 -62 0 5 -11 54 -25 108 -74 293 -241 570 -670 1104 -424 527 -530 668 -702 925 -390 585 -595 1120 -653 1704 -14 144 -13 271 2 271 7 0 114 -16 238 -35 853 -131 1551 -156 2005 -69 127 24 253 63 352 109 158 74 236 139 322 270 107 164 275 291 490 372 97 36 283 79 401 92 58 6 121 13 140 15 78 9 514 -4 638 -18z";
        traceSvgPath(unicorn,5000,-3000,.00008*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;
}