/// <summary> /// Cut through-hole either vertically or horizontally. /// /// One current failure mode is if we get more than two ray-hits, which /// can happen due pathological cases or unexpected mesh shape. Currently /// trying to handle the pathological cases (ie ray hits adjacent triangles cases) /// via sorting, not sure if this works spectacularly well. /// /// </summary> protected bool CutThroughHole(DMesh3 mesh, HoleInfo hi, Vector3d translate) { 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 origin = Vector3d.Zero; Vector3d direction = Vector3d.One; if (hi.IsVertical) { direction = Vector3d.AxisY; origin = basePoint + new Vector3d(hi.XZOffset.x, 0, hi.XZOffset.y) - 100 * direction; } else { origin = basePoint + hi.Height * Vector3d.AxisY; direction = Quaterniond.AxisAngleD(Vector3d.AxisY, hi.AroundAngle) * Vector3d.AxisX; } // Find upper and lower triangles that contain center-points of // holes we want to cut. This is the most error-prone part // because we depend on ray-hits, which is not very reliable... Ray3d ray1 = new Ray3d(origin, direction); Ray3d ray2 = new Ray3d(origin + 10000 * direction, -direction); if (hi.GroupIDFilters.a > 0) { spatial.TriangleFilterF = (tid) => { return(mesh.GetTriangleGroup(tid) == hi.GroupIDFilters.a); }; } int hit_1 = spatial.FindNearestHitTriangle(ray1); spatial.TriangleFilterF = null; if (hi.GroupIDFilters.b > 0) { spatial.TriangleFilterF = (tid) => { return(mesh.GetTriangleGroup(tid) == hi.GroupIDFilters.b); }; } int hit_2 = spatial.FindNearestHitTriangle(ray2); spatial.TriangleFilterF = null; if (hit_1 == DMesh3.InvalidID || hit_2 == DMesh3.InvalidID) { return(false); } if (hit_1 == hit_2) { return(false); } List <int> hitTris = new List <int>() { hit_1, hit_2 }; Frame3f projectFrame = new Frame3f(ray1.Origin, ray1.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); List <EdgeLoop> edgeLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { try { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid) { SimplifyInsertion = true }; if (insert.Insert()) { // if we have extra edges just randomly collapse EdgeLoop loop = insert.InsertedLoop; if (loop.VertexCount > circle.VertexCount) { loop = simplify_loop(mesh, loop, circle.VertexCount); } edgeLoops.Add(loop); } else { f3.DebugUtil.Log("insert.Insert() failed!!"); return(false); } } catch (Exception e) { // ignore this loop but we might already be in trouble... f3.DebugUtil.Log("insert.Insert() threw exception for hole {0}!!", hi.nHole); f3.DebugUtil.Log(e.Message); } } if (edgeLoops.Count != 2) { return(false); } try { MeshEditor editor = new MeshEditor(mesh); EdgeLoop l0 = edgeLoops[0]; EdgeLoop l1 = edgeLoops[1]; l1.Reverse(); editor.StitchVertexLoops_NearestV(l0.Vertices, l1.Vertices); // split edges around the holes we cut. This is helpful // if we are going to do additional operations in these areas, // as it gives us extra rings to work with //MeshEdgeSelection edges = new MeshEdgeSelection(mesh); //edges.SelectVertexEdges(l0.Vertices); //edges.SelectVertexEdges(l1.Vertices); //DMesh3.EdgeSplitInfo splitInfo; //foreach ( int eid in edges ) // mesh.SplitEdge(eid, out splitInfo); return(true); } catch { f3.DebugUtil.Log("stitch threw exception!"); return(false); } }
/// <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 testInsertPolygon_PlanarProj() { double dscale = 1.0; DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); dscale = 0.3; //DMesh3 mesh = TestUtil.LoadTestInputMesh("cylinder.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("cube.obj"); double size = mesh.CachedBounds.MaxDim; Vector3d c = mesh.CachedBounds.Center; Vector3d fw = c + mesh.CachedBounds.DiagonalLength * 2 * Vector3d.AxisZ; Ray3d ray = new Ray3d(fw, (c - fw).Normalized); // projection frame and polygon that lives in this frame Frame3f projectFrame = new Frame3f(ray.Origin, ray.Direction); Polygon2d circle = Polygon2d.MakeCircle(dscale * size * 0.1, 6); DMeshAABBTree3 spatial = new DMeshAABBTree3(mesh, true); List <int> hitTris = new List <int>(); spatial.FindAllHitTriangles(ray, hitTris); while (hitTris.Count != 2) { ray.Origin += 100 * MathUtil.Epsilon * Vector3d.One; hitTris.Clear(); spatial.FindAllHitTriangles(ray, hitTris); } // insert polygons but don't simplify the result DMesh3 noTrimMesh = new DMesh3(mesh); List <int[]> noTrimPolyVerts = new List <int[]>(); List <EdgeLoop> noTrimLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(noTrimMesh, circle, projectFrame, hit_tid); insert.SimplifyInsertion = false; if (insert.Insert()) { noTrimPolyVerts.Add(insert.InsertedPolygonVerts); noTrimLoops.Add(insert.InsertedLoop); } else { System.Console.WriteLine("testInsertPolygon_PlanarProj: no-trim Insert() failed"); } } TestUtil.WriteTestOutputMesh(noTrimMesh, "insert_polygon_notrim.obj"); // do different-vtx-count stitch if (noTrimLoops.Count == 2) { noTrimLoops[1].Reverse(); MeshStitchLoops stitcher = new MeshStitchLoops(noTrimMesh, noTrimLoops[0], noTrimLoops[1]); stitcher.TrustLoopOrientations = false; stitcher.AddKnownCorrespondences(noTrimPolyVerts[0], noTrimPolyVerts[1]); stitcher.Stitch(); } TestUtil.WriteTestOutputMesh(noTrimMesh, "insert_polygon_notrim_joined.obj"); // now do simplified version, which we can trivially stitch List <EdgeLoop> edgeLoops = new List <EdgeLoop>(); foreach (int hit_tid in hitTris) { MeshInsertProjectedPolygon insert = new MeshInsertProjectedPolygon(mesh, circle, projectFrame, hit_tid); if (insert.Insert()) { edgeLoops.Add(insert.InsertedLoop); } else { System.Console.WriteLine("testInsertPolygon_PlanarProj: Insert() failed"); } } //TestUtil.WriteTestOutputMesh(mesh, "insert_polygon_before_stitch.obj"); // do stitch if (edgeLoops.Count == 2) { MeshEditor editor = new MeshEditor(mesh); EdgeLoop l0 = edgeLoops[0]; EdgeLoop l1 = edgeLoops[1]; l1.Reverse(); editor.StitchLoop(l0.Vertices, l1.Vertices); } TestUtil.WriteTestOutputMesh(mesh, "insert_polygon_joined.obj"); }