using Codice.Client.Common.FsNodeReaders; using Mapbox.Vector.Tile; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace Wander { // Always multiple of 3. Each 3 form a triangle. public struct TriangulatedPolygon { public List<Vector2> vertices; } public class VectorTile { public bool Valid => valid; public bool Finished => finished; public bool Triangulated => triangulated; private bool valid; private bool finished; private bool triangulated; private Task parseTileTask; private List<VectorTileLayer> layers; private List<List<TriangulatedPolygon>> polygonLayers; internal UnityWebRequestAsyncOperation request; public bool IsFinished() { if (finished) return true; if (!request.isDone) return false; if (request.webRequest.result == UnityWebRequest.Result.Success) { if (parseTileTask == null) { parseTileTask = Task.Run( () => { var stream = new MemoryStream( request.webRequest.downloadHandler.data ); layers = VectorTileParser.Parse( stream ); } ); } else if (parseTileTask.IsCompleted) { valid = parseTileTask.IsCompletedSuccessfully; finished = true; parseTileTask = null; } } return finished; } // Number of failed polys to triangulate are returned. public int Triangulate() { if (triangulated ) return 0; int numFailedPolys = 0; polygonLayers = new List<List<TriangulatedPolygon>>(); List<double> vertices = new List<double>(); List<int> holeIndices = new List<int>(); for ( int i =0; i < layers.Count; i++ ) { var features = layers[i].VectorTileFeatures; var polygons = new List<TriangulatedPolygon>(); for (int j = 0; j< features.Count; j++) { if (features[j].GeometryType != Tile.GeomType.Polygon) continue; vertices.Clear(); holeIndices.Clear(); var rings = features[j].Geometry; for (int k = 0;k < rings.Count;k++) { if (k != 0) // if is not first ring, this is a hole { holeIndices.Add( vertices.Count / 2 ); } var ring = rings[k]; for (int q = 0;q < ring.Count;q++) { vertices.Add( ring[q].X ); vertices.Add( ring[q].Y ); } } try { var indices = EarcutNet.Earcut.Tessellate( vertices, holeIndices ); TriangulatedPolygon poly = new TriangulatedPolygon(); poly.vertices = new List<Vector2>( indices.Count ); for (int k = 0;k < indices.Count;k+= 3) { double x = vertices[indices[k]*2]; double y = vertices[indices[k]*2+1]; poly.vertices.Add( new Vector2( (float)x, (float)y ) ); } polygons.Add( poly ); Debug.Assert( poly.vertices.Count % 3 == 0 ); } catch (Exception) { numFailedPolys++; } } polygonLayers.Add( polygons ); } triangulated = true; return numFailedPolys; } // Returns an R8 texture (256 channels) where each value represents a single channel. // Retrieve back in shader using: texIdx = floor( texture.r*255+0.5 ). // If no polygon was hit, 255 is encoded to the texture. // If polygon was hit, but no feature was matched, 254 is encoded. public Texture2D RenderToTextureSingle( int width, int height, List<string> featureIds ) { Debug.Assert( triangulated, "First call Triangulate." ); Debug.Assert( featureIds.Count < 254, "254 and 255 are reserved for no hit or no match." ); byte [] pixels = new byte[width*height]; int addr = 0; for ( int y = 0; y < height; y++) { for ( int x = 0; x < width; x++ ) { Vector2 p = new Vector2(x, y); for ( int l = 0; l < layers.Count; l++ ) { var layer = layers[l]; for ( int f = 0; f < layer.VectorTileFeatures.Count; f ++ ) { var feature = layer.VectorTileFeatures[f]; if ( feature.GeometryType != Tile.GeomType.Polygon) continue; var triangles = polygonLayers[l][f].vertices; bool hit = false; bool match = false; for ( int i = 0; i < triangles.Count; i += 3 ) { // Cool part is, that geometry is in local space relative to a grid starting at: 0,0 which matches with the texture raster. // No conversions necessary. hit = GeomUtil.PointIsInsideTriangle( p, triangles[i], triangles[i+1], triangles[i+2] ); if ( hit ) { for (int ids = 0; ids < featureIds.Count; ids++ ) { if (feature.Id.Contains( featureIds[ids] )) { match = true; pixels[addr] = (byte) ids; break; } } break; } } if ( !hit ) { pixels[addr] = 255; } else if (!match) // hit but no match { pixels[addr] = 254; } } } ++addr; } } Texture2D t = new Texture2D(width, height, TextureFormat.R8, false); t.SetPixelData( pixels, 0, 0 ); return t; } } public static class VectorTileLoader { public static VectorTile LoadFromUrl( string url ) { VectorTile tile = new VectorTile(); tile.request = UnityWebRequest.Get( url ).SendWebRequest(); return tile; } } }