using System; using System.IO; using System.Collections.Generic; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Experimental.TerrainAPI; using UnityEngine.Rendering; using UnityEngine.UI; namespace UnityEditor.Experimental.TerrainAPI { [Serializable] public class Vector3Serializable { public float x; public float y; public float z; public void Fill(Vector3 v) { x = v.x; y = v.y; z = v.z; } public Vector3 V { get { return new Vector3(x, y, z); } set { Fill(value); } } } [Serializable] public class ColorSerializable { public float r; public float g; public float b; public float a; public void Fill(Color c) { r = c.r; g = c.g; b = c.b; a = c.a; } public Color C { get { return new Color(r, g, b, a); } set { Fill(value); } } } [Serializable] public class LayerSerializable { public string LayerPath; public bool IsSelected; public void Fill(Layer layer) { LayerPath = AssetDatabase.GetAssetPath(layer.AssignedLayer); IsSelected = layer.IsSelected; } public Layer L { get { return ScriptableObject.CreateInstance(); } set { Fill(value); } } } public class ToolboxHelper { // Gizmo public static bool GizmoEnabled = false; public static TerrainGizmos GizmoSettings; public static GameObject GizmoGO = null; // Toolbox setting serialization public static string LibraryPath = "/../Library/TerrainTools/"; public static string ToolboxPrefsWindow = "ToolboxWindowPrefs"; public static string ToolboxPrefsCreate = "ToolboxCreatePrefs"; public static string ToolboxPrefsSettings = "ToolboxSettingsPrefs"; public static string ToolboxPrefsUtility = "ToolboxUtilityPrefs"; public static string ToolboxPrefsVisualization = "ToolboxVisualizationPrefs"; public enum ByteOrder { Mac = 1, Windows = 2 }; public enum RenderPipeline { None, HD, LW, Universal } public static int[] GUITextureResolutions = new int[] { 32, 64, 128, 256, 512, 1024, 2048, 4096 }; public static string[] GUITextureResolutionNames = new string[] { "32", "64", "128", "256", "512", "1024", "2048", "4096" }; public static int[] GUIHeightmapResolutions = new int[] { 33, 65, 129, 257, 513, 1025, 2049, 4097 }; public static string[] GUIHeightmapResolutionNames = new string[] { "33", "65", "129", "257", "513", "1025", "2049", "4097" }; const string GizmoGOName = "TERRAIN_GIZMO"; public static bool IsPowerOfTwo(int x) { return (x != 0) && ((x & (x - 1)) == 0); } public static bool IsInteger(double x) { return (x % 1) == 0; } public static string GetPrefFilePath(string prefType) { string filePath = string.Empty; string dirPath = Application.dataPath + LibraryPath; if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } filePath = dirPath + prefType + ".json"; return filePath; } public static string GetBitDepth(Heightmap.Depth depth) { switch (depth) { case Heightmap.Depth.Bit16: return "16 bit"; case Heightmap.Depth.Bit8: return "8 bit"; default: return "8 bit"; } } public static Terrain[] GetSelectedTerrainsInScene() { var objs = Selection.GetFiltered(typeof(Terrain), SelectionMode.Unfiltered); var terrains = new Terrain[objs.Length]; for (var i = 0; i < objs.Length; i++) { terrains[i] = objs[i] as Terrain; } return terrains; } public static Terrain[] GetAllTerrainsInScene() { return GameObject.FindObjectsOfType(); } public static void CalculateAdjacencies(Terrain[] terrains, int tilesX, int tilesZ) { if (terrains == null || terrains.Length == 0) { return; } // set neighbor terrains to update normal maps for (int y = 0; y < tilesZ; y++) { for (int x = 0; x < tilesX; x++) { int index = (y * tilesX) + x; Terrain terrain = terrains[index]; Terrain leftTerrain = (x > 0) ? terrains[index - 1] : null; Terrain rightTerrain = (x < tilesX - 1) ? terrains[index + 1] : null; Terrain topTerrain = (y > 0) ? terrains[index - tilesX] : null; Terrain bottomTerrain = (y < tilesZ - 1) ? terrains[index + tilesX] : null; // NOTE: "top" and "bottom" are reversed because of the way the terrain is handled... terrain.SetNeighbors(leftTerrain, bottomTerrain, rightTerrain, topTerrain); } } } public static Texture2D GetPartialTexture(Texture2D sourceTexture, Vector2Int resolution, Vector2Int offset) { if (offset.x > resolution.x || offset.y > resolution.y) return null; var destColor = sourceTexture.GetPixels(offset.x, offset.y, resolution.x, resolution.y); Texture2D newTexture = new Texture2D(resolution.x, resolution.y); newTexture.SetPixels(destColor); return newTexture; } // referencing from TerrainInspector.ResizeControltexture() public static void ResizeControlTexture(TerrainData terrainData, int resolution) { RenderTexture oldRT = RenderTexture.active; RenderTexture[] oldAlphaMaps = new RenderTexture[terrainData.alphamapTextureCount]; for (int i = 0; i < oldAlphaMaps.Length; i++) { terrainData.alphamapTextures[i].filterMode = FilterMode.Bilinear; oldAlphaMaps[i] = RenderTexture.GetTemporary(resolution, resolution, 0, SystemInfo.GetGraphicsFormat(DefaultFormat.HDR)); Graphics.Blit(terrainData.alphamapTextures[i], oldAlphaMaps[i]); } Undo.RegisterCompleteObjectUndo(terrainData, "Resize alphamap"); terrainData.alphamapResolution = resolution; for (int i = 0; i < oldAlphaMaps.Length; i++) { RenderTexture.active = oldAlphaMaps[i]; CopyActiveRenderTextureToTexture(terrainData.GetAlphamapTexture(i), new RectInt(0, 0, resolution, resolution), Vector2Int.zero, false); } terrainData.SetBaseMapDirty(); RenderTexture.active = oldRT; for (int i = 0; i < oldAlphaMaps.Length; i++) { RenderTexture.ReleaseTemporary(oldAlphaMaps[i]); } terrainData.SetBaseMapDirty(); } // referencing from TerrainData.GPUCopy.CopyActiveRenderTextureToTexture() public static void CopyActiveRenderTextureToTexture(Texture2D dstTexture, RectInt sourceRect, Vector2Int dest, bool allowDelayedCPUSync) { var source = RenderTexture.active; if (source == null) throw new InvalidDataException("Active RenderTexture is null."); int dstWidth = dstTexture.width; int dstHeight = dstTexture.height; allowDelayedCPUSync = allowDelayedCPUSync && SupportsCopyTextureBetweenRTAndTexture; if (allowDelayedCPUSync) { if (dstTexture.mipmapCount > 1) { var tmp = RenderTexture.GetTemporary(new RenderTextureDescriptor(dstWidth, dstHeight, source.format)); if (!tmp.IsCreated()) { tmp.Create(); } Graphics.CopyTexture(dstTexture, 0, 0, tmp, 0, 0); Graphics.CopyTexture(source, 0, 0, sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height, tmp, 0, 0, dest.x, dest.y); tmp.GenerateMips(); Graphics.CopyTexture(tmp, dstTexture); RenderTexture.ReleaseTemporary(tmp); } else { Graphics.CopyTexture(source, 0, 0, sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height, dstTexture, 0, 0, dest.x, dest.y); } } else { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal || !SystemInfo.graphicsUVStartsAtTop) dstTexture.ReadPixels(new Rect(sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height), dest.x, dest.y); else dstTexture.ReadPixels(new Rect(sourceRect.x, source.height - sourceRect.yMax, sourceRect.width, sourceRect.height), dest.x, dest.y); dstTexture.Apply(true); } } private static bool SupportsCopyTextureBetweenRTAndTexture { get { const CopyTextureSupport kRT2TexAndTex2RT = CopyTextureSupport.RTToTexture | CopyTextureSupport.TextureToRT; return (SystemInfo.copyTextureSupport & kRT2TexAndTex2RT) == kRT2TexAndTex2RT; } } public static float kNormalizedHeightScale => 32766.0f / 65535.0f; public static void CopyTextureToTerrainHeight(TerrainData terrainData, Texture2D heightmap, Vector2Int indexOffset, int resolution, int numTiles, float baseLevel, float remap) { terrainData.heightmapResolution = resolution + 1; float hWidth = heightmap.height; float div = hWidth / numTiles; float scale = ((resolution / (resolution + 1.0f)) * (div + 1)) / hWidth; float offset = ((resolution / (resolution + 1.0f)) * div) / hWidth; Vector2 scaleV = new Vector2(scale, scale); Vector2 offsetV = new Vector2(offset * indexOffset.x, offset * indexOffset.y); Material blitMaterial = GetHeightBlitMaterial(); blitMaterial.SetFloat("_Height_Offset", baseLevel * kNormalizedHeightScale); blitMaterial.SetFloat("_Height_Scale", remap * kNormalizedHeightScale); RenderTexture heightmapRT = RenderTexture.GetTemporary(terrainData.heightmapTexture.descriptor); Graphics.Blit(heightmap, heightmapRT, blitMaterial); Graphics.Blit(heightmapRT, terrainData.heightmapTexture, scaleV, offsetV); terrainData.DirtyHeightmapRegion(new RectInt(0, 0, terrainData.heightmapTexture.width, terrainData.heightmapTexture.height), TerrainHeightmapSyncControl.HeightAndLod); } public static void ResizeHeightmap(TerrainData terrainData, int resolution) { RenderTexture oldRT = RenderTexture.active; RenderTexture oldHeightmap = RenderTexture.GetTemporary(terrainData.heightmapTexture.descriptor); Graphics.Blit(terrainData.heightmapTexture, oldHeightmap); #if UNITY_2019_3_OR_NEWER // terrain holes RenderTexture oldHoles = RenderTexture.GetTemporary(terrainData.holesTexture.width, terrainData.holesTexture.height); Graphics.Blit(terrainData.holesTexture, oldHoles); #endif Undo.RegisterCompleteObjectUndo(terrainData, "Resize heightmap"); float sUV = 1.0f; int dWidth = terrainData.heightmapResolution; int sWidth = resolution; Vector3 oldSize = terrainData.size; terrainData.heightmapResolution = resolution; terrainData.size = oldSize; oldHeightmap.filterMode = FilterMode.Bilinear; // Make sure textures are offset correctly when resampling // tsuv = (suv * swidth - 0.5) / (swidth - 1) // duv = (tsuv(dwidth - 1) + 0.5) / dwidth // duv = (((suv * swidth - 0.5) / (swidth - 1)) * (dwidth - 1) + 0.5) / dwidth // k = (dwidth - 1) / (swidth - 1) / dwidth // duv = suv * (swidth * k) + 0.5 / dwidth - 0.5 * k float k = (dWidth - 1.0f) / (sWidth - 1.0f) / dWidth; float scaleX = sUV * (sWidth * k); float offsetX = (float)(0.5 / dWidth - 0.5 * k); Vector2 scale = new Vector2(scaleX, scaleX); Vector2 offset = new Vector2(offsetX, offsetX); Graphics.Blit(oldHeightmap, terrainData.heightmapTexture, scale, offset); RenderTexture.ReleaseTemporary(oldHeightmap); #if UNITY_2019_3_OR_NEWER oldHoles.filterMode = FilterMode.Point; Graphics.Blit(oldHoles, (RenderTexture)terrainData.holesTexture); RenderTexture.ReleaseTemporary(oldHoles); #endif RenderTexture.active = oldRT; terrainData.DirtyHeightmapRegion(new RectInt(0, 0, terrainData.heightmapTexture.width, terrainData.heightmapTexture.height), TerrainHeightmapSyncControl.HeightAndLod); #if UNITY_2019_3_OR_NEWER terrainData.DirtyTextureRegion(TerrainData.HolesTextureName, new RectInt(0, 0, terrainData.holesTexture.width, terrainData.holesTexture.height), false); #endif } // Unity PNG encoder does not support 16bit export, change will come later 2019 public static void ExportTerrainHeightsToTexture(TerrainData terrainData, Heightmap.Format format, string path, bool flipVertical, Vector2 inputLevelsRange) { RenderTexture oldRT = RenderTexture.active; int width = terrainData.heightmapTexture.width - 1; int height = terrainData.heightmapTexture.height - 1; var texture = new Texture2D(width, height, terrainData.heightmapTexture.graphicsFormat, TextureCreationFlags.None); RenderTexture.active = terrainData.heightmapTexture; texture.ReadPixels(new Rect(0, 0, width, height), 0, 0); //Remap Texture Color[] pixels = texture.GetPixels(); for (int i = 0; i < pixels.Length; i += 4) { pixels[i].r = (pixels[i].r * 2) * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; pixels[i + 1].r = (pixels[i + 1].r * 2) * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; pixels[i + 2].r = (pixels[i + 2].r * 2) * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; pixels[i + 3].r = (pixels[i + 3].r * 2) * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; } texture.SetPixels(pixels); texture.Apply(); //Flip Texture if(flipVertical) ToolboxHelper.FlipTexture(texture, true); byte[] bytes; switch (format) { case Heightmap.Format.TGA: bytes = texture.EncodeToTGA(); path = path + ".tga"; break; default: bytes = texture.EncodeToPNG(); path = path + ".png"; break; } File.WriteAllBytes(path, bytes); RenderTexture.active = oldRT; } public static void ExportTerrainHeightsToRawFile(TerrainData terrainData, string path, Heightmap.Depth depth, bool flipVertical, ByteOrder byteOrder, Vector2 inputLevelsRange) { // trim off the extra 1 pixel, so we get a power of two sized texture #if UNITY_2019_3_OR_NEWER int heightmapWidth = terrainData.heightmapResolution - 1; int heightmapHeight = terrainData.heightmapResolution - 1; #else int heightmapWidth = terrainData.heightmapWidth - 1; int heightmapHeight = terrainData.heightmapHeight - 1; #endif float[,] heights = terrainData.GetHeights(0, 0, heightmapWidth, heightmapHeight); byte[] data = new byte[heightmapWidth * heightmapHeight * (int)depth]; if (depth == Heightmap.Depth.Bit16) { float normalize = (1 << 16); for (int y = 0; y < heightmapHeight; ++y) { for (int x = 0; x < heightmapWidth; ++x) { int index = x + y * heightmapWidth; int srcY = flipVertical ? heightmapHeight - 1 - y : y; float remappedHeight = heights[srcY, x] * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; int height = Mathf.RoundToInt(remappedHeight * normalize); ushort compressedHeight = (ushort)Mathf.Clamp(height, 0, ushort.MaxValue); byte[] byteData = System.BitConverter.GetBytes(compressedHeight); if ((byteOrder == ByteOrder.Mac) == System.BitConverter.IsLittleEndian) { data[index * 2 + 0] = byteData[1]; data[index * 2 + 1] = byteData[0]; } else { data[index * 2 + 0] = byteData[0]; data[index * 2 + 1] = byteData[1]; } } } } else { float normalize = (1 << 8); for (int y = 0; y < heightmapHeight; ++y) { for (int x = 0; x < heightmapWidth; ++x) { int index = x + y * heightmapWidth; int srcY = flipVertical ? heightmapHeight - 1 - y : y; float remappedHeight = heights[y, x] * (inputLevelsRange.y - inputLevelsRange.x) + inputLevelsRange.x; int height = Mathf.RoundToInt(remappedHeight * normalize); byte compressedHeight = (byte)Mathf.Clamp(height, 0, byte.MaxValue); data[index] = compressedHeight; } } } FileStream fs = new FileStream((path + ".raw"), FileMode.Create); fs.Write(data, 0, data.Length); fs.Close(); } public static Material GetHeightBlitMaterial() { return new Material(Shader.Find("Hidden/TerrainTools/HeightBlit")); } public static void FlipTexture(Texture2D texture, bool isHorizontal) { Color[] originalPixels = texture.GetPixels(); Color[] flippedPixels = new Color[originalPixels.Length]; int width = texture.width; int height = texture.height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int flippedIndex = isHorizontal ? y * width + width - 1 - x : (height-1)*width - y * width + x; int originalIndex = y * width + x; if (flippedIndex < 0) continue; flippedPixels[flippedIndex] = originalPixels[originalIndex]; } } texture.SetPixels(flippedPixels); texture.Apply(); } public static GameObject GetGizmo() { var obj = GameObject.Find(GizmoGOName); if (obj == null) { // create the gizmo GO GizmoGO = new GameObject(GizmoGOName); GizmoSettings = GizmoGO.AddComponent() as TerrainGizmos; } else { GizmoGO = obj; } return GizmoGO; } public static void ShowGizmo() { if (GizmoGO == null) { GetGizmo(); } GizmoGO.SetActive(true); GizmoSettings = GizmoGO.GetComponent(); GizmoEnabled = true; } public static void UpdateGizmos(float width, float height, float length, Vector3 position, int id) { if (GizmoGO == null || GizmoSettings == null) return; Vector3 centerPosition = new Vector3(width / 2, height / 2, length / 2); GizmoGO.transform.position = centerPosition + position; Vector3 cubeScale = new Vector3(width, height, length); GizmoGO.transform.localScale = cubeScale; GizmoSettings.GroupID = id; } public static void SetGizmoColor(Color cubeColor, Color wireColor) { if (GizmoGO == null || GizmoSettings == null) return; GizmoSettings.CubeColor = cubeColor; GizmoSettings.CubeWireColor = wireColor; } public static Vector3 GetGizmoPosition() { if (GizmoGO == null || GizmoSettings == null) return Vector3.zero; Vector3 centerPosition = new Vector3(GizmoGO.transform.localScale.x / 2, GizmoGO.transform.localScale.y / 2, GizmoGO.transform.localScale.z / 2); return GizmoGO.transform.position - centerPosition; } public static void HideGizmo() { GizmoEnabled = false; if (GizmoGO == null) return; UnityEngine.Object.DestroyImmediate(GizmoGO); } public static RenderPipeline GetRenderPipeline() { if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset == null) { return RenderPipeline.None; } else if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().FullName == "UnityEngine.Rendering.HighDefinition.HDRenderPipelineAsset") { return RenderPipeline.HD; } else if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().FullName == "UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset") { return RenderPipeline.Universal; } else if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().FullName == "UnityEngine.Rendering.LWRP.LightweightRenderPipelineAsset") { return RenderPipeline.LW; } return RenderPipeline.None; } } }