//This is to cater for situations where the mesh has duplicate points; these are when the same combination of x/y/z values are repeated //in the vertices collection public void Consolidate() { var vPts = Enumerable.Range(0, Vertices.Count() / 3).Select(v => new Point3D(Vertices[v * 3], Vertices[(v * 3) + 1], Vertices[(v * 3) + 2])).ToList(); //This algorithm is O(N^2) at the moment var indexConsolidateMappings = new Dictionary <int, int>(); var newPts = new List <Point3D>(); for (var i = 0; i < vPts.Count(); i++) { var found = false; for (int j = 0; j < newPts.Count(); j++) { if (vPts[i].Equals(newPts[j], Helper.PointComparisonEpsilon)) { indexConsolidateMappings.Add(i, j); found = true; break; } } if (!found) { var newIndex = newPts.Count(); newPts.Add(vPts[i]); indexConsolidateMappings.Add(i, newIndex); } } var newFaces = Faces.ToList(); var f = 0; do { var numInFace = (newFaces[f] == 0) ? 3 : 4; if ((f + numInFace) < newFaces.Count()) { f++; for (var v = 0; v < numInFace; v++) { if (indexConsolidateMappings.ContainsKey(newFaces[f + v])) { newFaces[f + v] = indexConsolidateMappings[newFaces[f + v]]; } } } f += numInFace; } while (f < newFaces.Count()); Faces = newFaces; Vertices = newPts.SelectMany(p => new[] { p.X, p.Y, p.Z }).ToList(); }
public FaceIndexableItem(Item item) : base(item) { var faceIdentification = new AzureFaceIdentification(); var mediaItem = new MediaItem(item); var mediaStream = mediaItem.GetMediaStream(); if (mediaStream != null) { Faces = !IsNullOrEmpty(item["FaceMetadata"]) ? JsonConvert.DeserializeObject <FaceMetadata[]>(item["FaceMetadata"]) : new FaceMetadata[0]; IdentifiedPersons = !IsNullOrEmpty(item["IdentifiedPersons"]) ? JsonConvert.DeserializeObject <IdentifiedPerson[]>(item["IdentifiedPersons"]) : new IdentifiedPerson[0]; Suggestions = faceIdentification.Identify(Faces.ToList(), mediaStream); } }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public bool BuildIfCylinderIsHole(bool isPositive) { if (isPositive) { throw new Exception("BuildIfCylinderIsHole assumes that the faces have already been collected, " + "such that the cylinder is negative"); } IsPositive = false; //To truly be a hole, there should be two loops of vertices that form circles on either ends of the faces. //These are easy to capture because all the edges between them should be shared by two of the faces //Start by collecting the edges at either end. Each edge belongs to only two faces, so any edge that only //comes up once, must be at the edge of the cylinder (assuming it is a cylinder). var edges = new HashSet <Edge>(); foreach (var face in Faces) { foreach (var edge in face.Edges) { if (edges.Contains(edge)) { edges.Remove(edge); } else { edges.Add(edge); } } } //Now we can loop through the edges to form two loops if (edges.Count < 5) { return(false); //5 is the minimum number of vertices to look remotely circular (8 is more likely) } var(allLoopsClosed, edgeLoops, loops) = GetLoops(edges, true); if (loops.Count != 2) { return(false); //There must be two and only two loops. } Loop1 = new HashSet <Vertex>(loops[0]); Loop2 = new HashSet <Vertex>(loops[1]); EdgeLoop1 = edgeLoops[0]; EdgeLoop2 = edgeLoops[1]; //Next, we need to get the central axis. //The ends of the cylinder could be any shape (flat, curved, angled) and the shapes //on each end do not need to match. This rules out using the vertex loops to form //a plane (most accurate) and creating a plane from edge midpoints (next most accurate). //The next most accurate thing is to use the edge vectors to set the axis. //This is more precise than taking a bunch of cross products with the faces. //And it is more universal than creating a plane from the loops, since it works //for holes that enter and exit at an angle. var throughEdgeVectors = new Dictionary <Vertex, double[]>(); var dotFromSharpestEdgesConnectedToVertex = new Dictionary <Vertex, double>(); foreach (var edge in InnerEdges) { //Skip those edges that are on "flat" surfaces var dot = edge.OwnedFace.Normal.dotProduct(edge.OtherFace.Normal); if (dot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface)) { continue; } //This uses a for loop to remove duplicate code, to decide which vertex to check with which loop for (var i = 0; i < 2; i++) { var A = i == 0 ? edge.To : edge.From; var B = i == 0 ? edge.From : edge.To; var direction = edge.Vector.normalize(); //Positive if B is further along var previousDistance = direction.dotProduct(B.Position); var sign = Math.Sign(direction.dotProduct(B.Position) - direction.dotProduct(A.Position)); if (Loop1.Contains(A)) { bool reachedEnd = Loop2.Contains(B); if (!reachedEnd) { //Check if this edge needs to "extended" to reach the end of the cylinder var previousEdge = edge; var previousVertex = B; while (!reachedEnd) { var maxDot = 0.0; Edge extensionEdge = null; foreach (var otherEdge in previousVertex.Edges.Where(e => e != previousEdge)) { //This other edge must be contained in the InnerEdges and along the same direction if (!InnerEdges.Contains(otherEdge)) { continue; } var edgeDot = Math.Abs(otherEdge.Vector.normalize().dotProduct(previousEdge.Vector.normalize())); if (!edgeDot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface)) { continue; } var distance = sign * (direction.dotProduct(otherEdge.OtherVertex(previousVertex).Position) - previousDistance); if (!distance.IsGreaterThanNonNegligible()) { continue; //This vertex is not any further along } //Choose the edge that is most along the previous edge if (edgeDot > maxDot) { maxDot = edgeDot; extensionEdge = otherEdge; } } if (extensionEdge == null) { break; //go to the next edge } if (Loop2.Contains(extensionEdge.OtherVertex(previousVertex))) { reachedEnd = true; B = extensionEdge.OtherVertex(previousVertex); } else { previousVertex = extensionEdge.OtherVertex(previousVertex); previousEdge = extensionEdge; } } } //If there was a vertex from the edge or edges in the second loop. if (reachedEnd) { if (!dotFromSharpestEdgesConnectedToVertex.ContainsKey(A)) { throughEdgeVectors.Add(A, B.Position.subtract(A.Position)); dotFromSharpestEdgesConnectedToVertex.Add(A, edge.InternalAngle); } else if (dot < dotFromSharpestEdgesConnectedToVertex[A]) { throughEdgeVectors[A] = B.Position.subtract(A.Position); dotFromSharpestEdgesConnectedToVertex[A] = dot; } break; //Go to the next edge } } } } if (throughEdgeVectors.Count < 3) { return(false); } //Estimate the axis from the sum of the through edge vectors //The axis points from loop 1 to loop 2, since we always start the edge vector from Loop1 var edgeVectors = new List <double[]>(throughEdgeVectors.Values); var numEdges = edgeVectors.Count; var axis = new double[] { 0.0, 0.0, 0.0 }; foreach (var edgeVector in edgeVectors) { axis = axis.add(edgeVector); } Axis = axis.normalize(); /* to adjust the Axis, we will average the cross products of the new face with all the old faces */ //Since we will be taking cross products, we need to be sure not to have faces along the same normal var faces = MiscFunctions.FacesWithDistinctNormals(Faces.ToList()); var n = faces.Count; //Check if the loops are circular along the axis var path1 = MiscFunctions.Get2DProjectionPointsAsLight(Loop1, Axis, out var backTransform); var poly = new PolygonLight(path1); if (!PolygonOperations.IsCircular(new Polygon(poly), out var centerCircle, Constants.MediumConfidence)) { return(false); } var path2 = MiscFunctions.Get2DProjectionPointsAsLight(Loop2, Axis, out var backTransform2); var poly2 = new PolygonLight(path2); if (!PolygonOperations.IsCircular(new Polygon(poly2), out var centerCircle2, Constants.MediumConfidence)) { return(false); } Radius = (centerCircle.Radius + centerCircle2.Radius) / 2; //Average //Set the Anchor/Center point var center = centerCircle.Center.Add(centerCircle2.Center).divide(2); Anchor = MiscFunctions.Convert2DVectorTo3DVector(center, backTransform); return(true); }