class WarpMesh{ // the image private PImage warpTexture; private float alpha; // the alpha level for the texture // geometry tables private int meshSize; // the, well, mesh size private int vertexCount; // number of vertices private int triangleCount; // number of triangles private Point[] vertices; // geometry table: the actual vertices private boolean[] isPinned; // marks if a vertex is pinned private float[] uTex; // u texture coords private float[] vTex; // u texture coords // corner table info private int cornerCount; // number of corners (3 per tri); private int[] V; // vertex / triangle table private int[] O; // opposite table private int[] W; // mid-edge vertex indices for subdivision // gui and draw flags private boolean bShowVertices; private boolean bShowEdges; private int snapVertex; // the corner that is snapping to the mouse private boolean bSnapToMouse; // true if we are snapping //// // Constructor //// public WarpMesh(PImage warpTexture, int meshSize){ this.warpTexture = warpTexture; this.alpha = 255; this.meshSize = meshSize; this.vertexCount = this.meshSize * this.meshSize; this.triangleCount = 2 * (this.meshSize - 1) * (this.meshSize - 1); this.cornerCount = this.triangleCount * 3; this.vertices = new Point[vertexCount]; this.isPinned = new boolean[vertexCount]; this.uTex = new float[vertexCount]; this.vTex = new float[vertexCount]; this.V = new int[cornerCount]; this.O = new int[cornerCount]; this.W = new int[cornerCount]; this.bShowVertices = true; this.bShowEdges = true; this.bSnapToMouse = false; this.snapVertex = -1; this.buildVertices(); this.buildTriangleTable(); this.buildOppositeTable(); this.pinBorder(); } //// // Draw //// public void draw(){ if(this.bSnapToMouse){ if(this.snapVertex != -1){ this.getVertex(this.snapVertex).setTo(mouseX, mouseY, 0); } } // smoothing noStroke(); fill(color(255)); textureMode(NORMALIZED); beginShape(TRIANGLES); texture(this.warpTexture); for(int i = 0; i < this.triangleCount; i++){ shadeTriangle(i); } endShape(); stroke(color(0)); noFill(); if(this.bShowEdges){ beginShape(TRIANGLES); for(int i = 0; i < this.triangleCount; i++){ markTriangle(i); } endShape(); for(int i = 0; i < this.vertexCount; i++){ if(this.isPinned[i]){ ellipse(this.vertices[i].x, this.vertices[i].y, 8, 8); } } } } //// // Shades a triangle with the texture //// public void shadeTriangle(int t){ int c = t * 3; Point p1 = getVertex(c); Point p2 = getVertex(c + 1); Point p3 = getVertex(c + 2); vertex(p1.x, p1.y, uTex[v(c)], vTex[v(c)]); vertex(p2.x, p2.y, uTex[v(c + 1)], vTex[v(c + 1)]); vertex(p3.x, p3.y, uTex[v(c + 2)], vTex[v(c + 2)]); } //// // Marks the vertices of a triangle //// public void markTriangle(int t){ Point p1 = getVertex((t * 3)); Point p2 = getVertex((t * 3) + 1); Point p3 = getVertex((t * 3) + 2); vertex(p1.x, p1.y, p1.z); vertex(p2.x, p2.y, p2.z); vertex(p3.x, p3.y, p3.z); } //// // Sees if anything can be dragged by the mouse, returns true if it can //// public boolean mouseClicked(float snapRadius){ Point mouse = new Point(mouseX, mouseY, 0); for(int i = 0; i < this.cornerCount; i++){ if(this.getVertex(i).distanceTo(mouse) < snapRadius){ this.snapVertex = i; this.bSnapToMouse = true; this.isPinned[v(i)] = true; return true; } } return false; } //// // Release the snap //// public void releaseSnap(){ this.snapVertex = -1; this.bSnapToMouse = false; } /////////////////////// // Conrner Table Ops // /////////////////////// private int t(int c){ return int(c / 3); } private int n(int c){ return (3 * int(c / 3)) + ((c + 1) % 3); } private int p(int c){ return (3 * int(c / 3)) + ((c + 2) % 3); } private int v(int c){ return V[c]; } private int o(int c){ return O[c]; } private int l(int c){ return o(n(c)); } private int r(int c){ return o(p(c)); } private boolean isBoarder(int c){ return O[c] == -1; } private Point getVertex(int c){ return this.vertices[V[c]]; } ////////////////////// // Contructor Tools // ////////////////////// private void buildVertices(){ this.resetVertices(); float w = this.warpTexture.width; float h = this.warpTexture.height; // first the vertex table for(int x = 0; x < this.meshSize; x ++){ for(int y = 0; y < this.meshSize; y ++){ this.vertices[x * this.meshSize + y].setTo(w * x / float(this.meshSize - 1), h * y / float(this.meshSize - 1), 0); } } // and now the texutre table for(int i = 0; i < this.vertexCount; i ++){ uTex[i] = 1.0 - this.vertices[i].x / w; vTex[i] = this.vertices[i].y / h; } } private void buildTriangleTable(){ for(int i = 0; i < this.meshSize - 1; i ++){ for(int j = 0; j < this.meshSize - 1; j ++){ // square of vertices defines to triangles: // tri one V[(i * (this.meshSize - 1) + j) * 6] = i * this.meshSize + j; V[(i * (this.meshSize - 1) + j) * 6 + 2] = (i + 1) * this.meshSize + j; V[(i * (this.meshSize - 1) + j) * 6 + 1] = (i + 1) * this.meshSize + j + 1; // tri two V[(i * (this.meshSize - 1) + j) * 6 + 3] = i * this.meshSize + j; V[(i * (this.meshSize - 1) + j) * 6 + 5] = (i + 1) * this.meshSize + j + 1; V[(i * (this.meshSize - 1) + j) * 6 + 4] = i * this.meshSize + j + 1; } } } private void buildOppositeTable(){ // init the whole board to -1 (no opposites) for(int i = 0; i < this.cornerCount; i++){ O[i] = -1; } // and figure out which is the opposite and which is not for(int i = 0; i < this.cornerCount; i ++){ for(int j = i + 1; j < this.cornerCount; j ++){ if((v(n(i)) == v(p(j))) && (v(p(i)) == v(n(j)))){ // they match up, so oppisitify the O[i] = j; O[j] = i; } } } } private void resetVertices(){ for(int i = 0; i < this.vertexCount; i ++){ this.vertices[i] = new Point(); } } private void pinBorder(){ for(int i = 0; i < this.vertexCount; i++){ this.isPinned[i] = false; } for(int i = 0; i < this.cornerCount; i ++){ if( O[i] == -1){ this.isPinned[v(n(i))] = true; } } } }