/// <summary> /// Interpolates a single spot height from the design, using the optimized spatial index /// </summary> public override bool InterpolateHeight(ref int Hint, double X, double Y, double Offset, out double Z) { if (Hint != -1) { Z = GetHeight2(ref triangleItems[Hint], X, Y); if (Z != Common.Consts.NullDouble) { Z += Offset; return(true); } Hint = -1; } // Search in the sub grid triangle list for this sub grid from the spatial index SpatialIndexOptimised.CalculateIndexOfCellContainingPosition(X, Y, out int CellX, out int CellY); TriangleArrayReference arrayReference = SpatialIndexOptimised[CellX, CellY]; if (arrayReference.Count == 0) { // There are no triangles that can satisfy the query Z = Common.Consts.NullReal; return(false); } // Search the triangles in the leaf to locate the one to interpolate height from int limit = arrayReference.TriangleArrayIndex + arrayReference.Count; for (int i = arrayReference.TriangleArrayIndex; i < limit; i++) { int triIndex = SpatialIndexOptimisedTriangles[i]; Z = GetHeight2(ref triangleItems[triIndex], X, Y); if (Z != Common.Consts.NullReal) { Hint = triIndex; Z += Offset; return(true); } } Z = Common.Consts.NullReal; return(false); }
/// <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); } }
/// <summary> /// Interpolates heights from the design for all the cells in a sub grid /// </summary> public override bool InterpolateHeights(float[,] Patch, double OriginX, double OriginY, double CellSize, double Offset) { bool hasValues = false; TriangleSubGridCellExtents triangleCellExtent = new TriangleSubGridCellExtents(); double HalfCellSize = CellSize / 2; double halfCellSizeMinusEpsilon = HalfCellSize - 0.0001; double OriginXPlusHalfCellSize = OriginX + HalfCellSize; double OriginYPlusHalfCellSize = OriginY + HalfCellSize; // Search in the sub grid triangle list for this sub grid from the spatial index // All cells in this sub grid will be contained in the same triangle list from the spatial index SpatialIndexOptimised.CalculateIndexOfCellContainingPosition(OriginXPlusHalfCellSize, OriginYPlusHalfCellSize, out int CellX, out int CellY); TriangleArrayReference arrayReference = SpatialIndexOptimised[CellX, CellY]; int triangleCount = arrayReference.Count; if (triangleCount > 0) // There are triangles that can satisfy the query (leaf cell is non-empty) { double leafCellSize = SpatialIndexOptimised.CellSize / SubGridTreeConsts.SubGridTreeDimension; BoundingWorldExtent3D cellWorldExtent = SpatialIndexOptimised.GetCellExtents(CellX, CellY); // Create the array of triangle cell extents in the sub grid TriangleSubGridCellExtents[] triangleCellExtents = new TriangleSubGridCellExtents[triangleCount]; // Compute the bounding structs for the triangles in this sub grid for (int i = 0; i < triangleCount; i++) { // Get the triangle... Triangle tri = triangleItems[SpatialIndexOptimisedTriangles[arrayReference.TriangleArrayIndex + i]]; // Get the real world bounding box for the triangle // Note: As sampling occurs at cell centers shrink the effective bounding box for each triangle used // for calculating the cell bounding box by half a cell size (less a small Epsilon) so the cell bounding box // captures cell centers falling in the triangle world coordinate bounding box XYZ TriVertex0 = vertexItems[tri.Vertex0]; XYZ TriVertex1 = vertexItems[tri.Vertex1]; XYZ TriVertex2 = vertexItems[tri.Vertex2]; double TriangleWorldExtent_MinX = Math.Min(TriVertex0.X, Math.Min(TriVertex1.X, TriVertex2.X)) + halfCellSizeMinusEpsilon; double TriangleWorldExtent_MinY = Math.Min(TriVertex0.Y, Math.Min(TriVertex1.Y, TriVertex2.Y)) + halfCellSizeMinusEpsilon; double TriangleWorldExtent_MaxX = Math.Max(TriVertex0.X, Math.Max(TriVertex1.X, TriVertex2.X)) - halfCellSizeMinusEpsilon; double TriangleWorldExtent_MaxY = Math.Max(TriVertex0.Y, Math.Max(TriVertex1.Y, TriVertex2.Y)) - halfCellSizeMinusEpsilon; int minCellX = (int)Math.Floor((TriangleWorldExtent_MinX - cellWorldExtent.MinX) / leafCellSize); int minCellY = (int)Math.Floor((TriangleWorldExtent_MinY - cellWorldExtent.MinY) / leafCellSize); int maxCellX = (int)Math.Floor((TriangleWorldExtent_MaxX - cellWorldExtent.MinX) / leafCellSize); int maxCellY = (int)Math.Floor((TriangleWorldExtent_MaxY - cellWorldExtent.MinY) / leafCellSize); triangleCellExtent.MinX = (byte)(minCellX <= 0 ? 0 : minCellX >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : minCellX); triangleCellExtent.MinY = (byte)(minCellY <= 0 ? 0 : minCellY >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : minCellY); triangleCellExtent.MaxX = (byte)(maxCellX <= 0 ? 0 : maxCellX >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : maxCellX); triangleCellExtent.MaxY = (byte)(maxCellY <= 0 ? 0 : maxCellY >= SubGridTreeConsts.SubGridTreeDimensionMinus1 ? SubGridTreeConsts.SubGridTreeDimensionMinus1 : maxCellY); triangleCellExtents[i] = triangleCellExtent; } // Initialise patch to null height values Array.Copy(kNullPatch, 0, Patch, 0, SubGridTreeConsts.SubGridTreeCellsPerSubGrid); // Iterate over all the cells in the grid using the triangle sub grid cell extents to filter // triangles in the leaf that will be considered for point-in-triangle & elevation checks. double X = OriginXPlusHalfCellSize; for (int x = 0; x < SubGridTreeConsts.SubGridTreeDimension; x++) { double Y = OriginYPlusHalfCellSize; for (int y = 0; y < SubGridTreeConsts.SubGridTreeDimension; y++) { // Search the triangles in the leaf to locate the one to interpolate height from for (int i = 0; i < triangleCount; i++) { if (x < triangleCellExtents[i].MinX || x > triangleCellExtents[i].MaxX || y <triangleCellExtents[i].MinY || y> triangleCellExtents[i].MaxY) { continue; // No intersection, move to next triangle } var Z = GetHeight2(ref triangleItems[SpatialIndexOptimisedTriangles[arrayReference.TriangleArrayIndex + i]], X, Y); if (Z != Common.Consts.NullReal) { hasValues = true; Patch[x, y] = (float)(Z + Offset); break; // No more triangles need to be examined for this cell } } Y += CellSize; } X += CellSize; } } return(hasValues); }