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();
}
// Identify feature by specifying a (unique) index based on some criteria. This can very per
// vector tile provider.
public void IdentifyLayers( Func<VectorTileLayer, List<KeyValuePair<string, object>>, int> selectionCallback )
{
Debug.Assert( triangulated, "First call Triangulate." );
var feature = layer.VectorTileFeatures[f];
feature.SelectedLayerIdx = 254;
int uniqueId = selectionCallback( layer, feature.Attributes );
if (uniqueId > -1)
feature.SelectedLayerIdx = uniqueId;
break; // Done with this feature.
// 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.
// Also out puts a remap of layer indices. For instance for a specific tile, only layerIdx 1, 6 and 3 are used.
// Then this a remapping of 1->0, 6->1, 3->2. So that in the shader only 3 textures are needed starting from 0, 1.. etc.
public byte[] RenderToTextureSingle(
int resolution,
out Dictionary<byte, byte> remappedLayerIndices,
out List<Vector3Int> failedPixels,
bool? cancelToken)
{
Debug.Assert( triangulated, "First call Triangulate." );
Debug.Assert( layersIdentified, "Identify layers first." );
Debug.Assert( resolution > 1, "Must be at least 2." );
byte [] texture = new byte[resolution*resolution];
failedPixels = new List<Vector3Int>();
remappedLayerIndices = new Dictionary<byte, byte>();
byte cachedPixel = 0;
// For each layer, for each pixel, check triangle intersections.
for (int l = 0;l < layers.Count && !cancelToken.Value;l++)
TriangulatedPolygon cachedPoly = default;
int cachedVtxIdx = -1;
int cachedTriIdx = -1;
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)
{
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;
if ( cachedPixel == 8 )
{
int jt = 0;
}
texture[(resolution - y -1)*resolution+x] = cachedPixel;
break;
}
}
}
}
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// Determine number of different layers. For instance, if only layer 3, 8, 14 and 15 are used, we select
// a material that only uses 4 textures and put 3 -> Albedo_0 -> 8 to Albedo_1, etc. in the shader.
cachedPixel = 255;
int remappedIndexCounter = -1;
byte remappedPixel = 255;
int addr = 0;
for (int y = 0;y < resolution && !cancelToken.Value;y++)
{
for (int x = 0;x < resolution;x++)
{
byte pixel = texture[addr];
if (pixel == 8 )
{
int jt = 0;
}
if ( pixel != cachedPixel && pixel != 254 /*used for not matched layer*/ )
{
if (!remappedLayerIndices.TryGetValue( pixel, out remappedPixel ))
{
if (remappedIndexCounter < 253) // 254 is reserved for no layer match, 255 is ray hit with triangle.
{
remappedIndexCounter++;
remappedPixel = (byte)remappedIndexCounter;
remappedLayerIndices.Add( pixel, remappedPixel );
}
else remappedPixel = 0;
}
cachedPixel = pixel;
}
texture[addr] = remappedPixel;
addr++;
}
}
Debug.Assert(remappedIndexCounter <= 254, "Exceeded layer count." );
return texture;
public static VectorTile LoadFromUrl( string url, bool autoStart=true )
tile.request = UnityWebRequest.Get( url );
if (autoStart)
{
tile.StartDownload();
}