• fullscreen
  • DLAparticle.pde
  • ball.pde
  • control.pde
  • controlPanel.pde
  • createBackgroundImg.pde
  • createMask.pde
  • createModifier.pde
  • drawingMachines3D.pde
  • masks.pde
  • modifiers.pde
  • scanConnector.pde
  • scanContainer.pde
  • scanMask.pde
  • scanModifier.pde
  • /*
     * DLAparticle class
     *
     * Difussion-Limited Aggregation particle
     */
    
    public class DLAparticle{
      public int x;               // x position
      public int y;               // y position
      public int w;               // region's width
      public int h;               // region's height
      public int xPrev;           // previous x position
      public int yPrev;           // previous y position
      public boolean aggregated;  // controls if it's already aggregated
    
    
      //
      // Constructor
      //
    
      public DLAparticle(int tempX, int tempY, int tempW, int tempH){
        x = tempX;
        y = tempY;
        w = tempW;
        h = tempH;
        xPrev = x;
        yPrev = y;
        aggregated = false;
      }
    
    
      //
      // Class Methods 
      //
    
      // update
      public void update(){
        switch(int(random(8))){
          case 0: 
            if(x + 1 < w){
              xPrev = x;
              x++;
            }
            break;
          case 1: 
            if(x + 1 < w && y + 1 < h){
              xPrev = x;
              yPrev = y;
              x++;
              y++;
            }
            break;
          case 2:  
            if(y + 1 < h){
              yPrev = y;
              y++;
            }
            break;
          case 3:
            if(x - 1 >= 0 && y + 1 < h){
              xPrev = x;
              yPrev = y;
              x--;
              y++;
            }
            break;
          case 4:  
            if(x - 1 >= 0){
              xPrev = x;
              x--;
            }
            break;
          case 5: 
            if(x - 1 >= 0 && y - 1 >= 0){
              xPrev = x;
              yPrev = y;
              x--;
              y--;
            }
            break;
          case 6:  
            if(y - 1 >= 0){
              yPrev = y;
              y--;
            }
            break;
          case 7: 
            if(x + 1 < w && y - 1 >= 0){
              xPrev = x;
              yPrev = y;
              x++;
              y--;
            }
            break;
        }
      }
    
      
      // checkForAggregation
      public int[] checkForAggregation(boolean[] m, boolean val){
        int[] aggregationPos = {-1,-1};
        if(!aggregated && m[x + y*w] == val){
          aggregated = true;
          aggregationPos[0] = xPrev;
          aggregationPos[1] = yPrev;
        }
        return aggregationPos;
      }
    
    }
    
    
    /*
     * Ball class
     */
    
    public class Ball{
      public float x;     // x position
      public float y;     // y position
      public float vx;    // velocity in x direction
      public float vy;    // velocity in y direction
      public float diam;  // ball diameter
      public color col;   // ball color
    
    
      //
      // Constructor
      //
      
      public Ball(float tempX, float tempY, float tempVx, float tempVy, float tempDiam, color tempCol){
        x = tempX;
        y = tempY;
        vx = tempVx;
        vy = tempVy;
        diam = tempDiam;
        col = tempCol;
      }
      
      
      //
      // Class Methods 
      //
    
      // update
      public void update(PGraphics g){
        x += vx;
        y += vy; 
        
        if(x < 0){
          x = 0;
          vx = -vx;
        }
        else if(x > g.width){
          x = g.width;
          vx = -vx;
        }
        if(y < 0){
          y = 0;
          vy = -vy;
        }
        else if(y > g.height){
          y = g.height;
          vy = -vy;
        }
      }
      
      
      // paint
      public void paint(PGraphics g){
        g.beginDraw();
        g.fill(col);
        g.ellipse(x,y,diam,diam);
        g.endDraw();
      }
    
    }
    
    
    /*
     * Scene controls
     */
    
    void mouseDragged(){
      noCursor();
      rotX -= map(mouseY-pmouseY,-height,height,-TWO_PI,TWO_PI);
      rotY -= map(mouseX-pmouseX,-width,width,-TWO_PI,TWO_PI);
    }
    
    
    void mouseReleased(){
      cursor();
    }
    
    
    void keyPressed(){
      switch(keyCode){
        case UP:
          zoom *= 1.02;
          break;
        case DOWN:
          zoom /= 1.02;
          break;
        case LEFT:
          resolution++;
          if(resolution > 5){
            resolution = 5;
          }
          else{
            if(drawMask){
              sMask = createMask(scans[currentScan],maskType);
              sMask.reduceResolution(resolution);
              counter = 0;
            }
            else if(drawModifier){
              sModifier = createModifier(scans[currentScan],modifierType);
              sModifier.reduceResolution(resolution);
              counter = 0;
            }
          }
          break;
        case RIGHT:
          resolution--;
          if(resolution < 1){
            resolution = 1;
          }
          else{
            if(drawMask){
              sMask = createMask(scans[currentScan],maskType);
              sMask.reduceResolution(resolution);
              counter = 0;
            }
            else if(drawModifier){
              sModifier = createModifier(scans[currentScan],modifierType);
              sModifier.reduceResolution(resolution);
              counter = 0;
            }
          }
          break;
      }
      
      if(key == ' '){
        psychedelic = !psychedelic;
      }
      if(key == 'l'){
        asLines = !asLines;
        asPixels = false;
      }
      if(key == 'p'){
        asPixels = !asPixels;
        asLines = false;
      }
      if(key == '+'){
        pixelSize++;
        if(pixelSize > 3) pixelSize = 3;
      }
      if(key == '-'){
        pixelSize--;
        if(pixelSize < 1) pixelSize = 1;
      } 
      if(key == 'c'){
        if(drawMask) sMask.centerOnCursor();
        else sModifier.centerOnCursor();
        counter = 0;
      } 
      if(key == 's'){
        //if(drawMask) sMask.savePoints();
        //else if(drawModifier) sModifier.savePoints();
        //save("pic.jpg");
      }
      if(key == 'i'){
        //if(drawMask) sMask.pointCoordinates();
        //else if(drawModifier) sModifier.pointCoordinates();
      }
      if(key == 'z'){
        //if(drawMask) sMask.scaleFactor(1.1);
        //else if(drawModifier) sModifier.scaleFactor(1.1);
      }
    }
    
    
    /*
     * ControlP5 selection panel
     */
    
    void controlPanel(){
      cp5 = new ControlP5(this);
      cp5.setAutoDraw(false);
    
      // Scan selector
      scanButtons = cp5.addRadioButton("scanSelector")
                       .setPosition(40,40)
                       .setSize(20,20)
                       .setItemsPerRow(10)
                       .setSpacingColumn(10)
                       .setSpacingRow(10)
                       .setNoneSelectedAllowed(false);
    
      for(int i = 0; i < scans.length; i++){
        scanButtons.addItem("scan"+i,i);
      }
      
      scanButtons.hideLabels();
      scanButtons.activate(currentScan);
    
      
      // ScanMask selector
      maskButtons = cp5.addRadioButton("maskSelector")
                       .setPosition(2*40+(scans.length)*(20+10),40)
                       .setSize(20,20)
                       .setItemsPerRow(10)
                       .setSpacingColumn(10)
                       .setSpacingRow(10)
                       .setNoneSelectedAllowed(true);
      
      for(int i = 0; i < maskTypes.length; i++){
        maskButtons.addItem(maskTypes[i],i);
      }
      
      maskButtons.hideLabels();
      
    
      // ScanModifier selector
      modifierButtons = cp5.addRadioButton("modifierSelector")
                           .setPosition(3*40+(scans.length+maskTypes.length)*(20+10),40)
                           .setSize(20,20)
                           .setItemsPerRow(10)
                           .setSpacingColumn(10)
                           .setSpacingRow(10)
                           .setNoneSelectedAllowed(true);
      
      for(int i = 0; i < modifierTypes.length; i++){
        modifierButtons.addItem(modifierTypes[i],i);
      }
      
      modifierButtons.hideLabels();
      
      
      // Morphing toggle
      morphingToggle = cp5.addToggle("morphing",drawMorphing)
                          .setPosition(4*40+(scans.length+maskTypes.length+modifierTypes.length)*(20+10),40)
                          .setSize(20,20)
                          .setLabel("");
    }
    
    
    
    // Manage events
    void controlEvent(ControlEvent event){
      if(event.isFrom(scanButtons)){
        if(event.getValue() >= 0){
          currentScan = int(event.getValue());
          
          if(drawMask || drawMorphing) sMask = createMask(scans[currentScan],maskType);
          else if(drawModifier) sModifier = createModifier(scans[currentScan],modifierType);
          
          resolution = initResolution;
          counter = 0;
          if(drawMorphing){
            morphingToggle.setValue(false);
            drawMask = true;
            drawModifier = false;
            drawMorphing = false;
          }
        }
      }
     
      if(event.isFrom(maskButtons)){
        if(int(event.getValue()) >= 0){
          maskType = maskTypes[int(event.getValue())];
          sMask = createMask(scans[currentScan],maskType);
          resolution = initResolution;
          counter = 0;
          drawMask = true;
          drawModifier = false;
          drawMorphing = false;
          
          modifierButtons.deactivateAll();
          morphingToggle.setState(false);
        }
        else if(drawMask){
          maskType = "empty";
          sMask = createMask(scans[currentScan],maskType);
          resolution = initResolution;
          counter = 0;
          drawMask = true;
          drawModifier = false;
          drawMorphing = false;
        }
      }
      
      if(event.isFrom(modifierButtons)){
        if(int(event.getValue()) >= 0){
          modifierType = modifierTypes[int(event.getValue())];
          sModifier = createModifier(scans[currentScan],modifierType);
          resolution = initResolution;
          counter = 0;
          drawMask = false;
          drawModifier = true;
          drawMorphing = false;
          
          maskButtons.deactivateAll();
          morphingToggle.setState(false);
        }
        else if(drawModifier){
          maskType = "empty";
          sMask = createMask(scans[currentScan],maskType);
          resolution = initResolution;
          counter = 0;
          drawMask = true;
          drawModifier = false;
          drawMorphing = false;
        }
      }
    
      if(event.isFrom(morphingToggle)){
        if(morphingToggle.getState()){
          if(scans.length > 1){
            morphingIterator = 1;
            sConnector = new ScanConnector(scans[morphingIterator-1],scans[morphingIterator]);
            resolution = initResolution;
            counter = 0;
            drawMask = false;
            drawModifier = false;
            drawMorphing = true;
            
            maskButtons.deactivateAll();
            modifierButtons.deactivateAll();        
          }
        }
        else if(drawMorphing){
          maskType = "empty";
          sMask = createMask(scans[currentScan],maskType);
          resolution = initResolution;
          counter = 0;
          drawMask = true;
          drawModifier = false;
          drawMorphing = false;
        }
      }
    
    }
    
    
    /*
     * Creates a background image with a circular gradient
     */
    
    PImage createBackgroundImg(color col){
      PImage img = createImage(width,height,RGB);
    
      img.loadPixels();
      for (int y = 0; y < height; y++){
        for (int x = 0; x < width; x++){
          float distance = sqrt(sq(x-width/2)+sq(y-height/2));
          float gradientR = map(distance,0,width/2,red(col),80);
          float gradientG = map(distance,0,width/2,green(col),80);
          float gradientB = map(distance,0,width/2,blue(col),80);
          img.pixels[x + y*width] = color(gradientR,gradientG,gradientB);
        }
      }
      img.updatePixels();
      
      return img;
    }
    
    
    /*
     * Returns a ScanMask of the selected type
     */
    
    ScanMask createMask(ScanContainer s, String type){
      if(type == "empty"){
        ScanMask m = new EmptyMask();
        m.create(s,true);
        return m;
      }
      else if(type == "linear"){
        ScanMask m = new LinearMask();
        m.create(s,true);
        return m;
      }
      else if(type == "circular"){
        ScanMask m = new CircularMask();
        m.create(s,true);
        return m;
      }
      else if(type == "random"){
        ScanMask m = new RandomMask();
        m.create(s,false);
        return m;
      }
      else if(type == "balls"){
        ScanMask m = new BallsMask();
        m.create(s,false);
        return m;
      }
      else if(type == "traces"){
        ScanMask m = new TracesMask();
        m.create(s,false);
        return m;
      }
      else if(type == "cursor"){
        ScanMask m = new CursorMask();
        m.create(s,false);
        return m;
      }
      else if(type == "diffusion"){
        ScanMask m = new DLAMask();
        m.create(s,false);
        return m;
      }
      else{
        ScanMask m = new EmptyMask();
        m.create(s,true);
        return m;
      }
    }
    
    
    /*
     * Returns a ScanModifier of the selected type
     */
    
    ScanModifier createModifier(ScanContainer s, String type){
      if(type == "empty"){
        ScanModifier m = new EmptyModifier();
        m.create(s);
        return m;
      }
      else if(type == "convolve"){
        ScanModifier m = new ConvolveModifier();
        m.create(s);
        return m;
      }
      else if(type == "ballMod"){
        ScanModifier m = new BallModifier();
        m.create(s);
        return m;
      }
      else{
        ScanModifier m = new EmptyModifier();
        m.create(s);
        return m;
      }
    }
    
    
    /* 
     * 3D drawing machines (JGC).
     *
     * Description: Reads and represents scans obtained with the Kinect 3D scanner.
     *   http://www.openprocessing.org/sketch/78606
     * 
     * Controls:
     *   - Use the mouse to rotate the scan
     *   - Use "Up" and "Down" arrows to zoom
     *   - Use "Left" and "Right" arrows to decrease/increase the scan resolution
     *   - 'p' switchs between the mesh view and the pixels view
     *   - 'l' switchs between the mesh view and the lines view
     *   - "+" and "-" increase/decrease the pixel size (only in pixel view)
     *   - 'Space' creates some psychedelic effects (only in mesh view) 
     *   - 'c' centers the scan on the cursor position
     *   - 's' saves the scan 
     */
    
    import controlP5.*;
    
    public String[] files = {"scan1.points","diego1.points","chloe1.points","scan3.points"};
    public String[] maskTypes = {"linear","circular","random","balls","traces","cursor","diffusion"};
    public String[] modifierTypes = {"convolve","ballMod"};
    public int currentScan = 0;
    public String maskType = "empty";
    public String modifierType = "empty";
    public ScanContainer[] scans;
    public ScanMask sMask;
    public ScanModifier sModifier;
    public ScanConnector sConnector;
    
    public boolean drawMask = true;
    public boolean drawModifier = false;
    public boolean drawMorphing = false;
    public boolean asPixels = false;
    public boolean asLines = false;
    public boolean psychedelic = false;
    public int initResolution = 1;
    public int resolution = initResolution;
    public int pixelSize = 1;
    public int counter = 0;
    public int morphingIterator = 1;
    public int morphingSteps = 30;
    
    public ControlP5 cp5;
    public RadioButton scanButtons;
    public RadioButton maskButtons;
    public RadioButton modifierButtons;
    public Toggle morphingToggle;
    
    public PImage bgImg;
    
    public float zoom = 1;
    public float rotX = PI;
    public float rotY = 0;
    
    
    void setup(){
      size(750,600,P3D);
      
      // Read the scans
      scans = new ScanContainer[files.length];
      for(int i = 0; i < files.length; i++){
        ScanContainer s = new ScanContainer();
        s.createFromFile(files[i]);
        s.reduceResolution(initResolution);
        s.fillHoles(15);
        s.gaussianSmooth(4);
        //s.constrainPoints(-1000,1000,-1000,1000,-1000,1000);
        s.crop();
        scans[i] = s;
      }
    
      // Create an image to represent the background
      bgImg = createBackgroundImg(color(220));
    
      // Initialize ControlP5
      controlPanel();
    }
    
    
    void draw(){
      // Draw the background image
      background(bgImg);
    
      // Draw the control panel
      cp5.draw();
    
      // Position the scene
      translate(width/2,height/2,0);
      rotateX(rotX);
      rotateY(rotY);
      scale(zoom);
       
      // Draw the ScanMask, the ScanModifier or the ScanConnector
      if(drawMask){
        sMask.update(counter);
        
        if(asPixels){
          sMask.drawAsPixels(pixelSize);
        }
        else if(asLines){
          sMask.drawAsLines();
        }
        else if(psychedelic){
          sMask.drawAsPsyTriangles();
          lights();
          sMask.drawBackSide(color(255));
        }
        else{
          sMask.drawAsTriangles();
          lights();
          sMask.drawBackSide(color(255));
        }
      }
      else if(drawModifier){
        sModifier.update(counter);
        
        if(asPixels){
          sModifier.drawAsPixels(pixelSize);
        }
        else if(asLines){
          sModifier.drawAsLines();
        }
        else if(psychedelic){
          sModifier.drawAsPsyTriangles();
          //lights();
          //sModifier.drawBackSide(color(255));
        }
        else{
          sModifier.drawAsTriangles();
          //lights();
          //sModifier.drawBackSide(color(255));
        }
      }
      else if(drawMorphing && scans.length > 1){
        if(counter > morphingSteps){
          counter = 0;
          morphingIterator++;
          if(morphingIterator == scans.length){
            sConnector = new ScanConnector(scans[morphingIterator-1],scans[0]);
            morphingIterator = 0;
          }
          else{
            sConnector = new ScanConnector(scans[morphingIterator-1],scans[morphingIterator]);
          }
        }
        
        sConnector.update(counter,morphingSteps);
        
        if(asPixels){
          sConnector.drawAsPixels(pixelSize);
        }
        else if(asLines){
          sConnector.drawAsLines();
        }
        else if(psychedelic){
          sConnector.drawAsPsyTriangles();
          lights();
          sConnector.drawBackSide(color(255));
        }
        else{
          sConnector.drawAsTriangles();
          lights();
          sConnector.drawBackSide(color(255));
        }
      }
    
      counter++;
    }
    
    
    /*
     * EmptyMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class EmptyMask extends ScanMask{
      
      //
      // Constructor
      //
    
      public EmptyMask(){
    
      }
      
    }
    
    
    
    /*
     * LinearMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class LinearMask extends ScanMask{
      private float[] distance;
      private float repetitionInterval;
      public float speed = 4;
      
      
      //
      // Constructor
      //
    
      public LinearMask(){
    
      }
    
    
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        float maxDist = 0;
        distance = new float[nPoints];
        for(int i = 0; i < nPoints; i++){
          if(consImgOrig[i]){
            distance[i] = abs(map3D[i].x-centralPos.x);
            if(distance[i] > maxDist){
              maxDist = distance[i];
            }
          }
          else{
            distance[i] = 0;
          } 
        }
        
        repetitionInterval = maxDist;
      }
    
    
      // update
      public void update(int t){
        if(t > 0){
          float distLimit = speed*t%repetitionInterval;
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize;
                if(distance[index] < distLimit){
                  maskImg[index] = !value;
                }
                else{
                  maskImg[index] = value;
                }
              }
            }
          }
          addMask();
          calculateBackSide();
        }
      }
    
    }
    
    
    
    /*
     * CircularMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class CircularMask extends ScanMask{
      private float[] distSq;
      private float repetitionInterval;
      public float speed = 5;
      
      
      //
      // Constructor
      //
    
      public CircularMask(){
    
      }
    
    
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        float maxDistSq = 0;
        distSq = new float[nPoints];
        for(int i = 0; i < nPoints; i++){
          if(consImgOrig[i]){
            distSq[i] = sq(map3D[i].x-centralPos.x) + sq(map3D[i].y-centralPos.y) + sq(map3D[i].z-centralPos.z);
            if(distSq[i] > maxDistSq){
              maxDistSq = distSq[i];
            }
          }
          else{
            distSq[i] = 0;
          } 
        }
        
        repetitionInterval = sqrt(maxDistSq);
      }
    
    
      // update
      public void update(int t){
        if(t > 0){
          float distLimitSq = sq(speed*t%repetitionInterval);
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize;
                if(distSq[index] < distLimitSq){
                  maskImg[index] = !value;
                }
                else{
                  maskImg[index] = value;
                }
              }
            }
          }
          addMask();
          calculateBackSide();
        }
      }
    
    }
    
    
    
    /*
     * RandomMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class RandomMask extends ScanMask{
      private int seeds;
      private int[] xPos;
      private int[] yPos;
      
      
      //
      // Constructor
      //
    
      public RandomMask(){
    
      }
    
     
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        seeds = int(0.002*nPoints);
        if(seeds < 4) seeds = 4;
        
        xPos = new int[seeds];
        yPos = new int[seeds];
    
        for(int i = 0; i < seeds; ){
          xPos[i] = int(random(xSize));
          yPos[i] = int(random(ySize));
          if(consImgOrig[xPos[i] + yPos[i]*xSize]){
            i++;
          }
        }
      }
    
    
      // update
      public void update(int t){
        for(int i = 0; i < seeds; i++){
          switch(int(random(4))){
            case 0: 
              if(xPos[i] + 1 < xSize) xPos[i]++;
              break;
            case 1:  
              if(yPos[i] + 1 < ySize) yPos[i]++;
              break;
            case 2:  
              if(xPos[i] - 1 >= 0) xPos[i]--;
              break;
            case 3:  
              if(yPos[i] - 1 >= 0) yPos[i]--;
              break;
          }
          maskImg[xPos[i] + yPos[i]*xSize] = !value;
        }
        addMask();
        calculateBackSide();
      }
    
    }
    
    
    
    /*
     * BallsMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class BallsMask extends ScanMask{
      private PGraphics pg;
      private int nBalls;
      private float ballSize = 10;
      private color ballColor = color(0);
      private Ball[] balls;
    
      
      //
      // Constructor
      //
    
      public BallsMask(){
    
      }
    
      
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        pg = createGraphics(xSize,ySize,P2D);
        pg.beginDraw();
        pg.background(255);
        pg.noStroke();
        pg.noSmooth();
        pg.endDraw();
        
        nBalls = int(0.01*nPoints);
        balls = new Ball[nBalls];
        for(int i = 0; i < nBalls; i++){
          float velMag = 0.015*xSize;
          float velAng = random(TWO_PI);
          balls[i] = new Ball(random(xSize),random(ySize),velMag*cos(velAng),velMag*sin(velAng),ballSize,ballColor);
        }
      }
     
    
      // update
      public void update(int t){
        pg.beginDraw();
        pg.background(255);
        pg.endDraw();
        
        for(int i = 0; i < nBalls; i++){
          balls[i].update(pg);
          balls[i].paint(pg);
        }
        
        pg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          if(pg.pixels[i] == ballColor){
            maskImg[i] = !value;
          }
          else{
            maskImg[i] = value;
          }
        }
        pg.updatePixels();
    
        addMask();
        calculateBackSide();
      }
    
    }
    
    
    
    /*
     * TracesMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class TracesMask extends ScanMask{
      private PGraphics pg;
      private int nBalls;
      private float ballSize = 5;
      private color ballColor = color(0);
      private Ball[] balls;
    
      
      //
      // Constructor
      //
    
      public TracesMask(){
    
      }
      
    
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        pg = createGraphics(xSize,ySize,P2D);
        pg.beginDraw();
        pg.background(255);
        pg.noStroke();
        pg.noSmooth();
        pg.endDraw();
    
        nBalls = int(0.0003*nPoints);
        if(nBalls < 10) nBalls = 10;
        balls = new Ball[nBalls];
        for(int i = 0; i < nBalls; i++){
          float velMag = 0.004*xSize;
          float velAng = random(TWO_PI);
          balls[i] = new Ball(random(xSize),random(ySize),velMag*cos(velAng),velMag*sin(velAng),ballSize,ballColor);
        }
      }
    
    
      // update
      public void update(int t){
        for(int i = 0; i < nBalls; i++){
          balls[i].update(pg);
          balls[i].paint(pg);
        }
        
        pg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          if(pg.pixels[i] == ballColor){
            maskImg[i] = !value;
          }
          else{
            maskImg[i] = value;
          }
        }
        pg.updatePixels();
    
        addMask();
        calculateBackSide();
      }
    
    }
    
    
    
    /*
     * CursorMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class CursorMask extends ScanMask{
      private float cursorSize;
      
      
      //
      // Constructor
      //
    
      public CursorMask(){
    
      }
     
    
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        cursorSize = 3000/sqrt(nPoints);
      }
      
        
      // update
      public void update(int t){
        int cursorIndex = pointUnderCursor();
        if(cursorIndex >= 0){  
          PVector cursorPos = map3D[cursorIndex].get();
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize; 
                if(consImgOrig[index]){
                  float distSq = sq(map3D[index].x-cursorPos.x) + sq(map3D[index].y-cursorPos.y) + sq(map3D[index].z-cursorPos.z);
                  if(distSq < sq(cursorSize)){
                    maskImg[index] = !value;
                  }
                }
              }
            }
          }
        }
        addMask();
        calculateBackSide();
      }
      
      
      // pointUnderCursor
      public int pointUnderCursor(){
        // Perspective parameters
        float fov = PI/3.0;
        float cameraZ = (height/2.0)/tan(fov/2.0);
        
        int cursorIndex = -1;
        float maxZ = -1000;
        float maxDistSq = sq(2*zoom);
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImgOrig[index]){
                // Scale and rotate the point as is shown in the screen
                PVector p = map3D[index].get();
                p.mult(zoom);
                p = new PVector(p.x*cos(rotY)+p.z*sin(rotY),p.y,-p.x*sin(rotY)+p.z*cos(rotY));
                p = new PVector(p.x,p.y*cos(rotX)-p.z*sin(rotX),p.y*sin(rotX)+p.z*cos(rotX));
                p.add(0,0,0);
                
                // Calculate the cursor position at the same z plane as the point
                float scaling = (cameraZ-p.z)/cameraZ;
                PVector cursorPos = new PVector((mouseX-width/2.0)*scaling,(mouseY-height/2.0)*scaling,p.z);
                
                // Find the closest point to the cursor
                float distSq = sq(p.x-cursorPos.x) + sq(p.y-cursorPos.y) + sq(p.z-cursorPos.z);
                if(distSq < maxDistSq && p.z > maxZ){
                  maxZ = p.z;
                  cursorIndex = index;
                }
              }
            }
          }
        }
        return cursorIndex;
      }
      
    }
    
    
    
    /*
     * DLAMask class
     *
     * Subclass of the ScanMask class
     */
    
    public class DLAMask extends ScanMask{
      private int nPar;
      private ArrayList particles;
      private int seeds = 1;
      
      
      //
      // Constructor
      //
    
      public DLAMask(){
    
      }
      
      
      //
      // Class Methods 
      //
    
      // init
      public void init(){
        nPar = int(0.08*nPoints);
        particles = new ArrayList(nPar);
        for(int i = 0; i < nPar; i++){
          particles.add(new DLAparticle(int(random(xSize)),int(random(ySize)),xSize,ySize));
        }
        
        // Introduce the seeds
        for(int i = 0; i < seeds; ){
          if(i == 0){
            maskImg[centralPix[0] + centralPix[1]*xSize] = !value;
            i++;
          }
          else{
            int index = int(random(nPoints));
            if(consImgOrig[index]){
              maskImg[index] = !value;
              i++; 
            }
          }
        }
      }
     
    
      // update
      public void update(int t){
        int nSteps = 10;  // to speed up the process
        for(int step = 0; step < nSteps; step++){
          for(Iterator i = particles.iterator(); i.hasNext(); ){
            DLAparticle par = (DLAparticle) i.next();
            // Move the particle in a random walk
            par.update();
            // Check if the particle needs to be aggregated
            int[] parIndexes = par.checkForAggregation(maskImg,!value);
            int x = parIndexes[0];
            int y = parIndexes[1];
            // Aggregate if needed
            if(x >= 0 && x < xSize && y >= 0 && y < ySize){
              int index = x + y*xSize;
              maskImg[index] = !value;
              if(x+1 < xSize) maskImg[index+1] = !value;
              if(x-1 >= 0) maskImg[index-1] = !value;
              if(y+1 < ySize) maskImg[index+xSize] = !value;
              if(y-1 >= 0) maskImg[index-xSize] = !value;
              i.remove();
            }
          }
        }
        addMask();
        calculateBackSide();
      }
    
    }
    
    
    /*
     * EmptyModifier class.
     *
     * Subclass of the ScanModifier class
     */
    
    public class EmptyModifier extends ScanModifier{
    
      //
      // Constructor
      //
      
      public EmptyModifier(){
    
      }
    
    }
    
    
    
    /*
     * ConvolveModifier class.
     *
     * Subclass of the ScanModifier class
     */
    
    public class ConvolveModifier extends ScanModifier{
      private float convolveRadius = 8;
      
    
      //
      // Constructor
      //
      
      public ConvolveModifier(){
    
      }
      
      
      //
      // Class Methods 
      //
    
      // update
      public void update(int t){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              boolean cond = consImgOrig[index] && x - 1 >= 0 && x + 1 < xSize && y - 1 >= 0 && y + 1 < ySize &&
                             consImgOrig[index-1] && consImgOrig[index+1] && consImgOrig[index - xSize] && consImgOrig[index + xSize];   
              if(cond){
                map3D[index] = PVector.add(map3DOrig[index],PVector.mult(normalsOrig[index],convolveRadius*(cos(float(t)*0.07)+0.3)));
              }
              else{
                map3D[index] = map3DOrig[index].get();
                consImg[index] = false;
              }
            }
          }
        }
        calculateNormals();
        calculateBackSide();
      }
      
    }
    
    
    
    /*
     * BallModifier class.
     *
     * Subclass of the ScanModifier class
     */
    
    public class BallModifier extends ScanModifier{
      private float ballSize = 25;
      
    
      //
      // Constructor
      //
    
      public BallModifier(){
    
      }
      
    
      //
      // Class Methods 
      //
    
      // update
      public void update(int t){
        int cursorIndex = pointUnderCursor();
        if(cursorIndex >= 0){
          PVector cursorPos = map3DOrig[cursorIndex].get();
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize;
                if(consImgOrig[index]){
                  PVector p = map3DOrig[index].get();
                  float distSq = sq(p.x-cursorPos.x) + sq(p.y-cursorPos.y) + sq(p.z-cursorPos.z);
                  if(distSq < sq(ballSize)){
                    PVector n = new PVector(0,0,-1);
                    for(int j = 0; j < 2*ballSize; j++){
                      PVector delta = PVector.mult(n,j);
                      PVector newP = PVector.add(p,delta);
                      distSq = sq(newP.x-cursorPos.x) + sq(newP.y-cursorPos.y) + sq(newP.z-cursorPos.z);
                      if(distSq > sq(ballSize)){
                        map3D[index] = newP.get();
                        break;
                      }
                    }
                  }
                  else{
                    map3D[index] = map3DOrig[index].get();
                  }
                }
              }
            }
          }
        }
      }
      
      
      // pointUnderCursor
      public int pointUnderCursor(){
        // Perspective parameters
        float fov = PI/3.0;
        float cameraZ = (height/2.0)/tan(fov/2.0);
        
        int cursorIndex = -1;
        float maxZ = -1000;
        float maxDistSq = sq(2*zoom);
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImgOrig[index]){
                // Scale and rotate the point as is shown in the screen
                PVector p = map3DOrig[index].get();
                p.mult(zoom);
                p = new PVector(p.x*cos(rotY)+p.z*sin(rotY),p.y,-p.x*sin(rotY)+p.z*cos(rotY));
                p = new PVector(p.x,p.y*cos(rotX)-p.z*sin(rotX),p.y*sin(rotX)+p.z*cos(rotX));
                p.add(0,0,0);
                
                // Calculate the cursor position at the same z plane as the point
                float scaling = (cameraZ-p.z)/cameraZ;
                PVector cursorPos = new PVector((mouseX-width/2.0)*scaling,(mouseY-height/2.0)*scaling,p.z);
                
                // Find the closest point to the cursor
                float distSq = sq(p.x-cursorPos.x) + sq(p.y-cursorPos.y) + sq(p.z-cursorPos.z);
                if(distSq < maxDistSq && p.z > maxZ){
                  maxZ = p.z;
                  cursorIndex = index;
                }
              }
            }
          }
        }
        return cursorIndex;
      }
      
    }
    
    
    /*
     * ScanConnector class.
     *
     * Usefull class to make the transition between two scans.
     */
    
    public class ScanConnector{
      public ScanContainer iniScan;
      public ScanContainer endScan;
      public ScanContainer intermediateScan;
      public PVector[] separation;
      public float[] deltaRed;
      public float[] deltaGreen;
      public float[] deltaBlue;
      
    
      //
      // Constructor
      //
    
      public ScanConnector(ScanContainer tempIniScan, ScanContainer tempEndScan){
        iniScan = new ScanContainer();
        iniScan.create(tempIniScan);
        iniScan.centerAndExtend();
        endScan = new ScanContainer();
        endScan.create(tempEndScan);
        endScan.centerAndExtend();
    
        // New dimensions of the scans
        int xSize = max(iniScan.xSize,endScan.xSize);
        int ySize = max(iniScan.ySize,endScan.ySize);
        int nPoints = xSize*ySize;
        iniScan.extend(xSize,ySize);
        endScan.extend(xSize,ySize);
        separation = new PVector[nPoints];
        deltaRed = new float[nPoints];
        deltaGreen = new float[nPoints];
        deltaBlue = new float[nPoints];
        
        
        // Populate the scans in the vertical direction
        int firstIni = -1;
        for(int y = 0; y < ySize; y++){
          if(!iniScan.empty[y]){
            firstIni = y;
            break;
          }
        }
        
        int firstEnd = -1;
        for(int y = 0; y < ySize; y++){
          if(!endScan.empty[y]){
            firstEnd = y;
            break;
          }
        }
        
        iniScan.rgbImg.loadPixels();
        endScan.rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!(iniScan.empty[y] && endScan.empty[y])){
            if(iniScan.empty[y] && y < firstIni){
              for(int x = 0; x < xSize; x++){
                int index = x + y*xSize;
                int firstIndex = x + firstIni*xSize;
                iniScan.map3D[index] = iniScan.map3D[firstIndex].get();
                iniScan.rgbImg.pixels[index] = iniScan.rgbImg.pixels[firstIndex];
                iniScan.consImg[index] = iniScan.consImg[firstIndex];
              }
            }
            else if(iniScan.empty[y] && y > firstIni){
              for(int x = 0; x < xSize; x++){
                int index = x + y*xSize;
                int prevIndex = x + (y-1)*xSize;
                iniScan.map3D[index] = iniScan.map3D[prevIndex].get();
                iniScan.rgbImg.pixels[index] = iniScan.rgbImg.pixels[prevIndex];
                iniScan.consImg[index] = iniScan.consImg[prevIndex];
              }
            }
            if(endScan.empty[y] && y < firstEnd){
              for(int x = 0; x < xSize; x++){
                int index = x + y*xSize;
                int firstIndex = x + firstEnd*xSize;
                endScan.map3D[index] = endScan.map3D[firstIndex].get();
                endScan.rgbImg.pixels[index] = endScan.rgbImg.pixels[firstIndex];
                endScan.consImg[index] = endScan.consImg[firstIndex];
              }
            }
            else if(endScan.empty[y] && y > firstEnd){
              for(int x = 0; x < xSize; x++){
                int index = x + y*xSize;
                int prevIndex = x + (y-1)*xSize;
                endScan.map3D[index] = endScan.map3D[prevIndex].get();
                endScan.rgbImg.pixels[index] = endScan.rgbImg.pixels[prevIndex];
                endScan.consImg[index] = endScan.consImg[prevIndex];
              }
            }
          }
        }
        iniScan.rgbImg.updatePixels();
        endScan.rgbImg.updatePixels();
        
        // Update the extremes
        iniScan.findExtremes();
        endScan.findExtremes();
    
    
        // Populate the scans in the horizontal direction
        int[] ini = new int[ySize];
        int[] end = new int[ySize];
        boolean[] empty = new boolean[ySize];
    
        for(int y = 0; y < ySize; y++){
          ini[y] = min(iniScan.ini[y],endScan.ini[y]);
          end[y] = max(iniScan.end[y],endScan.end[y]);
          empty[y] = iniScan.empty[y] && endScan.empty[y];
        }
    
        iniScan.rgbImg.loadPixels();
        endScan.rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(!iniScan.consImg[index] && x < iniScan.ini[y]){
                int indexIni = iniScan.ini[y] + y*xSize;
                iniScan.map3D[index] = iniScan.map3D[indexIni].get();
                iniScan.rgbImg.pixels[index] = iniScan.rgbImg.pixels[indexIni];
                iniScan.consImg[index] = iniScan.consImg[indexIni];
              }
              else if(!iniScan.consImg[index] && x > iniScan.ini[y]){
                int prevIndex = (x-1) + y*xSize;
                iniScan.map3D[index] = iniScan.map3D[prevIndex].get();
                iniScan.rgbImg.pixels[index] = iniScan.rgbImg.pixels[prevIndex];
                iniScan.consImg[index] = iniScan.consImg[prevIndex];
              }
              if(!endScan.consImg[index] && x < endScan.ini[y]){
                int indexIni = endScan.ini[y] + y*xSize;
                endScan.map3D[index] = endScan.map3D[indexIni].get();
                endScan.rgbImg.pixels[index] = endScan.rgbImg.pixels[indexIni];
                endScan.consImg[index] = endScan.consImg[indexIni];
              }
              else if(!endScan.consImg[index] && x > endScan.ini[y]){
                int prevIndex = (x-1) + y*xSize;
                endScan.map3D[index] = endScan.map3D[prevIndex].get();
                endScan.rgbImg.pixels[index] = endScan.rgbImg.pixels[prevIndex];
                endScan.consImg[index] = endScan.consImg[prevIndex];
              }
            }
          }
        }
        iniScan.rgbImg.updatePixels();
        endScan.rgbImg.updatePixels();
    
        // Update the extremes
        iniScan.findExtremes();
        endScan.findExtremes();
    
    
        // Populate the arrays
        iniScan.rgbImg.loadPixels();
        endScan.rgbImg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          separation[i] = PVector.sub(endScan.map3D[i],iniScan.map3D[i]);
          deltaRed[i] = red(endScan.rgbImg.pixels[i]) - red(iniScan.rgbImg.pixels[i]);
          deltaGreen[i] = green(endScan.rgbImg.pixels[i]) - green(iniScan.rgbImg.pixels[i]);
          deltaBlue[i] = blue(endScan.rgbImg.pixels[i]) - blue(iniScan.rgbImg.pixels[i]);
        }
    
        // Begin from the initial scan
        intermediateScan = new ScanContainer();
        intermediateScan.create(iniScan);
      }
      
      
      //
      // Class Methods 
      //
    
      // update
      public void update(float t, float steps){
        if(t >=0 && t <= steps){
          int xSize = intermediateScan.xSize;
          int ySize = intermediateScan.ySize;
        
          intermediateScan.rgbImg.loadPixels();
          iniScan.rgbImg.loadPixels();
          for(int y = 0; y < ySize; y++){
            if(!intermediateScan.empty[y]){
              for(int x = intermediateScan.ini[y]; x <= intermediateScan.end[y]; x++){
                int index = x + y*xSize;
                intermediateScan.map3D[index] = PVector.add(iniScan.map3D[index],PVector.mult(separation[index],t/steps));
                intermediateScan.rgbImg.pixels[index] = color(red(iniScan.rgbImg.pixels[index])+deltaRed[index]*(t/steps), 
                                                              green(iniScan.rgbImg.pixels[index])+deltaGreen[index]*(t/steps),
                                                              blue(iniScan.rgbImg.pixels[index])+deltaBlue[index]*(t/steps));
              }
            }
          }
          intermediateScan.rgbImg.updatePixels();
    
          intermediateScan.calculateNormals();
          intermediateScan.calculateBackSide();
        }
      }
    
    
      // drawAsPixels
      public void drawAsPixels(int pSize){
        intermediateScan.drawAsPixels(pSize);
      }
    
    
      // drawAsLines
      public void drawAsLines(){
        intermediateScan.drawAsLines();
      }
    
    
      // drawAsTriangles
      public void drawAsTriangles(){
        intermediateScan.drawAsTriangles();
      }
    
    
      // drawAsPsyTriangles
      public void drawAsPsyTriangles(){
        intermediateScan.drawAsPsyTriangles();
      }
    
    
      // drawBackSide
      public void drawBackSide(color c){
        intermediateScan.drawBackSide(c);
      }
      
    }
    
    
    /*
     * ScanContainer class
     *
     * Class to read and manipulate the scans produced with the kinect scanner
     */
    
    public class ScanContainer{
      public int xSize;                // Hozizontal dimension
      public int ySize;                // Vertical dimension
      public int nPoints;              // Number of points
      public PVector[] map3D;          // 3D points
      public PImage rgbImg;            // Point colors 
      public boolean[] consImg;        // Valid points 
      public PVector[] normals;        // Point normals
      public PVector[] back3D;         // 3D points from the back side
      public int[] ini;                // Start of the valid points (in the line)
      public int[] end;                // End of the valid points (in the line)
      public boolean[] empty;          // No valid points in the line
      public int[] centralPix;         // Central pixel (x,y,z) = (0,0,z) in pixel coordinates
      public PVector centralPos;       // Central position of the scan in 3D world coordinates
      public float maxSepSq = sq(120); // Maximum separation allowed between two consecutive points
       
      
      //
      // Constructor
      //
      
      public ScanContainer(){
    
      }
      
      
      //
      // Class Methods 
      //
    
      // create
      public void create(ScanContainer tempScan){
        initialize(tempScan.map3D,tempScan.rgbImg,tempScan.consImg);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
      }
    
    
      // createFromFile
      public void createFromFile(String tempFile){
        initialize(tempFile);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
      }
    
    
      // initialize
      public void initialize(PVector[] points3D, PImage img, boolean[] cons){
        xSize = img.width;
        ySize = img.height;
        nPoints = xSize*ySize;
        map3D = new PVector[nPoints];
        rgbImg = createImage(xSize,ySize,RGB); 
        consImg = new boolean[nPoints];
        normals = new PVector[nPoints];
        back3D = new PVector[nPoints];
        ini = new int[ySize];
        end = new int[ySize];
        empty = new boolean[ySize];
        centralPix = new int[2];
        centralPos = new PVector();
        
        // Populate the arrays
        rgbImg.loadPixels();
        img.loadPixels();
        for(int i = 0; i < nPoints; i++){
          map3D[i] = points3D[i].get();
          rgbImg.pixels[i] = img.pixels[i];
          consImg[i] = cons[i];
          normals[i] = new PVector();
          back3D[i] = new PVector();
        }
        rgbImg.updatePixels();
      
        for(int i = 0; i < ySize; i++){
          ini[i] = 0;
          end[i] = xSize - 1;
          empty[i] = false;
        }
        
        centralPix[0] = int(xSize/2);
        centralPix[1] = int(ySize/2);
        if(consImg[centralPix[0]+centralPix[1]*xSize]){
          centralPos = map3D[centralPix[0]+centralPix[1]*xSize].get();
        }
      }
      
      
      // initialize
      public void initialize(String file){
        String[] pointsAndColors = loadStrings(file);
        String[] header = split(pointsAndColors[0]," ");
    
        xSize = int(header[0]);
        ySize = int(header[1]);
        nPoints = xSize*ySize;
        map3D = new PVector[nPoints];
        rgbImg = createImage(xSize,ySize,RGB); 
        consImg = new boolean[nPoints]; 
        normals = new PVector[nPoints];
        back3D = new PVector[nPoints];
        ini = new int[ySize];
        end = new int[ySize];
        empty = new boolean[ySize];
        centralPix = new int[2];
        centralPos = new PVector();
      
        // Populate the arrays
        rgbImg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          String[] row = split(pointsAndColors[i+1]," ");
          map3D[i] = new PVector(float(row[0]),float(row[1]),float(row[2]));
          if(float(row[3]) < 0){
            rgbImg.pixels[i] = color(0);
            consImg[i] = false;
          }
          else{
            rgbImg.pixels[i] = color(float(row[3]),float(row[4]),float(row[5]));
            consImg[i] = true;
          }
          normals[i] = new PVector();
          back3D[i] = new PVector();
        }
        rgbImg.updatePixels();
      
        for(int i = 0; i < ySize; i++){
          ini[i] = 0;
          end[i] = xSize -1;
          empty[i] = false;
        }
        
        centralPix[0] = int(xSize/2);
        centralPix[1] = int(ySize/2);
        if(consImg[centralPix[0]+centralPix[1]*xSize]){
          centralPos = map3D[centralPix[0]+centralPix[1]*xSize].get();
        }
      }
    
    
      // findExtremes
      public void findExtremes(){
        for(int y = 0; y < ySize; y++){
          boolean started = false;
          for(int x = 0; x < xSize; x++){
            if(consImg[x + y*xSize]){
              if(!started){
                ini[y] = x;
                empty[y] = false;
                started = true;
              }
              end[y] = x;
            }
          }
          if(!started){
            empty[y] = true;
          }
        }
      }  
    
    
      // findCentralPixel
      public void findCentralPixel(){
        float minDistSq = 1000;
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                float distSq = sq(map3D[index].x)+sq(map3D[index].y);
                if(distSq < minDistSq){
                  minDistSq = distSq;
                  centralPix[0] = x;
                  centralPix[1] = y;
                  centralPos = map3D[index].get();
                }
              }       
            }
          }
        }
      }
    
    
      // findCentralPixel
      public void findCentralPixel(PVector cen){
        float minDistSq = 1000;
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                float distSq = sq(map3D[index].x-cen.x)+sq(map3D[index].y-cen.y)+sq(map3D[index].z-cen.z);
                if(distSq < minDistSq){
                  minDistSq = distSq;
                  centralPix[0] = x;
                  centralPix[1] = y;
                  centralPos = map3D[index].get();
                }
              }       
            }
          }
        }
      }
    
    
      // calculateNormals
      public void calculateNormals(){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                int n = 0;
                PVector averageNormal = new PVector();
                if(x+1 < xSize && y+1 < ySize && consImg[index+1] && consImg[index+xSize]){
                  PVector v1 = PVector.sub(map3D[index+1],map3D[index]);
                  PVector v2 = PVector.sub(map3D[index+xSize],map3D[index]);
                  PVector perp = v1.cross(v2);
                  perp.normalize();
                  averageNormal.add(perp);
                  n++;
                }
                if(x-1 >= 0 && y+1 < ySize && consImg[index-1] && consImg[index+xSize]){
                  PVector v1 = PVector.sub(map3D[index+xSize],map3D[index]);
                  PVector v2 = PVector.sub(map3D[index-1],map3D[index]);
                  PVector perp = v1.cross(v2);
                  perp.normalize();
                  averageNormal.add(perp);
                  n++;
                }
                if(x-1 >= 0 && y-1 >= 0 && consImg[index-1] && consImg[index-xSize]){
                  PVector v1 = PVector.sub(map3D[index-1],map3D[index]);
                  PVector v2 = PVector.sub(map3D[index-xSize],map3D[index]);
                  PVector perp = v1.cross(v2);
                  perp.normalize();
                  averageNormal.add(perp);
                  n++;
                }
                if(x+1 < xSize && y-1 >= 0 && consImg[index+1] && consImg[index-xSize]){
                  PVector v1 = PVector.sub(map3D[index-xSize],map3D[index]);
                  PVector v2 = PVector.sub(map3D[index+1],map3D[index]);
                  PVector perp = v1.cross(v2);
                  perp.normalize();
                  averageNormal.add(perp);
                  n++;
                }
                if(n > 0){
                  normals[index] = PVector.div(averageNormal,n);
                }
                else{
                  normals[index] = new PVector();
                }
              }
              else{
                normals[index] = new PVector();
              }
            }
          }
        }
      }
      
      
      // calculateBackSide
      public void calculateBackSide(){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                boolean cond = x-1 >= 0 && x+1 < xSize && y-1 >= 0 && y+1 < ySize &&
                               consImg[index-1] && consImg[index+1] && consImg[index-xSize] && consImg[index+xSize];
                if(cond){
                  back3D[index] = PVector.sub(map3D[index],PVector.mult(normals[index],0.1));
                }
                else{
                  back3D[index] = PVector.sub(map3D[index],PVector.mult(normals[index],0.01));
                }
              }
              else{
                back3D[index] = map3D[index].get();
              }
            }
          }
        }
      }
      
      
      // reduceResolution
      public void reduceResolution(int n){
        if(n > 1){
          // Dimensions of the reduced scan
          int xSizeRed = xSize/n + 2;
          int ySizeRed = ySize/n + 2;
          int nPointsRed = xSizeRed*ySizeRed;
          PVector[] redMap3D = new PVector[nPointsRed];
          PImage redRGBimg = createImage(xSizeRed,ySizeRed,RGB);
          boolean[] redConsImg = new boolean[nPointsRed]; 
    
          // Populate the arrays
          redRGBimg.loadPixels();
          rgbImg.loadPixels();
          for(int y = 0; y < ySizeRed; y++){
            for(int x = 0; x < xSizeRed; x++){
              int index = x*n + y*n*xSize;
              int indexRed = x + y*xSizeRed;
              
              // Average between nearby pixels
              PVector p = new PVector();
              float r = 0;
              float g = 0;
              float b = 0;
              float pointsCounter = 0;
              for(int i = -n/2; i <= n/2; i++){
                for(int j = -n/2; j <= n/2; j++){
                  int xNearby = (x*n)+i;
                  int yNearby = (y*n)+j;
                  if(xNearby >=0 && xNearby < xSize && yNearby >= 0 && yNearby < ySize){
                    int indexNearby = xNearby + yNearby*xSize;
                    if(consImg[indexNearby]){
                      p.add(map3D[indexNearby]);
                      r += red(rgbImg.pixels[indexNearby]);
                      g += green(rgbImg.pixels[indexNearby]);
                      b += blue(rgbImg.pixels[indexNearby]);
                      pointsCounter++;
                    }
                  }
                }
              }
              if(pointsCounter > 0){
                redMap3D[indexRed] = PVector.div(p,pointsCounter);
                redRGBimg.pixels[indexRed] = color(r/pointsCounter,g/pointsCounter,b/pointsCounter);
                redConsImg[indexRed] = true;
              }
              else{
                redMap3D[indexRed] = new PVector();
                redRGBimg.pixels[indexRed] = color(0);
                redConsImg[indexRed] = false;
              }
            }
          }
          redRGBimg.updatePixels();
        
          // Update the scan to the new resolution
          initialize(redMap3D,redRGBimg,redConsImg);
          findExtremes();
          findCentralPixel();
          calculateNormals();
          calculateBackSide();
        }
      }
    
    
      // centerAndExtend
      public void centerAndExtend(){
        // Dimensions of the extended scan
        int xSizeExt = 2*max(centralPix[0]+1,xSize-centralPix[0]) - 1;
        int ySizeExt = 2*max(centralPix[1]+1,ySize-centralPix[1]) - 1;
        int nPointsExt = xSizeExt*ySizeExt;
        PVector[] extMap3D = new PVector[nPointsExt];
        PImage extRGBimg = createImage(xSizeExt,ySizeExt,RGB);
        boolean[] extConsImg = new boolean[nPointsExt]; 
    
        // Populate the arrays
        extRGBimg.loadPixels();
        for(int i = 0; i < nPointsExt; i++){
          extMap3D[i] = new PVector();
          extRGBimg.pixels[i] = color(0);
          extConsImg[i] = false;
        }
        extRGBimg.updatePixels();
        
        int xStart, yStart;
        if((centralPix[0]+1) > (xSize-centralPix[0])) xStart = 0;
        else xStart = xSizeExt - xSize;
        if((centralPix[1]+1) > (ySize-centralPix[1])) yStart = 0;
        else yStart = ySizeExt - ySize;
            
        extRGBimg.loadPixels();
        rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              int indexExt = (xStart+x) + (yStart+y)*xSizeExt;
              extMap3D[indexExt] = map3D[index].get();
              extRGBimg.pixels[indexExt] = rgbImg.pixels[index];
              extConsImg[indexExt] = consImg[index];
            }
          }
        }
        extRGBimg.updatePixels();
    
        // Update the scan to the new resolution
        initialize(extMap3D,extRGBimg,extConsImg);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
      }
    
      
      // extend
      public void extend(int xSizeExt, int ySizeExt){
        if(xSizeExt >= xSize && ySizeExt >= ySize){
          // Dimensions of the extended scan
          int nPointsExt = xSizeExt*ySizeExt;
          PVector[] extMap3D = new PVector[nPointsExt];
          PImage extRGBimg = createImage(xSizeExt,ySizeExt,RGB);
          boolean[] extConsImg = new boolean[nPointsExt];
        
          // Populate the arrays
          extRGBimg.loadPixels();
          for(int i = 0; i < nPointsExt; i++){
            extMap3D[i] = new PVector();
            extRGBimg.pixels[i] = color(0);
            extConsImg[i] = false; 
          }
          extRGBimg.updatePixels();
     
          int xStart = (xSizeExt - xSize)/2;
          int yStart = (ySizeExt - ySize)/2;
     
          extRGBimg.loadPixels();
          rgbImg.loadPixels();
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize;
                int indexExt = (xStart+x) + (yStart+y)*xSizeExt;
                extMap3D[indexExt] = map3D[index].get();
                extRGBimg.pixels[indexExt] = rgbImg.pixels[index];
                extConsImg[indexExt] = consImg[index]; 
              }
            }
          }
          extRGBimg.updatePixels();
        
          // Update the scan to the new resolution
          initialize(extMap3D,extRGBimg,extConsImg);
          findExtremes();
          findCentralPixel();
          calculateNormals();
          calculateBackSide();
        }
      }
     
    
      // scaleFactor
      public void scaleFactor(float f){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              map3D[x + y*xSize].mult(f);
            }
          }
        }
        calculateBackSide();
      }
    
     
      // fillHoles
      public void fillHoles(int maxHoles){
        rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            // Find holes in the line
            for(int x = ini[y]+1; x < end[y]; x++){
              if(!consImg[x + y*xSize]){
                // Calculate the limits of the hole
                int start = (x-1) + y*xSize;
                int finish = start;
                for(int i = x+1; i <= end[y]; i++){
                  if(consImg[i + y*xSize]){
                    finish = i + y*xSize;
                    x = i; // the x loop will continue from here
                    break;
                  }
                }
                // Fill the hole if the gap is not too big
                if(((finish-start)-1) <= maxHoles){
                  PVector deltaPos = PVector.sub(map3D[finish],map3D[start]);
                  float deltaR = red(rgbImg.pixels[finish])-red(rgbImg.pixels[start]);
                  float deltaG = green(rgbImg.pixels[finish])-green(rgbImg.pixels[start]);
                  float deltaB = blue(rgbImg.pixels[finish])-blue(rgbImg.pixels[start]);
                  for(int i = start+1; i < finish; i++){
                    float step = float(i-start)/float(finish-start);
                    map3D[i] = PVector.add(map3D[start],PVector.mult(deltaPos,step));
                    rgbImg.pixels[i] = color(red(rgbImg.pixels[start])+deltaR*step,green(rgbImg.pixels[start])+deltaG*step,blue(rgbImg.pixels[start])+deltaB*step);
                    consImg[i] = true;
                  }
                }
              } 
            }    
          }
        }
        rgbImg.updatePixels();
        
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
      }
    
    
      // gaussianSmooth
      public void gaussianSmooth(int n){
        if(n > 0){
          // Create the gaussian mask
          float[][] m = new float[2*n+1][2*n+1];
          for(int i = -n; i <= n; i++){
            for(int j = -n; j <= n; j++){
              float distSq = sq(i)+sq(j);
              if(distSq <= sq(n)){
                m[i+n][j+n] = pow(2.718,-distSq/(2*sq(n/2)));
              }
              else{
                m[i+n][j+n] = 0;
              }
            }
          }
    
          // Create the array for the smoothed 3Dpoints
          PVector[] smMap3D = new PVector[nPoints];
    
          // Populate the array
          for(int y = 0; y < ySize; y++){
            for(int x = 0; x < xSize; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                // Average between nearby pixels
                PVector pCentral = map3D[index].get();
                PVector pAverage = new PVector();
                float multCounter = 0;
                for(int i = -n; i <= n; i++){
                  for(int j = -n; j <= n; j++){
                    if(x+i >=0 && x+i < xSize && y+j >= 0 && y+j < ySize){
                      int indexNearby = (x+i) + (y+j)*xSize;
                      if(consImg[indexNearby]){
                        PVector pNearby = map3D[indexNearby].get();
                        float distSq = sq(pCentral.x-pNearby.x) + sq(pCentral.y-pNearby.y) + sq(pCentral.z-pNearby.z);
                        if(distSq < maxSepSq){
                          pAverage.add(PVector.mult(pNearby,m[i+n][j+n]));
                          multCounter += m[i+n][j+n];
                        }
                      }
                    }
                  }
                }
                if(multCounter > 0){
                  smMap3D[index] = PVector.div(pAverage,multCounter);
                }
                else{
                  smMap3D[index] = map3D[index].get();
                }
              }
              else{
                smMap3D[index] = new PVector();
              }
            }
          }
          
          // Update the scan with the new points
          initialize(smMap3D,rgbImg,consImg);
          findExtremes();
          findCentralPixel();
          calculateNormals();
          calculateBackSide();
        }
      }
    
    
      // pointUnderCursor
      public int pointUnderCursor(){
        // Perspective parameters
        float fov = PI/3.0;
        float cameraZ = (height/2.0)/tan(fov/2.0);
        
        int cursorIndex = -1;
        float maxZ = -1000;
        float maxDistSq = sq(2*zoom);
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                // Scale and rotate the point as is shown in the screen
                PVector p = map3D[index].get();
                p.mult(zoom);
                p = new PVector(p.x*cos(rotY)+p.z*sin(rotY),p.y,-p.x*sin(rotY)+p.z*cos(rotY));
                p = new PVector(p.x,p.y*cos(rotX)-p.z*sin(rotX),p.y*sin(rotX)+p.z*cos(rotX));
                p.add(0,0,0);
                
                // Calculate the cursor position at the same z plane as the point
                float scaling = (cameraZ-p.z)/cameraZ;
                PVector cursorPos = new PVector((mouseX-width/2.0)*scaling,(mouseY-height/2.0)*scaling,p.z);
                
                // Find the closest point to the cursor
                float distSq = sq(p.x-cursorPos.x) + sq(p.y-cursorPos.y) + sq(p.z-cursorPos.z);
                if(distSq < maxDistSq && p.z > maxZ){
                  maxZ = p.z;
                  cursorIndex = index;
                }
              }
            }
          }
        }
        return cursorIndex;
      }
    
    
      // pointCoordinates
      public void pointCoordinates(){
        int curs = pointUnderCursor();
        if(curs >= 0){
          println(map3D[curs]);
        }
      }
    
    
      // centerOnCursor
      public void centerOnCursor(){
        int curs = pointUnderCursor();
        if(curs >= 0){
          PVector newCenter = map3D[curs].get();
          newCenter.add(0,0,100); // small offset
          for(int y = 0; y < ySize; y++){
            if(!empty[y]){
              for(int x = ini[y]; x <= end[y]; x++){
                int index = x + y*xSize;
                if(consImg[index]){
                  map3D[index].sub(newCenter);
                }
              }
            }
          }
          findCentralPixel();
          calculateBackSide();
        }
      }
     
    
      // constrainPoints
      public void constrainPoints(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                PVector p = map3D[index].get();
                if(p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax || p.z < zMin || p.z > zMax){
                  consImg[index] = false;
                }
              }
            }
          }
        }
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
      }
    
      
      // crop
      public void crop(){
        // Calculate the region in the scan with the valid data
        int xIni = xSize;
        int xEnd = -1;
        int yIni = ySize;
        int yEnd = -1;
    
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            if(ini[y] < xIni) xIni = ini[y];
            if(end[y] > xEnd) xEnd = end[y];
            if(y < yIni) yIni = y;
            if(y > yEnd) yEnd = y;
          }
        }
    
        if(xIni <= xEnd && yIni <= yEnd){
          // Dimensions of the cropped scan
          int xSizeCropped = (xEnd - xIni) + 1;
          int ySizeCropped = (yEnd - yIni) + 1;
          int nPointsCropped = xSizeCropped*ySizeCropped;
          PVector[] croppedMap3D = new PVector[nPointsCropped];
          PImage croppedRGBimg = createImage(xSizeCropped,ySizeCropped,RGB);
          boolean[] croppedConsImg = new boolean[nPointsCropped]; 
    
          // Populate the arrays
          croppedRGBimg.loadPixels();
          rgbImg.loadPixels();
          for(int y = 0; y < ySizeCropped; y++){
            for(int x = 0; x < xSizeCropped; x++){
              int indexCropped = x + y*xSizeCropped;
              int index = (xIni+x) + (yIni+y)*xSize;
              croppedMap3D[indexCropped] = map3D[index].get();
              croppedRGBimg.pixels[indexCropped] = rgbImg.pixels[index];
              croppedConsImg[indexCropped] = consImg[index];
            }
          }
          croppedRGBimg.updatePixels();
    
          // Update the scan to the new size
          initialize(croppedMap3D,croppedRGBimg,croppedConsImg);
          findExtremes();
          findCentralPixel();
          calculateNormals();
          calculateBackSide();
        }
      }
    
    
      // savePoints
      public void savePoints(String fileName){
        String[] s = new String[nPoints+1];
        s[0] = xSize+" "+ySize;
        
        rgbImg.loadPixels();
        for(int i = 0; i < s.length-1; i++){
          if(consImg[i]){
            PVector p = map3D[i].get();
            color col = rgbImg.pixels[i];
            s[i+1] = p.x+" "+p.y+" "+p.z+" "+red(col)+" "+green(col)+" "+blue(col);
          }
          else{
            s[i+1] = "-99"+" "+"-99"+" "+"-99"+" "+"-99"+" "+"-99"+" "+"-99";
          }
        }
        
        saveStrings(fileName+".points",s);
        println("3D points saved in "+fileName+".points");
      }
    
    
      // savePoints
      public void savePoints(){
        savePoints("savedScan");
      }
    
    
      // drawLine
      private void drawLine(PVector p1, PVector p2, color c){
        float distSq = sq(p1.x-p2.x) + sq(p1.y-p2.y) + sq(p1.z-p2.z);
        if(distSq < maxSepSq){
          stroke(c);
          line(p1.x,p1.y,p1.z,p2.x,p2.y,p2.z);
        }
      }
    
    
      // drawTriangle
      private void drawTriangle(PVector p1, PVector p2, PVector p3){
        float distSq1 = sq(p1.x-p2.x) + sq(p1.y-p2.y) + sq(p1.z-p2.z);
        float distSq2 = sq(p1.x-p3.x) + sq(p1.y-p3.y) + sq(p1.z-p3.z);
        float distSq3 = sq(p2.x-p3.x) + sq(p2.y-p3.y) + sq(p2.z-p3.z);
        boolean cond = (distSq1 < maxSepSq) && (distSq2 < maxSepSq) && (distSq3 < maxSepSq) &&
                       (distSq1 != 0) && (distSq2 != 0) && (distSq3 != 0);
        if(cond){
          beginShape(TRIANGLES);
            vertex(p1.x,p1.y,p1.z);
            vertex(p2.x,p2.y,p2.z);
            vertex(p3.x,p3.y,p3.z);
          endShape();
        }
      }
    
      
      // drawTriangle
      private void drawTriangle(PVector p1, PVector p2, PVector p3, color c1, color c2, color c3){
        float distSq1 = sq(p1.x-p2.x) + sq(p1.y-p2.y) + sq(p1.z-p2.z);
        float distSq2 = sq(p1.x-p3.x) + sq(p1.y-p3.y) + sq(p1.z-p3.z);
        float distSq3 = sq(p2.x-p3.x) + sq(p2.y-p3.y) + sq(p2.z-p3.z);
        boolean cond = (distSq1 < maxSepSq) && (distSq2 < maxSepSq) && (distSq3 < maxSepSq) &&
                       (distSq1 != 0) && (distSq2 != 0) && (distSq3 != 0);
        if(cond){
          beginShape(TRIANGLES);
            fill(c1);
            vertex(p1.x,p1.y,p1.z);
            fill(c2);
            vertex(p2.x,p2.y,p2.z);
            fill(c3);
            vertex(p3.x,p3.y,p3.z);
          endShape();
        }
      }
    
    
      // drawPsyTriangle
      private void drawPsyTriangle(PVector p1, PVector p2, PVector p3, color c){
        float distSq1 = sq(p1.x-p2.x) + sq(p1.y-p2.y) + sq(p1.z-p2.z);
        float distSq2 = sq(p1.x-p3.x) + sq(p1.y-p3.y) + sq(p1.z-p3.z);
        float distSq3 = sq(p2.x-p3.x) + sq(p2.y-p3.y) + sq(p2.z-p3.z);
        boolean cond = (distSq1 < maxSepSq) && (distSq2 < maxSepSq) && (distSq3 < maxSepSq) &&
                       (distSq1 != 0) && (distSq2 != 0) && (distSq3 != 0);
        if(cond){
          fill(blendColor(c,color(random(-150,150),random(-150,150),0),ADD));
          beginShape(TRIANGLES);
            vertex(p1.x,p1.y,p1.z);
            vertex(p2.x,p2.y,p2.z);
            vertex(p3.x,p3.y,p3.z);
          endShape();
        }
      }
    
      
      // drawAsPixels
      public void drawAsPixels(int pSize){
        strokeWeight(pSize);
        rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                stroke(rgbImg.pixels[index]);
                point(map3D[index].x,map3D[index].y,map3D[index].z);
              }
            }
          }  
        }
        noStroke();
      }
      
    
      // drawAsLines
      public void drawAsLines(){
        strokeWeight(1);
        rgbImg.loadPixels();
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              if(consImg[index]){
                if(x+1 < xSize && consImg[index+1]){
                  drawLine(map3D[index],map3D[index+1],rgbImg.pixels[index]);
                }
                if(y+1 < ySize && consImg[index+xSize]){
                  drawLine(map3D[index],map3D[index+xSize],rgbImg.pixels[index]);
                }
                if(x+1 < xSize && y+1 < ySize && consImg[index+1+xSize]){
                  drawLine(map3D[index],map3D[index+1+xSize],rgbImg.pixels[index]);
                }
              }
            }
          }
        }
        noStroke();
      }
      
    
      // drawAsTriangles
      public void drawAsTriangles(){
        noStroke();
        rgbImg.loadPixels();
        for(int y = 0; y < ySize-1; y++){
          if(!empty[y] && !empty[y+1]){
            int xStart = min(ini[y],ini[y+1]);
            int xEnd = max(end[y],end[y+1]);
            for(int x = xStart; x < xEnd; x++){
              int index = x + y*xSize;
              // First triangle
              if(consImg[index] && consImg[index+1] && consImg[index+xSize]){
                drawTriangle(map3D[index],map3D[index+1],map3D[index+xSize],rgbImg.pixels[index],rgbImg.pixels[index+1],rgbImg.pixels[index+xSize]);
              }
              else if(consImg[index] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawTriangle(map3D[index],map3D[index+1+xSize],map3D[index+xSize],rgbImg.pixels[index],rgbImg.pixels[index+1+xSize],rgbImg.pixels[index+xSize]);
              }
              // Second triangle
              if(consImg[index+1] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawTriangle(map3D[index+1],map3D[index+1+xSize],map3D[index+xSize],rgbImg.pixels[index+1],rgbImg.pixels[index+1+xSize],rgbImg.pixels[index+xSize]);
              }
              else if(consImg[index] && consImg[index+1] && consImg[index+1+xSize]){
                drawTriangle(map3D[index],map3D[index+1],map3D[index+1+xSize],rgbImg.pixels[index],rgbImg.pixels[index+1],rgbImg.pixels[index+1+xSize]);
              }
            }
          }
        }
      }
    
    
      // drawAsPsyTriangles
      public void drawAsPsyTriangles(){
        noStroke();
        rgbImg.loadPixels();
        for(int y = 0; y < ySize-1; y++){
          if(!empty[y] && !empty[y+1]){
            int xStart = min(ini[y],ini[y+1]);
            int xEnd = max(end[y],end[y+1]);
            for(int x = xStart; x < xEnd; x++){
              int index = x + y*xSize;
              // First triangle
              if(consImg[index] && consImg[index+1] && consImg[index+xSize]){
                drawPsyTriangle(map3D[index],map3D[index+1],map3D[index+xSize],rgbImg.pixels[index]);
              }
              else if(consImg[index] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawPsyTriangle(map3D[index],map3D[index+1+xSize],map3D[index+xSize],rgbImg.pixels[index]);
              }
              // Second triangle
              if(consImg[index+1] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawPsyTriangle(map3D[index+1],map3D[index+1+xSize],map3D[index+xSize],rgbImg.pixels[index+1]);
              }
              else if(consImg[index] && consImg[index+1] && consImg[index+1+xSize]){
                drawPsyTriangle(map3D[index],map3D[index+1],map3D[index+1+xSize],rgbImg.pixels[index+1]);
              }
            }
          }
        }
      }
    
    
      // drawBackSide
      public void drawBackSide(color c){
        noStroke();
        fill(c);
        for(int y = 0; y < ySize-1; y++){
          if(!empty[y] && !empty[y+1]){
            int xStart = min(ini[y],ini[y+1]);
            int xEnd = max(end[y],end[y+1]);
            for(int x = xStart; x < xEnd; x++){
              int index = x + y*xSize;
              // First triangle
              if(consImg[index] && consImg[index+1] && consImg[index+xSize]){
                drawTriangle(back3D[index],back3D[index+1],back3D[index+xSize]);
              }
              else if(consImg[index] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawTriangle(back3D[index],back3D[index+1+xSize],back3D[index+xSize]);
              }
              // Second triangle
              if(consImg[index+1] && consImg[index+1+xSize] && consImg[index+xSize]){
                drawTriangle(back3D[index+1],back3D[index+1+xSize],back3D[index+xSize]);
              }
              else if(consImg[index] && consImg[index+1] && consImg[index+1+xSize]){
                drawTriangle(back3D[index],back3D[index+1],back3D[index+1+xSize]);
              }
            }
          }
        }
      }
    
    }
    
    
    /*
     * ScanMask class
     *
     * Subclass of the ScanContainer class
     * Adds the posibility to use a mask to control the data that is shown
     */
    
    public class ScanMask extends ScanContainer{
      public boolean[] consImgOrig; // Copy of the original consImg array
      public boolean value;         // Value used to initialize the mask
      public boolean[] maskImg;     // Mask with the valid points 
        
      
      //
      // Constructor
      //
    
      public ScanMask(){
    
      }
      
      
      //
      // Class Methods 
      //
    
      // create
      public void create(ScanContainer tempScan, boolean tempValue){
        initialize(tempScan.map3D,tempScan.rgbImg,tempScan.consImg);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        copyOriginal();
        initializeMask(tempValue);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // createFromFile
      public void createFromFile(String tempFile, boolean tempValue){
        initialize(tempFile);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        copyOriginal();
        initializeMask(tempValue);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // copyOriginal
      public void copyOriginal(){
        consImgOrig = new boolean[nPoints];
        for(int i = 0; i < nPoints; i++){
          consImgOrig[i] = consImg[i];
        }
      }
    
    
      // restoreOriginal
      public void restoreOriginal(){
        for(int i = 0; i < nPoints; i++){
          consImg[i] = consImgOrig[i];
        }
      }
    
    
      // initializeMask
      public void initializeMask(boolean val){
        value = val;
        maskImg = new boolean[nPoints];
        for(int i = 0; i < nPoints; i++){
          maskImg[i] = value;
        }
      }
    
    
      // init
      public void init(){
    
      }
    
    
      // addMask
      public void addMask(){
        for(int y = 0; y < ySize; y++){
          if(!empty[y]){
            for(int x = ini[y]; x <= end[y]; x++){
              int index = x + y*xSize;
              consImg[index] = consImgOrig[index] && maskImg[index];
            }
          }
        }
      }
    
    
      // update
      public void update(int t){
    
      }
    
    
      // reduceResolution
      public void reduceResolution(int n){
        if(n > 1){
          restoreOriginal();
          super.reduceResolution(n);
          copyOriginal();
          initializeMask(value);
          init();
          addMask();
          calculateBackSide();
        }
      }
      
    
      // centerAndExtend
      public void centerAndExtend(){
        restoreOriginal();
        super.centerAndExtend();
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // extend
      public void extend(int xSizeExt, int ySizeExt){
        restoreOriginal();
        super.extend(xSizeExt,ySizeExt);
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // scaleFactor
      public void scaleFactor(float f){
        restoreOriginal();
        super.scaleFactor(f);
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
      
      // fillHoles
      public void fillHoles(int maxHoles){
        restoreOriginal();
        super.fillHoles(maxHoles);
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // gaussianSmooth
      public void gaussianSmooth(int n){
        if(n > 0){
          restoreOriginal();
          super.gaussianSmooth(n);
          copyOriginal();
          initializeMask(value);
          init();
          addMask();
          calculateBackSide();
        }
      }
      
      
      // centerOnCursor
      public void centerOnCursor(){
        restoreOriginal();
        super.centerOnCursor();
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
    
      // constrainPoints
      public void constrainPoints(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax){
        restoreOriginal();
        super.constrainPoints(xMin,xMax,yMin,yMax,zMin,zMax);
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
      
      
      // crop
      public void crop(){
        restoreOriginal();
        super.crop();
        copyOriginal();
        initializeMask(value);
        init();
        addMask();
        calculateBackSide();
      }
    
    }
    
    
    /*
     * ScanModifier class
     *
     * Subclass of the ScanContainer class
     * Adds the posibility to modify the data from the scan
     */
     
    public class ScanModifier extends ScanContainer{
      public PVector[] map3DOrig;   // Copy of the original map3D array
      public PImage rgbImgOrig;     // Copy of the original rgbImg array 
      public boolean[] consImgOrig; // Copy of the original consImg array 
      public PVector[] normalsOrig; // Copy of the original normals array 
      public PVector[] back3DOrig;  // Copy of the original back3D array 
    
    
      //
      // Constructor
      //
    
      public ScanModifier(){
    
      }
      
    
      //
      // Class Methods 
      //
    
      // create
      public void create(ScanContainer tempScan){
        initialize(tempScan.map3D,tempScan.rgbImg,tempScan.consImg);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
        copyOriginal();
        init();
      }
    
    
      // createFromFile
      public void createFromFile(String tempFile){
        initialize(tempFile);
        findExtremes();
        findCentralPixel();
        calculateNormals();
        calculateBackSide();
        copyOriginal();
        init();
      }
    
    
      // copyOriginal
      public void copyOriginal(){
        map3DOrig = new PVector[nPoints];
        rgbImgOrig = createImage(xSize,ySize,RGB);
        consImgOrig = new boolean[nPoints];
        normalsOrig = new PVector[nPoints];
        back3DOrig = new PVector[nPoints];
        
        rgbImgOrig.loadPixels();
        rgbImg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          map3DOrig[i] = map3D[i].get();
          rgbImgOrig.pixels[i] = rgbImg.pixels[i];
          consImgOrig[i] = consImg[i];
          normalsOrig[i] = normals[i].get();
          back3DOrig[i] = back3D[i].get();
        }
        rgbImgOrig.updatePixels();
      }
    
    
      // restoreOriginal
      public void restoreOriginal(){
        rgbImgOrig.loadPixels();
        rgbImg.loadPixels();
        for(int i = 0; i < nPoints; i++){
          map3D[i] = map3DOrig[i].get();
          rgbImg.pixels[i] = rgbImgOrig.pixels[i];
          consImg[i] = consImgOrig[i];
          normals[i] = normalsOrig[i].get();
          back3D[i] = back3DOrig[i].get();
        }
        rgbImg.updatePixels();
      }
    
    
      // init
      public void init(){
      
      }
    
    
      // update
      public void update(int t){
      
      }
    
    
      // reduceResolution
      public void reduceResolution(int n){
        if(n > 1){
          restoreOriginal();
          super.reduceResolution(n);
          copyOriginal();
          init();
          calculateBackSide();
        }
      }
    
      
      // centerAndExtend
      public void centerAndExtend(){
        restoreOriginal();
        super.centerAndExtend();
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    
      // extend
      public void extend(int xSizeExt, int ySizeExt){
        restoreOriginal();
        super.extend(xSizeExt,ySizeExt);
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    
      // scaleFactor
      public void scaleFactor(float f){
        restoreOriginal();
        super.scaleFactor(f);
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    
      // fillHoles
      public void fillHoles(int maxHoles){
        restoreOriginal();
        super.fillHoles(maxHoles);
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    
      // gaussianSmooth
      public void gaussianSmooth(int n){
        if(n > 0){
          restoreOriginal();
          super.gaussianSmooth(n);
          copyOriginal();
          init();
          calculateBackSide();
        }
      }
      
      
      // centerOnCursor
      public void centerOnCursor(){
        restoreOriginal();
        super.centerOnCursor();
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    
      // constrainPoints
      public void constrainPoints(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax){
        restoreOriginal();
        super.constrainPoints(xMin,xMax,yMin,yMax,zMin,zMax);
        copyOriginal();
        init();
        calculateBackSide();
      }
      
      
      // crop
      public void crop(){
        restoreOriginal();
        super.crop();
        copyOriginal();
        init();
        calculateBackSide();
      }
    
    }
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Javier Graciá Carpio

    3D drawing machines

    Add to Faves Me Likey@! 8
    You must login/register to add this sketch to your favorites.

    A new version of my Kinect scan viewer.

    It includes all the drawing effects that I used in this video:
    https://vimeo.com/54903115

    Including a simple 3D face morphing, and a DLA drawing effect inspired by this sketch by Diana Lange:
    http://www.openprocessing.org/sketch/80169

    It's quite easy to add new effects creating new ScanMask and/or ScanModifier subclasses. Please, tweak it with your inventions :)

    Controls:
    - Use the mouse to rotate the scan
    - Use "Up" and "Down" arrows to zoom
    - Use "Left" and "Right" arrows to decrease/increase the scan resolution
    - 'p' switchs between the mesh view and the pixels view
    - 'l' switchs between the mesh view and the lines view
    - "+" and "-" increase/decrease the pixel size (only in pixel view)
    - 'Space' creates some psychedelic effects (only in mesh view)
    - 'c' centers the scan on the cursor position
    I specially like the effects in the lines mode (press "l") and low resolution
    bitcraft
    7 Jan 2013
    Sweet! I really like your explorations in Mesh-Land!
    Keep em coming :-)
    Thanks! Hey bitcraft, very few people is publishing kinect related sketches in Openprocessing, it will be nice if this changes in 2013. You are not tempted?
    ale plus+
    8 Jan 2013
    Hola Javier!
    I agree with Bitcraft, thanks for sharing your kinect explorations, good job!
    Gracias Ale! As I said to bitcraft, why don't you buy yourself a kinect and start posting some of your explorations? I can guarantee is very fun!
    Pierre MARZIN
    15 Jan 2013
    Excellent!
    Thanks Pierre!
    You need to login/register to comment.