Nessuna descrizione
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TimeUtility.cs 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using System;
  2. using System.Globalization;
  3. using System.Text.RegularExpressions;
  4. #if TIMELINE_FRAMEACCURATE
  5. using UnityEngine.Playables;
  6. #endif
  7. namespace UnityEngine.Timeline
  8. {
  9. /// <summary>
  10. /// The standard frame rates supported when locking Timeline playback to frames.
  11. /// The frame rate is expressed in frames per second (fps).
  12. /// </summary>
  13. public enum StandardFrameRates
  14. {
  15. /// <summary>
  16. /// Represents a frame rate of 24 fps. This is the common frame rate for film.
  17. /// </summary>
  18. Fps24,
  19. /// <summary>
  20. /// Represents a drop frame rate of 23.97 fps. This is the common frame rate for NTSC film broadcast.
  21. /// </summary>
  22. Fps23_97,
  23. /// <summary>
  24. /// Represents a frame rate of 25 fps. This is commonly used for non-interlaced PAL television broadcast.
  25. /// </summary>
  26. Fps25,
  27. /// <summary>
  28. /// Represents a frame rate of 30 fps. This is commonly used for HD footage.
  29. /// </summary>
  30. Fps30,
  31. /// <summary>
  32. /// Represents a drop frame rate of 29.97 fps. This is commonly used for NTSC television broadcast.
  33. /// </summary>
  34. Fps29_97,
  35. /// <summary>
  36. /// Represents a frame rate of 50 fps. This is commonly used for interlaced PAL television broadcast.
  37. /// </summary>
  38. Fps50,
  39. /// <summary>
  40. /// Represents a frame rate of 60 fps. This is commonly used for games.
  41. /// </summary>
  42. Fps60,
  43. /// <summary>
  44. /// Represents a drop frame rate of 59.94 fps. This is commonly used for interlaced NTSC television broadcast.
  45. /// </summary>
  46. Fps59_94
  47. }
  48. // Sequence specific utilities for time manipulation
  49. static class TimeUtility
  50. {
  51. // chosen because it will cause no rounding errors between time/frames for frames values up to at least 10 million
  52. public static readonly double kTimeEpsilon = 1e-14;
  53. public static readonly double kFrameRateEpsilon = 1e-6;
  54. public static readonly double k_MaxTimelineDurationInSeconds = 9e6; //104 days of running time
  55. public static readonly double kFrameRateRounding = 1e-2;
  56. static void ValidateFrameRate(double frameRate)
  57. {
  58. if (frameRate <= kTimeEpsilon)
  59. throw new ArgumentException("frame rate cannot be 0 or negative");
  60. }
  61. public static int ToFrames(double time, double frameRate)
  62. {
  63. ValidateFrameRate(frameRate);
  64. time = Math.Min(Math.Max(time, -k_MaxTimelineDurationInSeconds), k_MaxTimelineDurationInSeconds);
  65. // this matches OnFrameBoundary
  66. double tolerance = GetEpsilon(time, frameRate);
  67. if (time < 0)
  68. {
  69. return (int)Math.Ceiling(time * frameRate - tolerance);
  70. }
  71. return (int)Math.Floor(time * frameRate + tolerance);
  72. }
  73. public static double ToExactFrames(double time, double frameRate)
  74. {
  75. ValidateFrameRate(frameRate);
  76. return time * frameRate;
  77. }
  78. public static double FromFrames(int frames, double frameRate)
  79. {
  80. ValidateFrameRate(frameRate);
  81. return (frames / frameRate);
  82. }
  83. public static double FromFrames(double frames, double frameRate)
  84. {
  85. ValidateFrameRate(frameRate);
  86. return frames / frameRate;
  87. }
  88. public static bool OnFrameBoundary(double time, double frameRate)
  89. {
  90. return OnFrameBoundary(time, frameRate, GetEpsilon(time, frameRate));
  91. }
  92. public static double GetEpsilon(double time, double frameRate)
  93. {
  94. return Math.Max(Math.Abs(time), 1) * frameRate * kTimeEpsilon;
  95. }
  96. public static bool OnFrameBoundary(double time, double frameRate, double epsilon)
  97. {
  98. ValidateFrameRate(frameRate);
  99. double exact = ToExactFrames(time, frameRate);
  100. double rounded = Math.Round(exact);
  101. return Math.Abs(exact - rounded) < epsilon;
  102. }
  103. public static double RoundToFrame(double time, double frameRate)
  104. {
  105. ValidateFrameRate(frameRate);
  106. var frameBefore = (int)Math.Floor(time * frameRate) / frameRate;
  107. var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate;
  108. return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter;
  109. }
  110. public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2")
  111. {
  112. if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders
  113. return ToFrames(timeValue, frameRate).ToString();
  114. return ToExactFrames(timeValue, frameRate).ToString(format);
  115. }
  116. public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2")
  117. {
  118. ValidateFrameRate(frameRate);
  119. int intTime = (int)Math.Abs(timeValue);
  120. int hours = intTime / 3600;
  121. int minutes = (intTime % 3600) / 60;
  122. int seconds = intTime % 60;
  123. string result;
  124. string sign = timeValue < 0 ? "-" : string.Empty;
  125. if (hours > 0)
  126. result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2");
  127. else if (minutes > 0)
  128. result = minutes + ":" + seconds.ToString("D2");
  129. else
  130. result = seconds.ToString();
  131. int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1);
  132. // Add partial digits on the frame if needed.
  133. // we are testing the original value (not the truncated), because the truncation can cause rounding errors leading
  134. // to invalid strings for items on frame boundaries
  135. string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0');
  136. if (!OnFrameBoundary(timeValue, frameRate))
  137. {
  138. string decimals = ToExactFrames(timeValue, frameRate).ToString(format);
  139. int decPlace = decimals.IndexOf('.');
  140. if (decPlace >= 0)
  141. frames += " [" + decimals.Substring(decPlace) + "]";
  142. }
  143. return sign + result + ":" + frames;
  144. }
  145. // Given a time code string, return the time in seconds
  146. // 1.5 -> 1.5 seconds
  147. // 1:1.5 -> 1 minute, 1.5 seconds
  148. // 1:1[.5] -> 1 second, 1.5 frames
  149. // 2:3:4 -> 2 minutes, 3 seconds, 4 frames
  150. // 1[.6] -> 1.6 frames
  151. public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue)
  152. {
  153. timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
  154. string[] sections = timeCode.Split(':');
  155. if (sections.Length == 0 || sections.Length > 4)
  156. return defaultValue;
  157. int hours = 0;
  158. int minutes = 0;
  159. double seconds = 0;
  160. double frames = 0;
  161. try
  162. {
  163. // depending on the format of the last numbers
  164. // seconds format
  165. string lastSection = sections[sections.Length - 1];
  166. if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
  167. {
  168. seconds = double.Parse(lastSection);
  169. if (sections.Length > 3) return defaultValue;
  170. if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
  171. if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
  172. }
  173. // frame formats
  174. else
  175. {
  176. if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success)
  177. {
  178. string stripped = RemoveChar(lastSection, c => c == '[' || c == ']');
  179. frames = double.Parse(stripped);
  180. }
  181. else if (Regex.Match(lastSection, @"^\d*$").Success)
  182. {
  183. frames = int.Parse(lastSection);
  184. }
  185. else
  186. {
  187. return defaultValue;
  188. }
  189. if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]);
  190. if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]);
  191. if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]);
  192. }
  193. }
  194. catch (FormatException)
  195. {
  196. return defaultValue;
  197. }
  198. return frames / frameRate + seconds + minutes * 60 + hours * 3600;
  199. }
  200. public static double ParseTimeSeconds(string timeCode, double frameRate, double defaultValue)
  201. {
  202. timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
  203. string[] sections = timeCode.Split(':');
  204. if (sections.Length == 0 || sections.Length > 4)
  205. return defaultValue;
  206. int hours = 0;
  207. int minutes = 0;
  208. double seconds = 0;
  209. try
  210. {
  211. // depending on the format of the last numbers
  212. // seconds format
  213. string lastSection = sections[sections.Length - 1];
  214. {
  215. if (!double.TryParse(lastSection, NumberStyles.Integer, CultureInfo.InvariantCulture, out seconds))
  216. if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
  217. seconds = double.Parse(lastSection);
  218. else
  219. return defaultValue;
  220. if (!double.TryParse(lastSection, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds))
  221. return defaultValue;
  222. if (sections.Length > 3) return defaultValue;
  223. if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
  224. if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
  225. }
  226. }
  227. catch (FormatException)
  228. {
  229. return defaultValue;
  230. }
  231. return seconds + minutes * 60 + hours * 3600;
  232. }
  233. // fixes rounding errors from using single precision for length
  234. public static double GetAnimationClipLength(AnimationClip clip)
  235. {
  236. if (clip == null || clip.empty)
  237. return 0;
  238. double length = clip.length;
  239. if (clip.frameRate > 0)
  240. {
  241. double frames = Mathf.Round(clip.length * clip.frameRate);
  242. length = frames / clip.frameRate;
  243. }
  244. return length;
  245. }
  246. static string RemoveChar(string str, Func<char, bool> charToRemoveFunc)
  247. {
  248. var len = str.Length;
  249. var src = str.ToCharArray();
  250. var dstIdx = 0;
  251. for (var i = 0; i < len; i++)
  252. {
  253. if (!charToRemoveFunc(src[i]))
  254. src[dstIdx++] = src[i];
  255. }
  256. return new string(src, 0, dstIdx);
  257. }
  258. public static FrameRate GetClosestFrameRate(double frameRate)
  259. {
  260. ValidateFrameRate(frameRate);
  261. var actualFrameRate = FrameRate.DoubleToFrameRate(frameRate);
  262. return Math.Abs(frameRate - actualFrameRate.rate) < kFrameRateRounding ? actualFrameRate : new FrameRate();
  263. }
  264. public static FrameRate ToFrameRate(StandardFrameRates enumValue)
  265. {
  266. switch (enumValue)
  267. {
  268. case StandardFrameRates.Fps23_97:
  269. return FrameRate.k_23_976Fps;
  270. case StandardFrameRates.Fps24:
  271. return FrameRate.k_24Fps;
  272. case StandardFrameRates.Fps25:
  273. return FrameRate.k_25Fps;
  274. case StandardFrameRates.Fps29_97:
  275. return FrameRate.k_29_97Fps;
  276. case StandardFrameRates.Fps30:
  277. return FrameRate.k_30Fps;
  278. case StandardFrameRates.Fps50:
  279. return FrameRate.k_50Fps;
  280. case StandardFrameRates.Fps59_94:
  281. return FrameRate.k_59_94Fps;
  282. case StandardFrameRates.Fps60:
  283. return FrameRate.k_60Fps;
  284. default:
  285. return new FrameRate();
  286. }
  287. ;
  288. }
  289. internal static bool ToStandardFrameRate(FrameRate rate, out StandardFrameRates standard)
  290. {
  291. if (rate == FrameRate.k_23_976Fps)
  292. standard = StandardFrameRates.Fps23_97;
  293. else if (rate == FrameRate.k_24Fps)
  294. standard = StandardFrameRates.Fps24;
  295. else if (rate == FrameRate.k_25Fps)
  296. standard = StandardFrameRates.Fps25;
  297. else if (rate == FrameRate.k_30Fps)
  298. standard = StandardFrameRates.Fps30;
  299. else if (rate == FrameRate.k_29_97Fps)
  300. standard = StandardFrameRates.Fps29_97;
  301. else if (rate == FrameRate.k_50Fps)
  302. standard = StandardFrameRates.Fps50;
  303. else if (rate == FrameRate.k_59_94Fps)
  304. standard = StandardFrameRates.Fps59_94;
  305. else if (rate == FrameRate.k_60Fps)
  306. standard = StandardFrameRates.Fps60;
  307. else
  308. {
  309. standard = (StandardFrameRates)Enum.GetValues(typeof(StandardFrameRates)).Length;
  310. return false;
  311. }
  312. return true;
  313. }
  314. }
  315. }