Hemesh Tutorial: Color + Dissolve


I usually have about a dozen projects/experiments up and running. All in different stages of development. Some with a big code history, others with a tiny idea that I’d like to explore further. And those semi-active projects are just a fraction of the complete list of topics I’d like to investigate! ;-) I’ve tried to structure my experiments a bit, which kind of worked, but I still like shifting my attention intuitively between them. Which is great, except that projects never really get finished. So many of them are basically in a constant state of experimental development.

Given that context, I’m trying to move my blogposts a bit more towards sharing code snippets, as in my last post, or bigger library-specific tutorials, as I will do in this post. Now let’s get to the content! ;-) The above video shows quite accurately what you can expect from this blogpost and it’s source code. I’ve been working again with the awesome Hemesh library for mesh creation and manipulation by Frederik Vanhoutte (W:Blut). This tutorial is an educational derivative of those experiments. I want to share two of the areas I’ve been exploring, which is coloring individual faces on a mesh and dissolving a mesh. Both rely on the interconnectivity information that Hemesh can provide, allowing you to get a face’s direct neighbours. These explorations are in an early stage and could provide the spark for many more ideas. But I’d like to share what I’ve got now, before I intuitively move on again to an entirely different experiment. :D

At first I thought to introduce the coloring and the dissolve effect separately. But since they share so much code, it would be a bit repetitious. Instead I’ve opted for fully commenting the source code. So almost every line is explained. For convenience I’ve also added a description of the in-sketch keyboard shortcuts on the screen (and thus also in the code). So running the code and browsing through the source, should really clear up what I’m doing. My route may or may not be a good one, but it’s effective. Which adheres to my coding philosophy: whatever works!

Let me take this opportunity to state the obvious. You will need to have the Hemesh library installed for this example to work! You can download it here. Go here for general instructions on installing contributed libraries for Processing. A practical sidenote. Hemesh just happens to be in a state of transition right now, working towards release 2.0. This is of course excellent, because things will only get better. However it also makes sharing code a bit difficult, because classes and packages are being renamed, (re)moved etc. So anticipating the troubles of some users, I have added two versions of my code example. The first includes comments and is compatible with the latest -at the time of writing- Hemesh version: Beta 1.70 gx3 (hemesh_b1_7agx3.zip). The second code example (without comments to share some space) is compatible with an earlier, but often downloaded, Hemesh version: Beta 1.50a. All code has been tested with the latest stable Processing release, which is Processing 1.5.1. Please make sure you are using the right versions, when trying to run this code. When you run into errors (such as “missing library” or “cannot find a class”), make sure to doublecheck the above.

At the start of the sketch a new mesh is created, either completely white or randomly colored. You can set the colors of the whole mesh yourself by either pressing ‘w’ for white or ‘r’ for random colors. With the LEFT mouse button, you can draw on the mesh. There are 32 drawing colors loaded at the start. You can cycle through the colors with ‘c’. You can keep the key pressed, to keep cycling through the colors, while you are drawing on the mesh. When you press the RIGHT mouse button the mesh will start to dissolve. You can press it more than once if you want to. The shape will keep dissolving until it’s completely gone. When there is nothing left, a new shape will be created automatically. Finally, you can use ‘f’ to toggle the display of all faces versus only the dissolving ‘faces’. All of these aspects are also shown in the video. For now, feel free to copy-paste the main code and the custom classes code into the PDE to play with it interactively. Good luck and have fun! :D

[ Code for Hemesh Beta 1.70 gx3 ]

Main Code

// HE_Mesh that can be colored and dissolved into thin air ;-)
// by Amnon Owed @ http://amnonp5.wordpress.com (14-03-2012)
// Tested under Hemesh Beta 1.70 gx3 & Processing 1.5.1

import processing.opengl.*;
import wblut.hemesh.tools.*;
import wblut.hemesh.subdividors.*;
import wblut.hemesh.creators.*;
import wblut.hemesh.core.*;
import wblut.core.processing.*;
import wblut.geom.core.*;

