using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
public class Horizon2_BingMaps : MonoBehaviour
{
private const int maxDownloads = 1;
///
/// 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;
///
/// 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;
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 GetBordersUsingReflection()
{
Type type = typeof(OnlineMapsBingMapsElevationManager);
OnlineMapsBingMapsElevationManager manager = OnlineMapsBingMapsElevationManager.instance;
IEnumerable fields = OnlineMapsReflectionHelper.GetFields(type, BindingFlags.Instance | BindingFlags.NonPublic);
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;
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 = OnlineMapsTile.GetTile(zoom, (int) tx, (int) ty);
}
if (tile == null)
{
heights[x, y] = 0;
continue;
}
CData data = tile["cdata"] as CData;
if (data == null)
{
heights[x, y] = 0;
continue;
}
heights[x, 31 - y] = data.GetElevation(tx, ty);
}
}
if (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 (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 (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;
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(tlx, tly, brx, bry);
int ox = countY * resolution * resolution - (resolution - 1) * resolution;
int oz = resolution * (resolution - 1) + 1;
for (int x = 0; x < countX; x++)
{
int tileX = itx + x;
int nextTileX = tileX + 1;
if (tileX >= max) tileX -= max;
if (nextTileX >= max) nextTileX -= max;
for (int y = 0; y < countY; y++)
{
int tileY = ity + y;
int nextTileY = tileY + 1;
if (tileY >= max) tileY -= max;
if (nextTileY >= max) nextTileY -= max;
OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, tileX, tileY);
if (tile == null)
{
OnlineMapsTile parentTile = OnlineMapsTile.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;
}
double px, py;
map.projection.TileToCoordinates(tileX, tileY, zoom, out px, out py);
Vector3 v1 = control.GetWorldPosition(px, py) + offset;
map.projection.TileToCoordinates(nextTileX, nextTileY, zoom, out px, out py);
Vector3 v2 = control.GetWorldPosition(px, py) + offset;
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) v.y = data.GetElevation(evx, evz) * elevationScale + offset.y;
else v.y = positionYOffset;
}
vertices[vi++] = v;
}
}
materials[tileIndex].mainTexture = tile.texture;
materials[tileIndex].color = new Color(1, 1, 1, tile.texture != null ? 1 : 0);
}
}
mesh.vertices = vertices;
}
internal class CData
{
private short[,] heights;
public CData()
{
heights = new short[32, 32];
}
public short GetElevation(double tx, double ty)
{
if (heights == null) return 0;
double rx = tx - Math.Floor(tx);
double ry = ty - Math.Floor(ty);
int x = (int)Math.Round(rx * 32);
int y = (int)Math.Round(ry * 32);
if (x > 31) x = 31;
if (y > 31) y = 31;
return heights[x, 31 - y];
}
public void SetHeight(short[,] elevations)
{
heights = elevations;
}
}
}