block by dribnet 6dd1c8399ba7c5863aba05d0774e1227

sewn by Jared Schiffman (1998)

Full Screen

Restoration of Jared Schiffman’s sewn

directions: use the three mouse buttons or option+click and command+click*

This is a port of Jared Schiffman’s sewn project to p5.js.

Sewn is an interactive sketch originally created in 1998 when Jared was a student at the Aesthetics and Computation group (ACG) at the MIT Media Lab.

This restoration project was done by Tom White and based on archived copies of the original, including executables. Sewn was one of the very early software sketches based on ACG’s acu library. The first port was to OpenFrameworks as OpenFrameworks is based on the coding conventions of acu, and this port required very few code changes to compile and run. This followup port to p5.js was very much a rewrite / translation based on the working OpenFrameworks version and meant to allow this sketch to live on the web and reach a larger modern audience.

index.html

<head>
    <script src="p5_1.2.0.js"></script>
    <script language="javascript" type="text/javascript" src="block.js"></script>
    <script language="javascript" type="text/javascript" src="sketch.js"></script>
    <style> body {padding: 0; margin: 0;} </style>
</head>

<body style="background-color:white">
</body>

block.js

// split type
const VERTICAL = 0;
const HORIZONTAL = 1;

// animation types
const ANIM_NONE = 0;
const LB_IN = 1;
const LB_OUT = 2;
const LT_IN = 3;
const LT_OUT = 4;
const RB_IN = 5;
const RB_OUT = 6;
const RT_IN = 7;
const RT_OUT = 8;
const TL_IN = 9;
const TL_OUT = 10;
const TR_IN = 11;
const TR_OUT = 12;
const BL_IN = 13;
const BL_OUT = 14;
const BR_IN = 15;
const BR_OUT = 16;
const LL_IN = 17;
const LL_OUT = 18;
const RR_IN = 19;
const RR_OUT = 20;
const BB_IN = 21;
const BB_OUT = 22;
const TT_IN = 23;
const TT_OUT = 24;
const TP_IN = 25;
const TP_OUT = 26;
const LP_IN = 27;
const LP_OUT = 28;
const BP_IN = 29;
const BP_OUT = 30;
const RP_IN = 31;
const RP_OUT = 32;

// color constants
const pullVr = 0.4;
const pullVg = 0.0;
const pullVb = 0.0;
const pullHr = 0.8;
const pullHg = 0.6;
const pullHb = 0.0;

// millisecond based timer used to unsplit blocks after 30 seconds
let theTime = 0;

// OG lerp function (different argument order)
function acuLerpf(A, l1, l2) {
  return lerp(l1, l2, A);
}

