Recursive Sierpinski triangle (just a bit of fun)

this is how I changed the top of the pyramid (using minus here)

and also

 translate(currentPoint.x, 
      245+currentPoint.y,   // !!!!!!!!!!!!!!!!!!!!!!
      260-currentPoint.z);
1 Like

Here’s the one I posted on OpenProcessing:-

/** 
 * Copyright (c) 2011 Martin Prout
 * 
 * This demo & library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * http://creativecommons.org/licenses/LGPL/2.1/
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
PVector[] pts = {              // points of the unit tetrahedron
  new PVector(-0.5, -0.5, -0.5), 
  new PVector(0.5, 0.5, -0.5), 
  new PVector(-0.5, 0.5, 0.5), 
  new PVector(0.5, -0.5, 0.5)
  };

int TETRA_SIZE = 600;

void setup() {
  size(800, 800, P3D);
}

void draw() {
  background(0);
  configureLights();
  translate(width/2, height/2, -100);
  rotateX(cos(radians(frameCount)));
  rotateY(sin(radians(frameCount)));
  noStroke();
  drawSierpinski(pts);
}

void configureLights() {
  ambientLight(100, 100, 100);
  directionalLight(155, 0, 0, -1, -1, -1); // colored directional
  directionalLight(0, 0, 155, 1, -1, -1);  // lights essential
  directionalLight(155, 155, 0, 1, 1, 1);  // for 3D illusion
}

PVector midPoint(PVector a, PVector b) {
  PVector result = PVector.add(a, b);
  result.div(2);
  return result;
}

void drawSierpinski(PVector[] pts) {
  if (pts[0].dist(pts[1]) < 0.1) {    // limits recursion on relative size
    drawTetrahedron(pts, TETRA_SIZE); // render the tetrahedra
  }
  else {
    PVector av = midPoint(pts[0], pts[1]); // a tetrahedron midpoint vertices
    PVector bv = midPoint(pts[0], pts[2]); // b
    PVector cv = midPoint(pts[0], pts[3]); // b
    PVector dv = midPoint(pts[1], pts[2]); // d
    PVector ev = midPoint(pts[1], pts[3]); // e
    PVector fv = midPoint(pts[3], pts[2]);  // e
    PVector[] aa = {
      pts[0], 
      av, 
      bv, 
      cv
    };
    PVector[] bb = {
      av, 
      ev, 
      dv, 
      pts[1]
    };
    PVector[] cc = {
      cv, 
      ev, 
      fv, 
      pts[3]
    };
    PVector[] dd = {
      bv, 
      dv, 
      fv, 
      pts[2]
    };
    drawSierpinski(aa); // calculate further inner tetrahedra coordinates
    drawSierpinski(bb);
    drawSierpinski(cc);
    drawSierpinski(dd);
  }
}

void drawTetrahedron(PVector[] pts, float sz) {
  fill(255);
  beginShape(TRIANGLES);
  vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz);  // 1
  vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz);  // 2  
  vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz);  // 3

  vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz);  // 3
  vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz);  // 1  
  vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz);  // 4  

  vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz);  // 4  
  vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz);  // 3
  vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz);  // 2

  vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz);  // 2  
  vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz);  // 4
  vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz);  // 1
  endShape();
}
5 Likes

Here translated to RubyArt and making use of Vec3D :to_vertex method

# frozen_string_literal: true

PTS = [ # points of the unit tetrahedron
  Vec3D.new(-0.5, -0.5, -0.5),
  Vec3D.new(0.5, 0.5, -0.5),
  Vec3D.new(-0.5, 0.5, 0.5),
  Vec3D.new(0.5, -0.5, 0.5)
].freeze

SIZE = 600

def settings
  size 800, 800, P3D
end

def setup
  sketch_title '3D Sierpinksi'
end

def renderer
  @renderer ||= GfxRender.new(self.g)
end

def draw
  background(0)
  configure_lights
  translate(width / 2, height / 2, -100)
  rotate_x(cos(frame_count.radians))
  rotate_y(sin(frame_count.radians))
  no_stroke
  draw_sierpinski(PTS)
end

def configure_lights
  ambient_light(100, 100, 100)
  directional_light(155, 0, 0, -1, -1, -1) # colored directional
  directional_light(0, 0, 155, 1, -1, -1)  # lights essential
  directional_light(155, 155, 0, 1, 1, 1)  # for 3D illusion
end

def mid_point(a, b)
  (a + b) / 2.0
end

def draw_sierpinski(pts)
  if pts[0].dist(pts[1]) < 0.1 # limits recursion on relative size
    draw_tetrahedron(pts, SIZE) # render the tetrahedra
  else
    av = mid_point(pts[0], pts[1]) # a tetrahedron midpoint vertices
    bv = mid_point(pts[0], pts[2]) # b
    cv = mid_point(pts[0], pts[3]) # b
    dv = mid_point(pts[1], pts[2]) # d
    ev = mid_point(pts[1], pts[3]) # e
    fv = mid_point(pts[3], pts[2]) # e
    aa = [pts[0], av, bv, cv]

    bb = [av, ev, dv, pts[1]]

    cc = [cv, ev, fv, pts[3]]
    dd = [bv, dv, fv, pts[2]]
    draw_sierpinski(aa) # calculate further inner tetrahedra coordinates
    draw_sierpinski(bb)
    draw_sierpinski(cc)
    draw_sierpinski(dd)
  end
end

def draw_tetrahedron(pts, sz)
  fill(255)
  begin_shape(TRIANGLES)

  (pts[0] * sz).to_vertex(renderer)  # 1
  (pts[1] * sz).to_vertex(renderer)  # 2
  (pts[2] * sz).to_vertex(renderer)  # 3

  (pts[2] * sz).to_vertex(renderer)  # 3
  (pts[0] * sz).to_vertex(renderer)  # 1
  (pts[3] * sz).to_vertex(renderer)  # 4

  (pts[3] * sz).to_vertex(renderer)  # 4
  (pts[2] * sz).to_vertex(renderer)  # 3
  (pts[1] * sz).to_vertex(renderer)  # 2

  (pts[1] * sz).to_vertex(renderer)  # 2
  (pts[3] * sz).to_vertex(renderer)  # 4
  (pts[0] * sz).to_vertex(renderer)  # 1

  end_shape
end

1 Like

These are great! Especially that shader version @monkstone posted. I used a a Shadertoy example and just replaced the shader, worked nicely! I haven’t used shaders much previously, they seem almost like magic!

@Chrisir Thanks also for the pointers about PeasyCam.

1 Like

For the ArcBall variant (mouse wheel zoom-able, mouse drag rotatable variant) substitute lines:-

def setup
  sketch_title '3D Sierpinksi'
  ArcBall.init(self)
end

def renderer
  @renderer ||= GfxRender.new(self.g)
end

def draw
  background(0)
  configure_lights
  no_stroke
  draw_sierpinski(PTS)
end

Note no need to translate sketch as ArcBall is designed to center (centre in the Queens English) sketch around the origin.

1 Like

That you did! Nice!

It was in the back of my head that there were probably some redundancy in the points that were drawn. My FPS went up from ~33 to ~45 with recursion depth 6 using your method. With depth 7 it was only from about 9 FPS to 11 FPS though.

Continued your fix with negative coordinates, so the shapes line up and doesn’t need rotating:

  new PVector(150, 0, -260), 
  new PVector(150, -245, -130)

I might have made it slightly worse again. Played with your mini tetrahedron shapes and made recursion depth depending on camera distance (kind of a mipmap).

/** Sierpinski test 8 - 3D (Tetrahedron)
 
 All paths method (using recursion)
 
 2020.08.31 modified Chrisir's variation
 2020.08.30 raron
 */
