You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

403 lines
13 KiB

5 years ago
  1. /*
  2. Simple Mesh Combine
  3. Copyright Unluck Software
  4. www.chemicalbliss.com
  5. Change Log
  6. v1.1
  7. Added naming and prefab save option
  8. v1.2
  9. Added lightmap support
  10. v1.3
  11. Added multiple material support
  12. v1.301
  13. Fixed compile error trying to unwrap UV in game mode
  14. v1.4
  15. Added C# scripts
  16. v1.41 - 22.01.2015
  17. Changed from using SharedMaterial.Name to SharedMaterial directly to identify different materials
  18. Fixed error when combining meshes with more submeshes than materials
  19. v1.5 -24.01.2015
  20. Improved editor layout, added more info and tips
  21. Lightmap option as own function
  22. Now sets UV2 to null to reduce mesh size
  23. v1.53 -31.03.2015
  24. Fixed lightmapping for Unity 5
  25. v1.54 -01.05.2015
  26. Fixed build error Unity 5
  27. v1.6 & v1.61 - 28.05.2015
  28. Added Export to OBJ (Beta)
  29. - Used to fix/optimize combined meshes in external 3D sofware
  30. - Submesh/mulitple material support limited
  31. Improved Save Mesh asset
  32. - Save to custom folder
  33. - Overwrite keeps prefab
  34. Added Copy functionions
  35. - Duplicates gameObject then removes all components and empty gameObjects exept Colliders
  36. - Used to create prefabs with combined mesh + colliders
  37. Fixed: Deleting combined gameObject no longer gives null error
  38. v1.62 - 25.04.2016
  39. Fixed: Null errors
  40. v1.63 - 26.04.2015
  41. Added Optional skin
  42. */
  43. using UnityEngine;
  44. using System;
  45. using System.IO;
  46. using System.Text;
  47. using System.Collections.Generic;
  48. using UnityEditor;
  49. [CustomEditor(typeof(SimpleMeshCombine))]
  50. [System.Serializable]
  51. public class SimpleMeshCombineEditor: Editor {
  52. public Texture titleTexture;
  53. public bool unitySkin = true;
  54. public void OnEnable(){
  55. if(PlayerPrefs.GetInt("unitySkin") == 1) unitySkin = true;
  56. else unitySkin = false;
  57. }
  58. public void ToggleUnitySkin(){
  59. unitySkin = !unitySkin;
  60. if(unitySkin) PlayerPrefs.SetInt("unitySkin", 1);
  61. else PlayerPrefs.SetInt("unitySkin", 0);
  62. }
  63. public void ExportMesh(MeshFilter meshFilter,string folder,string filename) {
  64. string path = SaveFile(folder, filename, "obj");
  65. if (path != null) {
  66. StreamWriter sw = new StreamWriter(path);
  67. sw.Write(MeshToString(meshFilter));
  68. sw.Flush();
  69. sw.Close();
  70. AssetDatabase.Refresh();
  71. Debug.Log("Exported OBJ file to folder: " + path);
  72. }
  73. }
  74. public string MeshToString(MeshFilter meshFilter) {
  75. Mesh sMesh = meshFilter.sharedMesh;
  76. StringBuilder stringBuilder = new StringBuilder();
  77. stringBuilder.Append("g ").Append(meshFilter.name).Append("\n");
  78. foreach(Vector3 vert in sMesh.vertices) {
  79. Vector3 tPoint = meshFilter.transform.TransformPoint(vert);
  80. stringBuilder.Append(String.Format("v {0} {1} {2}\n", -tPoint.x, tPoint.y, tPoint.z));
  81. }
  82. stringBuilder.Append("\n");
  83. foreach(Vector3 norm in sMesh.normals) {
  84. Vector3 tDir = meshFilter.transform.TransformDirection(norm);
  85. stringBuilder.Append(String.Format("vn {0} {1} {2}\n", -tDir.x, tDir.y, tDir.z));
  86. }
  87. stringBuilder.Append("\n");
  88. foreach(Vector3 uv in sMesh.uv) {
  89. stringBuilder.Append(String.Format("vt {0} {1}\n", uv.x, uv.y));
  90. }
  91. for(int material = 0; material < sMesh.subMeshCount; material++) {
  92. stringBuilder.Append("\n");
  93. int[] tris = sMesh.GetTriangles(material);
  94. for(int i = 0; i < tris.Length; i += 3) {
  95. stringBuilder.Append(String.Format("f {1}/{1}/{1} {0}/{0}/{0} {2}/{2}/{2}\n", tris[i] + 1, tris[i + 1] + 1, tris[i + 2] + 1));
  96. }
  97. }
  98. return stringBuilder.ToString();
  99. }
  100. public string SaveFile(string folder,string name,string type) {
  101. string newPath = "";
  102. string path = EditorUtility.SaveFilePanel("Select Folder ", folder, name, type);
  103. if (path.Length > 0) {
  104. if (path.Contains("" + Application.dataPath)) {
  105. string s = "" + path + "";
  106. string d = "" + Application.dataPath + "/";
  107. string p = "Assets/" + s.Remove(0, d.Length);
  108. bool cancel = false;
  109. if (cancel) Debug.Log("Canceled");
  110. newPath = p;
  111. } else {
  112. Debug.LogError("Prefab Save Failed: Can't save outside project: " + path);
  113. }
  114. }
  115. return newPath;
  116. }
  117. public override void OnInspectorGUI() {
  118. //
  119. // STYLE AND COLOR
  120. //
  121. var target_cs = (SimpleMeshCombine)target;
  122. Color color2 = Color.white;
  123. Color color1 = Color.white;
  124. GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
  125. GUIStyle buttonStyle2 = new GUIStyle(GUI.skin.button);
  126. GUIStyle titleStyle = new GUIStyle(GUI.skin.label);
  127. if(!unitySkin){
  128. color2 = Color.yellow;
  129. color1 = Color.cyan;
  130. buttonStyle.fontStyle = FontStyle.Bold;
  131. buttonStyle.fixedWidth = 150.0f;
  132. buttonStyle.fixedHeight = 35.0f;
  133. buttonStyle.fontSize = 15;
  134. buttonStyle2.fixedWidth = 200.0f;
  135. buttonStyle2.fixedHeight = 25.0f;
  136. if(titleTexture == null) titleTexture = (Texture)Resources.Load("SMC_Title");
  137. GUILayout.Label(titleTexture, titleStyle);
  138. }
  139. buttonStyle2.margin = new RectOffset((int)((Screen.width - 200) * .5f), (int)((Screen.width - 200) * .5f), 0, 0);
  140. buttonStyle.margin = new RectOffset((int)((Screen.width - 150) * .5f), (int)((Screen.width - 150) * .5f), 0, 0);
  141. titleStyle.fixedWidth = 256.0f;
  142. titleStyle.fixedHeight = 64.0f;
  143. titleStyle.margin = new RectOffset((int)((Screen.width - 256) * .5f), (int)((Screen.width - 256) * .5f), 0, 0);
  144. GUIStyle infoStyle = new GUIStyle(GUI.skin.label);
  145. infoStyle.fontSize = 10;
  146. infoStyle.margin.top = 0;
  147. infoStyle.margin.bottom = 0;
  148. if (!Application.isPlaying) {
  149. GUI.enabled = true;
  150. } else {
  151. GUILayout.Label("Editor can't combine in play-mode", infoStyle);
  152. GUILayout.Label("Use SimpleMeshCombine.CombineMeshes();", infoStyle);
  153. GUI.enabled = false;
  154. }
  155. GUILayout.Space(15.0f);
  156. //
  157. // COMBINE MESH AREA
  158. //
  159. GUI.color = color1;
  160. if (target_cs.combinedGameOjects == null || target_cs.combinedGameOjects.Length == 0) {
  161. if (GUILayout.Button("Combine", buttonStyle)) {
  162. if (target_cs.transform.childCount > 1) target_cs.CombineMeshes();
  163. target_cs.combined.isStatic = true;
  164. }
  165. } else {
  166. if (GUILayout.Button("Release", buttonStyle)) {
  167. target_cs.EnableRenderers(true);
  168. if (target_cs.combined != null) DestroyImmediate(target_cs.combined);
  169. target_cs.combinedGameOjects = null;
  170. }
  171. }
  172. GUILayout.Space(5.0f);
  173. //
  174. // SAVE MESH AREA
  175. //
  176. if (target_cs.combined != null) {
  177. if (!target_cs._canGenerateLightmapUV) {
  178. GUILayout.Label("Warning: Mesh has too high vertex count", EditorStyles.boldLabel);
  179. GUI.enabled = false;
  180. }
  181. if (target_cs.combined.GetComponent<MeshFilter>().sharedMesh.name != "") {
  182. GUI.enabled = false;
  183. } else if(!Application.isPlaying){
  184. GUI.enabled = true;
  185. }
  186. if (GUILayout.Button("Save Mesh", buttonStyle2)) {
  187. string path = SaveFile("Assets/", target_cs.transform.name + " [SMC Asset]", "asset");
  188. if (path != null) {
  189. UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(path, (Type)typeof(object));
  190. if (asset == null) {
  191. AssetDatabase.CreateAsset(target_cs.combined.GetComponent<MeshFilter>().sharedMesh, path);
  192. } else {
  193. ((Mesh)asset).Clear();
  194. EditorUtility.CopySerialized(target_cs.combined.GetComponent<MeshFilter>().sharedMesh, asset);
  195. AssetDatabase.SaveAssets();
  196. }
  197. target_cs.combined.GetComponent<MeshFilter>().sharedMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path, (Type)typeof(object));
  198. Debug.Log("Saved mesh asset: " + path);
  199. }
  200. }
  201. GUILayout.Space(5.0f);
  202. }
  203. if(!Application.isPlaying){
  204. GUI.enabled = true;
  205. }
  206. if (target_cs.combined != null) {
  207. if (GUILayout.Button("Export OBJ", buttonStyle2)) {
  208. if (target_cs.combined != null) {
  209. ExportMesh(target_cs.combined.GetComponent<MeshFilter>(), "Assets/", target_cs.transform.name + " [SMC Mesh]" + ".obj");
  210. }
  211. }
  212. GUILayout.Space(15.0f);
  213. //
  214. // COPY
  215. //
  216. GUI.color = color2;
  217. string bText = "Create Copy";
  218. if (target_cs.combined.GetComponent<MeshFilter>().sharedMesh.name == "") {
  219. bText = bText + " (Saved mesh)";
  220. GUI.enabled = false;
  221. } else if(!Application.isPlaying){
  222. GUI.enabled = true;
  223. }
  224. if (GUILayout.Button(bText, buttonStyle2)) {
  225. GameObject newCopy = new GameObject();
  226. GameObject newCopy2 = new GameObject();
  227. newCopy2.transform.parent = newCopy.transform;
  228. newCopy2.transform.localPosition = target_cs.combined.transform.localPosition;
  229. newCopy2.transform.localRotation = target_cs.combined.transform.localRotation;
  230. newCopy.name = target_cs.name + " [SMC Copy]";
  231. newCopy2.name = "Mesh [SMC]";
  232. newCopy.transform.position = target_cs.transform.position;
  233. newCopy.transform.rotation = target_cs.transform.rotation;
  234. MeshFilter mf = newCopy2.AddComponent<MeshFilter>();
  235. newCopy2.AddComponent<MeshRenderer>();
  236. mf.sharedMesh = target_cs.combined.GetComponent<MeshFilter>().sharedMesh;
  237. target_cs.copyTarget = newCopy;
  238. CopyMaterials(newCopy2.transform);
  239. CopyColliders();
  240. Selection.activeTransform = newCopy.transform;
  241. }
  242. GUILayout.Space(5.0f);
  243. if (target_cs.copyTarget == null) {
  244. GUI.enabled = false;
  245. } else if(!Application.isPlaying){
  246. GUI.enabled = true;
  247. }
  248. if (GUILayout.Button("Copy Colliders", buttonStyle2)) {
  249. CopyColliders();
  250. }
  251. GUILayout.Space(5.0f);
  252. if (GUILayout.Button("Copy Materials", buttonStyle2)) {
  253. CopyMaterials(target_cs.copyTarget.transform.Find("Mesh [SMC]"));
  254. }
  255. if (!Application.isPlaying) {
  256. GUI.enabled = true;
  257. }
  258. target_cs.destroyOldColliders = EditorGUILayout.Toggle("Destroy old colliders", target_cs.destroyOldColliders);
  259. target_cs.keepStructure = EditorGUILayout.Toggle("Keep collider structure", target_cs.keepStructure);
  260. target_cs.copyTarget = (GameObject)EditorGUILayout.ObjectField("Copy to: ", target_cs.copyTarget, typeof(GameObject), true);
  261. }
  262. if(target_cs.combined == null){
  263. target_cs.generateLightmapUV = EditorGUILayout.Toggle("Create Lightmap UV", target_cs.generateLightmapUV);
  264. }
  265. GUILayout.Space(5.0f);
  266. GUI.color = color1;
  267. EditorGUILayout.BeginVertical("Box");
  268. if (target_cs.combined != null) {
  269. GUILayout.Label("Vertex count: " + target_cs.vCount + " / 65536", infoStyle);
  270. GUILayout.Label("Material count: " + target_cs.combined.GetComponent<Renderer>().sharedMaterials.Length, infoStyle);
  271. }else{
  272. GUILayout.Label("Vertex count: - / 65536", infoStyle);
  273. GUILayout.Label("Material count: -", infoStyle);
  274. }
  275. GUI.color = Color.white;
  276. EditorGUILayout.EndVertical();
  277. GUIStyle buttonStyle3 = new GUIStyle(GUI.skin.button);
  278. buttonStyle3.fixedWidth = 11.0f;
  279. buttonStyle3.fixedHeight = 14.0f;
  280. buttonStyle3.fontSize = 9;
  281. buttonStyle3.padding = new RectOffset(-2,0,0,0);
  282. EditorGUILayout.BeginHorizontal();
  283. GUILayout.FlexibleSpace();
  284. if (GUILayout.Button("S", buttonStyle3)) {
  285. ToggleUnitySkin();
  286. }
  287. EditorGUILayout.EndHorizontal();
  288. if (GUI.changed) {
  289. EditorUtility.SetDirty(target_cs);
  290. }
  291. }
  292. public void DestroyComponentsExeptColliders(Transform t){
  293. var target_cs = (SimpleMeshCombine)target;
  294. Component[] transforms = t.GetComponentsInChildren(typeof(Transform));
  295. foreach(Transform trans in transforms){
  296. if(!target_cs.keepStructure && trans.transform.parent != t && trans.transform != t && (trans.GetComponent(typeof(Collider)) != null)){
  297. trans.transform.name = ""+ GetParentStructure(t, trans.transform);
  298. trans.transform.parent = t;
  299. }
  300. }
  301. Component[] components = t.GetComponentsInChildren(typeof(Component));
  302. foreach(Component comp in components){
  303. if( !( comp is Collider) && !( comp is Transform) ){
  304. DestroyImmediate(comp);
  305. }
  306. }
  307. }
  308. public string GetParentStructure(Transform root,Transform t){
  309. Transform ct = t;
  310. string s = "";
  311. while(ct !=root ){
  312. s = s.Insert(0, ct.name + " - ");
  313. ct = ct.parent;
  314. }
  315. s = s.Remove(s.Length-3, 3);
  316. return s;
  317. }
  318. public void DestroyEmptyGameObjects(Transform t){
  319. Component[] components = t.GetComponentsInChildren(typeof(Transform));
  320. foreach(Transform comp in components){
  321. if((comp != null) && (comp.childCount == 0 || !CheckChildrenForColliders(comp))){
  322. Collider col = (Collider)comp.GetComponent(typeof(Collider));
  323. if(col == null){
  324. DestroyImmediate(comp.gameObject);
  325. }
  326. }
  327. }
  328. }
  329. public bool CheckChildrenForColliders(Transform t){
  330. Component[] components = t.GetComponentsInChildren(typeof(Collider));
  331. if(components.Length > 0){
  332. return true;
  333. }
  334. return false;
  335. }
  336. public void CopyMaterials(Transform t){
  337. var target_cs = (SimpleMeshCombine)target;
  338. Renderer r = t.GetComponent<Renderer>();
  339. r.sharedMaterials = target_cs.combined.transform.GetComponent<Renderer>().sharedMaterials;
  340. }
  341. public void CopyColliders(){
  342. var target_cs = (SimpleMeshCombine)target;
  343. GameObject clone = (GameObject)Instantiate(target_cs.gameObject, target_cs.copyTarget.transform.position, target_cs.copyTarget.transform.rotation);
  344. if(target_cs.destroyOldColliders){
  345. Transform o = target_cs.copyTarget.transform.Find("Colliders [SMC]");
  346. if(o != null){
  347. DestroyImmediate(o.gameObject);
  348. }
  349. }
  350. clone.transform.name = "Colliders [SMC]";
  351. clone.transform.parent = target_cs.copyTarget.transform;
  352. DestroyComponentsExeptColliders(clone.transform);
  353. DestroyEmptyGameObjects(clone.transform);
  354. }
  355. }