/// <summary> /// Construct a solid by sweeping a face along a curve. /// </summary> /// <param name="perimeter">The perimeter of the face to sweep.</param> /// <param name="holes">The holes of the face to sweep.</param> /// <param name="curve">The curve along which to sweep.</param> /// <param name="startSetback">The setback distance of the sweep from the start of the curve.</param> /// <param name="endSetback">The setback distance of the sweep from the end of the curve.</param> /// <returns>A solid.</returns> public static Solid SweepFaceAlongCurve(Polygon perimeter, IList <Polygon> holes, ICurve curve, double startSetback = 0, double endSetback = 0) { var solid = new Solid(); var l = curve.Length(); // The start and end setbacks can't be more than // the length of the beam together. if ((startSetback + endSetback) >= l) { startSetback = 0; endSetback = 0; } // Calculate the setback parameter as a percentage // of the curve length. This will not work for curves // without non-uniform parameterization. var ssb = startSetback / l; var esb = endSetback / l; var transforms = curve.Frames(ssb, esb); if (curve is Polygon) { for (var i = 0; i < transforms.Length; i++) { var next = i == transforms.Length - 1 ? transforms[0] : transforms[i + 1]; solid.SweepPolygonBetweenPlanes(perimeter, transforms[i], next); } } else if (curve is Bezier) { var startCap = solid.AddFace(perimeter, transform: transforms[0]); for (var i = 0; i < transforms.Length - 1; i++) { var next = transforms[i + 1]; solid.SweepPolygonBetweenPlanes(perimeter, transforms[i], next); } var endCap = solid.AddFace(perimeter, transform: transforms[transforms.Length - 1], reverse: true); } else { // Add start cap. Face cap = null; Edge[][] openEdges; if (holes != null) { cap = solid.AddFace(perimeter, holes, transform: transforms[0]); openEdges = new Edge[1 + holes.Count][]; } else { cap = solid.AddFace(perimeter, transform: transforms[0]); openEdges = new Edge[1][]; } // last outer edge var openEdge = cap.Outer.GetLinkedEdges(); openEdge = solid.SweepEdges(transforms, openEdge); openEdges[0] = openEdge; if (holes != null) { for (var i = 0; i < cap.Inner.Length; i++) { openEdge = cap.Inner[i].GetLinkedEdges(); // last inner edge for one hole openEdge = solid.SweepEdges(transforms, openEdge); openEdges[i + 1] = openEdge; } } solid.Cap(openEdges, true); } return(solid); }
/// <summary> /// Compute the difference of two solids. /// </summary> /// <param name="a">The first solid.</param> /// <param name="aTransform">A local transformation of a.</param> /// <param name="b">The second solid.</param> /// <param name="bTransform">A local transformation of b.</param> /// <returns>A solid which is the difference of a and b.</returns> public static Solid Difference(Solid a, Transform aTransform, Solid b, Transform bTransform) { var allFaces = Intersect(a, aTransform, b, bTransform); var s = new Solid(); // Group the faces according to their classification. // AOutsideB for everything outside B which should remain. // AInsideB for everything inside A which should become a hole. var outsideFaces = allFaces.Where(o => o.classification == SetClassification.AOutsideB).ToList(); // TODO: The following is a hack because our Polygon.IntersectOneToMany // method returns overlapping polygons where there are disjoint polygons. var insideFaces = allFaces.Where(i => i.classification == SetClassification.AInsideB).ToList(); foreach (var(polygon, classification, coplanarClassification) in outsideFaces) { if (insideFaces.Count == 0) { s.AddFace(polygon, mergeVerticesAndEdges: true); } else { var plane = polygon._plane; var holes = new List <Polygon>(); foreach (var insideFace in insideFaces) { if (polygon.Contains3D(insideFace.polygon)) { // We need to do this edge overlap check to ensure // that we're not making a profile where the openings // overlaps the edges of the perimeter, creating a face // with zero thickness between the outer and inner // loops. var hasEdgeOverlap = false; foreach (var(from, to) in polygon.Edges()) { foreach (var edge in insideFace.polygon.Edges()) { if (from.DistanceTo(edge, out _).ApproximatelyEquals(0) && to.DistanceTo(edge, out _).ApproximatelyEquals(0)) { hasEdgeOverlap = true; break; } } } if (!hasEdgeOverlap) { if (plane.Normal.Dot(insideFace.polygon._plane.Normal).ApproximatelyEquals(1.0)) { holes.Add(insideFace.polygon.Reversed()); } else { holes.Add(insideFace.polygon); } } } } s.AddFace(polygon, holes, mergeVerticesAndEdges: true); } } // TODO: Can we invert the faces first? var bInsideFaces = allFaces.Where(o => o.classification == SetClassification.BInsideA).ToList(); foreach (var(polygon, classification, coplanarClassification) in bInsideFaces) { s.AddFace(polygon.Reversed(), mergeVerticesAndEdges: true); } var result = MergeCoplanarFaces(allFaces, Difference); if (result != null) { foreach (var(perimeter, holes) in result) { s.AddFace(perimeter, holes, mergeVerticesAndEdges: true); } } return(s); }
/// <summary> /// Construct a solid by sweeping a face in a direction. /// </summary> /// <param name="perimeter">The perimeter of the face to sweep.</param> /// <param name="holes">The holes of the face to sweep.</param> /// <param name="direction">The direction in which to sweep.</param> /// <param name="distance">The distance to sweep.</param> /// <param name="bothSides">Should the sweep start offset by direction distance/2? </param> /// <param name="rotation">An optional rotation in degrees of the perimeter around the direction vector.</param> /// <returns>A solid.</returns> public static Solid SweepFace(Polygon perimeter, IList <Polygon> holes, Vector3 direction, double distance, bool bothSides = false, double rotation = 0.0) { // We do a difference of the polygons // to get the clipped shape. This will fail in interesting // ways if the clip creates two islands. // if(holes != null) // { // var newPerimeter = perimeter.Difference(holes); // perimeter = newPerimeter[0]; // holes = newPerimeter.Skip(1).Take(newPerimeter.Count - 1).ToArray(); // } var solid = new Solid(); Face fStart = null; if (bothSides) { var t = new Transform(direction.Negate() * (distance / 2), rotation); if (holes != null) { fStart = solid.AddFace((Polygon)perimeter.Reversed().Transformed(t), t.OfPolygons(holes.Reversed())); } else { fStart = solid.AddFace((Polygon)perimeter.Reversed().Transformed(t)); } } else { if (holes != null) { fStart = solid.AddFace(perimeter.Reversed(), holes.Reversed()); } else { fStart = solid.AddFace(perimeter.Reversed()); } } var fEndOuter = solid.SweepLoop(fStart.Outer, direction, distance); if (holes != null) { var fEndInner = new Loop[holes.Count]; for (var i = 0; i < holes.Count; i++) { fEndInner[i] = solid.SweepLoop(fStart.Inner[i], direction, distance); } solid.AddFace(fEndOuter, fEndInner); } else { solid.AddFace(fEndOuter); } return(solid); }
private static List <(Polygon polygon, SetClassification classification, CoplanarSetClassification coplanarClassification)> Intersect(Solid a, Transform aTransform, Solid b, Transform bTransform) { var allFaces = new List <(Polygon, SetClassification, CoplanarSetClassification)>(); // TODO: Don't create polygons. Operate on the loops and edges directly. // TODO: Support holes. We drop the inner loop information here currently. // An explanation of what's going on here. // At a high level, the act of intersecting two solids is the intersection and splitting // of the solid's faces against those of the other solid, and the classification of the // split pieces as: // A inside B (ex. a piece of A which is inside B) // A outside B // B inside A // B outside A // A coplanar B // B coplanar A // 1. Sort the faces of a and b into two collections -> non-coplanar faces, and co-planar faces. // 2. Split and classify each non-coplanar face of a against all the faces of b which it intersects. // 3. Split and classify each non-coplanar face of b against all the faces of a which it intersects. // 3. Intersect coplanar faces or return faces which are contained in other coplanar faces. Coplanar faces // contained within other faces, will become holes. var aFaces = a.Faces.Select(f => f.Value.Outer.ToPolygon(aTransform)).ToList(); var bFaces = b.Faces.Select(f => f.Value.Outer.ToPolygon(bTransform)).ToList(); var aCoplanarFaces = aFaces.Where(af => bFaces.Any(bf => bf._plane.IsCoplanar(af._plane))).ToList(); var bCoplanarFaces = bFaces.Where(bf => aFaces.Any(af => af._plane.IsCoplanar(bf._plane))).ToList(); var aNonCoplanar = aFaces.Except(aCoplanarFaces).ToList(); var bNonCoplanar = bFaces.Except(bCoplanarFaces).ToList(); foreach (var af in aNonCoplanar) { var classifications = af.IntersectAndClassify(bNonCoplanar.Where(bf => bf._bounds.Intersects(af._bounds)).ToList(), bFaces, out _, out _, SetClassification.AOutsideB, SetClassification.AInsideB); allFaces.AddRange(classifications); } foreach (var bf in bNonCoplanar) { var classifications = bf.IntersectAndClassify(aNonCoplanar.Where(af => af._bounds.Intersects(bf._bounds)).ToList(), aFaces, out _, out _, SetClassification.BOutsideA, SetClassification.BInsideA); allFaces.AddRange(classifications); } var aCoplanarFaceSets = aCoplanarFaces.GroupBy(af => af._plane.Normal); var bCoplanarFaceSets = bCoplanarFaces.GroupBy(af => af._plane.Normal); foreach (var aCoplanarFaceSet in aCoplanarFaceSets) { foreach (var aFace in aCoplanarFaceSet) { var bCoplanarFaceSet = bCoplanarFaceSets.FirstOrDefault(x => x.Key == aCoplanarFaceSet.Key); if (bCoplanarFaceSet != null) { foreach (var bFace in bCoplanarFaceSet) { // The b face is completely contained within the a face, or is outside of it. // In the former case, we return the face to become a hole. In the latter case, // we return the face to become a stand-alone face. if (aFace.Contains3D(bFace) || !aFace._bounds.Intersects(bFace._bounds)) { var aa = (aFace, SetClassification.None, CoplanarSetClassification.ACoplanarB); var bb = (bFace, SetClassification.None, CoplanarSetClassification.BCoplanarA); if (!allFaces.Contains(aa)) { allFaces.Add(aa); } if (!allFaces.Contains(bb)) { allFaces.Add(bb); } } // The b face intersects the a face. We do a planar intersection of the two faces, which // adds new vertices to the a face and the b face. These faces are marked as coplanar, // for later 2D boolean operations. else if (aFace.Intersects2d(bFace, out List <(Vector3 result, int aSegumentIndices, int bSegmentIndices)> planarIntersectionResults, false)) { var result = planarIntersectionResults.Select(r => r.result).ToList(); aFace.Split(result); allFaces.Add((aFace, SetClassification.None, CoplanarSetClassification.ACoplanarB)); bFace.Split(result); allFaces.Add((bFace, SetClassification.None, CoplanarSetClassification.BCoplanarA)); } } } } } return(allFaces); }
internal static Csg.Solid ToCsg(this Solid solid) { var polygons = new List <Csg.Polygon>(); foreach (var f in solid.Faces.Values) { if (f.Inner != null && f.Inner.Count() > 0) { var tess = new Tess { NoEmptyPolygons = true }; tess.AddContour(f.Outer.ToContourVertexArray()); foreach (var loop in f.Inner) { tess.AddContour(loop.ToContourVertexArray()); } tess.Tessellate(WindingRule.Positive, LibTessDotNet.Double.ElementType.Polygons, 3); Vector3 e1 = new Vector3(); Vector3 e2 = new Vector3(); var vertices = new List <Csg.Vertex>(tess.Elements.Count() * 3); for (var i = 0; i < tess.ElementCount; i++) { var a = tess.Vertices[tess.Elements[i * 3]].ToCsgVector3(); var b = tess.Vertices[tess.Elements[i * 3 + 1]].ToCsgVector3(); var c = tess.Vertices[tess.Elements[i * 3 + 2]].ToCsgVector3(); Csg.Vertex av = null; Csg.Vertex bv = null; Csg.Vertex cv = null; if (i == 0) { var n = f.Plane().Normal; e1 = n.Cross(n.IsParallelTo(Vector3.XAxis) ? Vector3.YAxis : Vector3.XAxis).Unitized(); e2 = n.Cross(e1).Unitized(); } if (av == null) { var avv = new Vector3(a.X, a.Y, a.Z); av = new Csg.Vertex(a, new Csg.Vector2D(e1.Dot(avv), e2.Dot(avv))); vertices.Add(av); } if (bv == null) { var bvv = new Vector3(b.X, b.Y, b.Z); bv = new Csg.Vertex(b, new Csg.Vector2D(e1.Dot(bvv), e2.Dot(bvv))); vertices.Add(bv); } if (cv == null) { var cvv = new Vector3(c.X, c.Y, c.Z); cv = new Csg.Vertex(c, new Csg.Vector2D(e1.Dot(cvv), e2.Dot(cvv))); vertices.Add(cv); } var p = new Csg.Polygon(new List <Csg.Vertex>() { av, bv, cv }); polygons.Add(p); } } else { var verts = new List <Csg.Vertex>(f.Outer.Edges.Count); var n = f.Plane().Normal; var e1 = n.Cross(n.IsParallelTo(Vector3.XAxis) ? Vector3.YAxis : Vector3.XAxis).Unitized(); var e2 = n.Cross(e1).Unitized(); foreach (var e in f.Outer.Edges) { var l = e.Vertex.Point; var v = new Csg.Vertex(l.ToCsgVector3(), new Csg.Vector2D(e1.Dot(l), e2.Dot(l))); verts.Add(v); } var p = new Csg.Polygon(verts); polygons.Add(p); } } return(Csg.Solid.FromPolygons(polygons)); }