import peasy.*; 

int depth = 6;  // recursion depth - try 6

int displayedDepth = depth;
float camMaxDistance = 3000.0;

// 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, -130)
};

color[] vertexColor =
  {
  color(255, 0, 0), 
  color(  0, 255, 0), 
  color(  0, 0, 255), 
  color(255, 255, 255)
};

int verts = coord.length;
PeasyCam cam;

// random start point
PVector startPoint = new PVector(150, -122.5, -130);

PShape[] ps = new PShape[depth+1];
PShape outline;

//------------------------------------------------------------------------------
// Two core functions 

void setup() {
  size(600, 600, P3D);

  avoidClipping();

  cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
  //cam.setMinimumDistance(200);
  cam.setMaximumDistance(camMaxDistance);

  PVector startPoint = new PVector(0, 0, 0);
  PVector currentPoint=new PVector(0, 0, 0);
  PVector sVertice; 

  // Make different sized tetrahedron shapes based on currently displayed recursion depth
  for( int d=0; d<depth+1; d++)
  {
    ps[d] = createShape(); 
    ps[d].beginShape(TRIANGLE_STRIP);
    //for (PVector sVertice : coord) {
    // currentPoint.set((startPoint.x+sVertice.x)/2, (startPoint.y+sVertice.y)/2, (startPoint.z+sVertice.z)/2);
    float f = pow(2,d);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 2
    sVertice = coord[1].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 3
    sVertice = coord[2].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);

    // }//for
    ps[d].endShape();
  }

  // Outline shape
