Harriss Spiral Techniques

The following source code is one technique of drawing a basic Harriss spiral using ninety degree Processing arcs. Each successive arc is the previous arc’s width/height divided by 1.325 going from outer to inner. The colored circles show the center point of its respective arc. There are other methods of drawing an arc, none better than Logo’s Turtle that I have been able to find so far. This demo builds the spiral manually without the aid of a loop or recursion.

void setup() {
  size(600, 600);
  // ======== 1 ========= //
  pushMatrix();
  translate(312, 370);
  noFill();
  strokeWeight(16);
  arc(0, 0, 243, 243, radians(-45), radians(45));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("1", -2, 4);
  popMatrix();
  // ======== 2 ======== //
  pushMatrix();
  translate(334, 350);
  noFill();
  strokeWeight(14);
  stroke(11,123,54);
  arc(0, 0, 183, 183, radians(225), radians(315));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("2", -2, 4);
  popMatrix();
  // ======== 3 ======= //
  pushMatrix();
  translate(319, 333);
  noFill();
  strokeWeight(12);
  stroke(0,0,254);
  arc(0, 0, 138, 138, radians(135), radians(225));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("3", -2, 4);
  popMatrix();
  // ======== 4 ======= //
  pushMatrix();
  translate(308, 345);
  noFill();
  strokeWeight(10);
  stroke(234,193,54);
  arc(0, 0, 104, 104, radians(45), radians(135));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("4", -2, 4);
  popMatrix();
  // ======== 5 ======= //
  pushMatrix();
  translate(316, 355);
  noFill();
  strokeWeight(8);
  stroke(179,23,254);
  arc(0, 0, 79, 79, radians(-45), radians(45));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("5", -2, 4);
  popMatrix();
  // ======= 6 ======== //
  pushMatrix();
  translate(322, 349);
  noFill();
  strokeWeight(6);
  stroke(221,223,54);
  arc(0, 0, 60, 60, radians(225), radians(315));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("6", -2, 4);
  popMatrix();
  // ======== 7 ======= //
  pushMatrix();
  translate(317, 342);
  noFill();
  strokeWeight(4);
  stroke(255,0,54);
  arc(0, 0, 45, 45, radians(135), radians(225));
  strokeWeight(1);
  circle(0, 0, 10);
  fill(0);
  text("7", -2, 4);
  popMatrix();
}

Output:

Technique 2:
The following technique uses recursion and an ArrayList of PVectors with a custom arc drawing method to create a Harriss spiral.

ArrayList<PVector> vectors;

int _wndW = 800;
int _wndH = 800;

float x1 = 0;
float y1 = 0;

void drawArc(float x, float y, float radius, int rotation, float penSize, int iteration) {
  float angle = 0.0;

  rotation += 90;
  if (rotation > 315) {
    rotation = 45;
  }
  radius = radius*1.325;

  if (iteration > 0) {
    iteration -= 1;
    PVector v = vectors.get(iteration);
    println("[" + iteration + "]" + v.x +" : " + v.y);
    pushMatrix();
    translate(v.x, v.y);
    rotate(radians(rotation));
    println("radius = " + radius);
    println("rotation = ", rotation);
    println("====================");
    for ( int i = 0; i < 90; i++ ) {
      angle = radians( i );
      x = cos( angle ) * radius;
      y = sin( angle ) * radius;
      stroke(random(255),random(255),random(255));
      strokeWeight(penSize);
      if ((x>0)&&(y>0)) {
        line(x, y, x1, y1);
      }
      x1 = x;
      y1 = y;
    }
    popMatrix();
    drawArc(x, y, radius, rotation, penSize, iteration);
  }
}

