using System; using System.Collections.Generic; using AwesomeTechnologies.Utility; using AwesomeTechnologies.Utility.Quadtree; using AwesomeTechnologies.Vegetation; using AwesomeTechnologies.VegetationStudio; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; using UnityEngine.Serialization; namespace AwesomeTechnologies.VegetationSystem { [BurstCompile(CompileSynchronously = true)] public struct UnityTerrainSampleConcaveJob : IJobParallelForDefer { [NativeDisableParallelForRestriction] public NativeList Excluded; [NativeDisableParallelForRestriction] public NativeList Position; [ReadOnly] public NativeArray InputHeights; public float Distance; public float MinHeightDifference; public bool Inverse; public bool Average; public int HeightmapWidth; public int HeightmapHeight; public Vector3 HeightMapScale; public Vector3 Size; public Vector3 TerrainPosition; public void Execute(int index) { if (Excluded[index] == 1) return; Vector3 worldPosition = Position[index]; Vector3 terrainSpacePositon = worldPosition - TerrainPosition; float2 heightmapPosition = new float2(terrainSpacePositon.x / HeightMapScale.x, terrainSpacePositon.z / HeightMapScale.z); int x = Mathf.RoundToInt(heightmapPosition.x); int z = Mathf.RoundToInt(heightmapPosition.y); int sampleDistance = Mathf.RoundToInt(Distance / HeightMapScale.x); float centerHeight = GetHeight(x, z); float height1 = GetHeight(x - sampleDistance, z - sampleDistance); float height2 = GetHeight(x, z - sampleDistance); float height3 = GetHeight(x + sampleDistance, z - sampleDistance); float height4 = GetHeight(x - sampleDistance, z); float height5 = GetHeight(x + sampleDistance, z); float height6 = GetHeight(x - sampleDistance, z + sampleDistance); float height7 = GetHeight(x, z + sampleDistance); float height8 = GetHeight(x + sampleDistance, z + sampleDistance); float edgeHeight; if (Average) { edgeHeight = (height1 + height2 + height3 + height4 + height5 + height6 + height7 + height8) / 8f; } else { edgeHeight = GetMinimumHeight(height1, height2, height3, height4, height5, height6, height7, height8); } bool remove = edgeHeight < centerHeight + MinHeightDifference; if (Inverse) remove = !remove; if (!remove) return; Excluded[index] = 1; } float GetMinimumHeight(float height1, float height2, float height3, float height4, float height5, float height6, float height7, float height8) { float minHeight = math.min(height1, height2); minHeight = math.min(minHeight, height3); minHeight = math.min(minHeight, height4); minHeight = math.min(minHeight, height5); minHeight = math.min(minHeight, height6); minHeight = math.min(minHeight, height7); minHeight = math.min(minHeight, height8); return minHeight; } float GetHeight(int x, int y) { x = math.clamp(x, 0, HeightmapWidth - 1); y = math.clamp(y, 0, HeightmapHeight - 1); return InputHeights[y * HeightmapWidth + x] * HeightMapScale.y; } } [BurstCompile(CompileSynchronously = true)] public struct UnityTerranCellSampleJob : IJobParallelFor { [ReadOnly] public NativeArray InputHeights; public NativeArray VegetationCellBoundsList; public int HeightmapWidth; public int HeightmapHeight; //public Vector3 Scale; public Vector3 HeightMapScale; public Rect TerrainRect; public Vector3 TerrainPosition; public float WorldspaceHeightCutoff; public void Execute(int index) { Bounds vegetationCellBounds = VegetationCellBoundsList[index]; Rect cellRect = RectExtension.CreateRectFromBounds(vegetationCellBounds); if (!TerrainRect.Overlaps(cellRect)) return; float2 worldspaceCellCorner = new float2(vegetationCellBounds.center.x - vegetationCellBounds.extents.x, vegetationCellBounds.center.z - vegetationCellBounds.extents.z); float2 terrainspaceCellCorner = new float2(worldspaceCellCorner.x - TerrainPosition.x, worldspaceCellCorner.y - TerrainPosition.z); float2 heightmapPosition = new float2(terrainspaceCellCorner.x / HeightMapScale.x, terrainspaceCellCorner.y / HeightMapScale.z); int xCount = Mathf.CeilToInt(cellRect.width / HeightMapScale.x); int zCount = Mathf.CeilToInt(cellRect.height / HeightMapScale.z); int xStart = Mathf.FloorToInt(heightmapPosition.x); int zStart = Mathf.FloorToInt(heightmapPosition.y); float minHeight = float.MaxValue; float maxHeight = float.MinValue; for (int x = xStart; x <= xStart + xCount; x++) { for (int z = zStart; z <= zStart + zCount; z++) { float heightSample = GetHeight(x, z); if (heightSample < minHeight) minHeight = heightSample; if (heightSample > maxHeight) maxHeight = heightSample; } } if (maxHeight + TerrainPosition.y < WorldspaceHeightCutoff) return; float centerY = (maxHeight + minHeight) / 2f; float height = maxHeight - minHeight; vegetationCellBounds = new Bounds( new Vector3(vegetationCellBounds.center.x, centerY + TerrainPosition.y, vegetationCellBounds.center.z), new Vector3(vegetationCellBounds.size.x, height, vegetationCellBounds.size.z)); VegetationCellBoundsList[index] = vegetationCellBounds; } float GetHeight(int x, int y) { x = math.clamp(x, 0, HeightmapWidth - 1); y = math.clamp(y, 0, HeightmapHeight - 1); return InputHeights[y * HeightmapWidth + x] * HeightMapScale.y; } } // [BurstCompile(CompileSynchronously = true)] // public struct UnityTerrainSampleJob : IJobParallelFor // { // [ReadOnly] public NativeArray InputHeights; // [ReadOnly] public NativeArray SpawnLocationList; // public NativeArray InstanceList; // public int HeightmapWidth; // public int HeightmapHeight; // public Vector3 Scale; // public Vector3 Size; // public Vector3 HeightMapScale; // public Vector3 TerrainPosition; // public byte TerrainSourceID; // // public void Execute(int index) // { // VegetationInstance vegetationInstance = InstanceList[index]; // if (vegetationInstance.HeightmapSampled == 1) return; // // VegetationSpawnLocationInstance spawnLocation = SpawnLocationList[index]; // // if (spawnLocation.SpawnChance < 0) // { // vegetationInstance.Excluded = 1; // vegetationInstance.HeightmapSampled = 1; // InstanceList[index] = vegetationInstance; // return; // } // // Vector3 worldPosition = spawnLocation.Position; // Vector3 terrainSpacePositon = worldPosition - TerrainPosition; // float2 interpolatedPosition = new float2(terrainSpacePositon.x / Size.x, terrainSpacePositon.z / Size.z); // // if (interpolatedPosition.x < 0 || interpolatedPosition.x > 1 || interpolatedPosition.y < 0 || // interpolatedPosition.y > 1) // { // { // vegetationInstance.Excluded = 1; // InstanceList[index] = vegetationInstance; // return; // } // } // // float height = GetTriangleInterpolatedHeight(interpolatedPosition.x, interpolatedPosition.y); // vegetationInstance.Position = new float3(spawnLocation.Position.x, height + TerrainPosition.y, // spawnLocation.Position.z); // vegetationInstance.TerrainNormal = // GetInterpolatedNormal(interpolatedPosition.x, interpolatedPosition.y); // vegetationInstance.Scale = new float3(1, 1, 1); // vegetationInstance.Rotation = Quaternion.Euler(0, 0, 0); // vegetationInstance.RandomNumberIndex = spawnLocation.RandomNumberIndex; // vegetationInstance.BiomeDistance = spawnLocation.BiomeDistance; // vegetationInstance.DistanceFalloff = 1; // vegetationInstance.TerrainSourceID = TerrainSourceID; // vegetationInstance.Excluded = 0; // vegetationInstance.HeightmapSampled = 1; // InstanceList[index] = vegetationInstance; // } // // float GetTriangleInterpolatedHeight(float x, float y) // { // float fx = x * (HeightmapWidth - 1); // float fy = y * (HeightmapHeight - 1); // int lx = (int) fx; // int ly = (int) fy; // // float u = fx - lx; // float v = fy - ly; // if (u > v) // { // float z00 = GetHeight(lx + 0, ly + 0); // float z01 = GetHeight(lx + 1, ly + 0); // float z11 = GetHeight(lx + 1, ly + 1); // return z00 + (z01 - z00) * u + (z11 - z01) * v; // } // else // { // float z00 = GetHeight(lx + 0, ly + 0); // float z10 = GetHeight(lx + 0, ly + 1); // float z11 = GetHeight(lx + 1, ly + 1); // return z00 + (z11 - z10) * u + (z10 - z00) * v; // } // } // // float GetHeight(int x, int y) // { // x = math.clamp(x, 0, HeightmapWidth - 1); // y = math.clamp(y, 0, HeightmapHeight - 1); // return InputHeights[y * HeightmapWidth + x] * HeightMapScale.y; // } // // public float3 GetInterpolatedNormal(float x, float y) // { // float fx = x * (HeightmapWidth - 1); // float fy = y * (HeightmapHeight - 1); // int lx = (int) fx; // int ly = (int) fy; // // float3 n00 = CalculateNormalSobel(lx + 0, ly + 0); // float3 n10 = CalculateNormalSobel(lx + 1, ly + 0); // float3 n01 = CalculateNormalSobel(lx + 0, ly + 1); // float3 n11 = CalculateNormalSobel(lx + 1, ly + 1); // // float u = fx - lx; // float v = fy - ly; // // float3 s = math.lerp(n00, n10, u); // float3 t = math.lerp(n01, n11, u); // float3 value = math.lerp(s, t, v); // return math.normalize(value); // } // // float3 CalculateNormalSobel(int x, int y) // { // float3 normal; // var dX = GetHeight(x - 1, y - 1) * -1.0F; // dX += GetHeight(x - 1, y) * -2.0F; // dX += GetHeight(x - 1, y + 1) * -1.0F; // dX += GetHeight(x + 1, y - 1) * 1.0F; // dX += GetHeight(x + 1, y) * 2.0F; // dX += GetHeight(x + 1, y + 1) * 1.0F; // // dX /= Scale.x; // // var dY = GetHeight(x - 1, y - 1) * -1.0F; // dY += GetHeight(x, y - 1) * -2.0F; // dY += GetHeight(x + 1, y - 1) * -1.0F; // dY += GetHeight(x - 1, y + 1) * 1.0F; // dY += GetHeight(x, y + 1) * 2.0F; // dY += GetHeight(x + 1, y + 1) * 1.0F; // dY /= Scale.z; // // normal.x = -dX; // normal.y = 8; // normal.z = -dY; // return math.normalize(normal); // } // } [BurstCompile(CompileSynchronously = true)] public struct UnityTerrainSampleJob : IJobParallelFor { [ReadOnly] public NativeArray InputHeights; [ReadOnly] public NativeArray SpawnLocationList; //public NativeArray InstanceList; public NativeArray Position; public NativeArray Rotation; public NativeArray Scales; public NativeArray TerrainNormal; public NativeArray BiomeDistance; public NativeArray TerrainTextureData; public NativeArray RandomNumberIndex; public NativeArray DistanceFalloff; public NativeArray VegetationMaskDensity; public NativeArray VegetationMaskScale; public NativeArray TerrainSourceIDs; public NativeArray TextureMaskData; public NativeArray Excluded; public NativeArray HeightmapSampled; public int HeightmapWidth; public int HeightmapHeight; public Vector3 Scale; public Vector3 Size; public Vector3 HeightMapScale; public Vector3 TerrainPosition; public byte TerrainSourceID; public void Execute(int index) { if (HeightmapSampled[index] == 1) return; VegetationSpawnLocationInstance spawnLocation = SpawnLocationList[index]; if (spawnLocation.SpawnChance < 0) { Excluded[index] = 1; HeightmapSampled[index] = 1; return; } Vector3 worldPosition = spawnLocation.Position; Vector3 terrainSpacePositon = worldPosition - TerrainPosition; float2 interpolatedPosition = new float2(terrainSpacePositon.x / Size.x, terrainSpacePositon.z / Size.z); if (interpolatedPosition.x < 0 || interpolatedPosition.x > 1 || interpolatedPosition.y < 0 || interpolatedPosition.y > 1) { { Excluded[index] = 1; return; } } float height = GetTriangleInterpolatedHeight(interpolatedPosition.x, interpolatedPosition.y); Position[index] = new float3(spawnLocation.Position.x, height + TerrainPosition.y,spawnLocation.Position.z); TerrainNormal[index] = GetInterpolatedNormal(interpolatedPosition.x, interpolatedPosition.y); Scales[index] = new float3(1, 1, 1); Rotation[index] = Quaternion.Euler(0, 0, 0); RandomNumberIndex[index] = spawnLocation.RandomNumberIndex; BiomeDistance[index] = spawnLocation.BiomeDistance; DistanceFalloff[index] = 1; TerrainSourceIDs[index] = TerrainSourceID; Excluded[index] = 0; HeightmapSampled[index] = 1; TerrainTextureData[index] = 0; //TODO make sure we do not need to init these 2 here VegetationMaskDensity[index] = 0; VegetationMaskScale[index] = 0; TextureMaskData[index] = 0; } float GetTriangleInterpolatedHeight(float x, float y) { float fx = x * (HeightmapWidth - 1); float fy = y * (HeightmapHeight - 1); int lx = (int) fx; int ly = (int) fy; float u = fx - lx; float v = fy - ly; if (u > v) { float z00 = GetHeight(lx + 0, ly + 0); float z01 = GetHeight(lx + 1, ly + 0); float z11 = GetHeight(lx + 1, ly + 1); return z00 + (z01 - z00) * u + (z11 - z01) * v; } else { float z00 = GetHeight(lx + 0, ly + 0); float z10 = GetHeight(lx + 0, ly + 1); float z11 = GetHeight(lx + 1, ly + 1); return z00 + (z11 - z10) * u + (z10 - z00) * v; } } float GetHeight(int x, int y) { x = math.clamp(x, 0, HeightmapWidth - 1); y = math.clamp(y, 0, HeightmapHeight - 1); return InputHeights[y * HeightmapWidth + x] * HeightMapScale.y; } public float3 GetInterpolatedNormal(float x, float y) { float fx = x * (HeightmapWidth - 1); float fy = y * (HeightmapHeight - 1); int lx = (int) fx; int ly = (int) fy; float3 n00 = CalculateNormalSobel(lx + 0, ly + 0); float3 n10 = CalculateNormalSobel(lx + 1, ly + 0); float3 n01 = CalculateNormalSobel(lx + 0, ly + 1); float3 n11 = CalculateNormalSobel(lx + 1, ly + 1); float u = fx - lx; float v = fy - ly; float3 s = math.lerp(n00, n10, u); float3 t = math.lerp(n01, n11, u); float3 value = math.lerp(s, t, v); return math.normalize(value); } float3 CalculateNormalSobel(int x, int y) { float3 normal; var dX = GetHeight(x - 1, y - 1) * -1.0F; dX += GetHeight(x - 1, y) * -2.0F; dX += GetHeight(x - 1, y + 1) * -1.0F; dX += GetHeight(x + 1, y - 1) * 1.0F; dX += GetHeight(x + 1, y) * 2.0F; dX += GetHeight(x + 1, y + 1) * 1.0F; dX /= Scale.x; var dY = GetHeight(x - 1, y - 1) * -1.0F; dY += GetHeight(x, y - 1) * -2.0F; dY += GetHeight(x + 1, y - 1) * -1.0F; dY += GetHeight(x - 1, y + 1) * 1.0F; dY += GetHeight(x, y + 1) * 2.0F; dY += GetHeight(x + 1, y + 1) * 1.0F; dY /= Scale.z; normal.x = -dX; normal.y = 8; normal.z = -dY; return math.normalize(normal); } } [BurstCompile] public struct AddToInstanceListJob : IJob { [DeallocateOnJobCompletion] public NativeArray SourceInstanceArray; public NativeList TargetInstanceList; public void Execute() { for (int i = 0; i <= SourceInstanceArray.Length - 1; i++) { VegetationInstance vegetationInstance = SourceInstanceArray[i]; if (vegetationInstance.Excluded == 1) continue; TargetInstanceList.Add(vegetationInstance); } } } [AwesomeTechnologiesScriptOrder(-100)] [ExecuteInEditMode] public partial class UnityTerrain : MonoBehaviour, IVegetationStudioTerrain { public NativeArray Heights; [FormerlySerializedAs("_terrain")] public Terrain Terrain; private int _heightmapHeight; private int _heightmapWidth; private Vector3 _size; private Vector3 _scale; private Vector3 _heightmapScale; private Rect _terrainRect; private readonly List> _splatMapArrayList = new List>(); private readonly List _splatMapFormatList = new List(); #if !UNITY_2019_2_OR_NEWER private Terrain.MaterialType _originalTerrainMaterialType = Terrain.MaterialType.BuiltInStandard; #endif private Material _originalTerrainMaterial; private float _originalTerrainheightmapPixelError; public bool TerrainMaterialOverridden; private bool _originalTerrainInstanced; private float _originalBasemapDistance; [NonSerialized] public Material TerrainHeatmapMaterial; public bool DisableTerrainTreesAndDetails = true; public bool AutoAddToVegegetationSystem; private bool _initDone; public TerrainSourceID TerrainSourceID; public Vector3 TerrainPosition = Vector3.zero; // ReSharper disable once UnusedMember.Local void Reset() { FindTerrain(); TerrainPosition = transform.position; } void FindTerrain() { if (!Terrain) { Terrain = gameObject.GetComponent(); } } // ReSharper disable once UnusedMember.Local void Awake() { FindTerrain(); } void Start() { if (Terrain && DisableTerrainTreesAndDetails) { Terrain.drawTreesAndFoliage = false; } } //private ManagedNativeFloatArray _nativeManagedFloatArray; void LoadHeightData() { var terrainData = Terrain.terrainData; _heightmapScale = terrainData.heightmapScale; _heightmapHeight = terrainData.heightmapResolution; _heightmapWidth = terrainData.heightmapResolution; _size = terrainData.size; _scale.x = _size.x / (_heightmapWidth - 1); _scale.y = _size.y; _scale.z = _size.z / (_heightmapHeight - 1); Vector2 terrainCenter = new Vector2(TerrainPosition.x, TerrainPosition.z); Vector2 terrainSize = new Vector2(_size.x, _size.z); _terrainRect = new Rect(terrainCenter, terrainSize); float[,] hs = Terrain.terrainData.GetHeights(0, 0, _heightmapWidth, _heightmapHeight); if (Heights.IsCreated) Heights.Dispose(); Heights = new NativeArray(_heightmapWidth * _heightmapHeight, Allocator.Persistent); Heights.CopyFromFast(hs); } public JobHandle SampleTerrain(NativeList spawnLocationList, VegetationInstanceData instanceData, int sampleCount, Rect spawnRect, JobHandle dependsOn) { if (!_initDone) return dependsOn; if (spawnRect.Overlaps(_terrainRect)) { UnityTerrainSampleJob unityTerrainSampleJob = new UnityTerrainSampleJob { InputHeights = Heights, SpawnLocationList = spawnLocationList, Position = instanceData.Position, Rotation = instanceData.Rotation, Scales = instanceData.Scale, TerrainNormal= instanceData.TerrainNormal, BiomeDistance= instanceData.BiomeDistance, TerrainTextureData= instanceData.TerrainTextureData, RandomNumberIndex= instanceData.RandomNumberIndex, DistanceFalloff= instanceData.DistanceFalloff, VegetationMaskDensity= instanceData.VegetationMaskDensity, VegetationMaskScale= instanceData.VegetationMaskScale, TerrainSourceIDs= instanceData.TerrainSourceID, TextureMaskData= instanceData.TextureMaskData, Excluded= instanceData.Excluded, HeightmapSampled= instanceData.HeightmapSampled, HeightMapScale = _heightmapScale, HeightmapHeight = _heightmapHeight, HeightmapWidth = _heightmapWidth, TerrainPosition = TerrainPosition, Scale = _scale, Size = _size, TerrainSourceID = (byte) TerrainSourceID }; JobHandle handle = unityTerrainSampleJob.Schedule(sampleCount,64,dependsOn); return handle; } return dependsOn; } public void RefreshTerrainData() { LoadHeightData(); } public void RefreshTerrainData(Bounds bounds) { Rect terrainRect = RectExtension.CreateRectFromBounds(TerrainBounds); Rect updateRect = RectExtension.CreateRectFromBounds(bounds); if (!updateRect.Overlaps(terrainRect)) { LoadHeightData(); } } public JobHandle SampleCellHeight(NativeArray vegetationCellBoundsList, float worldspaceHeightCutoff, Rect cellBoundsRect, JobHandle dependsOn = default(JobHandle)) { if (!_initDone) return dependsOn; if (!Heights.IsCreated) LoadHeightData(); if (cellBoundsRect.Overlaps(_terrainRect)) { UnityTerranCellSampleJob unityTerranCellSampleJob = new UnityTerranCellSampleJob { InputHeights = Heights, VegetationCellBoundsList = vegetationCellBoundsList, HeightMapScale = _heightmapScale, HeightmapHeight = _heightmapHeight, HeightmapWidth = _heightmapWidth, TerrainPosition = TerrainPosition, WorldspaceHeightCutoff = worldspaceHeightCutoff, TerrainRect = RectExtension.CreateRectFromBounds(TerrainBounds) }; JobHandle handle = unityTerranCellSampleJob.Schedule(vegetationCellBoundsList.Length, 32, dependsOn); return handle; } return dependsOn; } public JobHandle SampleConcaveLocation(VegetationInstanceData instanceData, float minHeightDifference, float distance, bool inverse, bool average, Rect spawnRect, JobHandle dependsOn) { if (!_initDone) return dependsOn; if (spawnRect.Overlaps(_terrainRect)) { UnityTerrainSampleConcaveJob unityTerrainSampleConcaveLocationJob = new UnityTerrainSampleConcaveJob { InputHeights = Heights, Excluded = instanceData.Excluded, Position = instanceData.Position, HeightMapScale = _heightmapScale, HeightmapHeight = _heightmapHeight, HeightmapWidth = _heightmapWidth, TerrainPosition = TerrainPosition, Size = _size, Distance = distance, MinHeightDifference = minHeightDifference, Inverse = inverse, Average = average }; JobHandle handle = unityTerrainSampleConcaveLocationJob.Schedule(instanceData.Excluded, 64, dependsOn); return handle; } return dependsOn; } public void Init() { if (!Heights.IsCreated) LoadHeightData(); //if (_nativeManagedFloatArray == null) LoadHeightData(); } public void DisposeTemporaryMemory() { } public bool HasTerrainTextures() { return true; } public Texture2D GetTerrainTexture(int index) { if (!Terrain) return null; if (!Terrain.terrainData) return null; #if UNITY_2018_3_OR_NEWER if (Terrain.terrainData.terrainLayers.Length > index) { if (Terrain.terrainData.terrainLayers[index]) { return Terrain.terrainData.terrainLayers[index].diffuseTexture; } else { return null; } } #else if (Terrain.terrainData.splatPrototypes.Length > index) { return Terrain.terrainData.splatPrototypes[index].texture; } #endif return null; } #if UNITY_2018_3_OR_NEWER public TerrainLayer[] GetTerrainLayers() { if (!Terrain) return new TerrainLayer[0]; return Terrain.terrainData.terrainLayers; } public void SetTerrainLayers(TerrainLayer[] terrainLayers) { if (Terrain) { Terrain.terrainData.terrainLayers = terrainLayers; } } #else public SplatPrototype[] GetSplatPrototypes() { if (!Terrain) return new SplatPrototype[0]; return Terrain.terrainData.splatPrototypes; } public void SetSplatPrototypes(SplatPrototype[] splatPrototypes) { if (Terrain) { Terrain.terrainData.splatPrototypes = splatPrototypes; } } #endif //// ReSharper disable once UnusedMember.Local void OnEnable() { RefreshSplatMaps(); _initDone = true; if (AutoAddToVegegetationSystem && Application.isPlaying) { AddTerrainToVegetationSystem(); } else { VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } } public void AddTerrainToVegetationSystem() { VegetationStudioManager.AddTerrain(gameObject, false); } // ReSharper disable once UnusedMember.Local void OnDisable() { _initDone = false; if (AutoAddToVegegetationSystem && Application.isPlaying) { VegetationStudioManager.RemoveTerrain(gameObject); } else { VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } Dispose(); } public void RefreshTerrainArea() { VegetationStudioManager.RefreshTerrainArea(TerrainBounds); } public void Dispose() { //_nativeManagedFloatArray?.Dispose(); if (Heights.IsCreated) { Heights.Dispose(); } } public string TerrainType => "Unity terrain"; public Bounds TerrainBounds { get { if (Terrain) { var terrainData = Terrain.terrainData; return new Bounds(terrainData.bounds.center + TerrainPosition, terrainData.bounds.size); } return new Bounds(Vector3.zero, Vector3.zero); } } // ReSharper disable once UnusedMember.Local void OnDrawGizmosSelected() { Gizmos.color = Color.blue; Gizmos.DrawWireCube(TerrainBounds.center, TerrainBounds.size); } void Update() { if (!Application.isPlaying) { TerrainPosition = transform.position; } } } }