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:

Hi @svan I created an improved version of your code
it’s far from being perfect but it allows to control the basic parameters with a ui and move around with mouse drag and mouse wheel zooming

int h = 600;
final float HR = 1.325;
float cntrX = 0;
float cntrY = 0;
float radius = 0;
String heading = "";
boolean showLines = false;

int _wndW;
int _wndH;

float startX;
float startY;
float initLen;
float lineThickness = 18.0;
int maxIterations = 7;

boolean uiVisible = true;
float uiTabX = 20;
float uiTabY = 20;
float uiTabWidth = 200;
float uiTabHeight = 300;
float uiHeaderHeight = 30;
boolean draggingUI = false;
float dragOffsetX, dragOffsetY;

float sliderHeight = 10;
float sliderMargin = 10;
float sliderX;
float hSliderY;
float lineThicknessSliderY;
float maxIterationSliderY;
float sliderWidth;
float sliderHandleWidth = 16;
boolean sliderDragging = false;
float activeSliderY = -1;
float sliderValueRatio;

color backgroundColor = color(140);
color uiBackgroundColor = color(40);
color uiHeaderColor = color(40);
color textColor = color(255);
color sliderBarColor = color(60);
color sliderHandleColor = color(200);
color checkboxColor = color(40);
color checkboxCheckColor = color(255);

float worldCenterX = 0;
float worldCenterY = 0;
float scale = 1;
float initialScale = 1;
float targetScale = 1;
float targetWorldCenterX = 0;
float targetWorldCenterY = 0;
float lastMouseX, lastMouseY;
boolean isDragging = false;
float zoomSensitivity = 0.15;
float panSensitivity = 1.5;
float lerpSpeed = 0.2;
float scaleMultiplier = 1.0;
float targetScaleMultiplier = 1.0;

void setup() {
  fullScreen();
  _wndW = width;
  _wndH = height;

  background(backgroundColor);
  noFill();

  startX = 0;
  startY = 250;
  initLen = h / HR / HR;

  sliderX = uiTabX + 20;
  hSliderY = uiTabY + uiHeaderHeight + sliderMargin + 30;
  lineThicknessSliderY = hSliderY + sliderHeight + sliderMargin + 30;
  maxIterationSliderY = lineThicknessSliderY + sliderHeight + sliderMargin + 30;

  sliderWidth = uiTabWidth - 40;

  uiTabHeight = maxIterationSliderY + sliderHeight + sliderMargin + 60 - uiTabY;

  targetWorldCenterX = 0;
  targetWorldCenterY = 0;

  worldCenterX = 0;
  worldCenterY = 0;

  initialScale = scale;
}

void draw() {
  background(backgroundColor);

  pushMatrix();
  translate(_wndW / 2, _wndH / 2);
  scale(scaleMultiplier);
  translate(-worldCenterX, -worldCenterY);
  drawFractal();
  popMatrix();

  if (uiVisible) {
    drawUI();
  }

  scaleMultiplier = lerp(scaleMultiplier, targetScaleMultiplier, lerpSpeed);
  worldCenterX = lerp(worldCenterX, targetWorldCenterX, lerpSpeed);
  worldCenterY = lerp(worldCenterY, targetWorldCenterY, lerpSpeed);
}

void mousePressed() {
  if (mouseX < _wndW && mouseY < _wndH && mouseX > 0 && mouseY > 0 && !isMouseOnUI()) {
    isDragging = true;
    lastMouseX = mouseX;
    lastMouseY = mouseY;
  }

  if (mouseX > uiTabX && mouseX < uiTabX + uiTabWidth && mouseY > uiTabY && mouseY < uiTabY + uiHeaderHeight) {

    draggingUI = true;
    dragOffsetX = mouseX - uiTabX;
    dragOffsetY = mouseY - uiTabY;
    return;
  }

  sliderX = uiTabX + 20;
  hSliderY = uiTabY + uiHeaderHeight + sliderMargin + 30;
  lineThicknessSliderY = hSliderY + sliderHeight + sliderMargin + 30;
  maxIterationSliderY = lineThicknessSliderY + sliderHeight + sliderMargin + 30;

  if (isMouseOnSlider(sliderX, hSliderY)) {
    sliderDragging = true;
    activeSliderY = hSliderY;
  } else if (isMouseOnSlider(sliderX, lineThicknessSliderY)) {
    sliderDragging = true;
    activeSliderY = lineThicknessSliderY;
  } else if (isMouseOnSlider(sliderX, maxIterationSliderY)) {
    sliderDragging = true;
    activeSliderY = maxIterationSliderY;
  }

  float checkboxX = sliderX;
  float checkboxY = maxIterationSliderY + sliderHeight + sliderMargin + 20;
  float toggleButtonX = checkboxX + 100;
  float toggleButtonY = checkboxY;
  float toggleButtonWidth = 40;
  float toggleButtonHeight = 20;

  if (mouseX > toggleButtonX && mouseX < toggleButtonX + toggleButtonWidth &&
    mouseY > toggleButtonY && mouseY < toggleButtonY + toggleButtonHeight) {
    showLines = !showLines;
  }
}

