How to make crystal like shapes with Processing?

Dear community,

I’m new to Processing and would need your guidance and suggestions for creating 3D crystal shapes.
My guess is I need to start with a PShape object but I have no clue what kind of transformations I should then operate and how to do it.

Do you think such project is feasible for a beginner ?

Regards

1 Like

My suggestion is to start with 2D first so you become familiar with Processing. Working in 3D is possible and it will require more work. It depends on your concept and your requirements/expectations. I suggest you start with small steps.

I found this online hoping it could give you an idea. It is named Crystal growth:

I will post the code below. You need to paste it into a sketch and add an image file in your data folder within the sketch folder. See this for instructions.

How to run it?

  • After you add the image file to your sketch, you need to update the image filename in your pde file
  • Run the sketch. Click any place on the sketch.
  • Press b to toggle showing the image

If something is not clear, check the reference and ask below.

Kf

ArrayList<Crack> cracks;
PImage bg;

boolean drawBG = false;

static final int CLUSTER_SIZE = 10;
static final float CLUSTER_CHANCE = .3;

void setup() {
  size(320, 480);
  colorMode(HSB);
  background(0);

  cracks = new ArrayList<Crack>();
  /*
  for(int j=0; j<2; j++) {
   spawnCracks(random(0,width), random(0,height));
   }
   */
  bg = loadImage("coffee9.JPG");//("iphonebg.png");
  imageMode(CENTER);
  smooth();
}

void draw() {
  background(0);
  if (drawBG)
    image(bg, width/2, height/2, width, height);
  stroke(0, 0, 255);
  for (int i=0; i<cracks.size(); i++) {
    cracks.get(i).update();
  }
  //scale(2);
  for (int i=0; i<cracks.size(); i++) {
    cracks.get(i).draw();
  }
}

void spawnCracks(float x, float y) {
  PVector start = new PVector(x, y);
  PVector center = new PVector(x, y);
  float primaryAngle;

  int min, max;
  if (random(0, 1) < .5) {
    min = 3; 
    max = 5;
  } else {
    min = 15; 
    max = 50;
  }

  boolean cluster = random(0, 1) < CLUSTER_CHANCE ? true : false;
  if (cracks.size() == 0) cluster = true;  // Always cluster the first crack
  for (int j=0; j<CLUSTER_SIZE; j++) {
    primaryAngle = random(0, TWO_PI);
    start = PVector.add(center, new PVector(10 * cos(j/(float)CLUSTER_SIZE * TWO_PI), 10 * sin(j/(float)CLUSTER_SIZE * TWO_PI)));
    for (int i=0; i<random(min, max); i++) {
      Crack c = new Crack(start, primaryAngle + random(0, 2*PI), 
        random(5, 30), random(.8, .99), random(-.05, .05), cracks);
      cracks.add(c);
    }  
    if (!cluster) break;
  }
}

void mousePressed() {
  if (mouseButton == LEFT)
    spawnCracks(mouseX, mouseY);
  else
    cracks.clear();
}

void keyPressed() {
  if (key == 'b')
    drawBG = !drawBG;
}


public class Crack {
  PVector position;
  float angle;
  float length;
  float curvature;
  float decay;

  boolean active;
  BoundingBox box;

  float BRANCH_CHANCE = .1;
  float STOP_CRACK_CHANCE = .9; 
  ArrayList<Segment> segments;
  ArrayList<Crack> fellowCracks;


  public Crack() {
    this(new PVector(0, 0), 0, 50, .95, .05, null);
  }

  public Crack(PVector iPosition, float iAngle, float iLength, float iDecay, float iCurvature, ArrayList<Crack> fellows) {
    this.position = new PVector(iPosition.x, iPosition.y);
    this.angle = iAngle;
    this.length = iLength;
    this.curvature = iCurvature;  
    this.decay = iDecay;
    this.active = true;
    this.fellowCracks = fellows;

    this.box = new BoundingBox(this.position, this.position);

    this.segments = new ArrayList<Segment>();
  }

  public void update() {
    if (this.active) {
      PVector end = PVector.add(this.position, new PVector(cos(this.angle) * this.length, 
        sin(this.angle) * this.length));
      Segment nextSegment = new Segment(this.position.x, this.position.y, end.x, end.y);
      PVector collision = getCollision(nextSegment);
      if (collision == null) {
        this.segments.add(nextSegment);
        this.angle += this.curvature;
        //this.length *= this.decay;
        if (this.length < 1) this.active = false;

        position.set(end); 

        // Randomly branch
        if (random(0, 1) < BRANCH_CHANCE && this.active) {
          this.fellowCracks.add(new Crack(position, this.angle + random(-PI/6, PI/6), random(this.length, this.length*2), random(.8, .95), random(-.05, .05), this.fellowCracks));
        }
      } else {
        nextSegment.end = collision;
        this.segments.add(nextSegment);
        this.active = false;
        // And spawn a new crack?
        //this.fellowCracks.add(new Crack(collision, this.angle + random(-PI/4, PI/4), random(5,30), random(.8,.99), random(0,.05)));
      }
      this.box.updateToInclude(nextSegment.start);
      this.box.updateToInclude(nextSegment.end);
    }
  }


