grass-rendering-prototype/Assets/TerrainTool/GrasFeld.cs
2025-09-25 15:22:35 +02:00

331 lines
10 KiB
C#

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<Vector2Int, ChunkData> allChunckData;
List<Vector2Int> ChunksModifyed = new List<Vector2Int>();
List<Vector2Int> ActiveChunks;
public List<ChunkGameObject> AlleChunkObjekte = new List<ChunkGameObject>();
#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<Vector2Int, ChunkData>();
List<ChunkData> zuÄnderndeChunks = new List<ChunkData>();
//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<ChunkGameObject> ChunkObjectsToRecalculate = new List<ChunkGameObject>();
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<ChunkGameObject>();
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<ChunkGameObject>().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<ChunkGameObject>().DebugGrasTextur = tempTextur;
chunkObject.GetComponent<ChunkGameObject>().pos = chunk.Key;
chunkObject.GetComponent<ChunkGameObject>().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<Vector2Int> GetActiveChunckPositions(float brushRadius, Vector2 brushPos)
{
List<Vector2Int> activeChuncksTemp = new List<Vector2Int>();
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<ChunkPixel> Data;
public FixedList32Bytes<int> VerwendeteGrassorten;
public ChunkData(Vector2Int pos)
{
isEmpty = false;
this.pos = pos;
Data = new NativeArray<ChunkPixel>(256, Allocator.Persistent);
VerwendeteGrassorten = new FixedList32Bytes<int>();
}
}
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()
};
}
}