void setup() {
  size(_wndW, _wndH);
  background(209);
  vectors = new ArrayList<PVector>();
  vectors.add(new PVector(290, 430)); // 0 Outside
  vectors.add(new PVector(334, 390)); // 1
  vectors.add(new PVector(305, 355)); // 2
  vectors.add(new PVector(278, 376)); // 3
  vectors.add(new PVector(293, 398)); // 4
  vectors.add(new PVector(309, 387)); // 5
  vectors.add(new PVector(301, 375)); // 6 Inside
  for (int i = 0; i < vectors.size(); i++) {
    PVector v = vectors.get(i);
    println("[" + i + "]" + v.x +" : " + v.y);
  }
  println(" ================== ");
  noLoop(); // uses recursion
}

void draw() {
  //draws from inside out
  drawArc(291, 430, 34, 45, 15, 7); // initial x,y values irrelevant; vectors start with 6
}

Output:

Technique 3:

// Harriss rectangle is subdivided into squares.
// Each square's dimension is equal to the preceding dimension divided by 1.325
// Center point of each arc is calculated using Pythagorean Theorem

class Square {
  float x, y, w;

  Square(float xpos, float ypos, float dimension) {
    x = xpos;
    y = ypos;
    w = dimension;
  }

  void display() {
    noFill();
    stroke(0);
    rect(x, y, w, w);
  }
}

Square s;

ArrayList<Square> squares;

int _wndH = 600;
float w = _wndH*1.325; // Harriss rectangle to start
int _wndW = (int)w;

int startDegrees = 0;
int stopDegrees = 0;
float cntrX = 0;
float cntrY = 0;

void drawArc(int id) {
  s = squares.get(id);
  float radius = 0.7*s.w;
  println("radius =", radius);
  float d = sqrt((radius*radius)-(s.w/2*s.w/2));
  println("d =", d);
  noFill();
  stroke(0);
  strokeWeight(10);
  switch(id) {
  case 0:
    startDegrees = -45;
    stopDegrees = 45;
    cntrX = s.x - d;
    cntrY = s.y + s.w/2;
    break;
  case 1:
    startDegrees = 225;
    stopDegrees = 315;
    cntrX = s.x + s.w/2;
    cntrY = s.y + s.w + d;
    break;
  case 2:
    startDegrees = 135;
    stopDegrees = 225;
    cntrX = s.x + s.w + d;
    cntrY = s.y + s.w/2;    
    break;
  case 3:
    startDegrees = 45;
    stopDegrees = 135;
    cntrX = s.x + s.w/2;
    cntrY = s.y - d;
    break;
  case 4:
    startDegrees = -45;
    stopDegrees = 45;
    cntrX = s.x - d;
    cntrY =  s.y + s.w/2;;
    break;
  case 5:
    startDegrees = 225;
    stopDegrees = 315;
    cntrX = s.x + s.w/2;
    cntrY = s.y + s.w + d;
    break;
  case 6:
    startDegrees = 135;
    stopDegrees = 225;
    cntrX = s.x + s.w + d;
    cntrY = s.y + s.w/2;   
    break;
  }
  arc(cntrX, cntrY, 2*radius, 2*radius, radians(startDegrees), radians(stopDegrees));
  strokeWeight(1);
  circle(cntrX, cntrY, 13);
  textSize(12);
  text(str(id),cntrX-3, cntrY+4);
}

void setup() {
  size(_wndW, _wndH);
  background(209);
  squares = new ArrayList<Square>();
  squares.add(new Square(452.83017, 258.24136, 342.16983));
  squares.add(new Square(194.6649, 0.0, 258.16528));
  squares.add(new Square(0.0, 258.16528, 194.6649));
  squares.add(new Square(194.6649, 452.8302, 146.9169));
  squares.add(new Square(341.5818, 341.94952, 111.0141));
  squares.add(new Square(257.82227, 258.16528, 83.75952));
  squares.add(new Square(194.6649, 341.9248, 63.157375));
  noLoop();
}

void draw() {
  for (int i = 0; i < squares.size(); i++) {
    Square s = squares.get(i);
    s.display();
    textSize(16);
    fill(0);
    text(str(squares.indexOf(s)), s.x + 32, s.y + 16);
    drawArc(i);
  }
}

