using System;
using System.Text;
using UnityEngine;
public class Horizon2 : MonoBehaviour
{
///
/// Mapbox Access Token for elevations.
/// Leave blank if you do not want to use elevations.
///
[Tooltip("Mapbox Access Token for elevations.\nLeave blank if you do not want to use elevations.")]
public string mapboxKey;
///
/// 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 void DownloadElevation(OnlineMapsTile tile)
{
if (string.IsNullOrEmpty(mapboxKey)) return;
string url = new StringBuilder("https://api.mapbox.com/v4/mapbox.terrain-rgb/")
.Append(tile.zoom).Append("/").Append(tile.x).Append("/").Append(tile.y)
.Append(".pngraw?access_token=").Append(mapboxKey).ToString();
OnlineMapsWWW www = OnlineMapsUtils.GetWWW(url);
www.OnComplete += delegate
{
tile.customData = new CData(www.bytes);
OnGetElevation(ctl, cbr);
UpdateMesh();
};
}
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(Vector2 tl, Vector2 br)
{
ctl = tl;
cbr = br;
double tlx, tly, brx, bry;
map.projection.CoordinatesToTile(tl.x, tl.y, map.zoom, out tlx, out tly);
map.projection.CoordinatesToTile(br.x, br.y, 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;
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;
OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, (int) tx, (int) ty);
if (tile == null)
{
heights[x, y] = 0;
continue;
}
CData data = tile.customData as CData;
if (data == null)
{
heights[x, y] = 0;
continue;
}
heights[x, 31 - y] = data.GetElevation(tx, ty);
}
}
control.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;
DownloadElevation(tile);
}
private void Start()
{
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;
control.OnGetElevation += OnGetElevation;
InitMesh();
}
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 = control.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 OnlineMapsTile(tileX, tileY, zoom, map, parentTile);
}
int tileIndex = x * countY + y;
tiles[tileIndex] = tile;
tile.Block(this);
CData data = tile.customData as CData;
bool hasElevation = data != null;
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 (hasElevation)
{
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 (control.useElevation) v.y = data.GetElevation(evx, evz) * elevationScale + offset.y;
else v.y = positionYOffset;
}
}
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(byte[] bytes)
{
Texture2D texture = new Texture2D(256, 256, TextureFormat.RGB24, false);
texture.LoadImage(bytes);
const int res = 256;
if (texture.width != res || texture.height != res) return;
Color[] colors = texture.GetPixels();
heights = new short[res, res];
for (int y = 0; y < res; y++)
{
int py = (255 - y) * res;
for (int x = 0; x < res; x++)
{
Color c = colors[py + x];
double height = -10000 + (c.r * 255 * 256 * 256 + c.g * 255 * 256 + c.b * 255) * 0.1;
heights[x, y] = (short)Math.Round(height);
}
}
}
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 * 256);
int y = (int)Math.Round(ry * 256);
if (x > 255) x = 255;
if (y > 255) y = 255;
return heights[x, y];
}
}
}