/// <summary> /// Includes a triangle into the list of triangles that intersect the extent of a sub grid /// </summary> /// <param name="tree"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="triIndex"></param> private void IncludeTriangleInSubGridTreeIndex(NonOptimisedSpatialIndexSubGridTree tree, int x, int y, int triIndex) { // Get sub grid from tree, creating the path and leaf if necessary var leaf = tree.ConstructPathToCell(x, y, SubGridPathConstructionType.CreateLeaf) as NonOptimisedSpatialIndexSubGridLeaf; leaf.GetSubGridCellIndex(x, y, out byte SubGridX, out byte SubGridY); // Get the list of triangles for the given cell List <int> triangles = leaf.Items[SubGridX, SubGridY]; // If there are none already create the list and assign it to the cell if (triangles == null) { triangles = new List <int>(); leaf.Items[SubGridX, SubGridY] = triangles; triangles.Add(triIndex); } else { // Add the triangle to the cell, even if it is already there (duplicates will be taken care of later) // Note: Duplicates tend to occur one after the other, so do a trivial last triangle duplicate check here if (triangles[triangles.Count - 1] != triIndex) { triangles.Add(triIndex); } } }
/// <summary> /// Build a spatial index for the triangles in the TIN surface by assigning each triangle to every sub grid it intersects with /// </summary> /// <returns></returns> public bool ConstructSpatialIndex() { // Read through all the triangles in the model and, for each triangle, // determine which sub grids in the index intersect it and add it to those sub grids try { // Create the optimized sub grid tree spatial index that minimizes the number of allocations in the final result. SpatialIndexOptimised = new OptimisedSpatialIndexSubGridTree(SubGridTreeConsts.SubGridTreeLevels - 1, SubGridTreeConsts.SubGridTreeDimension * CellSize); var FSpatialIndex = new NonOptimisedSpatialIndexSubGridTree(SubGridTreeConsts.SubGridTreeLevels - 1, SubGridTreeConsts.SubGridTreeDimension * CellSize); Log.LogInformation($"In: Constructing subgrid index for design containing {TTM.Triangles.Items.Length} triangles"); try { var cellScanner = new TriangleCellScanner(TTM); // Construct a sub grid tree containing list of triangles that intersect each on-the-ground sub grid int triangleCount = TTM.Triangles.Items.Length; for (int triIndex = 0; triIndex < triangleCount; triIndex++) { cellScanner.ScanCellsOverTriangle(FSpatialIndex, triIndex, (tree, x, y) => false, (tree, x, y, t) => IncludeTriangleInSubGridTreeIndex(tree as NonOptimisedSpatialIndexSubGridTree, x, y, t), cellScanner.AddTrianglePieceToSubgridIndex); } if (EnableDuplicateRemoval) { ///////////////////////////////////////////////// // Remove duplicate triangles added to the lists ///////////////////////////////////////////////// BitArray uniques = new BitArray(TriangleItems.Length); long TotalDuplicates = 0; FSpatialIndex.ScanAllSubGrids(leaf => { // Iterate across all cells in each (level 5) leaf sub grid. Each cell represents // a sub grid in the level 6 sub grid representing cells sampled across the surface at the // core cell size for the project SubGridUtilities.SubGridDimensionalIterator((x, y) => { List <int> triList = FSpatialIndex[leaf.OriginX + x, leaf.OriginY + y]; if (triList == null) { return; } uniques.SetAll(false); int triListCount = triList.Count; int uniqueCount = 0; for (int i = 0; i < triListCount; i++) { int triIndex = triList[i]; if (!uniques[triIndex]) { triList[uniqueCount++] = triIndex; uniques[triIndex] = true; } else { TotalDuplicates++; } } if (uniqueCount < triListCount) { triList.RemoveRange(uniqueCount, triListCount - uniqueCount); } }); return(true); }); Console.WriteLine($"Total duplicates encountered: {TotalDuplicates}"); } // Transform this sub grid tree into one where each on-the-ground sub grid is represented by an index and a number of triangles present in a // a single list of triangles. // Count the number of triangle references present in the tree var numTriangleReferences = 0; FSpatialIndex.ForEach(x => { numTriangleReferences += x?.Count ?? 0; return(true); }); // Create the single array spatialIndexOptimisedTriangles = new int[numTriangleReferences]; ///////////////////////////////////////////////// // Iterate across all leaf sub grids // Copy all triangle lists into it, and add the appropriate reference blocks in the new tree. ///////////////////////////////////////////////// var copiedCount = 0; var arrayReference = new TriangleArrayReference { Count = 0, TriangleArrayIndex = 0 }; var cellWorldExtent = new BoundingWorldExtent3D(); FSpatialIndex.ScanAllSubGrids(leaf => { // Iterate across all cells in each (level 5) leaf sub grid. Each cell represents // a sub grid in the level 6 sub grid representing cells sampled across the surface at the // core cell size for the project SubGridUtilities.SubGridDimensionalIterator((x, y) => { var CellX = leaf.OriginX + x; var CellY = leaf.OriginY + y; var triList = FSpatialIndex[CellX, CellY]; if (triList == null) { return; } ///////////////////////////////////////////////////////////////////////////////////////////////// // Start: Determine the triangles that definitely cannot cover one or more cells in each sub grid var leafCellSize = SpatialIndexOptimised.CellSize / SubGridTreeConsts.SubGridTreeDimension; var halfLeafCellSize = leafCellSize / 2; short trianglesCopiedToLeaf = 0; SpatialIndexOptimised.GetCellExtents(CellX, CellY, ref cellWorldExtent); // Compute the bounding structs for the triangles in this sub grid and remove any triangles whose // bounding struct is null (ie: no cell centers are covered by its bounding box). for (var i = 0; i < triList.Count; i++) { /* *** DO NOT REMOVE THIS CODE *** * It is commented out for now as it is an optimization for grid queries but it creates problems for profile queries * as in triangles that should be included for design profiling are excluded because they have no cell center in them. * * // Get the triangle... * var tri = TriangleItems[triList[i]]; * * // Get the real world bounding box for the triangle * * var Vertex0 = VertexItems[tri.Vertex0]; * var Vertex1 = VertexItems[tri.Vertex1]; * var Vertex2 = VertexItems[tri.Vertex2]; * * var TriangleWorldExtent_MinX = Math.Min(Vertex0.X, Math.Min(Vertex1.X, Vertex2.X)); * var TriangleWorldExtent_MinY = Math.Min(Vertex0.Y, Math.Min(Vertex1.Y, Vertex2.Y)); * var TriangleWorldExtent_MaxX = Math.Max(Vertex0.X, Math.Max(Vertex1.X, Vertex2.X)); * var TriangleWorldExtent_MaxY = Math.Max(Vertex0.Y, Math.Max(Vertex1.Y, Vertex2.Y)); * * // Calculate cell coordinates relative to the origin of the sub grid * var minCellX = (int)Math.Floor((TriangleWorldExtent_MinX - cellWorldExtent.MinX) / leafCellSize); * var minCellY = (int)Math.Floor((TriangleWorldExtent_MinY - cellWorldExtent.MinY) / leafCellSize); * var maxCellX = (int)Math.Floor((TriangleWorldExtent_MaxX - cellWorldExtent.MinX) / leafCellSize); * var maxCellY = (int)Math.Floor((TriangleWorldExtent_MaxY - cellWorldExtent.MinY) / leafCellSize); * * // Check if the result bounds are valid - if not, there is no point including it * if (minCellX > maxCellX || minCellY > maxCellY) * { * // There are no cell probe positions that can lie in this triangle, ignore it * continue; * } * * // Check if there is an intersection between the triangle cell bounds and the leaf cell bounds * if (minCellX > SubGridTreeConsts.SubGridTreeDimensionMinus1 || minCellY > SubGridTreeConsts.SubGridTreeDimensionMinus1 || maxCellX < 0 || maxCellY < 0) * { * // There is no bounding box intersection, ignore it * continue; * } * * // Transform the cell bounds by clamping them to the bounds of this sub grid * minCellX = minCellX <= 0 ? 0 : minCellX >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : minCellX; * minCellY = minCellY <= 0 ? 0 : minCellY >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : minCellY; * maxCellX = maxCellX <= 0 ? 0 : maxCellX >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : maxCellX; * maxCellY = maxCellY <= 0 ? 0 : maxCellY >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : maxCellY; * * // Check all the cells in the sub grid covered by this bounding box to check if at least one cell will actively probe this triangle * * var found = false; * var _x = cellWorldExtent.MinX + minCellX * leafCellSize + halfLeafCellSize; * * for (var cellX = minCellX; cellX <= maxCellX; cellX++) * { * var _y = cellWorldExtent.MinY + minCellY * leafCellSize + halfLeafCellSize; * for (var cellY = minCellY; cellY <= maxCellY; cellY++) * { * if (XYZ.GetTriangleHeight(Vertex0, Vertex1, Vertex2, _x, _y) != Common.Consts.NullDouble) * { * found = true; * break; * } * * _y += leafCellSize; * } * * if (found) * break; * * _x += leafCellSize; * } * * if (!found) * { * // No cell in the sub grid intersects with the triangle - ignore it * continue; * } */ // This triangle is a candidate for being probed, copy it into the array trianglesCopiedToLeaf++; spatialIndexOptimisedTriangles[copiedCount++] = triList[i]; } // End: Determine the triangles that definitely cannot cover one or more cells in each sub grid /////////////////////////////////////////////////////////////////////////////////////////////// arrayReference.Count = trianglesCopiedToLeaf; // Add new entry for optimized tree SpatialIndexOptimised[leaf.OriginX + x, leaf.OriginY + y] = arrayReference; // Set copied count into the array reference for the next leaf so it captures the starting location in the overall array for it arrayReference.TriangleArrayIndex = copiedCount; }); return(true); }); Console.WriteLine($"Number of vertices in model {VertexItems.Length}"); Console.WriteLine($"Number of triangles in model {TriangleItems.Length}"); Console.WriteLine($"Number of original triangle references in index: {spatialIndexOptimisedTriangles.Length}"); Console.WriteLine($"Number of triangle references removed as un-probable: {spatialIndexOptimisedTriangles.Length - copiedCount}"); // Finally, resize the master triangle reference array to remove the unused entries due to un-probable triangles Array.Resize(ref spatialIndexOptimisedTriangles, copiedCount); Console.WriteLine($"Final number of triangle references in index: {spatialIndexOptimisedTriangles.Length}"); } finally { // Emit some logging indicating likely efficiency of index. long sumTriangleReferences = 0; long sumTriangleLists = 0; long sumLeafSubGrids = 0; long sumNodeSubGrids = 0; FSpatialIndex.ScanAllSubGrids(l => { sumLeafSubGrids++; return(true); }, n => { sumNodeSubGrids++; return(SubGridProcessNodeSubGridResult.OK); }); FSpatialIndex.ForEach(x => { sumTriangleLists++; sumTriangleReferences += x?.Count ?? 0; return(true); }); Log.LogInformation( $"Constructed sub grid index for design containing {TTM.Triangles.Items.Length} triangles, using {sumLeafSubGrids} leaf and {sumNodeSubGrids} node subgrids, {sumTriangleLists} triangle lists and {sumTriangleReferences} triangle references"); } return(true); } catch (Exception e) { Log.LogError(e, "Exception in ConstructSpatialIndex"); return(false); } }