A game about forced loneliness, made by TACStudios
at master 376 lines 15 kB view raw
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}