예제 #1
0
        private GrassPrefab Duplicate(GrassPrefab source)
        {
            GrassPrefab dest = new GrassPrefab();

            dest.type      = source.type;
            dest.prefab    = source.prefab;
            dest.billboard = source.billboard;

            dest.mainColor       = source.mainColor;
            dest.secondairyColor = source.secondairyColor;
            dest.linkColors      = source.linkColors;

            dest.minMaxHeight = source.minMaxHeight;
            dest.minMaxWidth  = source.minMaxWidth;

            //Base
            dest.seed           = source.seed;
            dest.probability    = source.probability;
            dest.heightRange    = source.heightRange;
            dest.slopeRange     = source.slopeRange;
            dest.curvatureRange = source.curvatureRange;

            dest.layerMasks = new List <TerrainLayerMask>(source.layerMasks);
            for (int i = 0; i < dest.layerMasks.Count; i++)
            {
                dest.layerMasks[i] = new TerrainLayerMask(source.layerMasks[i].layerID, source.layerMasks[i].threshold);
            }

            dest.collisionCheck   = source.collisionCheck;
            dest.rejectUnderwater = source.rejectUnderwater;

            return(dest);
        }
예제 #2
0
        private void UpdateGrassItem(GrassPrefab item, DetailPrototype d)
        {
            d.healthyColor = item.mainColor;
            d.dryColor     = item.secondairyColor;

            d.minHeight = item.minMaxHeight.x;
            d.maxHeight = item.minMaxHeight.y;

            d.minWidth = item.minMaxWidth.x;
            d.maxWidth = item.minMaxWidth.y;

            d.noiseSpread      = item.noiseSize;
            d.prototype        = item.prefab;
            d.prototypeTexture = item.billboard;

            if (item.type == GrassType.Mesh && item.prefab)
            {
                d.renderMode       = DetailRenderMode.Grass; //Actually mesh
                d.usePrototypeMesh = true;
                d.prototype        = item.prefab;
                d.prototypeTexture = null;
            }
            if (item.type == GrassType.Billboard && item.billboard)
            {
                d.renderMode       = DetailRenderMode.GrassBillboard;
                d.usePrototypeMesh = false;
                d.prototypeTexture = item.billboard;
                d.prototype        = null;
            }
        }
예제 #3
0
        public void UpdateProperties(GrassPrefab item)
        {
            foreach (Terrain terrain in terrains)
            {
                //Note only works when creating these copies :/
                DetailPrototype[] detailPrototypes = terrain.terrainData.detailPrototypes;
                DetailPrototype   detailPrototype  = GetGrassPrototype(item, terrain);

                //Could have been removed
                if (detailPrototype == null)
                {
                    continue;
                }

                UpdateGrassItem(item, detailPrototype);

                detailPrototypes[item.index]         = detailPrototype;
                terrain.terrainData.detailPrototypes = detailPrototypes;

#if UNITY_EDITOR
                UnityEditor.EditorUtility.SetDirty(terrain);
                UnityEditor.EditorUtility.SetDirty(terrain.terrainData);
#endif
            }
        }
예제 #4
0
        private DetailPrototype GetGrassPrototype(GrassPrefab item, Terrain terrain)
        {
            if (item.index >= terrain.terrainData.detailPrototypes.Length)
            {
                return(null);
            }

            return(terrain.terrainData.detailPrototypes[item.index]);
        }
예제 #5
0
        public void SpawnGrass(GrassPrefab item, Terrain targetTerrain = null)
        {
            if (item.collisionCheck)
            {
                RebuildCollisionCacheIfNeeded();
            }

            item.instanceCount = 0;

            if (targetTerrain == null)
            {
                foreach (Terrain terrain in terrains)
                {
                    SpawnGrassOnTerrain(terrain, item);
                }
            }
            else
            {
                SpawnGrassOnTerrain(targetTerrain, item);
            }

            onGrassRespawn?.Invoke(item);
        }
예제 #6
0
 private DetailPrototype GetGrassPrototype(GrassPrefab item, Terrain terrain)
 {
     return(terrain.terrainData.detailPrototypes[item.index]);
 }