WB_Render render; // the renderer to easily display things
HET_Selector selector; // the selector to pick faces
HE_MeshDissolveColor mesh; // the mesh from a custom class (see class below)

int currentDrawColor; // keep track of the current drawing color
color[] drawColors; // array to hold all the drawing colors
boolean onlyDissolve; // toggle display only dissolving faces

void setup() {
  size(800, 800, OPENGL);
  render = new WB_Render(this); // initialize the renderer
  selector = new HET_Selector(this); // initialize the selector
  createColors(32); // create a number (here 32) of colors in the HSB range
  createNewMesh(); // create a new mesh
}

void draw() {
  background(255);

  // display the keyboard shortcuts on the top-left of the screen
  fill(0);
  text("c = cycle through drawColors", 10, 50);
  text("w = set mesh colors to white", 10, 70);
  text("r = randomize mesh colors", 10, 90);
  text("f = display only dissolving faces", 10, 110);

  // display the current drawing color on the top-left of the screen
  stroke(0);
  fill(drawColors[currentDrawColor]);
  rect(10, 10, 20, 20);

  // turn on the lights to prettify the  geometry
  lights();

  // center and slowly rotate the mesh
  translate(width/2, height/2);
  rotateX(frameCount * 0.005);
  rotateY(frameCount * 0.008);

  // run these two object functions (see class for more info)
  mesh.selector();
  mesh.display();

  // if there are no more (dissolving) faces, create a new mesh
  if (mesh.numberOfFaces() == 0 && mesh.dFaces.size() == 0) { createNewMesh(); }
}

// if the mouse is pressed...
void mousePressed() {
  // on a face and with the RIGHT mouse button...
  if (selector.lastKey() != null && mouseButton == RIGHT) {
    HE_Face face = mesh.getFaceByKey(selector.lastKey());
    if (face != null) {
      // start dissolving this face! :D
      // ...and set in motion a chain of events that will
      // ultimately lead to the full dissolve of the mesh
      mesh.dissolveFace(face);
    }
  }
}

// keyboard shortcuts (see on-screen display for more info)
void keyPressed() {
  if (key == 'c') { currentDrawColor++;  if (currentDrawColor>drawColors.length-1) { currentDrawColor = 0; } }
  if (key == 'w') { mesh.setColors(HE_MeshDissolveColor.WHITE); }
  if (key == 'r') { mesh.setColors(HE_MeshDissolveColor.RANDOM); }
  if (key == 'f') { onlyDissolve = !onlyDissolve; }
}

// create a number of drawing colors from the (continuous) HSB range
void createColors(int numDrawColors) {
  drawColors = new color[numDrawColors];
  colorMode(HSB, 360, 100, 100);
  for (int i=0; i<drawColors.length; i++) {
    drawColors[i] = color((float)i/drawColors.length*360, 100, 100);
  }
  colorMode(RGB, 255, 255, 255);
}

// create the mesh, subdivide it and set the colors (white or random)
// note that this could be ANY mesh that you can make with a creator
// the custom class extends HE_Mesh, so all functionality is inherited
void createNewMesh() {
  mesh = new HE_MeshDissolveColor(new HEC_Cube().setEdge(400));
  mesh.subdivide(new HES_CatmullClark(), 4);
  mesh.setColors(int(random(2)));
}

Custom Classes Code

// a class that extends HE_Mesh on two seperate areas:
// 1. Colored faces (display and draw colors per face, instantly set all colors)
// 2. Dissolvable mesh (dissolve effect that spreads to neighbour faces until mesh is gone)

class HE_MeshDissolveColor extends HE_Mesh {
  // hashmap to hold the colors based on the unique face key
  HashMap <Integer, Integer> colors = new HashMap <Integer, Integer> ();
  // arraylist to hold all the dissolving faces (while they are dissolving)
  ArrayList <DissolvingFace> dFaces = new ArrayList <DissolvingFace> ();

