> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.WJuw4ovdPex/rev.2091
 * 
 * authors: 
 *   Alexander Ruff
 *   

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/*
 * INSTRUCTIONS:
 *  left-click/drag - make a fold through the line, that folds toward the
 *    top left corner
 *  left-click/drag - make a fold through the line, that folds away from the
 *    top left corner
 *  'n' - New piece of paper
 *  'Right' - Undo previous cut
 *  'Left'  - Redo/Next
 *  'r' - reflect previous fold
 *  'f' - flip page over
 * TODO
 *  pull from server
 *  refactor sheets data structure to maintain pieces connected by folds
 *  unfold (different from undo because folds are still marked)
 *  clean up code
 *  mountain folds
 * MAYBE:
 *  save to server
 *  symmetric folds
 *  bounce to grid points
 *  allow user to rotate model
 *  allow user to translate model
 *  fold bias at center of page instead of top left.
 *  ghost new fold
 *  RTCE
 *  scissors
 *  figure out which pieces are visible
 *  ruler (as an extension of grid bouncing)
 *  snap edges
 *  smart center
 *  smart rotate
 *  allow unfolds other than the most recent one where possible
 */
var foldingSheets = [];
var foldingFinal = [];
var foldingTime = 1000;
var foldingStart;

var firstX, firstY;
var lastX, lastY;

var sheets;
var oldSheets = [];
var btn;
50,50,450,50,450,600,50,600
var foldList = [
                [249.9,50,50,249.9,true],
                [250.1,50,450,249.9,false],
                [249,49.9,50,528,true],
                [251,49.9,450,528,false]];

function valley(x1,y1,x2,y2) {
  strokeWeight(2);
  stroke(0);
  var d = dist(firstX, firstY, mouseX, mouseY);
  for(var i=0; i<d; i+=20) {
    line(lerp(firstX, mouseX, i/d),lerp(firstY, mouseY, i/d),
         lerp(firstX, mouseX, min((i+10)/d,1)),lerp(firstY, mouseY, min((i+10)/d,1)));
  }
}

function mountain(x1,y1,x2,y2) {
  strokeWeight(2);
  stroke(0);
  var d = dist(firstX, firstY, mouseX, mouseY);
  for(var i=0; i<d; i+=40) {
    line(lerp(firstX, mouseX, i/d),lerp(firstY, mouseY, i/d),
         lerp(firstX, mouseX, min((i+10)/d,1)),lerp(firstY, mouseY, min((i+10)/d,1)));
  }
  for(var i=0; i<d; i+=10) {
    point(lerp(firstX, mouseX, i/d),lerp(firstY, mouseY, i/d));
  }
}

function poly(P) {
  strokeWeight(2);
  stroke(0);
  if(P[0]) fill(230);
  else     fill(250);
  beginShape();
  for (var i = 1; i < P.length; i += 2) {
    vertex(P[i], P[i+1]);
  }
  endShape(CLOSE);
}

function intersect(P, x1, y1, x2, y2) {
  var I = [];
  var x3 = P[P.length-2];
  var y3 = P[P.length-1];
  for (var i = 1; i < P.length; i += 2) {
    var x4 = P[i];
    var y4 = P[i+1];
    var d = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
    var x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/d;
    var y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/d;
    /*if(max(min(x1,x2),min(x3,x4))-.0001<=x &&
       min(max(x1,x2),max(x3,x4))+.0001>=x &&
       max(min(y1,y2),min(y3,y4))-.0001<=y &&
       min(max(y1,y2),max(y3,y4))+.0001>=y)*/
    if(min(x3,x4)-.0001<=x &&
       max(x3,x4)+.0001>=x &&
       min(y3,y4)-.0001<=y &&
       max(y3,y4)+.0001>=y)
      I.push([i,[x,y]]);
    x3 = x4;
    y3 = y4;
  }
  return I;
}

