Recursive Sierpinski triangle (just a bit of fun)

What fun!

A while ago I wrote a small recursion library for animated recursion.

It has an animated recursive serpinski triangle demo:

export-714

4 Likes

Ahh I was just searching for Sierpinski triangle (simple).
So, while there I just posted this as well. Very simple.

void setup() {
  size(410, 230);
  background(255);
  fill(0);
  triangle (10, 25, 100, 5);
}
 
void triangle (int x, int y, int l, int n) {
    if( n == 0) text("*", x, y);
    else {
        triangle(x, y+l, l/2, n-1);
        triangle(x+l, y, l/2, n-1);
        triangle(x+l*2, y+l, l/2, n-1);
    }
}

sp

4 Likes

Nice one!

(>=20 character posts)

Just…

Odd. It’s just that it complained my post was too short.

Anyway, my tetrahedron wasn’t quite symmetrical it turns out. The top was a bit off :flushed:

Brushed up a bit on geometry, hoping this is better:

// 3D Sierpinski tetrahedron vertices
PVector [] coord = {
  new PVector(   0, 0, 0), 
  new PVector( 300, 0, 0), 
  new PVector( 150, 0, -260), 
  new PVector( 150, -245, -86.6)
};

// "random" start point (mid point)
PVector startPoint = new PVector(150, 183.7, 173.2);

Tried to keep startPoint in the middle, which it now should be (after translating it into view).

Yes, nice.
And thanks for posting on the Rosetta page. Jeremy already said that it was a good idea to post the strict version and an animated && || 3d as well. So maybe you want to do that?
b.t.w I meant keep typing points and at the end only three will be shown.

Curious about Jeremy’s animated version I found no jar in the lib, but here is an abstract of the code.

/**
 * RecursorTriangle
 * 2020-03-20 Jeremy Douglass - Processing 3.4
 * Progressively animate a recursive Sierpinski Triangle.
 * Press space to restart.
 */

import java.util.Random;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;

void setup() {
  size(600, 550);
  smooth();
  noStroke();
  colorMode(HSB, 360, 100, 100);
  // seed
  rq.add(new CallST(0, 450, 400, 0, 500, 550, 8));
  background(0, 0, 40);
}

void draw() {
  rq.stepUntil(3, 0);
}

void keyReleased() {  
  if (key==' ') {  // reset
    rq.clear();
    frameCount=-1;
  }
}

RecursorQueue rq = new RecursorQueue<CallST>() {
  public ArrayList<CallST> recurse(CallST call) {
    return call.recurse();
  }
};

class CallST {
  float x1, y1, x2, y2, x3, y3;
  int n;
  
  CallST(float x1, float y1, float x2, 
    float y2, float x3, float y3, int n) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.x3 = x3;
    this.y3 = y3;
    this.n = n;
  }
  
  ArrayList<CallST> recurse() {
    ArrayList<CallST> calls = new ArrayList<CallST>();
    if ( n > 0 ) {
      int h = (110+n*(360/8))%360;
      fill(h, 100, 100);
      triangle(x1, y1, x2, y2, x3, y3);
      // calculate segment midpoints
      float h1 = (x1+x2)/2.0;
      float w1 = (y1+y2)/2.0;
      float h2 = (x2+x3)/2.0;
      float w2 = (y2+y3)/2.0;
      float h3 = (x3+x1)/2.0;
      float w3 = (y3+y1)/2.0;
      // call three subtriangles at corners
      calls.add(new CallST(x1, y1, h1, w1, h3, w3, n-1));
      calls.add(new CallST(h1, w1, x2, y2, h2, w2, n-1));
      calls.add(new CallST(h3, w3, h2, w2, x3, y3, n-1));
    }
    return calls;
  }
}

public enum Mode {
  FIRST, LAST, EITHER;
  
  private static final Mode[] VALUES = values();
  private static final Random RANDOM = new Random();
  /**
   * @return Returns either FIRST or LAST, randomly.
   */
  public static Mode either() {
    return VALUES[RANDOM.nextInt(2)];
  }
  /**
   * @return Returns any one of FIRST, LAST, EITHER.
   */
  public static Mode random() {
    return VALUES[RANDOM.nextInt(VALUES.length)];
  }
}

