For a few weeks I have been having trouble finding a clean way to measure the accumulated angle of a circular drag while snapping the result to seconds on a clock.
This is as close as I have come but it is still not working smoothly. The problem I’m having in this sketch is that the seconds snap to their angle with round()
, and I wanted the accumulated minutes to snap with floor()
.
After the second gets to 59.5, the marker snaps to 0, but the accumulated minute doesn’t turn to 1 until the drag goes past 0. So the accumulated drag time shows 00:00 from 00:59.5 to 01:00.
I fully understand why this isn’t working because I have spent a couple of weeks picking apart the problem, but I am hoping someone could suggest another way.
I have been able to make it work 100% as-expected with values that are not snapped to the nearest second, i.e. raw radians. But I can’t see how to do it with all of the values snapped to the nearest TWO_PI/60
EDIT: I added accSec to the variable declarations in the code.
float r, offset; // clock radius
PVector c; // clock center
PFont mono, sans;
int textH = 14;
color cRed = color(200, 35, 40);
color cRedT = color(200, 35, 40, 127);
color cYellow = color(240, 210, 50);
color cWhite = color(200, 220, 200);
color cGreen = color(50, 200, 65);
// mouse
float heading, cross;
PVector m, pm, mo, po;
boolean mouseDrag, cwDrag, ccwDrag, measuring, measurementBegan;
float snapA, dragA, dragASnap, accDragA;
float startA, startASnap, endASnap, accASnap;
int startSec, endSec, accSec, accMin;
void setup() {
size(400, 600);
//pixelDensity(2);
ellipseMode(RADIUS);
mono = createFont("Menlo", 14);
sans = createFont("Helvetica", 14);
r = width/2 - 40;
c = new PVector(width/2, height-r-40);
offset = -PI/2;
m = new PVector();
pm = new PVector();
snapA = PI*2/60;
}
void draw() {
background(127);
checkMouse();
dragA = heading;
dragA %= TWO_PI;
dragASnap = snapRound(dragA);
dragASnap %= TWO_PI;
if (mousePressed && !measurementBegan) {
measurementBegan = true;
startA = dragA;
startASnap = dragASnap;
endASnap = dragASnap;
accDragA = 0;
}
startSec = round(startASnap/snapA);
endSec = round(endASnap/snapA);
if (measuring) {
endASnap = dragASnap;
float dragAngDiff = PVector.angleBetween(mo, po);
if (ccwDrag) dragAngDiff = -dragAngDiff;
accDragA += dragAngDiff;
}
accASnap = snapRound(accDragA-(accDragA-dragA));
accSec = (endSec-startSec>0) ? endSec-startSec : endSec-startSec+60;
accSec %= 60;
accMin = floor(accDragA/TWO_PI);
showNeedle(dragA, cWhite, 3);
showNeedle(startA, cGreen, 1);
showMarker(startASnap, 10, cGreen);
showNeedle(endASnap, cRed, 1);
showMarker(endASnap, 10, cRed);
showMarker(accASnap, 7, cYellow);
showFace();
showDragState();
showTextRight();
}
void mouseDragged() {
measuring = true;
}
void showTextRight() {
String dragAStr = "dragA: " + nf(dragA, 2, 3);
String dragASnapStr = "dragASnap: " + nf(dragASnap, 2, 3);
String accDragAStr = "accDragA: " + nf(accDragA, 2, 3);
String startSecStr = "startSec: " + nf(startSec, 2);
String endSecStr = "endSec: " + nf(endSec, 2);
String accSecStr = "accSec: " + nf(accSec, 2);
String accMinStr = "accMin: " + nf(accMin, 2);
String[] strings = { dragAStr, dragASnapStr, startSecStr, endSecStr, accDragAStr, accSecStr, accMinStr };
textFont(mono);
textSize(textH);
textAlign(RIGHT, TOP);
fill(0);
for (int i=0; i<strings.length; i++) {
text(strings[i], width-5, 5+i*textH);
}
}
float snapRound(float a) {
a /= snapA;
a = round(a);
a *= snapA;
return a;
}
void checkMouse() {
m.set(mouseX, mouseY);
mo = PVector.sub(m, c);
mo.rotate(-offset);
pm.set(pmouseX, pmouseY);
po = PVector.sub(pm, c);
po.rotate(-offset);
mouseDrag = PVector.angleBetween(mo, po) > 0;
cross = mo.cross(po).z;
cwDrag = cross < 0;
ccwDrag = cross > 0;
heading = (mo.heading() > 0) ? mo.heading() : mo.heading() + TWO_PI;
if (!mousePressed) {
measuring = false;
measurementBegan = false;
}
}
void showDragState() {
if (measuring && !mouseDrag) {
fill(cYellow);
} else if (measuring && cwDrag) {
fill(50, 200, 65);
} else if (measuring && ccwDrag) {
fill(cRed);
} else {
fill(50);
}
stroke(0);
strokeWeight(1);
circleCR(c, 3);
}
void showDragA() {
strokeWeight(3);
stroke(cWhite);
lineStartRA(c, width/2 - 5, dragA + offset);
}
void showMarker(float angle, int sz, color clr) {
strokeWeight(1);
stroke(clr);
color fillC = clr;
PVector tickLoc = new PVector();
angle += offset;
float x = cos(angle);
float y = sin(angle);
tickLoc.set(x, y);
tickLoc.mult(r);
tickLoc.add(c);
drawTick(tickLoc, sz, fillC);
}
void showNeedle(float angle, color clr, int sz) {
strokeWeight(sz);
stroke(clr);
angle += offset;
lineStartRA(c, width/2 - 10, angle);
}
void showFace() {
PVector tickLoc = new PVector();
PVector textLoc = new PVector();
float faceR = r;
color fillC = 75;
float scaleActive = 9;
float scaleStart = 5;
float scaleNormal = 3;
float scale = 1;
strokeWeight(1);
noStroke();
fill(127, 200);
circleCR(c, faceR);
for (int i=0; i<360; i+=6) {
float angle = radians(i);
angle += offset;
float x = cos(angle);
float y = sin(angle);
tickLoc.set(x, y);
tickLoc.mult(faceR);
tickLoc.add(c);
textLoc.set(x, y);
textLoc.mult(faceR+20);
textLoc.add(c);
boolean active = snapRound(angle) == snapRound(dragASnap+offset);
scale = scaleNormal;
fillC = color(175);
if (i%30==0) {
int hVal = (i/30+11)%12+1;
String hTextStr = nf(hVal, 1);
fillC = color(50);
stroke(0);
drawTick(tickLoc, scale, fillC);
hourText(hTextStr, textLoc);
} else {
stroke(0);
drawTick(tickLoc, scale, fillC);
}
}
}
void drawTick(PVector loc, float size, color clr) {
stroke(0);
fill(clr);
circleCR(loc, size);
}
void hourText(String s, PVector loc) {
textFont(sans, 18);
textAlign(CENTER, CENTER);
fill(50);
text(s, loc.x, loc.y);
}
void lineStartRA(PVector start, float r, float a) {
PVector end = new PVector(cos(a), sin(a));
end.mult(r);
end.add(start);
linePts(start, end);
}
void linePts(PVector start, PVector end) {
line(start.x, start.y, end.x, end.y);
}
void circleCR(PVector loc, float radius) {
ellipse(loc.x, loc.y, radius, radius);
}