From b72ce56a47791e7fbde5c3281faf1edbb62e4d53 Mon Sep 17 00:00:00 2001
From: bartjuhhh <bart.knuiman@wur.nl>
Date: Mon, 7 Aug 2023 13:23:30 +0200
Subject: [PATCH] Now correctly handles two pixels per byte. So 4 bits per
 pixel (16 channels of which the last one is reserved for not valid).

---
 Runtime/VectorTile.cs | 118 ++++++++++++++++++++++++------------------
 1 file changed, 69 insertions(+), 49 deletions(-)

diff --git a/Runtime/VectorTile.cs b/Runtime/VectorTile.cs
index c978537..19230f3 100644
--- a/Runtime/VectorTile.cs
+++ b/Runtime/VectorTile.cs
@@ -7,33 +7,33 @@ using System.Threading.Tasks;
 using UnityEngine;
 using UnityEngine.Networking;
 
-namespace Wander
-{
+namespace Wander
+{
     internal class QuadTreeNode
     {
         internal Vector2 min, max;
         internal QuadTreeNode[] children;
         internal List<(int, int, int)> triangles; // Layer, Feature, Triangle index.
         internal int depth;
-    }
-
-    // Always multiple of 3. Each 3 form a triangle.
+    }
+
+    // Always multiple of 3. Each 3 form a triangle.
     public class TriangulatedPolygon
     {
-        public List<Vector2> vertices;  
+        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.
+            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 )
+            for (int i = 0;i < vertices.Count;i += 3)
             {
                 float minx = float.MaxValue;
                 float miny = float.MaxValue;
@@ -61,8 +61,8 @@ namespace Wander
                 denoms.Add( denominator );
             }
         }
-    }
-
+    }
+
     public class VectorTile
     {
         public bool DownloadStarted => started;
@@ -84,7 +84,7 @@ namespace Wander
 
         public void StartDownload()
         {
-            UnityEngine.Debug.Assert(!started);
+            UnityEngine.Debug.Assert( !started );
             request.SendWebRequest();
             started = true;
         }
@@ -122,17 +122,17 @@ namespace Wander
         // Number of failed polys to triangulate are returned.
         public int Triangulate()
         {
-            if (triangulated )
+            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++ )
+            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++)
+                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)
@@ -160,7 +160,7 @@ namespace Wander
                         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++)
+                        for (int k = 0;k < indices.Count;k++)
                         {
                             double x = vertices[indices[k]*2];
                             double y = vertices[indices[k]*2+1];
@@ -273,7 +273,7 @@ namespace Wander
                 }
                 UnityEngine.Debug.Log( "Optimize for raytracing took " + sw.ElapsedMilliseconds );
             }
-            catch ( Exception e )
+            catch (Exception e)
             {
                 UnityEngine.Debug.LogException( e );
             }
@@ -304,15 +304,15 @@ namespace Wander
             layersIdentified = true;
         }
 
