/* 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();
}
}