Output:

Technique 4:

This is the most automated of all the techniques shown so far. All that the user has to provide is the window height and an initial arc rotation and the source code will do the rest.

/*
 Uses an ArrayList of Square class squares
 Arcs are derived from squares, each decreased by a factor of 1.325 (outer -> inner)
 Ninety degree arcs are drawn clockwise using recursion (inner -> outer)
 Arc centers are calculated with Pythagorean Theorem
*/

int h = 600;  // Scalable (may be any value)
float wndW = h*1.325; // Start with Harriss rectangle based on window height

int _wndW = (int)wndW;
int _wndH = h;

float l = _wndH/1.325;
float w = l/1.325;
float t = w/1.325;

float x = 0;
float y = 0;

int rotation = 45; // Initial rotation
float angle = 0.0;
float cntrX = 0;
float cntrY = 0;

class Square {
  float x, y, w;

  Square(float xpos, float ypos, float dimension) {
    x = xpos;
    y = ypos;
    w = dimension;
  }

  void display() {
    noFill();
    stroke(0);
    strokeWeight(1);
    rect(x, y, w, w);
  }
}

Square s;

ArrayList<Square> squares;

void drawMyArc(int iteration) {
  if (iteration > 0) {
    iteration -= 1;
    println("id =", iteration);
    s = squares.get(iteration);
    float radius = 0.7*s.w;
    println("radius =", radius);
    float d = sqrt((radius*radius)-(s.w/2*s.w/2));
    println("d =", d);
    rotation += 90;
    if (rotation > 315) {
      rotation = 45;
    }
    println("radius = " + radius);
    println("rotation = ", rotation);
    switch(iteration) {
    case 0:
      cntrX = s.x - d;
      cntrY = s.y + s.w/2;
      break;
    case 1:
      cntrX = s.x + s.w/2;
      cntrY = s.y + s.w + d;
      break;
    case 2:
      cntrX = s.x + s.w + d;
      cntrY = s.y + s.w/2;
      break;
    case 3:
      cntrX = s.x + s.w/2;
      cntrY = s.y - d;
      break;
    case 4:
      cntrX = s.x - d;
      cntrY =  s.y + s.w/2;
      break;
    case 5:
      cntrX = s.x + s.w/2;
      cntrY = s.y + s.w + d;
      break;
    case 6:
      cntrX = s.x + s.w + d;
      cntrY = s.y + s.w/2;
      break;
    }
    println("cntrX = " + cntrX + " : cntrY = " + cntrY);
    println("====================");
    pushMatrix();
    translate(cntrX, cntrY);
    rotate(radians(rotation));
    for ( int i = 0; i < 90; i++ ) {
      angle = radians( i );
      x = cos( angle ) * radius;
      y = sin( angle ) * radius;
      stroke(0, 0, 255);
      strokeWeight(15);
      point(x, y);
    }
    popMatrix();
    drawMyArc(iteration); // recursion
  }
}

void setup() {
  size(_wndW, _wndH);
  background(255);
  squares = new ArrayList<Square>();
  squares.add(new Square(l, t, w));
  w = w/1.325;
  squares.add(new Square(l-w, t-w, w));
  w = w/1.325;
  squares.add(new Square(0.0, t, w));
  w = w/1.325;
  squares.add(new Square(w*1.325, t+w*1.325, w));
  w = w/1.325;
  squares.add(new Square((w*1.325)+1.325*(w*1.325), t+w*1.325*1.325-w, w));
  w = w/1.325;
  squares.add(new Square((w*1.325)+1.325*(w*1.325), t, w));
  w = w/1.325;
  squares.add(new Square((w*1.325)+1.325*(w*1.325), t+w*1.325, w));
  noLoop();
}

void draw() {
  drawMyArc(squares.size());
  /*
  for(int i = 0; i < squares.size(); i++) {
   s = squares.get(i);
   s.display();
  }
  */
}

