using Mapbox.Unity.Map.Interfaces; namespace Mapbox.Unity.Map { using System.Linq; using System.Collections.Generic; using UnityEngine; using Mapbox.Map; using Mapbox.Unity.MeshGeneration.Factories; using Mapbox.Unity.MeshGeneration.Data; using System; using Mapbox.Platform; using UnityEngine.Serialization; using Mapbox.Unity.Utilities; using Mapbox.Unity.MeshGeneration.Enums; using Mapbox.Unity.MeshGeneration.Interfaces; /// /// Map Visualizer /// Represents a map.Doesn’t contain much logic and at the moment, it creates requested tiles and relays them to the factories /// under itself.It has a caching mechanism to reuse tiles and does the tile positioning in unity world. /// Later we’ll most likely keep track of map features here as well to allow devs to query for features easier /// (i.e.query all buildings x meters around any restaurant etc). /// public abstract class AbstractMapVisualizer : ScriptableObject { [SerializeField] [NodeEditorElementAttribute("Factories")] [FormerlySerializedAs("_factories")] public List Factories; protected IMapReadable _map; protected Dictionary _activeTiles = new Dictionary(); protected Queue _inactiveTiles = new Queue(); private int _counter; private ModuleState _state; public ModuleState State { get { return _state; } internal set { if (_state != value) { _state = value; OnMapVisualizerStateChanged(_state); } } } public IMapReadable Map { get { return _map; } } public Dictionary ActiveTiles { get { return _activeTiles; } } public Dictionary _tileProgress; public event Action OnMapVisualizerStateChanged = delegate { }; public event Action OnTileFinished = delegate { }; /// /// Gets the unity tile from unwrapped tile identifier. /// /// The unity tile from unwrapped tile identifier. /// Tile identifier. public UnityTile GetUnityTileFromUnwrappedTileId(UnwrappedTileId tileId) { return _activeTiles[tileId]; } /// /// Initializes the factories by passing the file source down, which is necessary for data (web/file) calls /// /// public virtual void Initialize(IMapReadable map, IFileSource fileSource) { _map = map; _tileProgress = new Dictionary(); // Allow for map re-use by recycling any active tiles. var activeTiles = _activeTiles.Keys.ToList(); foreach (var tile in activeTiles) { DisposeTile(tile); } State = ModuleState.Initialized; foreach (var factory in Factories) { if (null == factory) { Debug.LogError("AbstractMapVisualizer: Factory is NULL"); } else { factory.Initialize(fileSource); UnregisterEvents(factory); RegisterEvents(factory); } } } private void RegisterEvents(AbstractTileFactory factory) { factory.OnTileError += Factory_OnTileError; } private void UnregisterEvents(AbstractTileFactory factory) { factory.OnTileError -= Factory_OnTileError; } public virtual void Destroy() { if (Factories != null) { _counter = Factories.Count; for (int i = 0; i < _counter; i++) { if (Factories[i] != null) { UnregisterEvents(Factories[i]); } } } // Inform all downstream nodes that we no longer need to process these tiles. // This scriptable object may be re-used, but it's gameobjects are likely // to be destroyed by a scene change, for example. foreach (var tileId in _activeTiles.Keys.ToList()) { DisposeTile(tileId); } _activeTiles.Clear(); _inactiveTiles.Clear(); } #region Factory event callbacks //factory event callback, not relaying this up for now private void TileHeightStateChanged(UnityTile tile) { if (tile.HeightDataState == TilePropertyState.Loaded) { OnTileHeightProcessingFinished(tile); } TileStateChanged(tile); } private void TileRasterStateChanged(UnityTile tile) { if (tile.RasterDataState == TilePropertyState.Loaded) { OnTileImageProcessingFinished(tile); } TileStateChanged(tile); } private void TileVectorStateChanged(UnityTile tile) { if (tile.VectorDataState == TilePropertyState.Loaded) { OnTileVectorProcessingFinished(tile); } TileStateChanged(tile); } public virtual void TileStateChanged(UnityTile tile) { bool rasterDone = (tile.RasterDataState == TilePropertyState.None || tile.RasterDataState == TilePropertyState.Loaded || tile.RasterDataState == TilePropertyState.Error || tile.RasterDataState == TilePropertyState.Cancelled); bool terrainDone = (tile.HeightDataState == TilePropertyState.None || tile.HeightDataState == TilePropertyState.Loaded || tile.HeightDataState == TilePropertyState.Error || tile.HeightDataState == TilePropertyState.Cancelled); bool vectorDone = (tile.VectorDataState == TilePropertyState.None || tile.VectorDataState == TilePropertyState.Loaded || tile.VectorDataState == TilePropertyState.Error || tile.VectorDataState == TilePropertyState.Cancelled); if (rasterDone && terrainDone && vectorDone) { tile.TileState = MeshGeneration.Enums.TilePropertyState.Loaded; OnTileFinished(tile); // Check if all tiles in extent are active tiles if (_map.CurrentExtent.Count == _activeTiles.Count) { bool allDone = true; // Check if all tiles are loaded. foreach (var currentTile in _map.CurrentExtent) { allDone = allDone && (_activeTiles.ContainsKey(currentTile) && _activeTiles[currentTile].TileState == TilePropertyState.Loaded); } if (allDone) { State = ModuleState.Finished; } else { State = ModuleState.Working; } } else { State = ModuleState.Working; } } } #endregion /// /// Registers requested tiles to the factories /// /// public virtual UnityTile LoadTile(UnwrappedTileId tileId) { UnityTile unityTile = null; if (_inactiveTiles.Count > 0) { unityTile = _inactiveTiles.Dequeue(); } if (unityTile == null) { unityTile = new GameObject().AddComponent(); try { unityTile.MeshRenderer.sharedMaterial = Instantiate(_map.TileMaterial); } catch { Debug.Log("Tile Material not set. Using default material"); unityTile.MeshRenderer.sharedMaterial = Instantiate(new Material(Shader.Find("Diffuse"))); } unityTile.transform.SetParent(_map.Root, false); } unityTile.Initialize(_map, tileId, _map.WorldRelativeScale, _map.AbsoluteZoom, _map.LoadingTexture); PlaceTile(tileId, unityTile, _map); // Don't spend resources naming objects, as you shouldn't find objects by name anyway! #if UNITY_EDITOR unityTile.gameObject.name = unityTile.CanonicalTileId.ToString(); #endif unityTile.OnHeightDataChanged += TileHeightStateChanged; unityTile.OnRasterDataChanged += TileRasterStateChanged; unityTile.OnVectorDataChanged += TileVectorStateChanged; unityTile.TileState = MeshGeneration.Enums.TilePropertyState.Loading; ActiveTiles.Add(tileId, unityTile); foreach (var factory in Factories) { factory.Register(unityTile); } return unityTile; } public virtual void DisposeTile(UnwrappedTileId tileId) { var unityTile = ActiveTiles[tileId]; foreach (var factory in Factories) { factory.Unregister(unityTile); } unityTile.Recycle(); ActiveTiles.Remove(tileId); _inactiveTiles.Enqueue(unityTile); } /// /// Repositions active tiles instead of recreating them. Useful for panning the map /// /// public virtual void RepositionTile(UnwrappedTileId tileId) { UnityTile currentTile; if (ActiveTiles.TryGetValue(tileId, out currentTile)) { PlaceTile(tileId, currentTile, _map); } } protected abstract void PlaceTile(UnwrappedTileId tileId, UnityTile tile, IMapReadable map); public void ClearMap() { UnregisterAllTiles(); if (Factories != null) { foreach (var tileFactory in Factories) { if (tileFactory != null) { tileFactory.Clear(); DestroyImmediate(tileFactory); } } } foreach (var tileId in _activeTiles.Keys.ToList()) { _activeTiles[tileId].ClearAssets(); DisposeTile(tileId); } foreach (var tile in _inactiveTiles) { tile.ClearAssets(); DestroyImmediate(tile.gameObject); } _inactiveTiles.Clear(); State = ModuleState.Initialized; } public void ReregisterAllTiles() { foreach (var activeTile in _activeTiles) { foreach (var abstractTileFactory in Factories) { abstractTileFactory.Register(activeTile.Value); } } } public void UnregisterAllTiles() { foreach (var activeTile in _activeTiles) { foreach (var abstractTileFactory in Factories) { abstractTileFactory.Unregister(activeTile.Value); } } } public void UnregisterTilesFrom(AbstractTileFactory factory) { foreach (KeyValuePair tileBundle in _activeTiles) { factory.Unregister(tileBundle.Value); } } public void UnregisterAndRedrawTilesFromLayer(VectorTileFactory factory, LayerVisualizerBase layerVisualizer) { foreach (KeyValuePair tileBundle in _activeTiles) { factory.UnregisterLayer(tileBundle.Value, layerVisualizer); } layerVisualizer.Clear(); layerVisualizer.UnbindSubLayerEvents(); layerVisualizer.SetProperties(layerVisualizer.SubLayerProperties); layerVisualizer.InitializeStack(); foreach (KeyValuePair tileBundle in _activeTiles) { factory.RedrawSubLayer(tileBundle.Value, layerVisualizer); } } public void RemoveTilesFromLayer(VectorTileFactory factory, LayerVisualizerBase layerVisualizer) { foreach (KeyValuePair tileBundle in _activeTiles) { factory.UnregisterLayer(tileBundle.Value, layerVisualizer); } factory.RemoveVectorLayerVisualizer(layerVisualizer); } public void ReregisterTilesTo(VectorTileFactory factory) { foreach (KeyValuePair tileBundle in _activeTiles) { factory.Register(tileBundle.Value); } } public void UpdateTileForProperty(AbstractTileFactory factory, LayerUpdateArgs updateArgs) { foreach (KeyValuePair tileBundle in _activeTiles) { factory.UpdateTileProperty(tileBundle.Value, updateArgs); } } #region Events /// /// The OnTileError event triggers when there's a Tile error. /// Returns a instance as a parameter, for the tile on which error occurred. /// public event EventHandler OnTileError; private void Factory_OnTileError(object sender, TileErrorEventArgs e) { EventHandler handler = OnTileError; if (handler != null) { handler(this, e); } } /// /// Event delegate, gets called when terrain factory finishes processing a tile. /// public event Action OnTileHeightProcessingFinished = delegate {}; /// /// Event delegate, gets called when image factory finishes processing a tile. /// public event Action OnTileImageProcessingFinished = delegate {}; /// /// Event delegate, gets called when vector factory finishes processing a tile. /// public event Action OnTileVectorProcessingFinished = delegate {}; #endregion } }