// [TODO] projection pass // - only project vertices modified by smooth pass? // - and/or verts in set of modified edges? protected virtual void TrackedFaceProjectionPass() { var normalTarget = ProjectionTarget as IOrientedProjectionTarget; if (normalTarget == null) { throw new Exception("RemesherPro.TrackedFaceProjectionPass: projection target does not have normals!"); } InitializeBuffersForFacePass(); var buffer_lock = new SpinLock(); // this function computes rotated position of triangle, such that it // aligns with face normal on target surface. We accumulate weighted-average // of vertex positions, which we will then use further down where possible. Action <int> process_triangle = (tid) => { Vector3d normal; double area; Vector3d centroid; mesh.GetTriInfo(tid, out normal, out area, out centroid); Vector3d projNormal; Vector3d projPos = normalTarget.Project(centroid, out projNormal); Index3i tv = mesh.GetTriangle(tid); Vector3d v0 = mesh.GetVertex(tv.a), v1 = mesh.GetVertex(tv.b), v2 = mesh.GetVertex(tv.c); // ugh could probably do this more efficiently... var triF = new Frame3f(centroid, normal); v0 = triF.ToFrameP(ref v0); v1 = triF.ToFrameP(ref v1); v2 = triF.ToFrameP(ref v2); triF.AlignAxis(2, (Vector3f)projNormal); triF.Origin = (Vector3f)projPos; v0 = triF.FromFrameP(ref v0); v1 = triF.FromFrameP(ref v1); v2 = triF.FromFrameP(ref v2); double dot = normal.Dot(projNormal); dot = MathUtil.Clamp(dot, 0, 1.0); double w = area * (dot * dot * dot); bool taken = false; buffer_lock.Enter(ref taken); vBufferV[tv.a] += w * v0; vBufferVWeights[tv.a] += w; vBufferV[tv.b] += w * v1; vBufferVWeights[tv.b] += w; vBufferV[tv.c] += w * v2; vBufferVWeights[tv.c] += w; buffer_lock.Exit(); }; // compute face-aligned vertex positions gParallel.ForEach(mesh.TriangleIndices(), process_triangle); // ok now we filter out all the positions we can't change, as well as vertices that // did not actually move. We also queue any edges that moved far enough to fall // under min/max edge length thresholds gParallel.ForEach(mesh.VertexIndices(), (vID) => { vModifiedV[vID] = false; if (vBufferVWeights[vID] < MathUtil.ZeroTolerance) { return; } if (vertex_is_constrained(vID)) { return; } if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) { return; } Vector3d curpos = mesh.GetVertex(vID); Vector3d projPos = vBufferV[vID] / vBufferVWeights[vID]; if (curpos.EpsilonEqual(projPos, MathUtil.ZeroTolerancef)) { return; } vModifiedV[vID] = true; vBufferV[vID] = projPos; foreach (int eid in mesh.VtxEdgesItr(vID)) { Index2i ev = Mesh.GetEdgeV(eid); int othervid = (ev.a == vID) ? ev.b : ev.a; Vector3d otherv = mesh.GetVertex(othervid); double old_len = curpos.Distance(otherv); double new_len = projPos.Distance(otherv); if (new_len < MinEdgeLength || new_len > MaxEdgeLength) { queue_edge_safe(eid); } } }); // update vertices ApplyVertexBuffer(true); }
unsafe IOReadResult BuildMeshes_ByMaterial(ReadOptions options, IMeshBuilder builder) { if (vPositions.Length == 0) { return(new IOReadResult(IOCode.GarbageDataError, "No vertices in file")); } if (vTriangles.Length == 0) { return(new IOReadResult(IOCode.GarbageDataError, "No triangles in file")); } bool bHaveNormals = (vNormals.Length > 0); bool bHaveColors = (vColors.Length > 0); bool bHaveUVs = (vUVs.Length > 0); List <int> usedMaterialIDs = new List <int>(UsedMaterials.Keys); usedMaterialIDs.Add(Triangle.InvalidMaterialID); foreach (int material_id in usedMaterialIDs) { int matID = Triangle.InvalidMaterialID; if (material_id != Triangle.InvalidMaterialID) { string sMatName = UsedMaterials[material_id]; OBJMaterial useMat = Materials[sMatName]; matID = builder.BuildMaterial(useMat); } bool bMatHaveUVs = (material_id == Triangle.InvalidMaterialID) ? false : bHaveUVs; // don't append mesh until we actually see triangles int meshID = -1; Dictionary <Index3i, int> mapV = new Dictionary <Index3i, int>(); for (int k = 0; k < vTriangles.Length; ++k) { Triangle t = vTriangles[k]; if (t.nMaterialID == material_id) { if (meshID == -1) { meshID = builder.AppendNewMesh(bHaveNormals, bHaveColors, bMatHaveUVs, false); } Triangle t2 = new Triangle(); for (int j = 0; j < 3; ++j) { Index3i vk = new Index3i( t.vIndices[j] - 1, t.vNormals[j] - 1, t.vUVs[j] - 1); int use_vtx = -1; if (mapV.ContainsKey(vk) == false) { use_vtx = append_vertex(builder, vk, bHaveNormals, bHaveColors, bMatHaveUVs); mapV[vk] = use_vtx; } else { use_vtx = mapV[vk]; } t2.vIndices[j] = use_vtx; } append_triangle(builder, t2); } } if (matID != Triangle.InvalidMaterialID) { builder.AssignMaterial(matID, meshID); } } return(new IOReadResult(IOCode.Ok, "")); }
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(); } } } DMeshAABBTreePro spatial = (Spatial != null && testAgainstMesh == Mesh) ? Spatial : new DMeshAABBTreePro(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 void BuildLinear() { int NV = mesh.MaxVertexID; if (TrackVertexMapping) { mapTo = new Index2i[NV]; for (int i = 0; i < NV; ++i) { mapTo[i] = Index2i.Zero; } mapToMulti = new DVector <int>(); } // temporary map from orig vertices to submesh vertices int[] mapToCur = new int[NV]; Array.Clear(mapToCur, 0, mapToCur.Length); int nT = mesh.MaxTriangleID; // depending on the mesh, either the vertex count or triangle count // could hit the upper bound first. For connected meshes we have // NT =~ 2*NV by eulers formula, but eg for a pathological triangle // soup mesh, we might have NV = 3*NT int[] cur_subt = new int[MaxComponentSize]; // accumulated tris for this component int subti = 0; // index into cur_subt int subi = 1; // component index // also need to make sure we don't get too many verts. To do this // we have to keep count of how many unique verts we have accumulated. int[] cur_subv = new int[MaxComponentSize]; // temp buffer in add_component BitArray vert_bits = new BitArray(mesh.MaxVertexID); // which verts have we seen for this component int subvcount = 0; // accumulated vert count for this component Action add_component = () => { Index2i mapRange; int max_subv; Component new_comp = Extract_submesh(subi++, cur_subt, subti, mapToCur, cur_subv, out mapRange, out max_subv); // [TODO] perhaps manager can request smaller chunks? Manager.AddComponent(new_comp); Array.Clear(cur_subt, 0, subti); subti = 0; Array.Clear(mapToCur, mapRange.a, mapRange.b - mapRange.a + 1); Array.Clear(cur_subv, 0, max_subv); subvcount = 0; vert_bits.SetAll(false); }; int[] tri_order = Get_tri_order_by_axis_sort(); int tri_count = tri_order.Length; for (int ii = 0; ii < tri_count; ++ii) { int ti = tri_order[ii]; Index3i tri = mesh.GetTriangle(ti); if (vert_bits[tri.a] == false) { vert_bits[tri.a] = true; subvcount++; } if (vert_bits[tri.b] == false) { vert_bits[tri.b] = true; subvcount++; } if (vert_bits[tri.c] == false) { vert_bits[tri.c] = true; subvcount++; } cur_subt[subti++] = ti; if (subti == MaxComponentSize || subvcount > MaxComponentSize - 3) { add_component(); } } if (subti > 0) { add_component(); } }
/// <summary> /// Assumption is that input graph is a polygon with inserted ray-spans. We want to /// find a set of paths (ie no junctions) that cover all the spans, and travel between /// adjacent spans along edges of the input polygon. /// </summary> protected DGraph2 BuildPathGraph(DGraph2 input) { int NV = input.MaxVertexID; /* * OK, as input we have a graph of our original polygon and a bunch of inserted * segments ("spans"). Orig polygon segments have gid < 0, and span segments >= 0. * However between polygon/span junctions, we have an arbitrary # of polygon edges. * So first step is to simplify these to single-edge "connectors", in new graph MinGraph. * the [connector-edge, path] mappings (if pathlen > 1) are stored in MinEdgePaths * We also store a weight for each connector edge in EdgeWeights (just distance for now) */ DGraph2 MinGraph = new DGraph2(); Dictionary <int, List <int> > MinEdgePaths = new Dictionary <int, List <int> >(); DVector <double> EdgeWeights = new DVector <double>(); EdgeWeights.resize(NV); BitArray done_edge = new BitArray(input.MaxEdgeID); // we should see each edge twice, this avoids repetition // vertex map from input graph to MinGraph int[] MapV = new int[NV]; for (int i = 0; i < NV; ++i) { MapV[i] = -1; } for (int a = 0; a < NV; ++a) { if (input.IsVertex(a) == false || input.IsJunctionVertex(a) == false) { continue; } if (MapV[a] == -1) { MapV[a] = MinGraph.AppendVertex(input.GetVertex(a)); } foreach (int eid in input.VtxEdgesItr(a)) { if (done_edge[eid]) { continue; } Index2i ev = input.GetEdgeV(eid); int b = (ev.a == a) ? ev.b : ev.a; if (input.IsJunctionVertex(b)) { // if we have junction/juntion connection, we can just copy this edge to MinGraph if (MapV[b] == -1) { MapV[b] = MinGraph.AppendVertex(input.GetVertex(b)); } int gid = input.GetEdgeGroup(eid); int existing = MinGraph.FindEdge(MapV[a], MapV[b]); if (existing == DMesh3.InvalidID) { int new_eid = MinGraph.AppendEdge(MapV[a], MapV[b], gid); double path_len = input.GetEdgeSegment(eid).Length; EdgeWeights.insertAt(path_len, new_eid); } else { // we may have inserted this edge already in the simplify branch, this happens eg at the // edge of a circle where the minimal path is between the same vertices as the segment. // But if this is also a fill edge, we want to treat it that way (determind via positive gid) if (gid >= 0) { MinGraph.SetEdgeGroup(existing, gid); } } } else { // not a junction - walk until we find other vtx, and add single edge to MinGraph List <int> path = DGraph2Util.WalkToNextNonRegularVtx(input, a, eid); if (path == null || path.Count < 2) { throw new Exception("build_min_graph: invalid walk!"); } int c = path[path.Count - 1]; // it is somehow possible to get loops... if (c == a) { goto skip_this_edge; } if (MapV[c] == -1) { MapV[c] = MinGraph.AppendVertex(input.GetVertex(c)); } if (MinGraph.FindEdge(MapV[a], MapV[c]) == DMesh3.InvalidID) { int new_eid = MinGraph.AppendEdge(MapV[a], MapV[c], -2); path.Add(MapV[a]); path.Add(MapV[c]); MinEdgePaths[new_eid] = path; double path_len = DGraph2Util.PathLength(input, path); EdgeWeights.insertAt(path_len, new_eid); } } skip_this_edge: done_edge[eid] = true; } } // [TODO] filter MinGraph to remove invalid connectors // - can a connector between two connectors happen? that would be bad. /// - connector that is too close to paths should be ignored (ie avoid collisions) /* * Now that we have MinGraph, we can easily walk between the spans because * they are connected by at most one edge. To find a sequence of spans, we * pick one to start, then walk along connectors, discarding as we go, * so that we don't pass through these vertices again. Repeat until * there are no remaining spans. */ // [TODO] // do we actually have to delete from MinGraph? this prevents us from doing // certain things, like trying different options. Maybe could use a hash for // remaining vertices and edges instead? DGraph2 PathGraph = new DGraph2(); Vector2d sortAxis = Vector2d.FromAngleDeg(AngleDeg).Perp; while (true) { // find most extreme edge to start at // [TODO] could use segment gid here as we set them based on insertion span! // [TODO] could use a smarter metric? like, closest to previous last endpoint? Using // extrema like this tends to produce longest spans, though... double min_dot = double.MaxValue; int start_eid = -1; foreach (int eid in MinGraph.EdgeIndices()) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { double dot = MinGraph.GetVertex(evg.a).Dot(sortAxis); if (dot < min_dot) { min_dot = dot; start_eid = eid; } } } if (start_eid == -1) { break; // if we could not find a start edge, we must be done! } // ok now walk forward through connectors and spans. We do this in // connector/span pairs - we are always at an end-of-span point, and // we pick a next-connector and then a next-span. // We need to keep track of vertices in both the pathgraph and mingraph, // these are the "new" and "old" vertices Index3i start_evg = MinGraph.GetEdge(start_eid); int new_start = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.a)); int new_prev = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.b)); int old_prev = start_evg.b; PathGraph.AppendEdge(new_start, new_prev, start_evg.c); MinGraph.RemoveVertex(start_evg.a, true); while (true) { // choose next connector edge, outgoing from current vtx int connector_e = -1; foreach (int eid in MinGraph.VtxEdgesItr(old_prev)) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { continue; // what?? } if (connector_e == -1 || EdgeWeights[connector_e] > EdgeWeights[eid]) { connector_e = eid; } } if (connector_e == -1) { break; } // find the vertex at end of connector Index3i conn_evg = MinGraph.GetEdge(connector_e); int old_conn_v = (conn_evg.a == old_prev) ? conn_evg.b : conn_evg.a; // can never look at prev vertex again, or any edges connected to it // [TODO] are we sure none of these edges are unused spans?!? MinGraph.RemoveVertex(old_prev, true); // now find outgoing span edge int span_e = -1; foreach (int eid in MinGraph.VtxEdgesItr(old_conn_v)) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { span_e = eid; break; } } if (span_e == -1) { break; // disaster! } // find vertex at far end of span Index3i span_evg = MinGraph.GetEdge(span_e); int old_span_v = (span_evg.a == old_conn_v) ? span_evg.b : span_evg.a; // ok we want to insert the connectr to the path graph, however the // connector might actually have come from a more complex path in the input graph. int new_conn_next = -1; if (MinEdgePaths.ContainsKey(connector_e)) { // complex path case. Note that the order [old_prev, old_conn_v] may be the opposite // of the order in the pathv. But above, we appended the [a,b] edge order to the pathv. // So we can check if we need to flip, but this means we need to be a bit clever w/ indices... List <int> pathv = MinEdgePaths[connector_e]; int N = pathv.Count; int path_prev = new_prev; int k = 1; if (pathv[N - 2] != old_prev) // case where order flipped { pathv.Reverse(); k = 3; } else { N = N - 2; } while (k < N) { int path_next = PathGraph.AppendVertex(input.GetVertex(pathv[k])); PathGraph.AppendEdge(path_prev, path_next); path_prev = path_next; k++; } new_conn_next = path_prev; } else { new_conn_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_conn_v)); PathGraph.AppendEdge(new_prev, new_conn_next, conn_evg.c); } // add span to path int new_fill_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_span_v)); PathGraph.AppendEdge(new_conn_next, new_fill_next, span_evg.c); // remove the connector vertex MinGraph.RemoveVertex(old_conn_v, true); // next iter starts at far end of span new_prev = new_fill_next; old_prev = old_span_v; } sortAxis = -sortAxis; } // for testing/debugging //SVGWriter writer = new SVGWriter(); ////writer.AddGraph(input, SVGWriter.Style.Outline("blue", 0.1f)); //writer.AddGraph(MinGraph, SVGWriter.Style.Outline("red", 0.1f)); ////foreach ( int eid in MinGraph.EdgeIndices() ) { //// if ( MinGraph.GetEdgeGroup(eid) >= 0 ) writer.AddLine(MinGraph.GetEdgeSegment(eid), SVGWriter.Style.Outline("green", 0.07f)); ////} ////writer.AddGraph(MinGraph, SVGWriter.Style.Outline("black", 0.03f)); //writer.AddGraph(PathGraph, SVGWriter.Style.Outline("black", 0.03f)); //foreach (int vid in PathGraph.VertexIndices()) { // if (PathGraph.IsBoundaryVertex(vid)) // writer.AddCircle(new Circle2d(PathGraph.GetVertex(vid), 0.5f), SVGWriter.Style.Outline("blue", 0.03f)); //} ////writer.AddGraph(IntervalGraph, SVGWriter.Style.Outline("black", 0.03f)); //writer.Write("c:\\scratch\\MIN_GRAPH.svg"); return(PathGraph); }
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; } }
public IOWriteResult Write(TextWriter writer, List <WriteMesh> vMeshes, WriteOptions options) { int N = vMeshes.Count; writer.WriteLine("OFF"); string three_floats = Util.MakeVec3FormatString(0, 1, 2, options.RealPrecisionDigits); int nTotalV = 0, nTotalT = 0, nTotalE = 0; // OFF only supports one mesh, so have to collapse all input meshes // into a single list, with mapping for triangles // [TODO] can skip this if input is a single mesh! int[][] mapV = new int[N][]; for (int mi = 0; mi < N; ++mi) { nTotalV += vMeshes[mi].Mesh.VertexCount; nTotalT += vMeshes[mi].Mesh.TriangleCount; nTotalE += 0; mapV[mi] = new int[vMeshes[mi].Mesh.MaxVertexID]; } writer.WriteLine(string.Format("{0} {1} {2}", nTotalV, nTotalT, nTotalE)); // write all vertices, and construct vertex re-map int vi = 0; for (int mi = 0; mi < N; ++mi) { IMesh mesh = vMeshes[mi].Mesh; if (options.ProgressFunc != null) { options.ProgressFunc(mi, 2 * (N - 1)); } foreach (int vid in mesh.VertexIndices()) { Vector3D v = mesh.GetVertex(vid); writer.WriteLine(three_floats, v.x, v.y, v.z); mapV[mi][vid] = vi; vi++; } } // write all triangles for (int mi = 0; mi < N; ++mi) { IMesh mesh = vMeshes[mi].Mesh; if (options.ProgressFunc != null) { options.ProgressFunc(N + mi, 2 * (N - 1)); } foreach (int ti in mesh.TriangleIndices()) { Index3i t = mesh.GetTriangle(ti); t[0] = mapV[mi][t[0]]; t[1] = mapV[mi][t[1]]; t[2] = mapV[mi][t[2]]; writer.WriteLine(string.Format("3 {0} {1} {2}", t[0], t[1], t[2])); } } return(new IOWriteResult(IOCode.Ok, "")); }
double get_tri_aspect(DMesh3 mesh, ref Index3i tri) { return(MathUtil.AspectRatio(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
/// <summary> // This function checks that the mesh is well-formed, ie all internal data // structures are consistent /// </summary> public bool CheckValidity(bool bAllowNonManifoldVertices = false, FailMode eFailMode = FailMode.Throw) { int[] triToVtxRefs = new int[this.MaxVertexID]; bool is_ok = true; Action <bool> CheckOrFailF = (b) => { is_ok = is_ok && b; }; if (eFailMode == FailMode.DebugAssert) { CheckOrFailF = (b) => { Debug.Assert(b); is_ok = is_ok && b; }; } else if (eFailMode == FailMode.gDevAssert) { CheckOrFailF = (b) => { Util.gDevAssert(b); is_ok = is_ok && b; }; } else if (eFailMode == FailMode.Throw) { CheckOrFailF = (b) => { if (b == false) { throw new Exception("DMesh3.CheckValidity: check failed"); } }; } if (normals != null) { CheckOrFailF(normals.Size == vertices.Size); } if (colors != null) { CheckOrFailF(colors.Size == vertices.Size); } if (uv != null) { CheckOrFailF(uv.Size / 2 == vertices.Size / 3); } if (triangle_groups != null) { CheckOrFailF(triangle_groups.Size == triangles.Size / 3); } foreach (int tID in TriangleIndices()) { CheckOrFailF(IsTriangle(tID)); CheckOrFailF(triangles_refcount.RefCount(tID) == 1); // vertices must exist Index3i tv = GetTriangle(tID); for (int j = 0; j < 3; ++j) { CheckOrFailF(IsVertex(tv[j])); triToVtxRefs[tv[j]] += 1; } // edges must exist and reference this tri Index3i e = new Index3i(); for (int j = 0; j < 3; ++j) { int a = tv[j], b = tv[(j + 1) % 3]; e[j] = FindEdge(a, b); CheckOrFailF(e[j] != InvalidID); CheckOrFailF(Edge_has_t(e[j], tID)); CheckOrFailF(e[j] == FindEdgeFromTri(a, b, tID)); } CheckOrFailF(e[0] != e[1] && e[0] != e[2] && e[1] != e[2]); // tri nbrs must exist and reference this tri, or same edge must be boundary edge Index3i te = GetTriEdges(tID); for (int j = 0; j < 3; ++j) { int eid = te[j]; CheckOrFailF(IsEdge(eid)); int tOther = Edge_other_t(eid, tID); if (tOther == InvalidID) { CheckOrFailF(Tri_is_boundary(tID)); continue; } CheckOrFailF(Tri_has_neighbour_t(tOther, tID) == true); // edge must have same two verts as tri for same index int a = tv[j], b = tv[(j + 1) % 3]; Index2i ev = GetEdgeV(te[j]); CheckOrFailF(IndexUtil.same_pair_unordered(a, b, ev[0], ev[1])); // also check that nbr edge has opposite orientation Index3i othertv = GetTriangle(tOther); int found = IndexUtil.find_tri_ordered_edge(b, a, othertv.array); CheckOrFailF(found != InvalidID); } } // edge verts/tris must exist foreach (int eID in EdgeIndices()) { CheckOrFailF(IsEdge(eID)); CheckOrFailF(edges_refcount.RefCount(eID) == 1); Index2i ev = GetEdgeV(eID); Index2i et = GetEdgeT(eID); CheckOrFailF(IsVertex(ev[0])); CheckOrFailF(IsVertex(ev[1])); CheckOrFailF(et[0] != InvalidID); CheckOrFailF(ev[0] < ev[1]); CheckOrFailF(IsTriangle(et[0])); if (et[1] != InvalidID) { CheckOrFailF(IsTriangle(et[1])); } } // verify compact check bool is_compact = vertices_refcount.Is_dense; if (is_compact) { for (int vid = 0; vid < vertices.Length / 3; ++vid) { CheckOrFailF(vertices_refcount.IsValid(vid)); } } // vertex edges must exist and reference this vert foreach (int vID in VertexIndices()) { CheckOrFailF(IsVertex(vID)); Vector3D v = GetVertex(vID); CheckOrFailF(double.IsNaN(v.LengthSquared) == false); CheckOrFailF(double.IsInfinity(v.LengthSquared) == false); List <int> l = vertex_edges[vID]; foreach (int edgeid in l) { CheckOrFailF(IsEdge(edgeid)); CheckOrFailF(Edge_has_v(edgeid, vID)); int otherV = Edge_other_v(edgeid, vID); int e2 = Find_edge(vID, otherV); CheckOrFailF(e2 != InvalidID); CheckOrFailF(e2 == edgeid); e2 = Find_edge(otherV, vID); CheckOrFailF(e2 != InvalidID); CheckOrFailF(e2 == edgeid); } List <int> vTris = new List <int>(), vTris2 = new List <int>(); GetVtxTriangles(vID, vTris, false); GetVtxTriangles(vID, vTris2, true); CheckOrFailF(vTris.Count == vTris2.Count); //System.Console.WriteLine(string.Format("{0} {1} {2}", vID, vTris.Count, GetVtxEdges(vID).Count)); if (bAllowNonManifoldVertices) { CheckOrFailF(vTris.Count <= GetVtxEdges(vID).Count); } else { CheckOrFailF(vTris.Count == GetVtxEdges(vID).Count || vTris.Count == GetVtxEdges(vID).Count - 1); } CheckOrFailF(vertices_refcount.RefCount(vID) == vTris.Count + 1); CheckOrFailF(triToVtxRefs[vID] == vTris.Count); foreach (int tID in vTris) { CheckOrFailF(Tri_has_v(tID, vID)); } // check that edges around vert only references tris above, and reference all of them! List <int> vRemoveTris = new List <int>(vTris); foreach (int edgeid in l) { Index2i edget = GetEdgeT(edgeid); CheckOrFailF(vTris.Contains(edget[0])); if (edget[1] != InvalidID) { CheckOrFailF(vTris.Contains(edget[1])); } vRemoveTris.Remove(edget[0]); if (edget[1] != InvalidID) { vRemoveTris.Remove(edget[1]); } } CheckOrFailF(vRemoveTris.Count == 0); } return(is_ok); }
double get_tri_area(DMesh3 mesh, ref Index3i tri) { return(MathUtil.Area(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
Vector3d get_tri_normal(DMesh3 mesh, Index3i tri) { return(MathUtil.Normal(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c))); }
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); }
public static fMesh DMeshToUnityMesh(DMesh3 m, bool bSwapLeftRight, bool bAllowLargeMeshes = false) { if (bSwapLeftRight) { throw new Exception("[RMSNOTE] I think this conversion is wrong, see MeshTransforms.SwapLeftRight. Just want to know if this code is ever hit."); } if (bAllowLargeMeshes == false) { if (m.MaxVertexID > 65000 || m.MaxTriangleID > 65000) { Debug.Log("[UnityUtil.DMeshToUnityMesh] attempted to import object larger than 65000 verts/tris, not supported by Unity!"); return(null); } } Mesh unityMesh = new Mesh(); Vector3[] vertices = dvector_to_vector3(m.VerticesBuffer); Vector3[] normals = (m.HasVertexNormals) ? dvector_to_vector3(m.NormalsBuffer) : null; unityMesh.vertices = vertices; if (m.HasVertexNormals) { unityMesh.normals = normals; } if (m.HasVertexColors) { unityMesh.colors = dvector_to_color(m.ColorsBuffer); } if (m.HasVertexUVs) { unityMesh.uv = dvector_to_vector2(m.UVBuffer); } if (bAllowLargeMeshes && (m.MaxVertexID > 65000 || m.TriangleCount > 65000)) { unityMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } if (m.IsCompactT) { unityMesh.triangles = dvector_to_int(m.TrianglesBuffer); } else { int[] triangles = new int[m.TriangleCount * 3]; int ti = 0; for (int k = 0; k < m.MaxTriangleID; ++k) { if (m.IsTriangle(k)) { Index3i t = m.GetTriangle(k); int j = 3 * ti; triangles[j] = t.a; triangles[j + 1] = t.b; triangles[j + 2] = t.c; ti++; } } unityMesh.triangles = triangles; } if (m.HasVertexNormals == false) { unityMesh.RecalculateNormals(); } return(new fMesh(unityMesh)); }
} // Calculate() bool is_connection(Index3i flags) { return((flags.a & (int)TPVertexFlags.IsConnector) != 0); }