static void smooth(DenseGrid3f grid, DenseGrid3f tmp, float alpha, int iters, int min_j = 1) { if (tmp == null) { tmp = new DenseGrid3f(grid); } int ni = grid.ni, nj = grid.nj, nk = grid.nk; for (int iter = 0; iter < iters; ++iter) { for (int j = min_j; j < nj - 1; ++j) { for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { float avg = 0; foreach (Vector3i o in gIndices.GridOffsets26) { int xi = i + o.x, yi = j + o.y, zi = k + o.z; float f = grid[xi, yi, zi]; avg += f; } avg /= 26.0f; tmp[i, j, k] = (1 - alpha) * grid[i, j, k] + (alpha) * avg; } } } grid.swap(tmp); } }
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
} // end make_level_set_3 void fill_spans(int ni, int nj, int nk, DenseGrid3f scalars) { gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (idx) => { int j = idx.y, k = idx.z; float last = scalars[0, j, k]; if (last == float.MaxValue) { last = 0; } for (int i = 0; i < ni; ++i) { if (scalars[i, j, k] == float.MaxValue) { scalars[i, j, k] = last; } else { last = scalars[i, j, k]; if (last < IsoValue) // propagate zeros on outside { last = 0; } } } }); }
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
public void Generate() { // figure out origin & dimensions AxisAlignedBox3d bounds = Mesh.CachedBounds; if (ForceMinY != float.MaxValue) { bounds.Min.y = ForceMinY; } // expand grid so we have some border space in x and z float fBufferWidth = 2 * (float)CellSize; Vector3f b = new Vector3f(fBufferWidth, 0, fBufferWidth); grid_origin = (Vector3f)bounds.Min - b; // need zero isovalue to be at y=0. right now we set yi=0 voxels to be -1, // so if we nudge up half a cell, then interpolation with boundary outside // value should be 0 right at cell border (seems to be working?) grid_origin.y += (float)CellSize * 0.5f; Vector3f max = (Vector3f)bounds.Max + b; int ni = (int)((max.x - grid_origin.x) / (float)CellSize) + 1; int nj = (int)((max.y - grid_origin.y) / (float)CellSize) + 1; int nk = (int)((max.z - grid_origin.z) / (float)CellSize) + 1; volume_grid = new DenseGrid3f(); generate_support(grid_origin, (float)CellSize, ni, nj, nk, volume_grid); }
static DenseGrid3i binarize(DenseGrid3f grid, float thresh = 0) { DenseGrid3i result = new DenseGrid3i(); result.resize(grid.ni, grid.nj, grid.nk); int size = result.size; for (int k = 0; k < size; ++k) { result[k] = (grid[k] < thresh) ? 1 : 0; } return(result); }
void generate_mesh(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { DenseGridTrilinearImplicit volume = new DenseGridTrilinearImplicit( supportGrid, GridOrigin, CellSize); BoundedImplicitFunction3d inputF = volume; if (SubtractMesh) { BoundedImplicitFunction3d sub = distanceField; if (SubtractMeshOffset > 0) { sub = new ImplicitOffset3d() { A = distanceField, Offset = SubtractMeshOffset } } ; ImplicitDifference3d subtract = new ImplicitDifference3d() { A = volume, B = sub }; inputF = subtract; } ImplicitHalfSpace3d cutPlane = new ImplicitHalfSpace3d() { Origin = Vector3d.Zero, Normal = Vector3d.AxisY }; ImplicitDifference3d cut = new ImplicitDifference3d() { A = inputF, B = cutPlane }; MarchingCubes mc = new MarchingCubes() { Implicit = cut, Bounds = grid_bounds, CubeSize = CellSize }; mc.Bounds.Min.y = -2 * mc.CubeSize; mc.Bounds.Min.x -= 2 * mc.CubeSize; mc.Bounds.Min.z -= 2 * mc.CubeSize; mc.Bounds.Max.x += 2 * mc.CubeSize; mc.Bounds.Max.z += 2 * mc.CubeSize; mc.Generate(); SupportMesh = mc.Mesh; } }
public void GenerateContinuation(IEnumerable <Vector3d> seeds) { Mesh = new DMesh3(); int nx = (int)(Bounds.Width / CubeSize) + 1; int ny = (int)(Bounds.Height / CubeSize) + 1; int nz = (int)(Bounds.Depth / CubeSize) + 1; CellDimensions = new Vector3i(nx, ny, nz); GridBounds = new AxisAlignedBox3i(Vector3i.Zero, CellDimensions); if (LastGridBounds != GridBounds) { corner_values_grid = new DenseGrid3f(nx + 1, ny + 1, nz + 1, float.MaxValue); edge_vertices = new Dictionary <long, int>(); corner_values = new Dictionary <long, double>(); if (ParallelCompute) { done_cells = new DenseGrid3i(CellDimensions.x, CellDimensions.y, CellDimensions.z, 0); } } else { edge_vertices.Clear(); corner_values.Clear(); corner_values_grid.assign(float.MaxValue); if (ParallelCompute) { done_cells.assign(0); } } if (ParallelCompute) { generate_continuation_parallel(seeds); } else { generate_continuation(seeds); } LastGridBounds = GridBounds; }
IEnumerable <int> down_neighbours(Vector3i idx, DenseGrid3f grid) { yield return(grid.to_linear(idx.x, idx.y - 1, idx.z)); yield return(grid.to_linear(idx.x - 1, idx.y - 1, idx.z)); yield return(grid.to_linear(idx.x + 1, idx.y - 1, idx.z)); yield return(grid.to_linear(idx.x, idx.y - 1, idx.z - 1)); yield return(grid.to_linear(idx.x, idx.y - 1, idx.z + 1)); yield return(grid.to_linear(idx.x - 1, idx.y - 1, idx.z - 1)); yield return(grid.to_linear(idx.x + 1, idx.y - 1, idx.z - 1)); yield return(grid.to_linear(idx.x - 1, idx.y - 1, idx.z + 1)); yield return(grid.to_linear(idx.x + 1, idx.y - 1, idx.z + 1)); }
protected override void SolveInstance(IGH_DataAccess DA) { Point3d pt = Point3d.Origin; int ni = 0; int nj = 0; int nk = 0; double cellSize = 0; DA.GetData(0, ref pt); DA.GetData(1, ref ni); DA.GetData(2, ref nj); DA.GetData(3, ref nk); DA.GetData(4, ref cellSize); DenseGrid3f grid = new DenseGrid3f(ni, nj, nk, 0); DenseGridTrilinearImplicit grd = new DenseGridTrilinearImplicit(grid, pt.ToVec3d(), cellSize); DA.SetData(0, grd); }
void fill_vertical_spans(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk; float dx = (float)CellSize; Vector3f origin = this.GridOrigin; // sweep values down, column by column for (int k = 0; k < nk; ++k) { for (int i = 0; i < ni; ++i) { bool in_support = false; for (int j = nj - 1; j >= 0; j--) { float fcur = supportGrid[i, j, k]; if (fcur >= 0) { Vector3d cell_center = get_cell_center(i, j, k); if (in_support) { bool is_inside = distanceField.Value(ref cell_center) < 0; if (is_inside) { supportGrid[i, j, k] = -3; in_support = false; } else { supportGrid[i, j, k] = -1; } } } else { in_support = true; } } } } }
public void Compute() { // figure out origin & dimensions AxisAlignedBox3d bounds = Mesh.CachedBounds; float fBufferWidth = 2 * BufferCells * (float)CellSize; grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One; Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One; int ni = (int)((max.x - grid_origin.x) / (float)CellSize) + 1; int nj = (int)((max.y - grid_origin.y) / (float)CellSize) + 1; int nk = (int)((max.z - grid_origin.z) / (float)CellSize) + 1; scalar_grid = new DenseGrid3f(); if (ComputeMode == ComputeModes.FullGrid) { make_grid_dense(grid_origin, (float)CellSize, ni, nj, nk, scalar_grid); } else { make_grid(grid_origin, (float)CellSize, ni, nj, nk, scalar_grid); } }
/// <summary> /// Run MC algorithm and generate Output mesh /// </summary> public void Generate() { Mesh = new DMesh3(); int nx = (int)(Bounds.Width / CubeSize) + 1; int ny = (int)(Bounds.Height / CubeSize) + 1; int nz = (int)(Bounds.Depth / CubeSize) + 1; CellDimensions = new Vector3i(nx, ny, nz); GridBounds = new AxisAlignedBox3i(Vector3i.Zero, CellDimensions); corner_values_grid = new DenseGrid3f(nx + 1, ny + 1, nz + 1, float.MaxValue); edge_vertices = new Dictionary <long, int>(); corner_values = new Dictionary <long, double>(); if (ParallelCompute) { generate_parallel(); } else { generate_basic(); } }
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 process_version1(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk; float dx = (float)CellSize; Vector3f origin = this.GridOrigin; // sweep values down, column by column for (int k = 0; k < nk; ++k) { for (int i = 0; i < ni; ++i) { bool in_support = false; for (int j = nj - 1; j >= 0; j--) { float fcur = supportGrid[i, j, k]; if (fcur >= 0) { Vector3d cell_center = new Vector3f(i * dx, j * dx, k * dx) + origin; if (in_support) { bool is_inside = distanceField.Value(ref cell_center) < 0; if (is_inside) { supportGrid[i, j, k] = -3; in_support = false; } else { supportGrid[i, j, k] = -1; } } } else { in_support = true; } } } } // skeletonize each layer // todo: would be nice to skeletonize the 3D volume.. ? DenseGrid3i binary = new DenseGrid3i(ni, nj, nk, 0); foreach (Vector3i idx in binary.Indices()) { binary[idx] = (supportGrid[idx] < 0) ? 1 : 0; } for (int j = 0; j < nj; ++j) { skeletonize_layer(binary, j); } // debug thing //VoxelSurfaceGenerator voxgen = new VoxelSurfaceGenerator() { // Voxels = binary.get_bitmap() //}; //voxgen.Generate(); //Util.WriteDebugMesh(voxgen.makemesh(), "c:\\scratch\\binary.obj"); // for skeleton voxels, we add some power for (int j = 0; j < nj; ++j) { for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { if (binary[i, j, k] > 0) { supportGrid[i, j, k] = -3; } //else // supportGrid[i, j, k] = 1; // clear non-skeleton voxels } } } // power up the ground-plane voxels for (int k = 0; k < nk; ++k) { for (int i = 0; i < ni; ++i) { if (supportGrid[i, 0, k] < 0) { supportGrid[i, 0, k] = -5; } } } #if true DenseGrid3f smoothed = new DenseGrid3f(supportGrid); float nbr_weight = 0.5f; for (int iter = 0; iter < 15; ++iter) { // add some mass to skeleton voxels for (int j = 0; j < nj; ++j) { for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { if (binary[i, j, k] > 0) { supportGrid[i, j, k] = supportGrid[i, j, k] - nbr_weight / 25.0f; } } } } for (int j = 0; j < nj; ++j) { for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { int neg = 0; float avg = 0, w = 0; for (int n = 0; n < 8; ++n) { int xi = i + gIndices.GridOffsets8[n].x; int zi = k + gIndices.GridOffsets8[n].y; float f = supportGrid[xi, j, zi]; if (f < 0) { neg++; } avg += nbr_weight * f; w += nbr_weight; } if (neg > -1) { avg += supportGrid[i, j, k]; w += 1.0f; smoothed[i, j, k] = avg / w; } else { smoothed[i, j, k] = supportGrid[i, j, k]; } } } } supportGrid.swap(smoothed); } #endif // hard-enforce that skeleton voxels stay inside //for (int j = 0; j < nj; ++j) { // for (int k = 1; k < nk - 1; ++k) { // for (int i = 1; i < ni - 1; ++i) { // if (binary[i, j, k] > 0) // supportGrid[i, j, k] = Math.Min(supportGrid[i, j, k], - 1); // } // } //} }
void process_version2(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk; float dx = (float)CellSize; Vector3f origin = this.GridOrigin; // sweep values down layer by layer DenseGrid2f prev = supportGrid.get_slice(nj - 1, 1); DenseGrid2f tmp = new DenseGrid2f(prev); Bitmap3 bmp = new Bitmap3(new Vector3i(ni, nj, nk)); for (int j = nj - 2; j >= 0; j--) { // skeletonize prev layer DenseGrid2i prev_skel = binarize(prev, 0.0f); skeletonize(prev_skel, null, 2); //dilate_loners(prev_skel, null, 2); if (j == 0) { dilate(prev_skel, null, true); dilate(prev_skel, null, true); } for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { bmp[new Vector3i(i, j, k)] = (prev_skel[i, k] == 1) ? true : false; } } smooth(prev, tmp, 0.5f, 5); DenseGrid2f cur = supportGrid.get_slice(j, 1); cur.set_min(prev); for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { float skelf = prev_skel[i, k] > 0 ? -1.0f : int.MaxValue; cur[i, k] = Math.Min(cur[i, k], skelf); if (cur[i, k] < 0) { Vector3d cell_center = new Vector3f(i * dx, j * dx, k * dx) + origin; if (distanceField.Value(ref cell_center) < -CellSize) { cur[i, k] = 1; } } } } for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { if (is_loner(prev_skel, i, k)) { foreach (Vector2i d in gIndices.GridOffsets8) { float f = 1.0f / (float)Math.Sqrt(d.x * d.x + d.y * d.y); cur[i + d.x, k + d.y] += -0.25f * f; } } } } for (int k = 1; k < nk - 1; ++k) { for (int i = 1; i < ni - 1; ++i) { supportGrid[i, j, k] = cur[i, k]; } } prev.swap(cur); } VoxelSurfaceGenerator gen = new VoxelSurfaceGenerator() { Voxels = bmp }; gen.Generate(); Util.WriteDebugMesh(gen.Meshes[0], "c:\\scratch\\binary.obj"); }
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); } }); }
public static void test_marching_cubes_topology() { AxisAlignedBox3d bounds = new AxisAlignedBox3d(1.0); int numcells = 64; double cellsize = bounds.MaxDim / numcells; Random r = new Random(31337); for (int ii = 0; ii < 100; ++ii) { DenseGrid3f grid = new DenseGrid3f(); grid.resize(numcells, numcells, numcells); grid.assign(1); for (int k = 2; k < numcells - 3; k++) { for (int j = 2; j < numcells - 3; j++) { for (int i = 2; i < numcells - 3; i++) { double d = r.NextDouble(); if (d > 0.9) { grid[i, j, k] = 0.0f; } else if (d > 0.5) { grid[i, j, k] = 1.0f; } else { grid[i, j, k] = -1.0f; } } } } var iso = new DenseGridTrilinearImplicit(grid, Vector3f.Zero, cellsize); MarchingCubes c = new MarchingCubes(); c.Implicit = iso; c.Bounds = bounds; //c.Bounds.Max += 3 * cellsize * Vector3d.One; //c.Bounds.Expand(2*cellsize); // this produces holes c.CubeSize = cellsize * 4.1; //c.CubeSize = cellsize * 2; //c.Bounds = new AxisAlignedBox3d(2.0); c.Generate(); for (float f = 2.0f; f < 8.0f; f += 0.13107f) { c.CubeSize = cellsize * 4.1; c.Generate(); c.Mesh.CheckValidity(false); MeshBoundaryLoops loops = new MeshBoundaryLoops(c.Mesh); if (loops.Count > 0) { throw new Exception("found loops!"); } } } //c.Mesh.CheckValidity(false); //TestUtil.WriteTestOutputMesh(c.Mesh, "marching_cubes_topotest.obj"); }
void generate_support(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f supportGrid) { supportGrid.resize(ni, nj, nk); supportGrid.assign(1); // sentinel if (DebugPrint) { System.Console.WriteLine("start"); } bool CHECKERBOARD = false; System.Console.WriteLine("Computing SDF"); // compute unsigned SDF MeshSignedDistanceGrid sdf = new MeshSignedDistanceGrid(Mesh, CellSize) { ComputeSigns = true, ExactBandWidth = 3, /*,ComputeMode = MeshSignedDistanceGrid.ComputeModes.FullGrid*/ }; sdf.CancelF = Cancelled; sdf.Compute(); if (Cancelled()) { return; } var distanceField = new DenseGridTrilinearImplicit(sdf.Grid, sdf.GridOrigin, sdf.CellSize); double angle = MathUtil.Clamp(OverhangAngleDeg, 0.01, 89.99); double cos_thresh = Math.Cos(angle * MathUtil.Deg2Rad); System.Console.WriteLine("Marking overhangs"); // Compute narrow-band distances. For each triangle, we find its grid-coord-bbox, // and compute exact distances within that box. The intersection_count grid // is also filled in this computation double ddx = (double)dx; double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; Vector3d va = Vector3d.Zero, vb = Vector3d.Zero, vc = Vector3d.Zero; foreach (int tid in Mesh.TriangleIndices()) { if (tid % 100 == 0 && Cancelled()) { break; } Mesh.GetTriVertices(tid, ref va, ref vb, ref vc); Vector3d normal = MathUtil.Normal(ref va, ref vb, ref vc); if (normal.Dot(-Vector3d.AxisY) < cos_thresh) { continue; } // real ijk coordinates of va/vb/vc double fip = (va[0] - ox) / ddx, fjp = (va[1] - oy) / ddx, fkp = (va[2] - oz) / ddx; double fiq = (vb[0] - ox) / ddx, fjq = (vb[1] - oy) / ddx, fkq = (vb[2] - oz) / ddx; double fir = (vc[0] - ox) / ddx, fjr = (vc[1] - oy) / ddx, fkr = (vc[2] - oz) / ddx; // clamped integer bounding box of triangle plus exact-band int exact_band = 0; int i0 = MathUtil.Clamp(((int)MathUtil.Min(fip, fiq, fir)) - exact_band, 0, ni - 1); int i1 = MathUtil.Clamp(((int)MathUtil.Max(fip, fiq, fir)) + exact_band + 1, 0, ni - 1); int j0 = MathUtil.Clamp(((int)MathUtil.Min(fjp, fjq, fjr)) - exact_band, 0, nj - 1); int j1 = MathUtil.Clamp(((int)MathUtil.Max(fjp, fjq, fjr)) + exact_band + 1, 0, nj - 1); int k0 = MathUtil.Clamp(((int)MathUtil.Min(fkp, fkq, fkr)) - exact_band, 0, nk - 1); int k1 = MathUtil.Clamp(((int)MathUtil.Max(fkp, fkq, fkr)) + exact_band + 1, 0, nk - 1); // don't put into y=0 plane if (j0 == 0) { j0 = 1; } // compute distance for each tri inside this bounding box // note: this can be very conservative if the triangle is large and on diagonal to grid axes for (int k = k0; k <= k1; ++k) { for (int j = j0; j <= j1; ++j) { for (int i = i0; i <= i1; ++i) { Vector3d gx = new Vector3d((float)i * dx + origin[0], (float)j * dx + origin[1], (float)k * dx + origin[2]); float d = (float)MeshSignedDistanceGrid.point_triangle_distance(ref gx, ref va, ref vb, ref vc); // vertical checkerboard pattern (eg 'tips') if (CHECKERBOARD) { int zz = (k % 2 == 0) ? 1 : 0; if (i % 2 == zz) { continue; } } if (d < dx / 2) { if (j > 1) { supportGrid[i, j, k] = SUPPORT_TIP_TOP; supportGrid[i, j - 1, k] = SUPPORT_TIP_BASE; } else { supportGrid[i, j, k] = SUPPORT_TIP_BASE; } } } } } } if (Cancelled()) { return; } //process_version1(supportGrid, distanceField); //process_version2(supportGrid, distanceField); generate_graph(supportGrid, distanceField); //Util.WriteDebugMesh(MakeDebugGraphMesh(), "c:\\scratch\\__LAST_GRAPH_INIT.obj"); postprocess_graph(); //Util.WriteDebugMesh(MakeDebugGraphMesh(), "c:\\scratch\\__LAST_GRAPH_OPT.obj"); }
void generate_support(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f supportGrid) { supportGrid.resize(ni, nj, nk); supportGrid.assign(1); // sentinel bool CHECKERBOARD = false; // compute unsigned SDF int exact_band = 1; if (SubtractMesh && SubtractMeshOffset > 0) { int offset_band = (int)(SubtractMeshOffset / CellSize) + 1; exact_band = Math.Max(exact_band, offset_band); } sdf = new MeshSignedDistanceGrid(Mesh, CellSize) { ComputeSigns = true, ExactBandWidth = exact_band }; sdf.CancelF = this.CancelF; sdf.Compute(); if (CancelF()) { return; } var distanceField = new DenseGridTrilinearImplicit(sdf.Grid, sdf.GridOrigin, sdf.CellSize); double angle = MathUtil.Clamp(OverhangAngleDeg, 0.01, 89.99); double cos_thresh = Math.Cos(angle * MathUtil.Deg2Rad); // Compute narrow-band distances. For each triangle, we find its grid-coord-bbox, // and compute exact distances within that box. The intersection_count grid // is also filled in this computation double ddx = (double)dx; double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; Vector3d va = Vector3d.Zero, vb = Vector3d.Zero, vc = Vector3d.Zero; foreach (int tid in Mesh.TriangleIndices()) { if (tid % 100 == 0 && CancelF()) { break; } Mesh.GetTriVertices(tid, ref va, ref vb, ref vc); Vector3d normal = MathUtil.Normal(ref va, ref vb, ref vc); if (normal.Dot(-Vector3d.AxisY) < cos_thresh) { continue; } // real ijk coordinates of va/vb/vc double fip = (va[0] - ox) / ddx, fjp = (va[1] - oy) / ddx, fkp = (va[2] - oz) / ddx; double fiq = (vb[0] - ox) / ddx, fjq = (vb[1] - oy) / ddx, fkq = (vb[2] - oz) / ddx; double fir = (vc[0] - ox) / ddx, fjr = (vc[1] - oy) / ddx, fkr = (vc[2] - oz) / ddx; // clamped integer bounding box of triangle plus exact-band int extra_band = 0; int i0 = MathUtil.Clamp(((int)MathUtil.Min(fip, fiq, fir)) - extra_band, 0, ni - 1); int i1 = MathUtil.Clamp(((int)MathUtil.Max(fip, fiq, fir)) + extra_band + 1, 0, ni - 1); int j0 = MathUtil.Clamp(((int)MathUtil.Min(fjp, fjq, fjr)) - extra_band, 0, nj - 1); int j1 = MathUtil.Clamp(((int)MathUtil.Max(fjp, fjq, fjr)) + extra_band + 1, 0, nj - 1); int k0 = MathUtil.Clamp(((int)MathUtil.Min(fkp, fkq, fkr)) - extra_band, 0, nk - 1); int k1 = MathUtil.Clamp(((int)MathUtil.Max(fkp, fkq, fkr)) + extra_band + 1, 0, nk - 1); // don't put into y=0 plane //if (j0 == 0) // j0 = 1; // compute distance for each tri inside this bounding box // note: this can be very conservative if the triangle is large and on diagonal to grid axes for (int k = k0; k <= k1; ++k) { for (int j = j0; j <= j1; ++j) { for (int i = i0; i <= i1; ++i) { Vector3d gx = new Vector3d((float)i * dx + origin[0], (float)j * dx + origin[1], (float)k * dx + origin[2]); float d = (float)MeshSignedDistanceGrid.point_triangle_distance(ref gx, ref va, ref vb, ref vc); // vertical checkerboard pattern (eg 'tips') if (CHECKERBOARD) { int zz = (k % 2 == 0) ? 1 : 0; if (i % 2 == zz) { continue; } } if (d < dx / 2) { supportGrid[i, j, k] = SUPPORT_TIP_TOP; } } } } } if (CancelF()) { return; } fill_vertical_spans(supportGrid, distanceField); generate_mesh(supportGrid, distanceField); }