/// <summary> /// precompute constant coefficients of point winding number approximation /// pointAreas must be provided, and pointSet must have vertex normals! /// p: 'center' of expansion for points (area-weighted point avg) /// r: max distance from p to points /// order1: first-order vector coeff /// order2: second-order matrix coeff /// </summary> public static void ComputeCoeffs( IPointSet pointSet, IEnumerable <int> points, double[] pointAreas, ref Vector3d p, ref double r, ref Vector3d order1, ref Matrix3d order2) { if (pointSet.HasVertexNormals == false) { throw new Exception("FastPointWinding.ComputeCoeffs: point set does not have normals!"); } p = Vector3d.Zero; order1 = Vector3d.Zero; order2 = Matrix3d.Zero; r = 0; // compute area-weighted centroid of points, we use this as the expansion point double sum_area = 0; foreach (int vid in points) { sum_area += pointAreas[vid]; p += pointAreas[vid] * pointSet.GetVertex(vid); } p /= sum_area; // compute first and second-order coefficients of FWN taylor expansion, as well as // 'radius' value r, which is max dist from any tri vertex to p foreach (int vid in points) { Vector3d p_i = pointSet.GetVertex(vid); Vector3d n_i = pointSet.GetVertexNormal(vid); double a_i = pointAreas[vid]; order1 += a_i * n_i; Vector3d dcp = p_i - p; order2 += a_i * new Matrix3d(ref dcp, ref n_i); // this is just for return value... r = Math.Max(r, p_i.Distance(p)); } }
public static void EdgeLengthStats(DMesh3 mesh, out double minEdgeLen, out double maxEdgeLen, out double avgEdgeLen, int samples = 0) { minEdgeLen = double.MaxValue; maxEdgeLen = double.MinValue; avgEdgeLen = 0; int avg_count = 0; int MaxID = mesh.MaxEdgeID; // if we are only taking some samples, use a prime-modulo-loop instead of random int nPrime = (samples == 0) ? 1 : nPrime = 31337; int max_count = (samples == 0) ? MaxID : samples; Vector3d a = Vector3d.Zero, b = Vector3d.Zero; int eid = 0; int count = 0; do { if (mesh.IsEdge(eid)) { mesh.GetEdgeV(eid, ref a, ref b); double len = a.Distance(b); if (len < minEdgeLen) { minEdgeLen = len; } if (len > maxEdgeLen) { maxEdgeLen = len; } avgEdgeLen += len; avg_count++; } eid = (eid + nPrime) % MaxID; } while (eid != 0 && count++ < max_count); avgEdgeLen /= (double)avg_count; }
protected bool check_for_cracks(DMesh3 mesh, out int boundary_edge_count, double crack_tol = MathUtil.ZeroTolerancef) { boundary_edge_count = 0; var boundary_verts = new MeshVertexSelection(mesh); foreach (int eid in mesh.BoundaryEdgeIndices()) { Index2i ev = mesh.GetEdgeV(eid); boundary_verts.Select(ev.a); boundary_verts.Select(ev.b); boundary_edge_count++; } if (boundary_verts.Count == 0) { return(false); } AxisAlignedBox3d bounds = mesh.CachedBounds; var borderV = new PointHashGrid3d <int>(bounds.MaxDim / 128, -1); foreach (int vid in boundary_verts) { Vector3d v = mesh.GetVertex(vid); var result = borderV.FindNearestInRadius(v, crack_tol, (existing_vid) => { return(v.Distance(mesh.GetVertex(existing_vid))); }); if (result.Key != -1) { return(true); // we found a crack vertex! } borderV.InsertPoint(vid, v); } // found no cracks return(false); }
public bool Fill() { compute_polygons(); // translate/scale fill loops to unit box. This will improve // accuracy in the calcs below... Vector2d shiftOrigin = Bounds.Center; double scale = 1.0 / Bounds.MaxDim; foreach (var floop in Loops) { floop.poly.Translate(-shiftOrigin); floop.poly.Scale(scale * Vector2d.One, Vector2d.Zero); } Dictionary <PlanarComplex.Element, int> ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>(); // [TODO] if we have multiple components in input mesh, we could do this per-component. // This also helps avoid nested shells creating holes. // *However*, we shouldn't *have* to because FindSolidRegions will do the right thing if // the polygons have the same orientation // add all loops to planar complex PlanarComplex complex = new PlanarComplex(); for (int i = 0; i < Loops.Count; ++i) { var elem = complex.Add(Loops[i].poly); ElemToLoopMap[elem] = i; } // sort into separate 2d solids PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(PlanarComplex.FindSolidsOptions.SortPolygons); // fill each 2d solid List <Index2i> failed_inserts = new List <Index2i>(); List <Index2i> failed_merges = new List <Index2i>(); for (int fi = 0; fi < solids.Polygons.Count; ++fi) { var gpoly = solids.Polygons[fi]; PlanarComplex.GeneralSolid gsolid = solids.PolygonsSources[fi]; // [TODO] could do scale/translate here, per-polygon would be more precise // generate planar mesh that we will insert polygons into MeshGenerator meshgen; float planeW = 1.5f; int nDivisions = 0; if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0) { int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1; nDivisions = (n <= 1) ? 0 : n; } if (nDivisions == 0) { meshgen = new TrivialRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, }; } else { meshgen = new GriddedRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, EdgeVertices = nDivisions }; } DMesh3 FillMesh = meshgen.Generate().MakeDMesh(); FillMesh.ReverseOrientation(); // why?!? // convenient list List <Polygon2d> polys = new List <Polygon2d>() { gpoly.Outer }; polys.AddRange(gpoly.Holes); // for each poly, we track the set of vertices inserted into mesh int[][] polyVertices = new int[polys.Count][]; // insert each poly for (int pi = 0; pi < polys.Count; ++pi) { MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, polys[pi]); ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale); bool failed = true; if (status == ValidationStatus.Ok) { if (insert.Apply()) { insert.Simplify(); polyVertices[pi] = insert.CurveVertices; failed = (insert.Loops.Count != 1) || (insert.Loops[0].VertexCount != polys[pi].VertexCount); } } if (failed) { failed_inserts.Add(new Index2i(fi, pi)); } } // remove any triangles not contained in gpoly // [TODO] degenerate triangle handling? may be 'on' edge of gpoly... List <int> removeT = new List <int>(); foreach (int tid in FillMesh.TriangleIndices()) { Vector3d v = FillMesh.GetTriCentroid(tid); if (gpoly.Contains(v.xy) == false) { removeT.Add(tid); } } foreach (int tid in removeT) { FillMesh.RemoveTriangle(tid, true, false); } //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj"); // transform fill mesh back to 3d MeshTransforms.PerVertexTransform(FillMesh, (v) => { Vector2d v2 = v.xy; v2 /= scale; v2 += shiftOrigin; return(to3D(v2)); }); //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj"); //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); // figure out map between new mesh and original edge loops // [TODO] if # of verts is different, we can still find correspondence, it is just harder // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); if (MergeFillBoundary) { for (int pi = 0; pi < polys.Count; ++pi) { if (polyVertices[pi] == null) { continue; } int[] fillLoopVerts = polyVertices[pi]; int NV = fillLoopVerts.Length; PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; int loopi = ElemToLoopMap[sourceElem]; EdgeLoop sourceLoop = Loops[loopi].edgeLoop; if (sourceLoop.VertexCount != NV) { failed_merges.Add(new Index2i(fi, pi)); continue; } for (int k = 0; k < NV; ++k) { Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) { mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; } } } } // append this fill to input mesh MeshEditor editor = new MeshEditor(Mesh); int[] mapV; editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); // [TODO] should verify that we actually merged the loops... } if (failed_inserts.Count > 0 || failed_merges.Count > 0) { return(false); } return(true); }
public double Value(ref Vector3d pt) { return(pt.Distance(ref Origin) - Radius); }
public double GetSquared() { if (DistanceSquared >= 0) { return(DistanceSquared); } if (cylinder.Height >= double.MaxValue) { return(get_squared_infinite()); } // Convert the point to the cylinder coordinate system. In this system, // the point believes (0,0,0) is the cylinder axis origin and (0,0,1) is // the cylinder axis direction. Vector3d basis0 = cylinder.Axis.Direction; Vector3d basis1 = Vector3d.Zero, basis2 = Vector3d.Zero; Vector3d.ComputeOrthogonalComplement(1, basis0, ref basis1, ref basis2); double height = Cylinder.Height / 2.0; Vector3d delta = point - cylinder.Axis.Origin; var P = new Vector3d(basis1.Dot(delta), basis2.Dot(delta), basis0.Dot(delta)); double result_distance = 0; // signed! Vector3d result_closest = Vector3d.Zero; double sqrRadius = cylinder.Radius * cylinder.Radius; double sqrDistance = P[0] * P[0] + P[1] * P[1]; // The point is outside the infinite cylinder, or on the cylinder wall. double distance = Math.Sqrt(sqrDistance); double inf_distance = distance - Cylinder.Radius; double temp = Cylinder.Radius / distance; var inf_closest = new Vector3d(temp * P.x, temp * P.y, P.z); bool bOutside = (sqrDistance >= sqrRadius); result_closest = inf_closest; result_distance = inf_distance; if (inf_closest.z >= height) { result_closest = (bOutside) ? inf_closest : P; result_closest.z = height; result_distance = result_closest.Distance(P); // TODO: only compute sqr here bOutside = true; } else if (inf_closest.z <= -height) { result_closest = (bOutside) ? inf_closest : P; result_closest.z = -height; result_distance = result_closest.Distance(P); // TODO: only compute sqr here bOutside = true; } else if (bOutside == false) { if (inf_closest.z > 0 && Math.Abs(inf_closest.z - height) < Math.Abs(inf_distance)) { result_closest = P; result_closest.z = height; result_distance = result_closest.Distance(P); // TODO: only compute sqr here } else if (inf_closest.z < 0 && Math.Abs(inf_closest.z - -height) < Math.Abs(inf_distance)) { result_closest = P; result_closest.z = -height; result_distance = result_closest.Distance(P); // TODO: only compute sqr here } } SignedDistance = (bOutside) ? Math.Abs(result_distance) : -Math.Abs(result_distance); // Convert the closest point from the cylinder coordinate system to the // original coordinate system. CylinderClosest = cylinder.Axis.Origin + result_closest.x * basis1 + result_closest.y * basis2 + result_closest.z * basis0; DistanceSquared = result_distance * result_distance; return(DistanceSquared); }
void generate_graph(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField) { int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk; float dx = (float)CellSize; Vector3f origin = this.GridOrigin; // parameters for initializing cost grid float MODEL_SPACE = 0.01f; // needs small positive so that points on triangles count as inside (eg on ground plane) //float MODEL_SPACE = 2.0f*(float)CellSize; float CRAZY_DISTANCE = 99999.0f; bool UNIFORM_DISTANCE = true; float MAX_DIST = 10 * (float)CellSize; // parameters for sorting seeds Vector3i center_idx = new Vector3i(ni / 2, 0, nk / 2); // middle //Vector3i center_idx = new Vector3i(0, 0, 0); // corner bool reverse_per_layer = true; DenseGrid3f costGrid = new DenseGrid3f(supportGrid); foreach ( Vector3i ijk in costGrid.Indices() ) { Vector3d cell_center = new Vector3f(ijk.x * dx, ijk.y * dx, ijk.z * dx) + origin; float f = (float)distanceField.Value(ref cell_center); if (f <= MODEL_SPACE) f = CRAZY_DISTANCE; else if (UNIFORM_DISTANCE) f = 1.0f; else if (f > MAX_DIST) f = MAX_DIST; costGrid[ijk] = f; } // Find seeds on each layer, sort, and add to accumulated bottom-up seeds list. // This sorting has an *enormous* effect on the support generation. List<Vector3i> seeds = new List<Vector3i>(); List<Vector3i> layer_seeds = new List<Vector3i>(); for (int j = 0; j < nj; ++j) { layer_seeds.Clear(); for (int k = 0; k < nk; ++k) { for (int i = 0; i < ni; ++i) { if (supportGrid[i, j, k] == SUPPORT_TIP_BASE) layer_seeds.Add(new Vector3i(i, j, k)); } } layer_seeds.Sort((a, b) => { Vector3i pa = a; pa.y = 0; Vector3i pb = b; pb.y = 0; int sa = (pa-center_idx).LengthSquared, sb = (pb-center_idx).LengthSquared; return sa.CompareTo(sb); }); // reversing sort order is intresting? if(reverse_per_layer) layer_seeds.Reverse(); seeds.AddRange(layer_seeds); } HashSet<Vector3i> seed_indices = new HashSet<Vector3i>(seeds); // gives very different results... if (ProcessBottomUp == false) seeds.Reverse(); // for linear index a, is this a node we allow in graph? (ie graph bounds) Func<int, bool> node_filter_f = (a) => { Vector3i ai = costGrid.to_index(a); // why not y check?? return ai.x > 0 && ai.z > 0 && ai.x != ni - 1 && ai.y != nj - 1 && ai.z != nk - 1; }; // distance from linear index a to linear index b // this defines the cost field we want to find shortest path through Func<int, int, float> node_dist_f = (a, b) => { Vector3i ai = costGrid.to_index(a), bi = costGrid.to_index(b); if (bi.y >= ai.y) // b.y should always be a.y-1 return float.MaxValue; float sg = supportGrid[bi]; // don't connect to tips //if (sg == SUPPORT_TIP_BASE || sg == SUPPORT_TIP_TOP) // return float.MaxValue; if (sg == SUPPORT_TIP_TOP) return float.MaxValue; if (sg < 0) return -999999; // if b is already used, we will terminate there, so this is a good choice // otherwise cost is sqr-grid-distance + costGrid value (which is basically distance to surface) float c = costGrid[b]; float f = (float)(Math.Sqrt((bi - ai).LengthSquared) * CellSize); //float f = 0; return c + f; }; // which linear-index nbrs to consider for linear index a Func<int, IEnumerable<int>> neighbour_f = (a) => { Vector3i ai = costGrid.to_index(a); return down_neighbours(ai, costGrid); }; // when do we terminate Func<int, bool> terminate_f = (a) => { Vector3i ai = costGrid.to_index(a); // terminate if we hit existing support path if (seed_indices.Contains(ai) == false && supportGrid[ai] < 0) return true; // terminate if we hit ground plane if (ai.y == 0) return true; return false; }; DijkstraGraphDistance dijkstra = new DijkstraGraphDistance(ni * nj * nk, false, node_filter_f, node_dist_f, neighbour_f); dijkstra.TrackOrder = true; List<int> path = new List<int>(); Graph = new DGraph3(); Dictionary<Vector3i, int> CellToGraph = new Dictionary<Vector3i, int>(); TipVertices = new HashSet<int>(); TipBaseVertices = new HashSet<int>(); GroundVertices = new HashSet<int>(); // seeds are tip-base points for (int k = 0; k < seeds.Count; ++k) { // add seed point (which is a tip-base vertex) as seed for dijkstra prop int seed = costGrid.to_linear(seeds[k]); dijkstra.Reset(); dijkstra.AddSeed(seed, 0); // compute to termination (ground, existing node, etc) int base_node = dijkstra.ComputeToNode(terminate_f); if (base_node < 0) base_node = dijkstra.GetOrder().Last(); // extract the path path.Clear(); dijkstra.GetPathToSeed(base_node, path); int N = path.Count; // first point on path is termination point. // create vertex for it if we have not yet Vector3i basept_idx = supportGrid.to_index(path[0]); int basept_vid; if ( CellToGraph.TryGetValue(basept_idx, out basept_vid) == false ) { Vector3d curv = get_cell_center(basept_idx); if (basept_idx.y == 0) { curv.y = 0; } basept_vid = Graph.AppendVertex(curv); if (basept_idx.y == 0) { GroundVertices.Add(basept_vid); } CellToGraph[basept_idx] = basept_vid; } int cur_vid = basept_vid; // now walk up path and create vertices as necessary for (int i = 0; i < N; ++i) { int idx = path[i]; if ( supportGrid[idx] >= 0 ) supportGrid[idx] = SUPPORT_GRID_USED; if ( i > 0 ) { Vector3i next_idx = supportGrid.to_index(path[i]); int next_vid; if (CellToGraph.TryGetValue(next_idx, out next_vid) == false) { Vector3d nextv = get_cell_center(next_idx); next_vid = Graph.AppendVertex(nextv); CellToGraph[next_idx] = next_vid; } Graph.AppendEdge(cur_vid, next_vid); cur_vid = next_vid; } } // seed was tip-base so we should always get back there. Then we // explicitly add tip-top and edge to it. if ( supportGrid[path[N-1]] == SUPPORT_TIP_BASE ) { Vector3i vec_idx = supportGrid.to_index(path[N-1]); TipBaseVertices.Add(CellToGraph[vec_idx]); Vector3i tip_idx = vec_idx + Vector3i.AxisY; int tip_vid; if (CellToGraph.TryGetValue(tip_idx, out tip_vid) == false) { Vector3d tipv = get_cell_center(tip_idx); tip_vid = Graph.AppendVertex(tipv); CellToGraph[tip_idx] = tip_vid; Graph.AppendEdge(cur_vid, tip_vid); TipVertices.Add(tip_vid); } } } /* * Snap tips to surface */ gParallel.ForEach(TipVertices, (tip_vid) => { bool snapped = false; Vector3d v = Graph.GetVertex(tip_vid); Frame3f hitF; // try shooting ray straight up. if that hits, and point is close, we use it if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, Vector3d.AxisY), out hitF)) { if (v.Distance(hitF.Origin) < 2 * CellSize) { v = hitF.Origin; snapped = true; } } // if that failed, try straight down if (!snapped) { if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, -Vector3d.AxisY), out hitF)) { if (v.Distance(hitF.Origin) < CellSize) { v = hitF.Origin; snapped = true; } } } // if it missed, or hit pt was too far, find nearest point and try that if (!snapped) { hitF = MeshQueries.NearestPointFrame(Mesh, MeshSpatial, v); if (v.Distance(hitF.Origin) < 2 * CellSize) { v = hitF.Origin; snapped = true; } // can this ever fail? tips should always be within 2 cells... } if (snapped) Graph.SetVertex(tip_vid, v); }); }
void constrained_smooth(DGraph3 graph, double surfDist, double dotThresh, double alpha, int rounds) { int NV = graph.MaxVertexID; Vector3d[] pos = new Vector3d[NV]; for (int ri = 0; ri < rounds; ++ri) { gParallel.ForEach(graph.VertexIndices(), (vid) => { Vector3d v = graph.GetVertex(vid); if ( GroundVertices.Contains(vid) || TipVertices.Contains(vid) ) { pos[vid] = v; return; } // for tip base vertices, we could allow them to move down and away within angle cone... if (TipBaseVertices.Contains(vid)) { pos[vid] = v; return; } // compute smoothed position of vtx Vector3d centroid = Vector3d.Zero; int nbr_count = 0; foreach (int nbr_vid in graph.VtxVerticesItr(vid)) { centroid += graph.GetVertex(nbr_vid); nbr_count++; } if (nbr_count == 1) { pos[vid] = v; return; } centroid /= nbr_count; Vector3d vnew = (1 - alpha) * v + (alpha) * centroid; // make sure we don't violate angle constraint to any nbrs int attempt = 0; try_again: foreach ( int nbr_vid in graph.VtxVerticesItr(vid)) { Vector3d dv = graph.GetVertex(nbr_vid) - vnew; dv.Normalize(); double dot = dv.Dot(Vector3d.AxisY); if ( Math.Abs(dot) < dotThresh ) { if (attempt++ < 3) { vnew = Vector3d.Lerp(v, vnew, 0.66); goto try_again; } else { pos[vid] = v; return; } } } // offset from nearest point on surface Frame3f fNearest = MeshQueries.NearestPointFrame(Mesh, MeshSpatial, vnew, true); Vector3d vNearest = fNearest.Origin; double dist = vnew.Distance(vNearest); bool inside = MeshSpatial.IsInside(vnew); if (inside || dist < surfDist) { Vector3d normal = fNearest.Z; // don't push down? if (normal.Dot(Vector3d.AxisY) < 0) { normal.y = 0; normal.Normalize(); } vnew = fNearest.Origin + surfDist * normal; } pos[vid] = vnew; }); foreach (int vid in graph.VertexIndices()) graph.SetVertex(vid, pos[vid]); } }