From 26192e5c18ebc05d6bf580bde746b8a83603211f Mon Sep 17 00:00:00 2001
From: bart <bart.knuiman@wur.nl>
Date: Thu, 2 Mar 2023 16:58:56 +0100
Subject: [PATCH] big wip

---
 Runtime/VectorTile.cs | 167 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 144 insertions(+), 23 deletions(-)

diff --git a/Runtime/VectorTile.cs b/Runtime/VectorTile.cs
index c1f6031..b28dae7 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;
         }
     }
 
-- 
GitLab