Need assistance: keeping a imported-graphics clock pegged to system time?

Over the weekend, as a first project, I made a clock program using imported SVG graphics—the position of the hands are mapped to h/m/s in the setup, using code based on that found in the example clock. However, because the example doesn't rotate the hands (it rescales them to be at the necessary angle), I had to use a transform–rotate–transform combination to get the hands to turn, which leads me to the current issue:

Because the movement of the hands is independent from the system clock once setup is done, it tends to drift—on the computer I'm currently on, it falls behind by ~1s/7m. (From this point forward, I'll only be referring to the second hand, because once that's working, the others should be solved similarly) I tried forcing it to stay synced by dividing the rotation by the framerate, ie

(TAU/60)/frameRate

or

(TWO_PI/60)/frameRate

but that still doesn't keep it synced, so I'm thinking that I either need to tell it constantly read from the system time to update (and animate*) it, or I need to tell it to rotate to an angle (0°, 6°, 12°, 18°, etc...) as opposed to by an angle (6° per second), based on the values of the system time, but I don't know how I'd go about scripting either of those actions. I imagine that it would be similar to how I set up the hands, but using the "s" variable gives one of 60 values, so I don't think it can be directly used to control rotation speed.

The current code is below** (nb: the rotation scripts have been nulled to avoid errors and distractions, and are given as blank because they're the problem. Also, the forum scripting is having a fit with the in-code URLs. Apologies for the weirdness):

int cx, cy;
int r = 364;
float clockDiameter;
float s = map(second(), 0, 60, 0, TWO_PI);
float m = map(minute() + norm(second(), 0, 60), 0, 60, 0, TWO_PI); 
float h = map(hour() + norm(minute(), 0, 60), 0, 24, 0, TWO_PI * 2);
PShape face,hrhand,mnhand,schand;

void setup() {
  size(1080, 1080);
  face = loadShape("goodmangooddesign.com/clock_test/watch_face-1_1.svg");
  hrhand = loadShape("goodmangooddesign.com/clock_test/watch_hrhand-1_1.svg");
  mnhand = loadShape("goodmangooddesign.com/clock_test/watch_mnhand-1_1.svg");
  schand = loadShape("goodmangooddesign.com/clock_test/watch_schand-1_1.svg");
  clockDiameter = r*2;
  cx = width / 2;
  cy = height / 2;
  hrhand.translate(r,r);
  hrhand.rotate(h);
  hrhand.translate(-r,-r);
  mnhand.translate(r,r);
  mnhand.rotate(m);
  mnhand.translate(-r,-r);
  schand.translate(r,r);
  schand.rotate(s);
  schand.translate(-r,-r);
}

void draw() {
  background(0);
  shape(face, cx-r, cy-r, clockDiameter, clockDiameter);
  shape(hrhand, cx-r, cy-r, clockDiameter, clockDiameter);
  hrhand.translate(r,r);
  //hrhand.rotate();
  hrhand.translate(-r,-r);
  shape(mnhand, cx-r, cy-r, clockDiameter, clockDiameter);
  mnhand.translate(r,r);
  //mnhand.rotate();
  mnhand.translate(-r,-r);
  shape(schand, cx-r, cy-r, clockDiameter, clockDiameter);
  schand.translate(r,r);
  //schand.rotate();
  schand.translate(-r,-r);
}

*I have a question about the animating, but I'll save that for once I get the calibration figured out. **I put the imported graphics online, so that anyone can run this.

Answers

  • edited January 2018

    To fix your formatting so that people can read, test, and help you, please edit your post, highlight the code and press Ctrl-o to indent. Make sure there is an empty line above and below.

    It sounds like ? you are accumulating rotations in a variable. Don't do that -- if you want long-term accuracy, this will never work, because of float precision. Whenever possible, avoid accumulating angular change -- throw any previous values out and compute from the origin each time.

    So, on each frame set the angle of each hand directly, using the current second() / minute() / hour() -- which is based on the system clock. Now your hands will be as accurate as your system clock.

    p.s. also, use modulo if needed -- in other words, calculating the radians or degrees on 1536PI is also an accumulated floating point error, just like a global variable with ++ or += ... don't go above 2PI.

  • Look at map() to change the value of 0..60 seconds to 0..360 degrees (it’s mapping a value like 29 from one range proportionally to another range)

    Then use radians()

  • @jeremydouglass: To fix your formatting so that people can read, test, and help you, please edit your post, highlight the code and press Ctrl-o to indent. Make sure there is an empty line above and below.

    My apologies, I was certain that I had formatted everything correctly when I created the post—the bits in the middle breaking up the code aren't even part of the actual script, so I'm unsure as to how they got there???

    I posted this both on r/Processing and here (I was going crazy trying to figure it out), and received a satisfactory answer from the former. I have updated the post with the solution given, as an aid to anyone searching for a similar answer in the future. I don't completely understand your suggestion, but from what I do, it sounds like you're offering a very similar solution to that provided.

  • It's a shame that you totally altered your previous post instead of posting a new post. So we can't see your original approach anymore.

    Also, my feeling is you received a code you don't fully understand. You were just given it. You would have learned by working systematically from what you got yourself.

    never mind.

  • edited January 2018

    @Chrisir—just for you, I fixed it :-* The OP has been reverted back to original (quasi-bugged posting), and the solution provided below, with the explanation comments included. And I do understand what's going on, once someone had explained it to me: first I have to create "float" decimal point values and angles for the current time, then I use an if parameter to check if the program values match the system values, and if not, the program "resets" the display to the current time. If everything does match up, then it renders the animations as normal. All I was "given" was the syntax for the reset for the second hand—everything else I worked out. The biggest thing that was new to me is how to write/where to place the functions at the bottom that allow it to get the angle for the hands based on the time (the mapping I already understood, because I had that at the top).

    I think everything in both this and the OP is as correct as it's going to get, but I'll edit/update any errors. I'm not 100% certain on the rotational math for the hours, but I'm pretty sure it's correct—divide Tau (one revolution) by 43,200 (60sc60mn12hrs), then multiply that by the framerate.

    I'm a very visual person, so coding is quite unnatural for me, but if you show me a bit of code and let me then see it in action, I can quickly figure out what's going on.

    int cx, cy;
    int r = 364;
    float clockDiameter;
    float s = map(second(), 0, 60, 0, TAU);
    float m = map(minute() + norm(second(), 0, 60), 0, 60, 0, TAU); 
    float h = map(hour() + norm(minute(), 0, 60), 0, 24, 0, TAU * 2);
    PShape face, hrhand, mnhand, schand;
    float currentSecond = second();
    float currentMinute = minute();
    float currentHour = hour();
    
    void setup() {
      size(1080, 1080);
      face = loadShape("http://www.goodmangooddesign.com/clock_test/watch_face-1_1.svg");
      hrhand = loadShape("http://www.goodmangooddesign.com/clock_test/watch_hrhand-1_1.svg");
      mnhand = loadShape("http://www.goodmangooddesign.com/clock_test/watch_mnhand-1_1.svg");
      schand = loadShape("http://www.goodmangooddesign.com/clock_test/watch_schand-1_1.svg");
      clockDiameter = r*2;
      cx = width / 2;
      cy = height / 2;
      hrhand.translate(r, r);
      hrhand.rotate(h);
      hrhand.translate(-r, -r);
      mnhand.translate(r, r);
      mnhand.rotate(m);
      mnhand.translate(-r, -r);
      schand.translate(r, r);
      schand.rotate(s);
      schand.translate(-r, -r);
    }
    
    void draw() {
      background(0);
      shape(face, cx-r, cy-r, clockDiameter, clockDiameter);
      shape(hrhand, cx-r, cy-r, clockDiameter, clockDiameter);
      if (currentHour != hour()) {
        currentHour = hour();
        hrhand.resetMatrix();
        hrhand.translate(r, r);
        hrhand.rotate(angleHour());
        hrhand.translate(-r, -r);
      }   
      hrhand.translate(r, r);
      hrhand.rotate((TAU/(43200*frameRate)));
      hrhand.translate(-r, -r);
      shape(mnhand, cx-r, cy-r, clockDiameter, clockDiameter);
      if (currentMinute != minute()) {
        currentMinute = minute();
        mnhand.resetMatrix();
        mnhand.translate(r, r);
        mnhand.rotate(angleMinute());
        mnhand.translate(-r, -r);
      }   
      mnhand.translate(r, r);
      mnhand.rotate((TAU/(3600*frameRate)));
      mnhand.translate(-r, -r);
      shape(schand, cx-r, cy-r, clockDiameter, clockDiameter);
      if (currentSecond != second()) {
        currentSecond = second(); // updating currentSecond to system time
        schand.resetMatrix(); // undoing the previous rotation & translations
        schand.translate(r, r); // apply translate, same as in setup()
        schand.rotate(angleSecond()); // call the function to apply appropriate angle
        schand.translate(-r, -r);
      } 
      schand.translate(r, r);
      schand.rotate((TAU/(60*frameRate)));
      schand.translate(-r, -r);
    }
    
    float angleSecond() {
      return map(second(), 0, 60, 0, TAU);
    }
    float angleMinute() {
      return map(minute() + norm(second(), 0, 60), 0, 60, 0, TAU);
    }
    float angleHour() {
      return map(hour() + norm(minute(), 0, 60), 0, 24, 0, TAU * 2);
    } 
    
  • Thanks a lot, Sir!

  • @chrisir—you're welcome! Apologies for the weird URL behavior in the code, but I felt it would be beneficial to everyone to be able to access the specific SVGs I am using, so I've hosted them online.

    This question can be considered answered.

Sign In or Register to comment.