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