using System; using System.Collections.Generic; using System.Security.Cryptography; using UnityEngine; using Random = UnityEngine.Random; public class GrassField : MonoBehaviour { public int size = 100; public int numChunks = 5; public Material grassMaterial; public GrassChunk chunkPrefab; private struct BladeData { public Vector3 rootPosition; public Vector3 tipOffset; } // ReSharper disable Unity.PerformanceAnalysis public void GenerateGrassField() { Debug.Log("Generating grass field... "); var blades = GenerateBladesRandom(100000); CreateChunks(blades); Debug.Log("Generating grass field done"); } private BladeData[] GenerateBladesRandom(int num) { var blades = new BladeData[num]; for (int i = 0; i < num; i++) { blades[i].rootPosition = new Vector3( Random.Range(0f, size), Random.Range(-0.5f, 0.5f), Random.Range(0f, size) ); blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f); } return blades; } private BladeData[] GenerateBladesEven() { var blades = new BladeData[size * size * 100]; for (int x = 0; x < size * 10; x++) for (int y = 0; y < size * 10; y++) { var i = x + y * size * 10; blades[i].rootPosition = new Vector3(x/10.0f, 0.0f, y/10.0f); blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f); } return blades; } private void CreateChunks(BladeData[] blades) { GameObject chunks; // If a child called "Chunks" exists, destroy it -> children are also destroyed var chunksTransform = gameObject.transform.Find("Chunks"); if (chunksTransform != null) { // Destroy(obj) does not work in edit mode DestroyImmediate(chunksTransform.gameObject); } // create GameObject "Chunks" as a container for all GrassChunk objects chunks = new GameObject("Chunks"); chunks.transform.SetParent(this.transform, false); var chunkSize = ((float)size) / numChunks; // bounds of the chunk (relative to chunk) - do not consider y position var chunkBounds = new Bounds( new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize), new Vector3(chunkSize, 10000f, chunkSize) ); for (int x = 0; x < numChunks; x++) for (int y = 0; y < numChunks; y++) { var chunkPos = new Vector3(x, 0.0f, y) * chunkSize; List rootPositions = new(); List tipOffsets = new(); List indices = new(); foreach (var blade in blades) { // position relative to chunk var localRootPos = blade.rootPosition - chunkPos; if (chunkBounds.Contains(localRootPos)) { rootPositions.Add(localRootPos); tipOffsets.Add(blade.tipOffset); indices.Add(rootPositions.Count - 1); } } var mesh = new Mesh(); // 16 bit (default) supports 65536 vertices, 32 bit supports 4 billion mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; mesh.name = "grass_chunk[" + x + "," + y + "]"; mesh.SetVertices(rootPositions); mesh.SetUVs(0, tipOffsets); // Vector3 UVs // AABB gets calculated automatically (considers all root positions) mesh.SetIndices(indices, MeshTopology.Points, 0); // update the AABB to match the actual grass (not just the root positions) // use mesh.bounds rather than meshRenderer.localBounds, // because the latter gets overwritten automatically var previousBounds = mesh.bounds; float width = 1f; float height = 1f; mesh.bounds = new Bounds( previousBounds.center + new Vector3(0f, height * 0.5f, 0f), previousBounds.size + new Vector3(width, height, width) ); var chunk = Instantiate(chunkPrefab, chunks.transform); chunk.name = "Chunk [" + x + ", " + y +"]"; chunk.transform.localPosition = chunkPos; var meshFilter = chunk.GetComponent(); meshFilter.sharedMesh = mesh; var meshRenderer = chunk.GetComponent(); meshRenderer.sharedMaterial = grassMaterial; } } private void OnDrawGizmos() { Gizmos.color = new Color(0f, 0.2f, 1f, 1f); var chunkSize = ((float)size) / numChunks; var bounds = new Bounds( new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize), new Vector3(chunkSize, 0f, chunkSize) ); for (int x = 0; x < numChunks; x++) for (int y = 0; y < numChunks; y++) { var chunkPos = new Vector3(x, 0.0f, y) * chunkSize; Gizmos.DrawWireCube(bounds.center + chunkPos + transform.position, bounds.size); } } }