Output:

Technique 5:

The following demo uses Harriss and Line classes to create a design composed of five Harriss spirals. The Harriss class is scalable to create larger or smaller spiral patterns. It uses eight line segments stored in an Array List to provide the scaffolding for ninety degree arcs which are then aligned to form the Harriss spiral. Each line is divided by a factor of 1.325 to give successively shorter arcs. Configuration variability is achieved by skipping arcs on either end of the base spiral in a ‘for’ loop. Increasing the minimum value of the loop will decrease the number of outer spiral arc segments (drawn first) and decreasing the maximum value of the loop will decrease the number of inner spiral segments (drawn last). [See image below.] Spirals are drawn in a counter clockwise direction. Translate() is then used to align the composite spirals to create a design pattern. Line thickness and arc color variables may be added to each arc for added variation.

int _wndW = 720;
int _wndH = 650;

Harriss hsp;

void setup() {
  size(_wndW, _wndH);
  background(209);
  // **** main spiral **** //
  hsp = new Harriss(600); 
  for (int i = 1; i < 8; i++) {
    hsp.drawMyArc(i);
  }
  // **** right upper spiral **** //
  hsp = new Harriss(300);
  pushMatrix();
  translate(390,-50);
  for (int i = 0; i < 6; i++) {
    hsp.drawMyArc(i);
  }
  popMatrix();
  // **** left upper spiral **** //
  pushMatrix();
  translate(0,-68);
  for (int i = 1; i < 6; i++) {
    hsp.drawMyArc(i);
  }
  popMatrix();
  // **** left lower spiral **** //
  pushMatrix();
  translate(-20,335);
  for (int i = 2; i < 6; i++) {
    hsp.drawMyArc(i);
  }
  popMatrix();  
  // **** mid lower spiral **** //
  pushMatrix();
  translate(240,330);
  for (int i = 3; i < 6; i++) {
    hsp.drawMyArc(i);
  }
  popMatrix();
}

void draw() {
}

Harriss Class (separate Tab):

class Harriss {
  
  float x,y;
  float cntrX = 0;
  float cntrY = 0;
  float radius = 0;
  float d = 0;
  int rotation;
  float angle = 0.0;
  int iteration = 0;

  ArrayList<Line> lines;
  
  // Scalable by changing the height parameter
  Harriss(int ht) {
    
    float h = ht;
    float x = h/1.325;
    float w = x/1.325;
    float y = w/1.325;

    lines = new ArrayList<Line>();
    lines.add( new Line(x - w, y + w, x, y+ w, w)); // 0 Outer Spiral
    lines.add( new Line(x, y + w, x, y, w)); // 1
    w = w/1.325;
    Line ln1 = lines.get(1);
    lines.add(new Line(ln1.x2, ln1.y2, ln1.x1 - w, ln1.y2, w)); // 2
    w = w/1.325;
    Line ln2 = lines.get(2);
    lines.add(new Line(ln2.x2, ln2.y2, ln2.x2, ln2.w + w, w));  // 3
    w = w/1.325;
    Line ln3 = lines.get(3);
    lines.add(new Line(ln3.x2, ln3.y2, ln3.x2 + w, ln3.y2, w)); // 4
    w = w/1.325;
    Line ln4 = lines.get(4);
    lines.add(new Line(ln4.x2, ln4.y2, ln4.x2, ln4.y2 - w, w)); // 5
    w = w/1.325;
    Line ln5 = lines.get(5);
    lines.add(new Line(ln5.x2, ln5.y2, ln5.x2 - w, ln5.x2, w)); // 6
    w = w/1.325;
    Line ln6 = lines.get(6);
    lines.add(new Line(ln6.x2, ln6.y2, ln6.x2, ln6.y2 + w, w)); // 7 Inner Spiral
  }