예제 #7
0
        public void SpawnGrass(GrassPrefab item)
        {
            if (item.collisionCheck)
            {
                RebuildCollisionCacheIfNeeded();
            }

            item.instanceCount = 0;

            foreach (Terrain terrain in terrains)
            {
                //2 texels per unit
                //int density = Mathf.NextPowerOfTwo(Mathf.RoundToInt(terrain.terrainData.size.x * 2f));
                //int[,] map = new int[density, density];
                //terrain.terrainData.SetDetailResolution(density, terrain.terrainData.detailResolutionPerPatch);

#if UNITY_EDITOR
                //UnityEditor.EditorUtility.DisplayProgressBar("Vegetation Spawner", "Spawning grass...", 1f);
#endif
                int[,] map = terrain.terrainData.GetDetailLayer(0, 0, terrain.terrainData.detailWidth, terrain.terrainData.detailHeight, item.index);

                for (int x = 0; x < terrain.terrainData.detailWidth; x++)
                {
                    for (int y = 0; y < terrain.terrainData.detailHeight; y++)
                    {
                        InitializeSeed(x * y + item.seed);

                        //Default
                        int instanceCount = 1;
                        map[x, y] = 0;

                        //XZ world position
                        Vector3 wPos = terrain.DetailToWorld(y, x);

                        Vector2 normalizedPos = terrain.GetNormalizedPosition(wPos);

                        if (item.collisionCheck)
                        {
                            //Check for collision
                            if (InsideOccupiedCell(terrain, wPos, normalizedPos))
                            {
                                continue;
                            }

                            /* 1 second slower
                             * RaycastHit hit;
                             * if (Physics.Raycast(wPos + (Vector3.up * 50f), -Vector3.up, out hit, 100f, -1))
                             * {
                             *  if (hit.collider.gameObject != terrain.gameObject)
                             *  {
                             *      continue;
                             *  }
                             * }
                             */
                        }

                        //Skip if failing probability check
                        if (((Random.value * 100f) <= item.probability) == false)
                        {
                            instanceCount = 0;
                            continue;
                        }

                        terrain.SampleHeight(normalizedPos, out _, out wPos.y, out _);

                        if (item.rejectUnderwater && wPos.y < waterHeight)
                        {
                            instanceCount = 0;
                            continue;
                        }
                        //Check height
                        if (wPos.y < item.heightRange.x || wPos.y > item.heightRange.y)
                        {
                            instanceCount = 0;
                            continue;
                        }

                        if (item.slopeRange.x > 0 || item.slopeRange.y < 90)
                        {
                            float slope = terrain.GetSlope(normalizedPos);
                            //Reject if slope check fails
                            if (slope < item.slopeRange.x || slope > item.slopeRange.y)
                            {
                                instanceCount = 0;
                                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)
                            {
                                instanceCount = 0;
                                continue;
                            }
                        }

                        //Reject based on layer masks
                        float      spawnChance = 0f;
                        Vector2Int texelIndex  = terrain.SplatmapTexelIndex(normalizedPos);

                        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(x * y + item.seed);
                        if ((Random.value <= spawnChance) == false)
                        {
                            instanceCount = 0;
                        }

                        //if (instanceCount == 1) DebugPoints.Add(wPos, true, sampler.slope);
                        item.instanceCount += instanceCount;
                        //Passed all conditions, spawn one instance here
                        map[x, y] = instanceCount;
                    }
                }


                terrain.terrainData.SetDetailLayer(0, 0, item.index, map);

#if UNITY_EDITOR
                UnityEditor.EditorUtility.ClearProgressBar();
#endif
            }

            onGrassRespawn?.Invoke(item);
        }
