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.
 
 
 
mapbox-sdk/Unity/MeshGeneration/LayerVisualizers/VectorLayerVisualizer.cs

744 lines
23 KiB

using Mapbox.VectorTile.Geometry;
namespace Mapbox.Unity.MeshGeneration.Interfaces
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Mapbox.Unity.MeshGeneration.Data;
using Mapbox.Unity.MeshGeneration.Modifiers;
using Mapbox.VectorTile;
using UnityEngine;
using Mapbox.Unity.Map;
using Mapbox.Unity.Utilities;
using Mapbox.Unity.MeshGeneration.Filters;
using Mapbox.Map;
public class VectorLayerVisualizerProperties
{
public FeatureProcessingStage featureProcessingStage;
public bool buildingsWithUniqueIds = false;
public VectorTileLayer vectorTileLayer;
public ILayerFeatureFilterComparer[] layerFeatureFilters;
public ILayerFeatureFilterComparer layerFeatureFilterCombiner;
}
public class VectorLayerVisualizer : LayerVisualizerBase
{
VectorSubLayerProperties _layerProperties;
public override VectorSubLayerProperties SubLayerProperties
{
get
{
return _layerProperties;
}
set
{
_layerProperties = value;
}
}
public ModifierStackBase DefaultModifierStack
{
get
{
return _defaultStack;
}
set
{
_defaultStack = value;
}
}
protected LayerPerformanceOptions _performanceOptions;
protected Dictionary<UnityTile, List<int>> _activeCoroutines;
int _entityInCurrentCoroutine = 0;
protected ModifierStackBase _defaultStack;
private HashSet<ulong> _activeIds;
private Dictionary<UnityTile, List<ulong>> _idPool; //necessary to keep _activeIds list up to date when unloading tiles
private string _key;
protected HashSet<ModifierBase> _coreModifiers = new HashSet<ModifierBase>();
public override string Key
{
get { return _layerProperties.coreOptions.layerName; }
set { _layerProperties.coreOptions.layerName = value; }
}
public T FindMeshModifier<T>() where T : MeshModifier
{
MeshModifier mod = _defaultStack.MeshModifiers.FirstOrDefault(x => x.GetType() == typeof(T));
return (T)mod;
}
public T FindGameObjectModifier<T>() where T : GameObjectModifier
{
GameObjectModifier mod = _defaultStack.GoModifiers.FirstOrDefault(x => x.GetType() == typeof(T));
return (T)mod;
}
public T AddOrCreateMeshModifier<T>() where T : MeshModifier
{
MeshModifier mod = _defaultStack.MeshModifiers.FirstOrDefault(x => x.GetType() == typeof(T));
if (mod == null)
{
mod = (MeshModifier)CreateInstance(typeof(T));
_coreModifiers.Add(mod);
_defaultStack.MeshModifiers.Add(mod);
}
return (T)mod;
}
public T AddOrCreateGameObjectModifier<T>() where T : GameObjectModifier
{
GameObjectModifier mod = _defaultStack.GoModifiers.FirstOrDefault(x => x.GetType() == typeof(T));
if (mod == null)
{
mod = (GameObjectModifier)CreateInstance(typeof(T));
_coreModifiers.Add(mod);
_defaultStack.GoModifiers.Add(mod);
}
return (T)mod;
}
private void UpdateVector(object sender, System.EventArgs eventArgs)
{
VectorLayerUpdateArgs layerUpdateArgs = eventArgs as VectorLayerUpdateArgs;
layerUpdateArgs.visualizer = this;
layerUpdateArgs.effectsVectorLayer = true;
if (layerUpdateArgs.modifier != null)
{
layerUpdateArgs.property.PropertyHasChanged -= layerUpdateArgs.modifier.UpdateModifier;
layerUpdateArgs.modifier.ModifierHasChanged -= UpdateVector;
}
else if (layerUpdateArgs.property != null)
{
layerUpdateArgs.property.PropertyHasChanged -= UpdateVector;
}
UnbindSubLayerEvents();
OnUpdateLayerVisualizer(layerUpdateArgs);
}
public override void UnbindSubLayerEvents()
{
foreach (var modifier in _defaultStack.MeshModifiers)
{
modifier.UnbindProperties();
modifier.ModifierHasChanged -= UpdateVector;
}
foreach (var modifier in _defaultStack.GoModifiers)
{
modifier.UnbindProperties();
modifier.ModifierHasChanged -= UpdateVector;
}
_layerProperties.extrusionOptions.PropertyHasChanged -= UpdateVector;
_layerProperties.coreOptions.PropertyHasChanged -= UpdateVector;
_layerProperties.filterOptions.PropertyHasChanged -= UpdateVector;
_layerProperties.filterOptions.UnRegisterFilters();
_layerProperties.materialOptions.PropertyHasChanged -= UpdateVector;
_layerProperties.PropertyHasChanged -= UpdateVector;
}
public override void SetProperties(VectorSubLayerProperties properties)
{
_coreModifiers = new HashSet<ModifierBase>();
if (_layerProperties == null && properties != null)
{
_layerProperties = properties;
if (_performanceOptions == null && properties.performanceOptions != null)
{
_performanceOptions = properties.performanceOptions;
}
}
if (_layerProperties.coreOptions.combineMeshes)
{
if (_defaultStack == null)
{
_defaultStack = ScriptableObject.CreateInstance<MergedModifierStack>();
}
else if (!(_defaultStack is MergedModifierStack))
{
_defaultStack.Clear();
DestroyImmediate(_defaultStack);
_defaultStack = ScriptableObject.CreateInstance<MergedModifierStack>();
}
else
{
// HACK - to clean out the Modifiers.
// Will this trigger GC that we could avoid ??
_defaultStack.MeshModifiers.Clear();
_defaultStack.GoModifiers.Clear();
}
}
else
{
if (_defaultStack == null)
{
_defaultStack = ScriptableObject.CreateInstance<ModifierStack>();
((ModifierStack)_defaultStack).moveFeaturePositionTo = _layerProperties.moveFeaturePositionTo;
}
if (!(_defaultStack is ModifierStack))
{
_defaultStack.Clear();
DestroyImmediate(_defaultStack);
_defaultStack = ScriptableObject.CreateInstance<ModifierStack>();
((ModifierStack)_defaultStack).moveFeaturePositionTo = _layerProperties.moveFeaturePositionTo;
}
else
{
// HACK - to clean out the Modifiers.
// Will this trigger GC that we could avoid ??
_defaultStack.MeshModifiers.Clear();
_defaultStack.GoModifiers.Clear();
}
}
//Add any additional modifiers that were added.
if (_defaultStack.MeshModifiers == null)
{
_defaultStack.MeshModifiers = new List<MeshModifier>();
}
if (_defaultStack.GoModifiers == null)
{
_defaultStack.GoModifiers = new List<GameObjectModifier>();
}
// Setup material options.
_layerProperties.materialOptions.SetDefaultMaterialOptions();
switch (_layerProperties.coreOptions.geometryType)
{
case VectorPrimitiveType.Point:
case VectorPrimitiveType.Custom:
{
// Let the user add anything that they want
if (_layerProperties.coreOptions.snapToTerrain == true)
{
AddOrCreateMeshModifier<SnapTerrainModifier>();
}
break;
}
case VectorPrimitiveType.Line:
{
if (_layerProperties.coreOptions.snapToTerrain == true)
{
AddOrCreateMeshModifier<SnapTerrainModifier>();
}
var lineMeshMod = AddOrCreateMeshModifier<LineMeshModifier>();
lineMeshMod.SetProperties(_layerProperties.lineGeometryOptions);
lineMeshMod.ModifierHasChanged += UpdateVector;
if (_layerProperties.extrusionOptions.extrusionType != Map.ExtrusionType.None)
{
var heightMod = AddOrCreateMeshModifier<HeightModifier>();
heightMod.SetProperties(_layerProperties.extrusionOptions);
heightMod.ModifierHasChanged += UpdateVector;
}
else
{
_layerProperties.extrusionOptions.PropertyHasChanged += UpdateVector;
}
//collider modifier options
var lineColliderMod = AddOrCreateGameObjectModifier<ColliderModifier>();
lineColliderMod.SetProperties(_layerProperties.colliderOptions);
lineColliderMod.ModifierHasChanged += UpdateVector;
var lineStyleMod = AddOrCreateGameObjectModifier<MaterialModifier>();
lineStyleMod.SetProperties(_layerProperties.materialOptions);
lineStyleMod.ModifierHasChanged += UpdateVector;
break;
}
case VectorPrimitiveType.Polygon:
{
if (_layerProperties.coreOptions.snapToTerrain == true)
{
AddOrCreateMeshModifier<SnapTerrainModifier>();
}
var poly = AddOrCreateMeshModifier<PolygonMeshModifier>();
UVModifierOptions uvModOptions = new UVModifierOptions();
uvModOptions.texturingType = (_layerProperties.materialOptions.style == StyleTypes.Custom) ? _layerProperties.materialOptions.customStyleOptions.texturingType : _layerProperties.materialOptions.texturingType;
uvModOptions.atlasInfo = (_layerProperties.materialOptions.style == StyleTypes.Custom) ? _layerProperties.materialOptions.customStyleOptions.atlasInfo : _layerProperties.materialOptions.atlasInfo;
uvModOptions.style = _layerProperties.materialOptions.style;
poly.SetProperties(uvModOptions);
if (_layerProperties.extrusionOptions.extrusionType != Map.ExtrusionType.None)
{
//replace materialOptions with styleOptions
bool useTextureSideWallModifier =
(_layerProperties.materialOptions.style == StyleTypes.Custom) ?
(_layerProperties.materialOptions.customStyleOptions.texturingType == UvMapType.Atlas || _layerProperties.materialOptions.customStyleOptions.texturingType == UvMapType.AtlasWithColorPalette)
: (_layerProperties.materialOptions.texturingType == UvMapType.Atlas || _layerProperties.materialOptions.texturingType == UvMapType.AtlasWithColorPalette);
if (useTextureSideWallModifier)
{
var atlasMod = AddOrCreateMeshModifier<TextureSideWallModifier>();
GeometryExtrusionWithAtlasOptions atlasOptions = new GeometryExtrusionWithAtlasOptions(_layerProperties.extrusionOptions, uvModOptions);
atlasMod.SetProperties(atlasOptions);
_layerProperties.extrusionOptions.PropertyHasChanged += UpdateVector;
}
else
{
var heightMod = AddOrCreateMeshModifier<HeightModifier>();
heightMod.SetProperties(_layerProperties.extrusionOptions);
heightMod.ModifierHasChanged += UpdateVector;
}
}
else
{
_layerProperties.extrusionOptions.PropertyHasChanged += UpdateVector;
}
//collider modifier options
var polyColliderMod = AddOrCreateGameObjectModifier<ColliderModifier>();
polyColliderMod.SetProperties(_layerProperties.colliderOptions);
polyColliderMod.ModifierHasChanged += UpdateVector;
var styleMod = AddOrCreateGameObjectModifier<MaterialModifier>();
styleMod.SetProperties(_layerProperties.materialOptions);
styleMod.ModifierHasChanged += UpdateVector;
bool isCustomStyle = (_layerProperties.materialOptions.style == StyleTypes.Custom);
if ((isCustomStyle) ? (_layerProperties.materialOptions.customStyleOptions.texturingType == UvMapType.AtlasWithColorPalette)
: (_layerProperties.materialOptions.texturingType == UvMapType.AtlasWithColorPalette))
{
var colorPaletteMod = AddOrCreateGameObjectModifier<MapboxStylesColorModifier>();
colorPaletteMod.m_scriptablePalette = (isCustomStyle) ? _layerProperties.materialOptions.customStyleOptions.colorPalette : _layerProperties.materialOptions.colorPalette;
_layerProperties.materialOptions.PropertyHasChanged += UpdateVector;
//TODO: Add SetProperties Method to MapboxStylesColorModifier
}
break;
}
default:
break;
}
_layerProperties.coreOptions.PropertyHasChanged += UpdateVector;
_layerProperties.filterOptions.PropertyHasChanged += UpdateVector;
_layerProperties.filterOptions.RegisterFilters();
if (_layerProperties.MeshModifiers != null)
{
_defaultStack.MeshModifiers.AddRange(_layerProperties.MeshModifiers);
}
if (_layerProperties.GoModifiers != null)
{
_defaultStack.GoModifiers.AddRange(_layerProperties.GoModifiers);
}
_layerProperties.PropertyHasChanged += UpdateVector;
}
/// <summary>
/// Add the replacement criteria to any mesh modifiers implementing IReplaceable
/// </summary>
/// <param name="criteria">Criteria.</param>
protected void SetReplacementCriteria(IReplacementCriteria criteria)
{
foreach (var meshMod in _defaultStack.MeshModifiers)
{
if (meshMod is IReplaceable)
{
((IReplaceable)meshMod).Criteria.Add(criteria);
}
}
}
#region Private Helper Methods
/// <summary>
/// Convenience function to add feature to Tile object pool.
/// </summary>
/// <param name="feature">Feature to be added to the pool.</param>
/// <param name="tile">Tile currently being processed.</param>
private void AddFeatureToTileObjectPool(VectorFeatureUnity feature, UnityTile tile)
{
_activeIds.Add(feature.Data.Id);
if (!_idPool.ContainsKey(tile))
{
_idPool.Add(tile, new List<ulong>() { feature.Data.Id });
}
else
{
_idPool[tile].Add(feature.Data.Id);
}
}
/// <summary>
/// Apply filters to the layer and check if the current feature is eleigible for rendering.
/// </summary>
/// <returns><c>true</c>, if feature eligible after filtering was applied, <c>false</c> otherwise.</returns>
/// <param name="feature">Feature.</param>
private bool IsFeatureEligibleAfterFiltering(VectorFeatureUnity feature, UnityTile tile, VectorLayerVisualizerProperties layerProperties)
{
if (layerProperties.layerFeatureFilters.Count() == 0)
{
return true;
}
else
{
// build features only if the filter returns true.
if (layerProperties.layerFeatureFilterCombiner.Try(feature))
{
return true;
}
}
return false;
}
/// <summary>
/// Function to fetch feature in vector tile at the index specified.
/// </summary>
/// <returns>The feature in tile at the index requested.</returns>
/// <param name="tile">Unity Tile containing the feature.</param>
/// <param name="index">Index of the vector feature being requested.</param>
private VectorFeatureUnity GetFeatureinTileAtIndex(int index, UnityTile tile, VectorLayerVisualizerProperties layerProperties)
{
return new VectorFeatureUnity(layerProperties.vectorTileLayer.GetFeature(index),
tile,
layerProperties.vectorTileLayer.Extent,
layerProperties.buildingsWithUniqueIds);
}
/// <summary>
/// Function to check if the feature is already in the active Id pool, features already in active Id pool should be skipped from processing.
/// </summary>
/// <returns><c>true</c>, if feature is already in activeId pool or if the layer has buildingsWithUniqueId flag set to <see langword="true"/>, <c>false</c> otherwise.</returns>
/// <param name="featureId">Feature identifier.</param>
private bool ShouldSkipProcessingFeatureWithId(ulong featureId, UnityTile tile, VectorLayerVisualizerProperties layerProperties)
{
return (layerProperties.buildingsWithUniqueIds && _activeIds.Contains(featureId));
}
/// <summary>
/// Gets a value indicating whether this entity per coroutine bucket is full.
/// </summary>
/// <value><c>true</c> if coroutine bucket is full; otherwise, <c>false</c>.</value>
private bool IsCoroutineBucketFull
{
get
{
return (_performanceOptions != null && _performanceOptions.isEnabled && _entityInCurrentCoroutine >= _performanceOptions.entityPerCoroutine);
}
}
public override bool Active
{
get
{
return _layerProperties.coreOptions.isActive;
}
}
#endregion
public override void Initialize()
{
base.Initialize();
_entityInCurrentCoroutine = 0;
_activeCoroutines = new Dictionary<UnityTile, List<int>>();
_activeIds = new HashSet<ulong>();
_idPool = new Dictionary<UnityTile, List<ulong>>();
if (_defaultStack != null)
{
_defaultStack.Initialize();
}
}
public override void InitializeStack()
{
if (_defaultStack != null)
{
_defaultStack.Initialize();
}
}
public override void Create(VectorTileLayer layer, UnityTile tile, Action<UnityTile, LayerVisualizerBase> callback)
{
if (!_activeCoroutines.ContainsKey(tile))
_activeCoroutines.Add(tile, new List<int>());
_activeCoroutines[tile].Add(Runnable.Run(ProcessLayer(layer, tile, tile.UnwrappedTileId, callback)));
}
protected IEnumerator ProcessLayer(VectorTileLayer layer, UnityTile tile, UnwrappedTileId tileId, Action<UnityTile, LayerVisualizerBase> callback = null)
{
if (tile == null)
{
yield break;
}
VectorLayerVisualizerProperties tempLayerProperties = new VectorLayerVisualizerProperties();
tempLayerProperties.vectorTileLayer = layer;
tempLayerProperties.featureProcessingStage = FeatureProcessingStage.PreProcess;
//Get all filters in the array.
tempLayerProperties.layerFeatureFilters = _layerProperties.filterOptions.filters.Select(m => m.GetFilterComparer()).ToArray();
// Pass them to the combiner
tempLayerProperties.layerFeatureFilterCombiner = new Filters.LayerFilterComparer();
switch (_layerProperties.filterOptions.combinerType)
{
case Filters.LayerFilterCombinerOperationType.Any:
tempLayerProperties.layerFeatureFilterCombiner = Filters.LayerFilterComparer.AnyOf(tempLayerProperties.layerFeatureFilters);
break;
case Filters.LayerFilterCombinerOperationType.All:
tempLayerProperties.layerFeatureFilterCombiner = Filters.LayerFilterComparer.AllOf(tempLayerProperties.layerFeatureFilters);
break;
case Filters.LayerFilterCombinerOperationType.None:
tempLayerProperties.layerFeatureFilterCombiner = Filters.LayerFilterComparer.NoneOf(tempLayerProperties.layerFeatureFilters);
break;
default:
break;
}
tempLayerProperties.buildingsWithUniqueIds = (_layerProperties.honorBuildingIdSetting) && _layerProperties.buildingsWithUniqueIds;
//find any replacement criteria and assign them
foreach (var goModifier in _defaultStack.GoModifiers)
{
if (goModifier is IReplacementCriteria && goModifier.Active)
{
SetReplacementCriteria((IReplacementCriteria)goModifier);
}
}
#region PreProcess & Process.
var featureCount = (tempLayerProperties.vectorTileLayer == null) ? 0 : tempLayerProperties.vectorTileLayer.FeatureCount();
do
{
for (int i = 0; i < featureCount; i++)
{
//checking if tile is recycled and changed
if (tile.UnwrappedTileId != tileId || !_activeCoroutines.ContainsKey(tile) || tile.TileState == Enums.TilePropertyState.Unregistered)
{
yield break;
}
ProcessFeature(i, tile, tempLayerProperties, layer.Extent);
if (IsCoroutineBucketFull && !(Application.isEditor && !Application.isPlaying))
{
//Reset bucket..
_entityInCurrentCoroutine = 0;
yield return null;
}
}
// move processing to next stage.
tempLayerProperties.featureProcessingStage++;
} while (tempLayerProperties.featureProcessingStage == FeatureProcessingStage.PreProcess
|| tempLayerProperties.featureProcessingStage == FeatureProcessingStage.Process);
#endregion
#region PostProcess
// TODO : Clean this up to follow the same pattern.
var mergedStack = _defaultStack as MergedModifierStack;
if (mergedStack != null && tile != null)
{
mergedStack.End(tile, tile.gameObject, layer.Name);
}
#endregion
if (callback != null)
callback(tile, this);
}
private bool ProcessFeature(int index, UnityTile tile, VectorLayerVisualizerProperties layerProperties, float layerExtent)
{
var fe = layerProperties.vectorTileLayer.GetFeature(index);
List<List<Point2d<float>>> geom;
if (layerProperties.buildingsWithUniqueIds == true) //ids from building dataset is big ulongs
{
geom = fe.Geometry<float>(); //and we're not clipping by passing no parameters
if (geom[0][0].X < 0 || geom[0][0].X > layerExtent || geom[0][0].Y < 0 || geom[0][0].Y > layerExtent)
{
return false;
}
}
else //streets ids, will require clipping
{
geom = fe.Geometry<float>(0); //passing zero means clip at tile edge
}
var feature = new VectorFeatureUnity(layerProperties.vectorTileLayer.GetFeature(index),
geom,
tile,
layerProperties.vectorTileLayer.Extent,
layerProperties.buildingsWithUniqueIds);
if (IsFeatureEligibleAfterFiltering(feature, tile, layerProperties))
{
if (tile != null && tile.gameObject != null && tile.VectorDataState != Enums.TilePropertyState.Cancelled)
{
switch (layerProperties.featureProcessingStage)
{
case FeatureProcessingStage.PreProcess:
//pre process features.
PreProcessFeatures(feature, tile, tile.gameObject);
break;
case FeatureProcessingStage.Process:
//skip existing features, only works on tilesets with unique ids
if (ShouldSkipProcessingFeatureWithId(feature.Data.Id, tile, layerProperties))
{
return false;
}
//feature not skipped. Add to pool only if features are in preprocess stage.
AddFeatureToTileObjectPool(feature, tile);
Build(feature, tile, tile.gameObject);
break;
case FeatureProcessingStage.PostProcess:
break;
default:
break;
}
_entityInCurrentCoroutine++;
}
}
return true;
}
/// <summary>
/// Preprocess features, finds the relevant modifier stack and passes the feature to that stack
/// </summary>
/// <param name="feature"></param>
/// <param name="tile"></param>
/// <param name="parent"></param>
private bool IsFeatureValid(VectorFeatureUnity feature)
{
if (feature.Properties.ContainsKey("extrude") && !bool.Parse(feature.Properties["extrude"].ToString()))
return false;
if (feature.Points.Count < 1)
return false;
return true;
}
protected void PreProcessFeatures(VectorFeatureUnity feature, UnityTile tile, GameObject parent)
{
//find any replacement criteria and assign them
foreach (var goModifier in _defaultStack.GoModifiers)
{
if (goModifier is IReplacementCriteria && goModifier.Active)
{
goModifier.FeaturePreProcess(feature);
}
}
}
protected void Build(VectorFeatureUnity feature, UnityTile tile, GameObject parent)
{
if (feature.Properties.ContainsKey("extrude") && !Convert.ToBoolean(feature.Properties["extrude"]))
return;
if (feature.Points.Count < 1)
return;
//this will be improved in next version and will probably be replaced by filters
var styleSelectorKey = _layerProperties.coreOptions.sublayerName;
var meshData = new MeshData();
meshData.TileRect = tile.Rect;
//and finally, running the modifier stack on the feature
var processed = false;
if (!processed)
{
if (_defaultStack != null)
{
_defaultStack.Execute(tile, feature, meshData, parent, styleSelectorKey);
}
}
}
/// <summary>
/// Handle tile destruction event and propagate it to modifier stacks
/// </summary>
/// <param name="tile">Destroyed tile object</param>
public override void OnUnregisterTile(UnityTile tile)
{
base.OnUnregisterTile(tile);
if (_activeCoroutines.ContainsKey(tile))
{
foreach (var cor in _activeCoroutines[tile])
{
Runnable.Stop(cor);
}
}
_activeCoroutines.Remove(tile);
if (_defaultStack != null)
{
_defaultStack.UnregisterTile(tile);
}
//removing ids from activeIds list so they'll be recreated next time tile loads (necessary when you're unloading/loading tiles)
if (_idPool.ContainsKey(tile))
{
foreach (var item in _idPool[tile])
{
_activeIds.Remove(item);
}
_idPool[tile].Clear();
}
}
public override void Clear()
{
_idPool.Clear();
_defaultStack.Clear();
foreach (var mod in _defaultStack.MeshModifiers)
{
if (mod == null)
{
continue;
}
if (_coreModifiers.Contains(mod))
{
DestroyImmediate(mod);
}
}
foreach (var mod in _defaultStack.GoModifiers)
{
if (mod == null)
{
continue;
}
mod.Clear();
if (_coreModifiers.Contains(mod))
{
DestroyImmediate(mod);
}
}
DestroyImmediate(_defaultStack);
}
}
}