/*   outline = createShape(); 
  outline.beginShape(TRIANGLE_STRIP);
  outline.noFill();
  outline.stroke(255,0,0);

  float f = 1;
  // base 1
  sVertice = coord[0].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
  // top
  sVertice = coord[3].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
  // base 2
  sVertice = coord[1].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
  // top
  sVertice = coord[3].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
  // base 3
  sVertice = coord[2].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
  // base 1
  sVertice = coord[0].copy();
  outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);

  outline.endShape();
 */

}

void draw() {
  background(32);
  
  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;
  
  lights(); 
  sierpinski(startPoint, displayedDepth);
  translate(0, 245, 260);

  //shape(outline, 0, 0);
  // cam.beginHUD();
  // text(" displayedDepth " + displayedDepth, 50, 50);
  // cam.endHUD();
}


//------------------------------------------------------------------------------
// Other functions 

void sierpinski(PVector currentPoint, int currentDepth) {
  if (currentDepth < 0) return;

  PVector temp = currentPoint.copy();
  for (PVector sVertice : coord) {
    currentPoint.set((currentPoint.x+sVertice.x)/2, (currentPoint.y+sVertice.y)/2, (currentPoint.z+sVertice.z)/2);
    sierpinski(currentPoint, currentDepth-1);
    currentPoint = temp.copy();
  }//for

  if (currentDepth == 0) {
    stroke(colorSpace(currentPoint));
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    ps[displayedDepth].setFill(colorSpace(currentPoint));
    //rotateY(PI/3);
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }//if
}


// Return a color depending on distances from vertices
color colorSpace(PVector spacePos) {
  float temp;
  color tempCol = color(0, 0, 0);
  for (int i=0; i<verts; i++)
  {
    temp    = 1-(spacePos.dist(coord[i])/300.0);
    tempCol = lerpColor(tempCol, vertexColor[i], temp);
  }
  return tempCol;
}


void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func 
1 Like

Quick minor edit: Better depth calculation (should have tested a bit more before posting).
Added ratio = pow(ratio, 3); to the draw() function:

void draw() {
  background(32);
  
  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  ratio = pow(ratio, 3);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;
  
  lights(); 
  sierpinski(startPoint, displayedDepth);
  translate(0, 245, 260);
}

One more for today (last one?). Just procrastinating at this point but couldn’t let it be.

/** Sierpinski test 9 - 3D (Tetrahedron)
 
 All paths method (using recursion)
 
 2020.08.31 modified Chrisir's variation
 2020.08.30 raron
 */
import peasy.*; 

int depth = 8;  // Max recursion depth - keep < 9 ish

int displayedDepth = depth;
float camMaxDistance = 3000.0;

// 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, -130)
};

