Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Created May 13, 2023 17:39
Show Gist options
  • Select an option

  • Save unitycoder/b3338858245ed38f4dc2485db1fa3238 to your computer and use it in GitHub Desktop.

Select an option

Save unitycoder/b3338858245ed38f4dc2485db1fa3238 to your computer and use it in GitHub Desktop.

Revisions

  1. unitycoder created this gist May 13, 2023.
    237 changes: 237 additions & 0 deletions ReliableTime.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,237 @@
    // https://forum.unity.com/threads/building-an-accurate-clock-that-will-stay-accurate-over-time.1436062/
    using System;
    using UnityEngine;
    using TimeUtil = UnityEngine.Time;
    using Debug = UnityEngine.Debug;

    public class ReliableTime : MonoBehaviour {

      [SerializeField] [Range(0f, 100f)] float _timeScale = 1f;

      private const int SIXTY = 60;
      private const double TOLERANCE = 1E-2;

      static ReliableTime _instance;
      static public ReliableTime Instance => _instance;

      float _lastTime;  // in seconds
      float _measTime;  // in seconds
      int _minRegister; // in minutes

      public float Time => SIXTY * _minRegister + _measTime;
      public double TimeAsDouble => SIXTY * (double)_minRegister + (double)_measTime;

      public (int h, int m, float s) GetTime()
        => ( (int)(_minRegister / (float)SIXTY),
             _minRegister % SIXTY,
             _measTime );

      void Awake() {
        _instance = this;
        setValues(TimeUtil.timeAsDouble);
        _lastTime = _measTime;
      }

      void setValues(double absTime) {
        _measTime = (float)(absTime % SIXTY);
        _minRegister = (int)(absTime / SIXTY);
      }

      void Update() {
        TimeUtil.timeScale = _timeScale;

        _measTime += TimeUtil.deltaTime;

        if(abs(_measTime - _lastTime) >= 1f) {
          _lastTime = floor(_measTime);

          if(_timeScale <= 1f) {
            var time = GetTime();
            Debug.Log($"{time.h}:{time.m}:{time.s:F3}");
          }

          var measured = TimeAsDouble;
          var lapsed = TimeUtil.timeAsDouble;

          if(Math.Abs(measured - lapsed) > TOLERANCE) {
            var next = (measured + lapsed) / 2f;
            Debug.Log($"correction: was {measured:F4} now {lapsed:F4}");
            setValues(lapsed);
          }

          if(_measTime >= SIXTY) {
            _measTime -= SIXTY;
            _minRegister++;
          }
        }
      }

      //----------------------------------------

      static readonly float D90 = .5f * MathF.PI;
      static readonly float TAU = 4f * D90;

      void OnDrawGizmos() {
        var t = Time;

        var pos = transform.position;

        for(int i = 0; i < 12 * 5; i++) {
          Color c;
          float t1;
          if(i % 5 == 0) {
            c = (i % 3) == 0? Color.white : Color.cyan;
            t1 = (i % 3) == 0? .7f : .9f;
          } else {
            c = Color.cyan;
            t1 = .96f;
          }
          drawDial(pos, 1f, t1, 1f, TAU / (12f * 5f) * i, c);
        }

        var sec = (int)t % 60f;
        var min = (int)t / 60f % 60f;
        var hrs = (int)t / 3600f % 12f;

        draw7SegmentValue(new Vector3(.12f, .35f, 0f), .02f, .07f, .01f, (int)hrs, Color.yellow);
        draw7SegmentValue(new Vector3(.34f, .35f, 0f), .02f, .07f, .01f, (int)min, Color.yellow);
        draw7SegmentValue(new Vector3(.56f, .35f, 0f), .02f, .07f, .01f, (int)sec, Color.yellow);

        drawDial(pos, 1f, 0f, .55f, D90 - TAU / 12f * hrs, Color.white);
        drawDial(pos, 1f, 0f, .8f, D90 - TAU / 60f * min, Color.blue);
        drawDial(pos, 1f, 0f, .9f, D90 - TAU / 60f * easedSecondsDial(t), Color.red);

        var msc = frac(t) * 1000f;
        var microDial = new Vector3(-1f, 1f, 0f) / 3f;
        drawDial(pos + microDial, 1f, 0f, .22f, D90 - TAU / 1000f * msc, Color.white);
        drawCircle(pos + microDial, .22f, segments: 24);

        drawPlrSine(pos + .1f * Vector3.up, new Vector3(2f, -.8f, 1.2f), pos + new Vector3(-.6f, -.333f, 0f), new Vector2(.6f, .1f), 6f * t, 6f * t + 12f * TAU, segments: 96, color: Color.yellow);
        drawPlrSine(pos, new Vector3(MathF.PI * 10f / 6f, .2f, 0f), pos + new Vector3(-.6f, -.333f, 0f), new Vector2(.6f, .1f), -6f * t, -6f * t + 6f * TAU, segments: 24, color: Color.gray);

        drawCircle(pos, 1f, color: Color.cyan);
      }

      void drawPlrSine(Vector3 o, Vector3 s, Vector3 c, Vector2 scale, float min, float max, int segments = 48, Color? color = null) {
        if(color.HasValue) Gizmos.color = color.Value;

        var last = Vector3.zero;
        var step = (max - min) / segments;

        for(int i = 0; i <= segments; i++) {
          var next = mad(1f, new Vector3(2f * scale.x / segments * i, scale.y * sin(i * step + min), 0f), c);
          if(i > 0) drawSeg(polar(ref o, ref s, last), polar(ref o, ref s, next));
          last = next;
        }

        static Vector3 polar(ref Vector3 c, ref Vector3 scale, Vector3 p) {
          p -= c; return mad(scale.y * (p.y + scale.z), trig(scale.x * p.x + D90), c);
        }
      }

      // void drawSine(Vector3 c, Vector2 scale, float min, float max, int segments = 48, Color? color = null) {
      //   if(color.HasValue) Gizmos.color = color.Value;

      //   var last = Vector3.zero;
      //   var step = (max - min) / segments;

      //   for(int i = 0; i <= segments; i++) {
      //     var next = mad(1f, new Vector3(2f * scale.x / segments * i, scale.y * sin(i * step + min), 0f), c);
      //     if(i > 0) drawSeg(last, next);
      //     last = next;
      //   }
      // }

      void drawDial(Vector3 c, float r, float t1, float t2, float rad, Color? color = null) {
        if(color.HasValue) Gizmos.color = color.Value;
        var q = mad(r, trig(rad), c);
        drawSeg(lerp(c, q, t1), lerp(c, q, t2));
      }

      void drawCircle(Vector3 c, float r, int segments = 48, Color? color = null) {
        if(color.HasValue) Gizmos.color = color.Value;

        var last = Vector2.zero;
        var step = 2f * MathF.PI / segments;

        for(int i = 0; i <= segments; i++) {
          var next = mad(r, trig(i * step), c);
          if(i > 0) drawSeg(last, next);
          last = next;
        }
      }

      void drawSeg(Vector3 a, Vector3 b, Color? color = null) {
        if(color.HasValue) Gizmos.color = color.Value;
        Gizmos.DrawLine(a, b);
      }

      static float easedSecondsDial(float n) {
        n = mod(n + .7f - 1f, 60f);
        var f = frac(n);
        return floor(n) + (f < .5f? 0f : easeInOutBack(2f * (f - .5f)));
      }

      static float easeInOutBack(float n) {
        const float c1 = 1.70158f;
        const float c2 = c1 * 1.525f;

        return n < .5f? (sqr(2f * n) * ((c2 + 1f) * 2f * n - c2)) * .5f
                      : (sqr(2f * n - 2f) * ((c2 + 1f) * (n * 2f - 2f) + c2) + 2f) * .5f;
      }

      void draw7SegmentValue(Vector3 p, float space, float scale, float slant, int value, Color color, int digits = 2) {
        for(int i = digits - 1; i >= 0; i--) {
          int pw = pow(10, i);
          int digit = value / pw;
          draw7SegmentDigit(p, scale, digit, slant, color);
          value -= digit * pw;
          p.x += scale + space;
        }
      }

      //     0
      //    ___
      // 5 |   | 1
      //   +---+
      // 4 | 6 | 2
      //   '---'
      //     3

      static Vector3[] _ppts;
      static int[] _pieces = new int[] { 0x3f, 0x6, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x7, 0x7f, 0x6f };

      void draw7SegmentDigit(Vector3 p, float scale, int digit, float slant, Color? color = null) {
        if(_ppts is null) {
          _ppts = new Vector3[6];
          for(int y = 0; y < 3; y++)
            for(int x = 0; x < 2; x++)
              _ppts[2*y+x] = new Vector3(x, -y, 0f);
        }

        drawSeg(pt(0), pt(1), test(0));
        drawSeg(pt(1), pt(3), test(1));
        drawSeg(pt(3), pt(5), test(2));
        drawSeg(pt(4), pt(5), test(3));
        drawSeg(pt(2), pt(4), test(4));
        drawSeg(pt(0), pt(2), test(5));
        drawSeg(pt(2), pt(3), test(6));

        Vector3 pt(int index) => p + scale * _ppts[index] + new Vector3(slant * _ppts[index].y, 0f);
        Color test(int bit) => (color.HasValue && (_pieces[digit] & (1 << bit)) != 0)? color.Value
                                                                                     : default(Color);
      }

      static float abs(float n) => Math.Abs(n);
      static float floor(float n) => MathF.Floor(n);
      static float frac(float n) => n % 1f;
      static float sin(float rad) => MathF.Sin(rad);
      static float cos(float rad) => MathF.Cos(rad);
      static Vector3 mad(float a, Vector3 b, Vector3 c) => a * b + c;
      static Vector3 lerp(Vector3 a, Vector3 b, float t) => (1f - t) * a + t * b;
      static Vector3 trig(float rad) => new Vector3(cos(rad), sin(rad), 0f);
      static float sqr(float n) => n * n;
      static float mod(float n, float m) => (n %= m) < 0f? m + n : n;
      static int pow(float b, float p) => (int)MathF.Pow(b, p);

    }