  // static variables for setting the color
  static public final int WHITE = 0;
  static public final int RANDOM = 1;

  // extends the original constructor
  HE_MeshDissolveColor(HEC_Creator creator) {
    super(creator);
  }

  // this function handles the selector that enables picking faces with the mouse
  void selector() {
    // (invisibly) draw the selector buffer
    noFill();
    noStroke();
    render.drawFaces(selector, mesh);

    // if a face is selected and the LEFT mouse button is pressed
    if (selector.lastKey() != null && mousePressed && mouseButton == LEFT) {
      HE_Face face = mesh.getFaceByKey(selector.lastKey());
      // color this face (and it's neighbours) using the current drawing color
      colorFace(face, drawColors[currentDrawColor]);
    }
  }

  // this function handles the display of the faces and actually also all the stuff for the dissolving faces
  void display() {
    // toggle based on the global variable 'f' to only show dissolving faces
    if (!onlyDissolve) {
      // go over all the faces in the mesh
      for (HE_Face f : getFacesAsList()) {
        // fill using their unique color (stored in the hashmap with their unique face key)
        fill(colors.get(f.key()));
        // draw the face
        render.drawFace(f);
      }
    }
    // go over all the dissolving faces in the arraylist (backwards because we are also removing them after interpolation)
    for (int i=dFaces.size()-1; i>=0; i--) {
      DissolvingFace df = dFaces.get(i);
      df.interpolate(); // interpolate into oblivion
      df.remove(); // remove when invisible
      df.display(); // display the face (actually it's a polygon)
    }
  }

  // you have been targeted for termination, uhm, I mean, put up a face for dissolvement
  void dissolveFace(HE_Face face) {
    // create a DissolvingFace from the face and add it to the arraylist
    dFaces.add(new DissolvingFace(face));
    // delete the original face (and all references to it) from the mesh
    deleteFace(face);
  }

  // set all the colors in the mesh (by default to white or random for each face)
  void setColors(int colorMode) {
    // clear the whole list of colors (start from scratch)
    colors.clear();
    color c = color(255);
    // go over all the faces in the mesh
    for (HE_Face f : getFacesAsList()) {
      if (colorMode == RANDOM) { c = color(random(255), random(255), random(255)); }
      // put the color in it's unique place in the hashmap (based on the unique face key)
      colors.put(f.key(), c);
    }
  }

  // color individual faces (and their neigbours) based on an input color and it's original color
  void colorFace(HE_Face face, color drawColor) {
    // get the current face color
    color c = colors.get(face.key());
    // linearly interpolate between the current color to the used drawing color
    c = lerpColor(c, drawColor, 0.3);
    // put the slightly interpolated color back into it's unique place
    colors.put(face.key(), c);
    // since only drawing one face at the a time is very time-consuming
    // it's better to increase the 'brush size' somewhat and also include
    // all the neighbour faces of the originally selected face
    List <HE_Face> neighbors = face.getNeighborFaces(); // get all the neighbours
    for (HE_Face f : neighbors) {
      // do the same as above for all the neighbours
      c = colors.get(f.key());
      c = lerpColor(c, drawColor, 0.3);
      colors.put(f.key(), c);
    }
  }
}

// class for a single dissolving face (create from an original face in the mesh)
class DissolvingFace {
  HE_Face face; // keep (a link to) the original face
  WB_Point3d center; // the center of the face
  WB_ExplicitPolygon polygon; // the working polygon to change and display
  WB_Point3d[] points; // the original face points
  WB_Point3d[] interpolatedPoints; // the interpolated dissolving face points
  float interpolation, speed; // the amount of interpolation it's speed
  color c; // the original color (once the face is deleted, it's color is also up for removal)

  DissolvingFace(HE_Face face) {
    this.face = face;
    c = mesh.colors.get(face.key()); // get the original face color
    center = face.getFaceCenter(); // get the face center
    polygon = face.toPolygon(); // turn the face into a polygon
    points = polygon.getPoints(); // get the original points from this polygon
    interpolatedPoints = new WB_Point3d[points.length]; // initialize the array of interpolated points
    speed = random(0.05, 0.25); // set the dissolve speed randomly (within given boundaries)
  }