  public void draw() {
    for (int i=0; i<segments.size(); i++) {
      segments.get(i).draw();

      rectMode(CORNERS);
      noFill();
      strokeWeight(.1);
      //rect(this.box.corner1.x, this.box.corner1.y, this.box.corner2.x, this.box.corner2.y);
    }
  }

  private PVector getCollision(Segment s) {
    if (fellowCracks != null) {
      PVector collision;
      if (s.end.x > width || s.end.x < 0 || s.end.y > height || s.end.y < 0)
        return new PVector(s.end.x, s.end.y); 

      for (int i=0; i<fellowCracks.size(); i++) {
        if (fellowCracks.get(i).box.contains(s.start) || fellowCracks.get(i).box.contains(s.end)) {
          for (int j=0; j<fellowCracks.get(i).segments.size(); j++) {
            collision = fellowCracks.get(i).segments.get(j).collides(s);
            if (collision != null && random(0, 1) < STOP_CRACK_CHANCE) {
              return collision;
            }
          }
        }
      }
      return null;
    } else return null;
  }
}

public class BoundingBox { 
  PVector corner1;
  PVector corner2;
  public BoundingBox(PVector a, PVector b) {
    this.corner1 = new PVector(a.x, a.y);
    this.corner2 = new PVector(b.x, b.y);
  }  

  public void updateToInclude(PVector p) {
    // Expand to the right?
    if (p.x > corner1.x && p.x > corner2.x) {
      if (corner1.x > corner2.x)
        corner1.x = p.x;
      else
        corner2.x = p.x;
    }
    // Expand to the left?
    if (p.x < corner1.x && p.x < corner2.x) {
      if (corner1.x < corner2.x)
        corner1.x = p.x;
      else
        corner2.x = p.x;
    }    
    // Expand to the top?
    if (p.y < corner1.y && p.y < corner2.y) {
      if (corner1.y < corner2.y)
        corner1.y = p.y;
      else
        corner2.y = p.y;
    }     
    // Expand to the bottom?
    if (p.y > corner1.y && p.y > corner2.y) {
      if (corner1.y > corner2.y)
        corner1.y = p.y;
      else
        corner2.y = p.y;
    }
  }

  public boolean contains(PVector p) {
    float left = corner1.x < corner2.x ? corner1.x : corner2.x;  
    float right = corner1.x > corner2.x ? corner1.x : corner2.x;
    float top = corner1.y < corner2.y ? corner1.y : corner2.y;  
    float bottom = corner1.y > corner2.y ? corner1.y : corner2.y;
    if (p.x < left || p.x > right || p.y < top || p.y > bottom)
      return false;
    else return true;
  }
}

public class Segment {
  PVector start;
  PVector end;

  public Segment(PVector start, PVector end) {
    this.start = new PVector(start.x, start.y);
    this.end = new PVector(end.x, end.y);
  }

  public Segment(float startx, float starty, float endx, float endy) {
    this.start = new PVector(startx, starty);
    this.end   = new PVector(endx, endy);
  }

  public void draw() {
    //strokeWeight(sqrt((sq(start.x-end.x) + sq(start.y-end.y))/100.));
    strokeWeight(1);
    stroke(0, 0, 0, 180);
    line(start.x+1, start.y-1, end.x+1, end.y-1);    
    strokeWeight(1);
    stroke(0, 0, 255, 255);
    line(start.x, start.y, end.x, end.y);
  }

  public PVector collides(Segment s) {
    // Algorithm by Ryan Alexander from http://wiki.processing.org/w/Line-Line_intersection
    float x1 = s.start.x;
    float y1 = s.start.y;
    float x2 = s.end.x;
    float y2 = s.end.y;
    float x3 = this.start.x;
    float y3 = this.start.y;
    float x4 = this.end.x;
    float y4 = this.end.y;

    float bx = x2 - x1; 
    float by = y2 - y1; 
    float dx = x4 - x3; 
    float dy = y4 - y3;
    float b_dot_d_perp = bx * dy - by * dx;
    if (b_dot_d_perp == 0) {
      return null;
    }
    float cx = x3 - x1;
    float cy = y3 - y1;
    float t = (cx * dy - cy * dx) / b_dot_d_perp;
    if (t <= 0 || t >= 1) {
      return null;
    }
    float u = (cx * by - cy * bx) / b_dot_d_perp;
    if (u <= 0 || u >= 1) { 
      return null;
    }    

    return new PVector(x1+t*bx, y1+t*by);
  }
}

2 Likes

Hi @liukov, welcome to the forum.

There are probably many ways to produce that kind of shape but I think your approach (applying modifications to a PShape object) is quite relevant in this case.