  void drawMyArc(int id) {
    ln = lines.get(id);
    radius = 0.7*ln.w;
    d = 0.7*radius;
    switch(id) {
    case 0:
      cntrX = ln.x1 + ln.w/2;
      cntrY = ln.y2 - d;
      rotation = 45;
      break;
    case 1:
      cntrX = ln.x1 - d;
      cntrY = ln.y1 - ln.w/2;
      rotation = 315;
      break;
    case 2:
      cntrX = ln.x1 - ln.w/2;
      cntrY = ln.y1 + d;
      rotation = 225;
      break;
    case 3:
      cntrX = ln.x1 + d;
      cntrY = ln.y1 + ln.w/2;
      rotation = 135;
      break;
    case 4:
      cntrX = ln.x1 + ln.w/2;
      cntrY = ln.y2 - d;
      rotation = 45;
      break;
    case 5:
      cntrX = ln.x2 - d;
      cntrY = ln.y1 - ln.w/2;
      rotation = 315;
      break;
    case 6:
      cntrX = ln.x2 + ln.w/2;
      cntrY = ln.y1 + d;
      rotation = 225;
      break;
    case 7:
      cntrX = ln.x2 + ln.w/2;
      cntrY = ln.y1 + d;
      rotation = 135;
      break;
    }
    println("id =", id);
    println("x1 = " + ln.x1 + " : y1 = " + ln.y1 +" : x2 = " + ln.x2 + " : y2 = " + ln.y2);
    println("w = ", ln.w);
    println("radius =", radius);
    println("d =", d);
    println("rotation =", rotation);
    println("cntrX = " + cntrX +" : cntrY = " + cntrY);
    println("================");
    pushMatrix();
    translate(cntrX, cntrY);
    rotate(radians(rotation));
    for ( int i = 0; i < 90; i++ ) {
      angle = radians( i );
      x = cos( angle ) * radius;
      y = sin( angle ) * radius;
      stroke(0, 0, 255);
      strokeWeight(12);
      point(x, y);
    }
    popMatrix();
    rotation -= 90;  // Draws arcs counter clockwise - Outer => Inner
  }

  void displayLines() {
    for (int i = 0; i < lines.size(); i++) {
      Line ln = lines.get(i);
      ln.display();
    }
  }
}

Line Class (separate Tab):

class Line {
  float x1, y1, x2, y2, w;

  // Constructor
  Line(float x1Pos, float y1Pos, float x2Pos, float y2Pos, float len) {
    x1 = x1Pos;
    y1 = y1Pos;
    x2 = x2Pos;
    y2 = y2Pos;
    w = len;
  }

  void display() {
    stroke(0);
    strokeWeight(1);
    line(x1, y1, x2, y2);
  }
}

Line ln;

Changing Spiral Configuration example:

Output:

8 Likes

Technique 6 Latest Iteration:
This version does not require a separate class and the long switch(…) construct was replaced by using a heading scheme, inspired by TurtleGraphics. The four headings are: NorthToSouth(NS), SouthToNorth(SN), EastToWest(EW), and WestToEast(WE); cntrX/Y values are then calculated for each heading. The source code for a more extensive Harriss Spiral design is shown below:

int h = 600; // scalable (may be any value)

final float HR = 1.325; // Harriss Ratio

int _wndW = 1000;
int _wndH = 800;

float cntrX = 0;
float cntrY = 0;
float radius = 0;
String heading = "";
boolean showLines = false;

