//// // same as the warp mesh,only with quads // kinda makes more sense //// class QuadMesh{ private PImage warpTexture; private float alpha; // the alpha level for the texture private Point[][] mesh; private boolean[][] isPinned; private float[] uCoord; private float[] vCoord; public final int meshSize; private boolean bShowEdges; private boolean bShowPins; // smooting private Vector[][] laplace; // interactivity private boolean bSnapVertex; private int snapX; private int snapY; // pretty stuff private Point finalDrawPoint; private Point currentDrawPoint; public QuadMesh(PImage warpTexture){ this(warpTexture, 5); } public QuadMesh(PImage warpTexture, int meshSize){ this.warpTexture = warpTexture; this.alpha = 255; // the mesh must be at least 5 x 5 for proper interp smoothing if(meshSize < 5){ this.meshSize = 5; } else { this.meshSize = meshSize; } this.mesh = new Point[this.meshSize + 1][this.meshSize + 1]; this.isPinned = new boolean[this.meshSize + 1][this.meshSize + 1]; this.uCoord = new float[this.meshSize + 1]; this.vCoord = new float[this.meshSize + 1]; this.laplace = new Vector[this.meshSize + 1][this.meshSize + 1]; this.bShowEdges = false; this.bShowPins = false; // interactivity this.bSnapVertex = false; this.snapX = -1; this.snapY = -1; // teh pretty! this.finalDrawPoint = new Point(); this.currentDrawPoint = new Point(); this.reset(); } public void draw(){ // draw point interpolation and translation this.currentDrawPoint = this.currentDrawPoint.interp(.4, this.finalDrawPoint); pushMatrix(); translate(this.currentDrawPoint.x, this.currentDrawPoint.y); // interactivity if(this.bSnapVertex){ this.mesh[this.snapX][this.snapY].setTo(mouseX - this.currentDrawPoint.x, mouseY - this.currentDrawPoint.y, 0); } // we can always use a border // noFill(); // stroke(0); // rect(- 1, - 1, this.warpTexture.width + 2, this.warpTexture.height + 2); this.repeatCubicSmoothing(NORMAL_SMOOTH_PASSES); // draw the textured mesh this.drawMesh(); if(this.bShowPins){ this.drawPins(); } popMatrix(); } private void drawMesh(){ if(this.bShowEdges){ stroke(0, 0, 0, 70); } else { noStroke(); } fill(255, 255, 255, this.alpha); textureMode(NORMALIZED); beginShape(QUADS); texture(this.warpTexture); for(int i = 0; i < this.meshSize; i ++){ for(int j = 0; j < this.meshSize; j ++){ this.shadeQuad(i, j); } } endShape(); } //// // Draws the pins //// public void drawPins(){ noFill(); stroke(255, 0, 0); for(int i = 0; i <= this.meshSize; i ++){ for(int j = 0; j <= this.meshSize; j ++){ if(this.isPinned[i][j]){ ellipse(this.mesh[i][j].x, this.mesh[i][j].y, 6, 6); } } } } //// // Marks a textured quad //// public void shadeQuad(int i, int j){ vertex(this.mesh[i][j].x, this.mesh[i][j].y, this.uCoord[i], this.vCoord[j]); vertex(this.mesh[i + 1][j].x, this.mesh[i + 1][j].y, this.uCoord[i + 1], this.vCoord[j]); vertex(this.mesh[i + 1][j + 1].x, this.mesh[i + 1][j + 1].y, this.uCoord[i + 1], this.vCoord[j + 1]); vertex(this.mesh[i][j + 1].x, this.mesh[i][j + 1].y, this.uCoord[i], this.vCoord[j + 1]); } //// // Marks an untextured quad //// public void markQuad(int i, int j){ vertex(this.mesh[i][j].x, this.mesh[i][j].y); vertex(this.mesh[i + 1][j].x, this.mesh[i + 1][j].y); vertex(this.mesh[i + 1][j + 1].x, this.mesh[i + 1][j + 1].y); vertex(this.mesh[i][j + 1].x, this.mesh[i][j + 1].y); } //// // Attemps to find a good snap vertex //// public void snap(Point inputClick, float r){ Point click = new Point(inputClick.x - this.currentDrawPoint.x, inputClick.y - this.currentDrawPoint.y); this.bSnapVertex = false; for(int i = 0; i <= this.meshSize; i ++){ for(int j = 0; j <= this.meshSize; j ++){ if(click.distanceTo(this.mesh[i][j]) <= r){ this.bSnapVertex = true; this.snapX = i; this.snapY = j; this.isPinned[i][j] = true; break; } } if(this.bSnapVertex){ if(keyPressed){ if(key == CODED ){ if(keyCode == CONTROL){ this.bSnapVertex = false; this.isPinned[this.snapX][this.snapY] = false; this.snapX = -1; this.snapY = -1; } } } break; } } } //// // Releases any snap hold //// public void releaseSnap(){ this.bSnapVertex = false; this.snapX = -1; this.snapY = -2; //this.repeatCubicSmoothing(FAST_SMOOTH_PASSES); } //// // Smooths an input number of times //// public void repeatCubicSmoothing(int times){ for(int i = 0; i < times; i++){ cubicSmooting(); } } //// // Basic cubic smooting! //// private void cubicSmooting(){ // first, compute the laplacian for(int i = 2; i <= this.meshSize - 2; i ++){ for(int j = 2; j <= this.meshSize - 2; j ++){ this.laplace[i][j] = cubicFitX(i, j).average(cubicFitY(i, j)); } } // and apply that shit for(int i = 2; i <= this.meshSize - 2; i ++){ for(int j = 2; j <= this.meshSize - 2; j ++){ if(!this.isPinned[i][j]){ this.mesh[i][j].addScaledVector(0.5, this.laplace[i][j]); } } } } //// // Builds a cubic around a vertex in the x //// private Vector cubicFitX(int i, int j){ return cubicFit(this.mesh[i - 2][j], this.mesh[i - 1][j], this.mesh[i][j], this.mesh[i + 1][j], this.mesh[i + 2][j]); } //// // Builds a cubic around a vertex in the y //// private Vector cubicFitY(int i, int j){ return cubicFit(this.mesh[i][j - 2], this.mesh[i][j - 1], this.mesh[i][j], this.mesh[i][j + 1], this.mesh[i][j + 2]); } //// // Actual cubic smoothing algorimth //// private Vector cubicFit(Point A, Point B, Point C, Point D, Point E){ return new Vector((-A.x + 4 * B.x - 6 * C.x + 4 *D.x - E.x) / 6, (-A.y + 4 * B.y - 6 * C.y + 4 * D.y - E.y) / 6, (-A.z + 4 * B.z - 6 * C.z + 4 * D.z - E.z) / 6); } //// // Builds the initial mesh and sets all the needed attributes //// private void buildMesh(){ float w = this.warpTexture.width; float h = this.warpTexture.height; //build the quad table and init the pinned all to false for(int i = 0; i <= this.meshSize; i ++){ for(int j = 0; j <= this.meshSize; j ++){ this.mesh[i][j] = new Point(w * i / float(this.meshSize), h * j / float(this.meshSize)); this.laplace[i][j] = new Vector(); this.isPinned[i][j] = false; } } //and figure out out texture mapping for(int i = 0; i <= this.meshSize; i++){ uCoord[i] = i / float(this.meshSize); vCoord[i] = i / float(this.meshSize); } } //// // Contrains the border so smoothing does not shrink the mesh //// private void pinBorder(){ for(int i = 0; i <= this.meshSize; i++){ // top and bottom this.isPinned[0][i] = true; this.isPinned[1][i] = true; this.isPinned[this.meshSize - 1][i] = true; this.isPinned[this.meshSize][i] = true; //left and right this.isPinned[i][0] = true; this.isPinned[i][1] = true; this.isPinned[i][this.meshSize - 1] = true; this.isPinned[i][this.meshSize] = true; } } //// // Shows the constrains //// public void toggleConstraints(){ this.bShowEdges = !this.bShowEdges; this.bShowPins = !this.bShowPins; } //// // Turn is all off //// public void hideContraints(){ this.bShowEdges = false; this.bShowPins = false; } public void reset(){ this.buildMesh(); this.pinBorder(); } ///////////////////////// // GETTERS AND SETTERS // ///////////////////////// public Point[][] getMesh(){ return this.mesh; } public boolean[][] getIsPinned(){ return this.isPinned; } public boolean isPinned(int x, int y){ return this.isPinned[x][y]; } public void setIsPinned(boolean[][] isPinned){ this.isPinned = isPinned; } public void setAlpha(float alpha){ this.alpha = alpha; } public float getAlpha(){ return alpha; } public void setFinalDrawPoint(Point p){ this.finalDrawPoint.setTo(p); } public Point getFinalDrawPoint(){ return this.finalDrawPoint; } public void setCurrentDrawPoint(Point p){ this.currentDrawPoint.setTo(p); } }