예제 #1
0
        //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));
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
            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);
            }
예제 #4
0
            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);
            }
예제 #5
0
 private TreePrototype GetTreePrototype(TreePrefab item, Terrain terrain)
 {
     return(terrain.terrainData.treePrototypes[item.index]);
 }
예제 #6
0
        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]);
            }
        }
예제 #7
0
        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
        }
예제 #8
0
        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();
                }
            }
        }