public override Schematic WriteSchematic() { DMesh3 mesh = StandardMeshReader.ReadMesh(_path); AxisAlignedBox3d bounds = mesh.CachedBounds; DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, autoBuild: true); double cellsize = mesh.CachedBounds.MaxDim / _gridSize; ShiftGridIndexer3 indexer = new ShiftGridIndexer3(bounds.Min, cellsize); MeshSignedDistanceGrid sdf = new MeshSignedDistanceGrid(mesh, cellsize); sdf.Compute(); Bitmap3 bmp = new Bitmap3(sdf.Dimensions); Schematic schematic = new Schematic() { Blocks = new HashSet <Block>(), Width = (ushort)bmp.Dimensions.x, Height = (ushort)bmp.Dimensions.y, Length = (ushort)bmp.Dimensions.z }; LoadedSchematic.WidthSchematic = schematic.Width; LoadedSchematic.HeightSchematic = schematic.Height; LoadedSchematic.LengthSchematic = schematic.Length; if (_winding_number != 0) { spatial.WindingNumber(Vector3d.Zero); // seed cache outside of parallel eval using (ProgressBar progressbar = new ProgressBar()) { List <Vector3i> list = bmp.Indices().ToList(); int count = 0; gParallel.ForEach(bmp.Indices(), (idx) => { Vector3d v = indexer.FromGrid(idx); bmp.SafeSet(idx, spatial.WindingNumber(v) > _winding_number); count++; progressbar.Report(count / (float)list.Count); }); } if (!_excavate) { foreach (Vector3i idx in bmp.Indices()) { if (bmp.Get(idx)) { schematic.Blocks.Add(new Block((ushort)idx.x, (ushort)idx.y, (ushort)idx.z, Color.White.ColorToUInt())); } } } } else { using (ProgressBar progressbar = new ProgressBar()) { int count = bmp.Indices().Count(); List <Vector3i> list = bmp.Indices().ToList(); for (int i = 0; i < count; i++) { Vector3i idx = list[i]; float f = sdf[idx.x, idx.y, idx.z]; bool isInside = f < 0; bmp.Set(idx, (f < 0)); if (!_excavate && isInside) { schematic.Blocks.Add(new Block((ushort)idx.x, (ushort)idx.y, (ushort)idx.z, Color.White.ColorToUInt())); } progressbar.Report((i / (float)count)); } } } if (_excavate) { foreach (Vector3i idx in bmp.Indices()) { if (bmp.Get(idx) && IsBlockConnectedToAir(bmp, idx)) { schematic.Blocks.Add(new Block((ushort)idx.x, (ushort)idx.y, (ushort)idx.z, Color.White.ColorToUInt())); } } } return(schematic); }
void make_grid(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f winding) { winding.resize(ni, nj, nk); winding.assign(float.MaxValue); // sentinel // seed MWN cache MeshSpatial.WindingNumber(Vector3d.Zero); if (DebugPrint) { System.Console.WriteLine("start"); } // Ok, because the whole idea is that the surface might have holes, we are going to // compute MWN along known triangles and then propagate the computed region outwards // until any MWN iso-sign-change is surrounded. // To seed propagation, we compute unsigned SDF and then compute MWN for any voxels // containing surface (ie w/ distance smaller than cellsize) // compute unsigned SDF MeshSignedDistanceGrid 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 MWN 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) { Vector3i 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) { Vector3d 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); } } }); 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 MWN sign changes, that neighbour is added to front. // Front is initialized w/ all voxels we computed above AxisAlignedBox3i bounds = winding.Bounds; bounds.Max -= Vector3i.One; // since we will be computing new MWN values as necessary, we cannot use // winding grid to track whether a voxel is 'new' or not. // So, using 3D bitmap intead - is updated at end of each pass. Bitmap3 bits = new Bitmap3(new Vector3i(ni, nj, nk)); List <Vector3i> cur_front = new List <Vector3i>(); foreach (Vector3i ijk in winding.Indices()) { if (winding[ijk] != float.MaxValue) { cur_front.Add(ijk); bits[ijk] = true; } } if (CancelF()) { return; } // Unique set of 'new' voxels to compute in next iteration. HashSet <Vector3i> queue = new HashSet <Vector3i>(); SpinLock 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 = winding[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 = winding[nijk]; if (val2 == float.MaxValue) { Vector3d gx = new Vector3d((float)nijk.x * dx + origin[0], (float)nijk.y * dx + origin[1], (float)nijk.z * dx + origin[2]); val2 = (float)MeshSpatial.WindingNumber(gx); winding[nijk] = val2; } if (bits[nijk] == false) { // this is a 'new' voxel this round. // If we have a MWN-iso-crossing, add it to the front next round bool crossing = (val <WindingIsoValue && val2> WindingIsoValue) || (val > WindingIsoValue && val2 < WindingIsoValue); 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 winding.Indices()) { if (winding[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 MWN values fill_spans(ni, nj, nk, winding); if (DebugPrint) { System.Console.WriteLine("done sweep"); } }
public static void test_Winding() { Sphere3Generator_NormalizedCube gen = new Sphere3Generator_NormalizedCube() { EdgeVertices = 200 }; DMesh3 mesh = gen.Generate().MakeDMesh(); //DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); GC.Collect(); double maxdim = mesh.CachedBounds.MaxDim; Vector3d center = mesh.CachedBounds.Center; var pts = TestUtil.RandomPoints3(100, new Random(31337), center, maxdim * 0.5); int num_inside = 0; foreach (Vector3d pt in pts) { bool inside = spatial.IsInside(pt); double distSqr = MeshQueries.TriDistanceSqr(mesh, spatial.FindNearestTriangle(pt), pt); double ptDist = pt.Length; double winding = mesh.WindingNumber(pt); double winding_2 = spatial.WindingNumber(pt); if (Math.Abs(winding - winding_2) > 0.00001) { System.Diagnostics.Debugger.Break(); } bool winding_inside = Math.Abs(winding) > 0.5; if (inside != winding_inside) { System.Diagnostics.Debugger.Break(); } if (inside) { num_inside++; } } System.Console.WriteLine("inside {0} / {1}", num_inside, pts.Length); // force rebuild for profiling code GC.Collect(); LocalProfiler p = new LocalProfiler(); pts = TestUtil.RandomPoints3(1000, new Random(31337), center, maxdim * 0.5); p.Start("full eval"); double sum = 0; foreach (Vector3d pt in pts) { double winding = mesh.WindingNumber(pt); sum += winding; } p.StopAll(); p.Start("tree build"); spatial = new DMeshAABBTree3(mesh, true); spatial.WindingNumber(Vector3d.Zero); p.StopAll(); GC.Collect(); GC.Collect(); p.Start("tree eval"); sum = 0; foreach (Vector3d pt in pts) { double winding = spatial.WindingNumber(pt); sum += winding; } p.StopAll(); System.Console.WriteLine(p.AllTimes()); }
public virtual bool Apply() { DMesh3 testAgainstMesh = Mesh; if (InsideMode == CalculationMode.RayParity) { MeshBoundaryLoops loops = new MeshBoundaryLoops(testAgainstMesh); if (loops.Count > 0) { testAgainstMesh = new DMesh3(Mesh); foreach (var loop in loops) { if (Cancelled()) { return(false); } SimpleHoleFiller filler = new SimpleHoleFiller(testAgainstMesh, loop); filler.Fill(); } } } DMeshAABBTree3 spatial = (Spatial != null && testAgainstMesh == Mesh) ? Spatial : new DMeshAABBTree3(testAgainstMesh, true); if (InsideMode == CalculationMode.AnalyticWindingNumber) { spatial.WindingNumber(Vector3d.Zero); } else if (InsideMode == CalculationMode.FastWindingNumber) { spatial.FastWindingNumber(Vector3d.Zero); } if (Cancelled()) { return(false); } // ray directions List <Vector3d> ray_dirs = null; int NR = 0; if (InsideMode == CalculationMode.SimpleOcclusionTest) { ray_dirs = new List <Vector3d>(); ray_dirs.Add(Vector3d.AxisX); ray_dirs.Add(-Vector3d.AxisX); ray_dirs.Add(Vector3d.AxisY); ray_dirs.Add(-Vector3d.AxisY); ray_dirs.Add(Vector3d.AxisZ); ray_dirs.Add(-Vector3d.AxisZ); NR = ray_dirs.Count; } Func <Vector3d, bool> isOccludedF = (pt) => { if (InsideMode == CalculationMode.RayParity) { return(spatial.IsInside(pt)); } else if (InsideMode == CalculationMode.AnalyticWindingNumber) { return(spatial.WindingNumber(pt) > WindingIsoValue); } else if (InsideMode == CalculationMode.FastWindingNumber) { return(spatial.FastWindingNumber(pt) > WindingIsoValue); } else { for (int k = 0; k < NR; ++k) { int hit_tid = spatial.FindNearestHitTriangle(new Ray3d(pt, ray_dirs[k])); if (hit_tid == DMesh3.InvalidID) { return(false); } } return(true); } }; bool cancel = false; BitArray vertices = null; if (PerVertex) { vertices = new BitArray(Mesh.MaxVertexID); MeshNormals normals = null; if (Mesh.HasVertexNormals == false) { normals = new MeshNormals(Mesh); normals.Compute(); } gParallel.ForEach(Mesh.VertexIndices(), (vid) => { if (cancel) { return; } if (vid % 10 == 0) { cancel = Cancelled(); } Vector3d c = Mesh.GetVertex(vid); Vector3d n = (normals == null) ? Mesh.GetVertexNormal(vid) : normals[vid]; c += n * NormalOffset; vertices[vid] = isOccludedF(c); }); } if (Cancelled()) { return(false); } RemovedT = new List <int>(); SpinLock removeLock = new SpinLock(); gParallel.ForEach(Mesh.TriangleIndices(), (tid) => { if (cancel) { return; } if (tid % 10 == 0) { cancel = Cancelled(); } bool inside = false; if (PerVertex) { Index3i tri = Mesh.GetTriangle(tid); inside = vertices[tri.a] || vertices[tri.b] || vertices[tri.c]; } else { Vector3d c = Mesh.GetTriCentroid(tid); Vector3d n = Mesh.GetTriNormal(tid); c += n * NormalOffset; inside = isOccludedF(c); } if (inside) { bool taken = false; removeLock.Enter(ref taken); RemovedT.Add(tid); removeLock.Exit(); } }); if (Cancelled()) { return(false); } if (RemovedT.Count > 0) { MeshEditor editor = new MeshEditor(Mesh); bool bOK = editor.RemoveTriangles(RemovedT, true); RemoveFailed = (bOK == false); } return(true); }
public static void test_fast_winding_grid() { //Sphere3Generator_NormalizedCube gen = new Sphere3Generator_NormalizedCube() { EdgeVertices = 15, Radius = 5 }; //CappedCylinderGenerator gen = new CappedCylinderGenerator() { }; //TrivialBox3Generator gen = new TrivialBox3Generator() { Box = new Box3d(Vector3d.Zero, 5 * Vector3d.One) }; //DMesh3 mesh = gen.Generate().MakeDMesh(); //MeshTransforms.Translate(mesh, 5 * Vector3d.One); DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("holey_bunny.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("holey_bunny_2.obj"); //DMesh3 mesh = TestUtil.LoadTestMesh("C:\\git\\gsOrthoVRApp\\sample_files\\scan_1_raw.obj"); // use iso 0.25 //DMesh3 mesh = TestUtil.LoadTestMesh("C:\\scratch\\irongiant.stl"); //DMesh3 mesh = TestUtil.LoadTestMesh("C:\\Users\\rms\\Dropbox\\meshes\\cars\\beetle.obj"); //DMesh3 mesh = TestUtil.LoadTestMesh("c:\\scratch\\PigHead_rot90.obj"); //DMesh3 mesh = TestUtil.LoadTestMesh("C:\\Projects\\nia_files\\testscan2.obj"); //TestUtil.WriteTestOutputMesh(mesh, "xxx.obj"); AxisAlignedBox3d meshBounds = mesh.CachedBounds; int num_cells = 256; double cell_size = meshBounds.MaxDim / num_cells; float winding_iso = 0.35f; DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); spatial.WindingNumber(Vector3d.Zero); DMeshAABBTreePro spatialPro = new DMeshAABBTreePro(mesh, true); spatialPro.FastWindingNumber(Vector3d.Zero); Func <Vector3d, double> exactWN = (q) => { return(spatial.WindingNumber(q)); }; Func <Vector3d, double> PerFacePtWN = (q) => { return(eval_point_wn(mesh, q)); }; Func <Vector3d, double> fastWN = (q) => { return(spatialPro.FastWindingNumber(q)); }; //MeshScalarSamplingGrid mwnGrid = new MeshScalarSamplingGrid(mesh, cell_size, exactWN); //MeshScalarSamplingGrid mwnGrid = new MeshScalarSamplingGrid(mesh, cell_size, PerFacePtWN ); MeshScalarSamplingGrid mwnGrid = new MeshScalarSamplingGrid(mesh, cell_size, fastWN); mwnGrid.IsoValue = winding_iso; mwnGrid.DebugPrint = true; LocalProfiler p = new LocalProfiler(); p.Start("grid"); mwnGrid.Compute(); p.Stop("grid"); System.Console.WriteLine(p.AllTimes()); MarchingCubes c = new MarchingCubes(); c.Implicit = new SampledGridImplicit(mwnGrid); c.Bounds = mesh.CachedBounds; c.CubeSize = c.Bounds.MaxDim / 128; //c.Bounds = mesh.CachedBounds; c.Bounds.Expand(c.CubeSize * 3); //c.CubeSize = cell_size * 0.5; //c.IsoValue = mwnGrid.WindingIsoValue; c.Generate(); // reproject foreach (int vid in c.Mesh.VertexIndices()) { Vector3d v = c.Mesh.GetVertex(vid); int tid = spatial.FindNearestTriangle(v, cell_size * MathUtil.SqrtTwo); if (tid != DMesh3.InvalidID) { var query = MeshQueries.TriangleDistance(mesh, tid, v); if (v.Distance(query.TriangleClosest) < cell_size * 1.5) { c.Mesh.SetVertex(vid, query.TriangleClosest); } } } //MeshNormals.QuickCompute(c.Mesh); TestUtil.WriteTestOutputMesh(c.Mesh, "mwn_implicit.obj"); }