diff --git a/Runtime/VectorTile.cs b/Runtime/VectorTile.cs index 1b9f62f411250c853052fbf2dc0784127ff862ed..a4bef7ccfa44050438b6e2cca239e57b0ef721db 100644 --- a/Runtime/VectorTile.cs +++ b/Runtime/VectorTile.cs @@ -1,6 +1,8 @@ +using Codice.CM.Client.Differences; using Mapbox.Vector.Tile; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,6 +13,14 @@ using static Wander.Easing; 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. public class TriangulatedPolygon { @@ -30,7 +40,7 @@ namespace Wander for ( int i = 0; i < vertices.Count; i += 3 ) { float minx = float.MaxValue; - float miny = 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; @@ -72,12 +82,13 @@ namespace Wander private Task parseTileTask; private List<VectorTileLayer> layers; private List<List<TriangulatedPolygon>> polygonLayers; + private QuadTreeNode root; internal UnityWebRequest request; public void StartDownload() { - Debug.Assert(!started); + UnityEngine.Debug.Assert(!started); request.SendWebRequest(); started = true; } @@ -175,7 +186,7 @@ namespace Wander // } //} polygons[polygons.Count-1] = poly; - Debug.Assert( poly.vertices.Count % 3 == 0 ); + UnityEngine.Debug.Assert( poly.vertices.Count % 3 == 0 ); } catch (Exception) { @@ -190,9 +201,92 @@ namespace Wander public void OptimizeForPointIsInsideTriangle() { + Stopwatch sw = new Stopwatch(); + sw.Restart(); for (int i = 0;i < polygonLayers.Count;i++) + { for (int j = 0;j < polygonLayers[i].Count;j++) + { polygonLayers[i][j].OptimizeForIsPointInTriangle(); + } + } + + // Generate quadtree + List<QuadTreeNode> stack = new List<QuadTreeNode>(); + root = new QuadTreeNode(); + root.triangles = new List<(int, int, int)>(); + root.min = new Vector2( 0, 0 ); + root.max = new Vector2( 4096, 4096 ); // TODO 4096 should be max of all layer extents. + for (int l = 0;l < polygonLayers.Count;l++) + { + var layer = layers[l]; + for (int f = 0;f < layer.VectorTileFeatures.Count;f++) + { + var feature = layer.VectorTileFeatures[f]; + + if (feature.GeometryType != Tile.GeomType.Polygon) + continue; + + var polygons = polygonLayers[l][f]; + if (polygons.vertices.Count == 0) + continue; + + if (feature.SelectedLayerIdx == 254) + continue; + + if (feature.RelativeHeight > 0) + continue; + + var poly = polygonLayers[l][f]; + for (int t = 0;t < poly.vertices.Count/3;t++) + { + root.triangles.Add( (l, f, t) ); + } + } + } + stack.Add( root ); + while (stack.Count > 0) + { + var node = stack[0]; + stack.RemoveAt( 0 ); + if (node.triangles.Count < 4) + continue; + if (node.depth > 7) + continue; + node.children = new QuadTreeNode[4]; + var hs = (node.max - node.min) / 2; + Vector2 [] mins = new [] { + node.min, + new Vector2(node.min.x+hs.x, node.min.y), + new Vector2(node.min.x, node.min.y+hs.y), + new Vector2(node.min.x+hs.x, node.min.y+hs.y) + }; + for (int i = 0;i < 4;i++) + { + var n2 = new QuadTreeNode(); + n2.depth = node.depth+1; + n2.min = mins[i]; + n2.max = n2.min + hs; + n2.triangles = new List<(int, int, int)>(); + for ( int t = 0; t < node.triangles.Count; t++ ) + { + int l = node.triangles[t].Item1; + int f = node.triangles[t].Item2; + int t2 = node.triangles[t].Item3; + var min2 = polygonLayers[l][f].mins[t2]; + var max2 = polygonLayers[l][f].maxs[t2]; + if ( (min2.x > n2.max.x) || (min2.y > n2.max.y) || (max2.x < n2.min.x) || (max2.y < n2.min.y) ) + { + continue; + } + n2.triangles.Add( (l, f, t2) ); + } + node.children[i] = n2; + stack.Add( n2 ); + } + node.triangles = null; + } + UnityEngine.Debug.Log( "Optimize for raytracing took " + sw.ElapsedMilliseconds ); } // Identify feature by specifying a (unique) index based on some criteria. This can very per @@ -230,103 +324,101 @@ namespace Wander int resolution, out Dictionary<byte, byte> remappedLayerIndices, out List<Vector3Int> failedPixels, - bool? cancelToken ) + ref bool cancelToken ) { - Debug.Assert( triangulated, "First call Triangulate." ); - Debug.Assert( layersIdentified, "Identify layers first." ); - Debug.Assert( resolution > 1, "Must be at least 2." ); + Stopwatch sw = new Stopwatch(); + sw.Restart(); + + UnityEngine.Debug.Assert( triangulated, "First call Triangulate." ); + UnityEngine.Debug.Assert( layersIdentified, "Identify layers first." ); + UnityEngine.Debug.Assert( resolution > 1, "Must be at least 2." ); byte [] texture = new byte[resolution*resolution]; failedPixels = new List<Vector3Int>(); remappedLayerIndices = new Dictionary<byte, byte>(); float oneOverRes = 1.0f / resolution; + int cachedTriIdx = 0; + float fx = 4096 * oneOverRes; // TODO 4096 may be different in other implementations. - int cachedTriIdx = 0; - int cachedLyrIdx = 0; - int cachedFtrIdx = 0; + QuadTreeNode node = null; + List<QuadTreeNode> stack = new List<QuadTreeNode>(); // For each pixel. for (int y = 0;y < resolution ;y++) { - for (int x = 0;x < resolution && !cancelToken.Value ;x++) + for (int x = 0;x < resolution && !cancelToken ;x++) { bool hit = false; + Vector2 p = new Vector2(fx*x+0.5f, fx*y+0.5f); - // For each layer, check triangle intersections. - for (int l = 0;l < layers.Count && !cancelToken.Value;l++) + stack.Add( root ); + while (stack.Count > 0) { - var lIdx = (l+cachedLyrIdx) % layers.Count; // Try last layer/feature/triangle first. - var layer = layers[lIdx]; - float fx = layer.Extent * oneOverRes; - Vector2 p = new Vector2(fx*x+0.5f, fx*y+0.5f); - - for (int f = 0;f < layer.VectorTileFeatures.Count;f++) + node = stack[0]; + stack.RemoveAt( 0 ); + if (p.x < node.min.x || p.y < node.min.y || p.x > node.max.x || p.y > node.max.y) { - var ftrIdx = (f + cachedFtrIdx) % layer.VectorTileFeatures.Count; - var feature = layer.VectorTileFeatures[ftrIdx]; - - if (feature.GeometryType != Tile.GeomType.Polygon) - continue; - - var polygons = polygonLayers[lIdx][ftrIdx]; - if (polygons.vertices.Count == 0) - continue; - - if (feature.SelectedLayerIdx == 254) - continue; - - if (feature.RelativeHeight > 0) - continue; + continue; + } - var poly = polygonLayers[lIdx][ftrIdx]; - var mins = poly.mins; - var maxs = poly.maxs; - var denoms = poly.denoms; - var vertices = poly.vertices; - var triCount = vertices.Count/3; - for (int t = 0; t < triCount;t++) + if ( node.children != null ) + { + 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++ ) { - int triIdx = (t+cachedTriIdx) % triCount; - 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[triIdx*3], vertices[triIdx*3+1], vertices[triIdx*3+2], denoms[triIdx] ); + int ti = (cachedTriIdx + t) % node.triangles.Count; + int l = node.triangles[ti].Item1; + int f = node.triangles[ti].Item2; + int t2 = node.triangles[ti].Item3; + var min2 = polygonLayers[l][f].mins[t2]; + var max2 = polygonLayers[l][f].maxs[t2]; + if (p.x < min2.x || p.x > max2.x) continue; + if (p.y < min2.y || p.y > max2.y) continue; + var vertices = polygonLayers[l][f].vertices; + var denoms = polygonLayers[l][f].denoms; + hit = GeomUtil.PointIsInsideTriangle2( p, vertices[t2*3], vertices[t2*3+1], vertices[t2*3+2], denoms[t2] ); if (hit) { - cachedTriIdx = triIdx; - cachedLyrIdx = lIdx; - cachedFtrIdx = ftrIdx; - texture[(resolution - y -1)*resolution+x] = (byte)feature.SelectedLayerIdx; + // TODO caching the TriIdx doees not work, I have no clue why, it should only function + // as a hint, as where to start. But using this hint results in massive number of triangles going wrong... + // cachedTriIdx = ti; + var layerIdx = (byte)layers[l].VectorTileFeatures[f].SelectedLayerIdx; + texture[(resolution - y -1)*resolution+x] = layerIdx; break; } } - - if (hit) break; - - cachedTriIdx += 1; - if(cachedTriIdx == triCount) cachedTriIdx = 0; } if (hit) break; - cachedFtrIdx = 0; - } + } // end while + stack.Clear(); if (!hit) { - cachedLyrIdx = 0; texture[(resolution - y -1)*resolution+x] = 255; failedPixels.Add( new Vector3Int( x, y, 255 ) ); } + else + { + stack.Add( node ); // Add the node that was hit as the next pixel may likely hit this poly again. + } } } // 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; - int remappedIndexCounter = -1; + int remappedIndexCounter = -1; byte remappedPixel = 255; - int addr = 0; - for (int y = 0;y < resolution && !cancelToken.Value;y++) + int addr = 0; + for (int y = 0;y < resolution && !cancelToken;y++) { for (int x = 0;x < resolution;x++) { @@ -353,7 +445,9 @@ namespace Wander } } - Debug.Assert(remappedIndexCounter <= 254, "Exceeded layer count." ); + UnityEngine.Debug.Assert(remappedIndexCounter <= 254, "Exceeded layer count." ); + + UnityEngine.Debug.Log( "Render to texture took " + sw.ElapsedMilliseconds ); return texture; } }