ZibraLiquidBakingUtility.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. using UnityEngine;
  2. using UnityEditor;
  3. using com.zibra.liquid.Solver;
  4. using com.zibra.liquid.Analytics;
  5. using UnityEngine.SceneManagement;
  6. using UnityEditor.SceneManagement;
  7. using System.IO;
  8. using System;
  9. using System.Collections;
  10. using com.zibra.common;
  11. using com.zibra.common.Analytics;
  12. namespace com.zibra.liquid.Editor.Solver
  13. {
  14. internal class ZibraLiquidBakingUtility : EditorWindow
  15. {
  16. public ZibraLiquid liquidInstance;
  17. // Timestep for baking simulation
  18. public float simulationDeltaTime = 0.02f;
  19. // Simulation time in seconds
  20. public float simulationTime = 2.0f;
  21. public bool useProjectFixedTimestep = true;
  22. private IEnumerator currentCoroutine = null;
  23. [MenuItem(Effects.BaseMenuBarPath + "Liquid/Initial State Baking Utility", false, 700)]
  24. public static void ShowWindow()
  25. {
  26. GetWindow<ZibraLiquidBakingUtility>("Zibra Liquid Baking Utility");
  27. }
  28. private void UpdateSelectedObject()
  29. {
  30. // If we only have single liquid instance, just set it right away save few clicks
  31. ZibraLiquid[] liquids = FindObjectsByType<ZibraLiquid>(FindObjectsSortMode.None);
  32. if (liquids.Length == 1)
  33. {
  34. liquidInstance = liquids[0];
  35. }
  36. }
  37. private void StateChangeHandler(PlayModeStateChange stateChange)
  38. {
  39. if (stateChange == PlayModeStateChange.EnteredEditMode)
  40. {
  41. UpdateSelectedObject();
  42. }
  43. }
  44. private void SceneOpenedCallback(Scene scene, OpenSceneMode _mode)
  45. {
  46. UpdateSelectedObject();
  47. }
  48. public void OnEnable()
  49. {
  50. UpdateSelectedObject();
  51. EditorApplication.playModeStateChanged += StateChangeHandler;
  52. EditorSceneManager.sceneOpened += SceneOpenedCallback;
  53. EditorApplication.update += EditorUpdate;
  54. }
  55. public void OnDisable()
  56. {
  57. if (liquidInstance && liquidInstance.Initialized)
  58. {
  59. liquidInstance.ReleaseSimulation();
  60. SceneView.RepaintAll();
  61. }
  62. EditorApplication.playModeStateChanged -= StateChangeHandler;
  63. EditorSceneManager.sceneOpened -= SceneOpenedCallback;
  64. EditorApplication.update -= EditorUpdate;
  65. }
  66. private void WriteToByteArray(float data, byte[] array, ref int startIndex)
  67. {
  68. Array.Copy(BitConverter.GetBytes(data), 0, array, startIndex, sizeof(float));
  69. startIndex += sizeof(float);
  70. }
  71. private void WriteToByteArray(int data, byte[] array, ref int startIndex)
  72. {
  73. Array.Copy(BitConverter.GetBytes(data), 0, array, startIndex, sizeof(int));
  74. startIndex += sizeof(int);
  75. }
  76. private byte[] ConvertInitialStateToBytes(ZibraLiquid.BakedInitialState initialStateData)
  77. {
  78. int particleCount = initialStateData.ParticleCount;
  79. int startIndex = 0;
  80. // Non Pro - Vector3 - position
  81. // Pro - Vector4 - position+species
  82. // Vector2Int - velocity
  83. // int - particleCount
  84. // int - header
  85. int byteCount = particleCount * (sizeof(float) * 4 + sizeof(int) * 2) + 2 * sizeof(int);
  86. byte[] output = new byte[byteCount];
  87. int header = ZibraLiquid.BAKED_LIQUID_PRO_HEADER_VALUE;
  88. WriteToByteArray(header, output, ref startIndex);
  89. WriteToByteArray(particleCount, output, ref startIndex);
  90. for (int i = 0; i < particleCount; i++)
  91. {
  92. for (int j = 0; j < 4; j++)
  93. {
  94. WriteToByteArray(initialStateData.Positions[i][j], output, ref startIndex);
  95. }
  96. }
  97. for (int i = 0; i < particleCount; i++)
  98. {
  99. for (int j = 0; j < 2; j++)
  100. {
  101. WriteToByteArray(initialStateData.AffineVelocity[4 * i + 3][j], output, ref startIndex);
  102. }
  103. }
  104. return output;
  105. }
  106. private void SaveState(bool saveToNewFile)
  107. {
  108. ZibraLiquid.BakedInitialState state = liquidInstance.SerializeCurrentLiquidState();
  109. if (state.ParticleCount == 0)
  110. {
  111. Debug.LogError("Error saving liquid state. State don't have any particles to save.");
  112. return;
  113. }
  114. string path = null;
  115. if (!saveToNewFile && liquidInstance.BakedInitialStateAsset)
  116. {
  117. path = AssetDatabase.GetAssetPath(liquidInstance.BakedInitialStateAsset);
  118. }
  119. if (path == null)
  120. {
  121. string scenePath = SceneManager.GetActiveScene().path;
  122. if (scenePath.Length == 0)
  123. {
  124. if (EditorUtility.DisplayDialog("The untitled scene needs saving",
  125. "You need to save the scene before saving Liquid Initial State.",
  126. "Save Scene", "Cancel"))
  127. {
  128. EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
  129. scenePath = SceneManager.GetActiveScene().path;
  130. }
  131. else
  132. {
  133. return;
  134. }
  135. }
  136. if (scenePath.Length == 0)
  137. {
  138. Debug.LogError("Failed querying active scene path");
  139. return;
  140. }
  141. string destFolder = scenePath.Remove(scenePath.Length - ".unity".Length);
  142. if (!AssetDatabase.IsValidFolder(destFolder))
  143. {
  144. int separatorIndex = destFolder.LastIndexOf('/');
  145. string parentFolder = destFolder.Substring(0, separatorIndex);
  146. string newFolder = destFolder.Substring(separatorIndex + 1);
  147. AssetDatabase.CreateFolder(parentFolder, newFolder);
  148. }
  149. string wantedFilename = destFolder + "/" + liquidInstance.name + "BakedState.bytes";
  150. path = AssetDatabase.GenerateUniqueAssetPath(wantedFilename);
  151. }
  152. if (path == null)
  153. {
  154. Debug.LogError("Error calculating path for saved liquid state");
  155. return;
  156. }
  157. File.WriteAllBytes(path, ConvertInitialStateToBytes(state));
  158. AssetDatabase.Refresh();
  159. Undo.RegisterCompleteObjectUndo(liquidInstance, "Saved liquid state");
  160. liquidInstance.InitialState = ZibraLiquid.InitialStateType.BakedLiquidState;
  161. liquidInstance.BakedInitialStateAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
  162. UnityEditor.EditorUtility.SetDirty(liquidInstance);
  163. ZibraLiquidAnalytics.TrackBakedStateSaved();
  164. ZibraEffectsAnalytics.TrackConfiguration("Liquids");
  165. }
  166. private IEnumerator RunSimulation()
  167. {
  168. if (!liquidInstance.Initialized)
  169. {
  170. // Don't use initial state when baking new initial state
  171. ZibraLiquid.InitialStateType initialState = liquidInstance.InitialState;
  172. liquidInstance.InitialState = ZibraLiquid.InitialStateType.NoParticles;
  173. liquidInstance.InitializeSimulation();
  174. liquidInstance.InitialState = initialState;
  175. }
  176. int simulationFrames = (int)(simulationTime / simulationDeltaTime);
  177. for (int i = 0; i < simulationFrames; i++)
  178. {
  179. if (EditorUtility.DisplayCancelableProgressBar("Zibra Liquid Simulation", "Simulation in progress...",
  180. (float)i / (float)simulationFrames))
  181. break;
  182. liquidInstance.UpdateSimulation(simulationDeltaTime);
  183. // Show simualtion progress
  184. if (i % 10 == 0)
  185. {
  186. SceneView.RepaintAll();
  187. yield return null;
  188. }
  189. }
  190. EditorUtility.ClearProgressBar();
  191. EditorApplication.QueuePlayerLoopUpdate();
  192. SceneView.RepaintAll();
  193. currentCoroutine = null;
  194. }
  195. // utility method
  196. private static void HorizontalLine(Color color)
  197. {
  198. GUIStyle horizontalLine;
  199. horizontalLine = new GUIStyle();
  200. horizontalLine.normal.background = EditorGUIUtility.whiteTexture;
  201. horizontalLine.margin = new RectOffset(0, 0, 4, 4);
  202. horizontalLine.fixedHeight = 1;
  203. var c = GUI.color;
  204. GUI.color = color;
  205. GUILayout.Space(10);
  206. GUILayout.Box(GUIContent.none, horizontalLine);
  207. GUILayout.Space(10);
  208. GUI.color = c;
  209. }
  210. private void EditorUpdate()
  211. {
  212. if (currentCoroutine != null)
  213. {
  214. currentCoroutine.MoveNext();
  215. }
  216. }
  217. private void OnGUI()
  218. {
  219. EditorGUI.BeginDisabledGroup(currentCoroutine != null);
  220. if (UnityEditor.EditorApplication.isPlaying)
  221. {
  222. EditorGUILayout.HelpBox("The baking utility can only be used in editor mode", MessageType.Warning);
  223. return;
  224. }
  225. HorizontalLine(Color.grey);
  226. EditorGUILayout.HelpBox(
  227. "Simulate first N seconds and save state. After that, in game mode, liquid will start simulation from your saved state.",
  228. MessageType.Info);
  229. HorizontalLine(Color.grey);
  230. if (liquidInstance == null)
  231. {
  232. EditorGUILayout.HelpBox("Please select the Zibra Liquid instance.", MessageType.Info);
  233. GUILayout.Space(10);
  234. }
  235. liquidInstance = (liquid.Solver.ZibraLiquid)EditorGUILayout.ObjectField(
  236. "Liquid Instance:", liquidInstance, typeof(liquid.Solver.ZibraLiquid), true);
  237. HorizontalLine(Color.grey);
  238. if (liquidInstance != null)
  239. {
  240. if (!liquidInstance.HasEmitter())
  241. {
  242. EditorGUILayout.HelpBox(
  243. "Selected instance don't have any emitters. No liquid can spawn under these conditions. Please add emitter to bake liquid.",
  244. MessageType.Error);
  245. return;
  246. }
  247. if (!liquidInstance.isActiveAndEnabled)
  248. {
  249. EditorGUILayout.HelpBox("Selected instance is not enabled, enable liquid instance and try again.",
  250. MessageType.Error);
  251. return;
  252. }
  253. if (!liquidInstance.RunSimulation)
  254. {
  255. EditorGUILayout.HelpBox(
  256. "Run simulation is set to false on this instance, enable simulation and try again.",
  257. MessageType.Error);
  258. return;
  259. }
  260. string runSimulationString = liquidInstance.Initialized ? "Run Simulation Forward" : "Run Simulation";
  261. if (GUILayout.Button(runSimulationString))
  262. {
  263. currentCoroutine = RunSimulation();
  264. liquidInstance.StartCoroutine(currentCoroutine);
  265. }
  266. if (liquidInstance.Initialized)
  267. {
  268. if (GUILayout.Button("Stop"))
  269. {
  270. liquidInstance.ReleaseSimulation();
  271. EditorApplication.QueuePlayerLoopUpdate();
  272. SceneView.RepaintAll();
  273. }
  274. }
  275. simulationTime = EditorGUILayout.FloatField("Simulation time (seconds)", simulationTime);
  276. useProjectFixedTimestep = EditorGUILayout.Toggle("Use project fixed timestep", useProjectFixedTimestep);
  277. if (useProjectFixedTimestep)
  278. {
  279. simulationDeltaTime = Time.fixedDeltaTime;
  280. }
  281. else
  282. {
  283. simulationDeltaTime = EditorGUILayout.FloatField("Simulation timestep", simulationDeltaTime);
  284. }
  285. HorizontalLine(Color.grey);
  286. if (liquidInstance.Initialized)
  287. {
  288. if (GUILayout.Button("Save new state"))
  289. {
  290. SaveState(true);
  291. }
  292. if (liquidInstance.BakedInitialStateAsset)
  293. {
  294. if (GUILayout.Button("Overwrite existing state"))
  295. {
  296. SaveState(false);
  297. }
  298. }
  299. HorizontalLine(Color.grey);
  300. }
  301. var serializedObject = new SerializedObject(liquidInstance);
  302. var bakedInitialStateAsset = serializedObject.FindProperty("BakedInitialStateAsset");
  303. EditorGUILayout.PropertyField(bakedInitialStateAsset);
  304. serializedObject.ApplyModifiedProperties();
  305. }
  306. EditorGUI.EndDisabledGroup();
  307. }
  308. }
  309. }