function Block() {
  this.hasChildren = false;
  this.parent = null;
  this.childA = null;
  this.childB = null;
  this.split = null;
  this.splitPercent = 0;
  this.L = 0;
  this.R = 0;
  this.B = 0;
  this.T = 0;
  this.cr = 0.5;
  this.cg = 0.5;
  this.cb = 0.5;
  this.animateTime = 0.0;
  this.complete = 1.0;
  this.unsplitCountdown = 0;
  this.unsplitBegun = false;
  this.animType = ANIM_NONE;

  this.Draw = function() {
    if (this.parent == null) {
      // only root sets time
      theTime = millis();
    }
    if (this.hasChildren) {
      this.childA.Draw();
      this.childB.Draw();
    }
    else {
      fill(this.cr * 255, this.cg * 255, this.cb * 255);
      noStroke();
      rect(this.L, this.B, this.R, this.T);
    }
  }

  // lines get drawn after the rectangles
  this.drawLines = function() {
    if (this.hasChildren) {
      this.childA.drawLines();
      this.childB.drawLines();
    }
    else {
      noFill();
      stroke(int(0.2*255));
      rect(this.L, this.T, this.R, this.B);
    }

    if ( this.hasChildren && ! this.unsplitBegun ) {
      if ( this.childA.hasChildren || this.childB.hasChildren )
        this.unsplitCountdown = theTime;
      else {
        if ( theTime - this.unsplitCountdown > 30000 ) {
          // 30 second delay
          this.UnsplitBegin();
        }
      }
    }
  }

  this.Split = function(S, P) {
    if(this.hasChildren) {
      // don't try to split twice
      return;
    }

    this.unsplitCountdown = theTime;
    this.hasChildren = true;
    this.split = S;
    // splitPercent will be computed
    this.splitPercent = P;
    this.childA = new Block();
    this.childB = new Block();
    this.childA.parent = this;
    this.childB.parent = this;

    let choose = random([false, true]);
    this.animateTime = 0.0;

    if (this.split == VERTICAL) {
      if (choose) {
        this.animType = BP_IN;
      }
      else {
        this.animType = TP_IN;
      }
    }
    if (this.split == HORIZONTAL) {
      if (choose) {
        this.animType = LP_IN;
      }
      else {
        this.animType = RP_IN;
      }
    }
  }

  this.UnsplitBegin = function() {
    if (! this.hasChildren) {
      return;
    }

    if (this.unsplitBegun) {
      return;
    }

    this.unsplitBegun = true;

    this.childA.UnsplitBegin();
    this.childB.UnsplitBegin();

    this.animateTime = 0.0;
    let choose = random([false, true]);
    if ( this.split==VERTICAL ) {
      if (choose) {
        this.animType = BP_OUT;
      }
      else {
        this.animType = TP_OUT;
      }
    }
    if ( this.split==HORIZONTAL ) { 
      if (choose) {
        this.animType = LP_OUT;
      }
      else {
        this.animType = RP_OUT;
      }
    }
  }

  this.UnsplitFinish = function() {
    if (! this.hasChildren) {
      return;
    }

    this.unsplitBegun = false;
    this.hasChildren = false;
    this.complete = 1.0;
    this.childA = null;
    this.childB = null;
    this.animType = ANIM_NONE;
  }

  // sub funciton implements goto logic in original
  // returns true if calling function should also return
  this.update_subroutine = function(P) {
    if ( this.animType != ANIM_NONE ) {
      this.childA.complete = this.animateTime;
      this.childB.complete = this.animateTime;
      if ( this.animateTime >= 1.0 ) {
        if ( this.animType==LB_OUT || this.animType==LT_OUT ||
             this.animType==RB_OUT || this.animType==RT_OUT ||
             this.animType==TL_OUT || this.animType==TR_OUT ||
             this.animType==BL_OUT || this.animType==BR_OUT ||
             this.animType==LL_OUT || this.animType==RR_OUT ||
             this.animType==BB_OUT || this.animType==TT_OUT || 
             this.animType==TP_OUT || this.animType==LP_OUT || 
             this.animType==BP_OUT || this.animType==RP_OUT ) {
            this.UnsplitFinish();
            this.animType = ANIM_NONE;
            return true;
        }
        else {
            this.animType = ANIM_NONE;
            return false;
        }
      }
      // note: animations run for 20 frames independent of wall time
      this.animateTime += 0.05;
    
      let A = 0.0;
      if ( this.animType==LB_OUT || this.animType==LT_OUT ||
        this.animType==RB_OUT || this.animType==RT_OUT ||
        this.animType==TL_OUT || this.animType==TR_OUT ||
        this.animType==BL_OUT || this.animType==BR_OUT ||
        this.animType==LL_OUT || this.animType==RR_OUT ||
        this.animType==BB_OUT || this.animType==TT_OUT ||
        this.animType==TP_OUT || this.animType==LP_OUT ||
        this.animType==BP_OUT || this.animType==RP_OUT ) {

        A = 1.0 - this.animateTime;
      }
      else {
        A = this.animateTime;
      }
      if ( A > 1.0 ) {
        A = 1.0;
      }
      if ( A < 0.0 ) {
        A = 0.0;
      }

      if ( this.split == VERTICAL ) {
        this.childA.setColor( acuLerpf( A, this.cr, (this.cr+pullVr)/2.0 ), 
              acuLerpf( A, this.cg, (this.cg+pullVg)/2.0 ), 
              acuLerpf( A, this.cb, (this.cb+pullVb)/2.0 ) );
        this.childB.setColor( acuLerpf( A, this.cr, (this.cr+(1.0-pullVr))/2.0 ), 
              acuLerpf( A, this.cg, (this.cg+(1.0-pullVg))/2.0 ), 
              acuLerpf( A, this.cb, (this.cb+(1.0-pullVb))/2.0 ) );
      }
      if ( this.split == HORIZONTAL ) {
        this.childA.setColor( acuLerpf( A, this.cr, (this.cr+pullHr)/2.0 ), 
              acuLerpf( A, this.cg, (this.cg+pullHg)/2.0 ), 
              acuLerpf( A, this.cb, (this.cb+pullHb)/2.0 ) );
        this.childB.setColor( acuLerpf( A, this.cr, (this.cr+(1.0-pullHr))/2.0 ), 
              acuLerpf( A, this.cg, (this.cg+(1.0-pullHg))/2.0 ), 
              acuLerpf( A, this.cb, (this.cb+(1.0-pullHb))/2.0 ) );
      }

      let L = this.L;
      let R = this.R;
      let T = this.T;
      let B = this.B;

      switch( this.animType ) {
        case TP_IN:
        case TP_OUT:
          this.childB.setLerpRect( A, L, T, R, T,
             L, (T-B)*P+B, R, T );
          this.childA.setLerpRect( A, L, B, R, T,
             L, B, R, (T-B)*P+B );
          break;
        case BP_IN:
        case BP_OUT:
          this.childA.setLerpRect( A, L, B, R, B,
             L, B, R, (T-B)*P+B );
          this.childB.setLerpRect( A, L, B, R, T,
             L, (T-B)*P+B, R, T );
          break;
        case LP_IN:
        case LP_OUT:
          this.childA.setLerpRect( A, L, B, L, T,
                                  L, B, L+(R-L)*P, T );
          this.childB.setLerpRect( A, L, B, R, T,
                                  L+(R-L)*P, B, R, T );
          break;
        case RP_IN:
        case RP_OUT:
          this.childB.setLerpRect( A, R, B, R, T,
                L+(R-L)*P, B, R, T );
          this.childA.setLerpRect( A, L, B, R, T,
                L, B, L+(R-L)*P, T );
          break;
      }
    }
    return false;
  }

  this.UpdateChildren = function() {
    if(! this.hasChildren) {
      return;
    }

    let P = this.splitPercent;
    if ( P > 1.0 ) {
      P = 1.0;
    }
    if ( P < 0.0 ) {
      P = 0.0;
    }

    let do_return = this.update_subroutine(P);
    if (do_return) {
      return;
    }
  
    let L = this.L;
    let R = this.R;
    let T = this.T;
    let B = this.B;

    if ( this.animType==ANIM_NONE ) {
      // don't animate, just set in place
      // P = 0.5;
      if ( this.split == VERTICAL ) {
        this.childA.setColor( (this.cr+pullVr)/2.0,
              (this.cg+pullVg)/2.0, 
              (this.cb+pullVb)/2.0  );
        this.childB.setColor( (this.cr+(1.0-pullVr))/2.0,
              (this.cg+(1.0-pullVg))/2.0,
              (this.cb+(1.0-pullVb))/2.0  );
        this.childA.setRect( L,B,R,(T-B)*P+B );
        this.childB.setRect( L,(T-B)*P+B,R,T );
        // childA.setRect( L,B,R,(T-B)*P+B );
        // childB.setRect( L,(T-B)*P+B,R,T );
      }
      if ( this.split == HORIZONTAL ) {
        this.childA.setColor( (this.cr+pullHr)/2.0,
                          (this.cg+pullHg)/2.0, 
                          (this.cb+pullHb)/2.0  );
        this.childB.setColor( (this.cr+(1.0-pullHr))/2.0,
                          (this.cg+(1.0-pullHg))/2.0,
                          (this.cb+(1.0-pullHb))/2.0  );
        this.childA.setRect( L,B,L+(R-L)*P,T );
        this.childB.setRect( L+(R-L)*P,B,R,T );
      }
    }

    this.childA.UpdateChildren();
    this.childB.UpdateChildren();
  }

  this.setRect = function(l, b, r, t) {
    this.L = l;
    this.B = b;
    this.R = r;
    this.T = t;
  }

  this.setLerpRect = function(A, 
                   l1, t1, r1, b1,
                   l2, t2, r2, b2 ) {
    A = (A<0) ? 0 : A;
    A = (A>1) ? 1 : A;
    A = Math.pow( A, 0.7 );
    this.L = acuLerpf( A, l1, l2 );
    this.T = acuLerpf( A, t1, t2 );
    this.R = acuLerpf( A, r1, r2 );
    this.B = acuLerpf( A, b1, b2 );
  }

  this.setColor = function(red, green, blue) {
    this.cr = red;
    this.cg = green;
    this.cb = blue;
  }

  this.MouseInBlock = function(X, Y) {
    if(this.hasChildren) {
      b1 = this.childA.MouseInBlock(X, Y);
      if (b1 != null) {
        return b1;
      }
      b1 = this.childB.MouseInBlock(X, Y);
      if (b1 != null) {
        return b1;
      }
      return null;
    }
    if ( X>=this.L && X<=this.R && Y<=this.T && Y>=this.B ) {
      return this;
    }
    else {
      return null;
    }
  }

  this.getNumLeaves = function() {
    if(! this.hasChildren) {
      return 1;
    }
    else {
      let l1 = this.childA.getNumLeaves();
      let l2 = this.childB.getNumLeaves();
      return l1 + l2;
    }
  }

  this.BalanceSplit = function() {
    if(! this.hasChildren) {
      return;
    }
  
    this.childA.BalanceSplit();
    this.childB.BalanceSplit();
  
    let na = this.childA.getNumLeaves();
    let nb = this.childB.getNumLeaves();
    let D = (na+nb);
    if ( D == 0 ) {
      D = 1;
    }

    this.splitPercent = 0.95*this.splitPercent + 0.05*(na/(D));
  }
}

