SkyEditorUtility.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using UnityEngine;
  5. using UnityEditor;
  6. using UnityEngine.SceneManagement;
  7. namespace Funly.SkyStudio
  8. {
  9. public abstract class SkyEditorUtility
  10. {
  11. public const float KEY_GRIP_HEIGHT = 18.0f;
  12. public const float KEY_GRIP_WIDTH = 18.0f;
  13. public const string KEY_GRIP_ACTIVE = "TriangleGripActive";
  14. public const string KEY_GRIP_INACTIVE = "TriangleGripInactive";
  15. public const string GENERATED_CONTENT_DIR = "Data";
  16. public const string PACKAGE_DIR_NAME = "FunlySkyStudio";
  17. public static Dictionary<string, Texture2D> _imageCache = new Dictionary<string, Texture2D>();
  18. // Keyframe time is pretty much zero.
  19. public static bool IsKeyFrameAtStart(IBaseKeyframe keyframe)
  20. {
  21. return keyframe.time >= 0 && keyframe.time < .00001f;
  22. }
  23. // Keyframe time is pretty much 1.0f.
  24. public static bool IsKeyFrameAtEnd(IBaseKeyframe keyframe)
  25. {
  26. return keyframe.time >= .99999f;
  27. }
  28. public static float GetWidthBetweenTimes(Rect rect, float fromTime, float toTime)
  29. {
  30. if (toTime < fromTime) {
  31. toTime = 1.0f;
  32. }
  33. return GetXPositionForPercent(rect, toTime) - GetXPositionForPercent(rect, fromTime);
  34. }
  35. // Get the xPosition that's a percent inside the rect.
  36. public static float GetXPositionForPercent(Rect rect, float percent)
  37. {
  38. float distance = rect.width * percent;
  39. return rect.x + distance;
  40. }
  41. public static float GetYPositionForPercent(Rect rect, float percent)
  42. {
  43. return rect.y + (rect.height * percent);
  44. }
  45. // Get the percentage this xPosition is inside the rect.
  46. public static float GetPercentForXPosition(Rect rect, float xPosition)
  47. {
  48. return Mathf.Clamp01((xPosition - rect.x) / rect.width);
  49. }
  50. public static float GetPercentForYPosition(Rect rect, float yPosition)
  51. {
  52. return Mathf.Clamp01((yPosition - rect.y) / rect.height);
  53. }
  54. public static void CancelKeyframeDrag() {
  55. TimelineSelection.selectedControlUUID = null;
  56. }
  57. // Cancel drag operations, useful for when mouse leaves window.
  58. public static void CancelTimelineDrags() {
  59. TimelineSelection.selectedControlUUID = null;
  60. TimelineSelection.isDraggingTimeline = false;
  61. }
  62. public static bool IsKeyframeSelected(IBaseKeyframe keyframe) {
  63. if (keyframe == null || TimelineSelection.selectedControlUUID == null ||
  64. TimelineSelection.selectedControlUUID != keyframe.id) {
  65. return false;
  66. }
  67. return true;
  68. }
  69. // Sticks to bottom of rect, and can slide horizontally only.
  70. public static void DrawHorizontalKeyMarker(
  71. Rect fullSliderRect, BaseKeyframe keyFrame, UnityEngine.Object undoObject, out bool didSingleClick, out bool isDragging, out bool keyFrameTimeChanged)
  72. {
  73. Rect markerRect = new Rect(
  74. SkyEditorUtility.GetXPositionForPercent(fullSliderRect, keyFrame.time) - (KEY_GRIP_WIDTH / 2),
  75. fullSliderRect.y + fullSliderRect.height - (KEY_GRIP_HEIGHT) / 2.0f,
  76. KEY_GRIP_WIDTH,
  77. KEY_GRIP_HEIGHT);
  78. bool wasDragging = TimelineSelection.selectedControlUUID != null && TimelineSelection.selectedControlUUID == keyFrame.id;
  79. bool isMouseOverControl = markerRect.Contains(Event.current.mousePosition);
  80. didSingleClick = false;
  81. isDragging = wasDragging;
  82. keyFrameTimeChanged = false;
  83. // Single Click.
  84. if (Event.current.isMouse) {
  85. // Check for single click, with no drag.
  86. if (Event.current.type == EventType.MouseUp && TimelineSelection.selectedControlUUID == null && isMouseOverControl) {
  87. Event.current.Use();
  88. didSingleClick = true;
  89. }
  90. // Start slide.
  91. if (TimelineSelection.selectedControlUUID == null && isMouseOverControl && Event.current.type == EventType.MouseDrag) {
  92. TimelineSelection.selectedControlUUID = keyFrame.id;
  93. Event.current.Use();
  94. isDragging = true;
  95. }
  96. // End Slide.
  97. if (wasDragging && Event.current.type == EventType.MouseUp) {
  98. TimelineSelection.selectedControlUUID = null;
  99. Event.current.Use();
  100. isDragging = false;
  101. }
  102. // If we're dragging this keyframe grip, move it's position.
  103. if (isDragging || wasDragging) {
  104. // Update key frame time value and reposition rectangle.
  105. Undo.RecordObject(undoObject, "Keyframe time position changed.");
  106. keyFrame.time = SkyEditorUtility.GetPercentForXPosition(fullSliderRect, Event.current.mousePosition.x);
  107. keyFrameTimeChanged = true;
  108. isDragging = true;
  109. // Position the marker rect.
  110. markerRect.x = SkyEditorUtility.GetXPositionForPercent(fullSliderRect, keyFrame.time) - (KEY_GRIP_WIDTH / 2);
  111. Event.current.Use();
  112. }
  113. }
  114. bool showAsActive = IsKeyframeActiveInInspector(keyFrame) || isDragging;
  115. // Draw the marker at this location.
  116. SkyEditorUtility.DrawKeyMarker(markerRect, showAsActive);
  117. }
  118. public static void DrawNumericKeyMarker(Rect fullSliderRect, NumberKeyframe keyFrame, NumberKeyframeGroup group,
  119. UnityEngine.Object undoObject, out bool didSingleClick, out bool isDragging, out bool keyFrameTimeChanged)
  120. {
  121. Rect markerRect = new Rect(
  122. SkyEditorUtility.GetXPositionForPercent(fullSliderRect, keyFrame.time) - (KEY_GRIP_WIDTH / 2),
  123. GetYPositionForPercent(fullSliderRect, 1 - group.ValueToPercent(keyFrame.value)),
  124. KEY_GRIP_WIDTH,
  125. KEY_GRIP_HEIGHT);
  126. bool wasDragging = TimelineSelection.selectedControlUUID != null && TimelineSelection.selectedControlUUID == keyFrame.id;
  127. bool isMouseOverControl = markerRect.Contains(Event.current.mousePosition);
  128. didSingleClick = false;
  129. keyFrameTimeChanged = false;
  130. isDragging = wasDragging;
  131. // Single Click.
  132. if (Event.current.isMouse) {
  133. // Check for single click, with no drag.
  134. if (Event.current.type == EventType.MouseUp && TimelineSelection.selectedControlUUID == null && isMouseOverControl) {
  135. didSingleClick = true;
  136. Event.current.Use();
  137. }
  138. // Start slide.
  139. if (TimelineSelection.selectedControlUUID == null && isMouseOverControl && Event.current.type == EventType.MouseDrag) {
  140. TimelineSelection.selectedControlUUID = keyFrame.id;
  141. // Find the position of the current value and record the offset so we can drag the keygrip relative from here.
  142. Vector2 valuePosition = new Vector2(
  143. GetXPositionForPercent(fullSliderRect, keyFrame.time),
  144. GetYPositionForPercent(fullSliderRect, 1 - group.ValueToPercent(keyFrame.value)));
  145. TimelineSelection.startingMouseOffset = valuePosition - Event.current.mousePosition;
  146. isDragging = true;
  147. Event.current.Use();
  148. }
  149. // End Slide.
  150. if (wasDragging && Event.current.type == EventType.MouseUp) {
  151. TimelineSelection.selectedControlUUID = null;
  152. isDragging = false;
  153. Event.current.Use();
  154. }
  155. // If we're dragging this keyframe grip, move it's position.
  156. if (wasDragging || isDragging) {
  157. // Update key frame time value and reposition rectangle.
  158. Undo.RecordObject(undoObject, "Keyframe time and value changed.");
  159. Vector2 adjustedMousePosition = Event.current.mousePosition + TimelineSelection.startingMouseOffset;
  160. keyFrame.time = GetPercentForXPosition(fullSliderRect, adjustedMousePosition.x);
  161. float adjustedValuePercent = 1 - GetPercentForYPosition(fullSliderRect, adjustedMousePosition.y);
  162. keyFrame.value = group.PercentToValue(adjustedValuePercent);
  163. keyFrameTimeChanged = true;
  164. isDragging = true;
  165. // Position the marker rect.
  166. markerRect.x = SkyEditorUtility.GetXPositionForPercent(fullSliderRect, keyFrame.time) - (KEY_GRIP_WIDTH / 2);
  167. markerRect.y = GetYPositionForPercent(fullSliderRect, 1 - group.ValueToPercent(keyFrame.value));
  168. Event.current.Use();
  169. }
  170. }
  171. bool showAsActive = IsKeyframeActiveInInspector(keyFrame) || isDragging;
  172. // Draw the marker at this location.
  173. SkyEditorUtility.DrawKeyMarker(markerRect, showAsActive);
  174. }
  175. // True if the keyframe is shown in the inspector window.
  176. public static bool IsKeyframeActiveInInspector(BaseKeyframe keyFrame) {
  177. if (KeyframeInspectorWindow.inspectorEnabled && KeyframeInspectorWindow.GetActiveKeyframeId() == keyFrame.id)
  178. {
  179. return true;
  180. } else {
  181. return false;
  182. }
  183. }
  184. // True if group is selected on timeline.
  185. public static bool IsGroupSelectedOnTimeline(string groupId) {
  186. if (TimelineSelection.selectedGroupUUID != null && TimelineSelection.selectedGroupUUID == groupId) {
  187. return true;
  188. } else {
  189. return false;
  190. }
  191. }
  192. public static void DrawKeyMarker(Rect rect, bool isActive)
  193. {
  194. if (Event.current.type != EventType.Repaint)
  195. {
  196. return;
  197. }
  198. Texture2D gripTexture = null;
  199. if (isActive) {
  200. gripTexture = GetActiveKeyframeMarkerTexture();
  201. } else {
  202. gripTexture = GetInactiveKeyframeMarkerTexture();
  203. }
  204. GUI.DrawTexture(rect, gripTexture, ScaleMode.ScaleAndCrop, true);
  205. }
  206. public static Texture2D GetActiveKeyframeMarkerTexture()
  207. {
  208. return LoadEditorResourceTexture(KEY_GRIP_ACTIVE);
  209. }
  210. public static Texture2D GetInactiveKeyframeMarkerTexture()
  211. {
  212. return LoadEditorResourceTexture(KEY_GRIP_INACTIVE);
  213. }
  214. public static Texture2D LoadEditorResourceTexture(string textureName) {
  215. return LoadEditorResourceTexture(textureName, true);
  216. }
  217. public static Texture2D LoadEditorResourceTexture(string textureName, bool useCache)
  218. {
  219. if (textureName == null) {
  220. Debug.LogError("Can't load null texture name");
  221. return null;
  222. }
  223. if (useCache && _imageCache.ContainsKey(textureName))
  224. {
  225. return _imageCache[textureName];
  226. }
  227. string[] guids = AssetDatabase.FindAssets(textureName);
  228. if (guids.Length == 0) {
  229. Debug.LogError("Failed to located editor keyframe texture, asset is in unknown location.");
  230. return null;
  231. }
  232. foreach (string guid in guids)
  233. {
  234. // Return first image located inside our asset directory.
  235. string assetPath = AssetDatabase.GUIDToAssetPath(guid);
  236. if (assetPath.Contains(PACKAGE_DIR_NAME))
  237. {
  238. Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
  239. if (useCache) {
  240. _imageCache.Add(textureName, tex);
  241. }
  242. return tex;
  243. }
  244. }
  245. return null;
  246. }
  247. public static GameObject LoadEditorPrefab(string prefabName)
  248. {
  249. string[] guids = AssetDatabase.FindAssets("t:prefab " + prefabName);
  250. if (guids.Length == 0) {
  251. Debug.LogError("Failed to located editor keyframe texture, asset is in unknown location.");
  252. return null;
  253. }
  254. foreach (string guid in guids) {
  255. string assetPath = AssetDatabase.GUIDToAssetPath(guid);
  256. if (assetPath.Contains(PACKAGE_DIR_NAME)) {
  257. return AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
  258. }
  259. }
  260. return null;
  261. }
  262. public static void WriteTextureToJpgFile(RenderTexture renderTexture, string filename)
  263. {
  264. Texture2D tex = new Texture2D(renderTexture.width, renderTexture.height);
  265. tex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
  266. tex.Apply();
  267. Debug.Log("Writing out texture width:" + tex.width + " height:" + tex.height);
  268. File.WriteAllBytes(filename, tex.EncodeToJPG());
  269. }
  270. public static void WriteTextureToFile(RenderTexture renderTexture, string filename)
  271. {
  272. Texture2D tex = new Texture2D(renderTexture.width, renderTexture.height);
  273. tex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
  274. tex.Apply();
  275. WriteTextureToFile(tex, filename);
  276. }
  277. public static void WriteTextureToFile(RenderTexture renderTexture, string filename, TextureFormat textureFormat)
  278. {
  279. Texture2D tex = new Texture2D(renderTexture.width, renderTexture.height, textureFormat, false);
  280. tex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
  281. tex.Apply();
  282. WriteTextureToFile(tex, filename);
  283. }
  284. public static void WriteTextureToFile(Texture2D tex, string filename)
  285. {
  286. File.WriteAllBytes(filename, tex.EncodeToPNG());
  287. }
  288. public static void CreateSkyDataDirectory(SkyProfile profile)
  289. {
  290. string profilePath = AssetDatabase.GetAssetPath(profile);
  291. if (profilePath == null) {
  292. Debug.LogError("Failed to locate sky profile on disk, is it saved yet?");
  293. return;
  294. }
  295. string containingDir = Path.GetDirectoryName(profilePath);
  296. string fullSkyDataPath = containingDir + "/" + GENERATED_CONTENT_DIR;
  297. if (AssetDatabase.IsValidFolder(fullSkyDataPath) == false) {
  298. AssetDatabase.CreateFolder(containingDir, GENERATED_CONTENT_DIR);
  299. }
  300. }
  301. public static string GetDirectoryForAssetGUID(string guid)
  302. {
  303. string assetPath = AssetDatabase.GUIDToAssetPath(guid);
  304. return Path.GetDirectoryName(assetPath);
  305. }
  306. public static string GetProfileSkyboxPath(string dataDirPath)
  307. {
  308. return dataDirPath + "/Skybox.mat";
  309. }
  310. public static string GetProfileSkyResourcesDirPath(string dataDirPath)
  311. {
  312. return dataDirPath + "/Sky-Resources";
  313. }
  314. public static string GetProfileSkyResourceFilePath(string dataDirPath, string assetName)
  315. {
  316. return GetProfileSkyResourcesDirPath(dataDirPath) + "/" + assetName;
  317. }
  318. public static string CreateSkyDataFolderWithScene(Scene scene, string skyProfileName)
  319. {
  320. string dataDir = Path.GetDirectoryName(scene.path);
  321. if (AssetDatabase.IsValidFolder(dataDir) == false) {
  322. AssetDatabase.CreateFolder(Path.GetDirectoryName(dataDir), Path.GetFileName(dataDir));
  323. }
  324. return dataDir;
  325. }
  326. public static string GenerateUniqueFolder(string parentFolder, string folderName, bool create)
  327. {
  328. const int maxTries = 200;
  329. int currentTry = 0;
  330. // Verify directory exists.
  331. if (!AssetDatabase.IsValidFolder(parentFolder)) {
  332. Debug.LogWarning("Invalid parent directory: " + parentFolder);
  333. return null;
  334. }
  335. while (currentTry < maxTries) {
  336. string tryDirName = null;
  337. if (currentTry == 0) {
  338. tryDirName = folderName;
  339. } else {
  340. tryDirName = folderName + "-" + currentTry;
  341. }
  342. string tryAssetPath = parentFolder + "/" + tryDirName;
  343. if (AssetDatabase.IsValidFolder(tryAssetPath) == false) {
  344. if (create) {
  345. AssetDatabase.CreateFolder(parentFolder, tryDirName);
  346. }
  347. return tryAssetPath;
  348. }
  349. currentTry += 1;
  350. }
  351. Debug.LogError("Failed to generate a unique filename after " + maxTries + " tries.");
  352. return null;
  353. }
  354. public static string GenerateUniqueFilename(string directoryPath, string filename, string extension)
  355. {
  356. const int maxTries = 200;
  357. int currentTry = 0;
  358. // Verify directory exists.
  359. if (!AssetDatabase.IsValidFolder(directoryPath)) {
  360. Debug.LogWarning("Filename is already unique since root directory does not exist: " + directoryPath);
  361. return directoryPath + "/" + filename + extension;
  362. }
  363. while (currentTry < maxTries) {
  364. string tryFilename = null;
  365. if (currentTry == 0) {
  366. tryFilename = filename;
  367. } else {
  368. tryFilename = filename + "-" + currentTry;
  369. }
  370. string tryAssetPath = directoryPath + "/" + tryFilename + extension;
  371. string guid = AssetDatabase.AssetPathToGUID(tryAssetPath);
  372. if ( guid == null || guid.Length == 0) {
  373. return tryAssetPath;
  374. }
  375. currentTry += 1;
  376. }
  377. Debug.LogError("Failed to generate a unique filename after " + maxTries + " tries.");
  378. return null;
  379. }
  380. public static string GetSkyProfileAssetPath(SkyProfile profile, string assetName)
  381. {
  382. CreateSkyDataDirectory(profile);
  383. string profileAssetPath = AssetDatabase.GetAssetPath(profile);
  384. if (profileAssetPath == null || profileAssetPath.Length == 0) {
  385. Debug.LogError("Can't return asset path that doesn't exist in database");
  386. return null;
  387. }
  388. string skyResourceDir = Path.GetDirectoryName(profileAssetPath);
  389. return skyResourceDir + "/Data/" + assetName + ".asset";
  390. }
  391. public static void RemoveSkyDataResource(SkyProfile profile, string textureName)
  392. {
  393. string texturePath = GetSkyProfileAssetPath(profile, textureName);
  394. AssetDatabase.DeleteAsset(texturePath);
  395. }
  396. public static void AddSkyResource(SkyProfile profile, Texture texture)
  397. {
  398. CreateSkyDataDirectory(profile);
  399. string texturePath = GetSkyProfileAssetPath(profile, texture.name);
  400. if (AssetDatabase.IsValidFolder(Path.GetDirectoryName(texturePath)) == false) {
  401. AssetDatabase.CreateFolder(Path.GetDirectoryName(texturePath), Path.GetFileName(texturePath));
  402. }
  403. AssetDatabase.CreateAsset(texture, texturePath);
  404. }
  405. // Converts back slashes to forward slashes.
  406. public static string WindowsPathToUnixPath(string path) {
  407. return path.Replace('\\', '/');
  408. }
  409. // Returns true if this asset path is inside the Sky Studio folder.
  410. public static bool IsAssetPathInsideSkyStudio(string assetPath) {
  411. return assetPath.Contains(PACKAGE_DIR_NAME);
  412. }
  413. public static string GetBestDefaultShaderNameForUnityVersion() {
  414. #if UNITY_2019_1_OR_NEWER
  415. return SkyProfile.DefaultShaderName;
  416. #else
  417. return SkyProfile.DefaultLegacyShaderName;
  418. #endif
  419. }
  420. // Does this version of unity support local keywords in shaders.
  421. public static bool SupportsLocalKeywords() {
  422. #if UNITY_2019_1_OR_NEWER
  423. return true;
  424. #else
  425. return false;
  426. #endif
  427. }
  428. }
  429. }