My first thought was to suggest playing with HemeshGui. It’s a visual tool built on the Hemesh library that facilitate the creation and manipulation of polygonal meshes. For instance you could start with a box, elongate it with the skew ‘modifier’ and then chamfer the corners.

I then realized you could get more realistic results with a simpler approach I tried some time go and which is based on this blogpost. Basically all you would have to do is noising the vertices of an icosahedron. The key part is to divide the normalized vertices locations (PVectors) by the noise value instead of multiplying them.

The simplest way to do this is to use the Hemesh library I mentionned above.

  • create an icosahedron with the creator class
  • normalize the vertices
  • compute a 3D noise value based their locations (x, y and z)
  • divide the (normalized) vertices by their corresponding noise value

Normally this should result in long, sharp spikes in the mesh:

Short example sketch with Python mode:

add_library('peasycam')
add_library('hemesh')

factor = 1.5

def setup():
    size(1000, 600, P3D)
    smooth(8)
           
    creator = HEC_Geodesic()
    creator.setType(WB_Geodesic.Type.OCTAHEDRON).setRadius(200).setB(12).setC(12)

    global mesh, render
    mesh = HE_Mesh(creator)
    render = WB_Render(this)
    cam = PeasyCam(this, 800)
    
    for i, v in enumerate(mesh.getVertices()):
        p = PVector(v.xf(), v.yf(), v.zf()).normalize().mult(1.1) ### --> Converting to PVector() for clarity but could use normalizeSelf(), scaleSelf() or mul() instead
        n = map(noise(p.x * factor + 1, p.y * factor + 2, p.z * factor + 3), 0, 1, -.004, .03)
        p.div(n)
        mesh.getVertex(i).set(WB_Point(p.x, p.y, p.z))

def draw():
    background('#DCDCDC')
    lights()
    
    render.drawFaces(mesh)

Because of the pseudo-random nature of Perlin noise you’ll never end-up with the same result so feel free to run sketch as many times as you like until you find an output you feel satisfied with.

If realism is important to you, you can go a step further and render your mesh with a third party software. Here’s an example of a mesh generated with the same sketch and then rendered with Keyshot:

(Note that If you have a windows/linux machine you could also try @kosowski’s simplePBR library within your Processing sketch)

11 Likes

Massive thanks to both of you for your time and thorough explanations. I’m truly pleasantly surprised by the quality of your answers. @solub’s suggestion is really close to what I first had in mind and the rendered image of the final output goes beyond my expectations.

Thanks again !

1 Like

@solub

thanks for your post.

Impressive.

I noted keystroke is not free. Do you know a free alternative?

not as elegant as the other posts:

simple example showing Dodecahedron and
tetrahedron and pyramid - maybe you can form the shapes to crystals.

I just wanted to show some usages of vectors in 3D

Chrisir


/*

 Demo program for a 3D class named ThreeD.
 
 This class shows different solids to draw in 3D.
 
 This class is not about one item (Car) but more a 
 collection of different commands for the realm / domain of 3D. 
 Among others Platonic solids.
 
 It also features a small camera class named CameraClass. 
 
 Use mainly space key to change state and q to start/stop rotation.
 Use also o/p to rotate cam manually. 
 
 Much better: shapes 3D library by quarks. 
 
 */
// core class 
ThreeD threeD = new ThreeD(); 

// ----------------------------------------------

void setup() {
  size(1900, 950, P3D);
}

void draw() {
  background(0);
  lights();

  pushMatrix(); 
  noStroke();
  fill(threeD.WHITE);    
  translate(width/2, 300, -210);
  rotateY(map(millis()%5000, 0, 5000, 0, TWO_PI));
  threeD.tetrahedron ( 0, 0, 0, 37 );
  popMatrix();

  // -------------------------------

  pushMatrix(); 
  fill(threeD.WHITE);    
  translate(width/2+300, 300, -210);
  rotateY(map(millis()%5000, 0, 5000, 0, TWO_PI));
  threeD.Octahedron();
  popMatrix();

  // -------------------------------

  pushMatrix(); 
  noStroke();
  stroke(111); 
  fill(threeD.WHITE);    
  translate(width/2-390, 300, -410);
  rotateY(map(millis()%5000, 0, 5000, 0, TWO_PI));
  threeD.pyramid (  );
  popMatrix();

  // -------------------------------

  pushMatrix(); 
  noStroke();
  // fill(threeD.WHITE);    
  translate(width/2-111, 500, 210);
  rotateY(map(millis()%5000, 0, 5000, 0, TWO_PI));
  threeD.dodecahedron();
  popMatrix();

  //
} // func 

// ==============================================

class ThreeD {

  /*
   This class shows different solids to draw in 3D.
   
   This class is not about one item (Car) but more a 
   collection of different commands for the realm of 3D. 
   
   section 1: reference section "2D Primitives" now in 3D
   section 2: reference section "3D Primitives" now in enhanced version 
   section 3: Platonic solids
   section 4: other, non platonic solids 
   section 5: coordinate system and related 
   section 6: HUD funcs / Head-up-Display funcs 
   section 7: floor
   section 8: minor help functions
   
   */