sketch.js

const canvasWidth = 960;
const canvasHeight = 720;

let B = null;
let mx=null, my=null;

function setup () {
  createCanvas(canvasWidth, canvasHeight);
  rectMode(CORNERS);

  B = new Block();
  B.setRect(0.2*400,0.2*400,0.8*400,0.8*400);
  mx = 0;
  my = 0;
  // noLoop();
  // colorMode(HSB, 360, 100, 100, 1);

  // enable right click
  for (let element of document.getElementsByClassName("p5Canvas")) {
    element.addEventListener("contextmenu", (e) => e.preventDefault());
  }
}

function draw () {
  translate(0, height); 
  scale(1, -1);

  background(0);
  B.setRect( 0.2*width,0.2*height,0.8*width,0.8*height );
  fill(255);

  B.BalanceSplit();
  B.UpdateChildren();
  B.Draw();
  B.drawLines();
}

function doSimulatedMousePress(whichButton, mx, my) {
  C = B.MouseInBlock(mx, my);
  if (whichButton == RIGHT) {
    if (C != null && C.parent != null) {
      C.parent.UnsplitBegin();
    }
  }
  else if (whichButton == CENTER) {
    if ( C != null ){
      C.Split( VERTICAL, 0.5  );
    }
  }
  else if (whichButton == LEFT) {
    if ( C != null ){
      C.Split( HORIZONTAL, 0.5  );
    }
  }
}

