123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- using System;
- using System.Text.RegularExpressions;
- namespace UnityEngine.Timeline
- {
- // Sequence specific utilities for time manipulation
- static class TimeUtility
- {
- // chosen because it will cause no rounding errors between time/frames for frames values up to at least 10 million
- public static readonly double kTimeEpsilon = 1e-14;
- public static readonly double kFrameRateEpsilon = 1e-6;
- public static readonly double k_MaxTimelineDurationInSeconds = 9e6; //104 days of running time
- static void ValidateFrameRate(double frameRate)
- {
- if (frameRate <= kTimeEpsilon)
- throw new ArgumentException("frame rate cannot be 0 or negative");
- }
- public static int ToFrames(double time, double frameRate)
- {
- ValidateFrameRate(frameRate);
- time = Math.Min(Math.Max(time, -k_MaxTimelineDurationInSeconds), k_MaxTimelineDurationInSeconds);
- // this matches OnFrameBoundary
- double tolerance = GetEpsilon(time, frameRate) / 2.0;
- if (time < 0)
- {
- return (int)Math.Ceiling(time * frameRate - tolerance);
- }
- return (int)Math.Floor(time * frameRate + tolerance);
- }
- public static double ToExactFrames(double time, double frameRate)
- {
- ValidateFrameRate(frameRate);
- return time * frameRate;
- }
- public static double FromFrames(int frames, double frameRate)
- {
- ValidateFrameRate(frameRate);
- return (frames / frameRate);
- }
- public static double FromFrames(double frames, double frameRate)
- {
- ValidateFrameRate(frameRate);
- return frames / frameRate;
- }
- public static bool OnFrameBoundary(double time, double frameRate)
- {
- return OnFrameBoundary(time, frameRate, GetEpsilon(time, frameRate));
- }
- public static double GetEpsilon(double time, double frameRate)
- {
- return Math.Max(Math.Abs(time), 1) * frameRate * kTimeEpsilon;
- }
- public static bool OnFrameBoundary(double time, double frameRate, double epsilon)
- {
- ValidateFrameRate(frameRate);
- double exact = ToExactFrames(time, frameRate);
- double rounded = Math.Round(exact);
- return Math.Abs(exact - rounded) < epsilon;
- }
- public static double RoundToFrame(double time, double frameRate)
- {
- ValidateFrameRate(frameRate);
- var frameBefore = (int)Math.Floor(time * frameRate) / frameRate;
- var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate;
- return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter;
- }
- public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2")
- {
- if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders
- return ToFrames(timeValue, frameRate).ToString();
- return ToExactFrames(timeValue, frameRate).ToString(format);
- }
- public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2")
- {
- ValidateFrameRate(frameRate);
- int intTime = (int)Math.Abs(timeValue);
- int hours = intTime / 3600;
- int minutes = (intTime % 3600) / 60;
- int seconds = intTime % 60;
- string result;
- string sign = timeValue < 0 ? "-" : string.Empty;
- if (hours > 0)
- result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2");
- else if (minutes > 0)
- result = minutes + ":" + seconds.ToString("D2");
- else
- result = seconds.ToString();
- int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1);
- // Add partial digits on the frame if needed.
- // we are testing the original value (not the truncated), because the truncation can cause rounding errors leading
- // to invalid strings for items on frame boundaries
- string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0');
- if (!OnFrameBoundary(timeValue, frameRate))
- {
- string decimals = ToExactFrames(timeValue, frameRate).ToString(format);
- int decPlace = decimals.IndexOf('.');
- if (decPlace >= 0)
- frames += " [" + decimals.Substring(decPlace) + "]";
- }
- return sign + result + ":" + frames;
- }
- // Given a time code string, return the time in seconds
- // 1.5 -> 1.5 seconds
- // 1:1.5 -> 1 minute, 1.5 seconds
- // 1:1[.5] -> 1 second, 1.5 frames
- // 2:3:4 -> 2 minutes, 3 seconds, 4 frames
- // 1[.6] -> 1.6 frames
- public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue)
- {
- timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
- string[] sections = timeCode.Split(':');
- if (sections.Length == 0 || sections.Length > 4)
- return defaultValue;
- int hours = 0;
- int minutes = 0;
- double seconds = 0;
- double frames = 0;
- try
- {
- // depending on the format of the last numbers
- // seconds format
- string lastSection = sections[sections.Length - 1];
- if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
- {
- seconds = double.Parse(lastSection);
- if (sections.Length > 3) return defaultValue;
- if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
- if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
- }
- // frame formats
- else
- {
- if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success)
- {
- string stripped = RemoveChar(lastSection, c => c == '[' || c == ']');
- frames = double.Parse(stripped);
- }
- else if (Regex.Match(lastSection, @"^\d*$").Success)
- {
- frames = int.Parse(lastSection);
- }
- else
- {
- return defaultValue;
- }
- if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]);
- if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]);
- if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]);
- }
- }
- catch (FormatException)
- {
- return defaultValue;
- }
- return frames / frameRate + seconds + minutes * 60 + hours * 3600;
- }
- // fixes rounding errors from using single precision for length
- public static double GetAnimationClipLength(AnimationClip clip)
- {
- if (clip == null || clip.empty)
- return 0;
- double length = clip.length;
- if (clip.frameRate > 0)
- {
- double frames = Mathf.Round(clip.length * clip.frameRate);
- length = frames / clip.frameRate;
- }
- return length;
- }
- static string RemoveChar(string str, Func<char, bool> charToRemoveFunc)
- {
- var len = str.Length;
- var src = str.ToCharArray();
- var dstIdx = 0;
- for (var i = 0; i < len; i++)
- {
- if (!charToRemoveFunc(src[i]))
- src[dstIdx++] = src[i];
- }
- return new string(src, 0, dstIdx);
- }
- }
- }
|