using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Burst.CompilerServices; using Unity.Collections; using Unity.VisualScripting; using UnityEditor; using UnityEngine; public class GrasFeld : MonoBehaviour { float grasDichteProMeter = 1.6f; static float basisChunkGroese = 5f; public GameObject GrasChunkPrefab; public Dictionary allChunckData; List ChunksModifyed = new List(); List ActiveChunks; public List AlleChunkObjekte = new List(); #if UNITY_EDITOR void OnEnable() { AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; } void OnDisable() { AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; } public void OnBeforeAssemblyReload() { ClearAllChunkData(); } public void ClearAllChunkData() { if (allChunckData == null) return; var enumerator = allChunckData.GetEnumerator(); while (enumerator.MoveNext()) { var pair = enumerator.Current; pair.Value.Data.Dispose(); } allChunckData.Clear(); } #endif public void OnAfterAssemblyReload() { } private void Update() { Vector3 cameraPosition = Camera.main.transform.position; foreach (var chunk in AlleChunkObjekte) { chunk.SetLOD(cameraPosition); } } public void GrasZeichnen(GrassortenScriptableObject sorte, Vector2 pos, float pinselRadius, bool zeichenInverter) { if (allChunckData == null) allChunckData = new Dictionary(); List zuÄnderndeChunks = new List(); //Alle chuncks finden in die Gezeichnet werden könnte ActiveChunks = GetActiveChunckPositions(pinselRadius, pos); foreach (var potential in ActiveChunks) { if(allChunckData.TryGetValue(potential, out var chunckData)) { zuÄnderndeChunks.Add(chunckData); } else { allChunckData.Add(potential, new ChunkData(potential)); if (allChunckData.TryGetValue(potential, out chunckData)) zuÄnderndeChunks.Add(chunckData); else Debug.LogError("Konnte Chunk nich adden " + potential); } } ChunksModifyed.Clear(); foreach(ChunkData chunk in zuÄnderndeChunks) { allChunckData[chunk.pos] = ZeichneInChunk(chunk, sorte, pos, pinselRadius, zeichenInverter); ChunksModifyed.Add(chunk.pos); if (allChunckData[chunk.pos].isEmpty == true) { allChunckData[chunk.pos].Data.Dispose(); allChunckData.Remove(chunk.pos); } } MakeChunks(); } public void MakeChunks() { List ChunkObjectsToRecalculate = new List(); foreach(Vector2Int chunkPos in ChunksModifyed) { ChunkGameObject chunkObject; foreach (ChunkGameObject currenChunckObject in AlleChunkObjekte) { if (currenChunckObject.ContainsChunk(chunkPos)) { chunkObject = currenChunckObject; goto ChunkExistiert; } } //Kein Chunk gefunden GameObject tempGO = Instantiate(GrasChunkPrefab); tempGO.transform.position = new Vector3(chunkPos.x, 0, chunkPos.y); tempGO.transform.parent = transform; tempGO.name = "Chunk LOD3"; chunkObject = tempGO.GetComponent(); chunkObject.SetPosToGrid(); chunkObject.grasFeld = this; AlleChunkObjekte.Add(chunkObject); ChunkExistiert: chunkObject.SetUpdate(chunkPos); if(ChunkObjectsToRecalculate.Contains(chunkObject) == false) ChunkObjectsToRecalculate.Add(chunkObject); } foreach(var chunkObj in ChunkObjectsToRecalculate) { chunkObj.DoMeshUpdate(); } return; int count = 0; while (transform.childCount > count) { var checkPos = transform.GetChild(count).gameObject.GetComponent().pos; if (ChunksModifyed.Contains(checkPos)) { DestroyImmediate(transform.GetChild(count).gameObject); } else count++; } foreach (var chunk in allChunckData) { if (ChunksModifyed.Contains(chunk.Key) == false) continue; Texture2D tempTextur = new Texture2D(16,16); for (int i = 0; i < 16; i++) for (int j = 0; j < 16; j++) { tempTextur.SetPixel(i, j, new Color(chunk.Value.Data[i * 16 + j][0] * 256, 0, 0)); } tempTextur.Apply(); GameObject chunkObject = Instantiate(GrasChunkPrefab); chunkObject.transform.position = new Vector3(chunk.Key.x, 0, chunk.Key.y); chunkObject.transform.parent = transform; chunkObject.GetComponent().DebugGrasTextur = tempTextur; chunkObject.GetComponent().pos = chunk.Key; chunkObject.GetComponent().hasChanged = ChunksModifyed.Contains(chunk.Key); chunkObject.name = "" + chunk.Key; } ChunksModifyed.Clear(); } static ChunkData ZeichneInChunk(ChunkData chunk, GrassortenScriptableObject grassorte, Vector2 pos, float brushSize, bool shift) { int grassortenIndex = -1; if (chunk.VerwendeteGrassorten.Contains(grassorte.grassorteId) == false) chunk.VerwendeteGrassorten.Add(grassorte.grassorteId); grassortenIndex = chunk.VerwendeteGrassorten.IndexOf(grassorte.grassorteId); int emptyCount = 0; for(int i = 0; i < 16; i++) for(int j = 0; j < 16; j++) { var currentValue = chunk.Data[i * 16 + j]; byte val = currentValue[grassortenIndex]; Vector2 inchunckPos = new Vector2(chunk.pos.x + i * (basisChunkGroese / 16f), chunk.pos.y + j * (basisChunkGroese / 16f)); if (Vector2.Distance(inchunckPos, pos) < brushSize) val = shift ? (byte)0 : (byte)255; currentValue[grassortenIndex] = val; if (currentValue[grassortenIndex] == 0) emptyCount++; chunk.Data[i * 16 + j] = currentValue; } if (emptyCount == (16 * 16)) chunk.isEmpty = true; return chunk; } List GetActiveChunckPositions(float brushRadius, Vector2 brushPos) { List activeChuncksTemp = new List(); int minPosX = Mathf.FloorToInt((brushPos.x - brushRadius) / basisChunkGroese) * (int)basisChunkGroese; int minPosY = Mathf.FloorToInt((brushPos.y - brushRadius) / basisChunkGroese) * (int)basisChunkGroese; int maxPosX = Mathf.CeilToInt((brushPos.x + brushRadius) / basisChunkGroese) * (int)basisChunkGroese; int maxPosY = Mathf.CeilToInt((brushPos.y + brushRadius) / basisChunkGroese) * (int)basisChunkGroese; for (int x = minPosX; x < maxPosX; x += (int)basisChunkGroese) { for (int y = minPosY; y < maxPosY; y += (int)basisChunkGroese) { Vector2Int chunckPos = new Vector2Int(x, y); if (chunckPos.x < 0 || chunckPos.x > 1000 || chunckPos.y < 0 || chunckPos.y > 1000) continue; if (IsChunckInRadius(brushPos, brushRadius, chunckPos, basisChunkGroese)) activeChuncksTemp.Add(chunckPos); } } return activeChuncksTemp; } static bool IsChunckInRadius(Vector2 pos, float radius, Vector2Int chunckPos, float chunckSize) { // Find the closest point to the circle within the rectangle Vector2 closestPosInChunck = new Vector2(Mathf.Clamp(pos.x, chunckPos.x, chunckPos.x + chunckSize), Mathf.Clamp(pos.y, chunckPos.y, chunckPos.y + chunckSize)); // Calculate the distance between the circle's center and this closest point Vector2 brushToPoint = pos - closestPosInChunck; return brushToPoint.sqrMagnitude < radius * radius; } #if UNITY_EDITOR private void OnDrawGizmos() { if (Application.isEditor) { Vector3 cameraPosition = SceneView.lastActiveSceneView.camera.transform.position; foreach (var chunk in AlleChunkObjekte) { chunk.SetLOD(cameraPosition); } } if(false) if (ActiveChunks != null) foreach(var p in ActiveChunks) { Gizmos.DrawWireCube(new Vector3(p.x + basisChunkGroese / 2f, 0, p.y + basisChunkGroese / 2f), Vector3.one * basisChunkGroese); } } #endif } public struct ChunkData { public bool isEmpty; public Vector2Int pos; public NativeArray Data; public FixedList32Bytes VerwendeteGrassorten; public ChunkData(Vector2Int pos) { isEmpty = false; this.pos = pos; Data = new NativeArray(256, Allocator.Persistent); VerwendeteGrassorten = new FixedList32Bytes(); } } public struct ChunkPixel { byte p1, p2, p3, p4; public byte this[int index] { get => index switch { 0 => p1, 1 => p2, 2 => p3, 3 => p4, _ => throw new IndexOutOfRangeException() }; set => _ = index switch { 0 => (p1 = value), 1 => (p2 = value), 2 => (p3 = value), 3 => (p4 = value), _ => throw new IndexOutOfRangeException() }; } }