/* INFINITY CODE */ /* https://infinity-code.com */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using DotLiquid.Util; using InfinityCode.RealWorldTerrain.Phases; using InfinityCode.RealWorldTerrain.Windows; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Mathematics; using UnityEditor; using UnityEngine; using Random = UnityEngine.Random; namespace InfinityCode.RealWorldTerrain.Generators { public abstract class RealWorldTerrainElevationGenerator { /// /// (Mercator X (0-1), Mercator Y (0-1), elevation value or double.MinValue if unknown, return modified elevation value or null) Called when an elevation value is received from the selected provider. Return null to use the value from the provider. /// public static Func OnElevationRetrieved; /// /// (Mercator X (0-1), Mercator Y (0-1), return Elevation Value or null) Allows you to intercept receiving elevation value from the selected provider. Return null to get the value from the selected provider. /// public static Func OnGetElevation; /// /// Allows you to intercept the range of elevation values for the generated area. /// public static OnGetElevationRangeDelegate OnGetElevationRange; public delegate void OnGetElevationRangeDelegate(out double minEl, out double maxEl); public static List elevations; public static float[,] tdataHeightmap; public static float depthStep; protected static RealWorldTerrainElevationGenerator lastElevation; protected static int lastX; protected static int mapSize; protected static int mapSize2; public static bool hasUnderwater; private static TerrainData tdata; public short[,] heightmap; public bool unziped; private static bool hasKnownValue; private static int countUnknownValues; protected static RealWorldTerrainPrefs prefs { get { return RealWorldTerrainWindow.prefs; } } public virtual bool Contains(double X, double Y) { return false; } private bool Contains(Vector2 point) { return Contains(point.x, point.y); } public static void Dispose() { if (prefs.elevationProvider == RealWorldTerrainElevationProvider.BingMaps) { RealWorldTerrainBingElevationGenerator.Dispose(); } /*else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.ArcGIS) { RealWorldTerrainArcGISElevationGenerator.Dispose(); }*/ else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.SRTM) { RealWorldTerrainSRTMElevationGenerator.Dispose(); } else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.SRTM30) { RealWorldTerrainSRTM30ElevationGenerator.Dispose(); } tdataHeightmap = null; tdata = null; elevations = null; lastElevation = null; lastX = 0; countUnknownValues = 0; } [BurstCompile] public unsafe struct GenerateUnderwaterJob : IJob { public int dimX; public int dimY; public int resolution; public int heightmapResolution; public int unknownCount; public float depthStep; public float underwaterValue; [NoAlias] public NativeArray heights; [NoAlias] public NativeArray newHeights; private float GetNeighborHeightValues(int y, int x, out int countVals) { float v; float val = 0; countVals = 0; int l1 = this.dimY - 1; int l2 = this.dimX - 1; if (y > 0) { v = this.heights[(y - 1) * this.resolution + x]; if (v > float.MinValue) { val += v * 2; countVals += 2; } if (x > 0) { v = this.heights[(y - 1) * this.resolution + (x - 1)]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = this.heights[(y - 1) * this.resolution + x + 1]; if (v > float.MinValue) { val += v; countVals++; } } } if (y < l1) { v = this.heights[(y + 1) * this.resolution + x]; if (v > float.MinValue) { val += v; countVals++; } if (x > 0) { v = this.heights[(y + 1) * this.resolution + (x - 1)]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = this.heights[(y + 1) * this.resolution + x + 1]; if (v > float.MinValue) { val += v; countVals++; } } } if (x > 0) { v = this.heights[y * this.resolution + (x - 1)]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = this.heights[y * this.resolution + (x + 1)]; if (v > float.MinValue) { val += v; countVals++; } } if (countVals > 0) val /= countVals; return val; } public void Execute() { int currentUnknown = this.unknownCount; int total = this.resolution * this.resolution; NativeArray neighborMap = new NativeArray(this.dimX * this.dimY, Allocator.Temp); NativeArray newNeighborMap = new NativeArray(this.dimX * this.dimY, Allocator.Temp); for (int idx = 0; idx < total; idx++) { if (this.heights[idx] > float.MinValue) neighborMap[idx] = 1; else { int x = idx % this.resolution; int y = idx / this.resolution; GetNeighborHeightValues(y, x, out int countvals); neighborMap[idx] = (byte)(countvals > 0 ? 2 : 0); } newNeighborMap[idx] = neighborMap[idx]; } float pnc = (this.heightmapResolution - 1) / 32f / this.resolution; while (currentUnknown > 0) { for (int idx = 0; idx < total; idx++) { byte b = neighborMap[idx]; if (b < 2) continue; int x = idx % this.resolution; int y = idx / this.resolution; float h = GetNeighborHeightValues(y, x, out int countVals); if (countVals < 3) continue; float n = (math.saturate(noise.cnoise(new float2(x * pnc, y * pnc))) * 3 + 1) / 4; h -= this.depthStep * n; if (h < this.underwaterValue) h = this.underwaterValue; this.newHeights[idx] = h; currentUnknown--; for (int cx = math.max(x - 1, 0); cx < math.min(x + 2, this.resolution); cx++) { for (int cy = math.max(y - 1, 0); cy < math.min(y + 2, this.resolution); cy++) { int cIdx = cy * this.resolution + cx; if (newNeighborMap[cIdx] == 0) newNeighborMap[cIdx] = 2; } } newNeighborMap[idx] = 1; } UnsafeUtility.MemCpy(neighborMap.GetUnsafePtr(), newNeighborMap.GetUnsafePtr(), this.dimX * this.dimY); UnsafeUtility.MemCpy(this.heights.GetUnsafePtr(), this.newHeights.GetUnsafePtr(), this.dimX * this.dimY * sizeof(float)); } } } public unsafe static void GenerateHeightMap(RealWorldTerrainItem item) { if (tdata == null) InitTData(item); double mx1 = item.leftMercator, my1 = item.topMercator, mx2 = item.rightMercator, my2 = item.bottomMercator; double minElevation = item.minElevation; double elevationRange = item.maxElevation - minElevation; RealWorldTerrainTimer timer = RealWorldTerrainTimer.Start(); int r = tdata.heightmapResolution; double thx = (mx2 - mx1) / (r - 1); double thy = (my2 - my1) / (r - 1); for (int hx = lastX; hx < r; hx++) { double px = hx * thx + mx1; for (int hy = 0; hy < r; hy++) { double py = hy * thy + my1; double elevation = GetElevation(px, py); int chy = r - hy - 1; if (Math.Abs(elevation - double.MinValue) > double.Epsilon) { tdataHeightmap[chy, hx] = (float)((elevation - minElevation) / elevationRange); hasKnownValue = true; } else if (!prefs.generateUnderWater) { tdataHeightmap[chy, hx] = (float)(-minElevation / elevationRange); hasKnownValue = true; } else { hasUnderwater = true; tdataHeightmap[chy, hx] = float.MinValue; countUnknownValues++; } } lastX = hx + 1; RealWorldTerrainPhase.phaseProgress = hx / (float)r; if (timer.seconds > 1) return; } float underwaterValue = (float)((prefs.nodataValue - minElevation) / elevationRange); if (hasKnownValue && hasUnderwater) { if (prefs.fasterUnderwaterGeneration) { int dimX = tdataHeightmap.GetLength(1); int dimY = tdataHeightmap.GetLength(0); var heights = new NativeArray(dimX * dimY, Allocator.TempJob); var newHeights = new NativeArray(dimX * dimY, Allocator.TempJob); fixed (float* heightmapPtr = &tdataHeightmap[0, 0]) { UnsafeUtility.MemCpy(heights.GetUnsafePtr(), heightmapPtr, sizeof(float) * dimX * dimY); UnsafeUtility.MemCpy(newHeights.GetUnsafePtr(), heightmapPtr, sizeof(float) * dimX * dimY); var generateUnderwaterJob = new GenerateUnderwaterJob() { depthStep = depthStep, dimX = dimX, dimY = dimY, heightmapResolution = prefs.heightmapResolution, heights = heights, resolution = r, underwaterValue = underwaterValue, unknownCount = countUnknownValues, newHeights = newHeights, }; generateUnderwaterJob.Schedule().Complete(); UnsafeUtility.MemCpy(heightmapPtr, heights.GetUnsafePtr(), sizeof(float) * dimX * dimY); heights.Dispose(); newHeights.Dispose(); } } else { float[,] newHeightmap = new float[tdataHeightmap.GetLength(0), tdataHeightmap.GetLength(1)]; int totalUnknownValues = countUnknownValues; bool showProgress = prefs.heightmapResolution > 256; byte[,] neighbormap = new byte[newHeightmap.GetLength(0), newHeightmap.GetLength(1)]; byte[,] newNeighbormap = new byte[newHeightmap.GetLength(0), newHeightmap.GetLength(1)]; for (int hx = 0; hx < r; hx++) { for (int hy = 0; hy < r; hy++) { if (tdataHeightmap[hy, hx] > float.MinValue) neighbormap[hy, hx] = 1; // Has value else { int countVals; GetNeighborHeightValues(tdataHeightmap, hy, hx, out countVals); if (countVals > 0) neighbormap[hy, hx] = 2; // Has neighbors else neighbormap[hy, hx] = 0; // Has no neighbors } newNeighbormap[hy, hx] = neighbormap[hy, hx]; } } float pnc = (prefs.heightmapResolution - 1) / 32f / r; while (countUnknownValues > 0) { if (showProgress) { float progress = (totalUnknownValues - countUnknownValues) / (float)totalUnknownValues; if (EditorUtility.DisplayCancelableProgressBar("Generate Underwater Area", (progress * 100).ToString("F2") + "%", progress)) { tdata = null; EditorUtility.ClearProgressBar(); RealWorldTerrainWindow.CancelCapture(); return; } } for (int hx = 0; hx < r; hx++) { for (int hy = 0; hy < r; hy++) { byte b = neighbormap[hy, hx]; if (b < 2) { newHeightmap[hy, hx] = tdataHeightmap[hy, hx]; continue; } int countVals; float h = GetNeighborHeightValues(tdataHeightmap, hy, hx, out countVals); if (countVals < 3) { newHeightmap[hy, hx] = tdataHeightmap[hy, hx]; continue; } float noise = (Mathf.PerlinNoise(pnc * hx, pnc * hy) * 3 + 1) / 4; h -= depthStep * noise; if (h < underwaterValue) h = underwaterValue; newHeightmap[hy, hx] = h; countUnknownValues--; for (int cx = Mathf.Max(hx - 1, 0); cx < Mathf.Min(hx + 2, r); cx++) { for (int cy = Mathf.Max(hy - 1, 0); cy < Mathf.Min(hy + 2, r); cy++) { if (newNeighbormap[cy, cx] == 0) newNeighbormap[cy, cx] = 2; } } newNeighbormap[hy, hx] = 1; } } float[,] tmp = tdataHeightmap; tdataHeightmap = newHeightmap; newHeightmap = tmp; for (int hx = 0; hx < r; hx++) { for (int hy = 0; hy < r; hy++) neighbormap[hy, hx] = newNeighbormap[hy, hx]; } } if (showProgress) EditorUtility.ClearProgressBar(); } } else if (hasUnderwater) { fixed (float* heightmapPtr = &tdataHeightmap[0, 0]) { NativeReference underwaterRef = new NativeReference(underwaterValue, Allocator.TempJob); UnsafeUtility.MemCpyReplicate(heightmapPtr, underwaterRef.GetUnsafePtr(), sizeof(float), r * r); underwaterRef.Dispose(); } } lastX = 0; tdata.SetHeights(0, 0, tdataHeightmap); tdata = null; hasUnderwater = false; hasKnownValue = false; countUnknownValues = 0; RealWorldTerrainPhase.phaseComplete = true; } public static double GetElevation(double x, double y, bool allowWaterTexture = true) { if (OnGetElevation != null) { double? r = OnGetElevation(x, y); if (r.HasValue) return r.Value; } bool useWaterTexture = RealWorldTerrainWaterMask.IsUsed(allowWaterTexture); if (useWaterTexture) { double value; if (RealWorldTerrainWaterMask.Get(x, y, out value)) return value; } double v = double.MinValue; if (lastElevation != null && lastElevation.Contains(x, y)) { v = lastElevation.GetElevationValue(x, y); } else { foreach (RealWorldTerrainElevationGenerator el in elevations) { if (el.Contains(x, y)) { lastElevation = el; v = el.GetElevationValue(x, y); break; } } } if (useWaterTexture && Math.Abs(v - double.MinValue) < double.Epsilon) v = 0; if (OnElevationRetrieved != null) { double? nv = OnElevationRetrieved(x, y, v); if (nv.HasValue) v = nv.Value; } return v; } public static void GetElevationRange(out double minEl, out double maxEl) { if (OnGetElevationRange != null) { OnGetElevationRange(out minEl, out maxEl); return; } if (prefs.elevationProvider == RealWorldTerrainElevationProvider.SRTM) RealWorldTerrainSRTMElevationGenerator.GetSRTMElevationRange(out minEl, out maxEl); else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.SRTM30) RealWorldTerrainSRTM30ElevationGenerator.GetSRTMElevationRange(out minEl, out maxEl); else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.Mapbox) RealWorldTerrainMapboxElevationGenerator.GetMapboxElevationRange(out minEl, out maxEl); //else if (prefs.elevationProvider == RealWorldTerrainElevationProvider.ArcGIS) RealWorldTerrainArcGISElevationGenerator.GetArcGISElevationRange(out minEl, out maxEl); else RealWorldTerrainBingElevationGenerator.GetBingElevationRange(out minEl, out maxEl); } public virtual double GetElevationValue(double x, double y) { return double.MinValue; } private short GetFixedValue(int X, int Y) { short v = GetValue(X, Y); if (v == short.MinValue) v = 0; return v; } private static float GetNeighborHeightValues(float[,] heightmap, int y, int x, out int countVals) { float v; float val = 0; countVals = 0; int l1 = heightmap.GetLength(0) - 1; int l2 = heightmap.GetLength(1) - 1; if (y > 0) { v = heightmap[y - 1, x]; if (v > float.MinValue) { val += v * 2; countVals += 2; } if (x > 0) { v = heightmap[y - 1, x - 1]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = heightmap[y - 1, x + 1]; if (v > float.MinValue) { val += v; countVals++; } } } if (y < l1) { v = heightmap[y + 1, x]; if (v > float.MinValue) { val += v; countVals++; } if (x > 0) { v = heightmap[y + 1, x - 1]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = heightmap[y + 1, x + 1]; if (v > float.MinValue) { val += v; countVals++; } } } if (x > 0) { v = heightmap[y, x - 1]; if (v > float.MinValue) { val += v; countVals++; } } if (x < l2) { v = heightmap[y, x + 1]; if (v > float.MinValue) { val += v; countVals++; } } if (countVals > 0) val /= countVals; return val; } protected double GetSmoothElevation( double xs1, double xs2, double xp1, double ys1, double ys2, double yp1, int ix, int iy, double ox, int ixp1, double oy, int iyp1, double oxy, int ixs1, int iys1, int ixp2, int iyp2) { double result = xs1 * xs2 * xp1 * ys1 * ys2 * yp1 * 0.25 * GetFixedValue(ix, iy); result -= ox * xp1 * xs2 * ys1 * ys2 * yp1 * 0.25 * GetFixedValue(ixp1, iy); result -= oy * xs1 * xs2 * xp1 * yp1 * ys2 * 0.25 * GetFixedValue(ix, iyp1); result += oxy * xp1 * xs2 * yp1 * ys2 * 0.25 * GetFixedValue(ixp1, iyp1); result -= ox * xs1 * xs2 * ys1 * ys2 * yp1 / 12.0 * GetFixedValue(ixs1, iy); result -= oy * xs1 * xs2 * xp1 * ys1 * ys2 / 12.0 * GetFixedValue(ix, iys1); result += oxy * xs1 * xs2 * yp1 * ys2 / 12.0 * GetFixedValue(ixs1, iyp1); result += oxy * xp1 * xs2 * ys1 * ys2 / 12.0 * GetFixedValue(ixp1, iys1); result += ox * xs1 * xp1 * ys1 * ys2 * yp1 / 12.0 * GetFixedValue(ixp2, iy); result += oy * xs1 * xs2 * xp1 * ys1 * yp1 / 12.0 * GetFixedValue(ix, iyp2); result += oxy * xs1 * xs2 * ys1 * ys2 / 36.0 * GetFixedValue(ixs1, iys1); result -= oxy * xs1 * xp1 * yp1 * ys2 / 12.0 * GetFixedValue(ixp2, iyp1); result -= oxy * xp1 * xs2 * ys1 * yp1 / 12.0 * GetFixedValue(ixp1, iyp2); result -= oxy * xs1 * xp1 * ys1 * ys2 / 36.0 * GetFixedValue(ixp2, iys1); result -= oxy * xs1 * xs2 * ys1 * yp1 / 36.0 * GetFixedValue(ixs1, iyp2); result += oxy * xs1 * xp1 * ys1 * yp1 / 36.0 * GetFixedValue(ixp2, iyp2); return result; } protected virtual short GetValue(int x, int y) { if (x < 0) x = 0; else if (x > mapSize2) x = mapSize2; if (y < 0) y = 0; else if (y > mapSize2) y = mapSize2; short v = heightmap[x, y]; return v; } private static void InitTData(RealWorldTerrainItem item) { tdata = item.terrain.terrainData; tdata.baseMapResolution = prefs.baseMapResolution; tdata.SetDetailResolution(prefs.detailResolution, prefs.resolutionPerPatch); tdata.heightmapResolution = prefs.heightmapResolution; tdata.size = item.size; if (tdataHeightmap == null) tdataHeightmap = new float[tdata.heightmapResolution, tdata.heightmapResolution]; hasUnderwater = false; lastX = 0; countUnknownValues = 0; } public static bool IsSingleDistance(int X, int Y, bool ignoreLeft, bool ignoreTop) { int r = tdata.heightmapResolution; if (!ignoreTop && Y > 0 && Math.Abs(tdataHeightmap[Y - 1, X] - float.MinValue) > float.Epsilon) return true; if (Y < r - 1 && Math.Abs(tdataHeightmap[Y + 1, X] - float.MinValue) > float.Epsilon) return true; if (!ignoreLeft && X > 0 && Math.Abs(tdataHeightmap[Y, X - 1] - float.MinValue) > float.Epsilon) return true; if (X < r - 1 && Math.Abs(tdataHeightmap[Y, X + 1] - float.MinValue) > float.Epsilon) return true; return false; } public virtual void UnzipHeightmap() { } } }