boolean isMouseOnSlider(float sliderX, float sliderY) {
  float sliderBarStartX = sliderX;
  float sliderBarEndX = sliderX + sliderWidth;
  return mouseX > sliderBarStartX && mouseX < sliderBarEndX &&
    mouseY > sliderY && mouseY < sliderY + sliderHeight;
}


boolean isMouseOnUI() {
  return mouseX > uiTabX && mouseX < uiTabX + uiTabWidth && mouseY > uiTabY && mouseY < uiTabY + uiTabHeight;
}


void mouseReleased() {
  isDragging = false;
  draggingUI = false;
  sliderDragging = false;
  activeSliderY = -1;
}


void mouseDragged() {
  if (isDragging) {
    float dx = (mouseX - lastMouseX) / scaleMultiplier;
    float dy = (mouseY - lastMouseY) / scaleMultiplier;

    targetWorldCenterX -= dx * panSensitivity;
    targetWorldCenterY -= dy * panSensitivity;

    lastMouseX = mouseX;
    lastMouseY = mouseY;
  }
  if (draggingUI) {
    uiTabX = mouseX - dragOffsetX;
    uiTabY = mouseY - dragOffsetY;
    uiTabX = constrain(uiTabX, 0, width - uiTabWidth);
    uiTabY = constrain(uiTabY, 0, height - uiTabHeight);
  }
  if (sliderDragging) {
    if (activeSliderY == hSliderY) {
      sliderValueRatio = handleSliderInput(hSliderY, 100, 1200, 1, sliderX);
      h = (int)map(sliderValueRatio, 0, 1, 100, 1200);
      initLen = h / HR / HR;
    } else if (activeSliderY == lineThicknessSliderY) {
      sliderValueRatio = handleSliderInput(lineThicknessSliderY, 1, 30, 0.01, sliderX);
      lineThickness = map(sliderValueRatio, 0, 1, 1, 30);
    } else if (activeSliderY == maxIterationSliderY) {
      sliderValueRatio = handleSliderInput(maxIterationSliderY, 1, 200, 1, sliderX);
      maxIterations = (int)map(sliderValueRatio, 0, 1, 1, 200);
    }
  }
}

void mouseWheel(MouseEvent event) {
  float delta = event.getCount();
  float adjustedZoomSensitivity = zoomSensitivity * 0.2 ;
  float zoomChange = delta > 0 ? -adjustedZoomSensitivity : adjustedZoomSensitivity;

  float worldMouseX = (mouseX - _wndW / 2) / scaleMultiplier + worldCenterX;
  float worldMouseY = (mouseY - _wndH / 2) / scaleMultiplier + worldCenterY;


  targetScaleMultiplier = max(initialScale, targetScaleMultiplier + zoomChange);


  float newWorldMouseX = (mouseX - _wndW / 2) / targetScaleMultiplier + worldCenterX;
  float newWorldMouseY = (mouseY - _wndH / 2) / targetScaleMultiplier + worldCenterY;


  targetWorldCenterX -= newWorldMouseX - worldMouseX;
  targetWorldCenterY -= newWorldMouseY - worldMouseY;
}