  // consts

  final color RED     = color (255, 0, 0);
  final color GREEN   = color (0, 255, 0);
  final color BLUE    = color (0, 0, 255);

  final color WHITE   = color (255);
  final color BLACK   = color (0);
  final color GRAY    = color (111);

  final color YELLOW       = color (255, 255, 0);
  final color YELLOWWARM   = color (255, 245, 49); 
  final color VIOLET       = color (143, 0, 255);
  final color INDIGO       = color (111, 0, 255);
  final color MAGENTA      = color (255, 0, 255);
  final color PINK         = color (255, 192, 203); 

  final color ORANGE    = color (255, 165, 0);

  // vars 

  boolean crosshair; 

  boolean showAsStandard=true; // space bar toggles

  // section 1: 
  // reference section "2D Primitives" now in 3D ----------------------
  // read the ref there, they are just changed a bit to be 3D

  void point(PVector pos) {
    // version with PVector parameter. 
    // calls another method in the same class. 
    // See there for description. 
    point(pos.x, pos.y, pos.z);
  }

  void point(PVector pos, String str) {
    // version with PVector parameter. 
    // calls another method in the same class. 
    // See there for description. 
    // With local text next to the point. 
    point(pos.x, pos.y, pos.z);
    text(str, pos.x+6, pos.y+6, pos.z);
  }

  void point(float x, float y, float z) {
    // point as a sphere, fixed size 5, 
    // fixed noStroke, no fill color, you 
    // must set the fill color before using 
    // this method. 
    noStroke();
    pushMatrix();
    translate(x, y, z);
    sphere(5);
    popMatrix();
  }

  void ellipse3D(float x, float y, float z, float size1) {
    // point as a sphere, fixed size 5, 
    // fixed noStroke, no fill color, you 
    // must set the fill color before using 
    // this method. 
    noStroke();
    pushMatrix();
    translate(x, y, z);
    ellipse(0, 0, size1, size1);
    popMatrix();
  }

  void line3D(float x1, float y1, float z1, 
    float x2, float y2, float z2, 
    float weight, color colorLine)
    // drawLine was programmed by James Carruthers
    // see http://processing.org/discourse/yabb2/YaBB.pl?num=1262458611/0#9 
  {
    // scale(90);

    PVector p1 = new PVector(x1, y1, z1);
    PVector p2 = new PVector(x2, y2, z2);
    PVector v1 = new PVector(x2-x1, y2-y1, z2-z1);
    float rho = sqrt(pow(v1.x, 2)+pow(v1.y, 2)+pow(v1.z, 2));
    float phi = acos(v1.z/rho);
    float the = atan2(v1.y, v1.x);
    v1.mult(0.5);
    pushMatrix();
    PVector v2 = new PVector ( x1, y1, z1 );
    translate(v2.x, v2.y, v2.z);
    translate(v1.x, v1.y, v1.z);
    rotateZ(the);
    rotateY(phi);
    noStroke();
    fill(colorLine);

    box(weight, weight, p1.dist(p2));
    popMatrix();
  } // method

  void triangle(float x1, float y1, float z1, 
    float x2, float y2, float z2, 
    float x3, float y3, float z3) {
    beginShape();
    vertex(x1, y1, z1);
    vertex(x2, y2, z2);
    vertex(x3, y3, z3);
    endShape(CLOSE);
  }

  void quad(
    float x1, float y1, float z1, 
    float x2, float y2, float z2, 
    float x3, float y3, float z3, 
    float x4, float y4, float z4) {
    beginShape();
    vertex(x1, y1, z1);
    vertex(x2, y2, z2);
    vertex(x3, y3, z3);
    vertex(x4, y4, z4);
    endShape(CLOSE);
  }

  void rect(
    float x1, float y1, float z1, 
    float w, float h) {
    beginShape();
    vertex(x1, y1, z1);
    vertex(x1+w, y1, z1);
    vertex(x1+w, y1+h, z1);
    vertex(x1, y1+h, z1);
    endShape(CLOSE);
  }

  // section 2: 
  // reference section "3D Primitives" now in enhanced version ----------------------

  void box3D (float x, float y, float z, 
    float sizeBox, 
    color col ) {
    fill(col);
    pushMatrix();
    translate(x, y, z);
    box(sizeBox);
    popMatrix();
  }

  void sphere3D (float x, float y, float z, 
    float sizeBox, 
    color col ) {
    fill(col);
    pushMatrix();
    translate(x, y, z);
    sphere(sizeBox);
    popMatrix();
  }

  // section 3: 
  // Platonic solids ----------------------
  // http://en.wikipedia.org/wiki/Platonic_solid 

  // also cube / box is a Platonic solid - see above

