Harriss Spiral Using TurtleGraphics With Processing Arcs

The following source code will create a basic Harriss Spiral by using TurtleGraphics with Processing arcs.

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

// Turtle Graphics in Processing
// Natalie Freed, February 2013
// This program shows another way to think about moving
// through Processing's coordinate system. Instead of placing
// points on a grid, you can imagine yourself as being somewhere
// on the grid, facing a direction. You can move forward or turn.
// The drawn line follows behind you.

// Modified to use recursion September 2024
// Forward function modified to draw Processing arcs November 2024

PVector loc; //current location
float orientation; //current orientation

float h = 600; // scalable for different sized Harriss spirals
float w = (h/1.325)/1.325; // initial arc length

int rotation = -45; //initial rotation (outer arcs => inner inner arcs)
boolean showLines;

void harrissSpiral(int iteration) {
  if (iteration > 0) {
    iteration -= 1;
    showLines = false;
    forward(w,iteration); //go forward by w
    left(radians(90)); //turn x degrees to the left
    w = w/1.325;
    harrissSpiral(iteration);
  }
}

void setup() {
  size(700, 700);
  loc = new PVector(width/2 + 100, height/2 + 200); //starting position is near center
  orientation = radians(90); //starting orientation is at 90 degrees
  noLoop(); // Uses recursion
}

void draw() {
  harrissSpiral(7);
}

// ================= Turtle Graphics Functions ======================== //