color[] vertexColor =
{
  color(255,   0,   0), 
  color(  0, 255,   0), 
  color(  0,   0, 255), 
  color(255, 255, 255)
};

int verts = coord.length;
PeasyCam cam;

// random start point
PVector startPoint = new PVector(150, -122.5, -130);

PShape[] ps = new PShape[depth+1];

//------------------------------------------------------------------------------
// Two core functions 

void setup() {
  size(600, 600, P3D);

  avoidClipping();

  cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
  //cam.setMinimumDistance(200);
  cam.setMaximumDistance(camMaxDistance);

  PVector startPoint = new PVector(0, 0, 0);
  PVector currentPoint=new PVector(0, 0, 0);
  PVector sVertice; 

  // Make different sized tetrahedron shapes based on currently displayed recursion depth
  for( int d=0; d<depth+1; d++)
  {
    ps[d] = createShape(); 
    ps[d].beginShape(TRIANGLE_STRIP);
    ps[d].noStroke();
    //for (PVector sVertice : coord) {
    // currentPoint.set((startPoint.x+sVertice.x)/2, (startPoint.y+sVertice.y)/2, (startPoint.z+sVertice.z)/2);
    float f = pow(2,d);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 2
    sVertice = coord[1].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 3
    sVertice = coord[2].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);

    // }//for
    ps[d].endShape();
  }

}

void draw() {
  background(32);
  
  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  ratio = pow(ratio, 3);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;
  
  lights(); 
  sierpinski(startPoint, displayedDepth, 3);
  // cam.beginHUD();
  // text(" displayedDepth " + displayedDepth, 50, 50);
  // cam.endHUD();
}


//------------------------------------------------------------------------------
// Other functions 

void sierpinski(PVector currentPoint, int currentDepth, int vert) {
  //if (currentDepth < 0) return; // Leave

  if (currentDepth == 0)
  {
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    //stroke(colorSpace(currentPoint));
    ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }

  PVector temp = currentPoint.copy();
  for (int v=0; v<verts; v++)
  {
    currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
    sierpinski(currentPoint, currentDepth-1, v);
    currentPoint = temp.copy();
  }

}


// Return a color depending on distances from vertices
// and "direction" of point (which vertice it moves towards)
color colorSpace(PVector spacePos, int vert) {
  float temp;
  color tempCol = color(0, 0, 0); // overall-ish color (unless very dark)
  //color tempCol = vertexColor[verts-vert-1];
  for (int i=0; i<verts; i++)
  {
    temp = 1-(spacePos.dist(coord[i])/300.0);
    if (i == vert) temp = pow(temp, 5);
    tempCol = lerpColor(tempCol, vertexColor[i], temp);
  }
  return tempCol;
}


void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func 
1 Like

your colorSpace() function is very time consuming

  • pow() is very time consuming and a 4 for-loop is too.

Can’t we treat the entire pyramid as having ONE color only? I wasn’t able to do it. Maybe you can do it without the for-loop? And temp = pow(temp, 5);
is just temp=temptemp…;

but here is a simple example

(I also simplified Sierpinski function again)


/** Sierpinski test 9 - 3D (Tetrahedron)
 
 All paths method (using recursion)
 
 2020.08.31 modified Chrisir's variation
 2020.08.31 raron + 1
 */
import peasy.*; 

int depth = 8;  // Max recursion depth - keep < 9 ish

int displayedDepth = depth;
float camMaxDistance = 3000.0;

// 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, -130)
};

color[] vertexColor = {
  color(255, 0, 0), 
  color(  0, 255, 0), 
  color(  0, 0, 255), 
  color(255, 255, 255)
};

int verts = coord.length;
PeasyCam cam;

// random start point
PVector startPoint = new PVector(150, -122.5, -130);

PShape[] ps = new PShape[depth+1];

//------------------------------------------------------------------------------
// Two core functions 