function mouseReleased() {
  let flippedY = height - mouseY;
  if (mouseButton == RIGHT || (mouseButton == LEFT && keyIsDown(CONTROL))) {
    doSimulatedMousePress(LEFT, mouseX, flippedY);
  }
  else if (mouseButton == CENTER || (mouseButton == LEFT && keyIsDown(OPTION))) {
    doSimulatedMousePress(CENTER, mouseX, flippedY);
  }
  else if (mouseButton == LEFT) {
    doSimulatedMousePress(LEFT, mouseX, flippedY);
  }
}

function keyTyped() {
  let flippedY = height - mouseY;
  if (key == 'a' || key == '1') {
    doSimulatedMousePress(LEFT, mouseX, flippedY);
  }
  else if (key == 's' || key == '2') {
    doSimulatedMousePress(CENTER, mouseX, flippedY);
  }
  else if (key == 'd' || key == '3') {
    doSimulatedMousePress(RIGHT, mouseX, flippedY);
  }
}

function keyPressed() {
  let flippedY = height - mouseY;
  if (keyCode == LEFT_ARROW) {
    doSimulatedMousePress(LEFT, mouseX, flippedY);
  }
  if (keyCode == UP_ARROW || keyCode == DOWN_ARROW) {
    doSimulatedMousePress(CENTER, mouseX, flippedY);
  }
  else if (keyCode == RIGHT_ARROW) {
    doSimulatedMousePress(RIGHT, mouseX, flippedY);
  }
}