function cut(x1,y1,x2,y2,fold_dir) {
  oldSheets.push(sheets.slice());
  foldingSheets = [];
  var newSheets = [];
  for(var i = 0; i<sheets.length; i++) {
    var I = intersect(sheets[i],x1,y1,x2,y2);
    if(I.length==2) {
      var a = [sheets[i][0]].concat(I[1][1],I[0][1],sheets[i].slice(I[0][0],I[1][0]));
      var b = [sheets[i][0]].concat(sheets[i].slice(1,I[0][0]),I[0][1],I[1][1],sheets[i].slice(I[1][0]));
      var x = sheets[i][I[0][0]];
      var y = sheets[i][I[0][0]+1];
      if((((x1*y2-x2*y1)*((x1-x)*(y2-y)-(x2-x)*(y1-y))>0) ^
          fold_dir)==1) {
        newSheets.push(a);
        foldingSheets.push(b);
      }
      else {
        newSheets.push(b);
        foldingSheets.push(a);
      }
    }
    else {
      var x = sheets[i][1];
      var y = sheets[i][2];
      if((((x1*y2-x2*y1)*((x1-x)*(y2-y)-(x2-x)*(y1-y))>0)^fold_dir)==1)
           newSheets.push(sheets[i]);
      else foldingSheets.push(sheets[i]);
    }
  }
  fold(x1,y1,x2,y2);
  sheets = newSheets;
  foldingStart = millis();
}

function reflect(x1, y1, x2, y2, x, y) {
  var proj = ((x-x1)*(x2-x1)+(y-y1)*(y2-y1))/((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
  return [2*proj*(x2-x1)+2*x1-x, 2*proj*(y2-y1)+2*y1-y];
}

function fold(x1,y1,x2,y2) {
  foldingFinal = foldingSheets.slice();
  for(var i=0; i<foldingFinal.length; i++) {
    foldingFinal[i] = foldingFinal[i].slice();
    foldingFinal[i][0] = !foldingFinal[i][0];
    for(var j=1; j<foldingFinal[i].length; j+=2) {
      var p = reflect(x1,y1,x2,y2,foldingFinal[i][j],foldingFinal[i][j+1]);
      foldingFinal[i][j] = p[0];
      foldingFinal[i][j+1] = p[1];
    }
  }
}

function folding() {
  var r = (millis()-foldingStart)/foldingTime;
  if(r>=1) {
    sheets = sheets.concat(foldingFinal.reverse());
    foldingSheets = [];
    for(var i=0; i<foldingFinal.length; i++) {
      poly(foldingFinal[i]);
    }
  }
  if(r<1/2) for(var i=0; i<foldingSheets.length; i++) {
    var p = [foldingSheets[i][0]];
    for(far j=1; j<foldingSheets[i].length; j++) {
      p.push(lerp(foldingSheets[i][j],foldingFinal[i][j],r));
    }
    poly(p);
  }
  else for(var i=foldingSheets.length-1; i>=0; i--) {
    var p = [!foldingSheets[i][0]];
    for(far j=1; j<foldingSheets[i].length; j++) {
      p.push(lerp(foldingSheets[i][j],foldingFinal[i][j],r));
    }
    poly(p);
  }
}

void setup() {
  strokeJoin(BEVEL);
  sheets = [[false,50,50,450,50,450,600,50,600]];
  size(500, 650);
  smooth();
}

void mousePressed() {
  btn = mouseButton
  firstX = mouseX;
  firstY = mouseY;
}

void mouseReleased() {
  if(dist(firstX,firstY,mouseX,mouseY)>.1 && foldingSheets.length==0)
    cut(firstX,firstY,mouseX,mouseY,btn==RIGHT);
}

void keyPressed() {
  switch (key) {
    case 'u':
      if(oldSheets.length>0) {
        sheets = oldSheets.pop();
        foldingSheets = [];
      }
      break;
    case 'n':
      sheets = [[false,50,50,450,50,450,600,50,600]];
      foldingSheets = [];
      oldSheets = [];
      break;
    case 'f':
      //needs to actually flip over
      if(foldingSheets.length==0) {
        foldingSheets = sheets;
        sheets = [];
        fold(250,0,250,650);
        foldingStart = millis();
      }
      break;
    case 'p':
      console.log(sheets);
      console.log(foldingSheets);
      console.log(foldingFinal);
      break;
    default:
      if(key==CODED) {
        switch(keyCode) {
          case RIGHT:
            if(foldingSheets.length==0) {
              f = foldList.shift();
              cut(f[0],f[1],f[2],f[3],f[4]);
            }
            break;
        }
      }
  }
}

void draw() {
  background(255);

  // canvas border
  strokeWeight(1);
  stroke(100);
  noFill();
  rect(0, 0, width-1, height-1);

  for(var i=0; i<sheets.length; i++) {
    poly(sheets[i]);
  }

  if(foldingSheets.length>0) folding();

  if (mousePressed) {
    valley(firstX, firstY, mouseX, mouseY);

    strokeWeight(6);
    for(var i=0; i<sheets.length; i++) {
      var I = intersect(sheets[i],firstX,firstY,mouseX,mouseY);
      for(var j=0; j<I.length; j++) point(I[j][1][0],I[j][1][1]);
    }
  }
}