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;
}
}
}