void make_grid_dense(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f scalars) { scalars.resize(ni, nj, nk); bool abort = false; int count = 0; gParallel.ForEach(scalars.Indices(), (ijk) => { Interlocked.Increment(ref count); if (count % 100 == 0) { abort = CancelF(); } if (abort) { return; } var gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); scalars[ijk] = (float)ScalarF(gx); }); } // end make_level_set_3
void make_grid_dense(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f winding) { winding.resize(ni, nj, nk); MeshSpatial.WindingNumber(Vector3d.Zero); bool abort = false; int count = 0; gParallel.ForEach(winding.Indices(), (ijk) => { Interlocked.Increment(ref count); if (count % 100 == 0) { abort = CancelF(); } if (abort) { return; } var gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); winding[ijk] = (float)MeshSpatial.WindingNumber(gx); }); } // end make_level_set_3
void make_grid(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f scalars) { scalars.resize(ni, nj, nk); scalars.assign(float.MaxValue); // sentinel if (DebugPrint) { System.Console.WriteLine("start"); } // Ok, because the whole idea is that the surface might have holes, we are going to // compute values along known triangles and then propagate the computed region outwards // until any iso-sign-change is surrounded. // To seed propagation, we compute unsigned SDF and then compute values for any voxels // containing surface (ie w/ distance smaller than cellsize) // compute unsigned SDF var sdf = new MeshSignedDistanceGrid(Mesh, CellSize) { ComputeSigns = false }; sdf.CancelF = this.CancelF; sdf.Compute(); if (CancelF()) { return; } DenseGrid3f distances = sdf.Grid; if (WantMeshSDFGrid) { mesh_sdf = sdf; } if (DebugPrint) { System.Console.WriteLine("done initial sdf"); } // compute values at surface voxels double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (jk) => { if (CancelF()) { return; } for (int i = 0; i < ni; ++i) { var ijk = new Vector3i(i, jk.y, jk.z); float dist = distances[ijk]; // this could be tighter? but I don't think it matters... if (dist < CellSize) { var gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); scalars[ijk] = (float)ScalarF(gx); } } }); if (CancelF()) { return; } if (DebugPrint) { System.Console.WriteLine("done narrow-band"); } // Now propagate outwards from computed voxels. // Current procedure is to check 26-neighbours around each 'front' voxel, // and if there are any sign changes, that neighbour is added to front. // Front is initialized w/ all voxels we computed above AxisAlignedBox3i bounds = scalars.Bounds; bounds.Max -= Vector3i.One; // since we will be computing new values as necessary, we cannot use // grid to track whether a voxel is 'new' or not. // So, using 3D bitmap intead - is updated at end of each pass. var bits = new Bitmap3(new Vector3i(ni, nj, nk)); var cur_front = new List <Vector3i>(); foreach (Vector3i ijk in scalars.Indices()) { if (scalars[ijk] != float.MaxValue) { cur_front.Add(ijk); bits[ijk] = true; } } if (CancelF()) { return; } // Unique set of 'new' voxels to compute in next iteration. var queue = new HashSet <Vector3i>(); var queue_lock = new SpinLock(); while (true) { if (CancelF()) { return; } // can process front voxels in parallel bool abort = false; int iter_count = 0; gParallel.ForEach(cur_front, (ijk) => { Interlocked.Increment(ref iter_count); if (iter_count % 100 == 0) { abort = CancelF(); } if (abort) { return; } float val = scalars[ijk]; // check 26-neighbours to see if we have a crossing in any direction for (int k = 0; k < 26; ++k) { Vector3i nijk = ijk + gIndices.GridOffsets26[k]; if (bounds.Contains(nijk) == false) { continue; } float val2 = scalars[nijk]; if (val2 == float.MaxValue) { var gx = new Vector3d((float)nijk.x * dx + origin[0], (float)nijk.y * dx + origin[1], (float)nijk.z * dx + origin[2]); val2 = (float)ScalarF(gx); scalars[nijk] = val2; } if (bits[nijk] == false) { // this is a 'new' voxel this round. // If we have an iso-crossing, add it to the front next round bool crossing = (val <IsoValue && val2> IsoValue) || (val > IsoValue && val2 < IsoValue); if (crossing) { bool taken = false; queue_lock.Enter(ref taken); queue.Add(nijk); queue_lock.Exit(); } } } }); if (DebugPrint) { System.Console.WriteLine("front has {0} voxels", queue.Count); } if (queue.Count == 0) { break; } // update known-voxels list and create front for next iteration foreach (Vector3i idx in queue) { bits[idx] = true; } cur_front.Clear(); cur_front.AddRange(queue); queue.Clear(); } if (DebugPrint) { System.Console.WriteLine("done front-prop"); } if (DebugPrint) { int filled = 0; foreach (Vector3i ijk in scalars.Indices()) { if (scalars[ijk] != float.MaxValue) { filled++; } } System.Console.WriteLine("filled: {0} / {1} - {2}%", filled, ni * nj * nk, (double)filled / (double)(ni * nj * nk) * 100.0); } if (CancelF()) { return; } // fill in the rest of the grid by propagating know values fill_spans(ni, nj, nk, scalars); if (DebugPrint) { System.Console.WriteLine("done sweep"); } }
void generate_graph(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk; float dx = (float)CellSize; Vector3f origin = this.GridOrigin; // parameters for initializing cost grid float MODEL_SPACE = 0.01f; // needs small positive so that points on triangles count as inside (eg on ground plane) //float MODEL_SPACE = 2.0f*(float)CellSize; float CRAZY_DISTANCE = 99999.0f; bool UNIFORM_DISTANCE = true; float MAX_DIST = 10 * (float)CellSize; // parameters for sorting seeds Vector3i center_idx = new Vector3i(ni / 2, 0, nk / 2); // middle //Vector3i center_idx = new Vector3i(0, 0, 0); // corner bool reverse_per_layer = true; DenseGrid3f costGrid = new DenseGrid3f(supportGrid); foreach (Vector3i ijk in costGrid.Indices()) { Vector3d cell_center = new Vector3f(ijk.x * dx, ijk.y * dx, ijk.z * dx) + origin; float f = (float)distanceField.Value(ref cell_center); if (f <= MODEL_SPACE) { f = CRAZY_DISTANCE; } else if (UNIFORM_DISTANCE) { f = 1.0f; } else if (f > MAX_DIST) { f = MAX_DIST; } costGrid[ijk] = f; } // Find seeds on each layer, sort, and add to accumulated bottom-up seeds list. // This sorting has an *enormous* effect on the support generation. List <Vector3i> seeds = new List <Vector3i>(); List <Vector3i> layer_seeds = new List <Vector3i>(); for (int j = 0; j < nj; ++j) { layer_seeds.Clear(); for (int k = 0; k < nk; ++k) { for (int i = 0; i < ni; ++i) { if (supportGrid[i, j, k] == SUPPORT_TIP_BASE) { layer_seeds.Add(new Vector3i(i, j, k)); } } } layer_seeds.Sort((a, b) => { Vector3i pa = a; pa.y = 0; Vector3i pb = b; pb.y = 0; int sa = (pa - center_idx).LengthSquared, sb = (pb - center_idx).LengthSquared; return(sa.CompareTo(sb)); }); // reversing sort order is intresting? if (reverse_per_layer) { layer_seeds.Reverse(); } seeds.AddRange(layer_seeds); } HashSet <Vector3i> seed_indices = new HashSet <Vector3i>(seeds); // gives very different results... if (ProcessBottomUp == false) { seeds.Reverse(); } // for linear index a, is this a node we allow in graph? (ie graph bounds) Func <int, bool> node_filter_f = (a) => { Vector3i ai = costGrid.to_index(a); // why not y check?? return(ai.x > 0 && ai.z > 0 && ai.x != ni - 1 && ai.y != nj - 1 && ai.z != nk - 1); }; // distance from linear index a to linear index b // this defines the cost field we want to find shortest path through Func <int, int, float> node_dist_f = (a, b) => { Vector3i ai = costGrid.to_index(a), bi = costGrid.to_index(b); if (bi.y >= ai.y) // b.y should always be a.y-1 { return(float.MaxValue); } float sg = supportGrid[bi]; // don't connect to tips //if (sg == SUPPORT_TIP_BASE || sg == SUPPORT_TIP_TOP) // return float.MaxValue; if (sg == SUPPORT_TIP_TOP) { return(float.MaxValue); } if (sg < 0) { return(-999999); // if b is already used, we will terminate there, so this is a good choice } // otherwise cost is sqr-grid-distance + costGrid value (which is basically distance to surface) float c = costGrid[b]; float f = (float)(Math.Sqrt((bi - ai).LengthSquared) * CellSize); //float f = 0; return(c + f); }; // which linear-index nbrs to consider for linear index a Func <int, IEnumerable <int> > neighbour_f = (a) => { Vector3i ai = costGrid.to_index(a); return(down_neighbours(ai, costGrid)); }; // when do we terminate Func <int, bool> terminate_f = (a) => { Vector3i ai = costGrid.to_index(a); // terminate if we hit existing support path if (seed_indices.Contains(ai) == false && supportGrid[ai] < 0) { return(true); } // terminate if we hit ground plane if (ai.y == 0) { return(true); } return(false); }; DijkstraGraphDistance dijkstra = new DijkstraGraphDistance(ni * nj * nk, false, node_filter_f, node_dist_f, neighbour_f); dijkstra.TrackOrder = true; List <int> path = new List <int>(); Graph = new DGraph3(); Dictionary <Vector3i, int> CellToGraph = new Dictionary <Vector3i, int>(); TipVertices = new HashSet <int>(); TipBaseVertices = new HashSet <int>(); GroundVertices = new HashSet <int>(); // seeds are tip-base points for (int k = 0; k < seeds.Count; ++k) { // add seed point (which is a tip-base vertex) as seed for dijkstra prop int seed = costGrid.to_linear(seeds[k]); dijkstra.Reset(); dijkstra.AddSeed(seed, 0); // compute to termination (ground, existing node, etc) int base_node = dijkstra.ComputeToNode(terminate_f); if (base_node < 0) { base_node = dijkstra.GetOrder().Last(); } // extract the path path.Clear(); dijkstra.GetPathToSeed(base_node, path); int N = path.Count; // first point on path is termination point. // create vertex for it if we have not yet Vector3i basept_idx = supportGrid.to_index(path[0]); int basept_vid; if (CellToGraph.TryGetValue(basept_idx, out basept_vid) == false) { Vector3d curv = get_cell_center(basept_idx); if (basept_idx.y == 0) { curv.y = 0; } basept_vid = Graph.AppendVertex(curv); if (basept_idx.y == 0) { GroundVertices.Add(basept_vid); } CellToGraph[basept_idx] = basept_vid; } int cur_vid = basept_vid; // now walk up path and create vertices as necessary for (int i = 0; i < N; ++i) { int idx = path[i]; if (supportGrid[idx] >= 0) { supportGrid[idx] = SUPPORT_GRID_USED; } if (i > 0) { Vector3i next_idx = supportGrid.to_index(path[i]); int next_vid; if (CellToGraph.TryGetValue(next_idx, out next_vid) == false) { Vector3d nextv = get_cell_center(next_idx); next_vid = Graph.AppendVertex(nextv); CellToGraph[next_idx] = next_vid; } Graph.AppendEdge(cur_vid, next_vid); cur_vid = next_vid; } } // seed was tip-base so we should always get back there. Then we // explicitly add tip-top and edge to it. if (supportGrid[path[N - 1]] == SUPPORT_TIP_BASE) { Vector3i vec_idx = supportGrid.to_index(path[N - 1]); TipBaseVertices.Add(CellToGraph[vec_idx]); Vector3i tip_idx = vec_idx + Vector3i.AxisY; int tip_vid; if (CellToGraph.TryGetValue(tip_idx, out tip_vid) == false) { Vector3d tipv = get_cell_center(tip_idx); tip_vid = Graph.AppendVertex(tipv); CellToGraph[tip_idx] = tip_vid; Graph.AppendEdge(cur_vid, tip_vid); TipVertices.Add(tip_vid); } } } /* * Snap tips to surface */ gParallel.ForEach(TipVertices, (tip_vid) => { bool snapped = false; Vector3d v = Graph.GetVertex(tip_vid); Frame3f hitF; // try shooting ray straight up. if that hits, and point is close, we use it if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, Vector3d.AxisY), out hitF)) { if (v.Distance(hitF.Origin) < 2 * CellSize) { v = hitF.Origin; snapped = true; } } // if that failed, try straight down if (!snapped) { if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, -Vector3d.AxisY), out hitF)) { if (v.Distance(hitF.Origin) < CellSize) { v = hitF.Origin; snapped = true; } } } // if it missed, or hit pt was too far, find nearest point and try that if (!snapped) { hitF = MeshQueries.NearestPointFrame(Mesh, MeshSpatial, v); if (v.Distance(hitF.Origin) < 2 * CellSize) { v = hitF.Origin; snapped = true; } // can this ever fail? tips should always be within 2 cells... } if (snapped) { Graph.SetVertex(tip_vid, v); } }); }