  void tetrahedron(float x1, float y1, float z1, float size1 ) {

    // tetrahedron
    //    A(1,1,–1), C(–1,–1,–1), F(–1,1,1) und H(1,–1,1).
    // see http://en.wikipedia.org/wiki/Tetrahedron#Formulas_for_a_regular_tetrahedron
    // see http://de.wikipedia.org/wiki/Datei:Tetraeder_animation_with_cube.gif 

    scale (size1);

    // fill(GREEN);
    beginShape();
    vertex(1, 1, -1);
    vertex(-1, -1, -1);
    vertex(-1, 1, 1) ;
    endShape();

    // fill(BLUE);
    beginShape();
    vertex(1, -1, 1);
    vertex(-1, -1, -1);
    vertex(-1, 1, 1) ;
    endShape();

    // fill(PINK);
    beginShape();
    vertex(1, -1, 1);
    vertex(-1, -1, -1);
    vertex(1, 1, -1);
    endShape();

    // fill(YELLOW);
    beginShape();
    vertex(1, 1, -1);
    vertex(1, -1, 1);
    vertex(-1, 1, 1) ;
    endShape();
    // 
    //    noFill();
    //    box(2);
    //
  }

  void Octahedron() {
    // (eight faces)
    // see http://en.wikipedia.org/wiki/Octahedron

    /*
     ( ±1, 0, 0 );
     ( 0, ±1, 0 );
     ( 0, 0, ±1 ). 
     */

    // define vertices 
    PVector [] list = new PVector[6];

    list [0] = new PVector  (1, 0, 0);
    list [1] = new PVector  (0, 0, 1);
    list [2] = new PVector  (0, -1, 0); 

    list [3] = new PVector  (-1, 0, 0);
    list [4] = new PVector  (0, 0, -1);
    list [5] = new PVector  (0, 1, 0);

    // scale (33);

    for (int i = 0; i < list.length; i++) {
      list[i].mult(49); 
      // point(list[i], str(i));
    }

    fillAndStroke (BLUE, BLACK);

    // defines the shapes 
    // the lower half
    makeShape (list [0], list [1], list [5] ) ;
    makeShape (list [0], list [4], list [5] ) ;
    makeShape (list [3], list [4], list [5] ) ;
    makeShape (list [3], list [1], list [5] ) ;

    // the upper half
    makeShape (list [0], list [1], list [2] ) ;
    makeShape (list [0], list [4], list [2] ) ;
    makeShape (list [3], list [4], list [2] ) ;
    makeShape (list [3], list [1], list [2] ) ;
  } // method 

  // 
  // section 4: 
  // other, non platonic solids ----------------------

  void pyramid () { 

    scale(88);
    noStroke(); 
    beginShape();
    fill(threeD.WHITE);    
    vertex(-1, -1, -1);
    vertex( 1, -1, -1);
    vertex( 0, 0, 1);
    endShape(CLOSE);

    beginShape();
    vertex( 1, -1, -1);
    vertex( 1, 1, -1);
    vertex( 0, 0, 1);
    endShape(CLOSE);

    beginShape();
    vertex( 1, 1, -1);
    vertex(-1, 1, -1);
    vertex( 0, 0, 1);
    endShape(CLOSE);

    beginShape();
    vertex(-1, 1, -1);
    vertex(-1, -1, -1);
    vertex( 0, 0, 1);
    endShape(CLOSE);
  }


  // -------------------------------------------------------------------------

