ExportTerrain.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System;
  4. using System.Collections;
  5. using System.IO;
  6. using System.Text;
  7. enum SaveFormat { Triangles, Quads }
  8. enum SaveResolution { Full = 0, Half, Quarter, Eighth, Sixteenth }
  9. public class ExportTerrain : EditorWindow
  10. {
  11. SaveFormat saveFormat = SaveFormat.Triangles;
  12. SaveResolution saveResolution = SaveResolution.Half;
  13. static TerrainData terrain;
  14. static Vector3 terrainPos;
  15. int tCount;
  16. int counter;
  17. int totalCount;
  18. int progressUpdateInterval = 10000;
  19. [MenuItem("Terrain/Export To Obj...")]
  20. static void Init()
  21. {
  22. terrain = null;
  23. Terrain terrainObject = Selection.activeObject as Terrain;
  24. if (!terrainObject)
  25. {
  26. terrainObject = Terrain.activeTerrain;
  27. }
  28. if (terrainObject)
  29. {
  30. terrain = terrainObject.terrainData;
  31. terrainPos = terrainObject.transform.position;
  32. }
  33. EditorWindow.GetWindow<ExportTerrain>().Show();
  34. }
  35. void OnGUI()
  36. {
  37. if (!terrain)
  38. {
  39. GUILayout.Label("No terrain found");
  40. if (GUILayout.Button("Cancel"))
  41. {
  42. EditorWindow.GetWindow<ExportTerrain>().Close();
  43. }
  44. return;
  45. }
  46. saveFormat = (SaveFormat)EditorGUILayout.EnumPopup("Export Format", saveFormat);
  47. saveResolution = (SaveResolution)EditorGUILayout.EnumPopup("Resolution", saveResolution);
  48. if (GUILayout.Button("Export"))
  49. {
  50. Export();
  51. }
  52. }
  53. void Export()
  54. {
  55. string fileName = EditorUtility.SaveFilePanel("Export .obj file", "", "Terrain", "obj");
  56. int w = terrain.heightmapResolution;
  57. int h = terrain.heightmapResolution;
  58. Vector3 meshScale = terrain.size;
  59. int tRes = (int)Mathf.Pow(2, (int)saveResolution);
  60. meshScale = new Vector3(meshScale.x / (w - 1) * tRes, meshScale.y, meshScale.z / (h - 1) * tRes);
  61. Vector2 uvScale = new Vector2(1.0f / (w - 1), 1.0f / (h - 1));
  62. float[,] tData = terrain.GetHeights(0, 0, w, h);
  63. w = (w - 1) / tRes + 1;
  64. h = (h - 1) / tRes + 1;
  65. Vector3[] tVertices = new Vector3[w * h];
  66. Vector2[] tUV = new Vector2[w * h];
  67. int[] tPolys;
  68. if (saveFormat == SaveFormat.Triangles)
  69. {
  70. tPolys = new int[(w - 1) * (h - 1) * 6];
  71. }
  72. else
  73. {
  74. tPolys = new int[(w - 1) * (h - 1) * 4];
  75. }
  76. // Build vertices and UVs
  77. for (int y = 0; y < h; y++)
  78. {
  79. for (int x = 0; x < w; x++)
  80. {
  81. tVertices[y * w + x] = Vector3.Scale(meshScale, new Vector3(-y, tData[x * tRes, y * tRes], x)) + terrainPos;
  82. tUV[y * w + x] = Vector2.Scale(new Vector2(x * tRes, y * tRes), uvScale);
  83. }
  84. }
  85. int index = 0;
  86. if (saveFormat == SaveFormat.Triangles)
  87. {
  88. // Build triangle indices: 3 indices into vertex array for each triangle
  89. for (int y = 0; y < h - 1; y++)
  90. {
  91. for (int x = 0; x < w - 1; x++)
  92. {
  93. // For each grid cell output two triangles
  94. tPolys[index++] = (y * w) + x;
  95. tPolys[index++] = ((y + 1) * w) + x;
  96. tPolys[index++] = (y * w) + x + 1;
  97. tPolys[index++] = ((y + 1) * w) + x;
  98. tPolys[index++] = ((y + 1) * w) + x + 1;
  99. tPolys[index++] = (y * w) + x + 1;
  100. }
  101. }
  102. }
  103. else
  104. {
  105. // Build quad indices: 4 indices into vertex array for each quad
  106. for (int y = 0; y < h - 1; y++)
  107. {
  108. for (int x = 0; x < w - 1; x++)
  109. {
  110. // For each grid cell output one quad
  111. tPolys[index++] = (y * w) + x;
  112. tPolys[index++] = ((y + 1) * w) + x;
  113. tPolys[index++] = ((y + 1) * w) + x + 1;
  114. tPolys[index++] = (y * w) + x + 1;
  115. }
  116. }
  117. }
  118. // Export to .obj
  119. StreamWriter sw = new StreamWriter(fileName);
  120. try
  121. {
  122. sw.WriteLine("# Unity terrain OBJ File");
  123. // Write vertices
  124. System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
  125. counter = tCount = 0;
  126. totalCount = (tVertices.Length * 2 + (saveFormat == SaveFormat.Triangles ? tPolys.Length / 3 : tPolys.Length / 4)) / progressUpdateInterval;
  127. for (int i = 0; i < tVertices.Length; i++)
  128. {
  129. UpdateProgress();
  130. StringBuilder sb = new StringBuilder("v ", 20);
  131. // StringBuilder stuff is done this way because it's faster than using the "{0} {1} {2}"etc. format
  132. // Which is important when you're exporting huge terrains.
  133. sb.Append(tVertices[i].x.ToString()).Append(" ").
  134. Append(tVertices[i].y.ToString()).Append(" ").
  135. Append(tVertices[i].z.ToString());
  136. sw.WriteLine(sb);
  137. }
  138. // Write UVs
  139. for (int i = 0; i < tUV.Length; i++)
  140. {
  141. UpdateProgress();
  142. StringBuilder sb = new StringBuilder("vt ", 22);
  143. sb.Append(tUV[i].x.ToString()).Append(" ").
  144. Append(tUV[i].y.ToString());
  145. sw.WriteLine(sb);
  146. }
  147. if (saveFormat == SaveFormat.Triangles)
  148. {
  149. // Write triangles
  150. for (int i = 0; i < tPolys.Length; i += 3)
  151. {
  152. UpdateProgress();
  153. StringBuilder sb = new StringBuilder("f ", 43);
  154. sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
  155. Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append(" ").
  156. Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1);
  157. sw.WriteLine(sb);
  158. }
  159. }
  160. else
  161. {
  162. // Write quads
  163. for (int i = 0; i < tPolys.Length; i += 4)
  164. {
  165. UpdateProgress();
  166. StringBuilder sb = new StringBuilder("f ", 57);
  167. sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
  168. Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append(" ").
  169. Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1).Append(" ").
  170. Append(tPolys[i + 3] + 1).Append("/").Append(tPolys[i + 3] + 1);
  171. sw.WriteLine(sb);
  172. }
  173. }
  174. }
  175. catch (Exception err)
  176. {
  177. Debug.Log("Error saving file: " + err.Message);
  178. }
  179. sw.Close();
  180. terrain = null;
  181. EditorUtility.DisplayProgressBar("Saving file to disc.", "This might take a while...", 1f);
  182. EditorWindow.GetWindow<ExportTerrain>().Close();
  183. EditorUtility.ClearProgressBar();
  184. }
  185. void UpdateProgress()
  186. {
  187. if (counter++ == progressUpdateInterval)
  188. {
  189. counter = 0;
  190. EditorUtility.DisplayProgressBar("Saving...", "", Mathf.InverseLerp(0, totalCount, ++tCount));
  191. }
  192. }
  193. }