-        // Generates a raster for each position (pixel) (256 channels) where each value represents a single channel.
-        // If no polygon was hit, 255 is encoded.
-        // If polygon was hit, but no feature was matched, 254 is encoded.
+        // Generates a raster for each position (pixel). 2 pixels are pushed in 1 (each 16 bit).
+        // If no polygon was hit, 15 is encoded (reserved).
+        // If polygon was hit, but no feature was matched, also 15 is encoded.
         // Returns a list of failed to match pixels. This can be due to geometry not exactly matching or a layer not being found.
         // Also outputs a remap of layer indices. For instance for a specific tile, only layerIdx 1, 6 and 3 are used.
         // Then it applies 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 Dictionary<byte, byte> remappedLayerIndices,
             out List<Vector3Int> failedPixels,
             ref bool cancelToken )
         {
@@ -323,7 +323,7 @@ namespace Wander
             UnityEngine.Debug.Assert( layersIdentified, "Identify layers first." );
             UnityEngine.Debug.Assert( resolution > 1, "Must be at least 2." );
 
-            byte [] texture = new byte[resolution*resolution];
+            byte [] texture = new byte[resolution*resolution/2];
             failedPixels = new List<Vector3Int>();
             remappedLayerIndices = new Dictionary<byte, byte>();
             float oneOverRes = 1.0f / resolution;
@@ -336,7 +336,7 @@ namespace Wander
             // For each pixel.
             for (int y = 0;y < resolution && !cancelToken;y++)
             {
-                for (int x = 0;x < resolution && !cancelToken ;x++)
+                for (int x = 0;x < resolution && !cancelToken;x++)
                 {
                     bool hit  = false;
                     Vector2 p = new Vector2(fx*x+0.5f, fx*y+0.5f);
@@ -351,16 +351,16 @@ namespace Wander
                             continue;
                         }
 
-                        if ( node.children != null )
+                        if (node.children != null)
                         {
-                            for ( int c = 0; c < node.children.Length; c++ )
+                            for (int c = 0;c < node.children.Length;c++)
                             {
                                 stack.Add( node.children[c] );
                             }
                         }
                         else if (node.triangles != null) // Is leaf
                         {
-                            for( int t = 0; t < node.triangles.Count; t++ )
+                            for (int t = 0;t < node.triangles.Count;t++)
                             {
                                 int ti = (cachedTriIdx + t)  % node.triangles.Count; // Continue searching where we left off 
                                 int l  = node.triangles[ti].Item1;
@@ -375,9 +375,13 @@ namespace Wander
                                 hit = GeomUtil.PointIsInsideTriangle2( p, vertices[t2*3], vertices[t2*3+1], vertices[t2*3+2], denoms[t2] );
                                 if (hit)
                                 {
-                                    cachedTriIdx = ti; 
+                                    cachedTriIdx = ti;
                                     var layerIdx = (byte)layers[l].VectorTileFeatures[f].SelectedLayerIdx;
-                                    texture[(resolution - y -1)*resolution+x] = layerIdx;
+                                    //var addr2    = y*resolution+x;
+                                    var addr2    = (resolution - y -1)*resolution+x;
+                                    if (layerIdx > 15) layerIdx = 15; // max layers 15 (15 is reserved, nothing was hit). 
+                                    var shift = (addr2&1) << 2;
+                                    texture[addr2>>1] |= (byte)(layerIdx << shift);
                                     break;
                                 }
                             }
@@ -390,8 +394,10 @@ namespace Wander
                     stack.Clear();
                     if (!hit)
                     {
-                        texture[(resolution - y -1)*resolution+x] = 255;
-                        failedPixels.Add( new Vector3Int( x, y, 255 ) );
+                        var addr2 = (resolution - y -1)*resolution+x;
+                        var shift = (addr2&1) << 2;
+                        texture[addr2>>1] |= (byte)(15 << shift);
+                        failedPixels.Add( new Vector3Int( x, y, 15 ) );
                     }
                     else
                     {
@@ -402,47 +408,61 @@ namespace Wander
 
             // 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.
-            byte cachedPixel = 255;
+            byte cachedPixel = 15;
             int  remappedIndexCounter = -1;
-            byte remappedPixel = 255;
-            int  addr = 0;
+            byte remappedPixel = 15;
+            uint addr  = 0;
+            byte pixel = 0;
             for (int y = 0;y < resolution && !cancelToken;y++)
             {
-                for (int x = 0;x < resolution;x++)
+                for (int x = 0;x < resolution && !cancelToken;x++)
                 {
-                    byte pixel = texture[addr];
-                    if (pixel < 254)
+                    if ((x&1) == 0)
+                        pixel = texture[addr>>1];
+                    byte shift  = (byte)((addr&1));
+                    byte pixel2 = (byte)((pixel >> (shift<<2)) & 15);
+                    if (pixel2 != 15)
                     {
-                        if (pixel != cachedPixel)
+                        if (pixel2 != cachedPixel)
                         {
-                            if (!remappedLayerIndices.TryGetValue( pixel, out remappedPixel ))
+                            if (!remappedLayerIndices.TryGetValue( pixel2, out remappedPixel ))
                             {
-                                if (remappedIndexCounter < 253) // 254 is reserved for no layer match, 255 is ray hit with triangle.
+                                if (remappedIndexCounter < 13) // 15 is reserved for not found, so 0-14 are valid.
                                 {
                                     remappedIndexCounter++;
                                     remappedPixel = (byte)remappedIndexCounter;
-                                    remappedLayerIndices.Add( pixel, remappedPixel );
+                                    remappedLayerIndices.Add( pixel2, remappedPixel );
                                 }
-                                else remappedPixel = 0;
+                                else remappedPixel = 15; // Reserved for invalid.
                             }
-                            cachedPixel = pixel;
+                            cachedPixel = pixel2;
                         }
-                        texture[addr] = remappedPixel;
-                    } // leave unfound or unhit pixels on their original value.
+                        UnityEngine.Debug.Assert( remappedPixel < 15 );
+                    }
+                    else
+                    {
+                        remappedPixel = 15;
+                        cachedPixel = 15;
+                    }
+                    // Data in texture is not zero anymore, so must check if is even, then just set, otherwise OR-add it.
+                    if (shift==0)
+                        texture[addr>>1] = remappedPixel;
+                    else
+                        texture[addr>>1] |= (byte)(remappedPixel << 4);
                     addr++;
                 }
             }
 
-            UnityEngine.Debug.Assert(remappedIndexCounter <= 254, "Exceeded layer count." );
+            UnityEngine.Debug.Assert( remappedIndexCounter <= 15, "Exceeded layer count." ); // Invalid pixel is not added to map, so max must be 15, not 16.
 
             UnityEngine.Debug.Log( "Render to texture took " + sw.ElapsedMilliseconds );
             return texture;
         }
-    }
-
-    public static class VectorTileLoader
+    }
+
+    public static class VectorTileLoader
     {
-        public static VectorTile LoadFromUrl( string url, bool autoStart=true )
+        public static VectorTile LoadFromUrl( string url, bool autoStart = true )
         {
             VectorTile tile = new VectorTile();
             tile.request = UnityWebRequest.Get( url );
@@ -452,5 +472,5 @@ namespace Wander
             }
             return tile;
         }
-    }
+    }
 }
\ No newline at end of file
-- 
GitLab