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. 😀
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! 😀
[ Code for Hemesh Beta 1.70 gx3 ]
Main Code
// HE_Mesh that can be colored and dissolved into thin air 😉 // by Amnon Owed @ https://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! 😀 // ...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 @ https://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); } }
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
Cool! 😀 Good to hear this post was useful.