public static void test_remesh_constraints_fixedverts() { int Slices = 128; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_fixed_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.Precompute(); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); var stopwatch = Stopwatch.StartNew(); //double fResScale = 1.0f; double fResScale = 0.5f; r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResScale; r.MaxEdgeLength = 0.2f * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5f; try { for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } } catch { // ignore } stopwatch.Stop(); System.Console.WriteLine("Second Pass Timing: " + stopwatch.Elapsed); if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_fixed_constraints_test_after.obj"); } }
public bool Apply() { // do a simple fill SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop); int fill_gid = Mesh.AllocateTriangleGroup(); bool bOK = simplefill.Fill(fill_gid); if (bOK == false) { return(false); } if (FillLoop.Vertices.Length <= 3) { FillTriangles = simplefill.NewTriangles; FillVertices = new int[0]; return(true); } // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later HashSet <int> intial_fill_tris = new HashSet <int>(simplefill.NewTriangles); regionop = new RegionOperator(Mesh, simplefill.NewTriangles, (submesh) => { submesh.ComputeTriMaps = true; }); fillmesh = regionop.Region.SubMesh; // for each boundary vertex, compute the exterior angle sum // we will use this to compute gaussian curvature later boundaryv = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(fillmesh)); exterior_angle_sums = new Dictionary <int, double>(); if (IgnoreBoundaryTriangles == false) { foreach (int sub_vid in boundaryv) { double angle_sum = 0; int base_vid = regionop.Region.MapVertexToBaseMesh(sub_vid); foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid)) { if (intial_fill_tris.Contains(tid) == false) { Index3i et = regionop.BaseMesh.GetTriangle(tid); int idx = IndexUtil.find_tri_index(base_vid, ref et); angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx); } } exterior_angle_sums[sub_vid] = angle_sum; } } // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge; MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge); MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge); double remesh_target_len = loop_avge; if (fill_maxe / remesh_target_len > 10) { remesh_target_len = fill_maxe / 10; } //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4); // remesh up to target edge length, ideally gives us some triangles to work with RemesherPro remesh1 = new RemesherPro(fillmesh); remesh1.SmoothSpeedT = 1.0; MeshConstraintUtil.FixAllBoundaryEdges(remesh1); //remesh1.SetTargetEdgeLength(remesh_target_len / 2); // would this speed things up? on large regions? //remesh1.FastestRemesh(); remesh1.SetTargetEdgeLength(remesh_target_len); remesh1.FastestRemesh(); /* * first round: collapse to minimal mesh, while flipping to try to * get to ballpark minimal mesh. We stop these passes as soon as * we have done two rounds where we couldn't do another collapse * * This is the most unstable part of the algorithm because there * are strong ordering effects. maybe we could sort the edges somehow?? */ int zero_collapse_passes = 0; int collapse_passes = 0; while (collapse_passes++ < 20 && zero_collapse_passes < 2) { // collapse pass int NE = fillmesh.MaxEdgeID; int collapses = 0; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); bool a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b); if (a_bdry && b_bdry) { continue; } int keepv = (a_bdry) ? ev.a : ev.b; int otherv = (keepv == ev.a) ? ev.b : ev.a; Vector3d newv = fillmesh.GetVertex(keepv); if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv)) { continue; } DMesh3.EdgeCollapseInfo info; MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info); if (result == MeshResult.Ok) { collapses++; } } if (collapses == 0) { zero_collapse_passes++; } else { zero_collapse_passes = 0; } // flip pass. we flip in these cases: // 1) if angle between current triangles is too small (slightly more than 90 degrees, currently) // 2) if angle between flipped triangles is smaller than between current triangles // 3) if flipped edge length is shorter *and* such a flip won't flip the normal NE = fillmesh.MaxEdgeID; Vector3d n1, n2, on1, on2; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip == false) { Index2i otherv = fillmesh.GetEdgeOpposingV(ei); double len_e = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b)); double len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b)); if (len_flip < len_e) { if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false) { do_flip = true; } } } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); } } } // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?) // Try to force removal of such vertices, even if it makes ugly mesh remove_remaining_interior_verts(); // enable/disable passes. bool DO_FLATTER_PASS = true; bool DO_CURVATURE_PASS = OptimizeDevelopability && true; bool DO_AREA_PASS = OptimizeDevelopability && OptimizeTriangles && true; /* * In this pass we repeat the flipping iterations from the previous pass. * * Note that because of the always-flip-if-dot-is-small case (commented), * this pass will frequently not converge, as some number of edges will * be able to flip back and forth (because neither has large enough dot). * This is not ideal, but also, if we remove this behavior, then we * generally get worse fills. This case basically introduces a sort of * randomization factor that lets us escape local minima... * */ HashSet <int> remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); HashSet <int> updated_edges = new HashSet <int>(); int flatter_passes = 0; int zero_flips_passes = 0; while (flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS) { zero_flips_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); Vector3d n1, n2, on1, on2; MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (flatter_passes < 20 && dot_cur < 0.1) // this check causes oscillatory behavior { do_flip = true; } if (dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result == MeshResult.Ok) { zero_flips_passes = 0; add_all_edges(ei, updated_edges); } } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } int curvature_passes = 0; if (DO_CURVATURE_PASS) { curvatures = new double[fillmesh.MaxVertexID]; foreach (int vid in fillmesh.VertexIndices()) { update_curvature(vid); } remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); /* * In this pass we try to minimize gaussian curvature at all the vertices. * This will recover sharp edges, etc, and do lots of good stuff. * However, this pass will not make much progress if we are not already * relatively close to a minimal mesh, so it really relies on the previous * passes getting us in the ballpark. */ while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS) { foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); if (total_curv_cur < MathUtil.ZeroTolerancef) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } //System.Console.WriteLine("collapse {0} flatter {1} curvature {2}", collapse_passes, flatter_passes, curvature_passes); /* * In this final pass, we try to improve triangle quality. We flip if * the flipped triangles have better total aspect ratio, and the * curvature doesn't change **too** much. The .DevelopabilityTolerance * parameter determines what is "too much" curvature change. */ if (DO_AREA_PASS) { remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); int area_passes = 0; while (remaining_edges.Count() > 0 && area_passes < 20) { area_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); double a = aspect_metric(ei); if (a > 1) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } regionop.BackPropropagate(); FillTriangles = regionop.CurrentBaseTriangles; FillVertices = regionop.CurrentBaseInteriorVertices().ToArray(); return(true); }
double get_tri_area(DMesh3 mesh, ref Index3i tri) { return(MathUtil.Area(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
public void fpMeshPoints() { List <Vector3d> points = new List <Vector3d>(); List <int> tris = new List <int>(); List <Vector3d> normals = new List <Vector3d>(); float normal = .01f; //string toPull = ReadString(); //string[] linesInFile = toPull.Split('\n'); foreach (var pointCloud in ARpoints.trackables) { //Debug.Log ("Lit" +pointCloud.transform.localPosition); int counter = 0; var visualize = GameObject.FindWithTag("allPoints").GetComponent <UnityEngine.XR.ARFoundation.ARAllPointCloudPointsParticleVisualizer>(); foreach (var kvp in visualize.m_Points) { counter++; points.Add(kvp.Value); tris.Add(counter); tris.Add(counter); tris.Add(counter); normals.Add(new Vector3d(normal, normal, normal)); } } DMesh3 pointSet = DMesh3Builder.Build(points, tris, normals); PointAABBTree3 bvtree = new PointAABBTree3(pointSet, true); bvtree.FastWindingNumber(Vector3d.Zero); // estimate point area based on nearest-neighbour distance double[] areas = new double[pointSet.MaxVertexID]; foreach (int vid in pointSet.VertexIndices()) { bvtree.PointFilterF = (i) => { return(i != vid); }; // otherwise vid is nearest to vid! int near_vid = bvtree.FindNearestPoint(pointSet.GetVertex(vid)); double dist = pointSet.GetVertex(vid).Distance(pointSet.GetVertex(near_vid)); areas[vid] = Circle2d.RadiusArea(dist); } bvtree.FWNAreaEstimateF = (vid) => { return(areas[vid]); }; MarchingCubes mc = new MarchingCubes(); mc.Implicit = new PWNImplicit() { Spatial = bvtree }; mc.IsoValue = 0.0; mc.CubeSize = bvtree.Bounds.MaxDim / 10; mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); mc.RootMode = MarchingCubes.RootfindingModes.Bisection; mc.Generate(); DMesh3 resultMesh = mc.Mesh; g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); /* MarchingCubes mc = new MarchingCubes(); * mc.Implicit = new PWNImplicit() { Spatial = bvtree }; * mc.IsoValue = 0.0; * mc.CubeSize = bvtree.Bounds.MaxDim / 10; * mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); * mc.RootMode = MarchingCubes.RootfindingModes.Bisection; * mc.Generate(); * DMesh3 resultMesh = mc.Mesh; * g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); */ }
public static UnityMesh ToUnityMesh(this DMesh3 dMesh, bool bNorm = true, bool bUV = false, bool bCol = false) { bNorm &= dMesh.HasVertexNormals; bUV &= dMesh.HasVertexUVs; bCol &= dMesh.HasVertexColors; int[] vertexMap = new int[dMesh.VerticesBuffer.Length]; int[] triangleMap = new int[dMesh.TrianglesBuffer.Length]; int[] triangles = new int[dMesh.TriangleCount * 3]; List <Vector3d> vertices = new List <Vector3d>(); List <Vector3f> normals = new List <Vector3f>(); List <Vector2f> uv = new List <Vector2f>(); List <Colorf> colors = new List <Colorf>(); List <int> vertexUseCount = new List <int>(); NewVertexInfo vInfo = new NewVertexInfo(new Vector3d(), new Vector3f(), new Vector3f(), new Vector2f()); IEnumerator e = dMesh.TrianglesRefCounts.GetEnumerator(); int ti = 0; while (e.MoveNext()) { int iRef = (int)e.Current; Index3i triangle = dMesh.GetTriangle(iRef); triangleMap[iRef] = ti; for (int i = 0; i < 3; i++) { int vertIndex = triangle[i]; if (vertexMap[vertIndex] == 0) { vertexUseCount.Add(1); dMesh.GetVertex(vertIndex, ref vInfo, bNorm, bCol, bUV); vertices.Add(new Vector3f((float)vInfo.v.x, (float)vInfo.v.y, (float)vInfo.v.z)); vertexMap[vertIndex] = vertices.Count - 1; if (bNorm) { normals.Add(vInfo.n); } if (bUV) { uv.Add(vInfo.uv); } if (bCol) { colors.Add(new Colorf(vInfo.c.x, vInfo.c.y, vInfo.c.z)); } } else { vertexUseCount[vertexMap[vertIndex]]++; } triangles[ti * 3 + i] = vertexMap[vertIndex]; } ti++; } UnityMesh uMesh = new UnityMesh(vertexUseCount.ToArray(), triangles, vertices, normals, uv, colors); // Triangle normals and neighbors. e = dMesh.TrianglesRefCounts.GetEnumerator(); while (e.MoveNext()) { int iRef = (int)e.Current; int[] nb = dMesh.GetTriNeighbourTris(iRef).array; int[] neighbors = new int[3]; for (int i = 0; i < 3; i++) { neighbors[i] = (nb[i] != -1) ? triangleMap[nb[i]] : -1; } uMesh.AddTriangleInfo(triangleMap[iRef], (Vector3f)dMesh.GetTriNormal(iRef), neighbors); } return(uMesh); }
public static void performance_grinder() { LocalProfiler p = new LocalProfiler(); DateTime start = DateTime.Now; //p.Start("Meshgen"); //for (int k = 0; k < 100; ++k) { // Sphere3Generator_NormalizedCube tmpgen = new Sphere3Generator_NormalizedCube(); // tmpgen.EdgeVertices = 100; // tmpgen.Generate(); // DMesh3 tmp = tmpgen.MakeDMesh(); //} //p.StopAndAccumulate("Meshgen"); //System.Console.WriteLine("done meshgen"); Sphere3Generator_NormalizedCube meshgen = new Sphere3Generator_NormalizedCube() { EdgeVertices = 100 }; meshgen.Generate(); DMesh3 sphereMesh = meshgen.MakeDMesh(); //p.Start("Spatial"); //for (int k = 0; k < 100; ++k) { // DMeshAABBTree3 tmpspatial = new DMeshAABBTree3(sphereMesh); // tmpspatial.Build(); //} //p.StopAndAccumulate("Spatial"); //System.Console.WriteLine("done spatial"); meshgen.EdgeVertices = 5; meshgen.Generate(); sphereMesh = meshgen.MakeDMesh(); double remesh_len = (2.0 / 5.0) * 0.025; // takes ~220s //double remesh_len = (2.0 / 5.0) * 0.05; long max_mem = 0; Remesher remesher = new Remesher(sphereMesh); for (int k = 0; k < 10; ++k) { System.Console.WriteLine("{0}", k); p.Start("Remesh"); remesher.SetTargetEdgeLength(remesh_len); remesher.SmoothSpeedT = 0.5f; for (int j = 0; j < 20; ++j) { remesher.BasicRemeshPass(); foreach (int vid in sphereMesh.VertexIndices()) { Vector3d v = sphereMesh.GetVertex(vid); v.Normalize(); sphereMesh.SetVertex(vid, v); } } p.StopAndAccumulate("Remesh"); //System.Console.WriteLine(sphereMesh.MeshInfoString()); System.Console.WriteLine(" {0}", k); p.Start("Reduce"); remesher.SetTargetEdgeLength(remesh_len * 10); for (int j = 0; j < 20; ++j) { remesher.BasicRemeshPass(); foreach (int vid in sphereMesh.VertexIndices()) { Vector3d v = sphereMesh.GetVertex(vid); v.Normalize(); sphereMesh.SetVertex(vid, v); } } p.StopAndAccumulate("Reduce"); } DateTime end = DateTime.Now; System.Console.WriteLine("done remesh"); System.Console.WriteLine("Time {0} MaxMem {1}", (end - start).TotalSeconds, max_mem / (1024 * 1024)); System.Console.WriteLine(p.AllAccumulatedTimes("Accumulated: ")); }
public void fpMeshPointsfromTextFileWithaSecondPoints() { //this is used in the fast points winding scene to optimize algorithm and make sure its working well //it reads points via text files and then meshes them List <Vector3d> points = new List <Vector3d>(); List <int> tris = new List <int>(); List <Vector3d> normals = new List <Vector3d>(); float normal = .01f; string toPull = ReadString(); string[] linesInFile = toPull.Split('\n'); int counter = 0; foreach (string line in linesInFile) { counter++; //Debug.Log(line); if (!string.IsNullOrEmpty(line)) { points.Add(StringToVector3(line)); tris.Add(counter); tris.Add(counter); tris.Add(counter); normals.Add(new Vector3d(normal, normal, normal)); } } DMesh3 pointSet = DMesh3Builder.Build(points, tris, normals); PointAABBTree3 bvtree = new PointAABBTree3(pointSet, true); bvtree.FastWindingNumber(Vector3d.Zero); // estimate point area based on nearest-neighbour distance double[] areas = new double[pointSet.MaxVertexID]; foreach (int vid in pointSet.VertexIndices()) { bvtree.PointFilterF = (i) => { return(i != vid); }; // otherwise vid is nearest to vid! int near_vid = bvtree.FindNearestPoint(pointSet.GetVertex(vid)); double dist = pointSet.GetVertex(vid).Distance(pointSet.GetVertex(near_vid)); areas[vid] = Circle2d.RadiusArea(dist); } bvtree.FWNAreaEstimateF = (vid) => { return(areas[vid]); }; MarchingCubes mc = new MarchingCubes(); mc.Implicit = new PWNImplicit() { Spatial = bvtree }; mc.IsoValue = 0.0; mc.CubeSize = bvtree.Bounds.MaxDim / 50; mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); mc.RootMode = MarchingCubes.RootfindingModes.Bisection; mc.Generate(); DMesh3 resultMesh = mc.Mesh; g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); /* MarchingCubes mc = new MarchingCubes(); * mc.Implicit = new PWNImplicit() { Spatial = bvtree }; * mc.IsoValue = 0.0; * mc.CubeSize = bvtree.Bounds.MaxDim / 10; * mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); * mc.RootMode = MarchingCubes.RootfindingModes.Bisection; * mc.Generate(); * DMesh3 resultMesh = mc.Mesh; * g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); */ //ok now that we meshed the first point set, lets try to take in a second frame of points and then add to orginal dmesh points = new List <Vector3d>(); tris = new List <int>(); normals = new List <Vector3d>(); toPull = ReadString1(); linesInFile = toPull.Split('\n'); counter = 0; foreach (string line in linesInFile) { counter++; Debug.Log(line); if (!string.IsNullOrEmpty(line)) { points.Add(StringToVector3(line)); tris.Add(counter); tris.Add(counter); tris.Add(counter); normals.Add(new Vector3d(normal, normal, normal)); } } pointSet = DMesh3Builder.Build(points, tris, normals); bvtree = new PointAABBTree3(pointSet, true); bvtree.FastWindingNumber(Vector3d.Zero); // estimate point area based on nearest-neighbour distance areas = new double[pointSet.MaxVertexID]; foreach (int vid in pointSet.VertexIndices()) { bvtree.PointFilterF = (i) => { return(i != vid); }; // otherwise vid is nearest to vid! int near_vid = bvtree.FindNearestPoint(pointSet.GetVertex(vid)); double dist = pointSet.GetVertex(vid).Distance(pointSet.GetVertex(near_vid)); areas[vid] = Circle2d.RadiusArea(dist); } bvtree.FWNAreaEstimateF = (vid) => { return(areas[vid]); }; mc = new MarchingCubes(); mc.Implicit = new PWNImplicit() { Spatial = bvtree }; mc.IsoValue = 0.0; mc.CubeSize = bvtree.Bounds.MaxDim / 50; mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); mc.RootMode = MarchingCubes.RootfindingModes.Bisection; mc.Generate(); DMesh3 resultMesh1 = mc.Mesh; MeshEditor editor = new MeshEditor(resultMesh); editor.AppendMesh(resultMesh1, resultMesh.AllocateTriangleGroup()); g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh1); // g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); }
DMesh3 ComputeInflation(DMesh3 planarMesh) { DMesh3 mesh = new DMesh3(planarMesh); DijkstraGraphDistance dist = new DijkstraGraphDistance( mesh.MaxVertexID, false, (vid) => { return(mesh.IsVertex(vid)); }, (a, b) => { return((float)mesh.GetVertex(a).Distance(mesh.GetVertex(b))); }, mesh.VtxVerticesItr); foreach (int vid in MeshIterators.BoundaryVertices(mesh)) { dist.AddSeed(vid, 0); } dist.Compute(); float max_dist = dist.MaxDistance; float[] distances = new float[mesh.MaxVertexID]; foreach (int vid in mesh.VertexIndices()) { distances[vid] = dist.GetDistance(vid); } List <int> local_maxima = new List <int>(); foreach (int vid in MeshIterators.InteriorVertices(mesh)) { float d = distances[vid]; bool is_maxima = true; foreach (int nbrid in mesh.VtxVerticesItr(vid)) { if (distances[nbrid] > d) { is_maxima = false; } } if (is_maxima) { local_maxima.Add(vid); } } // smooth distances (really should use cotan here!!) float smooth_alpha = 0.1f; int smooth_rounds = 5; foreach (int ri in Interval1i.Range(smooth_rounds)) { foreach (int vid in mesh.VertexIndices()) { float cur = distances[vid]; float centroid = 0; int nbr_count = 0; foreach (int nbrid in mesh.VtxVerticesItr(vid)) { centroid += distances[nbrid]; nbr_count++; } centroid /= nbr_count; distances[vid] = (1 - smooth_alpha) * cur + (smooth_alpha) * centroid; } } Vector3d normal = Vector3d.AxisZ; foreach (int vid in mesh.VertexIndices()) { if (dist.IsSeed(vid)) { continue; } float h = distances[vid]; // [RMS] there are different options here... h = 2 * (float)Math.Sqrt(h); float offset = h; Vector3d d = mesh.GetVertex(vid); mesh.SetVertex(vid, d + (Vector3d)(offset * normal)); } DMesh3 compacted = new DMesh3(); var compactInfo = compacted.CompactCopy(mesh); IndexUtil.Apply(local_maxima, compactInfo.MapV); mesh = compacted; MeshVertexSelection boundary_sel = new MeshVertexSelection(mesh); HashSet <int> boundaryV = new HashSet <int>(MeshIterators.BoundaryVertices(mesh)); boundary_sel.Select(boundaryV); boundary_sel.ExpandToOneRingNeighbours(); LaplacianMeshSmoother smoother = new LaplacianMeshSmoother(mesh); foreach (int vid in boundary_sel) { if (boundaryV.Contains(vid)) { smoother.SetConstraint(vid, mesh.GetVertex(vid), 100.0f, true); } else { smoother.SetConstraint(vid, mesh.GetVertex(vid), 10.0f, false); } } foreach (int vid in local_maxima) { smoother.SetConstraint(vid, mesh.GetVertex(vid), 50, false); } bool ok = smoother.SolveAndUpdateMesh(); Util.gDevAssert(ok); List <int> intVerts = new List <int>(MeshIterators.InteriorVertices(mesh)); MeshIterativeSmooth smooth = new MeshIterativeSmooth(mesh, intVerts.ToArray(), true); smooth.SmoothType = MeshIterativeSmooth.SmoothTypes.Cotan; smooth.Rounds = 10; smooth.Alpha = 0.1f; smooth.Smooth(); return(mesh); }
public static void test_arrangement_demo() { DMesh3 mesh = TestUtil.LoadTestInputMesh("spheres_and_planes.obj"); MeshTransforms.Scale(mesh, 8); AxisAlignedBox3d meshBounds = mesh.CachedBounds; Vector3d origin = meshBounds.Center; double simplify_thresh = 5.0; Frame3f plane = new Frame3f(origin, Vector3d.AxisY); MeshPlaneCut cut = new MeshPlaneCut(mesh, plane.Origin, plane.Z); cut.Cut(); Arrangement2d builder = new Arrangement2d(new AxisAlignedBox2d(1024.0)); // insert all cut edges HashSet <Vector2d> srcpts = new HashSet <Vector2d>(); foreach (EdgeLoop loop in cut.CutLoops) { Polygon2d poly = new Polygon2d(); foreach (int vid in loop.Vertices) { poly.AppendVertex(mesh.GetVertex(vid).xz); } poly.Simplify(simplify_thresh, 0.01, true); foreach (Vector2d v in poly.Vertices) { srcpts.Add(v); } builder.Insert(poly); } foreach (EdgeSpan span in cut.CutSpans) { PolyLine2d pline = new PolyLine2d(); foreach (int vid in span.Vertices) { pline.AppendVertex(mesh.GetVertex(vid).xz); } pline.Simplify(simplify_thresh, 0.01, true); foreach (Vector2d v in pline) { srcpts.Add(v); } builder.Insert(pline); } SVGWriter svg = new SVGWriter(); svg.AddGraph(builder.Graph); var vtx_style = SVGWriter.Style.Outline("red", 1.0f); foreach (int vid in builder.Graph.VertexIndices()) { Vector2d v = builder.Graph.GetVertex(vid); if (srcpts.Contains(v) == false) { svg.AddCircle(new Circle2d(v, 2), vtx_style); } } svg.Write(TestUtil.GetTestOutputPath("arrangement.svg")); }
public override void Selected(SelectionType button) { nullifyHitPos = true; transform.parent.SendMessage("Selected", button, SendMessageOptions.DontRequireReceiver); if (button == SelectionType.SELECTALL) { BlockMove = true; } else { MeshFilter mf = GetComponent <MeshFilter>(); Mesh mesh = mf.sharedMesh; currentHit = transform.InverseTransformPoint(AppState.instance.lastHitPosition); Vector3d target = currentHit; System.Random rand = new System.Random(); int count = 0; // // The algorithm will find local optima - repeat until you get the tru optima // but limit the interation using a count // Int32 current = 0; while (count < dmesh.VertexCount) { count++; // // choose a random starting point // current = rand.Next(0, dmesh.VertexCount); Vector3d vtx = dmesh.GetVertex(current); double currentDist = vtx.DistanceSquared(target); // // find the ring of triangles around the current point // int iter = 0; while (true) { iter++; // throw new InvalidOperationException("Meh One Ring operation invalid : " + res.ToString()); // // Interate through the vertices in the one Ring and find the clost to the target point // bool flag = false; foreach (Int32 v in dmesh.VtxVerticesItr(current)) { Vector3d thisVtx = dmesh.GetVertex(v); double thisDist = thisVtx.DistanceSquared(target); if (thisDist < currentDist) { flag = true; current = v; vtx = thisVtx; currentDist = thisDist; } } // // if the current point as closest - then have a local optima // if (!flag) { break; } } // // we now have a local optima // to check for a global optima look to see if hit is contained by one of the triangles in the one ring // bool f2 = false; foreach (Int32 t in dmesh.VtxTrianglesItr(current)) { Index3i tri = dmesh.GetTriangle(t); Triangle3d triangle = new Triangle3d( dmesh.GetVertex(tri.a), dmesh.GetVertex(tri.b), dmesh.GetVertex(tri.c) ); double[] xs = new double[3] { triangle.V0.x, triangle.V1.x, triangle.V2.x }; double[] ys = new double[3] { triangle.V0.y, triangle.V1.y, triangle.V2.y }; double[] zs = new double[3] { triangle.V0.z, triangle.V1.z, triangle.V2.z }; if ( target.x >= xs.Min() && target.x <= xs.Max() && target.y >= ys.Min() && target.y <= ys.Max() && target.z >= zs.Min() && target.z <= zs.Max() ) { f2 = true; currentHitTri = tri; } } // // if we found on triamgle that contain the current hit then we have finished // if (f2) { break; } } if (count >= dmesh.VertexCount) { // // This is the unoptimized verion but it is guaranteed to find a solution if one exits // current = -1; float currentDist = float.MaxValue; if (currentHit != null) { for (int i = 0; i < mesh.vertices.Length; i++) { Vector3 vtx = mesh.vertices[i]; float dist = (currentHit - vtx).sqrMagnitude; if (dist < currentDist) { current = i; currentDist = dist; } } } // // Check that the closet vertex to the point is an actual solution // bool f2 = false; foreach (Int32 t in dmesh.VtxTrianglesItr(current)) { Index3i tri = dmesh.GetTriangle(t); Triangle3d triangle = new Triangle3d( dmesh.GetVertex(tri.a), dmesh.GetVertex(tri.b), dmesh.GetVertex(tri.c) ); double[] xs = new double[3] { triangle.V0.x, triangle.V1.x, triangle.V2.x }; double[] ys = new double[3] { triangle.V0.y, triangle.V1.y, triangle.V2.y }; double[] zs = new double[3] { triangle.V0.z, triangle.V1.z, triangle.V2.z }; if ( target.x >= xs.Min() && target.x <= xs.Max() && target.y >= ys.Min() && target.y <= ys.Max() && target.z >= zs.Min() && target.z <= zs.Max() ) { f2 = true; currentHitTri = tri; } } // // if we found on triamgle that contain the current hit then we have finished // if (!f2) { Debug.LogError(" Mesh Vertex Search : No Solution Found"); return; } } selectedVertex = current; sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); sphere.transform.position = transform.TransformPoint(mesh.vertices[current]); sphere.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); sphere.transform.parent = transform; } }
protected void do_flatten(DMesh3 mesh) { double BAND_HEIGHT = flatten_band_height; Vector3d down_axis = -Vector3d.AxisY; double dot_thresh = 0.2; AxisAlignedBox3d bounds = mesh.CachedBounds; DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); Ray3d ray = new Ray3d(bounds.Center - 2 * bounds.Height * Vector3d.AxisY, Vector3d.AxisY); int hit_tid = spatial.FindNearestHitTriangle(ray); Frame3f hitF; MeshQueries.RayHitPointFrame(mesh, spatial, ray, out hitF); Vector3d basePt = hitF.Origin; Frame3f basePlane = new Frame3f(basePt, Vector3f.AxisY); MeshConnectedComponents components = new MeshConnectedComponents(mesh) { FilterF = (tid) => { if (mesh.GetTriangleGroup(tid) != LastExtrudeOuterGroupID) { return(false); } Vector3d n, c; double a; mesh.GetTriInfo(tid, out n, out a, out c); double h = Math.Abs(c.y - basePt.y); if (h > BAND_HEIGHT) { return(false); } if (n.Dot(down_axis) < dot_thresh) { return(false); } return(true); }, SeedFilterF = (tid) => { return(tid == hit_tid); } }; components.FindConnectedT(); MeshFaceSelection all_faces = new MeshFaceSelection(mesh); foreach (var comp in components) { MeshVertexSelection vertices = new MeshVertexSelection(mesh); vertices.SelectTriangleVertices(comp.Indices); foreach (int vid in vertices) { Vector3d v = mesh.GetVertex(vid); v = basePlane.ProjectToPlane((Vector3f)v, 2); mesh.SetVertex(vid, v); } all_faces.SelectVertexOneRings(vertices); } all_faces.ExpandToOneRingNeighbours(3, (tid) => { return(mesh.GetTriangleGroup(tid) == LastExtrudeOuterGroupID); }); RegionRemesher r = new RegionRemesher(mesh, all_faces); r.SetProjectionTarget(MeshProjectionTarget.Auto(mesh)); r.SetTargetEdgeLength(2.0f); r.SmoothSpeedT = 1.0f; for (int k = 0; k < 10; ++k) { r.BasicRemeshPass(); } r.SetProjectionTarget(null); r.SmoothSpeedT = 1.0f; for (int k = 0; k < 10; ++k) { r.BasicRemeshPass(); } r.BackPropropagate(); }
public override void MoveTo(MoveArgs args) { if (BlockMove) { if (args.translate != Vector3.zero) { transform.Translate(args.translate, Space.World); transform.parent.SendMessage("Translate", args); } } else { if (args.translate != Vector3.zero) { MeshFilter mf = GetComponent <MeshFilter>(); Mesh mesh = mf.sharedMesh; Vector3 localTranslate = transform.InverseTransformVector(args.translate); Vector3d target = mesh.vertices[selectedVertex] + localTranslate; if (AppState.instance.editScale > 2) { if (n != AppState.instance.editScale) { n = AppState.instance.editScale; // // first we need to find the n-ring of vertices // // first get 1-ring nRing = new List <int>(); List <Int32> inside = new List <int>(); nRing.Add(selectedVertex); for (int i = 0; i < n; i++) { int[] working = nRing.ToArray(); nRing.Clear(); foreach (int v in working) { if (!inside.Contains(v)) { foreach (int vring in dmesh.VtxVerticesItr(v)) { if (!inside.Contains(vring)) { nRing.Add(vring); } } } inside.Add(v); } } } // // create the deformer // set the constraint that the selected vertex is moved to position // set the contraint that the n-ring remains stationary // LaplacianMeshDeformer deform = new LaplacianMeshDeformer(dmesh); deform.SetConstraint(selectedVertex, target, 1, false); foreach (int v in nRing) { deform.SetConstraint(v, dmesh.GetVertex(v), 10, false); } deform.SolveAndUpdateMesh(); newDmesh = deform.Mesh; } else { dmesh.SetVertex(selectedVertex, target); } // // reset the Unity meh // if (sphere != null) { sphere.transform.localPosition = (Vector3)dmesh.GetVertex(selectedVertex); } List <Vector3> vtxs = new List <Vector3>(); foreach (int v in newDmesh.VertexIndices()) { vtxs.Add((Vector3)newDmesh.GetVertex(v)); } mesh.vertices = vtxs.ToArray(); mesh.RecalculateBounds(); mesh.RecalculateNormals(); } } }
protected virtual DMesh3 compute_bounded_distance() { DMesh3 sourceMesh = MeshSource.GetDMeshUnsafe(); ISpatial inputSpatial = MeshSource.GetSpatial(); DMesh3 targetMesh = TargetSource.GetDMeshUnsafe(); ISpatial targetSpatial = TargetSource.GetSpatial(); double max_dist = (TargetMaxDistance == double.MaxValue) ? double.MaxValue : TargetMaxDistance; DMesh3 meshIn = new DMesh3(sourceMesh); bool target_closed = targetMesh.IsClosed(); MeshVertexSelection roiV = new MeshVertexSelection(meshIn); SpinLock roi_lock = new SpinLock(); gParallel.ForEach(meshIn.VertexIndices(), (vid) => { Vector3d pos = meshIn.GetVertex(vid); Vector3d posTarget = TransformToTarget.TransformP(pos); double dist = MeshQueries.NearestPointDistance(targetMesh, targetSpatial, posTarget, max_dist); bool inside = (target_closed && targetSpatial.IsInside(posTarget)); if (dist < max_dist || inside) { bool taken = false; roi_lock.Enter(ref taken); roiV.Select(vid); roi_lock.Exit(); } }); if (is_invalidated()) { return(null); } MeshFaceSelection roi_faces = new MeshFaceSelection(meshIn, roiV, 1); roi_faces.ExpandToOneRingNeighbours(3); roi_faces.LocalOptimize(); if (is_invalidated()) { return(null); } RegionOperator op = new RegionOperator(meshIn, roi_faces); DMesh3 meshROI = op.Region.SubMesh; if (is_invalidated()) { return(null); } RemesherPro remesher = new RemesherPro(meshROI); remesher.SetTargetEdgeLength(TargetEdgeLength); remesher.PreventNormalFlips = this.PreventNormalFlips; remesher.EnableFlips = this.EnableFlips; remesher.EnableSplits = this.EnableSplits; remesher.EnableCollapses = this.EnableCollapses; remesher.EnableSmoothing = this.EnableSmoothing; remesher.SmoothSpeedT = this.SmoothingSpeed; BoundedProjectionTarget target = new BoundedProjectionTarget() { Source = sourceMesh, SourceSpatial = inputSpatial, Target = targetMesh, TargetSpatial = targetSpatial, SourceToTargetXForm = source_to_target, TargetToSourceXForm = target_to_source, MaxDistance = max_dist, Smoothness = transition_smoothness }; remesher.SetProjectionTarget(target); if (remesher.Constraints == null) { remesher.SetExternalConstraints(new MeshConstraints()); } MeshConstraintUtil.FixAllBoundaryEdges(remesher.Constraints, meshROI); if (is_invalidated()) { return(null); } remesher.Progress = new ProgressCancel(is_invalidated); remesher.FastestRemesh(RemeshRounds); if (is_invalidated()) { return(null); } op.BackPropropagate(); return(meshIn); }
public static void test_remesh_constraints_vertcurves() { int Slices = 16; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); //DMesh3 mesh = TestUtil.MakeRemeshedCappedCylinder(0.25); //DMesh3 mesh = TestUtil.MakeRemeshedCappedCylinder(1.0); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget mesh_target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // cylinder projection target CylinderProjectionTarget cyl_target = new CylinderProjectionTarget() { Cylinder = new Cylinder3d(new Vector3d(0, 1, 0), Vector3d.AxisY, 1, 2) }; //IProjectionTarget target = mesh_target; IProjectionTarget target = cyl_target; // construct projection target circles CircleProjectionTarget bottomCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; bottomCons.Circle.Center.y = bounds.Min.y; CircleProjectionTarget topCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; topCons.Circle.Center.y = bounds.Max.y; if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_analytic_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; bool bConstrainVertices = true; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { Index2i ev = mesh.GetEdgeV(eid); Vector3d ev0 = mesh.GetVertex(ev[0]); Vector3d ev1 = mesh.GetVertex(ev[1]); CircleProjectionTarget loopTarget = null; if (ev0.y > bounds.Center.y && ev1.y > bounds.Center.y) { loopTarget = topCons; } else if (ev0.y < bounds.Center.y && ev1.y < bounds.Center.y) { loopTarget = bottomCons; } cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags, loopTarget)); if (bConstrainVertices && loopTarget != null) { cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(loopTarget)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(loopTarget)); } } } Remesher r = new Remesher(mesh); //r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.ENABLE_PROFILING = true; var stopwatch = Stopwatch.StartNew(); //double fResScale = 1.0f; double fResScale = 0.5f; r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResScale; r.MaxEdgeLength = 0.2f * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 1.0f; try { for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } } catch { // continue; } stopwatch.Stop(); System.Console.WriteLine("Second Pass Timing: " + stopwatch.Elapsed); if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_analytic_constraints_test_after.obj"); } }
public virtual ExportStatus Export(FScene scene, string filename) { int[] vertexMap = new int[2048]; // temp List <WriteMesh> vMeshes = new List <WriteMesh>(); if (WriteFaceGroups) { throw new Exception("SceneMeshExporter.Export: writing face groups has not yet been implemented!"); } // extract all the mesh data we want to export foreach (SceneObject so in scene.SceneObjects) { if (so.IsTemporary || so.IsSurface == false || SceneUtil.IsVisible(so) == false) { continue; } if (SOFilterF != null && SOFilterF(so) == false) { continue; } // if this SO has an internal mesh we can just copy, use it if (so is DMeshSO) { DMeshSO meshSO = so as DMeshSO; // todo: flags // make a copy of mesh DMesh3 m = new DMesh3(meshSO.Mesh, true); // transform to scene coords and swap left/right foreach (int vid in m.VertexIndices()) { Vector3f v = (Vector3f)m.GetVertex(vid); v = SceneTransforms.ObjectToScene(meshSO, v); v = UnityUtil.SwapLeftRight(v); m.SetVertex(vid, v); } m.ReverseOrientation(); vMeshes.Add(new WriteMesh(m, so.Name)); } // Look for lower-level fGameObject items to export. By default // this is anything with a MeshFilter, override CollectGOChildren // or use GOFilterF to add restrictions List <fGameObject> vExports = CollectGOChildren(so); if (vExports.Count > 0) { SimpleMesh m = new SimpleMesh(); m.Initialize(WriteNormals, WriteVertexColors, WriteUVs, WriteFaceGroups); int groupCounter = 1; foreach (fGameObject childgo in vExports) { if (GOFilterF != null && GOFilterF(so, childgo) == false) { continue; } if (AppendGOMesh(childgo, m, vertexMap, scene, groupCounter)) { groupCounter++; } } vMeshes.Add(new WriteMesh(m, so.Name)); } } // ok, we are independent of Scene now and can write in bg thread if (WriteInBackgroundThreads) { ExportStatus status = new ExportStatus() { Exporter = this, IsComputing = true }; WriteOptions useOptions = Options; useOptions.ProgressFunc = (cur, max) => { status.Progress = cur; status.MaxProgress = max; }; BackgroundWriteThread t = new BackgroundWriteThread() { Meshes = vMeshes, options = useOptions, Filename = filename, CompletionF = (result) => { LastWriteStatus = result.code; LastErrorMessage = result.message; status.LastErrorMessage = result.message; status.Ok = (result.code == IOCode.Ok); status.IsComputing = false; if (BackgroundWriteCompleteF != null) { BackgroundWriteCompleteF(this, status); } } }; t.Start(); return(status); } else { IOWriteResult result = StandardMeshWriter.WriteFile(filename, vMeshes, Options); LastWriteStatus = result.code; LastErrorMessage = result.message; return(new ExportStatus() { Exporter = this, IsComputing = false, Ok = (result.code == IOCode.Ok), LastErrorMessage = result.message }); } }
public static void quick_test_2() { DMesh3 target = TestUtil.LoadTestInputMesh("cylinder_orig.obj"); DMeshAABBTree3 targetSpatial = new DMeshAABBTree3(target, true); DMesh3 mesh = TestUtil.LoadTestInputMesh("cylinder_approx.obj"); DMeshAABBTree3 meshSpatial = new DMeshAABBTree3(mesh, true); double search_dist = 10.0; MeshTopology topo = new MeshTopology(target); topo.Compute(); RemesherPro r = new RemesherPro(mesh); r.SetTargetEdgeLength(2.0); r.SmoothSpeedT = 0.5; r.SetProjectionTarget(MeshProjectionTarget.Auto(target)); MeshConstraints cons = new MeshConstraints(); r.SetExternalConstraints(cons); int set_id = 1; foreach (var loop in topo.Loops) { DCurveProjectionTarget curveTarget = new DCurveProjectionTarget(loop.ToCurve(target)); set_id++; // pick a set of points we will find paths between. We will chain // up those paths and constrain them to target loops. // (this part is the hack!) List <int> target_verts = new List <int>(); List <int> mesh_verts = new List <int>(); for (int k = 0; k < loop.VertexCount; k += 5) { target_verts.Add(loop.Vertices[k]); Vector3d vCurve = target.GetVertex(loop.Vertices[k]); int mesh_vid = meshSpatial.FindNearestVertex(vCurve, search_dist); mesh_verts.Add(mesh_vid); } int NT = target_verts.Count; // find the paths to assemble the edge chain // [TODO] need to filter out junction vertices? or will they just handle themselves // because they can be collapsed away? List <int> vert_seq = new List <int>(); for (int k = 0; k < NT; k++) { EdgeSpan e = find_edge_path(mesh, mesh_verts[k], mesh_verts[(k + 1) % NT]); int n = e.Vertices.Length; for (int i = 0; i < n - 1; ++i) { vert_seq.Add(e.Vertices[i]); } } // now it's easy, just add the loop constraint EdgeLoop full_loop = EdgeLoop.FromVertices(mesh, vert_seq); MeshConstraintUtil.ConstrainVtxLoopTo(cons, mesh, full_loop.Vertices, curveTarget, set_id); } r.FastestRemesh(); TestUtil.WriteTestOutputMesh(mesh, "curves_test_out.obj"); }
public bool Insert() { Func <int, bool> is_contained_v = (vid) => { Vector3d v = Mesh.GetVertex(vid); Vector2f vf2 = ProjectFrame.ToPlaneUV((Vector3f)v, 2); return(Polygon.Contains(vf2)); }; MeshVertexSelection vertexROI = new MeshVertexSelection(Mesh); Index3i seedT = Mesh.GetTriangle(SeedTriangle); // if a seed vert of seed triangle is containd in polygon, we will // flood-fill out from there, this gives a better ROI. // If not, we will try flood-fill from the seed triangles. List <int> seed_verts = new List <int>(); for (int j = 0; j < 3; ++j) { if (is_contained_v(seedT[j])) { seed_verts.Add(seedT[j]); } } if (seed_verts.Count == 0) { seed_verts.Add(seedT.a); seed_verts.Add(seedT.b); seed_verts.Add(seedT.c); } // flood-fill out from seed vertices until we have found all vertices // contained in polygon vertexROI.FloodFill(seed_verts.ToArray(), is_contained_v); // convert vertex ROI to face ROI MeshFaceSelection faceROI = new MeshFaceSelection(Mesh, vertexROI, 1); faceROI.ExpandToOneRingNeighbours(); faceROI.FillEars(true); // this might be a good idea... // construct submesh RegionOperator regionOp = new RegionOperator(Mesh, faceROI); DSubmesh3 roiSubmesh = regionOp.Region; DMesh3 roiMesh = roiSubmesh.SubMesh; // save 3D positions of unmodified mesh Vector3d[] initialPositions = new Vector3d[roiMesh.MaxVertexID]; // map roi mesh to plane MeshTransforms.PerVertexTransform(roiMesh, roiMesh.VertexIndices(), (v, vid) => { Vector2f uv = ProjectFrame.ToPlaneUV((Vector3f)v, 2); initialPositions[vid] = v; return(new Vector3d(uv.x, uv.y, 0)); }); // save a copy of 2D mesh and construct bvtree. we will use // this later to project back to 3d // [TODO] can we use a better spatial DS here, that takes advantage of 2D? DMesh3 projectMesh = new DMesh3(roiMesh); DMeshAABBTree3 projecter = new DMeshAABBTree3(projectMesh, true); MeshInsertUVPolyCurve insertUV = new MeshInsertUVPolyCurve(roiMesh, Polygon); //insertUV.Validate() bool bOK = insertUV.Apply(); if (!bOK) { throw new Exception("insertUV.Apply() failed"); } if (SimplifyInsertion) { insertUV.Simplify(); } int[] insertedPolyVerts = insertUV.CurveVertices; // grab inserted loop, assuming it worked EdgeLoop insertedLoop = null; if (insertUV.Loops.Count == 1) { insertedLoop = insertUV.Loops[0]; } // find interior triangles List <int> interiorT = new List <int>(); foreach (int tid in roiMesh.TriangleIndices()) { Vector3d centroid = roiMesh.GetTriCentroid(tid); if (Polygon.Contains(centroid.xy)) { interiorT.Add(tid); } } if (RemovePolygonInterior) { MeshEditor editor = new MeshEditor(roiMesh); editor.RemoveTriangles(interiorT, true); InteriorTriangles = null; } else { InteriorTriangles = interiorT.ToArray(); } // map back to 3d Vector3d a = Vector3d.Zero, b = Vector3d.Zero, c = Vector3d.Zero; foreach (int vid in roiMesh.VertexIndices()) { // [TODO] somehow re-use exact positions from regionOp maps? // construct new 3D pos w/ barycentric interpolation Vector3d v = roiMesh.GetVertex(vid); int tid = projecter.FindNearestTriangle(v); Index3i tri = projectMesh.GetTriangle(tid); projectMesh.GetTriVertices(tid, ref a, ref b, ref c); Vector3d bary = MathUtil.BarycentricCoords(ref v, ref a, ref b, ref c); Vector3d pos = bary.x * initialPositions[tri.a] + bary.y * initialPositions[tri.b] + bary.z * initialPositions[tri.c]; roiMesh.SetVertex(vid, pos); } bOK = BackPropagate(regionOp, insertedPolyVerts, insertedLoop); return(bOK); }
void extract_topology() { var graph = new DGraph3(); // add vertices to graph, and store mappings int[] mapV = new int[Mesh.MaxVertexID]; int[] mapVFrom = new int[AllVertices.Count]; foreach (int vid in AllVertices) { int new_vid = graph.AppendVertex(Mesh.GetVertex(vid)); mapV[vid] = new_vid; mapVFrom[new_vid] = vid; } // add edges to graph. graph-to-mesh eid mapping is stored via graph edge-group-id int[] mapE = new int[Mesh.MaxEdgeID]; foreach (int eid in AllEdges) { Index2i ev = Mesh.GetEdgeV(eid); int new_a = mapV[ev.a]; int new_b = mapV[ev.b]; int new_eid = graph.AppendEdge(new_a, new_b, eid); mapE[eid] = new_eid; } // extract the graph topology DGraph3Util.Curves curves = DGraph3Util.ExtractCurves(graph, true); // reconstruct mesh spans / curves / junctions from graph topology int NP = curves.PathEdges.Count; Spans = new EdgeSpan[NP]; for (int pi = 0; pi < NP; ++pi) { List <int> pathE = curves.PathEdges[pi]; for (int k = 0; k < pathE.Count; ++k) { pathE[k] = graph.GetEdgeGroup(pathE[k]); } Spans[pi] = EdgeSpan.FromEdges(Mesh, pathE); } int NL = curves.LoopEdges.Count; Loops = new EdgeLoop[NL]; for (int li = 0; li < NL; ++li) { List <int> loopE = curves.LoopEdges[li]; for (int k = 0; k < loopE.Count; ++k) { loopE[k] = graph.GetEdgeGroup(loopE[k]); } Loops[li] = EdgeLoop.FromEdges(Mesh, loopE); } JunctionVertices = new HashSet <int>(); foreach (int gvid in curves.JunctionV) { JunctionVertices.Add(mapVFrom[gvid]); } }
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 Mesh RemeshTest(Mesh inMesh, double fResScale = 1.0, int iterations = 50) { inMesh.Faces.ConvertQuadsToTriangles(); DMesh3 mesh = inMesh.ToDMesh3(); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.Precompute(); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.5 * fResScale; r.MaxEdgeLength = 1.0 * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5; try { for (int k = 0; k < iterations; ++k) { r.BasicRemeshPass(); // mesh.CheckValidity(); } } catch { // ignore } return(mesh.ToRhinoMesh()); }
public void takePointsinandAddtoMesh(List <Vector3d> pointers) { List <int> tris = new List <int>(); List <Vector3d> normals = new List <Vector3d>(); float normal = .01f; if (currentMesh == null) { //ok so first mesh is not been created yet so create it from the first frame int counter = 0; foreach (Vector3d line in pointers) { counter++; //Debug.Log(line); tris.Add(counter); tris.Add(counter); tris.Add(counter); normals.Add(new Vector3d(normal, normal, normal)); } DMesh3 pointSet = DMesh3Builder.Build(pointers, tris, normals); PointAABBTree3 bvtree = new PointAABBTree3(pointSet, true); bvtree.FastWindingNumber(Vector3d.Zero); // estimate point area based on nearest-neighbour distance double[] areas = new double[pointSet.MaxVertexID]; foreach (int vid in pointSet.VertexIndices()) { bvtree.PointFilterF = (i) => { return(i != vid); }; // otherwise vid is nearest to vid! int near_vid = bvtree.FindNearestPoint(pointSet.GetVertex(vid)); double dist = pointSet.GetVertex(vid).Distance(pointSet.GetVertex(near_vid)); areas[vid] = Circle2d.RadiusArea(dist); } bvtree.FWNAreaEstimateF = (vid) => { return(areas[vid]); }; MarchingCubes mc = new MarchingCubes(); mc.Implicit = new PWNImplicit() { Spatial = bvtree }; mc.IsoValue = 0.0; mc.CubeSize = bvtree.Bounds.MaxDim / 10; mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); mc.RootMode = MarchingCubes.RootfindingModes.Bisection; mc.Generate(); DMesh3 resultMesh = mc.Mesh; // g3UnityUtils.SetGOMesh(transform.gameObject, resultMesh); currentMesh = resultMesh; } else { //ok so this is where we are proscessing second mesh int counter = 0; foreach (Vector3d line in pointers) { counter++; //Debug.Log(line); tris.Add(counter); tris.Add(counter); tris.Add(counter); normals.Add(new Vector3d(normal, normal, normal)); } DMesh3 pointSet = DMesh3Builder.Build(pointers, tris, normals); PointAABBTree3 bvtree = new PointAABBTree3(pointSet, true); bvtree.FastWindingNumber(Vector3d.Zero); // estimate point area based on nearest-neighbour distance double[] areas = new double[pointSet.MaxVertexID]; foreach (int vid in pointSet.VertexIndices()) { bvtree.PointFilterF = (i) => { return(i != vid); }; // otherwise vid is nearest to vid! int near_vid = bvtree.FindNearestPoint(pointSet.GetVertex(vid)); double dist = pointSet.GetVertex(vid).Distance(pointSet.GetVertex(near_vid)); areas[vid] = Circle2d.RadiusArea(dist); } bvtree.FWNAreaEstimateF = (vid) => { return(areas[vid]); }; MarchingCubes mc = new MarchingCubes(); mc.Implicit = new PWNImplicit() { Spatial = bvtree }; mc.IsoValue = 0.0; mc.CubeSize = bvtree.Bounds.MaxDim / 10; mc.Bounds = bvtree.Bounds.Expanded(mc.CubeSize * 3); mc.RootMode = MarchingCubes.RootfindingModes.Bisection; mc.Generate(); DMesh3 resultMesh = mc.Mesh; MeshEditor editor = new MeshEditor(currentMesh); editor.AppendMesh(resultMesh, currentMesh.AllocateTriangleGroup()); //suspected its crashing after mesh is over 64000 faces, faceLog.text = "Vertex Count = " + transform.gameObject.GetComponent <MeshFilter>().mesh.triangles.Length; g3UnityUtils.SetGOMesh(transform.gameObject, currentMesh); } }
// [RMS] This doesn't work because I did something stupid when // making unwrap meshes. The set of triangles aren't the same. argh. public static DMesh3 ConvertUnwrapToUVs(RemoteControl rc, out DenseMeshUVSet UVSet, string mainName = "main") { int mainID = rc.FindObjectByName(mainName); rc.CompactMesh(mainID); DMesh3 mainMesh = ReadMeshFromMM(rc, mainID); Debug.Assert(mainMesh.IsCompact); List <int> all = rc.ListObjects(); List <int> unwrapIDs = new List <int>(); foreach (int id in all) { string sName = rc.GetObjectName(id); if (sName.StartsWith("unwrap", StringComparison.OrdinalIgnoreCase)) { unwrapIDs.Add(id); } } int[] AllGroups = FaceGroupUtil.FindAllGroups(mainMesh).ToArray(); Dictionary <int, DMesh3> Unwraps = new Dictionary <int, DMesh3>(); foreach (int id in unwrapIDs) { DMesh3 mesh = ReadMeshFromMM(rc, id); HashSet <int> subGroups = FaceGroupUtil.FindAllGroups(mesh); Debug.Assert(subGroups.Count == 1); int gid = subGroups.ToArray()[0]; Unwraps[gid] = mesh; } UVSet = new DenseMeshUVSet(); UVSet.TriangleUVs.resize(mainMesh.TriangleCount); Dictionary <Index2i, int> UVSetMap = new Dictionary <Index2i, int>(); Dictionary <int, int> GroupCounters = new Dictionary <int, int>(); for (int i = 0; i < AllGroups.Length; ++i) { GroupCounters[AllGroups[i]] = 0; } for (int ti = 0; ti < mainMesh.TriangleCount; ++ti) { Index3i main_tri = mainMesh.GetTriangle(ti); int gid = mainMesh.GetTriangleGroup(ti); int sub_tid = GroupCounters[gid]; GroupCounters[gid]++; DMesh3 SubMesh = Unwraps[gid]; Index3i sub_tri = SubMesh.GetTriangle(sub_tid); for (int j = 0; j < 3; ++j) { int sub_vid = sub_tri[j]; Index2i key = new Index2i(gid, sub_vid); int uvset_idx; if (UVSetMap.TryGetValue(key, out uvset_idx) == false) { Vector3d v = SubMesh.GetVertex(sub_vid); Vector2f uv = new Vector2f((float)v.x, (float)v.z); uvset_idx = UVSet.AppendUV(uv); UVSetMap[key] = uvset_idx; } sub_tri[j] = uvset_idx; } UVSet.TriangleUVs[ti] = sub_tri; } return(mainMesh); }
/// <summary> /// Cut a "partial" hole, ie we cut the mesh with the polygon once, and then /// extrude downwards to a planar version of the cut boundary. /// /// Currently only supports extruding downwards from topmost intersection. /// /// </summary> protected bool CutPartialHole(DMesh3 mesh, HoleInfo hi, Vector3d translate, bool bUpwards) { if (hi.IsVertical == false) { throw new Exception("unsupported!"); } Vector3d basePoint = CombinedBounds.Center - CombinedBounds.Extents.y * Vector3d.AxisY + translate; // do we need to compute spatial DS for each hole? not super efficient... DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); Vector3d direction = (bUpwards) ? Vector3d.AxisY : -Vector3d.AxisY; Vector3d center = basePoint + new Vector3d(hi.XZOffset.x, 0, hi.XZOffset.y) - 10000 * direction; Ray3d ray = new Ray3d(center, direction); int hit_tid = spatial.FindNearestHitTriangle(ray); if (hit_tid == DMesh3.InvalidID) { return(false); } IntrRay3Triangle3 intersection = MeshQueries.TriangleIntersection(mesh, hit_tid, ray); Vector3d inter_pos = ray.PointAt(intersection.RayParameter); Frame3f projectFrame = new Frame3f(ray.Origin, ray.Direction); int nVerts = 32; if (hi.Vertices != 0) { nVerts = hi.Vertices; } double angleShiftRad = hi.AxisAngleD * MathUtil.Deg2Rad; Polygon2d circle = Polygon2d.MakeCircle(hi.Radius, nVerts, angleShiftRad); try { EdgeLoop loop = null; MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid) { SimplifyInsertion = false }; if (insert.Insert()) { loop = insert.InsertedLoop; // [RMS] do we need to simplify for this one? //if (loop.VertexCount > circle.VertexCount) // loop = simplify_loop(mesh, loop, circle.VertexCount); MeshEditor editor = new MeshEditor(mesh); Vector3d base_pos = inter_pos; base_pos.y = basePoint.y + hi.PartialHoleBaseHeight; int N = loop.VertexCount; int[] newLoop = new int[N]; for (int k = 0; k < N; ++k) { newLoop[k] = mesh.AppendVertex(mesh, loop.Vertices[k]); Vector3d cur_v = mesh.GetVertex(newLoop[k]); cur_v.y = base_pos.y; mesh.SetVertex(newLoop[k], cur_v); } int base_vid = mesh.AppendVertex(base_pos); int[] fan_tris = editor.AddTriangleFan_OrderedVertexLoop(base_vid, newLoop); FaceGroupUtil.SetGroupID(mesh, fan_tris, hi.PartialHoleGroupID); int[] stitch_tris = editor.StitchLoop(loop.Vertices, newLoop); // need to remesh fan region because otherwise we get pathological cases RegionRemesher remesh = new RegionRemesher(mesh, fan_tris); remesh.SetTargetEdgeLength(2.0); remesh.SmoothSpeedT = 1.0; remesh.PreventNormalFlips = true; for (int k = 0; k < 25; ++k) { remesh.BasicRemeshPass(); } //remesh.EnableCollapses = remesh.EnableFlips = remesh.EnableSplits = false; //for (int k = 0; k < 20; ++k) // remesh.BasicRemeshPass(); remesh.BackPropropagate(); return(true); } else { return(false); } } catch (Exception e) { f3.DebugUtil.Log("partial hole {0} failed!! {1}", hi.nHole, e.Message); return(false); } }
public static void test_reduce_constraints_fixedverts() { int Slices = 128; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "reduce_fixed_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.PreserveTopology; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags) { TrackingSetID = 1 }); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Reducer r = new Reducer(mesh); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.ReduceToTriangleCount(50); mesh.CheckValidity(); if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "reduce_fixed_constraints_test_after.obj"); } }
public virtual void Update() { if (MeshSource == null) { throw new Exception("PlaneBandExpansionOp: must set valid MeshSource to compute!"); } IMesh imesh = MeshSource.GetIMesh(); if (imesh.HasVertexNormals == false) { throw new Exception("PlaneBandExpansionOp: input mesh does not have surface normals..."); } if (imesh is DMesh3 == false) { throw new Exception("RegionOffsetOp: in current implementation, input mesh must be a DMesh3. Ugh."); } DMesh3 mesh = imesh as DMesh3; IList <int> faces = IndexSource.GetIndices(); // [RMS] this is all f'n ugly! MeshVertexSelection selection = new MeshVertexSelection(mesh); selection.SelectTriangleVertices(faces); // ugly List <Vector2d> seeds = new List <Vector2d>(); foreach (int vid in selection) { foreach (int nbrvid in mesh.VtxVerticesItr(vid)) { if (selection.IsSelected(nbrvid) == false) { seeds.Add(new Vector2d(vid, 0)); break; } } } Func <int, int, float> distanceF = (a, b) => { return((float)mesh.GetVertex(a).Distance(mesh.GetVertex(b))); }; Func <int, bool> nodeF = (vid) => { return(selection.IsSelected(vid)); }; DijkstraGraphDistance dijkstra = new DijkstraGraphDistance(mesh.MaxVertexID, true, nodeF, distanceF, mesh.VtxVerticesItr, seeds); dijkstra.Compute(); float maxDist = dijkstra.MaxDistance; Displacement.Clear(); Displacement.Resize(mesh.MaxVertexID); // todo: can do this in parallel... foreach (int vid in selection) { //Vector3d v = mesh.GetVertex(vid); // [TODO]... double dist = maxDist - dijkstra.GetDistance(vid); double falloff = MathUtil.WyvillFalloff(dist, maxDist * 0.0, maxDist); Vector3d n = mesh.GetVertexNormal(vid); n = n - n.Dot(normal) * normal; n.Normalize(); Displacement[vid] = falloff * offset_distance * n; } // smooth it? result_valid = true; }
public virtual void Update() { if (MeshSource == null) { throw new Exception("EnclosedRegionOffsetOp: must set valid MeshSource to compute!"); } if (MeshSource.HasSpatial == false) { throw new Exception("EnclosedRegionOffsetOp: MeshSource must have spatial data structure!"); } IMesh imesh = MeshSource.GetIMesh(); if (imesh.HasVertexNormals == false) { throw new Exception("EnclosedRegionOffsetOp: input mesh does not have surface normals..."); } if (imesh is DMesh3 == false) { throw new Exception("RegionOffsetOp: in current implementation, input mesh must be a DMesh3. Ugh."); } DMesh3 mesh = imesh as DMesh3; ISpatial spatial = MeshSource.GetSpatial(); DCurve3 curve = new DCurve3(CurveSource.GetICurve()); MeshFacesFromLoop loop = new MeshFacesFromLoop(mesh, curve, spatial); // extract submesh RegionOperator op = new RegionOperator(mesh, loop.InteriorTriangles); DMesh3 submesh = op.Region.SubMesh; // find boundary verts and nbr ring HashSet <int> boundaryV = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(submesh)); HashSet <int> boundaryNbrs = new HashSet <int>(); foreach (int vid in boundaryV) { foreach (int nbrvid in submesh.VtxVerticesItr(vid)) { if (boundaryV.Contains(nbrvid) == false) { boundaryNbrs.Add(nbrvid); } } } // [TODO] maybe should be not using vertex normal here? // use an averaged normal, or a constant for patch? // offset mesh if requested if (Math.Abs(offset_distance) > 0.0001) { foreach (int vid in submesh.VertexIndices()) { if (boundaryV.Contains(vid)) { continue; } // if inner ring is non-zero, then it gets preserved below, and // creates a crease... //double dist = boundaryNbrs.Contains(vid) ? (offset_distance / 2) : offset_distance; double dist = boundaryNbrs.Contains(vid) ? 0 : offset_distance; submesh.SetVertex(vid, submesh.GetVertex(vid) + (float)dist * submesh.GetVertexNormal(vid)); } } //double t = MathUtil.Clamp(1.0 - SmoothAlpha, 0.1, 1.0); double t = 1.0 - SmoothAlpha; t = t * t; double boundary_t = 5.0; double ring_t = 1.0; // smooth submesh, with boundary-ring constraints LaplacianMeshSmoother smoother = new LaplacianMeshSmoother(submesh); foreach (int vid in submesh.VertexIndices()) { if (boundaryV.Contains(vid)) { smoother.SetConstraint(vid, submesh.GetVertex(vid), boundary_t, true); } else if (boundaryNbrs.Contains(vid)) { smoother.SetConstraint(vid, submesh.GetVertex(vid), ring_t); } else { smoother.SetConstraint(vid, submesh.GetVertex(vid), t); } } smoother.SolveAndUpdateMesh(); // turn into displacement vectors Displacement.Clear(); Displacement.Resize(mesh.MaxVertexID); foreach (int subvid in op.Region.SubMesh.VertexIndices()) { Vector3d subv = op.Region.SubMesh.GetVertex(subvid); int basevid = op.Region.SubToBaseV[subvid]; Vector3d basev = op.Region.BaseMesh.GetVertex(basevid); Displacement[basevid] = subv - basev; } result_valid = true; }
Vector3d get_tri_normal(DMesh3 mesh, Index3i tri) { return(MathUtil.Normal(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
} // test_basic_fills public static void test_plane_cut() { List <int> tests = new List <int>() { 1 }; bool DO_EXHAUSTIVE_TESTS = false; foreach (int num_test in tests) { string name; DMesh3 orig_mesh = MakeEditTestMesh(num_test, out name); DMesh3 mesh = new DMesh3(orig_mesh); AxisAlignedBox3d bounds = mesh.CachedBounds; MeshPlaneCut cut = new MeshPlaneCut(mesh, bounds.Center, Vector3d.AxisY); Debug.Assert(cut.Validate() == ValidationStatus.Ok); bool bCutOK = cut.Cut(); System.Console.Write("cut: {0} loops: {1} spans: {2}", ((bCutOK) ? "Ok" : "Failed"), cut.CutLoops.Count, cut.CutSpans.Count); foreach (var loop in cut.CutLoops) { loop.CheckValidity(FailMode.gDevAssert); } foreach (var span in cut.CutSpans) { span.CheckValidity(FailMode.gDevAssert); } bool bFillOK = cut.FillHoles(-1); System.Console.WriteLine(" fill: {0}", ((bFillOK) ? "Ok" : "Failed")); TestUtil.WriteTestOutputMesh(mesh, name + "_cut" + ".obj"); if (DO_EXHAUSTIVE_TESTS == false) { continue; } // grinder: cut through each vtx int VertexIncrement = 1; for (int vid = 0; vid < orig_mesh.MaxVertexID; vid += VertexIncrement) { if (orig_mesh.IsVertex(vid) == false) { continue; } if (vid % 100 == 0) { System.Console.WriteLine("{0} / {1}", vid, orig_mesh.MaxVertexID); } Vector3d v = orig_mesh.GetVertex(vid); mesh = new DMesh3(orig_mesh); cut = new MeshPlaneCut(mesh, v, Vector3d.AxisY); bCutOK = cut.Cut(); Debug.Assert(bCutOK); } } // test_plane_cut }
double get_tri_aspect(DMesh3 mesh, ref Index3i tri) { return(MathUtil.AspectRatio(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
// [RMS] this only tests some basic cases... public static void test_Laplacian() { // compact version DMesh3 mesh = new DMesh3(TestUtil.MakeRemeshedCappedCylinder(1.0), true); Debug.Assert(mesh.IsCompact); AxisAlignedBox3d bounds = mesh.GetBounds(); TestUtil.WriteDebugMesh(mesh, "___CG_before.obj"); List <IMesh> result_meshes = new List <IMesh>(); // make uniform laplacian matrix int N = mesh.VertexCount; SymmetricSparseMatrix M = new SymmetricSparseMatrix(); //DenseMatrix M = new DenseMatrix(N, N); double[] Px = new double[N], Py = new double[N], Pz = new double[N]; int[] nbr_counts = new int[N]; for (int vid = 0; vid < N; ++vid) { nbr_counts[vid] = mesh.GetVtxEdgeCount(vid); } int ti = MeshQueries.FindNearestTriangle_LinearSearch(mesh, new Vector3d(2, 5, 2)); int v_pin = mesh.GetTriangle(ti).a; List <int> constraints = new List <int>() { v_pin }; double consW = 10; double consBottom = 10; foreach (int vid in constraints) { result_meshes.Add(TestUtil.MakeMarker(mesh.GetVertex(vid), (vid == 0) ? 0.2f : 0.1f, Colorf.Red)); } for (int vid = 0; vid < N; ++vid) { int n = nbr_counts[vid]; Vector3d v = mesh.GetVertex(vid), c = Vector3d.Zero; Px[vid] = v.x; Py[vid] = v.y; Pz[vid] = v.z; bool bottom = (v.y - bounds.Min.y) < 0.01f; double sum_w = 0; foreach (int nbrvid in mesh.VtxVerticesItr(vid)) { int n2 = nbr_counts[nbrvid]; // weight options //double w = -1; double w = -1.0 / Math.Sqrt(n + n2); //double w = -1.0 / n; M.Set(vid, nbrvid, w); c += w * mesh.GetVertex(nbrvid); sum_w += w; } sum_w = -sum_w; M.Set(vid, vid, sum_w); // add soft constraints if (constraints.Contains(vid)) { M.Set(vid, vid, sum_w + consW); } else if (bottom) { M.Set(vid, vid, sum_w + consBottom); } } // compute laplacians double[] MLx = new double[N], MLy = new double[N], MLz = new double[N]; M.Multiply(Px, MLx); M.Multiply(Py, MLy); M.Multiply(Pz, MLz); DiagonalMatrix Preconditioner = new DiagonalMatrix(N); for (int i = 0; i < N; i++) { Preconditioner.Set(i, i, 1.0 / M[i, i]); } MLy[v_pin] += consW * 0.5f; MLx[v_pin] += consW * 0.5f; MLz[v_pin] += consW * 0.5f; bool useXAsGuess = true; // preconditioned SparseSymmetricCG SolverX = new SparseSymmetricCG() { B = MLx, X = Px, MultiplyF = M.Multiply, PreconditionMultiplyF = Preconditioner.Multiply, UseXAsInitialGuess = useXAsGuess }; // initial solution SparseSymmetricCG SolverY = new SparseSymmetricCG() { B = MLy, X = Py, MultiplyF = M.Multiply, UseXAsInitialGuess = useXAsGuess }; // neither of those SparseSymmetricCG SolverZ = new SparseSymmetricCG() { B = MLz, MultiplyF = M.Multiply }; bool bx = SolverX.Solve(); bool by = SolverY.Solve(); bool bz = SolverZ.Solve(); for (int vid = 0; vid < mesh.VertexCount; ++vid) { Vector3d newV = new Vector3d(SolverX.X[vid], SolverY.X[vid], SolverZ.X[vid]); mesh.SetVertex(vid, newV); } result_meshes.Add(mesh); TestUtil.WriteDebugMeshes(result_meshes, "___CG_result.obj"); }