//calculate positions when moving forward (modified to include arcs)
void forward(float w, int iteration) {
  PVector end = PVector.add(loc, polar(w, orientation));
  if(showLines){
   stroke(0); 
   strokeWeight(1);
   line(loc.x, loc.y, end.x, end.y);
  }
   noFill(); 
   stroke(0,0,255);
   strokeWeight(12);
   switch(iteration) { // 1.414 = sqrt(2)
     case 6: // Outer  
      arc(loc.x - w/2, loc.y - w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
     break;
    case 5:
      arc(loc.x - w/2, loc.y + w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
    break;
    case 4:
     arc(loc.x + w/2, loc.y + w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
    break; 
    case 3:
     arc(loc.x + w/2, loc.y - w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
    break;
    case 2:
     arc(loc.x - w/2, loc.y - w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
     break;
    case 1:
     arc(loc.x - w/2, loc.y + w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
     break;
    case 0: // Inner
     arc(loc.x + w/2, loc.y + w/2, w*1.414,w*1.414,radians(rotation),radians(rotation + 90));
    break; 
   }

  loc = end;
  rotation -= 90; // lines are drawn counter clockwise, arcs are drawn clockwise
}

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

//calculate new orientation
void right(float theta) {
  orientation -= theta;
}

//converts an angle and radius into a vector
PVector polar(float r, float theta) {
  return new PVector(r*cos(theta), r*sin(-theta)); // negate y for left handed coordinate system
}

Output:

Design Pattern with Composite Spirals:

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

// Turtle Graphics in Processing
// Natalie Freed, February 2013

// Modified to use recursion September 2024
// Forward function modified to draw Processing arcs November 2024

PVector loc; //current location
float orientation; //current orientation
float h; // spiral size
float w; // arc length
int rotation;
boolean showLines;

int _wndW = 900; // Don't change these.
int _wndH = 700; // Will have to reposition spirals if you do.

void harrissSpiral(int iteration) {
  if (iteration > 0) {
    iteration -= 1;
    forward(w, iteration); //go forward by w
    left(radians(90)); //turn x degrees to the left
    w = w/1.325;
    harrissSpiral(iteration);
  }
}

void setup() {
  size(_wndW, _wndH);
  showLines = false;
  loc = new PVector(width/2 + 100, height/2 + 250); //starting position
  noLoop(); // Uses recursion
}

void draw() {
  // Base spiral
  h = 600; // scalable for different sized spirals
  w = (h/1.325)/1.325; // initial arc length
  rotation = -45; // initial rotation (outer arcs => inner inner arcs)
  orientation = radians(90); // starting orientation is at 90 degrees
  harrissSpiral(7);
  // Right upper spiral
  jumpTo(555, 265);
  h = 300; 
  w = (h/1.325)/1.325;
  orientation = radians(0); 
  rotation = 45;
  harrissSpiral(8);
  // Left upper spiral
  jumpTo(300, 250);
  h = 250;
  w = (h/1.325)/1.325;
  rotation = -45;
  orientation = radians(90);
  harrissSpiral(7);
  // Left lower spiral
  jumpTo(292, 453);
  h = 200;
  w = (h/1.325)/1.325;
  rotation = 225;
  orientation = radians(180);
  harrissSpiral(6);
  // Center lower spiral
  jumpTo(440, 453);
  h = 175;
  w = (h/1.325)/1.325;
  rotation = 135;
  orientation = radians(-90);
  harrissSpiral(5);
}

// **** Turtle Graphics Functions **** //

//calculate positions when moving forward (modified to include arcs)
void forward(float w, int iteration) {
  PVector end = PVector.add(loc, polar(w, orientation));
  if (showLines) {
    stroke(0);
    strokeWeight(1);
    line(loc.x, loc.y, end.x, end.y);
  }
  noFill();
  //stroke(0, 0, 255);
  stroke(random(255),0,random(255));
  strokeWeight(12);
  // 1.414 = sqrt(2)
  if ((iteration == 7) || (iteration == 3)) {
    arc(loc.x + w/2, loc.y - w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
  }
  if ((iteration == 6) || (iteration == 2)) {
    arc(loc.x - w/2, loc.y - w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
  }
  if ((iteration == 5) || (iteration == 1)) {
    arc(loc.x - w/2, loc.y + w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
  }
  if ((iteration == 4) || (iteration == 0)) {
    arc(loc.x + w/2, loc.y + w/2, w*1.414, w*1.414, radians(rotation), radians(rotation + 90));
  } 
  loc = end;
  rotation -= 90; // lines are drawn counter clockwise, arcs are drawn clockwise 
}

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

//calculate new orientation
void right(float theta) {
  orientation -= theta;
}

//jump directly to a specific position
void jumpTo(int x, int y) {
  loc = new PVector(x, y);
}

//converts a radius and an angle into a vector
PVector polar(float r, float theta) {
  return new PVector(r*cos(theta), r*sin(-theta)); // negate y for left handed coordinate system
}

Output:

3 Likes

The following source code uses the same technique as the original post: https://wiki.secretgeek.net/harriss-spiral. The ‘scaffolding’ is almost a direct translation of the harriss() function using Natalie Freed’s Utility functions. The ‘forward’ function was modified to draw Processing arcs instead of lines and a ‘back’ function was added. I was unable to find the original Logo source code for ‘arc’ which appears to be different than the Processing arc() function. With this technique the turtle headings are used to draw the arcs correctly. There are four possible directions NorthToSouth, SouthToNorth, EastToWest, and WestToEast. The output is very similar to the original.

//https://gist.githubusercontent.com/nataliefreed/8483050/raw/9e3f1d0f44bcb0c872762e4b984358d375e7b5fa/turtle.pde
//https://wiki.secretgeek.net/harriss-spiral

PVector loc; //current location
float rotation = radians(90); //initial rotation 
String heading = "";

void harriss(float len, int limit) {
  if (len > limit) {
    fwd(len);
    rt(radians(90));
    harriss(len/2.325, limit);
    lt(radians(180));
    harriss(len/1.325, limit);
    rt(radians(90));
    back(len);
  }
}

void setup() {
  size(800, 800);
  loc = new PVector(width/2 - 20, height - 300);
  noFill();
  noLoop();
}

void draw() {
  scale(1.3);
  harriss(300, 5);
}

// ======= UTILITY FUNCTIONS ========= //

void fwd(float len) {
  PVector start = loc;
  PVector end = PVector.add(loc, polar(len, rotation));
  
  if ((int)end.y < (int)start.y) {
    heading = "SN";  // seg. 6
  }  
  if ((int)end.x < (int)start.x) {
    heading = "EW";  // seg. 5
  }  
  if ((int)end.y > (int)start.y) {
    heading = "NS";  // seg. 4
  }  
  if ((int)end.x > (int)start.x) {
    heading = "WE";  // seg. 3
  }  

  if (heading == "SN") {
    stroke(255, 255, 0);
    strokeWeight(len/15);
    arc(loc.x - len/2, loc.y - len/2, len*1.414, len*1.414, radians(-45), radians(45));
  }
  if (heading == "EW") {
    stroke(255, 0, 0);
    strokeWeight(len/15);
    arc(loc.x - len/2, loc.y + len/2, len*1.414, len*1.414, radians(-135), radians(-45));
  }
  if (heading == "NS") {
    stroke(0, 0, 255);
    strokeWeight(len/12);
    arc(loc.x + len/2, loc.y + len/2, len*1.414, len*1.414, radians(135), radians(225));
  }
  if (heading == "WE") {
    stroke(0);
    strokeWeight(len/12);
    arc(loc.x + len/2, loc.y - len/2, len*1.414, len*1.414, radians(45), radians(135));
  }
  loc = end;
}

void back(float len) {
  PVector end = PVector.sub(loc, polar(len, rotation));
  loc = end;
}

void lt(float theta) {
  rotation += theta;
}

void rt(float theta) {
  rotation -= theta;
}

void jumpTo(int x, int y) {
  loc = new PVector(x, y);
}

//converts an angle and radius into a vector
PVector polar(float r, float theta) {
  return new PVector(r*cos(theta), r*sin(-theta)); // negate y for left handed coordinate system
}

Output:

1 Like