void setup() {
  size(600, 600, P3D);

  avoidClipping();

  cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
  //cam.setMinimumDistance(200);
  cam.setMaximumDistance(camMaxDistance);

  //PVector startPoint = new PVector(0, 0, 0);
  //PVector currentPoint=new PVector(0, 0, 0);
  PVector sVertice; 

  // Make different sized tetrahedron shapes based on currently displayed recursion depth
  for ( int d=0; d<depth+1; d++) {
    ps[d] = createShape(); 
    ps[d].beginShape(TRIANGLE_STRIP);
    ps[d].noStroke();


    float f = pow(2, d);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 2
    sVertice = coord[1].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 3
    sVertice = coord[2].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);

    ps[d].endShape();
  }
}

void draw() {
  background(32);

  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  ratio = pow(ratio, 3);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;

  lights(); 
  sierpinski(startPoint, displayedDepth, 3);
}


//------------------------------------------------------------------------------
// Other functions 

void sierpinski(PVector currentPoint, int currentDepth, int vert) {

  if (currentDepth == 0) {
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
    //   fill(colorSpace(currentPoint, vert));
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }

  for (int v=0; v<verts; v++) {
    sierpinski(new PVector((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2), 
      currentDepth-1, 
      v);
  }
}

// Return a color depending on distances from vertices
// and "direction" of point (which vertice it moves towards)
color colorSpace(PVector spacePos, int vert) {
  float temp;
  color tempCol = color(111, 0, 0); // overall-ish color (unless very dark)
  //for (int i=0; i<verts; i++) {
  //temp = 1-(spacePos.dist(coord[i])/300.0);
  //if (i == vert) 
  //temp = pow(temp, 5);
  tempCol = lerpColor(tempCol, vertexColor[0], 1-(spacePos.dist(coord[0])/300.0));
  // }
  return tempCol;
}

void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//
2 Likes

It got room for improvements, yes :slight_smile: Aware pow() is somewhat costly.

As for one color only, it’s only a matter of not using the colorSpace function at all (and make all shapes the same color).

(Both coloring and 3D shape “mipmapping” is just playing with it ofc)

Nice! The only thing is that the “mipmap” transitions isn’t smooth now. I’m a bit puzzled as to why. For the sake of just displaying a Sierpinski, it doesn’t matter.

/** Sierpinski test 10 - 3D (Tetrahedron)
 
 All paths method (using recursion)
 
 2020.09.01 modified Chrisir's variation #2
 2020.08.31 modified Chrisir's variation
 2020.08.31 raron + 1
 */
import peasy.*; 

int depth = 8;  // Max recursion depth - keep < 9 ish

color pyramidColor = color(111, 0, 0);
int displayedDepth = depth;
float camMaxDistance = 3000.0;

// 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, -130)
};

int verts = coord.length;

PeasyCam cam;

// random start point
PVector startPoint = new PVector(150, -122.5, -130);

PShape[] ps = new PShape[depth+1];

//------------------------------------------------------------------------------
// Two core functions 

void setup() {
  size(600, 600, P3D);

  avoidClipping();

  cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
  //cam.setMinimumDistance(200);
  cam.setMaximumDistance(camMaxDistance);

  // Mipmapping shapes
  // Make different sized tetrahedron shapes based on currently displayed recursion depth
  PVector sVertice; 
  // base 1, top, base 2, top, base 3, base 1
  int[] vertList = {0, 3, 1, 3, 2, 0};
  for (int d=0; d<depth+1; d++) {
    float f = pow(2, d);
    ps[d] = createShape(); 
    ps[d].beginShape(TRIANGLE_STRIP);
    ps[d].noStroke();
    for (int v=0; v<vertList.length; v++) {
      sVertice = coord[vertList[v]].copy();
      ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
    }
    ps[d].endShape();
    ps[d].setFill(pyramidColor);
  }
}

void draw() {
  background(32);

  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  ratio = pow(ratio, 3);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;

  lights(); 
  sierpinski(startPoint, displayedDepth, 0);
}


//------------------------------------------------------------------------------
// Other functions 


