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); }
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; } }
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 } }
private DetailPrototype GetGrassPrototype(GrassPrefab item, Terrain terrain) { if (item.index >= terrain.terrainData.detailPrototypes.Length) { return(null); } return(terrain.terrainData.detailPrototypes[item.index]); }
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); }
private DetailPrototype GetGrassPrototype(GrassPrefab item, Terrain terrain) { return(terrain.terrainData.detailPrototypes[item.index]); }
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); }
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..."); } } }
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 }