  void dodecahedron() {
    /*
     regular dodecahedron = composed of twelve regular pentagonal faces, three meeting at each vertex.
     
     A regular dodecahedron or pentagonal dodecahedron is a dodecahedron that is regular, which is composed of twelve regular pentagonal faces, three meeting at each vertex. 
     It is one of the five Platonic solids. It has 12 faces, 20 vertices, 30 edges, and 160 diagonals (60 face diagonals, 100 space diagonals).[1] It is represented by the Schläfli symbol {5,3}. 
     
     */

    boolean showPink = false; // 0
    boolean showOrange = false; // 1
    boolean showBlue = false; // 2
    boolean showGreen = false; // 3

    // size 
    float f1 = 19; // 19 is a good factor  

    float goldenRatio = (1 + sqrt(5)) / 2 ; // ~ 1.618;
    //print (goldenRatio);
    //println (" ~ 1.618");

    // The orange vertices lie at (±1, ±1, ±1) and form a cube (dotted lines).
    PVector [] orange = new PVector[8];
    orange [0] = new PVector  (1, 1, 1);  // 0 times - 
    orange [1] = new PVector  (-1, -1, -1); // 3 times - 

    orange [2] = new PVector  (-1, 1, 1);  // 1 time - 
    orange [3] = new PVector  (1, -1, 1);  // 1 time -
    orange [4] = new PVector  (1, 1, -1);  // 1 time -

    orange [5] = new PVector  (-1, -1, 1);// 2 time -
    orange [6] = new PVector  (1, -1, -1);// 2 time -
    orange [7] = new PVector  (-1, 1, -1);// 2 time -

    for (int i = 0; i < orange.length; i++) {
      orange[i].mult(f1); 
      if (showOrange) {
        fill(ORANGE);
        pointPvStr(orange[i], str(i));
      }
    }

    if (showOrange) {
      stroke(ORANGE);
      // //2536 
      linePV (orange [1], orange [5]);
      linePV (orange [5], orange [3]);
      linePV (orange [3], orange [6]);
      linePV (orange [6], orange [1]);

      //7204
      linePV (orange [7], orange [2]);
      linePV (orange [2], orange [0]);
      linePV (orange [0], orange [4]);
      linePV (orange [4], orange [7]);

      // 17 and 25
      linePV (orange [1], orange [7]);
      linePV (orange [2], orange [5]);

      // 30 and 64
      linePV (orange [3], orange [0]);
      linePV (orange [6], orange [4]);
    }

    // --------------------------------

    // The green vertices lie at (0, ±ϕ, ±1/ϕ) and form a rectangle on the yz-plane.
    PVector [] green = new PVector[4];
    green [0] = new PVector  (0, -goldenRatio, -1/goldenRatio); // 2 times -
    green [1] = new PVector  (0, goldenRatio, 1/goldenRatio);  // 0 tmes - 
    green [2] = new PVector  (0, -goldenRatio, 1/goldenRatio);  // 1 time - [minus and then +]
    green [3] = new PVector  (0, goldenRatio, -1/goldenRatio);  // 1 time - [+ and then minus] 
    for (int i = 0; i < green.length; i++) {
      green[i].mult(f1); 
      if (showGreen) {
        fill(GREEN);
        pointPvStr(green[i], str(i));
      }
    }
    if (showGreen) {
      PVector[] pvGreenArrayForFunctionQuadArray = 
        {  green [1], green [2], green [0], green [3] }; 
      stroke(GREEN); 
      quadArray(pvGreenArrayForFunctionQuadArray); //
    }// if

    // --------------------------------

    // The blue vertices lie at (±1/ϕ, 0, ±ϕ) and form a rectangle on the xz-plane. 
    PVector [] blue = new PVector[4];
    blue [0] = new PVector  (-1/goldenRatio, 0, -goldenRatio); // 2 times - 
    blue [1] = new PVector  ( 1/goldenRatio, 0, goldenRatio);  // 0 tmes -
    blue [2] = new PVector  ( 1/goldenRatio, 0, -goldenRatio); // 1 time -
    blue [3] = new PVector  (-1/goldenRatio, 0, goldenRatio);  // 1 time - 
    for (int i = 0; i < blue.length; i++) {
      blue[i].mult(f1); 
      if (showBlue) {
        fill(BLUE);
        pointPvStr(blue[i], str(i));
      }
    }
    if (showBlue) {
      PVector[] pvArrayBlueForFunctionQuadArray = 
        {  blue [1], blue [2], blue [0], blue [3] }; 
      stroke(BLUE); 
      quadArray(pvArrayBlueForFunctionQuadArray);
    }

    // -----------------------

    //The pink vertices lie at (±ϕ, ±1/ϕ, 0) and form a rectangle on the xy-plane.
    //The distance between adjacent vertices is 2/ϕ, and the distance from the origin to any vertex is √3.
    //ϕ = (1 + √5) / 2 is the golden ratio. 
    PVector [] pink = new PVector[4];
    pink [0] = new PVector  ( -goldenRatio, -1/ goldenRatio, 0); // 2 times -
    pink [1] = new PVector  (  goldenRatio, 1/ goldenRatio, 0);  // 0 tmes - 
    pink [2] = new PVector  (  goldenRatio, -1/ goldenRatio, 0); // 1 time - 
    pink [3] = new PVector  ( -goldenRatio, 1/ goldenRatio, 0);  // 1 time - 
    for (int i = 0; i < pink.length; i++) {
      pink[i].mult(f1);
      if (showPink) {
        fill(PINK);
        pointPvStr(pink[i], str(i));
      }
    }
    if (showPink) {
      PVector[] pvArrayPinkForFunctionQuadArray = 
        {  pink [1], pink [2], pink [0], pink [3] }; 
      stroke(PINK); 
      quadArray(pvArrayPinkForFunctionQuadArray);
    }

    // ----------------------------------------

    // fill(RED, 110);
    fill(RED);
    stroke(WHITE);
    makeShape5 ( blue[3], blue[1], 
      orange[0], 
      green [1], 
      orange[2]);

    makeShape5 (  blue[1], 
      orange[0], 
      pink[1], pink[2], 
      orange[3]);

    makeShape5 ( orange[0], 
      green [1], green [3], 
      orange[4], 
      pink[1] );

    makeShape5( orange[2], 
      pink[3], 
      orange[7], 
      green[3], green[1]);

    makeShape5( green[3], 
      orange[7], 
      blue[0], blue[2], 
      orange[4]);

    makeShape5( pink[1], 
      orange[4], 
      blue[2], 
      orange[6], 
      pink[2]);

    makeShape5( pink[0], //penta6.png 
      pink[3], 
      orange[7], 
      blue[0], 
      orange[1]);

    makeShape5( blue[3], //penta7.png
      orange[2], 
      pink[3], pink[0], 
      orange[5]);

    makeShape5( orange[1], // penta8
      blue[0], blue[2], 
      orange[6], 
      green[0] ); 

    fill(GREEN); 
    makeShape5( orange[5], // penta9
      pink[0], 
      orange[1], 
      green[0], green[2] );    

    // noFill(); 
    strokeWeight(4); 
    makeShape5( orange[3], // penta 10
      green[2], green[0], 
      orange[6], 
      pink[2]);
    strokeWeight(1); // back to default  

    fill(YELLOW); 
    makeShape5( blue[1], blue[3], // penta 11
      orange[5], 
      green[2], 
      orange[3] ); 

    // to test a new pentagon 
    //fill(YELLOW);
    //PVector [] c1 = {
    //  // vertices here 
    //};
    //quadArray(c1);

    // noLoop();
  }//method 