public class RecursorQueue<E> extends LinkedList<E> {
  private static final long serialVersionUID = -3584631587915680024L;
  /** Running count of calls added to the queue since initialize or reset(). */
  private int adds;
  /** Running count of calls popped off the queue since initialize or reset(). */
  private int pops;
  private int addsLast;
  private int popsLast;
  /** Mode for add operations: FIRST, LAST, EITHER. */
  public Mode addMode;
  /** Mode for pop operations: FIRST, LAST, EITHER. */
  public Mode popMode;
  
   public RecursorQueue() {
    super();
    this.popMode = Mode.FIRST;
    this.addMode = Mode.LAST;
  }
  
  public RecursorQueue( Mode addMode, Mode popMode) {
    super();
    this.addMode = addMode;
    this.popMode = popMode;
  }
  
  public ArrayList<E> recurse(E call) {
    ArrayList<E> result = new ArrayList<E>();
    // result.add(call);
    return result;
  }

  public int getAdds() {
    return adds;
  }

  public int getPops() {
    return pops;
  }
  
  public void resetCounts() {
    pops = 0;
    adds = 0;
    popsLast = 0;
    addsLast = 0;
  }
 
  public void shuffle() {
    Collections.shuffle(this);
  }

  public E step() {
    return step(this.popMode, this.addMode);
  }
 
  public E step(Mode popMode, Mode addMode) {
    // pop from first or last end of deque
    if(popMode==Mode.EITHER) popMode = Mode.either();
    E call = (popMode==Mode.FIRST) ? this.removeFirst() : this.removeLast();
    pops++;
    ArrayList<E> calls = recurse(call);  
    
    // add to first or last end of deque
    if(addMode==Mode.EITHER) addMode = Mode.either();
    if(addMode==Mode.FIRST) {
      for (int i=0; i<calls.size(); i++) {
        this.addFirst(calls.get(i));
      }
    } else {
      for (int i=0; i<calls.size(); i++) {
        this.addLast(calls.get(i));
      }      
    }
    adds += calls.size();
    return call;
  }

  public int[] stepAll() {
    return stepUntil(0, 0, popMode, addMode);
  }
  
  public int[] stepUntil(int popMax, int addCheck) {
    return stepUntil(popMax, addCheck, popMode, addMode);
  }
  
  public int[] stepUntil(int popMax, int addCheck, Mode popMode, Mode addMode) {
    // offset current counts
    this.popsLast = this.pops;
    this.addsLast = this.adds;
    int popsStop = popMax + popsLast;
    int addsStop = addCheck + addsLast;
    //System.out.println(popMax +" "+ pops +" "+ addCheck +" "+ adds +" "+ this.isEmpty());
    while ((popMax==0 || pops<popsStop) && (addCheck==0 || adds<addsStop) && !this.isEmpty()) {
      step(popMode, addMode);
    }
    return new int[] {getPopsLast(), getAddsLast()};
  }
  
  public int getAddsLast() {
    return adds-addsLast;
  }
  
  public int getPopsLast() {
    return pops-popsLast;
  }

  public String status() {
    return "pop,add[ " + popMode + "," + addMode + " ]  cnt[ " + pops + "," + adds + " ]  chg[ " + getPopsLast() + "," + getAddsLast() + " ]  size[" + size() + "]";
  }

  public String toString() {
    return getClass().getName() + "" + status();
  }
}
1 Like

Sure, I can try! By “strict version” I guess a minimalistic one? Like the one I posted, but I think it can be improved a bit.

Ah, got it!

Here’s a video of the Sierpinski sketch JRubyArt ContextFreeArt DSL on YouTube.

1 Like

Good, but what does take the biggest part of the 30 seconds start-to-play time?

