/// <summary> /// Gets the silhouette of a solid along a given normal. /// </summary> /// <param name="faces"></param> /// <param name="normal"></param> /// <param name="minAngle"></param> /// <param name="minPathAreaToConsider"></param> /// <param name="depthOfPart"></param> /// <returns></returns> public static List <List <PointLight> > Slow(IList <PolygonalFace> faces, double[] normal, double minAngle = 0.1, double minPathAreaToConsider = 0.0, double depthOfPart = 0.0) { var angleTolerance = Math.Cos((90 - minAngle) * Math.PI / 180); //Get the positive faces (defined as face normal along same direction as the silhoutte normal). var positiveFaces = new HashSet <PolygonalFace>(); var vertices = new HashSet <Vertex>(); foreach (var face in faces) { var dot = normal.dotProduct(face.Normal, 3); if (dot.IsGreaterThanNonNegligible(angleTolerance)) { positiveFaces.Add(face); //face.Color = new Color(KnownColors.Blue); foreach (var vertex in face.Vertices) { vertices.Add(vertex); } } } //Project all the vertices into points //The vertex is saved as a reference in the point var transform = MiscFunctions.TransformToXYPlane(normal, out _); var projectedPoints = new Dictionary <int, PointLight>(); foreach (var vertex in vertices) { projectedPoints.Add(vertex.IndexInList, MiscFunctions.Get2DProjectionPointAsLight(vertex, transform)); } //Build a dictionary of faces to polygons //var projectedFacePolygons = positiveFaces.ToDictionary(f => f, f => GetPolygonFromFace(f, projectedPoints, true)); //Use GetPolygonFromFace and force to be positive faces with true" var projectedFacePolygons2 = positiveFaces.Select(f => GetPolygonFromFace(f, projectedPoints, true)).ToList().Where(p => p.Area > minPathAreaToConsider).ToList(); var solution = PolygonOperations.Union(projectedFacePolygons2, false).Select(p => p.Path).ToList(); //Offset by enough to account for minimum angle var scale = Math.Tan(minAngle * Math.PI / 180) * depthOfPart; //Remove tiny polygons and slivers solution = PolygonOperations.SimplifyFuzzy(solution); var offsetPolygons = PolygonOperations.OffsetMiter(solution, scale); var significantSolution = PolygonOperations.OffsetMiter(offsetPolygons, -scale); //Presenter.ShowAndHang(significantSolution); return(significantSolution); //.Select(p => p.Path).ToList(); }
/// <summary> /// Gets the silhouette of a solid along a given normal. Depth of part is only used if removing tiny polygons. /// </summary> /// <param name="faces"></param> /// <param name="normal"></param> /// <param name="originalSolid"></param> /// <param name="minAngle"></param> /// <param name="minPathAreaToConsider"></param> /// <param name="depthOfPart"></param> /// <returns></returns> public static List <List <PointLight> > Run(IList <PolygonalFace> faces, double[] normal, TessellatedSolid originalSolid, double minAngle = 0.1, double minPathAreaToConsider = 0.0, double depthOfPart = 0.0) { //Get the positive faces into a dictionary if (minAngle > 4.999) { minAngle = 4.999; //min angle must be between 0 and 5 degrees. 0.1 degree has proven to be good. } //Note also that the offset is based on the min angle. var angleTolerance = Math.Cos((90 - minAngle) * Math.PI / 180); //Angle of 89.9 Degrees from normal var angleTolerance2 = Math.Cos((90 - 5) * Math.PI / 180); //Angle of 85 Degrees from normal var positiveFaces = new HashSet <PolygonalFace>(); var smallFaces = new List <PolygonalFace>(); var allPositives = new Dictionary <int, PolygonalFace>(); var allVertices = new HashSet <Vertex>(); var positiveEdgeFaces = new HashSet <PolygonalFace>(); foreach (var face in faces) { if (face.Area.IsNegligible()) { continue; } var dot = normal.dotProduct(face.Normal, 3); if (dot.IsGreaterThanNonNegligible(angleTolerance2)) { allPositives.Add(face.IndexInList, face); positiveFaces.Add(face); } else if (dot.IsGreaterThanNonNegligible(angleTolerance)) { //allPositives.Add(face.IndexInList, face); positiveEdgeFaces.Add(face); } else if (Math.Sign(dot) > 0 && face.Area < 1.0) { smallFaces.Add(face); } foreach (var vertex in face.Vertices) { allVertices.Add(vertex); } } //Add any small sliver faces that are sandwinched between two positive faces. foreach (var smallFace in smallFaces) { var largerEdges = smallFace.Edges.OrderBy(e => e.Length).Take(2).ToList(); var addToPositives = true; foreach (var edge in largerEdges) { if (edge.OwnedFace == smallFace && allPositives.ContainsKey(edge.OtherFace.IndexInList)) { } else if (edge.OtherFace == smallFace && allPositives.ContainsKey(edge.OwnedFace.IndexInList)) { } else { addToPositives = false; } } if (addToPositives) { //allPositives.Add(smallFace.IndexInList, smallFace); positiveEdgeFaces.Add(smallFace); } } //Get the polygons of all the positive faces. Force the polygons to be positive CCW var vertices = new HashSet <Vertex>(); foreach (var face in allPositives.Values) { foreach (var vertex in face.Vertices) { vertices.Add(vertex); } } var transform = MiscFunctions.TransformToXYPlane(normal, out _); var projectedPoints = new Dictionary <int, PointLight>(); foreach (var vertex in vertices) { projectedPoints.Add(vertex.IndexInList, MiscFunctions.Get2DProjectionPointAsLight(vertex, transform)); } var projectedFacePolygons = positiveFaces.ToDictionary(f => f.IndexInList, f => GetPathFromFace(f, projectedPoints, true)); //Get all the surfaces var allSurfaces = SeperateIntoSurfaces(allPositives); //var colors = new List<Color>() //{ // new Color(KnownColors.Blue), // new Color(KnownColors.Red), // new Color(KnownColors.Green), // new Color(KnownColors.Yellow), // new Color(KnownColors.Purple), // new Color(KnownColors.Pink), // new Color(KnownColors.Orange), // new Color(KnownColors.Turquoise), // new Color(KnownColors.White), // new Color(KnownColors.Tan) //}; //originalSolid.HasUniformColor = false; //var i = 0; //foreach (var surface in allSurfaces) //{ // if (i == colors.Count) i = 0; // var color = colors[i]; // i++; // foreach (var face in surface) // { // face.Color = color; // } //} //Presenter.ShowAndHang(originalSolid); //Get the surface paths from all the surfaces and union them together var solution = GetSurfacePaths(allSurfaces, normal, minPathAreaToConsider, originalSolid, projectedFacePolygons).ToList(); var positiveEdgeFacePolygons = new List <List <PointLight> >(); foreach (var face in positiveEdgeFaces) { var polygon = new PolygonLight(MiscFunctions.Get2DProjectionPointsAsLight(face.Vertices, normal)); if (!polygon.IsPositive) { polygon.Path.Reverse(); } positiveEdgeFacePolygons.Add(polygon.Path); } try //Try to merge them all at once { solution = PolygonOperations.Union(solution, positiveEdgeFacePolygons, false, PolygonFillType.NonZero); } catch { //Do them one at a time, skipping those that fail foreach (var face in positiveEdgeFacePolygons) { try { solution = PolygonOperations.Union(solution, face, false, PolygonFillType.NonZero); } catch { continue; } } } //Offset by enough to account for minimum angle var scale = Math.Tan(minAngle * Math.PI / 180) * depthOfPart; //Remove tiny polygons and slivers //First, Offset out and then perform a quick check for overhang polygons. //This is helpful when the polygon is nearly self-intersecting. //Then offset back out. solution = PolygonOperations.SimplifyFuzzy(solution, Math.Min(scale / 1000, Constants.LineLengthMinimum), Math.Min(angleTolerance / 1000, Constants.LineSlopeTolerance)); var offsetPolygons = PolygonOperations.OffsetMiter(solution, scale); offsetPolygons = EliminateOverhangPolygons(offsetPolygons, projectedFacePolygons); var significantSolution = PolygonOperations.OffsetMiter(offsetPolygons, -scale); return(significantSolution); }