  // ------------------------------


  // section 5: 
  // coordinate system and related ----------------------

  // by ofey 

  void coorWalls() {

    // draw 3 walls in 3D which mark a coordinate systems. 
    // coordinate system wall
    // by ofey 
    //
    fill(BLUE);//blue left side
    beginShape();
    vertex(0, 0, 0);
    vertex(0, 0, -400);
    vertex(0, 400, -400);
    vertex(0, 400, 0);
    endShape(CLOSE);

    fill(YELLOW);//yellow bottom
    beginShape();
    vertex(0, 400, 0);
    vertex(0, 400, -400);
    vertex(400, 400, -400);
    vertex(400, 400, 0);
    endShape(CLOSE);

    fill(GREEN);//green back
    beginShape();
    vertex(0, 0, -400);
    vertex(400, 0, -400);
    vertex(400, 400, -400);
    vertex(0, 400, -400);
    endShape(CLOSE);
  }

  void ShowCoordinates () {
    // Show Coordinates x, y and z as lines 
    //
    // X
    stroke (255, 0, 0);
    line (0, 0, 0, 100, 0, 0 ) ;
    sphere3D (100, 0, 0, 13, color (255, 0, 0) );
    text ("X", 120, 60, 0);

    // Y
    stroke    (0, 255, 0);
    line (0, 0, 0, 0, 100, 0 ) ;    
    sphere3D(0, 100, 0, 13, GREEN);    
    text ("Y", 0, 180, 0);

    // Z
    stroke (0, 0, 255);
    line (0, 0, 0, 0, 0, -300 ) ; 
    sphere3D (0, 0, -300, 33, BLUE);    
    text ("-Z", 30, 50, -300);
  } // function 

  void show3DCursor( float theX, float theY, float theZ) {
    // show 3D cursor
    //
    if (crosshair) {
      // 3D crosshair
      final int len = 15;

      stroke(244, 2, 2); // red  
      line (theX, theY, theZ-len, theX, theY, theZ+len);
      stroke(0, 0, 255);
      line (theX, theY-len, theZ, theX, theY+len, theZ);
      stroke(2, 244, 2);
      line (theX-len, theY, theZ, theX+len, theY, theZ);

      pushMatrix();
      translate(theX, theY, theZ+len);
      fill(244, 2, 2);
      noStroke();
      sphere(1);
      popMatrix();
    } else
    {
      // sphere
      pushMatrix();
      translate(theX, theY, theZ);
      fill(WHITE);
      noStroke();
      sphere(10);
      popMatrix();
    } // else
  } // func

  // section 6: 
  // HUD funcs  ----------------------
  // Head-up-Display 
  // see http://de.wikipedia.org/wiki/Head-up-Display

  void showTextInHUD(String str1) {
    // A small 2D HUD for text in the
    // upper left corner. 
    // This func may only be called a the very end of draw() afaik.
    camera();
    hint(DISABLE_DEPTH_TEST);
    noLights();
    textMode(MODEL);
    if (str1!=null)
      text(str1, 20, 20);
    hint(ENABLE_DEPTH_TEST);
  } // func 

  void showTextInHUD(String str1, float x, float y) {
    // A small 2D HUD for text at
    // free pos.
    // This func may only be called a the very end of draw() afaik.
    camera();
    hint(DISABLE_DEPTH_TEST);
    noLights();
    textMode(MODEL);
    if (str1!=null)
      text(str1, x, y);
    hint(ENABLE_DEPTH_TEST);
  } // func 

  // section 7:
  // floor (later walls (but see coordinate system wall above), sky sphere...) --------------------------------

  void CheckeredFloor() {

    noStroke();

    for (int i = 0; i < 40; i = i+1) {
      for (int j = 0; j < 40; j = j+1) {

        // % is modulo, meaning rest of division
        if (i%2 == 0) {
          if (j%2 == 0) {
            fill (255, 0, 0);
          } else
          {
            fill ( 103 );
          }
        } else {
          if (j%2 == 0) {
            fill ( 103 );
          } else
          {
            fill (255, 0, 0);
          }
        } // if

        pushMatrix();

        translate ( 40*i-500, 360, 40*j-640 );
        box (40, 7, 40);

        popMatrix();
      } // for
    } // for
  } // function


