暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

TimeUtility.cs 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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 int PreviousFrame(double time, double frameRate)
  97. {
  98. return Math.Max(0, ToFrames(time, frameRate) - 1);
  99. }
  100. public static int NextFrame(double time, double frameRate)
  101. {
  102. return ToFrames(time, frameRate) + 1;
  103. }
  104. public static double PreviousFrameTime(double time, double frameRate)
  105. {
  106. return FromFrames(PreviousFrame(time, frameRate), frameRate);
  107. }
  108. public static double NextFrameTime(double time, double frameRate)
  109. {
  110. return FromFrames(NextFrame(time, frameRate), frameRate);
  111. }
  112. public static bool OnFrameBoundary(double time, double frameRate, double epsilon)
  113. {
  114. ValidateFrameRate(frameRate);
  115. double exact = ToExactFrames(time, frameRate);
  116. double rounded = Math.Round(exact);
  117. return Math.Abs(exact - rounded) < epsilon;
  118. }
  119. public static double RoundToFrame(double time, double frameRate)
  120. {
  121. ValidateFrameRate(frameRate);
  122. var frameBefore = (int)Math.Floor(time * frameRate) / frameRate;
  123. var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate;
  124. return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter;
  125. }
  126. public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2")
  127. {
  128. if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders
  129. return ToFrames(timeValue, frameRate).ToString();
  130. return ToExactFrames(timeValue, frameRate).ToString(format);
  131. }
  132. public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2")
  133. {
  134. ValidateFrameRate(frameRate);
  135. int intTime = (int)Math.Abs(timeValue);
  136. int hours = intTime / 3600;
  137. int minutes = (intTime % 3600) / 60;
  138. int seconds = intTime % 60;
  139. string result;
  140. string sign = timeValue < 0 ? "-" : string.Empty;
  141. if (hours > 0)
  142. result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2");
  143. else if (minutes > 0)
  144. result = minutes + ":" + seconds.ToString("D2");
  145. else
  146. result = seconds.ToString();
  147. int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1);
  148. // Add partial digits on the frame if needed.
  149. // we are testing the original value (not the truncated), because the truncation can cause rounding errors leading
  150. // to invalid strings for items on frame boundaries
  151. string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0');
  152. if (!OnFrameBoundary(timeValue, frameRate))
  153. {
  154. string decimals = ToExactFrames(timeValue, frameRate).ToString(format);
  155. int decPlace = decimals.IndexOf('.');
  156. if (decPlace >= 0)
  157. frames += " [" + decimals.Substring(decPlace) + "]";
  158. }
  159. return sign + result + ":" + frames;
  160. }
  161. // Given a time code string, return the time in seconds
  162. // 1.5 -> 1.5 seconds
  163. // 1:1.5 -> 1 minute, 1.5 seconds
  164. // 1:1[.5] -> 1 second, 1.5 frames
  165. // 2:3:4 -> 2 minutes, 3 seconds, 4 frames
  166. // 1[.6] -> 1.6 frames
  167. public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue)
  168. {
  169. timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
  170. string[] sections = timeCode.Split(':');
  171. if (sections.Length == 0 || sections.Length > 4)
  172. return defaultValue;
  173. int hours = 0;
  174. int minutes = 0;
  175. double seconds = 0;
  176. double frames = 0;
  177. try
  178. {
  179. // depending on the format of the last numbers
  180. // seconds format
  181. string lastSection = sections[sections.Length - 1];
  182. if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
  183. {
  184. seconds = double.Parse(lastSection);
  185. if (sections.Length > 3) return defaultValue;
  186. if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
  187. if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
  188. }
  189. // frame formats
  190. else
  191. {
  192. if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success)
  193. {
  194. string stripped = RemoveChar(lastSection, c => c == '[' || c == ']');
  195. frames = double.Parse(stripped);
  196. }
  197. else if (Regex.Match(lastSection, @"^\d*$").Success)
  198. {
  199. frames = int.Parse(lastSection);
  200. }
  201. else
  202. {
  203. return defaultValue;
  204. }
  205. if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]);
  206. if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]);
  207. if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]);
  208. }
  209. }
  210. catch (FormatException)
  211. {
  212. return defaultValue;
  213. }
  214. return frames / frameRate + seconds + minutes * 60 + hours * 3600;
  215. }
  216. public static double ParseTimeSeconds(string timeCode, double frameRate, double defaultValue)
  217. {
  218. timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
  219. string[] sections = timeCode.Split(':');
  220. if (sections.Length == 0 || sections.Length > 4)
  221. return defaultValue;
  222. int hours = 0;
  223. int minutes = 0;
  224. double seconds = 0;
  225. try
  226. {
  227. // depending on the format of the last numbers
  228. // seconds format
  229. string lastSection = sections[sections.Length - 1];
  230. {
  231. if (!double.TryParse(lastSection, NumberStyles.Integer, CultureInfo.InvariantCulture, out seconds))
  232. if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
  233. seconds = double.Parse(lastSection);
  234. else
  235. return defaultValue;
  236. if (!double.TryParse(lastSection, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds))
  237. return defaultValue;
  238. if (sections.Length > 3) return defaultValue;
  239. if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
  240. if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
  241. }
  242. }
  243. catch (FormatException)
  244. {
  245. return defaultValue;
  246. }
  247. return seconds + minutes * 60 + hours * 3600;
  248. }
  249. // fixes rounding errors from using single precision for length
  250. public static double GetAnimationClipLength(AnimationClip clip)
  251. {
  252. if (clip == null || clip.empty)
  253. return 0;
  254. double length = clip.length;
  255. if (clip.frameRate > 0)
  256. {
  257. double frames = Mathf.Round(clip.length * clip.frameRate);
  258. length = frames / clip.frameRate;
  259. }
  260. return length;
  261. }
  262. static string RemoveChar(string str, Func<char, bool> charToRemoveFunc)
  263. {
  264. var len = str.Length;
  265. var src = str.ToCharArray();
  266. var dstIdx = 0;
  267. for (var i = 0; i < len; i++)
  268. {
  269. if (!charToRemoveFunc(src[i]))
  270. src[dstIdx++] = src[i];
  271. }
  272. return new string(src, 0, dstIdx);
  273. }
  274. public static FrameRate GetClosestFrameRate(double frameRate)
  275. {
  276. ValidateFrameRate(frameRate);
  277. var actualFrameRate = FrameRate.DoubleToFrameRate(frameRate);
  278. return Math.Abs(frameRate - actualFrameRate.rate) < kFrameRateRounding ? actualFrameRate : new FrameRate();
  279. }
  280. public static FrameRate ToFrameRate(StandardFrameRates enumValue)
  281. {
  282. switch (enumValue)
  283. {
  284. case StandardFrameRates.Fps23_97:
  285. return FrameRate.k_23_976Fps;
  286. case StandardFrameRates.Fps24:
  287. return FrameRate.k_24Fps;
  288. case StandardFrameRates.Fps25:
  289. return FrameRate.k_25Fps;
  290. case StandardFrameRates.Fps29_97:
  291. return FrameRate.k_29_97Fps;
  292. case StandardFrameRates.Fps30:
  293. return FrameRate.k_30Fps;
  294. case StandardFrameRates.Fps50:
  295. return FrameRate.k_50Fps;
  296. case StandardFrameRates.Fps59_94:
  297. return FrameRate.k_59_94Fps;
  298. case StandardFrameRates.Fps60:
  299. return FrameRate.k_60Fps;
  300. default:
  301. return new FrameRate();
  302. }
  303. ;
  304. }
  305. internal static bool ToStandardFrameRate(FrameRate rate, out StandardFrameRates standard)
  306. {
  307. if (rate == FrameRate.k_23_976Fps)
  308. standard = StandardFrameRates.Fps23_97;
  309. else if (rate == FrameRate.k_24Fps)
  310. standard = StandardFrameRates.Fps24;
  311. else if (rate == FrameRate.k_25Fps)
  312. standard = StandardFrameRates.Fps25;
  313. else if (rate == FrameRate.k_30Fps)
  314. standard = StandardFrameRates.Fps30;
  315. else if (rate == FrameRate.k_29_97Fps)
  316. standard = StandardFrameRates.Fps29_97;
  317. else if (rate == FrameRate.k_50Fps)
  318. standard = StandardFrameRates.Fps50;
  319. else if (rate == FrameRate.k_59_94Fps)
  320. standard = StandardFrameRates.Fps59_94;
  321. else if (rate == FrameRate.k_60Fps)
  322. standard = StandardFrameRates.Fps60;
  323. else
  324. {
  325. standard = (StandardFrameRates)Enum.GetValues(typeof(StandardFrameRates)).Length;
  326. return false;
  327. }
  328. return true;
  329. }
  330. }
  331. }