예제 #8
0
        private void DrawGrass()
        {
            EditorGUILayout.LabelField("Items", EditorStyles.boldLabel);

            grassScrollPos = EditorGUILayout.BeginScrollView(grassScrollPos, EditorStyles.textArea, GUILayout.Height(thumbSize + 10f));
            using (new EditorGUILayout.HorizontalScope())
            {
                for (int i = 0; i < script.grassPrefabs.Count; i++)
                {
                    Texture2D thumb = AssetPreview.GetAssetPreview(script.grassPrefabs[i].prefab);
                    if (script.grassPrefabs[i].type == SpawnerBase.GrassType.Billboard)
                    {
                        thumb = script.grassPrefabs[i].billboard;
                    }
                    if (thumb == null)
                    {
                        thumb = EditorGUIUtility.IconContent("d_BuildSettings.Broadcom").image as Texture2D;
                    }

                    if (GUILayout.Button(new GUIContent("", thumb), (selectedGrassID == i) ? VegetationSpawnerEditor.PreviewTexSelected : VegetationSpawnerEditor.PreviewTex, GUILayout.MinHeight(thumbSize), GUILayout.MaxWidth(thumbSize), GUILayout.MaxHeight(thumbSize)))
                    {
                        selectedGrassID = i;
                    }
                }
            }
            EditorGUILayout.EndScrollView();

            //Edge case: Clamp in case there's a switch to scene with less items
            selectedGrassID = Mathf.Min(selectedGrassID, script.grassPrefabs.Count - 1);

            VegetationSpawner.GrassPrefab grass = script.grassPrefabs.Count > 0 ? script.grassPrefabs[selectedGrassID] : null;

            using (new EditorGUILayout.HorizontalScope())
            {
                if (grass != null)
                {
                    EditorGUILayout.LabelField("Instances: " + grass.instanceCount.ToString("##,#"), EditorStyles.miniLabel);
                }
                GUILayout.FlexibleSpace();

                if (GUILayout.Button(new GUIContent("Add", EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Toolbar Plus" : "Toolbar Plus").image, "Add new item")))
                {
                    VegetationSpawner.GrassPrefab newGrass = new SpawnerBase.GrassPrefab();
                    script.grassPrefabs.Add(newGrass);

                    newGrass.seed   = Random.Range(0, 9999);
                    selectedGrassID = script.grassPrefabs.Count - 1;
                    newGrass.index  = script.grassPrefabs.Count;

                    script.RefreshGrassPrototypes();
                }

                if (GUILayout.Button(new GUIContent("", EditorGUIUtility.IconContent("TreeEditor.Duplicate").image, "Duplicate item")))
                {
                    GrassPrefab newGrass = Duplicate(script.grassPrefabs[selectedGrassID]);

                    script.grassPrefabs.Add(newGrass);
                    selectedGrassID = script.grassPrefabs.Count - 1;
                    newGrass.index  = script.grassPrefabs.Count;

                    script.RefreshGrassPrototypes();

                    //Spawn it now so changes are visible
                    script.SpawnGrass(script.grassPrefabs[selectedGrassID]);
                }

                if (GUILayout.Button(new GUIContent("", EditorGUIUtility.IconContent("d_TreeEditor.Trash").image, "Remove")))
                {
                    script.grassPrefabs.RemoveAt(selectedGrassID);

                    selectedGrassID = script.grassPrefabs.Count - 1;

                    script.RefreshGrassPrototypes();
                }
            }

            if (grass != null)
            {
                Undo.RecordObject(script, "Modified grass item");

                serializedObject.Update();
                using (var grassChange = new EditorGUI.ChangeCheckScope())
                {
                    EditorGUILayout.LabelField("Appearance", EditorStyles.boldLabel);

                    grass.type = (SpawnerBase.GrassType)EditorGUILayout.Popup("Render type", (int)grass.type, new string[] { "Mesh", "Billboard" });

                    if (grass.type == SpawnerBase.GrassType.Mesh)
                    {
                        grass.prefab = EditorGUILayout.ObjectField("Prefab", grass.prefab, typeof(GameObject), true) as GameObject;
                    }
                    if (grass.type == SpawnerBase.GrassType.Billboard)
                    {
                        grass.billboard = EditorGUILayout.ObjectField("Billboard", grass.billboard, typeof(Texture2D), true) as Texture2D;
                    }

                    grass.mainColor  = EditorGUILayout.ColorField("Main", grass.mainColor);
                    grass.linkColors = EditorGUILayout.Toggle(new GUIContent("Link", "Set the main and secondary color with one value"), grass.linkColors);

                    if (grass.linkColors)
                    {
                        grass.secondairyColor = grass.mainColor;
                    }
                    else
                    {
                        grass.secondairyColor = EditorGUILayout.ColorField("Secondary", grass.secondairyColor);
                    }
                    VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Width", "Min/max width of the mesh"), ref grass.minMaxWidth, 0f, 2f);
                    VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Length", "Min/max length of the mesh"), ref grass.minMaxHeight, 0f, 2f);

                    if (grassChange.changed)
                    {
                        EditorUtility.SetDirty(target);
                        script.UpdateProperties(grass);
                    }

                    EditorGUILayout.Space();

                    EditorGUILayout.LabelField("Spawning rules", EditorStyles.boldLabel);
                    using (new EditorGUILayout.HorizontalScope())
                    {
                        grass.seed = EditorGUILayout.IntField("Seed", grass.seed, GUILayout.MaxWidth(EditorGUIUtility.labelWidth + 50f));
                        if (GUILayout.Button("Randomize", GUILayout.MaxWidth(100f)))
                        {
                            grass.seed = Random.Range(0, 99999);
                        }
                    }

                    grass.probability = EditorGUILayout.Slider("Spawn chance %", grass.probability, 0f, 100f);

                    EditorGUILayout.Space();

                    grass.collisionCheck   = EditorGUILayout.Toggle(new GUIContent("Collision check", "Take into account the collision cache to avoid spawning inside colliders (see Settings tab)"), grass.collisionCheck);
                    grass.rejectUnderwater = EditorGUILayout.Toggle(new GUIContent("Remove underwater", "The water height level can be set in the settings tab"), grass.rejectUnderwater);
                    VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Height range", "Min/max height this item can spawn at"), ref grass.heightRange, 0f, 1000f);
                    VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Slope range", "Min/max slope (0-90 degrees) this item can spawn at"), ref grass.slopeRange, 0f, 90f);
                    VegetationSpawnerEditor.DrawRangeSlider(new GUIContent("Curvature range", "0=Concave (bowl), 0.5=flat, 1=convex (edge)"), ref grass.curvatureRange, 0f, 1f);

                    EditorGUILayout.Space();

                    LayerMaskSettings(grass.layerMasks);

                    if (grassChange.changed)
                    {
                        serializedObject.ApplyModifiedProperties();
                    }
                }
                EditorGUILayout.Space();

                if (GUILayout.Button(new GUIContent(" Respawn", EditorGUIUtility.IconContent("d_Refresh").image), GUILayout.Height(30f)))
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Restart();

                    script.SpawnGrass(script.grassPrefabs[selectedGrassID]);

                    sw.Stop();

                    Log.Add("Respawning grass: " + sw.Elapsed.Seconds + " seconds...");
                }
            }
        }
예제 #9
0
        private void SpawnGrassOnTerrain(Terrain terrain, GrassPrefab item)
        {
            //int[,] map = terrain.terrainData.GetDetailLayer(0, 0, terrain.terrainData.detailWidth, terrain.terrainData.detailHeight, item.index);
            int[,] map = new int[terrain.terrainData.detailWidth, terrain.terrainData.detailHeight];

            int counter   = 0;
            int cellCount = terrain.terrainData.detailWidth * terrain.terrainData.detailHeight;

            if (item.enabled)
            {
                for (int x = 0; x < terrain.terrainData.detailWidth; x++)
                {
                    for (int y = 0; y < terrain.terrainData.detailHeight; y++)
                    {
                        counter++;

 #if UNITY_EDITOR
                        //Show progress bar every 10%
                        if (counter % (cellCount / 10) == 0)
                        {
                            UnityEditor.EditorUtility.DisplayProgressBar("Vegetation Spawner", "Spawning " + item.name + " on " + terrain.name, (float)counter / (float)cellCount);
                        }
#endif
                        InitializeSeed(x * y + item.seed);

                        //Default
                        int instanceCount = 1;

                        //XZ world position
                        Vector3 wPos = terrain.DetailToWorld(y, x);

                        Vector2 normalizedPos = terrain.GetNormalizedPosition(wPos);

                        //Skip if failing probability check
                        if (((Random.value * 100f) <= item.probability) == false)
                        {
                            instanceCount = 0;
                            continue;
                        }

                        if (item.collisionCheck)
                        {
                            //Check for collision
                            if (InsideOccupiedCell(terrain, wPos, normalizedPos))
                            {
                                instanceCount = 0;
                                continue;
                            }
                        }

                        terrain.SampleHeight(normalizedPos, out _, out wPos.y, out _);

                        if (item.rejectUnderwater && wPos.y < waterHeight)
                        {
                            instanceCount = 0;
                            continue;
                        }
                        //Check height
                        if (wPos.y < item.heightRange.x || wPos.y > item.heightRange.y)
                        {
                            instanceCount = 0;
                            continue;
                        }

                        if (item.slopeRange.x > 0 || item.slopeRange.y < 90)
                        {
                            float slope = terrain.GetSlope(normalizedPos);
                            //Reject if slope check fails
                            if (slope < item.slopeRange.x || slope > item.slopeRange.y)
                            {
                                instanceCount = 0;
                                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)
                            {
                                instanceCount = 0;
                                continue;
                            }
                        }

                        //Reject based on layer masks
                        float spawnChance = 0f;
                        if (item.layerMasks.Count == 0)
                        {
                            spawnChance = 100f;
                        }
                        else
                        {
                            splatmapTexelIndex = terrain.SplatmapTexelIndex(normalizedPos);
                        }

                        foreach (TerrainLayerMask layer in item.layerMasks)
                        {
                            Texture2D splat = terrain.terrainData.GetAlphamapTexture(GetSplatmapID(layer.layerID));

                            m_splatmapColor = splat.GetPixel(splatmapTexelIndex.x, splatmapTexelIndex.y);

                            int   channel = layer.layerID % 4;
                            float value   = SampleChannel(m_splatmapColor, channel);

                            if (value > 0)
                            {
                                value = Mathf.Clamp01(value - layer.threshold);
                            }
                            value *= 100f;

                            spawnChance += value;
                        }
                        InitializeSeed(x * y + item.seed);
                        if ((Random.value <= spawnChance) == false)
                        {
                            instanceCount = 0;
                        }

                        //if (instanceCount == 1) DebugPoints.Instance.Add(wPos, true, 0f);
                        item.instanceCount += instanceCount;
                        //Passed all conditions, spawn one instance here
                        map[x, y] = instanceCount;
                    }
                }
            }

            terrain.terrainData.SetDetailLayer(0, 0, item.index, map);

#if UNITY_EDITOR
            UnityEditor.EditorUtility.ClearProgressBar();
#endif
        }