void drawHarriss(float x, float y, float angle, float len, int iteration, float lineW) {
  if (iteration > 0) {
    float startAngle = angle + 45;
    float endAngle = startAngle + 90;
    // Calculate end point of lines
    float xEnd = x + len * cos(radians(angle));
    float yEnd = y + len * sin(radians(angle));
    if ((int)yEnd < (int)y) {
      heading = "SN";
    }  //6
    if ((int)xEnd < (int)x) {
      heading = "EW" ;
    } //5
    if ((int)yEnd > (int)y) {
      heading = "NS";
    }  //4
    if ((int)xEnd > (int)x) {
      heading = "WE";
    }  //3
    if (showLines) {
      stroke(0);
      strokeWeight(1);
      line(x, y, xEnd, yEnd);
    }
    radius = 1.414*len;
    if (heading == "SN") {
      cntrX = x - len/2;
      cntrY = y - len/2;
      stroke(255, 255, 0);
      strokeWeight(lineW);
    }
    if (heading == "EW") {
      cntrX = x - len/2;
      cntrY = y + len/2;
      stroke(255, 0, 0);
      strokeWeight(lineW);
    }
    if (heading == "NS") {
      cntrX = x + len/2;
      cntrY = y + len/2;
      stroke(0, 0, 255);
      strokeWeight(lineW);
    }
    if (heading == "WE") {
      cntrX = x + len/2;
      cntrY = y - len/2;
      stroke(0);
      strokeWeight(lineW);
    }
    arc(cntrX, cntrY, radius, radius, radians(startAngle), radians(endAngle), OPEN);
    drawHarriss( xEnd, yEnd, angle - 90, len/HR, iteration - 1, lineW);
  }
}

void setup() {
  size(_wndW, _wndH);
  background(140);
  noFill();
  float startX = _wndW/2 + 50;
  float startY = _wndH - 50;
  float initLen = h/HR/HR; 
  // Reverse Order Hides Joints
  drawHarriss(startX - (initLen/HR + initLen/HR/HR/HR), startY - initLen/HR/HR/HR/HR/HR/HR/HR/HR, 180, initLen/HR/HR/HR/HR/HR/HR, 2, 6.0); // level 3
  drawHarriss(startX - (initLen/HR + initLen/HR/HR/HR), startY - (initLen+initLen/HR/HR), 270, initLen/HR/HR/HR/HR/HR, 3, 6.0); // level 3
  drawHarriss(startX + initLen/HR/HR/HR/HR, startY - (initLen + initLen/HR/HR), 270, initLen/HR/HR/HR/HR/HR, 3, 6.0); // level 3
  drawHarriss(startX + initLen/HR, startY - (initLen + initLen/HR/HR), 0, initLen/HR/HR/HR/HR, 4, 6.0); // level 3 rt-upper
  drawHarriss(startX - initLen/HR/HR/HR/HR, startY - initLen/HR, 0, initLen/HR/HR/HR/HR/HR, 2, 10.0); // level 2 mid-upper
  drawHarriss(startX - initLen/HR/HR/HR/HR, startY - initLen/HR/HR/HR, -270, initLen/HR/HR/HR/HR, 3, 12.0); // level 2 mid-lower
  drawHarriss(startX - initLen/HR, startY - initLen/HR/HR/HR, 180, initLen/HR/HR/HR, 4, 12.0); // level 2 lt-lower
  drawHarriss(startX - initLen/HR, startY - initLen, 270, initLen/HR/HR, 5, 14.0); // level 2 lt-upper
  drawHarriss(startX, startY - initLen, 0, initLen/HR, 6, 14.0); // level 2 rt-upper
  drawHarriss(startX, startY, -90, initLen, 7, 18.0); // level 1 base spiral
}

void draw() {
}

Output:

4 Likes

Amazing! thank you so much for this! i was asking for this back then and nobody knew what to do!
i might learn from this to create a program if that’s ok by you.

You’re welcome to use it however you want. Actually your post was the first time I ever heard of the Harriss spiral and is what inspired me to dig deeper. Only a few examples were available and not in any language that I’m fluent in so I decided to create my own example in Processing. It has taken all this time since your post to get to this point and there is still more to be done, but it has been an amazing and fun journey. Thanks for bringing Harriss Spiral to the forum.

1 Like

I am so glad to hear you did all that journey! and thank you so much for doing all this effort and making a working harris fractal code. i can’t wait to try it as soon as i get the time ! have a great week! (:100: