> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.7-uxeB4XEfy/rev.3665
 * 
 * authors: 
 *   James Aspnes

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



LinearMotion clock; // clock value in seconds since midnight
PoissonMotion badclock; // like above, but not very accurate
LinearMotion smoothedBadclock; // tracks above a bit more smoothly

void setup() {
  size(300, 200);
  smooth();
  frameRate(60);

  // seconds since start of day
  int t = hour() * 3600 + minute() * 60 + second();

  clock = new LinearMotion(t, 1);
  badclock = new PoissonMotion(t, 1);
  smoothedBadclock = new LinearMotion(t, 1);
}

void draw() {
  background(255);

  stroke(0);
  fill(0);

  drawClock(200, 100, 100, clock.get());

  drawClock(40, 40, 40, smoothedBadclock.get());

  drawClock(57, 122, 40, badclock.get());
  
  // adjust smoothedBadclock to track badclock
  if(smoothedBadclock.get() > badclock.get()) {
      smoothedBadclock.setRate(0.9);
  } else {
      smoothedBadclock.setRate(1.1);
  }
}

// convert radial to rectangular coordinates
// (x,y) is origin
// r is radius as fraction of min(width, height)/2
// TWO_PI*frac is angle clockwise from up
PVector toRect(PVector origin, float r, float frac) {
  float theta = TWO_PI * frac;

  return new PVector(origin.x + r * sin(theta), origin.y - r * cos(theta));
}

// like line() but takes two PVectors as arguments
void vLine(PVector s, PVector e)
{
  line(s.x, s.y, e.x, e.y);
}

// draw a clock hand
void drawHand(PVector origin, float r, float frac) {
  vLine(origin, toRect(origin, r, frac));
}

// draw a clock centered at (x,y) with radius r
// t specifies seconds since midnight
void drawClock(float x, float y, float r, float t) {
  // don't muck up external styles
  pushStyle();

  PVector origin = new PVector(x, y);

  // clock face
  pushStyle();
  noFill();
  strokeWeight(1);
  ellipse(origin.x, origin.y, 2*r, 2*r);
  popStyle();

  // pips
  for(int i = 1; i < 13; i++) {
    PVector place = toRect(origin, 0.87*r, i / 12.0);
    textAlign(CENTER, CENTER);
    textSize(0.2*r);
    text(str(i), place.x, place.y);
  }

  // hours
  strokeWeight(4);
  drawHand(origin, 0.6*r, t / (12*3600.0));
  // minutes
  strokeWeight(4);
  drawHand(origin, 0.96*r, t / 3600.0);

  // seconds
  stroke(0xffff0000);
  strokeWeight(1);
  drawHand(origin, r, t / 60.0);

  popStyle();
}

// base class for time-dependent variables
class Motion {
  float x; // position
  int t; // last time x was updated as returned by millis()

  Motion(float x0) {
    set(x0);
  }

  // update and return x
  float get() {
    return x;
  }

  // set new position
  void set(float x0) {
    x = x0;
    t = millis();
  }
}

class BrownianMotion extends Motion {
  float sd; // standard deviation of drift in units/second

    BrownianMotion(float x0, float sd) {
    super(x0);
    this.sd = sd;
  }

  float get() {
    int samples = 6; // how many samples to combine to fake normal
    int oldt = t;
    t = millis();
    if(t > oldt) {
      float step = sd * sqrt((t - oldt) / (1000.0 * samples));
      for(int i = 0; i < samples; i++) {
        // the random variable here has sd 1
        x += random(-sqrt(3), sqrt(3))*step;
      }
    }
    return x;
  }
}

class LinearMotion extends Motion {
  float rate; // velocity in units / second

  LinearMotion(float x0, float r) {
    super(x0);
    rate = r;
  }

  float get() {
    // note we don't change x to avoid accumulating error
    return x + rate*(millis() - t)/1000.0;
  }

  void setRate(float r) {
    // fix where we were
    set(get());
    // then update rate
    rate = r;
  }
}

// integer-valued Poisson process
class PoissonMotion extends LinearMotion {
  float nextTime; // when the next jump will happen

  PoissonMotion(float x, float rate) {
    super(x, rate);
    nextTime = millis() + waitingTime();
  }

  // generate a random waiting time in milliseconds
  float waitingTime() {
    return 1000*(-log(random(1))/rate);
  }

  float get() {
    while(nextTime < millis()) {
      set(x+1);
      nextTime += waitingTime();
    }
    return x;
  }

  void setRate(float r) {
    // trigger any pending events
    get();

    rate = r;
    nextTime = millis() + waitingTime();
  }
}