  // section 8: 
  // minor help functions ----------------------
  //
  // vertex ---
  //

  void pointPvStr(PVector pos, String str) {
    // version with PVector parameter. 
    // calls another method in the same class. 
    // See there for description. 
    point(pos.x, pos.y, pos.z);
    //  pointSphere(pos.x, pos.y, pos.z);
    text(str, pos.x+6, pos.y+6, pos.z);
  }

  void pointSpherePV(PVector pv) {
    // PVector
    pointSphere(pv.x, pv.y, pv.z);
  }

  void linePV ( PVector pv1, PVector pv2) {
    line (pv1.x, pv1.y, pv1.z, 
      pv2.x, pv2.y, pv2.z);
  } 


  void pointSphere(float x, float y, float z) {
    // point as a sphere, fixed size 5, 
    // fixed noStroke, no fill color, you 
    // must set the fill color before using 
    // this method. 
    noStroke();
    pushMatrix();
    translate(x, y, z);
    sphere(3);
    popMatrix();
  }

  void vertexPVector ( PVector pos ) {
    vertex(pos.x, pos.y, pos.z);
  }

  void makeShape( PVector p1, PVector p2, PVector p3 ) {
    beginShape();    
    vertexPVector(p1);
    vertexPVector(p2);
    vertexPVector(p3);
    endShape();
  }
  void makeShape5( PVector p1, PVector p2, PVector p3, PVector p4, PVector p5) {

    /*
    // too compensate optically for the different length of iPentagonCounter (it's either 0..9 or 10/11) we use either "  : " (one space more) or " : " (one space sign less)
     // to get all : underneath each other
     if (iPentagonCounter<=9) 
     print("Pentagon #"+iPentagonCounter+"  : ");
     else
     print("Pentagon #"+iPentagonCounter+" : ");
     
     print(p1);
     print(p2);
     print(p3);
     print(p4);
     print(p5);
     println(""); 
     iPentagonCounter++;
     */


    if (showAsStandard) { 
      beginShape();    
      vertexPVector(p1);
      vertexPVector(p2);
      vertexPVector(p3);
      vertexPVector(p4);
      vertexPVector(p5);
      endShape( CLOSE );
    } else {
      noFill(); 
      stroke(GRAY); 
      strokeWeight(4);
      // show shape 
      beginShape();    
      vertexPVector(p1);
      vertexPVector(p2);
      vertexPVector(p3);
      vertexPVector(p4);
      vertexPVector(p5);
      endShape( CLOSE );

      // show corners 
      fill(RED); 
      pointSpherePV(p1);
      pointSpherePV(p2);
      pointSpherePV(p3);
      pointSpherePV(p4);
      pointSpherePV(p5);

      strokeWeight(1); // back to default
    }//else
  }//method 

  void quadArray(PVector[] a1) {
    if (a1==null) 
      return;
    if (a1.length==0) 
      return;
    beginShape();
    for (PVector pv : a1) 
      vertex(pv.x, pv.y, pv.z);      
    // vertex(a1[0].x, a1[0].y, a1[0].z);
    endShape(CLOSE);
  }

  // colors ---
  // 
  void fillAndStroke( color f, color s ) {
    // set both colors at once (but different) 
    fill(f);
    stroke(s);
  }

  void fillAndStroke( color c ) {
    // set both at once (same color) 
    fill(c);
    stroke(c);
  }

  void fillAndNoStroke(color f) {
    // set fill color and switch stroke off
    fill(f);
    noStroke();
  }

  void strokeAndNoFill(color s) {
    // set stroke color and switch fill off
    noFill();
    stroke(s);
  }

  // crosshair ---
  void toogleCrosshair() {
    crosshair = !crosshair;
  }

  void crosshairOn() {
    crosshair=true;
  }

  void crosshairOff() {
    crosshair=false;
  }

  boolean crosshairState() {
    return crosshair;
  }

  //
} // class ThreeD
//
3 Likes

Hi @Chrisir,

(Thank you for the continuous help troughout the past year, you were actually one of those with kfrajer, jeremydouglass and GoToLoop who helped me a year ago on a similar issue.)

The idea of rendering with Keyshot is not from me, I have to give credit to this very straighforward article form Brendan Dawes: “Rendering 3D objects made with Processing”

He mentions using Autodesk Fusion 360 as well but both softwares come at a cost (one needs a montly/yearly subscription and the other one is crazy pricey). Free trial versions are available but you’ll get a watermark, at least with Keyshot.

I’ve just looked for alternatives, it looks like Lagoa (the free online rendering service featured in the blogpost I linked in my previous answer) has been bought-up by Autodesk and the only free solution I found is Luxrender.

4 Likes

Thank you!

POV-Ray and Blender might also work

http://www.povray.org/

2 Likes

https://funprogramming.org/153-Rendering-Processing-shapes-in-Blender.html

4 Likes