//Chooses a prefab based on probability, recursively executed until succesful private static TreePrefab PickTreeRecursive(TreeType treeType) { if (treeType.prefabs.Count == 0) { return(null); } TreePrefab p = treeType.prefabs[Random.Range(0, treeType.prefabs.Count)]; //If prefabs have an extremely low probabilty, give up after 4 attempts if (recursionCounter >= 4) { return(null); } if ((Random.value * 100f) <= p.probability) { //Debug.Log("<color=green>" + p.prefab.name + " passed probability check..</color>"); return(p); } //Debug.Log("<color=red>" + p.prefab.name + " failed probability check, trying another...</color>"); recursionCounter++; //Note: It's possible for the next candidate to be the one that just failed return(PickTreeRecursive(treeType)); }
private void PrefabPickingActions() { //New specifics (initial prefab) if (Event.current.commandName == "ObjectSelectorClosed" && EditorGUIUtility.GetObjectPickerControlID() == newTreeprefabPickerWindowID) { GameObject pickedPrefab = (GameObject)EditorGUIUtility.GetObjectPickerObject(); newTreeprefabPickerWindowID = -1; //if (pickedPrefab == null) return; VegetationSpawner.TreeType tree = SpawnerBase.TreeType.New(pickedPrefab); spawner.treeTypes.Add(tree); //Auto select new selectedTreeID = spawner.treeTypes.Count - 1; if (spawner.autoRespawnTrees) { spawner.SpawnTree(tree); } EditorUtility.SetDirty(target); } //Specifies prefabs if (Event.current.commandName == "ObjectSelectorClosed" && EditorGUIUtility.GetObjectPickerControlID() == prefabPickerWindowID) { GameObject pickedPrefab = (GameObject)EditorGUIUtility.GetObjectPickerObject(); prefabPickerWindowID = -1; //if (pickedPrefab == null) return; TreeType tree = spawner.treeTypes[selectedTreeID]; TreePrefab treePrefab = new TreePrefab(); treePrefab.probability = 100; treePrefab.prefab = pickedPrefab; tree.prefabs.Add(treePrefab); spawner.RefreshTreePrefabs(); if (spawner.autoRespawnTrees) { spawner.SpawnTree(tree); } EditorUtility.SetDirty(target); } }
public static TreeType New() { TreeType t = new TreeType(); //Constructor for inherent variables t.probability = 10f; //Add an initial prefab on construction TreePrefab p = new TreePrefab(); t.prefabs.Add(p); return(t); }
public static TreeType New(GameObject initialPrefab) { TreeType t = New(); //Add an initial prefab on construction TreePrefab p = new TreePrefab(); p.prefab = initialPrefab; t.prefabs.Add(p); if (initialPrefab) { t.name = initialPrefab.name; } return(t); }
private TreePrototype GetTreePrototype(TreePrefab item, Terrain terrain) { return(terrain.terrainData.treePrototypes[item.index]); }
public void SpawnTree(TreeType item) { if (item.collisionCheck) { RebuildCollisionCacheIfNeeded(); } item.instanceCount = 0; RefreshTreePrefabs(); float height, worldHeight, normalizedHeight; foreach (Terrain terrain in terrains) { List <TreeInstance> treeInstanceCollection = new List <TreeInstance>(terrain.terrainData.treeInstances); //Clear all existing instances first for (int i = 0; i < treeInstanceCollection.Count; i++) { foreach (TreePrefab prefab in item.prefabs) { treeInstanceCollection.RemoveAll(x => x.prototypeIndex == prefab.index); } } InitializeSeed(item.seed); item.spawnPoints = PoissonDisc.GetSpawnpoints(terrain, item.distance, item.seed + seed); foreach (Vector3 pos in item.spawnPoints) { //InitializeSeed(item.seed + index); //Relative position as 0-1 value Vector2 normalizedPos = terrain.GetNormalizedPosition(pos); if (item.collisionCheck) { //Check for collision if (InsideOccupiedCell(terrain, pos, normalizedPos)) { continue; } } InitializeSeed(item.seed + (int)pos.x + (int)pos.z); //Skip if failing global probability check if (((Random.value * 100f) <= item.probability) == false) { continue; } TreePrefab prefab = SpawnerBase.GetProbableTree(item); //Failed probability checks entirely if (prefab == null) { continue; } terrain.SampleHeight(normalizedPos, out height, out worldHeight, out normalizedHeight); if (item.rejectUnderwater && worldHeight < waterHeight) { continue; } //Check height if (worldHeight < item.heightRange.x || worldHeight > item.heightRange.y) { continue; } if (item.slopeRange.x > 0 || item.slopeRange.y < 90f) { float slope = terrain.GetSlope(normalizedPos, false); //Reject if slope check fails if (!(slope >= (item.slopeRange.x + 0.001f) && slope <= (item.slopeRange.y))) { continue; } } if (item.curvatureRange.x > 0 || item.curvatureRange.y < 1f) { float curvature = terrain.SampleConvexity(normalizedPos); //0=concave, 0.5=flat, 1=convex curvature = TerrainSampler.ConvexityToCurvature(curvature); if (curvature < item.curvatureRange.x || curvature > item.curvatureRange.y) { continue; } } //Reject based on layer masks Vector2Int texelIndex = terrain.SplatmapTexelIndex(normalizedPos); float spawnChance = 0f; if (item.layerMasks.Count == 0) { spawnChance = 100f; } foreach (TerrainLayerMask layer in item.layerMasks) { Texture2D splat = terrain.terrainData.GetAlphamapTexture(GetSplatmapID(layer.layerID)); Color color = splat.GetPixel(texelIndex.x, texelIndex.y); int channel = layer.layerID % 4; float value = SampleChannel(color, channel); if (value > 0) { value = Mathf.Clamp01(value - layer.threshold); } value *= 100f; spawnChance += value; } InitializeSeed((int)pos.x * (int)pos.z); if ((Random.value <= spawnChance) == false) { continue; } //Passed all conditions, add instance TreeInstance treeInstance = new TreeInstance(); treeInstance.prototypeIndex = prefab.index; treeInstance.position = new Vector3(normalizedPos.x, normalizedHeight, normalizedPos.y); treeInstance.rotation = Random.Range(0f, 359f) * Mathf.Deg2Rad; float scale = Random.Range(item.scaleRange.x, item.scaleRange.y); treeInstance.heightScale = scale; treeInstance.widthScale = scale; treeInstance.color = Color.white; treeInstance.lightmapColor = Color.white; treeInstanceCollection.Add(treeInstance); item.instanceCount++; } item.spawnPoints.Clear(); #if UNITY_2019_1_OR_NEWER terrain.terrainData.SetTreeInstances(treeInstanceCollection.ToArray(), false); #else terrain.terrainData.treeInstances = treeInstanceCollection.ToArray(); #endif } for (int i = 0; i < item.prefabs.Count; i++) { onTreeRespawn?.Invoke(item.prefabs[i]); } }
private void SpawnTreeOnTerrain(Terrain terrain, TreeType item) { float height, worldHeight, normalizedHeight; List <TreeInstance> treeInstanceCollection = new List <TreeInstance>(terrain.terrainData.treeInstances); //Clear all existing instances first, setting the tree instances is additive for (int i = 0; i < treeInstanceCollection.Count; i++) { foreach (TreePrefab prefab in item.prefabs) { treeInstanceCollection.RemoveAll(x => x.prototypeIndex == prefab.index); } } if (item.enabled) { InitializeSeed(item.seed); //Speed up, only recalculate spawn points if distance was altered, or if there are none at all if (item.prevDistance > item.distance || item.prevDistance < item.distance || item.spawnPoints.Count == 0) { item.prevDistance = item.distance; item.spawnPoints = PoissonDisc.GetSpawnpoints(terrain, item.distance, item.seed + seed); } foreach (Vector3 pos in item.spawnPoints) { //InitializeSeed(item.seed + index); //Relative position as 0-1 value Vector2 normalizedPos = terrain.GetNormalizedPosition(pos); InitializeSeed(item.seed + (int)pos.x * (int)pos.z); //Skip if failing global probability check if (((Random.value * 100f) <= item.probability) == false) { continue; } if (item.collisionCheck) { //Check for collision if (InsideOccupiedCell(terrain, pos, normalizedPos)) { continue; } } TreePrefab prefab = SpawnerBase.GetProbableTree(item); //Failed probability checks entirely if (prefab == null) { continue; } terrain.SampleHeight(normalizedPos, out height, out worldHeight, out normalizedHeight); //Reject if lower than chosen water level if (item.rejectUnderwater && worldHeight < waterHeight) { continue; } //Check height if (worldHeight < item.heightRange.x || worldHeight > item.heightRange.y) { continue; } if (item.slopeRange.x > 0 || item.slopeRange.y < 90f) { float slope = terrain.GetSlope(normalizedPos, false); //Reject if slope check fails if (!(slope >= (item.slopeRange.x) && slope <= (item.slopeRange.y))) { continue; } } if (item.curvatureRange.x > 0 || item.curvatureRange.y < 1f) { float curvature = terrain.SampleConvexity(normalizedPos); //0=concave, 0.5=flat, 1=convex curvature = TerrainSampler.ConvexityToCurvature(curvature); if (curvature < item.curvatureRange.x || curvature > item.curvatureRange.y) { continue; } } float spawnChance = 0f; if (item.layerMasks.Count == 0) { spawnChance = 100f; } else { //Reject based on layer masks splatmapTexelIndex = terrain.SplatmapTexelIndex(normalizedPos); } foreach (TerrainLayerMask layer in item.layerMasks) { Texture2D splat = terrain.terrainData.GetAlphamapTexture(GetSplatmapID(layer.layerID)); Color color = splat.GetPixel(splatmapTexelIndex.x, splatmapTexelIndex.y); int channel = layer.layerID % 4; float value = SampleChannel(color, channel); if (value > 0) { value = Mathf.Clamp01(value - layer.threshold); } value *= 100f; spawnChance += value; } InitializeSeed((int)pos.x * (int)pos.z); if ((Random.value <= spawnChance) == false) { continue; } //Passed all conditions, add instance TreeInstance treeInstance = new TreeInstance(); treeInstance.prototypeIndex = prefab.index; //Note: Sink amount should be converted to normalized 0-1 height treeInstance.position = new Vector3(normalizedPos.x, normalizedHeight - (item.sinkAmount / (terrain.terrainData.size.y + 0.01f)), normalizedPos.y); treeInstance.rotation = Random.Range(0f, 359f) * Mathf.Deg2Rad; float scale = Random.Range(item.scaleRange.x, item.scaleRange.y); treeInstance.heightScale = scale; treeInstance.widthScale = scale; treeInstance.color = Color.white; treeInstance.lightmapColor = Color.white; treeInstanceCollection.Add(treeInstance); item.instanceCount++; } } #if UNITY_2019_1_OR_NEWER terrain.terrainData.SetTreeInstances(treeInstanceCollection.ToArray(), false); #else terrain.terrainData.treeInstances = treeInstanceCollection.ToArray(); #endif }
private void DrawTrees() { EditorGUILayout.LabelField("Species", EditorStyles.boldLabel); //Tree item view treeScrollPos = EditorGUILayout.BeginScrollView(treeScrollPos, EditorStyles.textArea, GUILayout.Height(thumbSize + 10f)); using (new EditorGUILayout.HorizontalScope()) { for (int i = 0; i < script.treeTypes.Count; i++) { if (script.treeTypes[i] == null) { continue; } Texture2D thumb = EditorGUIUtility.IconContent("d_BuildSettings.Broadcom").image as Texture2D; if (script.treeTypes[i].prefabs.Count > 0) { if (script.treeTypes[i].prefabs[0] != null) { if (script.treeTypes[i].prefabs[0].prefab) { thumb = AssetPreview.GetAssetPreview(script.treeTypes[i].prefabs[0].prefab); } } } if (GUILayout.Button(new GUIContent("", thumb), (selectedTreeID == i) ? VegetationSpawnerEditor.PreviewTexSelected : VegetationSpawnerEditor.PreviewTex, GUILayout.MinHeight(thumbSize), GUILayout.MaxWidth(thumbSize), GUILayout.MaxHeight(thumbSize))) { selectedTreeID = i; } } } EditorGUILayout.EndScrollView(); Undo.RecordObject(script, "Modified tree species"); serializedObject.Update(); using (var treeChange = new EditorGUI.ChangeCheckScope()) { //Tree type view options using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); if (GUILayout.Button(new GUIContent("Add", EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Toolbar Plus" : "Toolbar Plus").image, "Add new item"))) { VegetationSpawner.TreeType tree = TreeType.New(); script.treeTypes.Add(tree); selectedTreeID = script.treeTypes.Count - 1; } if (script.treeTypes.Count > 0) { if (GUILayout.Button(new GUIContent("", EditorGUIUtility.IconContent("d_TreeEditor.Trash").image, "Remove"))) { script.treeTypes.RemoveAt(selectedTreeID); selectedTreeID = script.treeTypes.Count - 1; if (selectedTreeID < 0) { selectedTreeID = 0; } script.RefreshTreePrefabs(); } } } //Settings for selected if (script.treeTypes.Count > 0) { VegetationSpawner.TreeType tree = script.treeTypes[selectedTreeID]; EditorGUILayout.LabelField("Prefabs", EditorStyles.boldLabel); if (tree.prefabs.Count == 0) { EditorGUILayout.HelpBox("Add a tree prefab first", MessageType.Info); } for (int p = 0; p < tree.prefabs.Count; p++) { TreePrefab item = tree.prefabs[p]; using (new EditorGUILayout.HorizontalScope()) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUI.BeginChangeCheck(); item.prefab = EditorGUILayout.ObjectField("Prefab", item.prefab, typeof(GameObject), true) as GameObject; if (item.prefab) { if (EditorUtility.IsPersistent(item.prefab) == false) { EditorGUILayout.HelpBox("Prefab cannot be a scene instance", MessageType.Error); } } item.probability = EditorGUILayout.Slider("Spawn chance %", item.probability, 0f, 100f); if (EditorGUI.EndChangeCheck()) { script.UpdateTreeItem(tree); if (autoRespawnTrees.boolValue) { script.SpawnTree(tree); } EditorUtility.SetDirty(target); } } if (GUILayout.Button(new GUIContent("", EditorGUIUtility.IconContent("d_TreeEditor.Trash").image, "Remove"))) { tree.prefabs.RemoveAt(p); script.RefreshTreePrefabs(); } } } using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); if (GUILayout.Button(new GUIContent("Add", EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Toolbar Plus" : "Toolbar Plus").image, "Add new item"))) { TreePrefab p = new TreePrefab(); p.probability = 100f; tree.prefabs.Add(p); script.RefreshTreePrefabs(); } } if (tree.prefabs.Count > 0) { if (tree.prefabs[0].prefab != null) { EditorGUILayout.LabelField("Spawn rules", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); using (new EditorGUILayout.HorizontalScope()) { tree.seed = EditorGUILayout.IntField("Seed", tree.seed, GUILayout.MaxWidth(EditorGUIUtility.labelWidth + 50f)); if (GUILayout.Button("Randomize", GUILayout.MaxWidth(100f))) { tree.seed = Random.Range(0, 99999); } } tree.probability = EditorGUILayout.Slider("Global spawn chance %", tree.probability, 0f, 100f); tree.distance = EditorGUILayout.Slider("Distance between", tree.distance, 0.5f, 50f); VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Scale", "Scale is randomly selected from this range"), ref tree.scaleRange, 0f, 2f); tree.sinkAmount = EditorGUILayout.Slider(new GUIContent("Sink amount", "Lowers the Y position of the tree"), tree.sinkAmount, 0f, 1f); EditorGUILayout.Space(); tree.collisionCheck = EditorGUILayout.Toggle("Collision check", tree.collisionCheck); tree.rejectUnderwater = EditorGUILayout.Toggle(new GUIContent("Remove underwater", "The water height level can be set in the settings tab"), tree.rejectUnderwater); VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Height range", "Min/max height this item can spawn at"), ref tree.heightRange, 0f, script.maxTerrainHeight); VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Slope range", "Min/max slope (0-90 degrees) this item can spawn at"), ref tree.slopeRange, 0f, 90f); VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Curvature range", "0=Concave (bowl), 0.5=flat, 1=convex (edge)"), ref tree.curvatureRange, 0f, 1f); if (EditorGUI.EndChangeCheck()) { if (!autoRespawnTrees.boolValue) { return; } Stopwatch sw = new Stopwatch(); sw.Restart(); script.SpawnTree(tree); sw.Stop(); Log.Add("Respawning tree: " + sw.Elapsed.Milliseconds + "ms..."); EditorUtility.SetDirty(target); } LayerMaskSettings(tree.layerMasks); EditorGUILayout.LabelField("Instances: " + tree.instanceCount.ToString("##,#"), EditorStyles.miniLabel); if (autoRespawnTrees.boolValue == false) { EditorGUILayout.HelpBox("Auto respawning is disabled for trees in the settings tab", MessageType.Warning); } if (GUILayout.Button(new GUIContent(" Respawn", EditorGUIUtility.IconContent("d_Refresh").image), GUILayout.Height(30f))) { Stopwatch sw = new Stopwatch(); sw.Restart(); script.SpawnTree(tree); sw.Stop(); Log.Add("Respawning tree: " + sw.Elapsed.Milliseconds + "ms..."); } } } } if (treeChange.changed) { EditorUtility.SetDirty(target); serializedObject.ApplyModifiedProperties(); } } }