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"); } }
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); }