using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; public class Horizon2_BingMaps : MonoBehaviour { private const int maxDownloads = 1; public bool interceptDownloadElevations = true; /// /// Number of tiles horizontally (X axis). /// [Tooltip("Number of tiles horizontally (X axis).")] public int countX = 3; /// /// Number of tiles vertically (Z axis). /// [Tooltip("Number of tiles vertically (Z axis).")] public int countY = 3; /// /// Offset of the horizon mesh along the y-axis relative to the map. Keep it negative. /// [Tooltip("Offset of the horizon mesh along the y-axis relative to the map. Keep it negative.")] public float positionYOffset = -5; public float inMapViewYOffset = -35; /// /// Offset zoom of tiles relative to map.zoom. Keep positive. /// [Tooltip("Offset zoom of tiles relative to map.zoom. Keep positive.")] public int zoomOffset = 3; /// /// Shader of the tiles mesh. /// [Tooltip("Shader of the tiles mesh.")] public Shader shader; /// /// Offset of the render queue relative to render queue of the shader. /// [Tooltip("Offset of the render queue relative to render queue of the shader.")] public int renderQueueOffset = 100; /// /// Tile resolution. /// [Tooltip("Tile resolution.")] public int resolution = 32; private OnlineMapsTile[] tiles; private Mesh mesh; private Vector3[] vertices; private Vector3[] normals; private Vector2[] uv; private int[] triangles; private OnlineMaps map; private OnlineMapsTileSetControl control; private MeshRenderer meshRenderer; private Vector2 ctl; private Vector2 cbr; private Queue requestQueue; private List elevationRequests; internal static OverviewElevation overviewElevation; private static OverviewElevation nextOverviewElevation; private void DownloadElevation(OnlineMapsTile tile) { CData data = tile["cdata"] as CData; if (data != null) return; tile["cdata"] = new CData(); requestQueue.Enqueue(tile); StartNextDownload(); } private void DownloadOverview() { if (nextOverviewElevation != null) return; int zoom = map.zoom - zoomOffset; if (zoom < 3) zoom = 3; double tx, ty; map.GetTilePosition(out tx, out ty, zoom); int tlx = Mathf.RoundToInt((float)(tx - countX)); int tly = Mathf.RoundToInt((float)(ty - countY)); int max = 1 << zoom; int brx = tlx + countX * 2; int bry = tly + countY * 2; if (tlx >= max) tlx -= max; if (brx >= max) brx -= max; if (overviewElevation != null) { if (overviewElevation.tlx == tlx && overviewElevation.tly == tly && overviewElevation.brx == brx && overviewElevation.bry == bry) return; } nextOverviewElevation = new OverviewElevation(tlx, tly, brx, bry, zoom); nextOverviewElevation.OnDownloaded += OnOverviewDownloaded; } private void OnOverviewDownloaded() { if (ctl == Vector2.zero && cbr == Vector2.zero) GetBordersUsingReflection(); OnGetElevation(ctl.x, ctl.y, cbr.x, cbr.y); UpdateMesh(); } private void GetBordersUsingReflection() { Type type = typeof(OnlineMapsBingMapsElevationManager); OnlineMapsBingMapsElevationManager manager = OnlineMapsBingMapsElevationManager.instance; if (manager == null) return; IEnumerable fields = OnlineMapsReflectionHelper.GetFields(type, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (FieldInfo field in fields) { string n = field.Name; if (n == "elevationX1") ctl.x = (float)field.GetValue(manager); else if (n == "elevationY1") ctl.y = (float)field.GetValue(manager); else if (n == "elevationW") cbr.x = ctl.x + (float)field.GetValue(manager); else if (n == "elevationH") cbr.y = ctl.y + (float)field.GetValue(manager); } } private void InitMesh() { GameObject go = new GameObject("Horizon"); go.transform.parent = map.gameObject.transform; go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.Euler(Vector3.zero); go.transform.localScale = Vector3.one; MeshFilter meshFilter = go.AddComponent(); meshRenderer = go.AddComponent(); int countTiles = countX * countY; mesh = new Mesh(); mesh.name = "Horizon"; mesh.MarkDynamic(); meshFilter.sharedMesh = mesh; tiles = new OnlineMapsTile[countTiles]; int countVertices = (countX + 1) * (countY + 1) * resolution * resolution; vertices = new Vector3[countVertices]; normals = new Vector3[countVertices]; uv = new Vector2[countVertices]; triangles = new int[6 * resolution * resolution]; mesh.vertices = vertices; mesh.subMeshCount = countTiles; float r1 = resolution - 1; int index = 0; for (int i = 0; i < (countX + 1) * (countY + 1); i++) { for (int x = 0; x < resolution; x++) { for (int y = 0; y < resolution; y++) { normals[index] = Vector3.up; uv[index++] = new Vector2(x / r1, 1 - y / r1); } } } mesh.uv = uv; mesh.normals = normals; Material[] materials = new Material[countTiles]; for (int i = 0; i < countTiles; i++) { int ti = 0; for (int x = 0; x < resolution - 1; x++) { for (int y = 0; y < resolution - 1; y++) { int vi = i * resolution * resolution + x * resolution + y; triangles[ti] = vi; triangles[ti + 1] = vi + resolution + 1; triangles[ti + 2] = vi + 1; triangles[ti + 3] = vi; triangles[ti + 4] = vi + resolution; triangles[ti + 5] = vi + resolution + 1; ti += 6; } } mesh.SetTriangles(triangles, i); Material material = new Material(shader); material.renderQueue = shader.renderQueue + renderQueueOffset; materials[i] = material; } meshRenderer.sharedMaterials = materials; UpdateMesh(); mesh.RecalculateBounds(); } private void OnGetElevation(double leftLng, double topLat, double rightLng, double bottomLat) { ctl = new Vector2((float)leftLng, (float)topLat); cbr = new Vector2((float)rightLng, (float)bottomLat); double tlx, tly, brx, bry; map.projection.CoordinatesToTile(leftLng, topLat, map.zoom, out tlx, out tly); map.projection.CoordinatesToTile(rightLng, bottomLat, map.zoom, out brx, out bry); int scale = 1 << zoomOffset; int zoom = map.zoom - zoomOffset; short[,] heights = new short[32, 32]; double rx = (brx - tlx) / 31; double ry = (bry - tly) / 31; OnlineMapsTile tile = null; int max = 1 << zoom; for (int x = 0; x < 32; x++) { double tx = (rx * x + tlx) / scale; for (int y = 0; y < 32; y++) { double ty = (ry * y + tly) / scale; if (tile == null || tile.x != (int) tx || tile.y != (int) ty) { tile = map.tileManager.GetTile(zoom, (int) tx, (int) ty); } if (tile == null) { if (overviewElevation != null) heights[x, 31 - y] = (short)Mathf.Round(overviewElevation.GetElevation(tx / max, ty / max)); else heights[x, 31 - y] = 0; continue; } CData data = tile["cdata"] as CData; if (data == null || !data.hasData) { if (overviewElevation != null) heights[x, 31 - y] = (short)Mathf.Round(overviewElevation.GetElevation(tx / max, ty / max)); else heights[x, 31 - y] = 0; continue; } heights[x, 31 - y] = (short)Mathf.Round(data.GetElevation(tx, ty)); } } if (interceptDownloadElevations && OnlineMapsElevationManagerBase.isActive) OnlineMapsBingMapsElevationManager.instance.SetElevationData(heights); } private void OnTileDownloaded(OnlineMapsTile tile) { for (int i = 0; i < countX * countY; i++) { if (tiles[i] == tile) { meshRenderer.sharedMaterials[i].mainTexture = tile.texture; break; } } } private void Start() { elevationRequests = new List(); requestQueue = new Queue(); if (zoomOffset <= 0) throw new Exception("Zoom offset should be positive."); if (shader == null) shader = Shader.Find("Diffuse"); map = OnlineMaps.instance; control = OnlineMapsTileSetControl.instance; OnlineMapsTile.OnTileDownloaded += OnTileDownloaded; if (OnlineMapsCache.instance != null) OnlineMapsCache.instance.OnLoadedFromCache += OnTileDownloaded; map.OnMapUpdated += UpdateMesh; if (interceptDownloadElevations && OnlineMapsElevationManagerBase.isActive) OnlineMapsElevationManagerBase.instance.OnGetElevation += OnGetElevation; InitMesh(); } private void StartNextDownload() { if (requestQueue.Count == 0) return; if (elevationRequests.Count == maxDownloads) return; OnlineMapsTile tile = requestQueue.Dequeue(); CData data = tile["cdata"] as CData; string key = OnlineMapsKeyManager.hasBingMaps ? OnlineMapsKeyManager.BingMaps() : OnlineMapsBingMapsElevationManager.instance.bingAPI; OnlineMapsBingMapsElevation request = OnlineMapsBingMapsElevation.GetElevationByBounds(key, tile.topLeft.x, tile.topLeft.y, tile.bottomRight.x, tile.bottomRight.y, 32, 32); request.OnComplete += response => { short[,] elevations = new short[32, 32]; Array ed = elevations; elevationRequests.Remove(request); if (data != null && OnlineMapsBingMapsElevation.ParseElevationArray(response, OnlineMapsBingMapsElevation.Output.json, ref ed)) { data.SetHeight(elevations); if (ctl == Vector2.zero && cbr == Vector2.zero) GetBordersUsingReflection(); OnGetElevation(ctl.x, ctl.y, cbr.x, cbr.y); UpdateMesh(); } StartNextDownload(); }; elevationRequests.Add(request); } private void UpdateMesh() { int zoom = map.zoom - zoomOffset; if (zoom < 3) zoom = 3; if (zoom >= map.zoom) { meshRenderer.gameObject.SetActive(false); return; } if (!meshRenderer.gameObject.activeSelf) meshRenderer.gameObject.SetActive(true); for (int i = 0; i < countX * countY; i++) if (tiles[i] != null) tiles[i].Unblock(this); double tx, ty; map.GetTilePosition(out tx, out ty, zoom); int itx = Mathf.RoundToInt((float)(tx - countX / 2f)); int ity = Mathf.RoundToInt((float)(ty - countY / 2f)); Vector3 offset = new Vector3(0, positionYOffset, 0) - transform.position; int max = 1 << zoom; Material[] materials = meshRenderer.sharedMaterials; float r1 = resolution - 1; int vi = 0; double tlx, tly, brx, bry; map.GetCorners(out tlx, out tly, out brx, out bry); float elevationScale = OnlineMapsElevationManagerBase.GetBestElevationYScale(control.elevationManager, tlx, tly, brx, bry); int ox = countY * resolution * resolution - (resolution - 1) * resolution; int oz = resolution * (resolution - 1) + 1; float elevationOffset = 0; if (OnlineMapsElevationManagerBase.isActive && OnlineMapsElevationManagerBase.instance.bottomMode == OnlineMapsElevationBottomMode.minValue) { elevationOffset = OnlineMapsElevationManagerBase.instance.GetMinElevation(1); } DownloadOverview(); int zOffset = map.zoom - zoom; Vector2 sizeInScene = control.sizeInScene; Vector2 tileSize = new Vector2( sizeInScene.x / (map.width / OnlineMapsUtils.tileSize) * (1 << zOffset) / map.zoomFactor, sizeInScene.y / (map.height / OnlineMapsUtils.tileSize) * (1 << zOffset) / map.zoomFactor ); map.projection.TileToCoordinates(itx + countX / 2f, ity + countY / 2f, zoom, out tx, out ty); Vector3 startWorldPos = new Vector3(0, positionYOffset, 0) + Quaternion.Inverse(map.transform.rotation) * control.GetWorldPosition(tx, ty) - map.transform.position - new Vector3(-tileSize.x * countX / 2, 0, tileSize.y * countY / 2); for (int x = 0; x < countX; x++) { int tileX = itx + x; if (tileX >= max) tileX -= max; for (int y = 0; y < countY; y++) { int tileY = ity + y; if (tileY >= max) tileY -= max; OnlineMapsTile tile = map.tileManager.GetTile(zoom, tileX, tileY); if (tile == null) { OnlineMapsTile parentTile = map.tileManager.GetTile(zoom - 1, tileX / 2, tileY / 2); tile = new OnlineMapsRasterTile(tileX, tileY, zoom, map); tile.parent = parentTile; } int tileIndex = x * countY + y; tiles[tileIndex] = tile; tile.Block(this); CData data = tile["cdata"] as CData; if (data == null) { DownloadElevation(tile); data = tile["cdata"] as CData; } Vector3 v1 = startWorldPos + new Vector3(-tileSize.x * x, 0, tileSize.y * y); Vector3 v2 = startWorldPos + new Vector3(-tileSize.x * (x + 1), 0, tileSize.y * (y + 1)); Vector3 ov = (v2 - v1) / r1; for (int vx = 0; vx < resolution; vx++) { for (int vz = 0; vz < resolution; vz++) { Vector3 v = new Vector3(ov.x * vx + v1.x, 0, ov.z * vz + v1.z); if (vz == 0 && y > 0) v.y = vertices[vi - oz].y; else if (vx == 0 && x > 0) v.y = vertices[vi - ox].y; else { double evx = vx / r1; double evz = vz / r1; if (evx >= 1) evx = 0.999; if (evz >= 1) evz = 0.999; if (OnlineMapsElevationManagerBase.isActive) { float elevation = 0; if (data.hasData) { elevation = data.GetElevation(evx, evz); } else if (overviewElevation != null) elevation = overviewElevation.GetElevation((tileX + vx / r1) / max, (tileY + vz / r1) / max); v.y = (elevation - elevationOffset) * elevationScale + offset.y; } else v.y = positionYOffset; if (v.x <= 0 && v.x >= -sizeInScene.x && v.z >= 0 && v.z <= sizeInScene.y) { float rx = Mathf.Abs(sizeInScene.x / 2 + v.x) / sizeInScene.x * 2; float ry = Mathf.Abs(sizeInScene.y / 2 - v.z) / sizeInScene.y * 2; v.y += (1 - Mathf.Max(rx,ry)) * inMapViewYOffset; } } vertices[vi++] = v; } } materials[tileIndex].mainTexture = tile.texture; materials[tileIndex].color = new Color(1, 1, 1, tile.texture != null ? 1 : 0); } } mesh.vertices = vertices; mesh.RecalculateBounds(); } internal class OverviewElevation { public Action OnDownloaded; public int tlx; public int tly; public int brx; public int bry; public double mx1; public double my1; public double mx2; public double my2; public double mrx; public double mry; public short[,] heights; public OverviewElevation(int tlx, int tly, int brx, int bry, int zoom) { this.tlx = tlx; this.tly = tly; this.brx = brx; this.bry = bry; double z = 1 << zoom; mx1 = tlx / z; my1 = tly / z; mx2 = brx / z; my2 = bry / z; mrx = mx2 - mx1; mry = my2 - my1; double left, right, bottom, top; OnlineMaps.instance.projection.TileToCoordinates(tlx, tly, zoom, out left, out top); OnlineMaps.instance.projection.TileToCoordinates(brx, bry, zoom, out right, out bottom); string key = OnlineMapsKeyManager.hasBingMaps ? OnlineMapsKeyManager.BingMaps() : OnlineMapsBingMapsElevationManager.instance.bingAPI; OnlineMapsBingMapsElevation request = OnlineMapsBingMapsElevation.GetElevationByBounds(key, left, top, right, bottom, 32, 32); request.OnComplete += OnRequestComplete; } public void Dispose() { heights = null; } public float GetElevation(double mx, double my) { if (heights == null) return 0; double x1 = (mx - mx1) / mrx * 31; double y1 = (my - my1) / mry * 31; int ix1 = (int)x1; int iy1 = (int)y1; int ix2 = ix1 + 1; int iy2 = iy1 + 1; if (ix1 < 0) ix1 = 0; else if (ix1 > 31) ix1 = 31; if (iy1 < 0) iy1 = 0; else if (iy1 > 31) iy1 = 31; if (ix2 < 0) { x1 = 0; ix2 = 0; } else if (ix2 > 31) { x1 = 31; ix2 = 31; } if (iy2 < 0) { y1 = 0; iy2 = 0; } else if (iy2 > 31) { y1 = 31; iy2 = 31; } double rx = x1 - ix1; double ry = y1 - iy1; short h11 = heights[ix1, 31 - iy1]; short h12 = heights[ix1, 31 - iy2]; short h21 = heights[ix2, 31 - iy1]; short h22 = heights[ix2, 31 - iy2]; double h1 = (h21 - h11) * rx + h11; double h2 = (h22 - h12) * rx + h12; double h = (h2 - h1) * ry + h1; return (float)h; } private void OnRequestComplete(string response) { heights = new short[32, 32]; Array ed = heights; OnlineMapsBingMapsElevation.ParseElevationArray(response, OnlineMapsBingMapsElevation.Output.json, ref ed); if (overviewElevation != null) overviewElevation.Dispose(); overviewElevation = this; nextOverviewElevation = null; if (OnDownloaded == null) OnDownloaded(); } } internal class CData { public short[,] heights; public bool hasData { get { return heights != null; } } public CData() { } public float GetElevation(double tx, double ty) { if (heights == null) return 0; double x1 = (tx - Math.Floor(tx)) * 31; double y1 = (ty - Math.Floor(ty)) * 31; int ix1 = (int) x1; int iy1 = (int) y1; int ix2 = ix1 + 1; int iy2 = iy1 + 1; if (ix2 > 31) x1 = 31; if (iy2 > 31) y1 = 31; double rx = x1 - ix1; double ry = y1 - iy1; short h11 = heights[ix1, 31 - iy1]; short h12 = heights[ix1, 31 - iy2]; short h21 = heights[ix2, 31 - iy1]; short h22 = heights[ix2, 31 - iy2]; double h1 = (h21 - h11) * rx + h11; double h2 = (h22 - h12) * rx + h12; double h = (h2 - h1) * ry + h1; return (float)h; } public void SetHeight(short[,] elevations) { heights = elevations; } } }