void sierpinski(PVector currentPoint, int currentDepth, int vert) {

  if (currentDepth == 0) {
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    //ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }

  for (int v=0; v<verts; v++) {
    sierpinski(new PVector(
        (currentPoint.x+coord[v].x)/2,
        (currentPoint.y+coord[v].y)/2,
        (currentPoint.z+coord[v].z)/2),
      currentDepth-1,
      v);
  }
}


void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//

@noel There’s also the arrow head curve challenge at rosetta code, which I’m pretty sure I created with my LSystems library and in ruby-processing or JRubyArt. Here is my lsystems version of a regular sierpinski from my ruby-processing blog.

1 Like

When using ortho(); instead of perspective(), the sketch seems to rotate more correctly around it’s center.
Or maybe it’s the startPoint?

I think one problem here is that the triangle coordinates are not around 0,0,0

monkstone‘s sketch has this better

I’m a bit puzzled by that too. startPoint is one suspect, another one is that I made an error one or two of the tetrahedron vertices (not symmetrical?).

If you hold down a Shift key before and during rotating, PeasyCam is restricted to one direction only. As far as I can see it’s pretty centered (but maybe not symmetrical, as mentioned).

Part of it I blame Chrisir’s (sorry lol) simplified Sierpinski, which adds a displacement depending on the recursion depth (but it’s not the whole story). It helps if using my older Sierpinski with a temp variable:

void sierpinski(PVector currentPoint, int currentDepth, int vert) {

  if (currentDepth == 0) {
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }

  PVector temp = currentPoint.copy();
  for (int v=0; v<verts; v++) {
    currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
    sierpinski(currentPoint, currentDepth-1, v);
    currentPoint = temp.copy();
  }
}

1 Like

Got sidetracked a bit.

But it’s no good. It’s very, very dark (any way of improving that?).

Kind of fun to try though, mipmapping 3D shapes textured with 2D sierpinski.

A better way would probably be to have a sort of distance-dependent recursion depth for the 3D shapes, or points, instead, but currently I have no idea how to do that (not sure I want to go there either… or do I?).

Also, transparent textures on PShapes behaves very odd! The different sides (or planes) seem to have a sort of priority when deciding to which other planes they will be transparent. Some are transparent, some are not, some are transparent to some but not all others…

Anyway, the ugly code (Just trying out a few things. But feel free to suggest improvements etc.)

Click to (un) expand
/** Sierpinski test 10 - 3D (Tetrahedron) with textured shapes for added details illusion
 
 All paths method (using recursion)
 
 2020.09.01 Monocolored  with textured shapes for added details (but everything is too dark)
 2020.08.31 modified Chrisir's variation
 2020.08.31 raron + 1
 */
import peasy.*; 

int depth = 6;  // Max recursion depth - keep < 9 ish

color pyramidColor = color(255);
int displayedDepth = depth;
float camMaxDistance = 3000.0;

PImage sierpinskiTex = createImage(200,200,ARGB);
//PImage sierpinskiTex = createImage(600,600,RGB);

// 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, -130)
};

int verts = coord.length;

PeasyCam cam;

// "random" start point (mid point)
PVector startPoint = new PVector(150, -122.5, -130);

PShape[] ps = new PShape[depth+1];

//------------------------------------------------------------------------------
// Two core functions 

void setup() {
  size(600, 600, P3D);

  avoidClipping();

  cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
  //cam.setMinimumDistance(200);
  cam.setMaximumDistance(camMaxDistance);

  // Make 2D texture
  sierpinski2D.plotOnImage(8, pyramidColor, sierpinskiTex);

  // Mipmapping shapes
  // Make different sized tetrahedron shapes based on currently displayed recursion depth
  PVector sVertice;
  int u = sierpinskiTex.width;
  int v = sierpinskiTex.height;
  for ( int d=0; d<depth+1; d++) {
    ps[d] = createShape(); 
    ps[d].beginShape(TRIANGLE_STRIP);
    ps[d].noStroke();

    float f = pow(2, d);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, 0, v);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u/2, 0);
    // base 2
    sVertice = coord[1].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u, v);
    // top
    sVertice = coord[3].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u/2, 0);
    // base 3
    sVertice = coord[2].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, 0, v);
    // base 1
    sVertice = coord[0].copy();
    ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u, v);

    ps[d].endShape();

    ps[d].setTexture(sierpinskiTex);
  }
}


