diff --git a/Runtime/VectorTile.cs b/Runtime/VectorTile.cs index c1f603165768ca216eff0c53154f29eead1b36ba..b28dae785c9d688fb4fce27d115ebff655bef7a3 100644 --- a/Runtime/VectorTile.cs +++ b/Runtime/VectorTile.cs @@ -2,16 +2,58 @@ using Mapbox.Vector.Tile; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; +using UnityEditor; using UnityEngine; using UnityEngine.Networking; +using static Wander.Easing; namespace Wander { // Always multiple of 3. Each 3 form a triangle. - public struct TriangulatedPolygon + public class TriangulatedPolygon { public List<Vector2> vertices; + 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 class VectorTile @@ -83,8 +125,11 @@ namespace Wander 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) + { continue; + } vertices.Clear(); holeIndices.Clear(); var rings = features[j].Geometry; @@ -112,7 +157,7 @@ namespace Wander double y = vertices[indices[k]*2+1]; poly.vertices.Add( new Vector2( (float)x, (float)y ) ); } - polygons.Add( poly ); + polygons[polygons.Count-1] = poly; Debug.Assert( poly.vertices.Count % 3 == 0 ); } catch (Exception) @@ -126,20 +171,32 @@ namespace Wander 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. - public void RenderToTextureSingle( int width, int height, string attributeKeyMatch, List<List<string>> layerNamesList, Func<int, int, byte, bool> callback ) + // 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." ); - bool cancel = false; // First match layers to layer names. + bool cancel = false; + List<Vector3Int> failedPixels = new List<Vector3Int>(); for (int l = 0;l < layers.Count && !cancel;l++) { - var layer = layers[l]; - + var layer = layers[l]; for (int f = 0;f < layer.VectorTileFeatures.Count && !cancel;f++) { var feature = layer.VectorTileFeatures[f]; @@ -155,15 +212,35 @@ namespace Wander bool matchingLayerFound = false; for (int a = 0;a < feature.Attributes.Count && !matchingLayerFound;a++) { - if (feature.Attributes[a].Key != attributeKeyMatch) - continue; - - string function = feature.Attributes[a].Value as string; + 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 (function.Contains( layerNamesList[layerIdx][layerNameIdx] )) + if (layer.Name.Contains( layerNamesList[layerIdx][layerNameIdx] )) { feature.SelectedLayerIdx = layerIdx; matchingLayerFound = true; @@ -175,44 +252,88 @@ namespace Wander } } + Debug.Assert( width == height, "Must be square images." ); + // 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(x, y); + Vector2 p = new Vector2(fx*x+0.5f*fx, fx*y+0.5f*fx); + bool hit = false; - for (int f = 0;f < layer.VectorTileFeatures.Count && !cancel;f++) + // 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]; if (feature.GeometryType != Tile.GeomType.Polygon) continue; - var triangles = polygonLayers[l][f].vertices; - bool hit = false; - for (int i = 0;i < triangles.Count && !cancel;i += 3) + 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++) { - hit = GeomUtil.PointIsInsideTriangle( p, triangles[i], triangles[i+1], triangles[i+2] ); + 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] ); if (hit) { - cancel = callback(x, y, (byte)feature.SelectedLayerIdx); // If SelectedLayerIdx did not match, it was set to 254. + cachedTriIdx = triIdx; + cachedVtxIdx = vIdx; + cachedPixel = (byte)feature.SelectedLayerIdx; + cancel = callback( x, y, cachedPixel ); break; } } - if (!hit) - { - callback( x, y, 255 ); // no hit - } + if (hit) break; + } + + if ( !hit ) + { + failedPixels.Add( new Vector3Int( x, y, 255 ) ); } } } } + + return failedPixels; } }