/* INFINITY CODE */
/* https://infinity-code.com */
using System;
using System.Collections.Generic;
using UnityEngine;
///
/// Implements elevation managers, which loads elevation data by tiles
///
/// Type of elevation manager
public abstract class OnlineMapsTiledElevationManager : OnlineMapsElevationManager
where T : OnlineMapsTiledElevationManager
{
public Action> OnAllTilesLoaded;
///
/// Called when data download starts.
///
public Action OnDownload;
///
/// Called when data is successfully downloaded.
///
public Action OnDownloadSuccess;
///
/// Offset of tile zoom from map zoom
///
public int zoomOffset = 13;
///
/// Cache elevations?
///
public bool cacheElevations = true;
protected Dictionary tiles;
protected bool needUpdateMinMax = true;
private bool needUpdateTiles = true;
private int prevTileX;
private int prevTileY;
protected abstract int tileWidth { get; }
protected abstract int tileHeight { get; }
protected abstract string cachePrefix { get; }
public override bool hasData
{
get { return true; }
}
protected void CheckAllTilesLoaded()
{
foreach (var pair in tiles)
{
if (!pair.Value.loaded) return;
}
if (OnAllTilesLoaded != null) OnAllTilesLoaded(this);
}
public override float GetElevationValue(double x, double z, float yScale, double tlx, double tly, double brx, double bry)
{
float v = GetUnscaledElevationValue(x, z, tlx, tly, brx, bry);
if (bottomMode == OnlineMapsElevationBottomMode.minValue) v -= minValue;
return v * yScale * scale;
}
public override float GetUnscaledElevationValue(double x, double z, double tlx, double tly, double brx, double bry)
{
if (tiles == null)
{
tiles = new Dictionary();
return 0;
}
x = x / -sizeInScene.x;
z = z / sizeInScene.y;
double ttlx, ttly, tbrx, tbry;
OnlineMaps m = map;
OnlineMapsProjection projection = m.projection;
projection.CoordinatesToTile(tlx, tly, m.zoom, out ttlx, out ttly);
projection.CoordinatesToTile(brx, bry, m.zoom, out tbrx, out tbry);
if (tbrx < ttlx) tbrx += 1 << m.zoom;
double cx = (tbrx - ttlx) * x + ttlx;
double cz = (tbry - ttly) * z + ttly;
int zoom = zoomOffset; //m.zoom - zoomOffset;
double tx, ty;
projection.TileToCoordinates(cx, cz, m.zoom, out cx, out cz);
projection.CoordinatesToTile(cx, cz, zoom, out tx, out ty);
int ix = (int)tx;
int iy = (int)ty;
ulong key = OnlineMapsTileManager.GetTileKey(zoom, ix, iy);
Tile tile;
bool hasTile = tiles.TryGetValue(key, out tile);
if (hasTile && !tile.loaded) hasTile = false;
if (!hasTile)
{
int nz = zoom;
while (!hasTile && nz < OnlineMaps.MAXZOOM)
{
nz++;
projection.CoordinatesToTile(cx, cz, nz, out tx, out ty);
ix = (int)tx;
iy = (int)ty;
key = OnlineMapsTileManager.GetTileKey(nz, ix, iy);
hasTile = tiles.TryGetValue(key, out tile) && tile.loaded;
}
}
if (!hasTile)
{
int nz = zoom;
while (!hasTile && nz > 1)
{
nz--;
projection.CoordinatesToTile(cx, cz, nz, out tx, out ty);
ix = (int)tx;
iy = (int)ty;
key = OnlineMapsTileManager.GetTileKey(nz, ix, iy);
hasTile = tiles.TryGetValue(key, out tile) && tile.loaded;
}
}
if (!hasTile) return 0;
projection.CoordinatesToTile(cx, cz, tile.zoom, out tx, out ty);
return tile.GetElevation(tx, ty);
}
protected override void OnDestroy()
{
base.OnDestroy();
if (map != null)
{
map.OnChangePosition -= OnChangePosition;
map.OnChangeZoom -= OnChangeZoom;
map.OnLateUpdateBefore -= OnLateUpdateBefore;
}
}
private void OnChangePosition()
{
if (needUpdateTiles) return;
double sx, sy;
map.GetTilePosition(out sx, out sy);
int isx = (int) sx;
int isy = (int) sy;
if (prevTileX != isx || prevTileY != isy) needUpdateTiles = true;
}
private void OnChangeZoom()
{
needUpdateTiles = true;
}
protected override void OnEnable()
{
base.OnEnable();
if (tiles == null) tiles = new Dictionary();
}
private void OnLateUpdateBefore()
{
if (!needUpdateTiles)
{
if (needUpdateMinMax) UpdateMinMax();
return;
}
needUpdateTiles = false;
int zoom = zoomOffset; // map.zoom - zoomOffset;
if (zoom < 1) zoom = 1;
if (!zoomRange.InRange(map.zoom)) return;
int currentOffset = map.zoom - zoom;
int coef = (1 << currentOffset) * OnlineMapsUtils.tileSize;
int countX = Mathf.CeilToInt(map.width / 2f / coef) * 2 + 2;
int countY = Mathf.CeilToInt(map.height / 2f / coef) * 2 + 2;
double sx, sy;
map.GetTilePosition(out sx, out sy, zoom);
prevTileX = (int) sx;
prevTileY = (int) sy;
int isx = prevTileX - countX / 2;
int isy = prevTileY - countY / 2;
int max = 1 << zoom;
foreach (KeyValuePair pair in tiles)
{
pair.Value.used = false;
}
for (int x = isx; x < isx + countX; x++)
{
int cx = x;
if (cx < 0) cx += max;
else if (cx >= max) cx -= max;
for (int y = Mathf.Max(isy, 0); y < Mathf.Min(isy + countY, max); y++)
{
ulong key = OnlineMapsTileManager.GetTileKey(zoom, cx, y);
Tile t;
if (tiles.TryGetValue(key, out t))
{
t.used = true;
continue;
}
t = new Tile
{
x = x,
y = y,
zoom = zoom,
width = tileWidth,
height = tileHeight,
used = true
};
tiles.Add(key, t);
if (TryLoadFromCache(t)) { }
else if (OnDownload != null) OnDownload(t);
else StartDownloadElevationTile(t);
}
}
double tlx, tly, brx, bry;
map.GetTileCorners(out tlx, out tly, out brx, out bry);
int itlx = (int)tlx;
int itly = (int)tly;
int ibrx = (int)brx;
int ibry = (int)bry;
List unloadKeys = new List();
foreach (KeyValuePair pair in tiles)
{
Tile tile = pair.Value;
if (tile.used) continue;
int scale = 1 << (map.zoom - tile.zoom);
int tx = tile.x * scale;
int ty = tile.y * scale;
if (ibrx >= tx && itlx <= tx + scale && ibry >= ty && itly <= ty + scale)
{
if (Mathf.Abs(zoom - tile.zoom) < 3) continue;
}
unloadKeys.Add(pair.Key);
}
foreach (ulong key in unloadKeys) tiles.Remove(key);
UpdateMinMax();
}
protected void SetElevationToCache(Tile tile, short[,] elevations)
{
if (!cacheElevations) return;
byte[] cache = new byte[tileWidth * tileHeight * 2];
int cacheIndex = 0;
for (int y = 0; y < tileHeight; y++)
{
for (int x = 0; x < tileWidth; x++)
{
short s = elevations[x, y];
cache[cacheIndex++] = (byte)(s & 255);
cache[cacheIndex++] = (byte)(s >> 8);
}
}
OnlineMapsCache.Add(tile.GetCacheKey(cachePrefix), cache);
}
protected void SetElevationData(Tile tile, short[,] elevations)
{
tile.elevations = elevations;
short max = short.MinValue;
short min = short.MaxValue;
for (int y = 0; y < tileHeight; y++)
{
for (int x = 0; x < tileWidth; x++)
{
short v = elevations[x, y];
if (v < min) min = v;
if (v > max) max = v;
}
}
tile.minValue = min;
tile.maxValue = max;
tile.loaded = true;
needUpdateMinMax = true;
CheckAllTilesLoaded();
map.Redraw();
}
protected override void Start()
{
map.OnChangePosition += OnChangePosition;
map.OnChangeZoom += OnChangeZoom;
map.OnLateUpdateBefore += OnLateUpdateBefore;
}
///
/// Starts downloading elevation data for a tile
///
/// Tile
public abstract void StartDownloadElevationTile(Tile tile);
protected bool TryLoadFromCache(Tile tile)
{
if (!cacheElevations) return false;
byte[] data = OnlineMapsCache.Get(tile.GetCacheKey(cachePrefix));
if (data == null || data.Length != tileWidth * tileHeight * 2) return false;
short[,] elevations = new short[tileWidth, tileHeight];
int dataIndex = 0;
for (int y = 0; y < tileHeight; y++)
{
for (int x = 0; x < tileWidth; x++)
{
elevations[x, y] = (short)((data[dataIndex + 1] << 8) + data[dataIndex]);
dataIndex += 2;
}
}
SetElevationData(tile, elevations);
if (OnElevationUpdated != null) OnElevationUpdated();
return true;
}
protected override void UpdateMinMax()
{
needUpdateMinMax = false;
double tlx, tly, brx, bry;
map.GetTileCorners(out tlx, out tly, out brx, out bry);
minValue = short.MaxValue;
maxValue = short.MinValue;
int itlx = (int)tlx;
int itly = (int)tly;
int ibrx = (int)brx;
int ibry = (int)bry;
foreach (KeyValuePair pair in tiles)
{
Tile tile = pair.Value;
if (!tile.loaded) continue;
int scale = 1 << (map.zoom - tile.zoom);
int tx = tile.x * scale;
int ty = tile.y * scale;
if (ibrx < tx || itlx > tx + scale) continue;
if (ibry < ty || itly > ty + scale) continue;
if (tile.minValue < minValue) minValue = tile.minValue;
if (tile.maxValue > maxValue) maxValue = tile.maxValue;
}
}
///
/// Elevation tile
///
public class Tile
{
///
/// Is the tile loaded?
///
public bool loaded = false;
///
/// Tile X
///
public int x;
///
/// Tile Y
///
public int y;
///
/// Tile zoom
///
public int zoom;
///
/// Minimum elevation value
///
public short minValue;
///
/// Maximum elevation value
///
public short maxValue;
///
/// Elevation data width
///
public int width;
///
/// Elevation data height
///
public int height;
///
/// Elevation values
///
public short[,] elevations;
public bool used;
///
/// Get elevation value from tile
///
/// Relative X (0-1)
/// Relative Y (0-1)
/// Elevation value
public float GetElevation(double tx, double ty)
{
if (!loaded) return 0;
double rx = (tx - (int)tx) * (width - 1);
double ry = (ty - (int)ty) * (height - 1);
int x1 = (int) rx;
int x2 = x1 + 1;
int y1 = (int) ry;
int y2 = y1 + 1;
if (x2 >= width) x2 = width - 1;
if (y2 >= height) y2 = height - 1;
double dx = rx - x1;
double dy = ry - y1;
double v1 = elevations[x1, y1];
double v2 = elevations[x2, y1];
double v3 = elevations[x1, y2];
double v4 = elevations[x2, y2];
v1 = (v2 - v1) * dx + v1;
v2 = (v4 - v3) * dx + v3;
return (float)((v2 - v1) * dy + v1);
}
public string GetCacheKey(string prefix)
{
return prefix + OnlineMapsTileManager.GetTileKey(zoom, x, y);
}
}
}