  // the below functions run continuously for all dissolving faces
  // interpolate() = each dissolving face is interpolated into it's individual center
  // remove() = once it reaches that destination, it is removed from the arraylist (then it's really gone)
  // display() = while it exists, it is displayed, of course with the original face color

  void interpolate() {
    interpolation += speed; // interpolate from 0 to 1 (and beyond)
    // change the location of all the points
    for (int i=0; i<interpolatedPoints.length; i++) {
      // set the value based on the original location, the center and the amount of interpolation (0 to 1)
      interpolatedPoints[i] = WB_Point3d.interpolate(points[i], center, interpolation);
    }
    // set the polygon based on the interpolated points
    polygon.set(interpolatedPoints, interpolatedPoints.length);
  }

  void remove() {
    // if the interpolation is bigger than 1 (aka the points have all reached the center)
    if (interpolation > 1) {
      // spread the dissolve effect to neighbour faces
      List <HE_Face> neighbors = face.getNeighborFaces(); // get all the neighbours of the face
      for (HE_Face face : neighbors) {
        // dissolve them ALL! ;-)
        mesh.dissolveFace(face);
      }
      // this dissolving face is a goner, so remove it from the arraylist
      mesh.dFaces.remove(this);
    }
  }

  void display() {
    // display the dissolving face (in interpolated polygon form) using it's original color
    fill(c);
    render.drawPolygon(polygon);
  }
}

[ Code for Hemesh Beta 1.50a ]

Main Code

// HE_Mesh that can be colored and dissolved into thin air ;-)
// by Amnon Owed @ http://amnonp5.wordpress.com (14-03-2012)
// Tested under Hemesh Beta 1.50a & Processing 1.5.1

import processing.opengl.*;
import wblut.processing.*;
import wblut.hemesh.tools.*;
import wblut.hemesh.subdividors.*;
import wblut.hemesh.creators.*;
import wblut.hemesh.core.*;
import wblut.geom.*;

WB_Render render;
HET_Selector selector;
HE_MeshDissolveColor mesh;

int currentDrawColor;
color[] drawColors;
boolean onlyDissolve;

void setup() {
  size(800, 800, OPENGL);
  render = new WB_Render(this);
  selector = new HET_Selector(this);
  createColors(32);
  createNewMesh();
}

void draw() {
  background(255);

  fill(0);
  text("c = cycle through drawColors", 10, 50);
  text("w = set mesh colors to white", 10, 70);
  text("r = randomize mesh colors", 10, 90);
  text("f = display only dissolving faces", 10, 110);

  stroke(0);
  fill(drawColors[currentDrawColor]);
  rect(10, 10, 20, 20);

  lights();

  translate(width/2, height/2);
  rotateX(frameCount * 0.005);
  rotateY(frameCount * 0.008);

  mesh.selector();
  mesh.display();

  if (mesh.numberOfFaces() == 0 && mesh.dFaces.size() == 0) { createNewMesh(); }
}

void mousePressed() {
  if (selector.lastKey() != null && mouseButton == RIGHT) {
    HE_Face face = mesh.getFaceByKey(selector.lastKey());
    if (face != null) {
      mesh.dissolveFace(face);
    }
  }
}

void keyPressed() {
  if (key == 'c') { currentDrawColor++;  if (currentDrawColor>drawColors.length-1) { currentDrawColor = 0; } }
  if (key == 'w') { mesh.setColors(HE_MeshDissolveColor.WHITE); }
  if (key == 'r') { mesh.setColors(HE_MeshDissolveColor.RANDOM); }
  if (key == 'f') { onlyDissolve = !onlyDissolve; }
}

void createColors(int numDrawColors) {
  drawColors = new color[numDrawColors];
  colorMode(HSB, 360, 100, 100);
  for (int i=0; i<drawColors.length; i++) {
    drawColors[i] = color((float)i/drawColors.length*360, 100, 100);
  }
  colorMode(RGB, 255, 255, 255);
}

