├── README.md ├── LICENSE ├── TextureScale.cs ├── Trees_Tool_Inspector.cs └── Trees_Tool.cs /README.md: -------------------------------------------------------------------------------- 1 | # TreesTool 2 | A Unity add-on to control foliage placement 3 | 4 | Texture scale script from Aviv (http://www.aogamesstudios.com/). 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 vincentaber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TextureScale.cs: -------------------------------------------------------------------------------- 1 | // Only works on ARGB32, RGB24 and Alpha8 textures that are marked readable 2 | 3 | using System.Threading; 4 | using UnityEngine; 5 | 6 | public class TextureScale//Class for converting the texture size to the details size 7 | { 8 | public class ThreadData 9 | { 10 | public int start; 11 | public int end; 12 | public ThreadData(int s, int e) 13 | { 14 | start = s; 15 | end = e; 16 | } 17 | } 18 | 19 | private static Color[] texColors; 20 | private static Color[] newColors; 21 | private static int w; 22 | private static float ratioX; 23 | private static float ratioY; 24 | private static int w2; 25 | private static int finishCount; 26 | private static Mutex mutex; 27 | 28 | public static void Point(Texture2D tex, int newWidth, int newHeight) 29 | { 30 | ThreadedScale(tex, newWidth, newHeight, false); 31 | } 32 | 33 | public static void Bilinear(Texture2D tex, int newWidth, int newHeight) 34 | { 35 | ThreadedScale(tex, newWidth, newHeight, true); 36 | } 37 | 38 | private static void ThreadedScale(Texture2D tex, int newWidth, int newHeight, bool useBilinear) 39 | { 40 | texColors = tex.GetPixels(); 41 | newColors = new Color[newWidth * newHeight]; 42 | if (useBilinear) 43 | { 44 | ratioX = 1.0f / ((float)newWidth / (tex.width - 1)); 45 | ratioY = 1.0f / ((float)newHeight / (tex.height - 1)); 46 | } 47 | else 48 | { 49 | ratioX = ((float)tex.width) / newWidth; 50 | ratioY = ((float)tex.height) / newHeight; 51 | } 52 | w = tex.width; 53 | w2 = newWidth; 54 | var cores = Mathf.Min(SystemInfo.processorCount, newHeight); 55 | var slice = newHeight / cores; 56 | 57 | finishCount = 0; 58 | if (mutex == null) 59 | { 60 | mutex = new Mutex(false); 61 | } 62 | if (cores > 1) 63 | { 64 | int i = 0; 65 | ThreadData threadData; 66 | for (i = 0; i < cores - 1; i++) 67 | { 68 | threadData = new ThreadData(slice * i, slice * (i + 1)); 69 | ParameterizedThreadStart ts = useBilinear ? new ParameterizedThreadStart(BilinearScale) : new ParameterizedThreadStart(PointScale); 70 | Thread thread = new Thread(ts); 71 | thread.Start(threadData); 72 | } 73 | threadData = new ThreadData(slice * i, newHeight); 74 | if (useBilinear) 75 | { 76 | BilinearScale(threadData); 77 | } 78 | else 79 | { 80 | PointScale(threadData); 81 | } 82 | while (finishCount < cores) 83 | { 84 | Thread.Sleep(1); 85 | } 86 | } 87 | else 88 | { 89 | ThreadData threadData = new ThreadData(0, newHeight); 90 | if (useBilinear) 91 | { 92 | BilinearScale(threadData); 93 | } 94 | else 95 | { 96 | PointScale(threadData); 97 | } 98 | } 99 | 100 | tex.Resize(newWidth, newHeight); 101 | tex.SetPixels(newColors); 102 | tex.Apply(); 103 | 104 | texColors = null; 105 | newColors = null; 106 | } 107 | 108 | public static void BilinearScale(System.Object obj) 109 | { 110 | ThreadData threadData = (ThreadData)obj; 111 | for (var y = threadData.start; y < threadData.end; y++) 112 | { 113 | int yFloor = (int)Mathf.Floor(y * ratioY); 114 | var y1 = yFloor * w; 115 | var y2 = (yFloor + 1) * w; 116 | var yw = y * w2; 117 | 118 | for (var x = 0; x < w2; x++) 119 | { 120 | int xFloor = (int)Mathf.Floor(x * ratioX); 121 | var xLerp = x * ratioX - xFloor; 122 | newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp), 123 | ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp), 124 | y * ratioY - yFloor); 125 | } 126 | } 127 | 128 | mutex.WaitOne(); 129 | finishCount++; 130 | mutex.ReleaseMutex(); 131 | } 132 | 133 | public static void PointScale(System.Object obj) 134 | { 135 | ThreadData threadData = (ThreadData)obj; 136 | for (var y = threadData.start; y < threadData.end; y++) 137 | { 138 | var thisY = (int)(ratioY * y) * w; 139 | var yw = y * w2; 140 | for (var x = 0; x < w2; x++) 141 | { 142 | newColors[yw + x] = texColors[(int)(thisY + ratioX * x)]; 143 | } 144 | } 145 | 146 | mutex.WaitOne(); 147 | finishCount++; 148 | mutex.ReleaseMutex(); 149 | } 150 | 151 | private static Color ColorLerpUnclamped(Color c1, Color c2, float value) 152 | { 153 | return new Color(c1.r + (c2.r - c1.r) * value, 154 | c1.g + (c2.g - c1.g) * value, 155 | c1.b + (c2.b - c1.b) * value, 156 | c1.a + (c2.a - c1.a) * value); 157 | } 158 | } -------------------------------------------------------------------------------- /Trees_Tool_Inspector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System.Linq; 6 | using UnityEngine.UI; 7 | 8 | [CustomEditor(typeof(Trees_Tool))] 9 | public class Trees_Tool_Inspector : Editor 10 | { 11 | bool ShowTrees = false; 12 | bool ShowDetail = false; 13 | 14 | 15 | public override void OnInspectorGUI() 16 | { 17 | GUIStyle UndoStyle = new GUIStyle(GUI.skin.button); 18 | UndoStyle.normal.textColor = Color.red; 19 | 20 | Trees_Tool TreesTool = (Trees_Tool)target; 21 | 22 | if (GUILayout.Button("Initialize")) 23 | { 24 | TreesTool.InitTrees(); 25 | 26 | } 27 | 28 | TreesTool.useExperimental = EditorGUILayout.Toggle("Use Experimental Features", TreesTool.useExperimental); 29 | 30 | 31 | GUILayout.Space(10); 32 | 33 | EditorGUILayout.BeginHorizontal(); 34 | EditorGUILayout.LabelField("Slope", GUILayout.Width(80)); 35 | TreesTool.minSteepness = EditorGUILayout.FloatField(TreesTool.minSteepness, GUILayout.Width(21)); 36 | EditorGUILayout.MinMaxSlider(ref TreesTool.minSteepness, ref TreesTool.maxSteepness, 0f, 90f); 37 | TreesTool.maxSteepness = EditorGUILayout.FloatField(TreesTool.maxSteepness, GUILayout.Width(21)); 38 | EditorGUILayout.EndHorizontal(); 39 | if (TreesTool.useExperimental) 40 | EditorGUILayout.MinMaxSlider("Height", ref TreesTool.minHeight, ref TreesTool.maxHeight, 0f, 1f); 41 | 42 | 43 | TreesTool.TextureArrayIndex = EditorGUILayout.IntSlider("Texture Index", TreesTool.TextureArrayIndex, 0, TreesTool.currentTerrainData.terrainLayers.Length - 1); 44 | 45 | 46 | Texture TerrainTexture = TreesTool.currentTerrainData.terrainLayers[TreesTool.TextureArrayIndex].diffuseTexture; 47 | GUILayout.Box(TerrainTexture, GUILayout.Width(120), GUILayout.Height(120)); 48 | 49 | GUILayout.Space(10); 50 | 51 | 52 | 53 | 54 | ShowTrees = EditorGUILayout.BeginFoldoutHeaderGroup(ShowTrees, "Trees"); 55 | 56 | 57 | if (ShowTrees) 58 | { 59 | 60 | GUILayout.BeginHorizontal(); 61 | GUILayout.Label("Use Texture"); 62 | GUILayout.Label("Use Slope"); 63 | if (TreesTool.useExperimental) 64 | GUILayout.Label("Use Height"); 65 | 66 | 67 | GUILayout.EndHorizontal(); 68 | 69 | GUILayout.BeginHorizontal(); 70 | 71 | TreesTool.UseTexture = EditorGUILayout.Toggle(TreesTool.UseTexture); 72 | TreesTool.UseSlope = EditorGUILayout.Toggle(TreesTool.UseSlope); 73 | if(TreesTool.useExperimental) 74 | TreesTool.UseHeight = EditorGUILayout.Toggle(TreesTool.UseHeight); 75 | 76 | GUILayout.EndHorizontal(); 77 | GUILayout.Space(10); 78 | 79 | if (TreesTool.useExperimental) 80 | { 81 | EditorGUILayout.HelpBox("The Height feature can be slow", MessageType.Warning, true); 82 | GUILayout.Space(10); 83 | } 84 | 85 | 86 | TreesTool.TreeUsePerlin = EditorGUILayout.BeginToggleGroup("Use Perlin Noise", TreesTool.TreeUsePerlin); 87 | if (TreesTool.TreeUsePerlin) 88 | { 89 | TreesTool.TreePerlinScale = EditorGUILayout.Slider("Perlin Scale", TreesTool.TreePerlinScale, 0, 5); 90 | 91 | TreesTool.TreeInvertPerlin = EditorGUILayout.Toggle("Invert Perlin", TreesTool.TreeInvertPerlin); 92 | TreesTool.TreePerlinSeed = EditorGUILayout.IntField("Perlin Seed", TreesTool.TreePerlinSeed); 93 | 94 | TreesTool.TreePerlinBias = EditorGUILayout.Slider("PerlinBias", TreesTool.TreePerlinBias, -0.5f, 0.5f); 95 | } 96 | 97 | EditorGUILayout.EndToggleGroup(); 98 | 99 | GUILayout.Space(15); 100 | GUILayout.Label("Spawn", EditorStyles.boldLabel); 101 | 102 | 103 | TreesTool.TreePrototypeIndex = EditorGUILayout.IntSlider("Tree Index", TreesTool.TreePrototypeIndex, 0, TreesTool.currentTerrainData.treePrototypes.Length - 1); 104 | 105 | TreesTool.TreeSpawnDensity = EditorGUILayout.IntSlider("Spawn Density", TreesTool.TreeSpawnDensity, 1, 100); 106 | 107 | EditorGUILayout.Space(10); 108 | TreesTool.transitionSmoothness = EditorGUILayout.Slider("Transition Smoothness", TreesTool.transitionSmoothness, 0, 1); 109 | TreesTool.BorderSize = EditorGUILayout.Slider("Border Size", TreesTool.BorderSize, 0, 1); 110 | 111 | EditorGUILayout.Space(10); 112 | 113 | TreesTool.PositionRandomness = EditorGUILayout.Slider("Position Randomness", TreesTool.PositionRandomness, 0, 1); 114 | TreesTool.SizeRandomness = EditorGUILayout.Slider("Size Randomness", TreesTool.SizeRandomness, 0, 1); 115 | 116 | if (GUILayout.Button("Spawn")) 117 | { 118 | 119 | TreesTool.AddTree(TreesTool.UseTexture,TreesTool.UseSlope, TreesTool.UseHeight); 120 | 121 | } 122 | 123 | GUILayout.Space(15); 124 | 125 | 126 | if(TreesTool.useExperimental) 127 | { 128 | GUILayout.Label("Remove", EditorStyles.boldLabel); 129 | 130 | if (GUILayout.Button("Remove")) 131 | { 132 | TreesTool.RemoveTrees(TreesTool.UseTexture, TreesTool.UseSlope, TreesTool.UseHeight); 133 | 134 | } 135 | EditorGUILayout.HelpBox("The Remove feature can be slow when used on a lot of trees.", MessageType.Warning); 136 | 137 | TreesTool.DecimatePercentage = EditorGUILayout.IntSlider("Decimate Percentage", TreesTool.DecimatePercentage, 1, 100); 138 | if (GUILayout.Button("Decimate")) 139 | { 140 | TreesTool.DecimateTrees(TreesTool.DecimatePercentage); 141 | } 142 | EditorGUILayout.HelpBox("The Decimate feature can be slow when used on a lot of trees.", MessageType.Warning); 143 | } 144 | 145 | GUILayout.BeginHorizontal(); 146 | if (GUILayout.Button("Randomize Size")) 147 | { 148 | TreesTool.RandomizeSize(); 149 | } 150 | 151 | if (GUILayout.Button("Randomize Position")) 152 | { 153 | TreesTool.RandomizePosition(); 154 | } 155 | GUILayout.EndHorizontal(); 156 | 157 | if (GUILayout.Button("Unify Size")) 158 | { 159 | TreesTool.UnifySize(); 160 | } 161 | 162 | 163 | 164 | 165 | if (GUILayout.Button("Undo", UndoStyle)) 166 | { 167 | TreesTool.UndoTrees(); 168 | } 169 | 170 | 171 | 172 | 173 | GUILayout.Space(10); 174 | 175 | EditorGUILayout.LabelField("Utilities", EditorStyles.centeredGreyMiniLabel); 176 | GUILayout.BeginHorizontal(); 177 | if (GUILayout.Button("Total Tree Count")) 178 | { 179 | TreesTool.TreeCount(); 180 | } 181 | if (GUILayout.Button("Selected Tree Count")) 182 | { 183 | TreesTool.SelectedTreeCount(); 184 | } 185 | GUILayout.EndHorizontal(); 186 | 187 | } 188 | 189 | EditorGUILayout.EndFoldoutHeaderGroup(); 190 | 191 | 192 | ShowDetail = EditorGUILayout.BeginFoldoutHeaderGroup(ShowDetail, "Details"); 193 | 194 | 195 | if (ShowDetail) 196 | { 197 | 198 | TreesTool.DetailPrototypeIndex = EditorGUILayout.IntSlider("Detail Index", TreesTool.DetailPrototypeIndex, 0, TreesTool.currentTerrainData.detailPrototypes.Length - 1); 199 | Texture DetailTexture = TreesTool.currentTerrainData.detailPrototypes[TreesTool.DetailPrototypeIndex].prototypeTexture; 200 | GUILayout.Box(DetailTexture, GUILayout.Width(120), GUILayout.Height(120)); 201 | 202 | 203 | 204 | TreesTool.DetailSpawnDensity = EditorGUILayout.IntSlider("Spawn Density", TreesTool.DetailSpawnDensity, 1, 10); 205 | TreesTool.detailExpand = EditorGUILayout.Slider("Expand Mask", TreesTool.detailExpand, 0, 1); 206 | TreesTool.LacunarityProbability = EditorGUILayout.Slider("LacunarityProbability", TreesTool.LacunarityProbability, 0, 1); 207 | 208 | TreesTool.DetailUsePerlin = EditorGUILayout.BeginToggleGroup("Use Perlin Noise", TreesTool.DetailUsePerlin); 209 | if(TreesTool.DetailUsePerlin) 210 | { 211 | TreesTool.DetaliPerlinScale = EditorGUILayout.Slider("Perlin Scale", TreesTool.DetaliPerlinScale, 0, 4); 212 | 213 | TreesTool.DetailInvertPerlin = EditorGUILayout.Toggle("Invert Perlin", TreesTool.DetailInvertPerlin); 214 | TreesTool.DetailPerlinSeed = EditorGUILayout.IntField("Perlin Seed", TreesTool.DetailPerlinSeed); 215 | 216 | TreesTool.DetailPerlinBias = EditorGUILayout.Slider("PerlinBias", TreesTool.DetailPerlinBias, -0.5f, 0.5f); 217 | } 218 | 219 | EditorGUILayout.EndToggleGroup(); 220 | 221 | 222 | if (GUILayout.Button("Spawn on Texture")) 223 | { 224 | TreesTool.AddDetailsTexture(); 225 | } 226 | 227 | if (GUILayout.Button("Set Density")) 228 | { 229 | TreesTool.SetDetailDensity(); 230 | } 231 | 232 | if (GUILayout.Button("Add Lacunarity")) 233 | { 234 | TreesTool.AddDetailLacunarity(TreesTool.DetailPrototypeIndex); 235 | } 236 | 237 | if (GUILayout.Button("Clean Detail")) 238 | { 239 | TreesTool.CleanDetails(TreesTool.DetailPrototypeIndex); 240 | } 241 | 242 | if (GUILayout.Button("Remove from Texture")) 243 | { 244 | TreesTool.RemoveDetailsTexture(); 245 | } 246 | 247 | if (GUILayout.Button("Undo", UndoStyle)) 248 | { 249 | TreesTool.UndoDetails(); 250 | } 251 | 252 | EditorGUILayout.EndFoldoutHeaderGroup(); 253 | } 254 | 255 | } 256 | 257 | 258 | } 259 | -------------------------------------------------------------------------------- /Trees_Tool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Xml; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | using UnityEngine; 10 | [ExecuteInEditMode] 11 | public class Trees_Tool : MonoBehaviour 12 | { 13 | 14 | Terrain currentTerrain; 15 | public TerrainData currentTerrainData; 16 | Vector3 terrainPosition; 17 | 18 | 19 | public int TextureArrayIndex = 0; 20 | public int TreePrototypeIndex = 0; 21 | public int DetailPrototypeIndex = 0; 22 | 23 | 24 | public int TreeSpawnDensity; 25 | public int DetailSpawnDensity; 26 | 27 | public float PositionRandomness = 0.2f; 28 | public float SizeRandomness = 0.1f; 29 | public float BorderSize = 0.2f; 30 | public float LacunarityProbability = 0.5f; 31 | 32 | public float minHeight = 0; 33 | public float maxHeight = 1f; 34 | 35 | public float minSteepness = 0f; 36 | public float maxSteepness = 90f; 37 | public float transitionSmoothness = 0.9f; 38 | public float detailExpand = 0.5f; 39 | 40 | public int DecimatePercentage; 41 | 42 | public float DetaliPerlinScale = 1; 43 | public bool DetailUsePerlin; 44 | public float DetailPerlinBias = 0f; 45 | public int DetailPerlinSeed = 1; 46 | 47 | public bool DetailInvertPerlin; 48 | 49 | public float TreePerlinScale = 1; 50 | public bool TreeUsePerlin; 51 | public float TreePerlinBias = 0f; 52 | public int TreePerlinSeed = 1; 53 | 54 | public bool TreeInvertPerlin; 55 | 56 | public bool UseTexture; 57 | public bool UseSlope; 58 | public bool UseHeight; 59 | 60 | public bool useExperimental; 61 | 62 | private List TreesBackup; 63 | private int[,] DetailBackup; 64 | 65 | 66 | public void Awake() 67 | { 68 | currentTerrain = GetComponent(); 69 | currentTerrainData = currentTerrain.terrainData; 70 | terrainPosition = transform.position; 71 | TreesBackup = new List(); 72 | Debug.Log("Trees_Tool Initialized"); 73 | } 74 | 75 | public void InitTrees() 76 | { 77 | currentTerrain = GetComponent(); 78 | currentTerrainData = currentTerrain.terrainData; 79 | terrainPosition = transform.position; 80 | TreesBackup = new List(); 81 | Debug.Log("Trees_Tool Initialized"); 82 | } 83 | 84 | 85 | 86 | public void AddTree(bool useText, bool useSlope, bool useHeight) 87 | { 88 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 89 | TreesBackup = NewTrees.ToList(); 90 | List TreesList = NewTrees.ToList(); 91 | 92 | float time = (float)EditorApplication.timeSinceStartup; 93 | 94 | int AddedTrees = 0; 95 | 96 | //CreatingTree 97 | TreeInstance tree = new TreeInstance(); 98 | 99 | tree.prototypeIndex = TreePrototypeIndex; 100 | tree.heightScale = 1; 101 | tree.widthScale = 1; 102 | tree.color = Color.white; 103 | tree.lightmapColor = Color.white; 104 | tree.rotation = 0; 105 | tree.position = Vector3.zero; 106 | 107 | 108 | float tempheight = tree.heightScale; 109 | float tempwidth = tree.widthScale; 110 | int raycastAmount = TreeSpawnDensity * 10; 111 | Vector3 SpawnPos; 112 | for (int i = 0; i < raycastAmount; i++) 113 | { 114 | for (int x = 0; x < raycastAmount; x++) 115 | { 116 | 117 | Vector3 treePos = new Vector3((float)i / (float)raycastAmount, 0, (float)x / (float)raycastAmount); 118 | treePos.x += Random.Range(-0.05f, 0.05f) * PositionRandomness; 119 | treePos.x = Mathf.Clamp(treePos.x, 0, 1); 120 | treePos.z += Random.Range(-0.05f, 0.05f) * PositionRandomness; 121 | treePos.z = Mathf.Clamp(treePos.z, 0, 1); 122 | 123 | float RandomSize = Random.Range(SizeRandomness * -1, SizeRandomness); 124 | 125 | SpawnPos = TreePosToWorldPos(treePos); 126 | if (useText) 127 | { 128 | 129 | float TextureMix = GetTextureThreshold(SpawnPos, TextureArrayIndex); 130 | if (TextureMix > Mathf.Abs(1 - transitionSmoothness)) 131 | { 132 | float rando = Random.Range(0, 1); 133 | if (rando < TextureMix) 134 | { 135 | 136 | tree.heightScale = (tempheight + RandomSize) * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 137 | tree.widthScale = (tempwidth + RandomSize) * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 138 | tree.position = treePos; 139 | tree.rotation = Random.Range(-180, 180); 140 | 141 | if (!TreeUsePerlin) 142 | { 143 | if (useSlope) 144 | { 145 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 146 | { 147 | TreesList.Add(tree); 148 | AddedTrees += 1; 149 | } 150 | } 151 | 152 | else 153 | { 154 | TreesList.Add(tree); 155 | AddedTrees += 1; 156 | } 157 | 158 | } 159 | 160 | else 161 | { 162 | if (Mathf.Clamp(Mathf.PerlinNoise((treePos.x + TreePerlinSeed) * TreePerlinScale * 100, (treePos.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) > 0.5f && !TreeInvertPerlin) 163 | { 164 | if (useSlope) 165 | { 166 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 167 | { 168 | TreesList.Add(tree); 169 | AddedTrees += 1; 170 | } 171 | } 172 | 173 | else 174 | { 175 | TreesList.Add(tree); 176 | AddedTrees += 1; 177 | } 178 | 179 | } 180 | else if (Mathf.Clamp(Mathf.PerlinNoise((treePos.x + TreePerlinSeed) * TreePerlinScale * 100, (treePos.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) < 0.5f && TreeInvertPerlin) 181 | { 182 | if (useSlope) 183 | { 184 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 185 | { 186 | TreesList.Add(tree); 187 | AddedTrees += 1; 188 | } 189 | } 190 | 191 | else 192 | { 193 | TreesList.Add(tree); 194 | AddedTrees += 1; 195 | } 196 | } 197 | } 198 | } 199 | 200 | } 201 | } 202 | 203 | else 204 | { 205 | 206 | 207 | tree.heightScale = (tempheight + RandomSize); 208 | tree.widthScale = (tempwidth + RandomSize); 209 | tree.position = treePos; 210 | tree.rotation = Random.Range(-180, 180); 211 | 212 | if (!TreeUsePerlin) 213 | { 214 | if (useSlope) 215 | { 216 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 217 | { 218 | TreesList.Add(tree); 219 | AddedTrees += 1; 220 | } 221 | } 222 | 223 | else 224 | { 225 | TreesList.Add(tree); 226 | AddedTrees += 1; 227 | } 228 | } 229 | 230 | else 231 | { 232 | if (Mathf.Clamp(Mathf.PerlinNoise((treePos.x + TreePerlinSeed) * TreePerlinScale * 100, (treePos.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) > 0.5f && !TreeInvertPerlin) 233 | { 234 | if (useSlope) 235 | { 236 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 237 | { 238 | TreesList.Add(tree); 239 | AddedTrees += 1; 240 | } 241 | } 242 | 243 | else 244 | { 245 | TreesList.Add(tree); 246 | AddedTrees += 1; 247 | } 248 | } 249 | else if (Mathf.Clamp(Mathf.PerlinNoise((treePos.x + TreePerlinSeed) * TreePerlinScale * 100, (treePos.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) < 0.5f && TreeInvertPerlin) 250 | { 251 | if (useSlope) 252 | { 253 | if (currentTerrainData.GetSteepness(treePos.x, treePos.z) >= minSteepness && currentTerrainData.GetSteepness(treePos.x, treePos.z) <= maxSteepness) 254 | { 255 | TreesList.Add(tree); 256 | AddedTrees += 1; 257 | } 258 | } 259 | 260 | else 261 | { 262 | TreesList.Add(tree); 263 | AddedTrees += 1; 264 | } 265 | } 266 | } 267 | 268 | } 269 | 270 | 271 | } 272 | } 273 | 274 | NewTrees = TreesList.ToArray(); 275 | currentTerrainData.SetTreeInstances(NewTrees, true); 276 | NewTrees = currentTerrainData.treeInstances; 277 | TreesList = NewTrees.ToList(); 278 | 279 | if (useHeight) 280 | { 281 | for (int i = 0; i < currentTerrainData.treeInstanceCount; i++) 282 | { 283 | TreeInstance currentTree = NewTrees[i]; 284 | 285 | if (currentTree.prototypeIndex == TreePrototypeIndex) 286 | { 287 | if (currentTree.position.y > maxHeight || currentTree.position.y < minHeight) 288 | { 289 | TreesList.Remove(currentTree); 290 | AddedTrees -= 1; 291 | } 292 | } 293 | 294 | } 295 | } 296 | 297 | NewTrees = TreesList.ToArray(); 298 | currentTerrainData.SetTreeInstances(NewTrees, true); 299 | time = (float)EditorApplication.timeSinceStartup - time; 300 | Debug.Log(AddedTrees + " trees added in " + Mathf.RoundToInt(time)); 301 | 302 | } 303 | 304 | public void RemoveTrees(bool useTexture, bool useSlope, bool useHeight) 305 | { 306 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 307 | TreesBackup = NewTrees.ToList(); 308 | List TreesList = NewTrees.ToList(); 309 | TreesBackup = NewTrees.ToList(); 310 | 311 | float time = (float)EditorApplication.timeSinceStartup; 312 | 313 | bool treedone; 314 | 315 | int DestroyedTrees = 0; 316 | 317 | for (int i = 0; i < currentTerrainData.treeInstanceCount; i++) 318 | { 319 | TreeInstance currentTree = currentTerrainData.GetTreeInstance(i); 320 | treedone = false; 321 | if (currentTree.prototypeIndex == TreePrototypeIndex) 322 | { 323 | if (useTexture) 324 | { 325 | float TextureMix = GetTextureThreshold(TreePosToWorldPos(currentTree.position), TextureArrayIndex); 326 | if (TextureMix > transitionSmoothness) 327 | { 328 | TreesList.Remove(currentTree); 329 | DestroyedTrees += 1; 330 | treedone = true; 331 | } 332 | } 333 | 334 | if (useSlope && !treedone) 335 | { 336 | if (currentTerrainData.GetSteepness(currentTree.position.x, currentTree.position.z) > minSteepness && currentTerrainData.GetSteepness(currentTree.position.x, currentTree.position.z) < maxSteepness) 337 | { 338 | TreesList.Remove(currentTree); 339 | DestroyedTrees += 1; 340 | treedone = true; 341 | } 342 | } 343 | 344 | if (useHeight && !treedone) 345 | { 346 | if (currentTree.position.y < maxHeight && currentTree.position.y > minHeight) 347 | { 348 | TreesList.Remove(currentTree); 349 | DestroyedTrees += 1; 350 | } 351 | } 352 | 353 | if (TreeUsePerlin && !treedone) 354 | { 355 | if (Mathf.Clamp(Mathf.PerlinNoise((currentTree.position.x + TreePerlinSeed) * TreePerlinScale * 100, (currentTree.position.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) > 0.5f && !TreeInvertPerlin) 356 | { 357 | TreesList.Remove(currentTree); 358 | DestroyedTrees += 1; 359 | 360 | } 361 | else if (Mathf.Clamp(Mathf.PerlinNoise((currentTree.position.x + TreePerlinSeed) * TreePerlinScale * 100, (currentTree.position.z + TreePerlinSeed) * TreePerlinScale * 100) + TreePerlinBias, 0, 1) < 0.5f && TreeInvertPerlin) 362 | { 363 | TreesList.Remove(currentTree); 364 | DestroyedTrees += 1; 365 | } 366 | } 367 | 368 | 369 | } 370 | 371 | } 372 | 373 | 374 | NewTrees = TreesList.ToArray(); 375 | 376 | 377 | if (useTexture) 378 | { 379 | 380 | for (int i = 0; i < NewTrees.Length; i++) 381 | { 382 | TreeInstance currentTree = NewTrees[i]; 383 | 384 | 385 | float TextureMix = Mathf.Abs(1 - GetTextureThreshold(TreePosToWorldPos(currentTree.position), TextureArrayIndex)); 386 | 387 | if (currentTree.prototypeIndex == TreePrototypeIndex) 388 | { 389 | NewTrees[i].heightScale = currentTree.heightScale * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 390 | NewTrees[i].widthScale = currentTree.widthScale * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 391 | } 392 | } 393 | } 394 | 395 | 396 | currentTerrainData.SetTreeInstances(NewTrees, true); 397 | time = (float)EditorApplication.timeSinceStartup - time; 398 | Debug.Log(DestroyedTrees + " trees removed in " + Mathf.RoundToInt(time)); 399 | 400 | 401 | } 402 | 403 | //Not implemented, but might be usefull ? 404 | public void KeepTreesTexture() 405 | { 406 | 407 | TreesBackup = currentTerrainData.treeInstances.ToList(); 408 | List TreesList = currentTerrainData.treeInstances.ToList(); 409 | 410 | float time = (float)EditorApplication.timeSinceStartup; 411 | 412 | int DestroyedTrees = 0; 413 | 414 | for (int i = 0; i < currentTerrainData.treeInstanceCount; i++) 415 | { 416 | TreeInstance currentTree = currentTerrainData.GetTreeInstance(i); 417 | if (currentTree.prototypeIndex == TreePrototypeIndex) 418 | { 419 | if (!(GetTextureThreshold(TreePosToWorldPos(currentTree.position), TextureArrayIndex) > Mathf.Abs(1 - transitionSmoothness))) 420 | { 421 | TreesList.Remove(currentTree); 422 | DestroyedTrees += 1; 423 | } 424 | } 425 | } 426 | 427 | TreeInstance[] NewTrees = TreesList.ToArray(); 428 | 429 | for (int i = 0; i < NewTrees.Length; i++) 430 | { 431 | TreeInstance currentTree = NewTrees[i]; 432 | 433 | 434 | float TextureMix = GetTextureThreshold(TreePosToWorldPos(currentTree.position), TextureArrayIndex); 435 | 436 | if (currentTree.prototypeIndex == TreePrototypeIndex) 437 | { 438 | NewTrees[i].heightScale = currentTree.heightScale * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 439 | NewTrees[i].widthScale = currentTree.widthScale * Mathf.Clamp(TextureMix + BorderSize, 0, 1); 440 | } 441 | } 442 | currentTerrainData.SetTreeInstances(NewTrees, true); 443 | time = (float)EditorApplication.timeSinceStartup - time; 444 | Debug.Log(DestroyedTrees + " trees removed in " + Mathf.RoundToInt(time)); 445 | } 446 | 447 | public void DecimateTrees(int PercentageToKill) 448 | { 449 | 450 | float time = (float)EditorApplication.timeSinceStartup; 451 | 452 | if (PercentageToKill == 0) 453 | return; 454 | else if (PercentageToKill == 100) 455 | { 456 | RemoveTreesIndex(); 457 | return; 458 | } 459 | 460 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 461 | TreesBackup = NewTrees.ToList(); 462 | List TreesList = NewTrees.ToList(); 463 | 464 | int DestroyedTrees = 0; 465 | int rando; 466 | for (int i = 0; i < currentTerrainData.treeInstanceCount; i ++) 467 | { 468 | TreeInstance currentTree = currentTerrainData.GetTreeInstance(i); 469 | if (currentTree.prototypeIndex == TreePrototypeIndex) 470 | { 471 | rando = Random.Range(0, 100); 472 | if (rando < PercentageToKill) 473 | { 474 | TreesList.Remove(currentTree); 475 | DestroyedTrees += 1; 476 | } 477 | 478 | } 479 | 480 | } 481 | currentTerrainData.SetTreeInstances(TreesList.ToArray(), true); 482 | time = (float)EditorApplication.timeSinceStartup - time; 483 | Debug.Log(DestroyedTrees + " trees removed in " + Mathf.RoundToInt(time)); 484 | } 485 | 486 | 487 | 488 | 489 | 490 | public void RandomizeSize() 491 | { 492 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 493 | TreesBackup = NewTrees.ToList(); 494 | for (int i = 0; i < NewTrees.Length; i++) 495 | { 496 | if (NewTrees[i].prototypeIndex == TreePrototypeIndex) 497 | { 498 | float RandomSize = Random.Range(SizeRandomness * -1, SizeRandomness); 499 | 500 | NewTrees[i].heightScale = (NewTrees[i].heightScale + RandomSize); 501 | NewTrees[i].widthScale = (NewTrees[i].widthScale + RandomSize); 502 | } 503 | 504 | 505 | } 506 | currentTerrainData.SetTreeInstances(NewTrees, false); 507 | 508 | } 509 | 510 | public void UnifySize() 511 | { 512 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 513 | TreesBackup = NewTrees.ToList(); 514 | 515 | 516 | for (int i = 0; i < NewTrees.Length; i++) 517 | { 518 | if (NewTrees[i].prototypeIndex == TreePrototypeIndex) 519 | { 520 | NewTrees[i].heightScale = 1; 521 | NewTrees[i].widthScale = 1; 522 | } 523 | } 524 | 525 | currentTerrainData.SetTreeInstances(NewTrees, false); 526 | } 527 | 528 | public void RandomizePosition() 529 | { 530 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 531 | TreesBackup = NewTrees.ToList(); 532 | for (int i = 0; i < NewTrees.Length; i++) 533 | { 534 | if (NewTrees[i].prototypeIndex == TreePrototypeIndex) 535 | { 536 | NewTrees[i].position.x += Random.Range(-0.05f, 0.05f) * PositionRandomness; 537 | NewTrees[i].position.x = Mathf.Clamp(NewTrees[i].position.x, 0, 1); 538 | NewTrees[i].position.z += Random.Range(-0.05f, 0.05f) * PositionRandomness; 539 | NewTrees[i].position.z = Mathf.Clamp(NewTrees[i].position.z, 0, 1); 540 | } 541 | } 542 | currentTerrainData.SetTreeInstances(NewTrees, true); 543 | } 544 | 545 | 546 | public void UndoTrees() 547 | { 548 | TreeInstance[] CurrentTrees = currentTerrainData.treeInstances; 549 | TreeInstance[] RestoredTrees = TreesBackup.ToArray(); 550 | currentTerrainData.SetTreeInstances(RestoredTrees, false); 551 | TreesBackup = CurrentTrees.ToList(); 552 | } 553 | 554 | private float[] GetTextureMix(Vector3 WorldPos) 555 | { 556 | 557 | 558 | // calculate which splat map cell the worldPos falls within (ignoring y) 559 | int mapX = (int)(((WorldPos.x - terrainPosition.x) / currentTerrainData.size.x) * currentTerrainData.alphamapWidth); 560 | int mapZ = (int)(((WorldPos.z - terrainPosition.z) / currentTerrainData.size.z) * currentTerrainData.alphamapHeight); 561 | // get the splat data for this cell as a 1x1xN 3d array (where N = number of textures) 562 | float[,,] splatmapData = currentTerrainData.GetAlphamaps(mapX, mapZ, 1, 1); 563 | 564 | // extract the 3D array data to a 1D array: 565 | float[] cellMix = new float[splatmapData.GetUpperBound(2) + 1]; 566 | 567 | for (int n = 0; n < cellMix.Length; n++) 568 | { 569 | cellMix[n] = splatmapData[0, 0, n]; 570 | } 571 | return cellMix; 572 | } 573 | 574 | 575 | 576 | private float GetTextureThreshold(Vector3 WorldPos, int textureIndex) 577 | { 578 | 579 | float[] mix = GetTextureMix(WorldPos); 580 | return mix[textureIndex]; 581 | 582 | } 583 | 584 | Vector3 TreePosToWorldPos(Vector3 TreePos) 585 | { 586 | Vector3 WorldPos = Vector3.zero; 587 | WorldPos.x = TreePos.x * currentTerrainData.size.x + transform.position.x; 588 | WorldPos.z = TreePos.z * currentTerrainData.size.z + transform.position.z; 589 | 590 | return WorldPos; 591 | } 592 | 593 | Vector3 WorldPosToTreePos(Vector3 WorldPos) 594 | { 595 | Vector3 TreePos = Vector3.zero; 596 | TreePos.x = (WorldPos.x - transform.position.x) / currentTerrainData.size.x; 597 | TreePos.z = (WorldPos.z - transform.position.z) / currentTerrainData.size.z; 598 | return TreePos; 599 | } 600 | 601 | public void TreeCount() 602 | { 603 | Debug.Log(currentTerrainData.treeInstanceCount + "trees on the terrain"); 604 | } 605 | public void SelectedTreeCount() 606 | { 607 | int SelectedTrees = 0; 608 | foreach(TreeInstance tree in currentTerrainData.treeInstances) 609 | { 610 | if (tree.prototypeIndex == TreePrototypeIndex) 611 | { 612 | SelectedTrees += 1; 613 | } 614 | } 615 | Debug.Log(SelectedTrees + " trees of index " + TreePrototypeIndex + " on the terrain"); 616 | } 617 | 618 | public void RemoveTreesIndex() 619 | { 620 | TreeInstance[] NewTrees = currentTerrainData.treeInstances; 621 | TreesBackup = NewTrees.ToList(); 622 | List TreesList = NewTrees.ToList(); 623 | 624 | for (int i = 0; i < currentTerrainData.treeInstanceCount; i++) 625 | { 626 | TreeInstance currentTree = currentTerrainData.GetTreeInstance(i); 627 | if (currentTree.prototypeIndex == TreePrototypeIndex) 628 | { 629 | 630 | TreesList.Remove(currentTree); 631 | } 632 | 633 | } 634 | currentTerrainData.SetTreeInstances(TreesList.ToArray(), true); 635 | } 636 | 637 | 638 | 639 | TreeInstance CreateTree(TreeInstance[] TreeSamples) 640 | { 641 | int prototypeIndex = TreeSamples[0].prototypeIndex; 642 | float heightScale = (TreeSamples[0].heightScale + TreeSamples[1].heightScale + TreeSamples[2].heightScale + TreeSamples[3].heightScale + TreeSamples[4].heightScale) / 5; 643 | float widthScale = (TreeSamples[0].widthScale + TreeSamples[1].widthScale + TreeSamples[2].widthScale + TreeSamples[3].widthScale + TreeSamples[4].widthScale) / 5; 644 | Color color = Color.white; 645 | Color lightmapColor = Color.white; 646 | float rotation = 0; 647 | Vector3 position = Vector3.zero; 648 | 649 | TreeInstance CreatedTree = new TreeInstance(); 650 | 651 | CreatedTree.prototypeIndex = prototypeIndex; 652 | CreatedTree.heightScale = heightScale; 653 | CreatedTree.widthScale = widthScale; 654 | CreatedTree.color = color; 655 | CreatedTree.lightmapColor = lightmapColor; 656 | CreatedTree.rotation = rotation; 657 | CreatedTree.position = position; 658 | 659 | return CreatedTree; 660 | 661 | } 662 | 663 | 664 | public void AddDetailsTexture() 665 | { 666 | DetailBackup = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 667 | 668 | 669 | float[,,] alphaMapData = currentTerrainData.GetAlphamaps(0, 0, currentTerrainData.alphamapWidth, currentTerrainData.alphamapHeight);//The terrain texture maps 670 | int[,] olddetailsMap = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex);//The terrain detail maps(Where everything is placed) 671 | 672 | 673 | Texture2D temp = new Texture2D(currentTerrainData.alphamapWidth, currentTerrainData.alphamapHeight); 674 | for (int x = 0; x < currentTerrainData.alphamapWidth; x++) 675 | { 676 | for (int y = 0; y < currentTerrainData.alphamapHeight; y++) 677 | { 678 | temp.SetPixel(x, y, new Color(0, 0, 0, alphaMapData[x, y, TextureArrayIndex])); 679 | } 680 | } 681 | temp.Apply(); 682 | int targetLength = olddetailsMap.GetLength(0); 683 | TextureScale.Bilinear(temp, targetLength, targetLength); 684 | temp.Apply(); 685 | 686 | int [,] detailsMap = new int[targetLength, targetLength]; 687 | for (int x = 0; x < targetLength; x += 1) 688 | { 689 | for (int y = 0; y < targetLength; y += 1) 690 | { 691 | 692 | if (!DetailUsePerlin) 693 | { 694 | detailsMap[x, y] = Mathf.RoundToInt(Mathf.Clamp(olddetailsMap[x, y], 0, 100) + (temp.GetPixel(x, y).a > (Mathf.Abs(1 - detailExpand)) ? DetailSpawnDensity : 0)); 695 | } 696 | 697 | else 698 | { 699 | if (!DetailInvertPerlin) 700 | { 701 | detailsMap[x, y] = Mathf.RoundToInt(Mathf.Clamp(olddetailsMap[x, y], 0, 100) + (temp.GetPixel(x, y).a > (Mathf.Abs(1 - detailExpand)) ? DetailSpawnDensity : 0) * (Mathf.Clamp(Mathf.PerlinNoise((x + DetailPerlinSeed) * DetaliPerlinScale / 10, (y + DetailPerlinSeed) * DetaliPerlinScale / 10) + DetailPerlinBias, 0 ,1) > 0.5f ? 0 : 1)); 702 | } 703 | else 704 | { 705 | detailsMap[x, y] = Mathf.RoundToInt(Mathf.Clamp(olddetailsMap[x, y], 0, 100) + (temp.GetPixel(x, y).a > (Mathf.Abs(1 - detailExpand)) ? DetailSpawnDensity : 0) * (Mathf.Clamp(Mathf.PerlinNoise((x + DetailPerlinSeed) * DetaliPerlinScale / 10, (y + DetailPerlinSeed) * DetaliPerlinScale / 10) + DetailPerlinBias, 0, 1) > 0.5f ? 1 : 0)); 706 | } 707 | } 708 | 709 | 710 | 711 | } 712 | } 713 | 714 | currentTerrainData.SetDetailLayer(0, 0, DetailPrototypeIndex, detailsMap); 715 | } 716 | 717 | public void RemoveDetailsTexture() 718 | { 719 | DetailBackup = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 720 | #region Get Terrain Data 721 | float[,,] alphaMapData = currentTerrainData.GetAlphamaps(0, 0, currentTerrainData.alphamapWidth, currentTerrainData.alphamapHeight);//The terrain texture maps 722 | int[,] olddetailsMap = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex);//The terrain detail maps(Where everything is placed) 723 | #endregion 724 | #region Convert texture map data length to details map length 725 | Texture2D temp = new Texture2D(currentTerrainData.alphamapWidth, currentTerrainData.alphamapHeight); 726 | for (int x = 0; x < currentTerrainData.alphamapWidth; x++) 727 | { 728 | for (int y = 0; y < currentTerrainData.alphamapHeight; y++) 729 | { 730 | temp.SetPixel(x, y, new Color(0, 0, 0, alphaMapData[x, y, TextureArrayIndex])); 731 | } 732 | } 733 | temp.Apply(); 734 | int targetLength = olddetailsMap.GetLength(0); 735 | TextureScale.Bilinear(temp, targetLength, targetLength); 736 | temp.Apply(); 737 | #endregion 738 | #region Apply detail data by user presents and selected texture 739 | int[,] detailsMap = new int[targetLength, targetLength]; 740 | for (int x = 0; x < targetLength; x += 1) 741 | { 742 | for (int y = 0; y < targetLength; y += 1) 743 | { 744 | 745 | if (olddetailsMap[x, y] > 0) 746 | detailsMap[x, y] = (temp.GetPixel(x, y).a > (Mathf.Abs(1 - detailExpand)) ? 0 : olddetailsMap[x, y]); 747 | 748 | } 749 | } 750 | #endregion 751 | currentTerrainData.SetDetailLayer(0, 0, DetailPrototypeIndex, detailsMap); 752 | } 753 | public void SetDetailDensity() 754 | { 755 | DetailBackup = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 756 | int[,] detailsMap = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 757 | int targetLength = detailsMap.GetLength(0); 758 | for (int x = 0; x 0) 763 | detailsMap[x, y] = DetailSpawnDensity; 764 | 765 | } 766 | } 767 | 768 | currentTerrainData.SetDetailLayer(0, 0, DetailPrototypeIndex, detailsMap); 769 | } 770 | 771 | public void AddDetailLacunarity(int PrototypeIndex) 772 | { 773 | DetailBackup = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, PrototypeIndex); 774 | bool LastwasHole = false; 775 | int[,] detailsMap = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, PrototypeIndex); 776 | 777 | int targetLength = detailsMap.GetLength(0); 778 | 779 | float rando; 780 | 781 | 782 | for (int x = 0; x < targetLength; x += 1) 783 | { 784 | for (int y = 0; y < targetLength; y += 1) 785 | { 786 | 787 | rando = Random.Range(0f, 1f); ; 788 | 789 | if (detailsMap[x, y] > 0) 790 | { 791 | if (!LastwasHole) 792 | { 793 | if (rando < LacunarityProbability) 794 | { 795 | 796 | detailsMap[x, y] = 0; 797 | LastwasHole = true; 798 | } 799 | } 800 | else 801 | { 802 | if (rando < LacunarityProbability * 0.3f) 803 | { 804 | 805 | detailsMap[x, y] = 0; 806 | LastwasHole = false; 807 | 808 | } 809 | } 810 | } 811 | 812 | 813 | } 814 | } 815 | 816 | currentTerrainData.SetDetailLayer(0, 0, PrototypeIndex, detailsMap); 817 | } 818 | 819 | 820 | public void CleanDetails(int layer) 821 | { 822 | DetailBackup = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 823 | for (int i = 0; i < currentTerrainData.detailPrototypes.Length; i++) 824 | { 825 | int[,] map = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, layer == -1 ? i : layer); 826 | 827 | for (int x = 0; x < map.GetLength(0); x++) 828 | { 829 | for (int y = 0; y < map.GetLength(1); y++) 830 | { 831 | map[x, y] = 0; 832 | } 833 | } 834 | currentTerrainData.SetDetailLayer(0, 0, layer == -1 ? i : layer, map); 835 | if (layer != -1) 836 | { 837 | return; 838 | } 839 | } 840 | } 841 | 842 | public void UndoDetails() 843 | { 844 | int[,] temp = currentTerrainData.GetDetailLayer(0, 0, currentTerrainData.detailWidth, currentTerrainData.detailHeight, DetailPrototypeIndex); 845 | currentTerrainData.SetDetailLayer(0, 0, DetailPrototypeIndex, DetailBackup); 846 | DetailBackup = temp; 847 | } 848 | 849 | 850 | 851 | } 852 | 853 | 854 | --------------------------------------------------------------------------------