Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        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);
        }
Example #5
0
        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));
        }