/* Tom's Terrain Tools for Unity 3D Version 2.6 (2015) (C)2015 by Tom Vogt & Unitycoder.com http://lemuria.org/Unity/TTT/ http://unitycoder.com/blog/2014/08/14/asset-store-terrain-tools/ */ using UnityEditor; using UnityEngine; using System.Collections; using System.IO; namespace TTT { public class TerrainTools : EditorWindow { public Terrain myTerrain; private Object splatMap; private bool defaultsDone = false; private Texture2D[] splatTextures = new Texture2D[4]; private int tileSizeX=8; private int tileSizeY=8; private Texture2D[] splatTexNormals = new Texture2D[4]; private GameObject[] treeObjects = new GameObject[3]; private Texture2D[] grassTextures = new Texture2D[3]; private GameObject[] detailObjects = new GameObject[3]; // bushes, stones public Texture2D splatA; public Texture2D splatB; public Texture2D treemap; public bool resetTrees = true; public float treeDensity = 0.4f; public float treeThreshold = 0.1f; public float treeSize = 1f; public float sizeVariation = 0.2f; public Texture2D grassmap; public Texture2D bushmap; public float grassDensity = 0.15f; public float grassclumping = 0.5f; public float bushDensity = 0.02f; public Texture2D overlayMap; public float overlayThreshold = 0.1f; public Texture2D overlayTexture; public int tileSize = 15; public bool clearTrees = false; public float clearRadius = 1.0f; public bool clearGrass = true; public float changeTerrain = 0.0f; // internal variables GUIStyle boldTitleStyle; Vector2 scrollPosition = new Vector2(0,0); bool toggleSplat = true; bool toggleTrees = true; bool toggleGrass = true; bool toggleOverlay=false; bool toggleAutoMagic=false; bool toggleAdvanced=false; // default terrain settings int terrainInitWidth=2000; int terrainInitLength=2000; int terrainInitHeight=200; float windSettingsSpeed = 0.2f; float windSettingsSize = 0.2f; float windSettingsBending = 0.2f; bool assignNormalMapsIfFounded=true; // checks if texture has normals map in same folder: texturename.png & texturename_normal.png string[] texturePackOptionTitles = new string[] {"Default", "Plain colors#1", "Debug"}; int selectedTexturePack = 0; // which texture pack is selected (file names are hardcoded at AutoMagicDefaults() System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); // timer for timing method durations bool timeStampFileNames=false; // if enabled, every time you generate, new terrain is saved with a new name (as new terrain) [MenuItem("Window/Terrain Tools/Terrain Tools",false,0)] static void Init() { TerrainTools window = (TerrainTools)EditorWindow.GetWindow(typeof(TerrainTools)); window.titleContent = new GUIContent("TerrainTools"); window.minSize = new Vector2(410,400); window.Show(); } void OnInspectorUpdate() { Repaint(); } void OnGUI() { EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); DrawTitleGUI(); boldTitleStyle = new GUIStyle(EditorStyles.foldout); boldTitleStyle.fontStyle = FontStyle.Bold; EditorGUILayout.Separator(); GUILayout.BeginHorizontal(); GUILayout.Label("Terrain"); myTerrain = (Terrain)EditorGUILayout.ObjectField("", myTerrain, typeof(Terrain),true); GUILayout.EndHorizontal(); EditorGUILayout.Separator(); DrawAutoMagicGUI(); DrawTexturingGUI(); DrawTreeDistributionGUI(); DrawGrassGUI(); DrawOverlayGUI(); EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } static void DrawTitleGUI () { GUILayout.BeginHorizontal (); GUILayout.Label ("Terrain Tools v2.6", EditorStyles.boldLabel); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Read Manual")) { Help.BrowseURL ("http://lemuria.org/Unity/TTT/"); } GUILayout.EndHorizontal (); EditorGUILayout.Separator (); } void DrawAutoMagicGUI() { toggleAutoMagic = EditorGUILayout.Foldout (toggleAutoMagic, "AutoMagic", boldTitleStyle); if (toggleAutoMagic) { if (!defaultsDone) AutoMagicDefaults (); GUILayout.BeginHorizontal (); splatMap = EditorGUILayout.ObjectField (new GUIContent ("Splatmap Object", "Heightmap, Splatmap, Treemap, Grassmap"), splatMap, typeof(Object), false); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel("Textures"); GUILayout.FlexibleSpace(); splatTextures[0] = (Texture2D)EditorGUILayout.ObjectField (splatTextures [0], typeof(Texture2D), false, GUILayout.Height (48)); splatTextures[1] = (Texture2D)EditorGUILayout.ObjectField (splatTextures [1], typeof(Texture2D), false, GUILayout.Height (48)); splatTextures[2] = (Texture2D)EditorGUILayout.ObjectField (splatTextures [2], typeof(Texture2D), false, GUILayout.Height (48)); splatTextures[3] = (Texture2D)EditorGUILayout.ObjectField (splatTextures [3], typeof(Texture2D), false, GUILayout.Height (48)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel("Trees"); GUILayout.FlexibleSpace(); treeObjects[0] = (GameObject)EditorGUILayout.ObjectField (treeObjects [0], typeof(GameObject), false); treeObjects[1] = (GameObject)EditorGUILayout.ObjectField (treeObjects [1], typeof(GameObject), false); treeObjects[2] = (GameObject)EditorGUILayout.ObjectField (treeObjects [2], typeof(GameObject), false); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel ("Grass"); GUILayout.FlexibleSpace (); grassTextures[0] = (Texture2D)EditorGUILayout.ObjectField (grassTextures [0], typeof(Texture2D), false, GUILayout.Height (32)); grassTextures[1] = (Texture2D)EditorGUILayout.ObjectField (grassTextures [1], typeof(Texture2D), false, GUILayout.Height (32)); grassTextures[2] = (Texture2D)EditorGUILayout.ObjectField (grassTextures [2], typeof(Texture2D), false, GUILayout.Height (32)); GUILayout.EndHorizontal(); EditorGUILayout.Separator(); GUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel("Detail meshes"); GUILayout.FlexibleSpace(); detailObjects[0] = (GameObject)EditorGUILayout.ObjectField (detailObjects[0], typeof(GameObject), false); detailObjects[1] = (GameObject)EditorGUILayout.ObjectField (detailObjects[1], typeof(GameObject), false); detailObjects[2] = (GameObject)EditorGUILayout.ObjectField (detailObjects[2], typeof(GameObject), false); GUILayout.EndHorizontal(); EditorGUILayout.Separator(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button ("Run AutoMagic", GUILayout.Width (200), GUILayout.Height (32))) { AutoMagic(); } GUILayout.FlexibleSpace (); GUILayout.EndHorizontal (); toggleAdvanced = EditorGUILayout.Foldout(toggleAdvanced, "Advanced Settings", boldTitleStyle); if (toggleAdvanced) { // texture pack selection GUI.changed = false; selectedTexturePack = EditorGUILayout.Popup("Texture pack",selectedTexturePack, texturePackOptionTitles); if (GUI.changed) { AutoMagicDefaults(); // reload texture on change }else{ } GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Apply Textures to Terrain", GUILayout.Width (200), GUILayout.Height (22))) { ApplySelectedTexturesToTerrain(); } GUILayout.EndHorizontal (); EditorGUILayout.Separator(); // create timestamped filenames for terrain (unique terrains for each generation) timeStampFileNames = EditorGUILayout.Toggle("Unique terrain files",timeStampFileNames); EditorGUILayout.Separator(); // terrain default settings terrainInitWidth = EditorGUILayout.IntSlider ("Terrain Width",terrainInitWidth,100,8000); terrainInitLength = EditorGUILayout.IntSlider ("Terrain Length",terrainInitLength,100,8000); terrainInitHeight = EditorGUILayout.IntSlider ("Terrain Height",terrainInitHeight,100,8000); tileSizeY = EditorGUILayout.IntField("Texture Tiling Y",(int)Mathf.Clamp(tileSizeY,1,9999)); tileSizeX = EditorGUILayout.IntField("Texture Tiling X",(int)Mathf.Clamp(tileSizeX,1,9999)); EditorGUILayout.Separator(); // misc options assignNormalMapsIfFounded = EditorGUILayout.Toggle("Use normal maps if founded",assignNormalMapsIfFounded); } } GUILayout.FlexibleSpace (); EditorGUILayout.Separator (); } void DrawTexturingGUI () { toggleSplat = EditorGUILayout.Foldout (toggleSplat, "Texturing", boldTitleStyle); if (toggleSplat) { GUILayout.BeginHorizontal (); EditorGUILayout.PrefixLabel ("First Splatmap"); GUILayout.FlexibleSpace (); splatA = (Texture2D)EditorGUILayout.ObjectField ("", splatA, typeof(Texture2D), false); GUILayout.EndHorizontal (); EditorGUILayout.Separator (); GUILayout.BeginHorizontal (); GUILayout.Label ("Second Splatmap (optional)"); GUILayout.FlexibleSpace (); splatB = (Texture2D)EditorGUILayout.ObjectField ("", splatB, typeof(Texture2D), false); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Apply Splatmap(s)", GUILayout.Width (200), GUILayout.Height (32))) { if (CheckSplatmap()) { ApplySplatmap(); }else{ Debug.LogError("No first splatmap assigned"); } } GUILayout.FlexibleSpace (); GUILayout.EndHorizontal (); } EditorGUILayout.Separator (); } void DrawTreeDistributionGUI () { toggleTrees = EditorGUILayout.Foldout (toggleTrees, "Tree Distribution", boldTitleStyle); if (toggleTrees) { GUILayout.BeginHorizontal (); GUILayout.Label ("Tree map"); GUILayout.FlexibleSpace (); treemap = (Texture2D)EditorGUILayout.ObjectField ("", treemap, typeof(Texture2D), false); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Clear Existing Trees?"); GUILayout.FlexibleSpace (); resetTrees = EditorGUILayout.Toggle ("remove trees", resetTrees); GUILayout.EndHorizontal (); EditorGUILayout.Separator (); GUILayout.BeginHorizontal (); GUILayout.Label ("Tree Density"); GUILayout.FlexibleSpace (); treeDensity = EditorGUILayout.Slider (treeDensity, 0.05f, 3f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Threshold"); GUILayout.FlexibleSpace (); treeThreshold = EditorGUILayout.Slider (treeThreshold, 0.01f, 0.99f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Tree Size"); GUILayout.FlexibleSpace (); treeSize = EditorGUILayout.Slider (treeSize, 0.2f, 5f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Size Variation"); GUILayout.FlexibleSpace (); sizeVariation = EditorGUILayout.Slider (sizeVariation, 0f, 1f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Generate Trees", GUILayout.Width (200), GUILayout.Height (32))) { if (CheckTreemap()) ApplyTreemap(); } GUILayout.FlexibleSpace (); GUILayout.EndHorizontal (); } EditorGUILayout.Separator (); } void DrawGrassGUI () { toggleGrass = EditorGUILayout.Foldout (toggleGrass, "Grass and Details", boldTitleStyle); if (toggleGrass) { GUILayout.BeginHorizontal (); GUILayout.Label ("Grass map"); GUILayout.FlexibleSpace (); grassmap = (Texture2D)EditorGUILayout.ObjectField ("", grassmap, typeof(Texture2D), false); GUILayout.EndHorizontal (); EditorGUILayout.Separator (); GUILayout.BeginHorizontal (); GUILayout.Label ("Bush/Detail map"); GUILayout.FlexibleSpace (); bushmap = (Texture2D)EditorGUILayout.ObjectField ("", bushmap, typeof(Texture2D), false); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Grass Density"); GUILayout.FlexibleSpace (); grassDensity = EditorGUILayout.Slider (grassDensity, 0.01f, 3f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Grass Clumping"); GUILayout.FlexibleSpace (); grassclumping = EditorGUILayout.Slider (grassclumping, 0f, 1f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Bush/Detail Density"); GUILayout.FlexibleSpace (); bushDensity = EditorGUILayout.Slider (bushDensity, 0.01f, 2f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Generate Grass and Details", GUILayout.Width (200), GUILayout.Height (32))) { if (CheckGrassmap ()) ApplyGrassmap(); } GUILayout.FlexibleSpace (); GUILayout.EndHorizontal (); } EditorGUILayout.Separator (); } void DrawOverlayGUI () { toggleOverlay = EditorGUILayout.Foldout (toggleOverlay, "Overlays (roads, rivers, etc)", boldTitleStyle); if (toggleOverlay) { GUILayout.BeginHorizontal (); GUILayout.Label ("Overlay map"); GUILayout.FlexibleSpace (); overlayMap = (Texture2D)EditorGUILayout.ObjectField ("", overlayMap, typeof(Texture2D), false); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Threshold"); GUILayout.FlexibleSpace (); overlayThreshold = EditorGUILayout.Slider (overlayThreshold, 0.1f, 0.9f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Overlay texture"); GUILayout.FlexibleSpace (); overlayTexture = (Texture2D)EditorGUILayout.ObjectField ("", overlayTexture, typeof(Texture2D), false); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Tile size"); GUILayout.FlexibleSpace (); tileSize = EditorGUILayout.IntSlider (tileSize, 3, 127); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Clear trees"); GUILayout.FlexibleSpace (); clearTrees = EditorGUILayout.Toggle ("", clearTrees); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Clear tree radius"); GUILayout.FlexibleSpace (); clearRadius = EditorGUILayout.Slider (clearRadius, 0.5f, 10.0f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Clear grass/detail"); GUILayout.FlexibleSpace (); clearGrass = EditorGUILayout.Toggle ("", clearGrass); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Raise/lower terrain"); GUILayout.FlexibleSpace (); changeTerrain = EditorGUILayout.Slider (changeTerrain, -50f, 50f); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.FlexibleSpace (); if (GUILayout.Button ("Generate Overlay", GUILayout.Width (200), GUILayout.Height (32))) { if (CheckOverlaymap()) ApplyOverlaymap (); } GUILayout.FlexibleSpace (); GUILayout.EndHorizontal (); } } public void FixTextureSettings(Texture2D texture) { if (texture==null) {Debug.LogError("FixFormat failed - Texture is null"); return;} string path = AssetDatabase.GetAssetPath(texture); if (string.IsNullOrEmpty(path)) {Debug.LogError("FixFormat failed - Texture path is null"); return;} TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; if (!textureImporter.isReadable) { Debug.Log("File:"+path+" needs fixing: wrong texture format or not marked as read/write allowed"); textureImporter.mipmapEnabled = false; textureImporter.isReadable = true; AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } } public static string Reverse(string text) { if (text == null) return null; // this was posted by petebob as well char[] array = text.ToCharArray(); System.Array.Reverse(array); return new string(array); } public string FindFile(string basename) { string[] extensions = {"tif", "tiff", "png", "jpg", "jpeg","TIF", "TIFF", "PNG", "JPG", "JPEG"}; foreach (string ext in extensions) { string filename = basename + "." + ext; // Debug.Log(filename); if (File.Exists(filename)) { return filename; } } return null; } void AutoMagic() { if (splatMap == null) {Debug.LogError("No splatmap assigned"); return;} if (!ValidateAutomagicTextures()) {Debug.LogError("Not all textures are assigned"); return;} if (!ValidateAutomagicTrees()) {Debug.LogError("Not all trees are assigned"); return;} // TODO: validate grass and bushes? string origPath = AssetDatabase.GetAssetPath(splatMap); string pathrev = Reverse(Path.GetFileNameWithoutExtension(origPath)); string[] parts = pathrev.Split(new char[] { '-' }, 2); if (parts==null || parts.Length<2) {Debug.LogError("Filenames must start with \"number\" and \"-\" character. Example: 1-splatmap.tif"); return;} string basename = Reverse(parts[1]); string dirname = Path.GetDirectoryName(origPath); string fixedPath = Path.Combine(dirname, basename); string splatmap_name = FindFile(fixedPath+"-splatmap"); if (!File.Exists(splatmap_name)) {Debug.LogError("Splatmap file not found:"+splatmap_name+" \n Make sure that your filenames start with \"number\" and \"-\" character. Example: 1-splatmap.tif"); return;} splatA = AssetDatabase.LoadAssetAtPath(splatmap_name, typeof(Texture2D)) as Texture2D; CheckSplatmap(); string treemap_name = FindFile(fixedPath+"-treemap"); if (File.Exists(treemap_name)) { //Debug.LogError("Treemap file not found:"+treemap_name+" \n Make sure that your filenames start with \"number\" and \"-\" character. Example: 1-splatmap.tif"); return;} treemap = AssetDatabase.LoadAssetAtPath(treemap_name, typeof(Texture2D)) as Texture2D; CheckTreemap(); }else{ treemap_name=null; } string grassmap_name = FindFile(fixedPath+"-grassmap"); if (File.Exists(grassmap_name)) { //Debug.LogError("Grassmap file not found:"+grassmap_name+" \n Make sure that your filenames start with \"number\" and \"-\" character. Example: 1-splatmap.tif"); return;} grassmap = AssetDatabase.LoadAssetAtPath(grassmap_name, typeof(Texture2D)) as Texture2D; CheckGrassmap(); }else{ grassmap_name=null; } string bushmap_name = FindFile(fixedPath+"-bushmap"); if (File.Exists(bushmap_name)) { //Debug.LogError("Grassmap file not found:"+grassmap_name+" \n Make sure that your filenames start with \"number\" and \"-\" character. Example: 1-splatmap.tif"); return;} bushmap = AssetDatabase.LoadAssetAtPath(bushmap_name, typeof(Texture2D)) as Texture2D; CheckGrassmap(); }else{ grassmap_name=null; } CreateTerrain(fixedPath, splatA.width); if (myTerrain.terrainData==null) {Debug.LogError("Terrain generation failed.."); return;}; string heightmap_name = fixedPath+"-heightmap.raw"; myTerrain.terrainData.SetHeights(0, 0, ReadRawHeightMap(heightmap_name, splatA.width+1,myTerrain.terrainData.heightmapResolution,myTerrain.terrainData.heightmapResolution)); ApplySelectedTexturesToTerrain(); ApplySplatmap(); if (treemap_name!=null) { myTerrain.terrainData.treePrototypes = SetupTrees(); myTerrain.terrainData.RefreshPrototypes(); ApplyTreemap(); } if (grassmap_name!=null) { myTerrain.terrainData.detailPrototypes = SetupGrassAndDetails(); myTerrain.terrainData.RefreshPrototypes(); ApplyGrassmap(); } /* if (bushmap_name!=null) { myTerrain.terrainData.detailPrototypes = SetupDetailMeshes(); myTerrain.terrainData.RefreshPrototypes(); ApplyBushmap(); }*/ } void AutoMagicDefaults() { // TODO: add normals also, for now just clear them for (int i = 0; i < splatTexNormals.Length; i++) { splatTexNormals[i]=null; } string tempFilePath = ""; string tempFileName = ""; switch (selectedTexturePack) { // Default texture set #1 case 0: tempFilePath = "Assets/Terrain Assets/Textures/"; tempFileName = "Grass (Meadows).jpg"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[0] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "Grass (Hill).jpg"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[1] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "Grass&Rock.jpg"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[2] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "Grass (Muddy).jpg"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[3] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); break; // Default texture set #2 case 1: tempFilePath = "Assets/Terrain Assets/Textures/SingleColor/"; tempFileName = "green_color_tile-1.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[0] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "brown_color_tile-2.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[1] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "lite_green_color_tile-3.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[2] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "lite_green2_color_tile-4.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[3] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); break; // Default texture set #3 case 2: tempFilePath = "Assets/Terrain Assets/Textures/Debug/"; tempFileName = "debug_red.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[0] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "debug_green.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[1] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "debug_blue.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[2] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFileName = "debug_white.png"; if (File.Exists(tempFilePath+tempFileName)) splatTextures[3] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); break; default: Debug.LogError("Invalid selectedTexturePack value : "+selectedTexturePack); break; } // default trees tempFilePath = "Assets/Terrain Assets/Trees Ambient-Occlusion/"; tempFileName = "Alder.fbx"; if (File.Exists(tempFilePath+tempFileName)) treeObjects[0] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); tempFileName = "Sycamore.fbx"; if (File.Exists(tempFilePath+tempFileName)) treeObjects[1] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); tempFileName = "Mimosa.fbx"; if (File.Exists(tempFilePath+tempFileName)) treeObjects[2] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); // default grass tempFilePath = "Assets/Standard Assets/Environment/TerrainAssets/BillboardTextures/"; tempFileName = "GrassFrond01AlbedoAlpha.psd"; if (File.Exists(tempFilePath+tempFileName)) grassTextures[0] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFilePath = "Assets/Terrain Assets/Grass/"; tempFileName = "Fern.psd"; if (File.Exists(tempFilePath+tempFileName)) grassTextures[1] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); tempFilePath = "Assets/Standard Assets/Environment/TerrainAssets/BillboardTextures/"; tempFileName = "GrassFrond02AlbedoAlpha.psd"; if (File.Exists(tempFilePath+tempFileName)) grassTextures[2] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(Texture2D)); // default detail meshes tempFilePath = "Assets/Terrain Assets/Bushes/"; tempFileName = "Bush5.fbx"; if (File.Exists(tempFilePath+tempFileName)) { detailObjects[0] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); }else{ Debug.LogWarning("Default object missing : "+tempFilePath+tempFileName); } tempFileName = "Bush7.fbx"; if (File.Exists(tempFilePath+tempFileName)) { detailObjects[1] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); }else{ Debug.LogWarning("Default object missing : "+tempFilePath+tempFileName); } tempFilePath = "Assets/Terrain Assets/Rocks/"; tempFileName = "RockMesh.fbx"; if (File.Exists(tempFilePath+tempFileName)) { detailObjects[2] = (GameObject)AssetDatabase.LoadAssetAtPath(tempFilePath+tempFileName, typeof(GameObject)); }else{ Debug.LogWarning("Default object missing : "+tempFilePath+tempFileName); } defaultsDone = true; } bool ValidateAutomagicTextures() { if (splatTextures[0]==null || splatTextures[1]==null || splatTextures[2]==null || splatTextures[3]==null) return false; return true; } bool ValidateAutomagicTrees() { if (treeObjects[0]==null || treeObjects[1]==null || treeObjects[2]==null) return false; return true; } SplatPrototype[] SetupTextures() { SetupTextureNormals(); //Vector2 tilesize = new Vector2(40f, 40f); SplatPrototype[] Splat = new SplatPrototype[4]; for (int i = 0; i < 4; i++) { Splat[i] = new SplatPrototype(); Splat[i].texture = splatTextures[i]; if (assignNormalMapsIfFounded) Splat[i].normalMap = splatTexNormals[i]; Splat[i].tileSize = new Vector2(tileSizeX,tileSizeY); } return Splat; } void SetupTextureNormals() { for (int i = 0; i < 4; i++) { string tempFileName = AssetDatabase.GetAssetPath(splatTextures[i]); string tempFileWithoutExtension = Path.GetFileNameWithoutExtension(tempFileName); if (assignNormalMapsIfFounded) { string tempNormalMapFile = Path.GetDirectoryName(tempFileName)+Path.DirectorySeparatorChar+tempFileWithoutExtension+"_normal"+Path.GetExtension(tempFileName); if (File.Exists(tempNormalMapFile)) splatTexNormals[i] = (Texture2D)AssetDatabase.LoadAssetAtPath(tempNormalMapFile, typeof(Texture2D)); } } } TreePrototype[] SetupTrees() { TreePrototype[] trees = new TreePrototype[3]; trees[0] = new TreePrototype(); trees[0].prefab = treeObjects[0]; trees[1] = new TreePrototype(); trees[1].prefab = treeObjects[1]; trees[2] = new TreePrototype(); trees[2].prefab = treeObjects[2]; return trees; } DetailPrototype[] SetupGrassAndDetails() { // TODO: only take used amount from automagic, skip nulls DetailPrototype[] details = new DetailPrototype[6]; // grass details[0] = new DetailPrototype(); if (grassTextures[0]!=null) { details[0].prototypeTexture = grassTextures[0]; details[0].renderMode = DetailRenderMode.Grass; details[0].usePrototypeMesh=false; details[0].minHeight = 0.55f; details[0].maxHeight = 0.77f; details[0].healthyColor = Color.white; details[0].dryColor = Color.gray; } details[1] = new DetailPrototype(); if (grassTextures[1]!=null) { details[1].prototypeTexture = grassTextures[1]; details[1].renderMode = DetailRenderMode.Grass; details[1].usePrototypeMesh=false; details[1].minHeight = 0.66f; details[1].maxHeight = 0.88f; details[1].healthyColor = Color.white; details[1].dryColor = Color.gray; } details[2] = new DetailPrototype(); if (grassTextures[2]!=null) { details[2].prototypeTexture = grassTextures[2]; details[2].renderMode = DetailRenderMode.Grass; details[2].usePrototypeMesh=false; details[2].minHeight = 0.77f; details[2].maxHeight = 0.99f; details[2].healthyColor = Color.white; details[2].dryColor = Color.gray; } // meshes details[3] = new DetailPrototype(); if (detailObjects[0]!=null) { details[3].prototype = detailObjects[0]; details[3].renderMode = DetailRenderMode.Grass; // for default grass details[3].usePrototypeMesh=true; details[3].healthyColor = Color.white; details[3].dryColor = Color.gray; } details[4] = new DetailPrototype(); if (detailObjects[1]!=null) { details[4].prototype = detailObjects[1]; details[4].renderMode = DetailRenderMode.Grass; // for default grass details[4].usePrototypeMesh=true; details[4].healthyColor = Color.white; details[4].dryColor = Color.gray; } details[5] = new DetailPrototype(); if (detailObjects[2]!=null) { details[5].prototype = detailObjects[2]; details[5].renderMode = DetailRenderMode.VertexLit; // for default mesh rock details[5].usePrototypeMesh=true; details[5].healthyColor = Color.white; details[5].dryColor = Color.gray; } return details; } /* DetailPrototype[] SetupDetailMeshes() { // if we already have some details keep them, for now just skip default 3 grass details DetailPrototype[] detailMeshes = new DetailPrototype[3]; detailMeshes[0] = new DetailPrototype(); detailMeshes[0].prototype = detailObjects[0]; detailMeshes[1] = new DetailPrototype(); if (detailMeshes[1]!=null) { detailMeshes[1].prototype = detailObjects[1]; } detailMeshes[2] = new DetailPrototype(); if (detailMeshes[2]!=null) { detailMeshes[2].prototype = detailObjects[2]; } return detailMeshes; }*/ bool CheckSplatmap() { if (splatA==null) return false; FixTextureSettings(splatA); if (splatA.height != splatA.width) { EditorUtility.DisplayDialog("Wrong size", "First splatmap must be square (width and height must be the same)", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(splatA.width) != splatA.width) { EditorUtility.DisplayDialog("Wrong size", "Splatmap width and height must be a power of two", "Cancel"); return false; } if (splatB!=null) { FixTextureSettings(splatB); if (splatB.height != splatB.width) { EditorUtility.DisplayDialog("Wrong size", "Second splatmap must be square (width and height must be the same)", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(splatB.width) != splatB.width) { EditorUtility.DisplayDialog("Wrong size", "Second splatmap width and height must be a power of two", "Cancel"); return false; } } return true; } void ApplySelectedTexturesToTerrain() { if (!myTerrain) myTerrain = Terrain.activeTerrain; if (myTerrain==null) {Debug.LogError("No terrain selected"); return;} myTerrain.terrainData.splatPrototypes = SetupTextures(); } void ApplySplatmap() { StartTimer(); if (!myTerrain) myTerrain = Terrain.activeTerrain; if (myTerrain==null) {Debug.LogError("No terrain selected"); return;} TerrainData terrainData = myTerrain.terrainData; if (terrainData==null) {Debug.LogError("Failed getting terrain data"); return;} if (terrainData.alphamapLayers<4) { EditorUtility.DisplayDialog("Missing Terrain Textures", "Please set up at least 4 textures in the terrain painter dialog", "Cancel"); return; } if (splatB!=null && terrainData.alphamapLayers<8) { EditorUtility.DisplayDialog("Missing Terrain Textures", "Please set up at least 8 textures in the terrain painter dialog", "Cancel"); return; } int width = splatA.width; bool TwoMaps = false; if (splatB==null) { //splatB = new Texture2D(w, w, TextureFormat.ARGB32, false); } else { if (splatA.width != splatB.width && splatA.height != splatB.height) { Debug.LogError("Both splatmaps must have same resolution ("+splatA.width+" != "+splatB.width+")"); return; } TwoMaps=true; } Undo.RecordObject(terrainData, "Apply splatmap(s)"); // very slow.. terrainData.alphamapResolution = width; float[,,] splatmapData = terrainData.GetAlphamaps(0, 0, width, width); Color[] splatmapColors = splatA.GetPixels(); Color[] splatmapColors_b=null; //= splatB.GetPixels(); if (TwoMaps) { splatmapColors_b = splatB.GetPixels(); }else{ // DestroyImmediate(splatB); //splatB = null; } Color col=Color.clear; Color col_b=Color.clear; for (int y = 0; y < width; y++) { if (y%10 == 0) // update progress bar every now and then { if (EditorUtility.DisplayCancelableProgressBar("Applying splatmap", "calculating...", Mathf.InverseLerp(0.0f, (float)width, (float)y))) { EditorUtility.ClearProgressBar(); return; } } for (int x = 0; x < width; x++) { float sum; // Color col = splatmapColors[((w-1)-x)*w + y]; // Color col_b = splatmapColors_b[((w-1)-x)*w + y]; //col = splatmapColors[ ((w-1)-y)*w + x]; // TODO: get rid of flip col = splatmapColors[x*width+y]; if (TwoMaps) { // col_b = splatmapColors_b[((w-1)-y)*w + x]; col_b = splatmapColors_b[y*width+x]; sum = col.r+col.g+col.b+col_b.r+col_b.g+col_b.b; } else { sum = col.r+col.g+col.b; //sum = col.r+col.g+col.b+(splatHasAlpha?col.a:0); } if (sum>1.0f) { // no final channel, and scale down splatmapData[x,y,0] = col.r / sum; splatmapData[x,y,1] = col.g / sum; splatmapData[x,y,2] = col.b / sum; //splatmapData[x,y,3] = col.a / sum; splatmapData[x,y,3] = 1f-sum; // splatmapData[y,x,0] = col.r / sum; // TODO: these parts will be later used for invert options // splatmapData[y,x,1] = col.g / sum; // splatmapData[y,x,2] = col.b / sum; if (TwoMaps) { splatmapData[x,y,4] = col_b.r / sum; splatmapData[x,y,5] = col_b.g / sum; splatmapData[x,y,6] = col_b.b / sum; splatmapData[x,y,7] = 1f-sum; // splatmapData[x,y,6] = 0.0f; } else { // single map // TODO: need to reset old splats, if previously had 2 splatmaps? // splatmapData[x,y,4] = 0.0f; // reset old maps // splatmapData[x,y,4] = 0; //splatmapData[x,y,5] = 0; //splatmapData[x,y,6] = 0; //splatmapData[x,y,7] = 0; } } else { // not over 1.0 // splatmapData[w-1-y,x,0] = col.r; // splatmapData[w-1-y,x,1] = col.g; // splatmapData[w-1-y,x,2] = col.b; // derive final channel from white splatmapData[x,y,0] = col.r; splatmapData[x,y,1] = col.g; splatmapData[x,y,2] = col.b; splatmapData[x,y,3] = 1f-sum; if (TwoMaps) { //Debug.Log("asddfasdf"); //return; splatmapData[x,y,4] = col_b.r; splatmapData[x,y,5] = col_b.g; splatmapData[x,y,6] = col_b.b; splatmapData[x,y,7] = 1f-sum; } else { //splatmapData[x,y,3] = 1.0f - sum; // reset old maps //splatmapData[x,y,4] = 0; //splatmapData[x,y,5] = 0; //splatmapData[x,y,6] = 0; //splatmapData[x,y,7] = 0; } } } // for splatmap x pixels } // for splatmap y pixels EditorUtility.ClearProgressBar(); terrainData.SetAlphamaps(0, 0, splatmapData); Debug.Log("Splatmaps applied "+GetTimerTime()); } bool CheckTreemap() { if (treemap == null) return false; FixTextureSettings(treemap); if (treemap.height != treemap.width) { EditorUtility.DisplayDialog("Wrong size", "treemap width and height must be the same", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(treemap.width) != treemap.width) { EditorUtility.DisplayDialog("Wrong size", "treemap width and height must be a power of two", "Cancel"); return false; } return true; } void ApplyTreemap() { StartTimer(); // set up my data if (!myTerrain) myTerrain = Terrain.activeTerrain; if (myTerrain==null) {Debug.LogError("No terrain selected"); return;} TerrainData terrainData = myTerrain.terrainData; // check if trees are assigned if (terrainData.treePrototypes.Length<3) { if (terrainData.treePrototypes.Length<1) { Debug.LogError("You should assign at least 1 tree to Terrain (at Place Trees tab)"); return; }else{ Debug.LogWarning("You should assign 3 trees to Terrain to use all 3 splatmap color channels (at Place Trees tab)"); } } Undo.RecordObject(terrainData, "Apply tree map"); // slow.. int width = treemap.width; Color[] mapColors = treemap.GetPixels(); int index = -1; int maxIndex=terrainData.treePrototypes.Length; int trees = 0; float Step = 1.0f/treeDensity; float PositionVariation = (float)(Step*0.5f/(float)width); if (resetTrees) terrainData.treeInstances = new TreeInstance[0]; int progress = 0; for (float y = 1; y < width-1; y+=Step) { progress++; if (progress%10 == 0) { progress=0; if (EditorUtility.DisplayCancelableProgressBar("Placing trees", "placed "+trees+" trees so far", Mathf.InverseLerp(0.0f, (float)width, (float)y))) { EditorUtility.ClearProgressBar(); return; } } for (float x = 1; x < width-1; x+=Step) { // place the chosen tree, if the colours are right index = -1; //Color col = mapColors[Mathf.RoundToInt(y)*w + Mathf.RoundToInt(x)]; Color col = mapColors[(width-Mathf.RoundToInt(y))*width+Mathf.RoundToInt(x)]; //width-x,length-y if (col.r>treeThreshold+Random.Range(0.0f, 1.0f)) { index = 0; } else if (maxIndex>1 && col.g>treeThreshold+Random.Range(0.0f, 1.0f)) { index = 1; } else if (maxIndex>2 && col.b>treeThreshold+Random.Range(0.0f, 1.0f)) { index = 2; } if (index>=0) { // place a tree trees++; TreeInstance treeInstance = new TreeInstance(); // random placement modifier for a more natural look float xpos = x/(float)width; float ypos = y/(float)width; xpos = Mathf.Clamp01(xpos+Random.Range(-PositionVariation, PositionVariation)); ypos = Mathf.Clamp01(1-ypos+Random.Range(-PositionVariation, PositionVariation)); treeInstance.position = new Vector3(xpos, 0f, ypos); treeInstance.color = Color.white; treeInstance.lightmapColor = Color.white; treeInstance.prototypeIndex = index; treeInstance.widthScale = treeSize * (1f + Random.Range(-sizeVariation, sizeVariation)); treeInstance.heightScale = treeSize * (1f + Random.Range(-sizeVariation, sizeVariation)); myTerrain.AddTreeInstance(treeInstance); } } } EditorUtility.ClearProgressBar(); Debug.Log("placed "+trees+" trees "+GetTimerTime()); } bool CheckGrassmap() { if (grassmap != null) { FixTextureSettings(grassmap); int w = grassmap.width; if (grassmap.height != w) { EditorUtility.DisplayDialog("Wrong size", "grassmap width and height must be the same", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(w) != w) { EditorUtility.DisplayDialog("Wrong size", "grassmap width and height must be a power of two", "Cancel"); return false; } } if (bushmap != null) { FixTextureSettings(bushmap); int w = bushmap.width; if (bushmap.height != w) { EditorUtility.DisplayDialog("Wrong size", "bushmap width and height must be the same", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(w) != w) { EditorUtility.DisplayDialog("Wrong size", "bushmap width and height must be a power of two", "Cancel"); return false; } } return true; } void ApplyGrassmap() { StartTimer(); if (!myTerrain) myTerrain = Terrain.activeTerrain; if (myTerrain==null) {Debug.LogError("No terrain selected"); return;} TerrainData terrainData = myTerrain.terrainData; Undo.RecordObject(terrainData, "Apply grass and bush maps"); if (grassmap!=null) { if (SetDetailmap(grassmap, grassDensity, 0, grassclumping, "Grass map")) Debug.Log("Grass map applied "+GetTimerTime()); } if (bushmap!=null) { //if (SetDetailmap(bushmap, bushmod, 3, 0.0f, "Bush map")) Debug.Log("Bush map applied."); if (SetDetailmap(bushmap, bushDensity, grassmap==null?0:3, 0.0f, "Bush map")) Debug.Log("Bush map applied "+GetTimerTime()); } EditorUtility.ClearProgressBar(); } void ApplyBushmap() { StartTimer(); if (!myTerrain) myTerrain = Terrain.activeTerrain; if (myTerrain==null) {Debug.LogError("No terrain selected"); return;} TerrainData terrainData = myTerrain.terrainData; Undo.RecordObject(terrainData, "Apply grass and bush maps"); /* if (grassmap!=null) { if (SetDetailmap(grassmap, grassDensity, 0, grassclumping, "Grass map")) Debug.Log("Grass map applied "+GetTimerTime()); }*/ if (bushmap!=null) { //if (SetDetailmap(bushmap, bushmod, 3, 0.0f, "Bush map")) Debug.Log("Bush map applied."); if (SetDetailmap(bushmap, bushDensity, grassmap==null?0:3, 0.0f, "Bush map")) Debug.Log("Bush map applied "+GetTimerTime()); } EditorUtility.ClearProgressBar(); } bool SetDetailmap(Texture2D map, float mod, int firstlayer, float clumping, string MapName) { if (!myTerrain) myTerrain = Terrain.activeTerrain; TerrainData terrainData = myTerrain.terrainData; if (terrainData.detailPrototypes.Length<3) { if (terrainData.detailPrototypes.Length<1) { Debug.LogError ("You need to add at least 1 detail textures or 1 detail meshes to Terrain (at Paint Details)"); return false; } Debug.LogWarning ("You should add 3 detail textures or 3 detail meshes to Terrain to use all splat map color channels (at Paint Details)"); } // validate terrain details count int detailTextureCount=0; int detailMeshCount=0; int maxDetailMeshes = terrainData.detailPrototypes.Length; for (int nn=0; nn2) detail_b[x,y] = DetailValue(col.g*mod); if (maxDetailMeshes>3) detail_c[x,y] = DetailValue(col.b*mod); } } if (clumping>0.01f) { detail_a = MakeClumps(detail_a, clumping); if (maxDetailMeshes>2) detail_b = MakeClumps(detail_b, clumping); if (maxDetailMeshes>3) detail_c = MakeClumps(detail_c, clumping); } terrainData.SetDetailLayer(0, 0, firstlayer+0, detail_a); if (maxDetailMeshes>2) terrainData.SetDetailLayer(0, 0, firstlayer+1, detail_b); if (maxDetailMeshes>3) terrainData.SetDetailLayer(0, 0, firstlayer+2, detail_c); return true; } int DetailValue(float col) { // linearly translates a 0.0-1.0 number to a 0-16 integer int c = Mathf.FloorToInt(col*16); float r = col*16 - Mathf.FloorToInt(col*16); if (r>Random.Range(0.0f, 1.0f)) c++; return Mathf.Clamp(c, 0, 16); } int[,] MakeClumps(int[,] map, float clumping) { int res = map.GetLength(0); int [,] clumpmap = new int[res,res]; // init - there's probably a better way to do this in C# that I just don't know for (int y = 0; y < res; y++) { for (int x = 0; x < res; x++) { clumpmap[x,y]=0; } } for (int y = 0; y < res; y++) { for (int x = 0; x < res; x++) { clumpmap[x,y]+=map[x,y]; if (map[x,y]>0) { // there's grass here, we might want to raise the grass value of our neighbours for (int a=-1;a<=1;a++) for (int b=-1;b<=1;b++) { if (x+a<0||x+a>=res||y+b<0||y+b>=res) continue; if (a!=0||b!=0&&Random.Range(0.0f, 1.0f)9) clumpmap[x,y]=9; } } return clumpmap; } bool CheckOverlaymap() { if (overlayMap==null) {Debug.LogError("No overlay map assigned"); return false;} if (!myTerrain) myTerrain = Terrain.activeTerrain; FixTextureSettings(overlayMap); if (overlayMap.height != overlayMap.width) { EditorUtility.DisplayDialog("Wrong size", "OverlayMap width and height must be the same", "Cancel"); return false; } if (Mathf.ClosestPowerOfTwo(overlayMap.width) != overlayMap.width) { EditorUtility.DisplayDialog("Wrong size", "OverlayMap width and height must be a power of two", "Cancel"); return false; } if (overlayMap.width!=myTerrain.terrainData.alphamapResolution) { EditorUtility.DisplayDialog("Wrong size", "OverlayMap must have same size as existing splatmap ("+myTerrain.terrainData.alphamapResolution+")", "Cancel"); return false; } return true; } void ApplyOverlaymap() { StartTimer(); if (!myTerrain) myTerrain = Terrain.activeTerrain; TerrainData terrainData = myTerrain.terrainData; Undo.RecordObject(terrainData, "Apply overlay map"); int w = overlayMap.width; Color[] OverlayMapColors = overlayMap.GetPixels(); int layer = myTerrain.terrainData.alphamapLayers; int detailRes = terrainData.detailWidth; int[] detailLayers = terrainData.GetSupportedLayers(0, 0, detailRes, detailRes); int LayerCount = detailLayers.Length; AddTexture(); float[,,] splatmapData = terrainData.GetAlphamaps(0, 0, w, w); float terrainScale = (float)w / ((float)terrainData.heightmapResolution-1); float terrainHeight = terrainData.size.y; int terrainSample = Mathf.CeilToInt(terrainScale); ArrayList NewTrees = new ArrayList(terrainData.treeInstances); int TreesRemoved = 0; for (int y = 0; y < w; y++) { if (y%10 == 0) { if (clearTrees) { if (EditorUtility.DisplayCancelableProgressBar("Overlay map", "updating terrain and trees ("+TreesRemoved+" trees removed)", Mathf.InverseLerp(0.0f, (float)w, (float)y))) { EditorUtility.ClearProgressBar(); return; } } else { if (EditorUtility.DisplayCancelableProgressBar("Overlay map", "updating terrain", Mathf.InverseLerp(0.0f, (float)w, (float)y))) { EditorUtility.ClearProgressBar(); return; } } } for (int x = 0; x < w; x++) { // float value = OverlayMapColors[((w-1)-x)*w + y].grayscale; float value = OverlayMapColors[x*w+y].grayscale; if (value>overlayThreshold) { splatmapData[x,y,layer] = value; // fix the other layers for (int l = 0; l0.01f || changeTerrain<-0.01f) { if (value>overlayThreshold) { float change = changeTerrain * value / terrainHeight; int sx = Mathf.FloorToInt((float)y*terrainScale); int sy = Mathf.FloorToInt((float)x*terrainScale); float [,] data = terrainData.GetHeights(sx, sy, terrainSample, terrainSample); for (int a=0;a= 0; i--) { TreeInstance MyTree = (TreeInstance)NewTrees[i]; float distance = Vector2.Distance(new Vector2(MyTree.position.z*w, MyTree.position.x*w), new Vector2((float)x, (float)y)); if (distance < clearRadius) { NewTrees.RemoveAt(i); TreesRemoved++; } } } } else { splatmapData[x,y,layer] = 0.0f; } } } if (clearTrees) { terrainData.treeInstances = (TreeInstance[])NewTrees.ToArray(typeof(TreeInstance)); } terrainData.SetAlphamaps(0, 0, splatmapData); Debug.Log("Overlay map applied "+GetTimerTime()); if (clearGrass) { float scale = (float)w / (float)detailRes; for (int l = 0; l overlayThreshold && grass[x,y]>0) { if (value > 0.5f) grass[x,y]=0; else grass[x,y]=1; } } } terrainData.SetDetailLayer(0, 0, l, grass); } } EditorUtility.ClearProgressBar(); } void AddTexture() { if (!myTerrain) myTerrain = Terrain.activeTerrain; SplatPrototype[] oldPrototypes = myTerrain.terrainData.splatPrototypes; SplatPrototype[] newPrototypes = new SplatPrototype[oldPrototypes.Length + 1]; for (int x=0;xht1pc) moreMac++;else morePc+=1; } int ii=0; bool mac = deltaMac