void createNewMesh() {
  mesh = new HE_MeshDissolveColor(new HEC_Cube().setEdge(400));
  mesh.subdivide(new HES_CatmullClark(), 4);
  mesh.setColors(int(random(2)));
}

Custom Classes Code

class HE_MeshDissolveColor extends HE_Mesh {
  HashMap <Integer, Integer> colors = new HashMap <Integer, Integer> ();
  ArrayList <DissolvingFace> dFaces = new ArrayList <DissolvingFace> ();

  static public final int WHITE = 0;
  static public final int RANDOM = 1;

  HE_MeshDissolveColor(HEC_Creator creator) {
    super(creator);
  }

  void selector() {
    noFill();
    noStroke();
    render.drawFaces(selector, mesh);

    if (selector.lastKey() != null && mousePressed && mouseButton == LEFT) {
      HE_Face face = mesh.getFaceByKey(selector.lastKey());
      colorFace(face, drawColors[currentDrawColor]);
    }
  }

  void display() {
    if (!onlyDissolve) {
      for (HE_Face f : getFacesAsArrayList()) {
        fill(colors.get(f.key()));
        render.drawFace(f);
      }
    }
    for (int i=dFaces.size()-1; i>=0; i--) {
      DissolvingFace df = dFaces.get(i);
      df.interpolate();
      df.remove();
      df.display();
    }
  }

  void dissolveFace(HE_Face face) {
    dFaces.add(new DissolvingFace(face));
    deleteFace(face);
  }

  void setColors(int colorMode) {
    colors.clear();
    color c = color(255);
    for (HE_Face f : getFacesAsArrayList()) {
      if (colorMode == RANDOM) { c = color(random(255), random(255), random(255)); }
      colors.put(f.key(), c);
    }
  }

  void colorFace(HE_Face face, color drawColor) {
    color c = colors.get(face.key());
    c = lerpColor(c, drawColor, 0.3);
    colors.put(face.key(), c);
    ArrayList <HE_Face> neighbors = face.getNeighborFaces();
    for (HE_Face f : neighbors) {
      c = colors.get(f.key());
      c = lerpColor(c, drawColor, 0.3);
      colors.put(f.key(), c);
    }
  }
}

class DissolvingFace {
  HE_Face face;
  WB_Point center;
  WB_ExplicitPolygon polygon;
  WB_Point[] points;
  WB_Point[] interpolatedPoints;
  float interpolation, speed;
  color c;

  DissolvingFace(HE_Face face) {
    this.face = face;
    c = mesh.colors.get(face.key());
    center = face.getFaceCenter();
    polygon = face.toPolygon();
    points = polygon.getPoints();
    interpolatedPoints = new WB_Point[points.length];
    speed = random(0.05, 0.25);
  }

  void interpolate() {
    interpolation += speed;
    for (int i=0; i<interpolatedPoints.length; i++) {
      interpolatedPoints[i] = WB_Point.interpolate(points[i], center, interpolation);
    }
    polygon.set(interpolatedPoints, interpolatedPoints.length);
  }

  void remove() {
    if (interpolation > 1) {
      ArrayList <HE_Face> neighbors = face.getNeighborFaces();
      for (HE_Face face : neighbors) {
        mesh.dissolveFace(face);
      }
      mesh.dFaces.remove(this);
    }
  }

  void display() {
    fill(c);
    render.drawPolygon(polygon);
  }
}
About these ads
Comments
2 Responses to “Hemesh Tutorial: Color + Dissolve”
  1. Thanks so much for posting this, I’ve used the class to add colour to my STL imports. I’ve added the tiny code ammendment to make this possible on my blog:

    http://tmblr.co/ZuWVByIAkc4p

    plummerfernandez.tumblr.com

Follow

Get every new post delivered to your Inbox.

Join 106 other followers

%d bloggers like this: