You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
9.5 KiB

6 months ago
using Mapbox.Unity.Map.Interfaces;
namespace Mapbox.Unity.MeshGeneration.Data
{
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Enums;
using Mapbox.Unity.Utilities;
using Utils;
using Mapbox.Map;
using System;
using Mapbox.Unity.Map;
using System.Collections.Generic;
using Mapbox.Unity.MeshGeneration.Factories;
public class UnityTile : MonoBehaviour
{
public TileTerrainType ElevationType;
[SerializeField]
private Texture2D _rasterData;
public VectorTile VectorData { get; private set; }
private Texture2D _heightTexture;
public float[] HeightData;
private Texture2D _loadingTexture;
//keeping track of tile objects to be able to cancel them safely if tile is destroyed before data fetching finishes
private List<Tile> _tiles = new List<Tile>();
[SerializeField] private float _tileScale;
public bool IsRecycled = false;
#region CachedUnityComponents
MeshRenderer _meshRenderer;
public MeshRenderer MeshRenderer
{
get
{
if (_meshRenderer == null)
{
_meshRenderer = GetComponent<MeshRenderer>();
if (_meshRenderer == null)
{
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
}
}
return _meshRenderer;
}
}
private MeshFilter _meshFilter;
public MeshFilter MeshFilter
{
get
{
if (_meshFilter == null)
{
_meshFilter = GetComponent<MeshFilter>();
if (_meshFilter == null)
{
_meshFilter = gameObject.AddComponent<MeshFilter>();
_meshFilter.sharedMesh = new Mesh();
ElevationType = TileTerrainType.None;
}
}
return _meshFilter;
}
}
private Collider _collider;
public Collider Collider
{
get
{
if (_collider == null)
{
_collider = gameObject.GetComponent<MeshCollider>();
if (_collider == null)
{
_collider = gameObject.AddComponent<MeshCollider>();
}
}
return _collider;
}
}
#endregion
#region Tile Positon/Scale Properties
public RectD Rect { get; private set; }
public int InitialZoom { get; private set; }
public int CurrentZoom { get; private set; }
public float TileScale
{
get { return _tileScale; }
private set { _tileScale = value; }
}
public UnwrappedTileId UnwrappedTileId { get; private set; }
public CanonicalTileId CanonicalTileId { get; private set; }
private float _relativeScale;
#endregion
[SerializeField]
private TilePropertyState _rasterDataState;
public TilePropertyState RasterDataState
{
get
{
return _rasterDataState;
}
internal set
{
if (_rasterDataState != value)
{
_rasterDataState = value;
OnRasterDataChanged(this);
}
}
}
[SerializeField]
private TilePropertyState _heightDataState;
public TilePropertyState HeightDataState
{
get
{
return _heightDataState;
}
internal set
{
if (_heightDataState != value)
{
_heightDataState = value;
OnHeightDataChanged(this);
}
}
}
[SerializeField]
private TilePropertyState _vectorDataState;
public TilePropertyState VectorDataState
{
get
{
return _vectorDataState;
}
internal set
{
if (_vectorDataState != value)
{
_vectorDataState = value;
OnVectorDataChanged(this);
}
}
}
private TilePropertyState _tileState = TilePropertyState.None;
public TilePropertyState TileState
{
get
{
return _tileState;
}
set
{
if (_tileState != value)
{
_tileState = value;
}
}
}
public event Action<UnityTile> OnHeightDataChanged = delegate { };
public event Action<UnityTile> OnRasterDataChanged = delegate { };
public event Action<UnityTile> OnVectorDataChanged = delegate { };
private bool _isInitialized = false;
internal void Initialize(IMapReadable map, UnwrappedTileId tileId, float scale, int zoom, Texture2D loadingTexture = null)
{
gameObject.hideFlags = HideFlags.DontSave;
ElevationType = TileTerrainType.None;
TileScale = scale;
_relativeScale = 1 / Mathf.Cos(Mathf.Deg2Rad * (float)map.CenterLatitudeLongitude.x);
Rect = Conversions.TileBounds(tileId);
UnwrappedTileId = tileId;
CanonicalTileId = tileId.Canonical;
_loadingTexture = loadingTexture;
float scaleFactor = 1.0f;
if (_isInitialized == false)
{
_isInitialized = true;
InitialZoom = zoom;
}
CurrentZoom = zoom;
scaleFactor = Mathf.Pow(2, (map.InitialZoom - zoom));
gameObject.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);
gameObject.SetActive(true);
IsRecycled = false;
// Setup Loading as initial state - Unregistered
// When tile registers with factories, it will set the appropriate state.
// None, if Factory source is None, Loading otherwise.
}
internal void Recycle()
{
if (_loadingTexture && MeshRenderer != null && MeshRenderer.sharedMaterial != null)
{
MeshRenderer.sharedMaterial.mainTexture = _loadingTexture;
}
gameObject.SetActive(false);
IsRecycled = true;
// Reset internal state.
RasterDataState = TilePropertyState.Unregistered;
HeightDataState = TilePropertyState.Unregistered;
VectorDataState = TilePropertyState.Unregistered;
TileState = TilePropertyState.Unregistered;
OnHeightDataChanged = delegate { };
OnRasterDataChanged = delegate { };
OnVectorDataChanged = delegate { };
Cancel();
_tiles.Clear();
}
public void SetHeightData(byte[] data, float heightMultiplier = 1f, bool useRelative = false, bool addCollider = false)
{
if (HeightDataState != TilePropertyState.Unregistered)
{
//reset height data
if(data == null)
{
HeightData = new float[256 * 256];
HeightDataState = TilePropertyState.None;
return;
}
// HACK: compute height values for terrain. We could probably do this without a texture2d.
if (_heightTexture == null)
{
_heightTexture = new Texture2D(0, 0);
}
_heightTexture.LoadImage(data);
byte[] rgbData = _heightTexture.GetRawTextureData();
// Get rid of this temporary texture. We don't need to bloat memory.
_heightTexture.LoadImage(null);
if (HeightData == null)
{
HeightData = new float[256 * 256];
}
var relativeScale = useRelative ? _relativeScale : 1f;
for (int xx = 0; xx < 256; ++xx)
{
for (int yy = 0; yy < 256; ++yy)
{
float r = rgbData[(xx * 256 + yy) * 4 + 1];
float g = rgbData[(xx * 256 + yy) * 4 + 2];
float b = rgbData[(xx * 256 + yy) * 4 + 3];
//the formula below is the same as Conversions.GetAbsoluteHeightFromColor but it's inlined for performance
HeightData[xx * 256 + yy] = relativeScale * heightMultiplier * (-10000f + ((r * 65536f + g * 256f + b) * 0.1f));
}
}
HeightDataState = TilePropertyState.Loaded;
}
}
public void SetRasterData(byte[] data, bool useMipMap = true, bool useCompression = false)
{
// Don't leak the texture, just reuse it.
if (RasterDataState != TilePropertyState.Unregistered)
{
//reset image on null data
if (data == null)
{
MeshRenderer.material.mainTexture = null;
return;
}
if (_rasterData == null)
{
_rasterData = new Texture2D(1, 1, TextureFormat.RGB24, useMipMap);
_rasterData.wrapMode = TextureWrapMode.Clamp;
}
_rasterData.LoadImage(data);
if (useCompression)
{
// High quality = true seems to decrease image quality?
_rasterData.Compress(false);
}
MeshRenderer.sharedMaterial.mainTexture = _rasterData;
RasterDataState = TilePropertyState.Loaded;
}
}
public void SetVectorData(VectorTile vectorTile)
{
if (VectorDataState != TilePropertyState.Unregistered)
{
VectorData = vectorTile;
}
}
/// <summary>
/// Method to query elevation data in any point in the tile using [0-1] range inputs.
/// Input values are clamped for safety and QueryHeightDataNonclamped method should be used for
/// higher performance usage.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public float QueryHeightData(float x, float y)
{
if (HeightData != null)
{
return HeightData[(int)(Mathf.Clamp01(y) * 255) * 256 + (int)(Mathf.Clamp01(x) * 255)] * _tileScale;
}
return 0;
}
/// <summary>
/// Method to query elevation data in any point in the tile using [0-1] range inputs.
/// Input values aren't clamped for improved performance and assuring they are in [0-1] range
/// is left to caller.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public float QueryHeightDataNonclamped(float x, float y)
{
if (HeightData != null)
{
return HeightData[(int)(y * 255) * 256 + (int)(x * 255)] * _tileScale;
}
return 0;
}
public void SetLoadingTexture(Texture2D texture)
{
MeshRenderer.material.mainTexture = texture;
}
public Texture2D GetRasterData()
{
return _rasterData;
}
internal void AddTile(Tile tile)
{
_tiles.Add(tile);
}
public void ClearAssets()
{
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(_heightTexture, true);
DestroyImmediate(_rasterData, true);
DestroyImmediate(_meshFilter.sharedMesh);
DestroyImmediate(_meshRenderer.sharedMaterial);
}
}
public void Cancel()
{
for (int i = 0, _tilesCount = _tiles.Count; i < _tilesCount; i++)
{
_tiles[i].Cancel();
}
}
protected virtual void OnDestroy()
{
Cancel();
if (_heightTexture != null)
{
_heightTexture.Destroy();
}
if (_rasterData != null)
{
_rasterData.Destroy();
}
}
}
}