Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Created May 13, 2023 17:39
Show Gist options
  • Save unitycoder/b3338858245ed38f4dc2485db1fa3238 to your computer and use it in GitHub Desktop.
Save unitycoder/b3338858245ed38f4dc2485db1fa3238 to your computer and use it in GitHub Desktop.
Timer
// 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);
 
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment