Files
Fishing2/Packages/com.jbooth.microverse.vegetation/Scripts/SpawnProcessor_Vegetation.cs
2025-06-09 23:23:13 +08:00

479 lines
18 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
using Unity.Collections;
namespace JBooth.MicroVerseCore
{
public partial class SpawnProcessor
{
Dictionary<Terrain, List<TreeJobHolder>> treeJobHolders = new Dictionary<Terrain, List<TreeJobHolder>>();
Dictionary<Terrain, List<DetailJobHolder>> detailJobHolders = new Dictionary<Terrain, List<DetailJobHolder>>();
void FinishedRendereringVegetation(MicroVerse.DataCache dataCache, Dictionary<Terrain, Dictionary<int, List<RenderTexture>>> resultBuffers)
{
RenderTexture.active = null;
foreach (var td in dataCache.treeDatas.Values)
{
if (td.treeClearMap != null)
{
RenderTexture.ReleaseTemporary(td.treeClearMap);
td.treeClearMap = null;
}
}
foreach (var dd in dataCache.detailDatas.Values)
{
if (dd.clearMap != null)
{
RenderTexture.ReleaseTemporary(dd.clearMap);
dd.clearMap = null;
}
}
// merge detail layers together if they are for the same index
Material mergeMat = new Material(Shader.Find("Hidden/MicroVerse/CombineDetailBuffers"));
foreach (var tk in resultBuffers.Keys)
{
var dbuffer = resultBuffers[tk];
foreach (var k in dbuffer.Keys)
{
var resultList = dbuffer[k];
if (resultList.Count > 1)
{
RenderTexture targetA = RenderTexture.GetTemporary(resultList[0].descriptor);
RenderTexture targetB = RenderTexture.GetTemporary(resultList[0].descriptor);
targetA.name = "MicroVerse::GenerateDetails";
targetB.name = "MicroVerse::GenerateDetails";
Graphics.Blit(resultList[0], targetA);
RenderTexture.ReleaseTemporary(resultList[0]);
for (int i = 1; i < resultList.Count; ++i)
{
var buffer = resultList[i];
mergeMat.SetTexture("_Merge", buffer);
Graphics.Blit(targetA, targetB, mergeMat);
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(buffer);
(targetA, targetB) = (targetB, targetA);
}
resultList.Clear();
resultList.Add(targetA);
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(targetB);
}
}
foreach (var k in dbuffer.Keys)
{
var resultList = dbuffer[k];
if (resultList.Count != 1)
{
Debug.LogError("Detail channels have not been merged, memory will be leaked");
}
var holder = new DetailJobHolder();
holder.terrain = tk;
holder.AddJob(resultList[0], k);
if (detailJobHolders.ContainsKey(tk))
{
detailJobHolders[tk].Add(holder);
}
else
{
detailJobHolders.Add(tk, new List<DetailJobHolder>() { holder });
}
}
}
Object.DestroyImmediate(mergeMat);
}
void RenderVegetationClearLayers(Terrain[] terrains, MicroVerse.DataCache dataCache)
{
bool needsTreeClearMap = false;
bool needsDetailClearmap = false;
foreach (var m in spawners)
{
ITreeModifier tm = m as ITreeModifier;
IDetailModifier dm = m as IDetailModifier;
if (tm != null)
{
needsTreeClearMap |= tm.NeedTreeClear();
}
if (dm != null)
{
needsDetailClearmap |= dm.NeedDetailClear();
}
}
// generate clear maps for everything
foreach (var terrain in terrains)
{
var terrainBounds = TerrainUtil.ComputeTerrainBounds(terrain);
var indexMap = dataCache.indexMaps[terrain];
var weightMap = dataCache.weightMaps[terrain];
var heightmapGen = dataCache.heightMaps[terrain];
var normalGen = dataCache.normalMaps[terrain];
var curvatureGen = dataCache.curvatureMaps[terrain];
var flowGen = dataCache.flowMaps[terrain];
RenderTexture clearMap = null;
RenderTexture detailClearMap = null;
if (needsTreeClearMap)
{
int size = terrain.terrainData.alphamapResolution;
clearMap = RenderTexture.GetTemporary(size, size, 0, RenderTextureFormat.RG16, RenderTextureReadWrite.Linear);
RenderTexture.active = clearMap;
GL.Clear(false, true, Color.clear);
RenderTexture.active = null;
}
if (needsDetailClearmap)
{
int size = terrain.terrainData.detailResolution;
detailClearMap = RenderTexture.GetTemporary(size, size, 0, RenderTextureFormat.RG16, RenderTextureReadWrite.Linear);
RenderTexture.active = detailClearMap;
GL.Clear(false, true, Color.clear);
RenderTexture.active = null;
}
TreeData td = new TreeData(terrain, heightmapGen, normalGen, curvatureGen, flowGen, clearMap, dataCache);
dataCache.treeDatas[terrain] = td;
DetailData dd = new DetailData(terrain, heightmapGen, normalGen, curvatureGen, flowGen, detailClearMap, dataCache);
dataCache.detailDatas[terrain] = dd;
foreach (var s in spawners)
{
ITreeModifier treeModifier = s as ITreeModifier;
IDetailModifier detailModifier = s as IDetailModifier;
if (treeModifier != null)
{
if (terrainBounds.Intersects(treeModifier.GetBounds()))
{
treeModifier.ApplyTreeClear(td);
}
}
if (detailModifier != null)
{
if (terrainBounds.Intersects(detailModifier.GetBounds()))
{
detailModifier.ApplyDetailClear(dd);
}
}
}
}
// reset layers
foreach (var terrain in terrains)
{
if (dataCache.treeDatas.ContainsKey(terrain))
{
TreeData td = dataCache.treeDatas[terrain];
td.layerIndex = -1;
}
if (dataCache.detailDatas.ContainsKey(terrain))
{
DetailData dd = dataCache.detailDatas[terrain];
dd.layerIndex = -1;
}
}
}
void RenderDetailStamp(Terrain[] terrains, IDetailModifier detailModifier, MicroVerse.DataCache dataCache,
Dictionary<Terrain, Dictionary<int, List<RenderTexture>>> resultBuffers)
{
Profiler.BeginSample("Generate Details");
foreach (var terrain in terrains)
{
var terrainBounds = TerrainUtil.ComputeTerrainBounds(terrain);
if (terrainBounds.Intersects(detailModifier.GetBounds()))
{
var dd = dataCache.detailDatas[terrain];
var od = dataCache.occlusionDatas[terrain];
detailModifier.ApplyDetailStamp(dd, resultBuffers, od);
}
}
Profiler.EndSample();
}
void RenderTreeStamp(Terrain[] terrains, ITreeModifier treeModifier, MicroVerse.DataCache dataCache, bool allSDF, bool enableTreeSDF)
{
foreach (var terrain in terrains)
{
var terrainBounds = TerrainUtil.ComputeTerrainBounds(terrain);
if (terrainBounds.Intersects(treeModifier.GetBounds()))
{
var occlusionData = dataCache.occlusionDatas[terrain];
TreeData td = dataCache.treeDatas[terrain];
treeModifier.ApplyTreeStamp(td, treeJobHolders, occlusionData);
}
}
bool needToGenerateForChildren = treeModifier.NeedToGenerateSDFForChilden();
if (allSDF || treeModifier.NeedSDF() || needToGenerateForChildren || (treeModifier.OccludesOthers() && enableTreeSDF))
{
bool sdfOthers = treeModifier.OccludesOthers() && enableTreeSDF;
foreach (var terrain in terrains)
{
var terrainBounds = TerrainUtil.ComputeTerrainBounds(terrain);
if (terrainBounds.Intersects(treeModifier.GetBounds()))
{
var od = dataCache.occlusionDatas[terrain];
od?.RenderTreeSDF(terrain, dataCache.occlusionDatas, sdfOthers || allSDF);
if (needToGenerateForChildren && od != null && od.currentTreeSDF != null)
{
RenderTexture rt = RenderTexture.GetTemporary(od.currentTreeSDF.descriptor);
Graphics.Blit(od.currentTreeSDF, rt);
treeModifier.SetSDF(terrain, rt);
}
}
}
}
foreach (var terrain in terrains)
{
var terrainBounds = TerrainUtil.ComputeTerrainBounds(terrain);
var occlusionData = dataCache.occlusionDatas[terrain];
if (dataCache.treeDatas.ContainsKey(terrain))
{
if (terrainBounds.Intersects(treeModifier.GetBounds()))
{
var td = dataCache.treeDatas[terrain];
treeModifier.ProcessTreeStamp(td, treeJobHolders, occlusionData);
}
}
}
}
void InitTerrainVegetation(Terrain terrain, List<TreePrototypeSerializable> treePrototypes, List<DetailPrototypeSerializable> detailPrototypes)
{
TreePrototype[] trees = new TreePrototype[treePrototypes.Count];
DetailPrototype[] details = new DetailPrototype[detailPrototypes.Count];
for (int i = 0; i < treePrototypes.Count; ++i) { trees[i] = treePrototypes[i].GetPrototype(); }
for (int i = 0; i < detailPrototypes.Count; ++i) { details[i] = detailPrototypes[i].GetPrototype(); }
// try to avoid calling any unity terrain API functions if at all possible. This saves 7ms
// on the demo scene, which is before we even start GPU processing, so big win.
var existingTrees = terrain.terrainData.treePrototypes;
var existingDetails = terrain.terrainData.detailPrototypes;
bool setTrees = false;
bool setDetails = false;
if (existingTrees.Length != trees.Length)
setTrees = true;
if (existingDetails.Length != details.Length)
setDetails = true;
if (!setTrees)
{
for (int i = 0; i < trees.Length; ++i)
{
if (!trees[i].Equals(existingTrees[i]))
{
setTrees = true;
break;
}
}
}
if (!setDetails)
{
for (int i = 0; i < details.Length; ++i)
{
if (!details[i].Equals(existingDetails[i]))
{
setDetails = true;
break;
}
#if UNITY_2022_2_OR_NEWER
// UNITY BUG: They updated the struct, but not the equality operator, nice..
if (details[i].positionJitter != existingDetails[i].positionJitter ||
details[i].alignToGround != existingDetails[i].alignToGround)
{
setDetails = true;
break;
}
#endif
}
}
if (setTrees)
{
terrain.terrainData.SetTreeInstances(new TreeInstance[0], false);
terrain.terrainData.treePrototypes = trees;
}
if (setDetails)
{
terrain.terrainData.detailPrototypes = details;
}
}
void CancelVegetationJobs(MicroVerse.DataCache dataCache)
{
foreach (var lst in treeJobHolders.Values)
{
foreach (var h in lst)
{
h.canceled = true;
}
}
foreach (var lst in detailJobHolders.Values)
{
foreach (var h in lst)
{
h.canceled = true;
}
}
if (dataCache != null)
{
foreach (var td in dataCache.treeDatas.Values)
{
if (td.treeClearMap != null)
{
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(td.treeClearMap);
td.treeClearMap = null;
}
}
foreach (var td in dataCache.detailDatas.Values)
{
if (td.clearMap != null)
{
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(td.clearMap);
td.clearMap = null;
}
}
}
}
List<Terrain> finishedTrees = new List<Terrain>();
public void ApplyTrees()
{
Profiler.BeginSample("Apply Trees");
finishedTrees.Clear();
foreach (var lst in treeJobHolders.Values)
{
for (int i = 0; i < lst.Count; ++i)
{
if (lst[i].canceled)
{
if (lst[i].IsDone())
{
lst[i].Dispose();
lst.RemoveAt(i);
i--;
}
}
}
}
foreach (var terrain in treeJobHolders.Keys)
{
var lst = treeJobHolders[terrain];
bool isDone = true;
foreach (var h in lst)
{
if (h.IsDone() == false)
{
isDone = false;
break;
}
}
if (lst.Count == 0)
{
finishedTrees.Add(terrain);
}
if (isDone && lst.Count > 0)
{
int completeCount = 0;
foreach (var h in lst)
{
h.handle.Complete();// TODO: they should all be done, but aren't!? wtf?
completeCount += h.job.count[0];
}
NativeArray<TreeInstance> totalInstances = new NativeArray<TreeInstance>(completeCount, Allocator.Temp);
int destIndex = 0;
foreach (var h in lst)
{
if (h.job.count[0] > 0)
{
var range = h.job.trees.GetSubArray(0, h.job.count[0]);
NativeArray<TreeInstance>.Copy(range, 0, totalInstances, destIndex, h.job.count[0]);
destIndex += h.job.count[0];
}
h.Dispose();
}
lst.Clear();
TreeInstance[] instances = new TreeInstance[completeCount];
totalInstances.CopyTo(instances);
terrain.terrainData.SetTreeInstances(instances, false);
totalInstances.Dispose();
finishedTrees.Add(terrain);
}
}
foreach (var f in finishedTrees)
{
treeJobHolders.Remove(f);
}
finishedTrees.Clear();
Profiler.EndSample(); // apply trees
}
List<Terrain> finishedDetails = new List<Terrain>();
public void ApplyDetails()
{
Profiler.BeginSample("Apply Details");
foreach (var lst in detailJobHolders.Values)
{
for (int i = 0; i < lst.Count; ++i)
{
if (lst[i].canceled && lst[i].IsDone())
{
lst[i].Dispose();
lst.RemoveAt(i);
i--;
}
}
}
finishedDetails.Clear();
foreach (var terrain in detailJobHolders.Keys)
{
var lst = detailJobHolders[terrain];
bool isDone = true;
foreach (var h in lst)
{
if (h.IsDone() == false)
{
isDone = false;
break;
}
}
if (isDone)
{
finishedDetails.Add(terrain);
}
}
foreach (var f in finishedDetails)
{
detailJobHolders.Remove(f);
}
finishedDetails.Clear();
Profiler.EndSample(); // apply details
}
}
}