Newer
Older
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
// Always multiple of 3. Each 3 form a triangle.
public List<Vector2> vertices;
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public List<Vector2> mins;
public List<Vector2> maxs;
public List<float> denoms;
public void OptimizeForIsPointInTriangle()
{
if ( vertices == null ) return; // Non poly layers have this.
mins = new List<Vector2>( vertices.Count / 3 );
maxs = new List<Vector2>( vertices.Count / 3 );
denoms = new List<float>( vertices.Count / 3 );
for ( int i = 0; i < vertices.Count; i += 3 )
{
float minx = float.MaxValue;
float miny = float.MaxValue;
if (vertices[i+0].x < minx) minx = vertices[i].x;
if (vertices[i+0].y < miny) miny = vertices[i].y;
if (vertices[i+1].x < minx) minx = vertices[i+1].x;
if (vertices[i+1].y < miny) miny = vertices[i+1].y;
if (vertices[i+2].x < minx) minx = vertices[i+2].x;
if (vertices[i+2].y < miny) miny = vertices[i+2].y;
mins.Add( new Vector2( minx, miny ) );
float maxx = float.MinValue;
float maxy = float.MinValue;
if (vertices[i+0].x > maxx) maxx = vertices[i].x;
if (vertices[i+0].y > maxy) maxy = vertices[i].y;
if (vertices[i+1].x > maxx) maxx = vertices[i+1].x;
if (vertices[i+1].y > maxy) maxy = vertices[i+1].y;
if (vertices[i+2].x > maxx) maxx = vertices[i+2].x;
if (vertices[i+2].y > maxy) maxy = vertices[i+2].y;
maxs.Add( new Vector2( maxx, maxy ) );
// float denominator = ((vertex2.y - vertex3.y) * (vertex1.x - vertex3.x) + (vertex3.x - vertex2.x) * (vertex1.y - vertex3.y));
float denominator = ((vertices[i+1].y - vertices[i+2].y) * (vertices[i].x - vertices[i+2].x) + (vertices[i+2].x - vertices[i+1].x) * (vertices[i].y - vertices[i+2].y));
denoms.Add( 1.0f / denominator );
}
}
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 UnityWebRequest request;
public void StartDownload()
{
Debug.Assert(!started);
request.SendWebRequest();
started = true;
}
public bool IsFinished()
{
if (finished)
return true;
if (!request.isDone)
return false;
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++)
{
polygons.Add( new TriangulatedPolygon() ); // Add empty to ensure list(layer) of lists(features) match.
if (features[j].GeometryType != Tile.GeomType.Polygon)
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 );
{
double x = vertices[indices[k]*2];
double y = vertices[indices[k]*2+1];
poly.vertices.Add( new Vector2( (float)x, (float)y ) );
}
Debug.Assert( poly.vertices.Count % 3 == 0 );
}
catch (Exception)
{
numFailedPolys++;
}
}
polygonLayers.Add( polygons );
}
triangulated = true;
return numFailedPolys;
}
public void OptimizeForPointIsInsideTriangle()
{
for (int i = 0;i < polygonLayers.Count;i++)
for (int j = 0;j < polygonLayers[i].Count;j++)
polygonLayers[i][j].OptimizeForIsPointInTriangle();
}
// Calls callback(x, y, channel) for each raster position (256 channels) where each value represents a single channel.
// If no polygon was hit, 255 is called.
// If polygon was hit, but no feature was matched, 254 is called.
// Returns a list of failed to match pixels. This can be due to geometry not exactly matching or a layer not being found.
public List<Vector3Int> RenderToTextureSingle(
int width, int height,
List<string> matchingAttribKeys,
List<List<string>> layerNamesList,
Func<int, int, byte, bool> callback )
{
Debug.Assert( triangulated, "First call Triangulate." );
Debug.Assert( layerNamesList.Count < 254, "254 and 255 are reserved for no hit or no match." );
// First match layers to layer names.
bool cancel = false;
List<Vector3Int> failedPixels = new List<Vector3Int>();
var feature = layer.VectorTileFeatures[f];
feature.SelectedLayerIdx = 254;
if (feature.GeometryType != Tile.GeomType.Polygon)
continue;
if (feature.Attributes == null)
continue;
// Find matching layer.
bool matchingLayerFound = false;
for (int a = 0;a < feature.Attributes.Count && !matchingLayerFound;a++)
{
for (int m = 0;m < matchingAttribKeys.Count && !matchingLayerFound;m++)
{
if (feature.Attributes[a].Key != matchingAttribKeys[m])
continue;
string function = feature.Attributes[a].Value as string;
for (int layerIdx = 0;layerIdx < layerNamesList.Count && !matchingLayerFound;layerIdx++)
{
for (int layerNameIdx = 0;layerNameIdx < layerNamesList[layerIdx].Count;layerNameIdx++)
{
if (function.Contains( layerNamesList[layerIdx][layerNameIdx] ))
{
feature.SelectedLayerIdx = layerIdx;
matchingLayerFound = true;
break;
}
}
}
}
}
// try Layer.name
if (!matchingLayerFound)
{
for (int layerIdx = 0;layerIdx < layerNamesList.Count && !matchingLayerFound;layerIdx++)
{
for (int layerNameIdx = 0;layerNameIdx < layerNamesList[layerIdx].Count;layerNameIdx++)
{
if (layer.Name.Contains( layerNamesList[layerIdx][layerNameIdx] ))
{
feature.SelectedLayerIdx = layerIdx;
matchingLayerFound = true;
break;
}
}
}
}
}
}
// For each layer, for each pixel, now check triangle intersections.
for (int l = 0;l < layers.Count && !cancel;l++)
{
var layer = layers[l];
float fx = (float)layer.Extent / width;
TriangulatedPolygon cachedPoly = default;
int cachedVtxIdx = -1;
int cachedTriIdx = -1;
byte cachedPixel = 0;
for (int y = 0;y < height && !cancel;y++)
{
for (int x = 0;x < width && !cancel;x++)
Vector2 p = new Vector2(fx*x+0.5f*fx, fx*y+0.5f*fx);
bool hit = false;
// First try cache, quite often adjacent pixels will hit the same triangle.
if (cachedVtxIdx!=-1)
{
var vertices = cachedPoly.vertices;
var denoms = cachedPoly.denoms;
var mins = cachedPoly.mins;
var maxs = cachedPoly.maxs;
if (!(p.x < mins[cachedTriIdx].x || p.x > maxs[cachedTriIdx].x) &&
!(p.y < mins[cachedTriIdx].y || p.y > maxs[cachedTriIdx].y))
{
hit = GeomUtil.PointIsInsideTriangle2( p, vertices[cachedVtxIdx], vertices[cachedVtxIdx+1], vertices[cachedVtxIdx+2], denoms[cachedTriIdx] );
if (hit)
{
cancel = callback( x, y, cachedPixel ); // If SelectedLayerIdx did not match, it was set to 254.
continue; // Pass from cache, continue to next pixel.
}
}
}
// Must check all triangles, no cache or was no hit with cache.
for (int f = 0;f < layer.VectorTileFeatures.Count;f++)
{
var feature = layer.VectorTileFeatures[f];
continue;
var polygons = polygonLayers[l][f];
if (polygons.vertices.Count == 0)
continue;
cachedVtxIdx = -1;
cachedPoly = polygonLayers[l][f];
var mins = cachedPoly.mins;
var maxs = cachedPoly.maxs;
var denoms = cachedPoly.denoms;
var vertices = cachedPoly.vertices;
for (int vIdx = 0, triIdx = 0; vIdx < vertices.Count; vIdx += 3, triIdx++)
if ( p.x < mins[triIdx].x || p.x > maxs[triIdx].x ) continue;
if ( p.y < mins[triIdx].y || p.y > maxs[triIdx].y ) continue;
hit = GeomUtil.PointIsInsideTriangle2( p, vertices[vIdx], vertices[vIdx+1], vertices[vIdx+2], denoms[triIdx] );
cachedTriIdx = triIdx;
cachedVtxIdx = vIdx;
cachedPixel = (byte)feature.SelectedLayerIdx;
cancel = callback( x, y, cachedPixel );
break;
}
}
if (hit) break;
}
if ( !hit )
{
failedPixels.Add( new Vector3Int( x, y, 255 ) );
}
}
public static VectorTile LoadFromUrl( string url, bool autoStart=true )
tile.request = UnityWebRequest.Get( url );
if (autoStart)
{
tile.StartDownload();
}