I guess it’s the DSL, basically all the intermediate steps are calculated. Which is why when loaded the response to slider is relatively quick, there is a lot of recursion (similar to cfdg but that has the benefit of being written in C++). Nevertheless it is a bit of fun to create a DSL, and because it is not entirely symmetric (unlike cfdg) it can lead to some interesting results.

1 Like

I see. I also discovered that discourse does not count a link as visited when you right-click it to open in a new window. That explains for me now, people responding to a topic as if they had visited the link, but without really showing that as ‘visit-count’ sum.
Another annoying thing is, that every time I post here, I get the message,
“It could be better if you gave other people space to share their points of view, too.”
Really intimidating

Naah just ignore it like I do, otherwise there’s no chance of building an interesting thread. In the older forums there was a lot more interesting discussions, but here we get drowned out by newbie questions.

1 Like

I un-simplified it a bit, made it gradual. It’s rather “wasteful” though, going through all the recursions all the time even if not plotting.

Space key resets (as in jeremydouglass’ example).

int depth = 5;
int interval = 10;

int currentTime;
int lastTime;
int progress = 0;
int lastProgress = 0;
//int finished = int(pow(3,depth));
boolean intervalExpired = false;


void setup() {
  size(410, 230);
  background(255);
  fill(0);
  lastTime = millis();
}


void draw() {
  currentTime = millis();
  triangle (10, 25, 100, depth);
}

 
void triangle (int x, int y, int l, int n) {
  if( n == 0) {
    checkIfIntervalExpired();
    if (intervalExpired) {
      if (progress == lastProgress) {
        text("*", x, y);
        lastProgress++;
        intervalExpired = false;
      }
    }
    progress++;
  } else {
      triangle(x, y+l, l/2, n-1);
      triangle(x+l, y, l/2, n-1);
      triangle(x+l*2, y+l, l/2, n-1);
  }
}


void checkIfIntervalExpired() {
  if (currentTime-lastTime > interval) {
    lastTime = currentTime;
    progress = 0;
    intervalExpired = true;
  }
}


void keyReleased() {  
  if (key==' ') {  // reset
    progress = 0;
    lastProgress = 0;
    background(255);
  }
}

2 Likes

Nice, we can add it as an animated version! :smiley:

1 Like

The Chaos Game is actually very interesting! I found a very nice code on the old forum by @noahbuddy using the triangle. For the easy of copying I will post it here.

int N = 3; // Number of control points
int MAX_DIST = 60;
int MANY_TIMES = 2000;
float DIV = 2.0; // You can change the rules for drawing the pattern
 
// Control points
float[] px = new float[N];
float[] py = new float[N];
 
// The moving point, draws the pattern
float selx = 0;
float sely = 0;
 
boolean held = false;
int nearest = -1;
 
void setup()
{
  size(800,600,P2D);
 
  // Initial points, anywhere you like
  px[0] = width/4;
  py[0] = height/4;
  px[1] = width - (width/4);
  py[1] = height/4;
  px[2] = width - (width/4);
  py[2] = height - (height/4);
 
  noFill();
  frameRate(60);
  background(0);
}
 
void mouseMoved()
{
  float neardist = width + height;
  for (int i=0; i<N; i++)
  {
    float nd = dist(px[i],py[i], mouseX,mouseY);
    if (nd < neardist)
    {
      nearest = i;
      neardist = nd;
    }
  }
  if (MAX_DIST < neardist)
  {
    nearest = -1;
  }
}
 
void mousePressed()
{
  if (-1 != nearest)
  {
    held = true;
  }
}
void mouseReleased()
{
  held = false;
}
 
void draw()
{
  // This section allows you to move the control points
  if (held)
  {
    px[nearest] = constrain(mouseX,0,width);
    py[nearest] = constrain(mouseY,0,height);
    background(0);
  }
  // Still not to the pattern code
 
  // Show control points
  for (int i=0; i<N; i++)
  {
    if (i == nearest)
    {
      stroke(0,255,0);
    }
    else
    {
      stroke(0,0,255);
    }
    ellipse( px[i],py[i], MAX_DIST,MAX_DIST );
  }
 
  stroke(255);
  for (int i=0; i<MANY_TIMES; i++) // Draw many points
  {
    // These are the lines that actually generate the pattern.
    int thistime = (int)random(N);
    float nx = ((px[thistime] - selx) / DIV) + selx;
    float ny = ((py[thistime] - sely) / DIV) + sely;
    selx = nx;
    sely = ny;
    // Yep, that is it.
 
    point( nx, ny );
  }
}
2 Likes

