using System.Collections.Generic; using AwesomeTechnologies.Utility.BVHTree; using AwesomeTechnologies.Utility.Quadtree; using AwesomeTechnologies.Vegetation; using AwesomeTechnologies.VegetationStudio; using AwesomeTechnologies.VegetationSystem; using AwesomeTechnologies.VegetationSystem.Biomes; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace AwesomeTechnologies.MeshTerrains { public class MeshSampleCell { public Bounds CellBounds; public MeshSampleCell(Rect rectangle) { CellBounds = RectExtension.CreateBoundsFromRect(rectangle, -100000); } } public struct BVHRay { public float3 Origin; public float3 Direction; public int DoRaycast; } public struct BVHRaycastContainer { public NativeArray RaycastHits; public NativeList RaycastHitList; public NativeArray Rays; public NativeArray TempHi; } [System.Serializable] public struct MeshTerrainMeshSource { public MeshRenderer MeshRenderer; public TerrainSourceID TerrainSourceID; public MaterialPropertyBlock MaterialPropertyBlock; } [System.Serializable] public struct MeshTerrainTerrainSource { public Terrain Terrain; public TerrainSourceID TerrainSourceID; public MaterialPropertyBlock MaterialPropertyBlock; } [ExecuteInEditMode] public class MeshTerrain : MonoBehaviour, IVegetationStudioTerrain { List _objects; public List Tris; private List _nodes; private List _finalPrims; public int CurrentTabIndex; public MeshTerrainData MeshTerrainData; public List MeshTerrainTerrainSourceList = new List(); public List MeshTerrainMeshSourceList = new List(); public bool ShowDebugInfo; public bool NeedGeneration; private Material _debugMaterial; public bool MultiLevelRaycast; public bool AutoAddToVegegetationSystem; private NativeArray _nativeNodes; private NativeArray _nativePrims; private bool _initDone; public bool Filterlods; public List RaycastContainerList = new List(); public void GenerateMeshTerrain() { _objects = new List(); for (int i = 0; i <= MeshTerrainMeshSourceList.Count - 1; i++) { if (MeshTerrainMeshSourceList[i].MeshRenderer.GetComponent().sharedMesh == null) continue; ObjectData o = new ObjectData(MeshTerrainMeshSourceList[i].MeshRenderer, (int)MeshTerrainMeshSourceList[i].TerrainSourceID); if (o.IsValid) _objects.Add(o); } BVH.Build(ref _objects, out _nodes, out Tris, out _finalPrims); BVH.BuildLbvhData(_nodes, _finalPrims, out MeshTerrainData.lNodes, out MeshTerrainData.lPrims); MeshTerrainData.Bounds = CalculateTerrainBounds(); #if UNITY_EDITOR EditorUtility.SetDirty(MeshTerrainData); #endif //GenerateCellCoverage(); CreateNativeArrays(); VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } //void GenerateCellCoverage() //{ // List meshSampleCellList = new List(); // float sampleCellSize = 5; // int cellXCount = Mathf.CeilToInt(TerrainBounds.size.x / sampleCellSize); // int cellZCount = Mathf.CeilToInt(TerrainBounds.size.z / sampleCellSize); // Vector2 corner = new Vector2(TerrainBounds.center.x - TerrainBounds.size.x / 2f, // TerrainBounds.center.z - TerrainBounds.size.z / 2f); // for (int x = 0; x <= cellXCount - 1; x++) // { // for (int z = 0; z <= cellZCount - 1; z++) // { // MeshSampleCell meshSampleCell = new MeshSampleCell(new Rect( // new Vector2(sampleCellSize * x + corner.x, sampleCellSize * z + corner.y), // new Vector2(sampleCellSize, sampleCellSize))); // meshSampleCellList.Add(meshSampleCell); // } // } // var sampleCellBoundsArray = new NativeArray(meshSampleCellList.Count,Allocator.Persistent); // for (int i = 0; i <= meshSampleCellList.Count - 1; i++) // { // sampleCellBoundsArray[i] = meshSampleCellList[i].CellBounds; // } // NativeArray tempNativeNodes = new NativeArray(MeshTerrainData.lNodes.ToArray(), Allocator.Persistent); // BVHTerrainCellSampleJob2 bvhTerranCellSampleJob = new BVHTerrainCellSampleJob2 // { // VegetationCellBoundsList = sampleCellBoundsArray, // Nodes = tempNativeNodes, // TerrainRect = RectExtension.CreateRectFromBounds(TerrainBounds) // }; // JobHandle sampleHandle = bvhTerranCellSampleJob.Schedule(meshSampleCellList.Count, 32); // sampleHandle.Complete(); // MeshTerrainData.CoverageList.Clear(); // for (int i = 0; i <= sampleCellBoundsArray.Length - 1; i++) // { // if (float.IsNegativeInfinity(sampleCellBoundsArray[i].size.y)) // { // MeshTerrainData.CoverageList.Add(0); // } // else // { // MeshTerrainData.CoverageList.Add(1); // } // } // sampleCellBoundsArray.Dispose(); // tempNativeNodes.Dispose(); //} Bounds CalculateTerrainBounds() { Bounds terrainBounds = new Bounds(); for (int i = 0; i <= MeshTerrainMeshSourceList.Count - 1; i++) { if (i == 0) { if (MeshTerrainMeshSourceList[i].MeshRenderer) terrainBounds = MeshTerrainMeshSourceList[i].MeshRenderer.bounds; } else { if (MeshTerrainMeshSourceList[i].MeshRenderer) terrainBounds.Encapsulate(MeshTerrainMeshSourceList[i].MeshRenderer.bounds); } } return terrainBounds; } public void AddMeshRenderer(GameObject go, TerrainSourceID terrainSourceID) { MeshRenderer[] meshRenderers = go.GetComponentsInChildren(); for (int i = 0; i <= meshRenderers.Length - 1; i++) { if (Filterlods) { if (meshRenderers[i].name.ToUpper().Contains("LOD1")) continue; if (meshRenderers[i].name.ToUpper().Contains("LOD2")) continue; if (meshRenderers[i].name.ToUpper().Contains("LOD3")) continue; } var newMeshTerrainTerrainSource = new MeshTerrainMeshSource() { MeshRenderer = meshRenderers[i], TerrainSourceID = terrainSourceID, MaterialPropertyBlock = new MaterialPropertyBlock() }; MeshTerrainMeshSourceList.Add(newMeshTerrainTerrainSource); } NeedGeneration = true; } Color GetMeshTerrainSourceTypeColor(TerrainSourceID terrainSourceID) { switch (terrainSourceID) { case TerrainSourceID.TerrainSourceID1: return Color.green; case TerrainSourceID.TerrainSourceID2: return Color.red; case TerrainSourceID.TerrainSourceID3: return Color.blue; case TerrainSourceID.TerrainSourceID4: return Color.yellow; case TerrainSourceID.TerrainSourceID5: return Color.grey; case TerrainSourceID.TerrainSourceID6: return Color.magenta; case TerrainSourceID.TerrainSourceID7: return Color.cyan; case TerrainSourceID.TerrainSourceID8: return Color.white; } return Color.green; } public void AddTerrain(GameObject go, TerrainSourceID terrainSourceID) { Terrain[] terrains = go.GetComponentsInChildren(); for (int i = 0; i <= terrains.Length - 1; i++) { var newMeshTerrainTerrainSource = new MeshTerrainTerrainSource { Terrain = terrains[i], TerrainSourceID = terrainSourceID, MaterialPropertyBlock = new MaterialPropertyBlock() }; MeshTerrainTerrainSourceList.Add(newMeshTerrainTerrainSource); NeedGeneration = true; } } // ReSharper disable once UnusedMember.Local void OnEnable() { _debugMaterial = Resources.Load("MeshTerrainDebugMaterial", typeof(Material)) as Material; _initDone = true; if (AutoAddToVegegetationSystem && Application.isPlaying) { VegetationStudioManager.AddTerrain(gameObject,false); } else { VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } } void CreateNativeArrays() { DisposeNativeArrays(); if (MeshTerrainData == null) return; _nativeNodes = new NativeArray(MeshTerrainData.lNodes.ToArray(), Allocator.Persistent); _nativePrims = new NativeArray(MeshTerrainData.lPrims.ToArray(), Allocator.Persistent); } void DisposeNativeArrays() { if (_nativeNodes.IsCreated) _nativeNodes.Dispose(); if (_nativePrims.IsCreated) _nativePrims.Dispose(); } // ReSharper disable once UnusedMember.Local void OnDisable() { _initDone = false; if (AutoAddToVegegetationSystem && Application.isPlaying) { VegetationStudioManager.RemoveTerrain(gameObject); } else { VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } DisposeNativeArrays(); } public bool HasMeshRenderer(MeshRenderer meshRenderer) { for (int i = 0; i <= MeshTerrainMeshSourceList.Count - 1; i++) { if (MeshTerrainMeshSourceList[i].MeshRenderer == meshRenderer) return true; } return false; } public bool HasTerrain(Terrain terrain) { for (int i = 0; i <= MeshTerrainTerrainSourceList.Count - 1; i++) { if (MeshTerrainTerrainSourceList[i].Terrain == terrain) return true; } return false; } // ReSharper disable once UnusedMember.Local void Update() { DrawDebuginfo(); } void DrawDebuginfo() { if (ShowDebugInfo) { for (int i = 0; i <= MeshTerrainMeshSourceList.Count - 1; i++) { if (MeshTerrainMeshSourceList[i].MaterialPropertyBlock == null) { MeshTerrainMeshSource meshTerrainMeshSource = MeshTerrainMeshSourceList[i]; meshTerrainMeshSource.MaterialPropertyBlock = new MaterialPropertyBlock(); MeshTerrainMeshSourceList[i] = meshTerrainMeshSource; } DrawMeshRenderer(MeshTerrainMeshSourceList[i].MeshRenderer, MeshTerrainMeshSourceList[i].MaterialPropertyBlock, MeshTerrainMeshSourceList[i].TerrainSourceID); } } } void DrawMeshRenderer(MeshRenderer meshRenderer, MaterialPropertyBlock materialPropertyBlock, TerrainSourceID terrainSourceID) { if (!meshRenderer) return; MeshFilter meshFilter = meshRenderer.gameObject.GetComponent(); if (!meshFilter) return; if (!meshFilter.sharedMesh) return; Matrix4x4 matrix = Matrix4x4.TRS(meshRenderer.transform.position, meshRenderer.transform.rotation, meshRenderer.transform.lossyScale); materialPropertyBlock.SetColor("_Color", GetMeshTerrainSourceTypeColor(terrainSourceID)); for (int i = 0; i <= meshFilter.sharedMesh.subMeshCount - 1; i++) { Graphics.DrawMesh(meshFilter.sharedMesh, matrix, _debugMaterial, 0, null, i, materialPropertyBlock); } } public string TerrainType => "Mesh terrain"; public Bounds TerrainBounds { get { if (MeshTerrainData) { return MeshTerrainData.Bounds; } return new Bounds(); } } public void RefreshTerrainData() { } public void RefreshTerrainData(Bounds bounds) { } public JobHandle SampleCellHeight(NativeArray vegetationCellBoundsList, float worldspaceHeightCutoff, Rect cellBoundsRect, JobHandle dependsOn = default(JobHandle)) { if (!_initDone) return dependsOn; if (!_nativeNodes.IsCreated) CreateNativeArrays(); if (!_nativeNodes.IsCreated) return dependsOn; Rect terrainRect = RectExtension.CreateRectFromBounds(TerrainBounds); if (!cellBoundsRect.Overlaps(terrainRect)) return dependsOn; //BVHTerrainCellSampleJob bvhTerranCellSampleJob = new BVHTerrainCellSampleJob //{ // VegetationCellBoundsList = vegetationCellBoundsList, // TerrainMinHeight = TerrainBounds.center.y - TerrainBounds.extents.y, // TerrainMaxHeight = TerrainBounds.center.y + TerrainBounds.extents.y, // TerrainRect = RectExtension.CreateRectFromBounds(TerrainBounds) //}; //dependsOn = bvhTerranCellSampleJob.Schedule(vegetationCellBoundsList.Length, 32, dependsOn); BVHTerrainCellSampleJob2 bvhTerranCellSampleJob = new BVHTerrainCellSampleJob2 { VegetationCellBoundsList = vegetationCellBoundsList, Nodes = _nativeNodes, TerrainRect = RectExtension.CreateRectFromBounds(TerrainBounds) }; dependsOn = bvhTerranCellSampleJob.Schedule(vegetationCellBoundsList.Length, 32, dependsOn); return dependsOn; } public JobHandle SampleTerrain(NativeList spawnLocationList, VegetationInstanceData instanceData, int sampleCount, Rect spawnRect, JobHandle dependsOn) { if (!_initDone) return dependsOn; if (!_nativeNodes.IsCreated) CreateNativeArrays(); Rect terrainRect = RectExtension.CreateRectFromBounds(TerrainBounds); if (!spawnRect.Overlaps(terrainRect)) return dependsOn; BVHRaycastContainer raycastContainer = new BVHRaycastContainer { Rays = new NativeArray(sampleCount, Allocator.TempJob), RaycastHits = new NativeArray(sampleCount, Allocator.TempJob), RaycastHitList = new NativeList(sampleCount *2,Allocator.TempJob), TempHi = new NativeArray(sampleCount, Allocator.TempJob) }; RaycastContainerList.Add(raycastContainer); CreateBVHRaycastJob createBVHRaysJob = new CreateBVHRaycastJob { SpawnLocationList = spawnLocationList, Rays = raycastContainer.Rays, TerrainRect = terrainRect }; dependsOn = createBVHRaysJob.Schedule(dependsOn); if (MultiLevelRaycast) { SampleBVHTreeMultiLevelJob jobData = new SampleBVHTreeMultiLevelJob() { Rays = raycastContainer.Rays, HitInfos = raycastContainer.RaycastHitList, NativeNodes = _nativeNodes, NativePrims = _nativePrims, TempHi = raycastContainer.TempHi }; dependsOn = jobData.Schedule(dependsOn); UpdateBVHInstanceListMultiLevelJob updateInstanceListJob = new UpdateBVHInstanceListMultiLevelJob { Position = instanceData.Position, Rotation = instanceData.Rotation, Scale = instanceData.Scale, TerrainNormal= instanceData.TerrainNormal, BiomeDistance= instanceData.BiomeDistance, TerrainTextureData= instanceData.TerrainTextureData , RandomNumberIndex= instanceData.RandomNumberIndex, DistanceFalloff= instanceData.DistanceFalloff, VegetationMaskDensity= instanceData.VegetationMaskDensity, VegetationMaskScale= instanceData.VegetationMaskScale, TerrainSourceID= instanceData.TerrainSourceID, TextureMaskData= instanceData.TextureMaskData, Excluded= instanceData.Excluded, HeightmapSampled = instanceData.HeightmapSampled, RaycastHits = raycastContainer.RaycastHitList, }; dependsOn = updateInstanceListJob.Schedule(dependsOn); } else { SampleBVHTreeJob jobData = new SampleBVHTreeJob() { Rays = raycastContainer.Rays, HitInfos = raycastContainer.RaycastHits, NativeNodes = _nativeNodes, NativePrims = _nativePrims, TempHi = raycastContainer.TempHi }; dependsOn = jobData.Schedule(sampleCount, 32, dependsOn); UpdateBVHInstanceListJob updateInstanceListJob = new UpdateBVHInstanceListJob { Position = instanceData.Position, Rotation = instanceData.Rotation, Scale = instanceData.Scale, TerrainNormal= instanceData.TerrainNormal, BiomeDistance= instanceData.BiomeDistance, TerrainTextureData= instanceData.TerrainTextureData , RandomNumberIndex= instanceData.RandomNumberIndex, DistanceFalloff= instanceData.DistanceFalloff, VegetationMaskDensity= instanceData.VegetationMaskDensity, VegetationMaskScale= instanceData.VegetationMaskScale, TerrainSourceID= instanceData.TerrainSourceID, TextureMaskData= instanceData.TextureMaskData, Excluded= instanceData.Excluded, RaycastHits = raycastContainer.RaycastHits, HeightmapSampled = instanceData.HeightmapSampled, SpawnLocationList = spawnLocationList }; dependsOn = updateInstanceListJob.Schedule(dependsOn); } return dependsOn; } public bool NeedsSplatMapUpdate(Bounds updateBounds) { return false; } public void PrepareSplatmapGeneration(bool clearLockedTextures) { } public void GenerateSplatMapBiome(Bounds updateBounds, BiomeType biomeType, List polygonBiomeMaskList, List terrainTextureSettingsList, float heightCurveSampleHeight, float worldSpaceSeaLevel,bool clearLockedTextures) { } public virtual void CompleteSplatmapGeneration() { } public JobHandle SampleConcaveLocation(VegetationInstanceData instanceData, float minHeightDifference, float distance, bool inverse, bool average,Rect spawnRect, JobHandle dependsOn) { if (!_initDone) return dependsOn; //TODO implement concave sampling for mesh terrain return dependsOn; } public void Init() { } public void DisposeTemporaryMemory() { for (int i = 0; i <= RaycastContainerList.Count - 1; i++) { RaycastContainerList[i].Rays.Dispose(); RaycastContainerList[i].RaycastHits.Dispose(); RaycastContainerList[i].RaycastHitList.Dispose(); RaycastContainerList[i].TempHi.Dispose(); } RaycastContainerList.Clear(); } public void OverrideTerrainMaterial() { } public void RestoreTerrainMaterial() { } public void VerifySplatmapAccess() { } public void UpdateTerrainMaterial(float worldspaceSeaLevel, float worldspaceMaxTerrainHeight, TerrainTextureSettings terrainTextureSettings) { } public JobHandle ProcessSplatmapRules(List terrainTextureRuleList, VegetationInstanceData instanceData,bool include, Rect cellRect,JobHandle dependsOn) { return dependsOn; } public bool HasTerrainTextures() { return false; } public Texture2D GetTerrainTexture(int index) { return null; } #if UNITY_2018_3_OR_NEWER public TerrainLayer[] GetTerrainLayers() { return new TerrainLayer[0]; } public void SetTerrainLayers(TerrainLayer[] terrainLayers) { } #else public SplatPrototype[] GetSplatPrototypes() { return new SplatPrototype[0]; } public void SetSplatPrototypes(SplatPrototype[] splatPrototypes) { } #endif // ReSharper disable once UnusedMember.Local void OnDrawGizmosSelected() { Gizmos.color = Color.blue; Gizmos.DrawWireCube(TerrainBounds.center, TerrainBounds.size); } } }