void drawUI() {
  fill(uiBackgroundColor);
  noStroke();
  rect(uiTabX, uiTabY, uiTabWidth, uiTabHeight, 5);

  fill(uiHeaderColor);
  noStroke();
  rect(uiTabX, uiTabY, uiTabWidth, uiHeaderHeight, 5, 5, 0, 0);

  fill(textColor);
  textSize(16);
  textAlign(CENTER, CENTER);
  text("Settings", uiTabX + uiTabWidth / 2, uiTabY + uiHeaderHeight / 2);

  sliderX = uiTabX + 20;
  hSliderY = uiTabY + uiHeaderHeight + sliderMargin + 30;
  lineThicknessSliderY = hSliderY + sliderHeight + sliderMargin + 30;
  maxIterationSliderY = lineThicknessSliderY + sliderHeight + sliderMargin + 30;


  drawSlider(sliderX, hSliderY, "Height", h, 100, 1200);
  drawSlider(sliderX, lineThicknessSliderY, "Thickness", lineThickness, 1, 30);
  drawSlider(sliderX, maxIterationSliderY, "Iterations", maxIterations, 1, 200);


  drawCheckbox(sliderX, maxIterationSliderY + sliderHeight + sliderMargin + 20);
}

void drawCheckbox(float checkboxX, float checkboxY) {
  float toggleButtonSize = 20;

  fill(textColor);
  textSize(12);
  textAlign(LEFT, CENTER);
  text("Show Lines", checkboxX, checkboxY + toggleButtonSize / 2);


  noStroke();
  fill(showLines ? color(0, 200, 127) : color(60));
  rect(checkboxX + 100, checkboxY, toggleButtonSize * 2, toggleButtonSize, 10);


  fill(255);
  if (showLines) {
    ellipse(checkboxX + 100 + toggleButtonSize * 1.5, checkboxY + toggleButtonSize / 2, toggleButtonSize, toggleButtonSize);
  } else {
    ellipse(checkboxX + 100 + toggleButtonSize / 2, checkboxY + toggleButtonSize / 2, toggleButtonSize, toggleButtonSize);
  }
}

void drawSlider(float sliderX, float sliderY, String label, float value, float minVal, float maxVal) {
  float sliderBarEndX = sliderX + sliderWidth;

  fill(255);
  textSize(12);
  textAlign(CENTER, BOTTOM);
  text(label + ": " + nf(value, 1, 2), sliderX + sliderWidth / 2, sliderY - 5);

  fill(60);
  rect(sliderX, sliderY, sliderWidth, 8, 5);


  float ratio = constrain((value - minVal) / (maxVal - minVal), 0, 1);
  float handleX = sliderX + ratio * sliderWidth;
  fill(200);
  ellipse(handleX, sliderY + 4, 16, 16);
}

float handleSliderInput(float sliderY, float minVal, float maxVal, float step, float sliderX) {
  float sliderBarStartX = sliderX;
  float sliderBarEndX = sliderX + sliderWidth;

  float sliderPosition = constrain(mouseX, sliderBarStartX, sliderBarEndX);
  float sliderRatio = map(sliderPosition, sliderBarStartX, sliderBarEndX, 0, 1);
  float calculatedValue = round(map(sliderRatio, 0, 1, minVal, maxVal) / step) * step;
  return map(calculatedValue, minVal, maxVal, 0, 1);
}

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;
    float xEnd = x + len * cos(radians(angle));
    float yEnd = y + len * sin(radians(angle));
    if ((int) yEnd < (int) y) {
      heading = "SN";
    }
    if ((int) xEnd < (int) x) {
      heading = "EW";
    }
    if ((int) yEnd > (int) y) {
      heading = "NS";
    }
    if ((int) xEnd > (int) x) {
      heading = "WE";
    }
    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);
    }
    if (heading == "EW") {
      cntrX = x - len / 2;
      cntrY = y + len / 2;
      stroke(255, 0, 0);
    }
    if (heading == "NS") {
      cntrX = x + len / 2;
      cntrY = y + len / 2;
      stroke(0, 0, 255);
    }
    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 drawFractal() {
  drawHarriss(startX, startY, -90, initLen, maxIterations, lineThickness);
}

Why it’s limited?

-zooming in gets slow as i zoom in it should be fixed

-the fractals stays thick as i zoom in but it should be responsive so the lines look very thic and smaller parts of the fractal are not visible clearly

-can’t switch between different modes you made like the blue version yet

-no optimization to make it run smooth when zooming in with high iteration count

if you wish you can help improve the code and fix the limitations i found

i will keep improving this but this will gotta be another day because i don’t have time now

waiting to hear from ya!

My only suggestion is to add noFill() down in drawHarriss(); line 331 might be a good spot. This will do away with the white arcs. Personally I wouldn’t worry too much about zooming in; the Harriss spiral is different from a Mandelbrot. If you want a true fractal then I think you would need to do something similar to the Algol 68 contribution over on RosettaCode: https://rosettacode.org/wiki/Harriss_Spiral

2 Likes