Sorry it took a little while… Added them now, not entirely sure if the category is right though. Feel free to edit them also!

Interesting tidbit: The “Enhanced loop” works different than the regular one, which I didn’t expect. It only gives the same 3 points for any recursion depth except 0 (which only plots the starting point). I’m not sure why that happens.

/** Sierpinski test 3 c

    All paths method (using recursion)
    
    2020.09.05 Improved with Crisir's method of not using a temp PVector
    2020.08.31 Improved with Crisir's method of only displaying at the last recursion depth
    2020.08.29 raron
*/
PVector [] coord = {new PVector(0, 0), new PVector(150, 300), new PVector(300, 0)};

void setup()
{
  size(400,400);
  background(32);  
  sierpinski(new PVector(150,150), 8);
  noLoop();
}


void sierpinski(PVector cPoint, int cDepth)
{
  if (cDepth == 0) {
    set(50+int(cPoint.x), (height-50)-int(cPoint.y), color(192));
    return;
  }

  for (int v=0; v<3; v++) {
    sierpinski(new PVector((cPoint.x+coord[v].x)/2, (cPoint.y+coord[v].y)/2), cDepth-1);
  }

  // "Enhanced loop" doesn't work!
/** 
  for (PVector sVertice : coord) {
    sierpinski(new PVector(cPoint.x+sVertice.x/2, cPoint.y+sVertice.y/2), cDepth-1);
  }
 */
}

If you are looking for the jar, there is a release here:

…and if you download recursor.zip, it contains recursor/library/recursor.jar

1 Like

Something I found interesting while playing this “game” was the apparent projection from four points to a 3D illusion.
(Aside: I should probably pay more attention to my notifications. :upside_down_face:)

int NUMPTS = 10000;

float px = 0;
float py = 0;
//float pz = 0;
int nowc = 0;

int P = 4;

float[] nodex = new float[P];
float[] nodey = new float[P];
float[] nodez = new float[P];
int[] cols = new int[P];

float M = 350;
float N = 70;
float tm = 0;
float tn = PI;
float dm = 0.02;
float dn = -0.01;

void setup()
{
  size(768,768,P2D); // note the lack of third dimension
  cols[0] = color(42, 42, 42);
  cols[1] = color(0, 255, 255);
  cols[2] = color(0x3B, 0x1A, 0xC4);
  cols[3] = color(0, 255, 255);
  frameRate(30);
}

void draw()
{
  background(0);
  
  float ph = TWO_PI / 3;
  for (int i=0; i<(P-1); i++)
  {
    nodex[i] = M * cos(i*ph + tm);
    nodey[i] = M * sin(i*ph + tm);
    nodez[i] = 0;
  }
  nodex[P-1] = N * cos(tn);
  nodey[P-1] = N * sin(tn);
  //nodez[P-1] = 0;
  tm += dm;
  tn += dn;
  
  fill(255);
  text("Four control points.\nCenter is rotating at a different rate", 20, 20);
  
  translate(width/2, height/2);
  
  beginShape(POINTS);
  for (int i=0; i<NUMPTS; i++)
  {
    int t = (int)random(P);
    float tx = nodex[t];
    float ty = nodey[t];
    //float tz = nodez[t];
    int to = cols[t];
    
    nowc = lerpColor(nowc, to, 0.5);
    stroke(nowc);
    
    px = px + (tx - px)/2;
    py = py + (ty - py)/2;
    //pz = pz + (tz - pz)/2;
    vertex(px,py);
  }
  endShape();
}
5 Likes

I’ll also add an implementation to the list. Press Play :arrow_forward: to run the program:

4 Likes