void draw() {
  background(0);

  float ratio = 1-((float)cam.getDistance()/camMaxDistance);
  ratio = pow(ratio, 3);
  displayedDepth = int((depth+1) * ratio);
  if (displayedDepth > depth) displayedDepth = depth;

  lights();
  //ambientLight(255, 255, 255);
  sierpinski(startPoint, displayedDepth, 0);

}


//------------------------------------------------------------------------------
// Other functions 


void sierpinski(PVector currentPoint, int currentDepth, int vert) {

  if (currentDepth == 0) {
    pushMatrix();
    translate(currentPoint.x, 
      245+currentPoint.y, 
      260+currentPoint.z);
    shape(ps[displayedDepth], 0, 0);
    popMatrix();
    return; // Leave
  }

  PVector temp = currentPoint.copy();
  for (int v=0; v<verts; v++) {
    currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
    sierpinski(currentPoint, currentDepth-1, v);
    currentPoint = temp.copy();
  }
}


void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  //ortho();
  perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//

And the 2D class:

static class sierpinski2D
{
  static int       depth;  // recursion depth
  static color     sierpinskiColor;
  static PVector   startPoint;
  static PImage    texture2D;
  static PVector[] vert2D = new PVector[3];

  sierpinski2D(int _depth, color _sierpinskiColor, PImage _texture2D)
  {
    texture2D       = _texture2D;
    depth           = _depth;
    sierpinskiColor = _sierpinskiColor;
    // random start point (at the middle)
    int tw = texture2D.width;
    int th = texture2D.height;
    startPoint = new PVector(tw/2, th/2);
    vert2D[0] = new PVector(   0, th);
    vert2D[1] = new PVector(  tw, th);
    vert2D[2] = new PVector(tw/2,  0);

    sierpinski(startPoint, depth);
  }


  static void plotOnImage(int _depth, color _sierpinskiColor, PImage _texture2D)
  {
    texture2D       = _texture2D;
    depth           = _depth;
    sierpinskiColor = _sierpinskiColor;
    // random start point (at the middle)
    int tw = texture2D.width;
    int th = texture2D.height;
    startPoint = new PVector(tw/2, th/2);
    vert2D[0] = new PVector(   0, th);
    vert2D[1] = new PVector(  tw, th);
    vert2D[2] = new PVector(tw/2,  0);

    sierpinski(startPoint, depth);
  }


  static void sierpinski(PVector currentPoint, int currentDepth)
  {
    if (currentDepth == 0) {
      texture2D.set(int(currentPoint.x), int(currentPoint.y), sierpinskiColor);
      return;
    }
    PVector temp = currentPoint.get();
    for (PVector sVertice : vert2D) {
      currentPoint.set((currentPoint.x+sVertice.x)/2, (currentPoint.y+sVertice.y)/2);
      sierpinski(currentPoint, currentDepth-1);
      currentPoint = temp.get();
    }
  }
}
1 Like

I tried all your code against a light background to get more details. So maybe changing colors?

2 Likes

Sometimes simple is the answer, it helped.Thanks!
(I’d still like to know if there are ways of illuminating the scene better).

@Chrisir (and @noel)
Btw, I uploaded the 2D Sierpinski sketch to the Rosetta code page a couple of days ago. And updated it afterwards with your method of only drawing at the deepest recursion depth. Just FYI I guess.

3 Likes

@raron See the Barnsley Fern on rosetta code for an example of an iterated function.

2 Likes

Could you please provide the link?
I can’t find it.

Here: https://www.rosettacode.org/wiki/Sierpinski_triangle/Graphical#Processing

EDIT: Just realized it could do with one more edit with Chrisir’s simplified Sierpinski (I haven’t gotten to that yet)