EditorAnimInfo.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using bs;
  7. namespace bs.Editor
  8. {
  9. /** @addtogroup AnimationEditor
  10. * @{
  11. */
  12. /// <summary>
  13. /// A set of animation curves for a field of a certain type.
  14. /// </summary>
  15. internal struct FieldAnimCurves
  16. {
  17. public SerializableProperty.FieldType type;
  18. public EdCurveDrawInfo[] curveInfos;
  19. public bool isPropertyCurve;
  20. }
  21. /// <summary>
  22. /// Stores tangent modes for an 3D vector animation curve (one mode for each keyframe).
  23. /// </summary>
  24. [SerializeObject]
  25. internal class EditorVector3CurveTangents
  26. {
  27. public string name;
  28. public TangentMode[] tangentsX;
  29. public TangentMode[] tangentsY;
  30. public TangentMode[] tangentsZ;
  31. }
  32. /// <summary>
  33. /// Stores tangent modes for an float animation curve (one mode for each keyframe).
  34. /// </summary>
  35. [SerializeObject]
  36. internal class EditorFloatCurveTangents
  37. {
  38. public string name;
  39. public TangentMode[] tangents;
  40. }
  41. /// <summary>
  42. /// Stores tangent information for all curves in an animation clip.
  43. /// </summary>
  44. [SerializeObject]
  45. internal class EditorAnimClipTangents
  46. {
  47. public EditorVector3CurveTangents[] positionCurves;
  48. public EditorVector3CurveTangents[] rotationCurves;
  49. public EditorVector3CurveTangents[] scaleCurves;
  50. public EditorFloatCurveTangents[] floatCurves;
  51. }
  52. /// <summary>
  53. /// Contains a version of <see cref="NamedVector3Curve"/> that can be serialized from the editor.
  54. /// </summary>
  55. [SerializeObject]
  56. internal struct EditorNamedVector3Curve
  57. {
  58. /// <summary>Name of the curve.</summary>
  59. public string name;
  60. /// <summary>Flags that describe the animation curve.</summary>
  61. public AnimationCurveFlags flags;
  62. /// <summary> Curve keyframes. </summary>
  63. public KeyFrameVec3[] keyFrames;
  64. }
  65. /// <summary>
  66. /// Contains editor-only data for a specific <see cref="AnimationClip"/>.
  67. /// </summary>
  68. [SerializeObject]
  69. internal class EditorAnimClipData
  70. {
  71. public EditorNamedVector3Curve[] eulerCurves;
  72. public EditorAnimClipTangents tangents;
  73. }
  74. /// <summary>
  75. /// Stores animation clip data for clips that are currently being edited.
  76. /// </summary>
  77. internal class EditorAnimClipInfo
  78. {
  79. public AnimationClip clip;
  80. public bool isImported;
  81. public int sampleRate;
  82. public Dictionary<string, FieldAnimCurves> curves = new Dictionary<string, FieldAnimCurves>();
  83. public AnimationEvent[] events = new AnimationEvent[0];
  84. /// <summary>
  85. /// Loads curve and event information from the provided clip, and creates a new instance of this object containing
  86. /// the required data for editing the source clip in the animation editor.
  87. /// </summary>
  88. /// <param name="clip">Clip to load.</param>
  89. /// <returns>Editor specific editable information about an animation clip.</returns>
  90. public static EditorAnimClipInfo Create(AnimationClip clip)
  91. {
  92. EditorAnimClipInfo clipInfo = new EditorAnimClipInfo();
  93. clipInfo.clip = clip;
  94. clipInfo.isImported = IsClipImported(clip);
  95. clipInfo.sampleRate = (int)clip.SampleRate;
  96. AnimationCurves clipCurves = clip.Curves;
  97. EditorAnimClipData editorClipData = null;
  98. EditorAnimClipData lGetAnimClipData(ResourceMeta meta)
  99. {
  100. object editorData = meta.EditorData;
  101. EditorAnimClipData output = editorData as EditorAnimClipData;
  102. if (output == null)
  103. {
  104. // Old editor data stores tangents only
  105. if (editorData is EditorAnimClipTangents tangents)
  106. {
  107. output = new EditorAnimClipData();
  108. output.tangents = tangents;
  109. }
  110. }
  111. return output;
  112. }
  113. string resourcePath = ProjectLibrary.GetPath(clip);
  114. if (!string.IsNullOrEmpty(resourcePath))
  115. {
  116. LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath);
  117. string clipName = PathEx.GetTail(resourcePath);
  118. if (entry != null && entry.Type == LibraryEntryType.File)
  119. {
  120. FileEntry fileEntry = (FileEntry)entry;
  121. ResourceMeta[] metas = fileEntry.ResourceMetas;
  122. if (clipInfo.isImported)
  123. {
  124. for (int i = 0; i < metas.Length; i++)
  125. {
  126. if (clipName == metas[i].SubresourceName)
  127. {
  128. editorClipData = lGetAnimClipData(metas[i]);
  129. break;
  130. }
  131. }
  132. }
  133. else
  134. {
  135. if (metas.Length > 0)
  136. editorClipData = lGetAnimClipData(metas[0]);
  137. }
  138. }
  139. }
  140. if (editorClipData == null)
  141. {
  142. editorClipData = new EditorAnimClipData();
  143. editorClipData.tangents = new EditorAnimClipTangents();
  144. }
  145. int globalCurveIdx = 0;
  146. void lLoadVector3Curve(NamedVector3Curve[] curves, EditorVector3CurveTangents[] tangents, string subPath)
  147. {
  148. foreach (var curveEntry in curves)
  149. {
  150. TangentMode[] tangentsX = null;
  151. TangentMode[] tangentsY = null;
  152. TangentMode[] tangentsZ = null;
  153. if (tangents != null)
  154. {
  155. foreach (var tangentEntry in tangents)
  156. {
  157. if (tangentEntry.name == curveEntry.name)
  158. {
  159. tangentsX = tangentEntry.tangentsX;
  160. tangentsY = tangentEntry.tangentsY;
  161. tangentsZ = tangentEntry.tangentsZ;
  162. break;
  163. }
  164. }
  165. }
  166. // Convert compound curve to three per-component curves
  167. AnimationCurve[] componentCurves = AnimationUtility.SplitCurve3D(curveEntry.curve);
  168. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  169. fieldCurves.type = SerializableProperty.FieldType.Vector3;
  170. fieldCurves.curveInfos = new EdCurveDrawInfo[3];
  171. fieldCurves.isPropertyCurve = !clipInfo.isImported;
  172. fieldCurves.curveInfos[0] = new EdCurveDrawInfo();
  173. fieldCurves.curveInfos[0].curve = new EdAnimationCurve(componentCurves[0], tangentsX);
  174. fieldCurves.curveInfos[0].color = GetUniqueColor(globalCurveIdx++);
  175. fieldCurves.curveInfos[1] = new EdCurveDrawInfo();
  176. fieldCurves.curveInfos[1].curve = new EdAnimationCurve(componentCurves[1], tangentsY);
  177. fieldCurves.curveInfos[1].color = GetUniqueColor(globalCurveIdx++);
  178. fieldCurves.curveInfos[2] = new EdCurveDrawInfo();
  179. fieldCurves.curveInfos[2].curve = new EdAnimationCurve(componentCurves[2], tangentsZ);
  180. fieldCurves.curveInfos[2].color = GetUniqueColor(globalCurveIdx++);
  181. string curvePath = curveEntry.name.TrimEnd('/') + subPath;
  182. clipInfo.curves[curvePath] = fieldCurves;
  183. }
  184. };
  185. NamedQuaternionCurve[] rotationCurves = clipCurves.Rotation;
  186. NamedVector3Curve[] eulerRotationCurves = new NamedVector3Curve[rotationCurves.Length];
  187. if (editorClipData.eulerCurves == null || editorClipData.eulerCurves.Length != rotationCurves.Length)
  188. {
  189. // Convert rotation from quaternion to euler if we don't have original euler animation data stored.
  190. for (int i = 0; i < rotationCurves.Length; i++)
  191. {
  192. NamedQuaternionCurve quatCurve = rotationCurves[i];
  193. Vector3Curve eulerCurve = AnimationUtility.QuaternionToEulerCurve(quatCurve.curve);
  194. eulerRotationCurves[i] = new NamedVector3Curve(quatCurve.name, quatCurve.flags, eulerCurve);
  195. }
  196. }
  197. else
  198. {
  199. for (int i = 0; i < editorClipData.eulerCurves.Length; i++)
  200. {
  201. EditorNamedVector3Curve edCurve = editorClipData.eulerCurves[i];
  202. eulerRotationCurves[i] = new NamedVector3Curve(
  203. edCurve.name, edCurve.flags, new Vector3Curve(edCurve.keyFrames));
  204. }
  205. }
  206. lLoadVector3Curve(clipCurves.Position, editorClipData.tangents.positionCurves, "/Position");
  207. lLoadVector3Curve(eulerRotationCurves, editorClipData.tangents.rotationCurves, "/Rotation");
  208. lLoadVector3Curve(clipCurves.Scale, editorClipData.tangents.scaleCurves, "/Scale");
  209. // Find which individual float curves belong to the same field
  210. Dictionary<string, Tuple<int, int, bool>[]> floatCurveMapping = new Dictionary<string, Tuple<int, int, bool>[]>();
  211. {
  212. int curveIdx = 0;
  213. foreach (var curveEntry in clipCurves.Generic)
  214. {
  215. string path = curveEntry.name;
  216. string pathNoSuffix = null;
  217. string pathSuffix;
  218. if (path.Length >= 2)
  219. {
  220. pathSuffix = path.Substring(path.Length - 2, 2);
  221. pathNoSuffix = path.Substring(0, path.Length - 2);
  222. }
  223. else
  224. pathSuffix = "";
  225. int tangentIdx = -1;
  226. int currentTangentIdx = 0;
  227. foreach (var tangentEntry in editorClipData.tangents.floatCurves)
  228. {
  229. if (tangentEntry.name == curveEntry.name)
  230. {
  231. tangentIdx = currentTangentIdx;
  232. break;
  233. }
  234. currentTangentIdx++;
  235. }
  236. Animation.PropertySuffixInfo suffixInfo;
  237. if (Animation.PropertySuffixInfos.TryGetValue(pathSuffix, out suffixInfo))
  238. {
  239. Tuple<int, int, bool>[] curveInfo;
  240. if (!floatCurveMapping.TryGetValue(pathNoSuffix, out curveInfo))
  241. curveInfo = new Tuple<int, int, bool>[4];
  242. curveInfo[suffixInfo.elementIdx] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector);
  243. floatCurveMapping[pathNoSuffix] = curveInfo;
  244. }
  245. else
  246. {
  247. Tuple<int, int, bool>[] curveInfo = new Tuple<int, int, bool>[4];
  248. curveInfo[0] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector);
  249. floatCurveMapping[path] = curveInfo;
  250. }
  251. curveIdx++;
  252. }
  253. }
  254. foreach (var KVP in floatCurveMapping)
  255. {
  256. int numCurves = 0;
  257. for (int i = 0; i < 4; i++)
  258. {
  259. if (KVP.Value[i] == null)
  260. continue;
  261. numCurves++;
  262. }
  263. if (numCurves == 0)
  264. continue; // Invalid curve
  265. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  266. // Deduce type (note that all single value types are assumed to be float even if their source type is int or bool)
  267. if (numCurves == 1)
  268. fieldCurves.type = SerializableProperty.FieldType.Float;
  269. else if (numCurves == 2)
  270. fieldCurves.type = SerializableProperty.FieldType.Vector2;
  271. else if (numCurves == 3)
  272. fieldCurves.type = SerializableProperty.FieldType.Vector3;
  273. else // 4 curves
  274. {
  275. bool isVector = KVP.Value[0].Item3;
  276. if (isVector)
  277. fieldCurves.type = SerializableProperty.FieldType.Vector4;
  278. else
  279. fieldCurves.type = SerializableProperty.FieldType.Color;
  280. }
  281. bool isMorphCurve = false;
  282. string curvePath = KVP.Key;
  283. fieldCurves.curveInfos = new EdCurveDrawInfo[numCurves];
  284. for (int i = 0; i < numCurves; i++)
  285. {
  286. int curveIdx = KVP.Value[i].Item1;
  287. int tangentIdx = KVP.Value[i].Item2;
  288. TangentMode[] tangents = null;
  289. if (tangentIdx != -1)
  290. tangents = editorClipData.tangents.floatCurves[tangentIdx].tangents;
  291. fieldCurves.curveInfos[i] = new EdCurveDrawInfo();
  292. fieldCurves.curveInfos[i].curve = new EdAnimationCurve(clipCurves.Generic[curveIdx].curve, tangents);
  293. fieldCurves.curveInfos[i].color = GetUniqueColor(globalCurveIdx++);
  294. if (clipCurves.Generic[curveIdx].flags.HasFlag(AnimationCurveFlags.MorphFrame))
  295. {
  296. curvePath = "MorphShapes/Frames/" + KVP.Key;
  297. isMorphCurve = true;
  298. }
  299. else if (clipCurves.Generic[curveIdx].flags.HasFlag(AnimationCurveFlags.MorphWeight))
  300. {
  301. curvePath = "MorphShapes/Weight/" + KVP.Key;
  302. isMorphCurve = true;
  303. }
  304. }
  305. fieldCurves.isPropertyCurve = !clipInfo.isImported && !isMorphCurve;
  306. clipInfo.curves[curvePath] = fieldCurves;
  307. }
  308. // Add events
  309. clipInfo.events = clip.Events;
  310. return clipInfo;
  311. }
  312. /// <summary>
  313. /// Checks is the specified animation clip is imported from an external file, or created within the editor.
  314. /// </summary>
  315. /// <param name="clip">Clip to check.</param>
  316. /// <returns>True if the clip is imported from an external file (e.g. FBX file), or false if the clip is a native
  317. /// resource created within the editor.</returns>
  318. public static bool IsClipImported(AnimationClip clip)
  319. {
  320. string resourcePath = ProjectLibrary.GetPath(clip);
  321. return ProjectLibrary.IsSubresource(resourcePath);
  322. }
  323. /// <summary>
  324. /// Checks does a curve with the specified path represent a curve affecting a morph shape.
  325. /// </summary>
  326. /// <param name="path">Path of the curve to check.</param>
  327. /// <returns>True if morph shape frame or weight animation, false otherwise.</returns>
  328. public static bool IsMorphShapeCurve(string path)
  329. {
  330. if (string.IsNullOrEmpty(path))
  331. return false;
  332. string trimmedPath = path.Trim('/');
  333. string[] entries = trimmedPath.Split('/');
  334. if (entries.Length < 3)
  335. return false;
  336. if (entries[entries.Length - 3] != "MorphShapes")
  337. return false;
  338. return entries[entries.Length - 2] == "Weight" || entries[entries.Length - 2] == "Frames";
  339. }
  340. /// <summary>
  341. /// Applies any changes made to the animation curves or events to the actual animation clip. Only works for
  342. /// non-imported animation clips.
  343. /// </summary>
  344. /// <param name="editorData">Additional animation clip data for use in editor.</param>
  345. public void Apply(out EditorAnimClipData editorData)
  346. {
  347. if (isImported || clip == null)
  348. {
  349. editorData = null;
  350. return;
  351. }
  352. List<NamedVector3Curve> positionCurves = new List<NamedVector3Curve>();
  353. List<NamedQuaternionCurve> rotationCurves = new List<NamedQuaternionCurve>();
  354. List<NamedVector3Curve> scaleCurves = new List<NamedVector3Curve>();
  355. List<NamedFloatCurve> floatCurves = new List<NamedFloatCurve>();
  356. List<EditorVector3CurveTangents> positionTangents = new List<EditorVector3CurveTangents>();
  357. List<EditorVector3CurveTangents> rotationTangents = new List<EditorVector3CurveTangents>();
  358. List<EditorVector3CurveTangents> scaleTangents = new List<EditorVector3CurveTangents>();
  359. List<EditorFloatCurveTangents> floatTangents = new List<EditorFloatCurveTangents>();
  360. List<EditorNamedVector3Curve> eulerRotationCurves = new List<EditorNamedVector3Curve>();
  361. foreach (var kvp in curves)
  362. {
  363. string[] pathEntries = kvp.Key.Split('/');
  364. if (pathEntries.Length == 0)
  365. continue;
  366. string lastEntry = pathEntries[pathEntries.Length - 1];
  367. if (lastEntry == "Position" || lastEntry == "Rotation" || lastEntry == "Scale")
  368. {
  369. StringBuilder sb = new StringBuilder();
  370. for (int i = 0; i < pathEntries.Length - 2; i++)
  371. sb.Append(pathEntries[i] + "/");
  372. if (pathEntries.Length > 1)
  373. sb.Append(pathEntries[pathEntries.Length - 2]);
  374. string curvePath = sb.ToString();
  375. NamedVector3Curve curve = new NamedVector3Curve();
  376. curve.name = curvePath;
  377. curve.curve = AnimationUtility.CombineCurve3D(new[]
  378. {
  379. new AnimationCurve(kvp.Value.curveInfos[0].curve.KeyFrames),
  380. new AnimationCurve(kvp.Value.curveInfos[1].curve.KeyFrames),
  381. new AnimationCurve(kvp.Value.curveInfos[2].curve.KeyFrames)
  382. });
  383. EditorVector3CurveTangents curveTangents = new EditorVector3CurveTangents();
  384. curveTangents.name = curvePath;
  385. curveTangents.tangentsX = kvp.Value.curveInfos[0].curve.TangentModes;
  386. curveTangents.tangentsY = kvp.Value.curveInfos[1].curve.TangentModes;
  387. curveTangents.tangentsZ = kvp.Value.curveInfos[2].curve.TangentModes;
  388. if (lastEntry == "Position")
  389. {
  390. positionCurves.Add(curve);
  391. positionTangents.Add(curveTangents);
  392. }
  393. else if (lastEntry == "Rotation")
  394. {
  395. NamedQuaternionCurve quatCurve = new NamedQuaternionCurve();
  396. quatCurve.name = curve.name;
  397. quatCurve.curve = AnimationUtility.EulerToQuaternionCurve(curve.curve);
  398. EditorNamedVector3Curve edEulerCurve = new EditorNamedVector3Curve();
  399. edEulerCurve.name = curve.name;
  400. edEulerCurve.keyFrames = curve.curve.KeyFrames;
  401. rotationCurves.Add(quatCurve);
  402. eulerRotationCurves.Add(edEulerCurve);
  403. rotationTangents.Add(curveTangents);
  404. }
  405. else if (lastEntry == "Scale")
  406. {
  407. scaleCurves.Add(curve);
  408. scaleTangents.Add(curveTangents);
  409. }
  410. }
  411. else
  412. {
  413. Action<int, string, string, AnimationCurveFlags> addCurve = (idx, path, subPath, flags) =>
  414. {
  415. string fullPath = path + subPath;
  416. NamedFloatCurve curve = new NamedFloatCurve(fullPath,
  417. new AnimationCurve(kvp.Value.curveInfos[idx].curve.KeyFrames));
  418. curve.flags = flags;
  419. EditorFloatCurveTangents curveTangents = new EditorFloatCurveTangents();
  420. curveTangents.name = fullPath;
  421. curveTangents.tangents = kvp.Value.curveInfos[idx].curve.TangentModes;
  422. floatCurves.Add(curve);
  423. floatTangents.Add(curveTangents);
  424. };
  425. switch (kvp.Value.type)
  426. {
  427. case SerializableProperty.FieldType.Vector2:
  428. addCurve(0, kvp.Key, ".x", 0);
  429. addCurve(1, kvp.Key, ".y", 0);
  430. break;
  431. case SerializableProperty.FieldType.Vector3:
  432. addCurve(0, kvp.Key, ".x", 0);
  433. addCurve(1, kvp.Key, ".y", 0);
  434. addCurve(2, kvp.Key, ".z", 0);
  435. break;
  436. case SerializableProperty.FieldType.Vector4:
  437. addCurve(0, kvp.Key, ".x", 0);
  438. addCurve(1, kvp.Key, ".y", 0);
  439. addCurve(2, kvp.Key, ".z", 0);
  440. addCurve(3, kvp.Key, ".w", 0);
  441. break;
  442. case SerializableProperty.FieldType.Color:
  443. addCurve(0, kvp.Key, ".r", 0);
  444. addCurve(1, kvp.Key, ".g", 0);
  445. addCurve(2, kvp.Key, ".b", 0);
  446. addCurve(3, kvp.Key, ".a", 0);
  447. break;
  448. case SerializableProperty.FieldType.Bool:
  449. case SerializableProperty.FieldType.Int:
  450. case SerializableProperty.FieldType.Float:
  451. {
  452. AnimationCurveFlags flags = 0;
  453. string path = kvp.Key;
  454. if (IsMorphShapeCurve(kvp.Key))
  455. {
  456. string trimmedPath = path.Trim('/');
  457. string[] entries = trimmedPath.Split('/');
  458. bool isWeight = entries[entries.Length - 2] == "Weight";
  459. if (isWeight)
  460. flags = AnimationCurveFlags.MorphWeight;
  461. else
  462. flags = AnimationCurveFlags.MorphFrame;
  463. path = entries[entries.Length - 1];
  464. }
  465. addCurve(0, path, "", flags);
  466. }
  467. break;
  468. }
  469. }
  470. }
  471. AnimationCurves newClipCurves = new AnimationCurves();
  472. newClipCurves.Position = positionCurves.ToArray();
  473. newClipCurves.Rotation = rotationCurves.ToArray();
  474. newClipCurves.Scale = scaleCurves.ToArray();
  475. newClipCurves.Generic = floatCurves.ToArray();
  476. clip.Curves = newClipCurves;
  477. clip.Events = events;
  478. clip.SampleRate = sampleRate;
  479. editorData = new EditorAnimClipData();
  480. editorData.tangents = new EditorAnimClipTangents();
  481. editorData.tangents.positionCurves = positionTangents.ToArray();
  482. editorData.tangents.rotationCurves = rotationTangents.ToArray();
  483. editorData.tangents.scaleCurves = scaleTangents.ToArray();
  484. editorData.tangents.floatCurves = floatTangents.ToArray();
  485. editorData.eulerCurves = eulerRotationCurves.ToArray();
  486. }
  487. /// <summary>
  488. /// Saves the animation curves and events stored in this object, into the associated animation clip resource.
  489. /// Relevant animation clip resource must already be created and exist in the project library.
  490. /// </summary>
  491. public void SaveToClip()
  492. {
  493. if (!isImported)
  494. {
  495. EditorAnimClipData editorAnimClipData;
  496. Apply(out editorAnimClipData);
  497. string resourcePath = ProjectLibrary.GetPath(clip);
  498. ProjectLibrary.Save(clip);
  499. ProjectLibrary.SetEditorData(resourcePath, editorAnimClipData);
  500. }
  501. else
  502. {
  503. string resourcePath = ProjectLibrary.GetPath(clip);
  504. LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath);
  505. if (entry != null && entry.Type == LibraryEntryType.File)
  506. {
  507. FileEntry fileEntry = (FileEntry)entry;
  508. MeshImportOptions meshImportOptions = (MeshImportOptions)fileEntry.Options;
  509. string clipName = PathEx.GetTail(resourcePath);
  510. List<ImportedAnimationEvents> newEvents = new List<ImportedAnimationEvents>();
  511. newEvents.AddRange(meshImportOptions.AnimationEvents);
  512. bool isExisting = false;
  513. for (int i = 0; i < newEvents.Count; i++)
  514. {
  515. if (newEvents[i].Name == clipName)
  516. {
  517. newEvents[i].Events = events;
  518. isExisting = true;
  519. break;
  520. }
  521. }
  522. if (!isExisting)
  523. {
  524. ImportedAnimationEvents newEntry = new ImportedAnimationEvents();
  525. newEntry.Name = clipName;
  526. newEntry.Events = events;
  527. newEvents.Add(newEntry);
  528. }
  529. meshImportOptions.AnimationEvents = newEvents.ToArray();
  530. ProjectLibrary.Reimport(resourcePath, meshImportOptions, true);
  531. }
  532. }
  533. }
  534. /// <summary>
  535. /// Generates a unique color based on the provided index.
  536. /// </summary>
  537. /// <param name="idx">Index to use for generating a color. Should be less than 30 in order to guarantee reasonably
  538. /// different colors.</param>
  539. /// <returns>Unique color.</returns>
  540. public static Color GetUniqueColor(int idx)
  541. {
  542. const int COLOR_SPACING = 359 / 15;
  543. float hue = ((idx * COLOR_SPACING) % 359) / 359.0f;
  544. return Color.HSV2RGB(new Color(hue, 175.0f / 255.0f, 175.0f / 255.0f));
  545. }
  546. }
  547. /** @} */
  548. }