예제 #1
0
파일: PolyCurve.cs 프로젝트: lulzzz/Nucleus
        /// <summary>
        /// Generates a trapezoid polygon on the XY plane with the midpoint of each edge
        /// centred on the origin and with a different top and bottom width.
        /// </summary>
        /// <param name="depth">The depth of the trapezoid</param>
        /// <param name="topWidth">The width of the top of the trapezoid</param>
        /// <param name="baseWidth">The width of the base of the trapezoid</param>
        /// <returns></returns>
        public static PolyCurve Trapezoid(double depth, double topWidth, double baseWidth)
        {
            double xT = topWidth / 2;
            double xB = baseWidth / 2;
            double y  = depth / 2;

            PolyCurve result = new PolyCurve(new Line(xT, y, -xT, y));

            result.AddLine(-xB, -y);
            result.AddLine(xB, -y);
            result.AddLine(xT, y);

            return(result);
        }
예제 #2
0
파일: PolyCurve.cs 프로젝트: lulzzz/Nucleus
        /// <summary>
        /// Extract a portion of this curve as a new curve
        /// </summary>
        /// <param name="subDomain">The subdomain of this curve to
        /// be extracted as a new curve</param>
        /// <returns></returns>
        public override Curve Extract(Interval subDomain)
        {
            var result = new PolyCurve();

            Interval subDomainAdj = subDomain;

            if (subDomain.IsDecreasing)
            {
                subDomainAdj = new Interval(subDomain.Start, 1.0);
            }

            PopulateWithSubCurves(subDomainAdj, result);

            // If looping, do a second pass:
            if (Closed && subDomain.IsDecreasing)
            {
                PopulateWithSubCurves(new Maths.Interval(0.0, subDomain.End), result);
            }

            return(result);
        }
예제 #3
0
        /// <summary>
        /// Get a closed polycurve containing the edges of this section of the path.
        /// The edges of the path must have been generated first.
        /// </summary>
        /// <returns></returns>
        public static PolyCurve GetBoundaryCurve <TPath>(this TPath path)
            where TPath : IWidePath
        {
            var result = new PolyCurve();

            if (path.StartCapLeft != null)
            {
                result.Add(path.StartCapLeft);
            }

            if (path.LeftEdge != null)
            {
                result.Add(path.LeftEdge, true);
            }

            if (path.EndCapLeft != null)
            {
                result.Add(path.EndCapLeft, true);
            }

            if (path.EndCapRight != null)
            {
                result.Add(path.EndCapRight.Reversed(), true);
            }

            if (path.RightEdge != null)
            {
                result.Add(path.RightEdge.Reversed(), true);
            }

            if (path.StartCapRight != null)
            {
                result.Add(path.StartCapRight.Reversed(), true);
            }

            result.Close();

            return(result);
        }
예제 #4
0
        /// <summary>
        /// Split this region into two (or more) sub-regions along a straight line
        /// </summary>
        /// <param name="splitPt">A point on the splitting line</param>
        /// <param name="splitDir">The direction of the line</param>
        /// <param name="splitWidth">Optional.  The width of the split.</param>
        /// <returns>The resultant list of regions.  If the line does not bisect
        /// this region and the region could not be split, this collection will contain
        /// only the original region.</returns>
        public IList <PlanarRegion> SplitByLineXY(Vector splitPt, Vector splitDir, double splitWidth = 0)
        {
            var result    = new List <PlanarRegion>();
            var lineInts  = new List <double>();
            var outerInts = Intersect.CurveLineXY(Perimeter, splitPt, splitDir, null, 0, 1, false, lineInts);

            if (outerInts.Count > 1)
            {
                // Sort intersections by position along curve:
                var sortedInts = new SortedList <double, double>(outerInts.Count);
                for (int i = 0; i < outerInts.Count; i++)
                {
                    sortedInts.Add(outerInts[i], lineInts[i]);
                }

                outerInts = sortedInts.Keys.ToList();
                lineInts  = sortedInts.Values.ToList();
                int offset = lineInts.IndexOfMin();
                outerInts.Shift(offset);
                lineInts.Shift(offset);

                // Create segments data structure
                var segments = new List <PerimeterSegment>(outerInts.Count - 1);
                for (int i = 0; i < outerInts.Count; i++)
                {
                    double t0      = outerInts[i];
                    double t1      = outerInts.GetWrapped(i + 1);
                    double tC0     = lineInts[i];
                    double tC1     = lineInts.GetWrapped(i + 1);
                    var    segment = new PerimeterSegment(Perimeter, t0, t1, tC0, tC1);
                    segments.Add(segment);
                }

                //TODO: void intersections

                bool backwards = true;
                while (segments.Count > 0)
                {
                    var offsets = new List <double>();
                    PerimeterSegment segment = segments.First();

                    PolyCurve newPerimeter = segment.Extract().ToPolyCurve();
                    for (int i = 0; i < newPerimeter.SegmentCount; i++)
                    {
                        offsets.Add(0);
                    }
                    PerimeterSegment nextSegment = FindNextPerimeterSegment(segments, segment.CutterDomain.End, backwards);
                    while (nextSegment != null && nextSegment != segment)
                    {
                        Curve nextCurve = nextSegment.Extract();
                        newPerimeter.AddLine(nextCurve.StartPoint);
                        offsets.Add(splitWidth / 2);
                        newPerimeter.Add(nextCurve);
                        for (int i = 0; i < nextCurve.SegmentCount; i++)
                        {
                            offsets.Add(0);
                        }
                        segments.Remove(nextSegment);
                        nextSegment = FindNextPerimeterSegment(segments, nextSegment.CutterDomain.End, backwards);
                    }
                    segments.RemoveAt(0);
                    if (!newPerimeter.Closed)
                    {
                        /*if (splitWidth > 0)
                         * {
                         *  // Temporary bodge to get rid of 'blades' at ends of split
                         *  Vector endToEnd = (newPerimeter.StartPoint - newPerimeter.EndPoint).Unitize();
                         *  var line = new Line(
                         *      newPerimeter.EndPoint - endToEnd * splitWidth / 4,
                         *      newPerimeter.StartPoint + endToEnd * splitWidth / 4);
                         *  newPerimeter.AddLine(line.StartPoint);
                         *  offsets.Add(0);
                         *  newPerimeter.Add(line);
                         *  offsets.Add(splitWidth / 2);
                         *  newPerimeter.Close();
                         *  offsets.Add(0);
                         * }
                         * else
                         * {*/
                        newPerimeter.Close();
                        offsets.Add(splitWidth / 2);
                        //}
                    }
                    backwards = !backwards;

                    if (splitWidth > 0)
                    {
                        var newNewPerimeter = newPerimeter.OffsetInwards(offsets);
                        // Check offset has not inverted perimeter:
                        // TODO: Do this automatically when offsetting?
                        if (newNewPerimeter != null && newNewPerimeter.IsClockwiseXY() == newPerimeter.IsClockwiseXY())
                        {
                            newPerimeter = newNewPerimeter.ToPolyCurve();
                        }
                        else
                        {
                            newPerimeter = null;
                        }
                    }

                    if (newPerimeter != null)
                    {
                        result.Add(new PlanarRegion(newPerimeter, Attributes?.Duplicate()));
                    }
                }

                // OLD VERSION:

                /*for (int i = 0; i < outerInts.Count; i++)
                 * {
                 *  double t0 = outerInts[i];
                 *  double t1 = outerInts.GetWrapped(i + 1);
                 *  Curve newPerimeter = Perimeter.Extract(new Interval(t0, t1))?.ToPolyCurve();
                 *  //TODO: Cut through and include voids
                 *  if (!newPerimeter.Closed)
                 *  {
                 *      ((PolyCurve)newPerimeter).Close();
                 *      if (splitWidth > 0)
                 *      {
                 *          var offsets = new double[newPerimeter.SegmentCount];
                 *          offsets[offsets.Length - 1] = splitWidth / 2;
                 *          var newNewPerimeter = newPerimeter.OffsetInwards(offsets);
                 *          // Check offset has not inverted perimeter:
                 *          // TODO: Do this automatically when offsetting?
                 *          if (newNewPerimeter != null && newNewPerimeter.IsClockwiseXY() == newPerimeter.IsClockwiseXY())
                 *          {
                 *              newPerimeter = newNewPerimeter;
                 *          }
                 *          else newPerimeter = null;
                 *      }
                 *  }
                 *  if (newPerimeter != null) result.Add(new PlanarRegion(newPerimeter, Attributes?.Duplicate()));
                 * }*/
            }
            else
            {
                result.Add(this); //Return the original
            }
            return(result);
        }
예제 #5
0
        /// <summary>
        /// Split this region into two (or more) sub-regions along a straight line
        /// </summary>
        /// <param name="splitPt">A point on the splitting line</param>
        /// <param name="splitDir">The direction of the line</param>
        /// <param name="splitWidth">Optional.  The width of the split.</param>
        /// <returns>The resultant list of regions.  If the line does not bisect
        /// this region and the region could not be split, this collection will contain
        /// only the original region.</returns>
        public IList <PlanarRegion> SplitByLineXY2(Vector splitPt, Vector splitDir, double splitWidth = 0)
        {
            var result = new List <PlanarRegion>();

            // Offset splitting line by half splitWidth in each direction:
            Vector offsetDir = splitDir.PerpendicularXY().Unitize();
            Vector offset    = offsetDir * splitWidth / 2;
            Vector leftPt    = splitPt + offset;
            Vector rightPt   = splitPt - offset;

            // Find intersection points on perimeter:
            var perimeterInts = new List <CurveLineIntersection>();

            // Left side:
            IList <CurveLineIntersection> leftInts = Intersect.CurveLineXYIntersections(Perimeter, leftPt, splitDir);

            perimeterInts.AddRange(leftInts);

            IList <CurveLineIntersection> rightInts;

            // Shortcut: If the split has no width, left and right intersections will be the same
            if (splitWidth == 0)
            {
                rightInts = leftInts;
            }
            else
            {
                rightInts = Intersect.CurveLineXYIntersections(Perimeter, rightPt, splitDir);
                perimeterInts.AddRange(rightInts);
            }

            // If no intersections on the perimeter, the region does not need to be split:
            if (leftInts.Count <= 1 && rightInts.Count <= 1)
            {
                result.Add(this);
                return(result);
            }

            // Voids which do not intersect but will need to be placed:
            CurveCollection freeVoids = new CurveCollection();

            // Find intersection points on voids:
            foreach (var voidCrv in Voids)
            {
                // Left side:
                var leftVoidInts = Intersect.CurveLineXYIntersections(voidCrv, leftPt, splitDir);
                foreach (var vI in leftVoidInts)
                {
                    leftInts.Add(vI);
                }

                IList <CurveLineIntersection> rightVoidInts;
                // Shortcut: If the split has no width, left and right intersections will be the same
                if (splitWidth == 0)
                {
                    rightVoidInts = leftVoidInts;
                }
                else
                {
                    rightVoidInts = Intersect.CurveLineXYIntersections(voidCrv, rightPt, splitDir);
                }
                foreach (var vI in rightVoidInts)
                {
                    rightInts.Add(vI);
                }

                if (leftVoidInts.Count <= 1 && rightVoidInts.Count <= 1)
                {
                    // Void is wholly to one side or the other...
                    freeVoids.Add(voidCrv);
                }
            }

            // Tag left and right intersections
            foreach (var lI in leftInts)
            {
                lI.Side = HandSide.Left;
            }
            foreach (var rI in rightInts)
            {
                rI.Side = HandSide.Right;
            }

            // Determine direction of travel along cutting lines:
            int rightDir = RightCutterDirectionOfTravel(perimeterInts);

            // 'Fake' intersection to represent perimeter start:
            CurveLineIntersection startInt = new CurveLineIntersection(Perimeter, 0, double.NaN);

            // Loop through intersections and build up new sub-regions by traversing connected
            // intersections along the perimeter, cutting lines and void curves
            CurveLineIntersection currentInt   = startInt;
            CurveLineIntersection nextStartInt = null;
            PolyCurve             newPerimeter = null;
            PlanarRegion          newRegion    = null;
            // The cutter to travel along (Undefined is perimeter or void cuve):
            HandSide travelSide   = HandSide.Undefined;
            int      segmentCount = perimeterInts.Count + 1; //TODO: Include void intersections

            for (int i = 0; i < segmentCount; i++)           // Loop through intersections
            {
                CurveLineIntersection nextInt;

                // Travel to next intersection, along the cutters or perimeter
                if (travelSide == HandSide.Left)
                {
                    // Travel along left cutter
                    if (rightDir == 1)
                    {
                        nextInt = leftInts.ItemWithPrevious(cLI => cLI.LineParameter, currentInt.LineParameter);
                    }
                    else
                    {
                        nextInt = leftInts.ItemWithNext(cLI => cLI.LineParameter, currentInt.LineParameter);
                    }
                }
                else if (travelSide == HandSide.Right)
                {
                    // Travel along right cutter
                    if (rightDir == 1)
                    {
                        nextInt = rightInts.ItemWithNext(cLI => cLI.LineParameter, currentInt.LineParameter);
                    }
                    else
                    {
                        nextInt = rightInts.ItemWithPrevious(cLI => cLI.LineParameter, currentInt.LineParameter);
                    }
                }
                else
                {
                    // Travel to next intersection on perimeter
                    nextInt = perimeterInts.ItemWithNext(cLI => cLI.CurveParameter, currentInt.CurveParameter);
                    // 'Fake' intersection to represent perimeter end:
                    if (nextInt == null)
                    {
                        nextInt = new CurveLineIntersection(Perimeter, 1, double.NaN);
                    }
                    if (nextStartInt == null)
                    {
                        nextStartInt = nextInt;
                    }
                }


                if (nextInt != null && currentInt.Curve == nextInt.Curve &&  // Check both ints on same curve
                    (travelSide == HandSide.Undefined || currentInt.Curve != Perimeter))
                {
                    Curve    curve     = nextInt.Curve;
                    Interval subDomain = new Interval(currentInt.CurveParameter, nextInt.CurveParameter);
                    Vector   crvPt     = curve.PointAt(subDomain.Mid);
                    HandSide side      = SideOfSplit(crvPt, leftPt, rightPt, offsetDir);
                    if (side != HandSide.Undefined)
                    {
                        // TODO: Adjust void curve direction

                        // Add perimeter to outputs
                        Curve subCrv = curve.Extract(subDomain);
                        if (newPerimeter == null)
                        {
                            newPerimeter = new PolyCurve(subCrv.Explode());
                            newRegion    = new PlanarRegion(newPerimeter);
                            result.Add(newRegion);
                        }
                        else
                        {
                            newPerimeter.Add(subCrv, true, true);
                        }
                    }
                    else
                    {
                        //Traversing the gap, need a new start Point
                        startInt = nextInt;
                    }
                }

                // Next side to travel:
                if (travelSide != HandSide.Undefined &&
                    (nextInt == null || nextInt.Curve == Perimeter))
                {
                    travelSide = HandSide.Undefined;
                }
                else
                {
                    travelSide = nextInt.Side;
                }

                if (nextInt == null || nextInt == startInt || nextInt.CurveParameter >= 1)
                {
                    // Reset to start next segment
                    nextInt      = nextStartInt;
                    startInt     = nextStartInt;
                    nextStartInt = null;
                    if (newPerimeter != null)
                    {
                        newPerimeter.Close();
                    }
                    newPerimeter = null;
                    newRegion    = null;
                    travelSide   = HandSide.Undefined;
                }
                currentInt = nextInt;
            }
            if (newPerimeter != null)
            {
                newPerimeter.Close();
            }

            //TODO: Assign un-intersected voids
            return(result);
        }
예제 #6
0
파일: PolyCurve.cs 프로젝트: lulzzz/Nucleus
        /// <summary>
        /// Offset this curve on the XY plane by varying distances for
        /// each span.
        /// </summary>
        /// <param name="distances">The offset distance.
        /// Positive numbers will result in the offset curve being to the right-hand
        /// side, looking along the curve.  Negative numbers to the left.</param>
        /// <param name="tidy">If true (default) collapsed segments will be removed.</param>
        /// <returns></returns>
        public override Curve Offset(IList <double> distances, bool tidy = true, bool copyAttributes = true)
        {
            //TODO: Implement collapsed segments tidying

            var result    = new PolyCurve();
            int distIndex = 0;

            // Offset sub curves:
            foreach (Curve crv in SubCurves)
            {
                Curve offsetCrv = crv.Offset(distances.SubListFrom(distIndex));
                distIndex += crv.SegmentCount;
                if (distIndex > distances.Count - 1)
                {
                    distIndex = distances.Count - 1;
                }

                if (offsetCrv != null)
                {
                    if (result.SubCurves.Count > 0)
                    {
                        // Adjust offset curve ends to node out
                        Curve prevCrv = result.SubCurves.Last();
                        MatchEnds(prevCrv.End, offsetCrv.Start);
                    }
                    result.Add(offsetCrv);
                    if (copyAttributes && offsetCrv.Attributes == null)
                    {
                        offsetCrv.Attributes = Attributes;
                    }
                }
            }

            // Match end to start
            if (Closed && result.SubCurves.Count > 1)
            {
                MatchEnds(result.SubCurves.Last().End, result.SubCurves.First().Start);
            }

            if (tidy)
            {
                List <Curve> originals = new List <Curve>();
                originals.AddRange(SubCurves);

                // Removed flipped segments
                int j = 0;
                while (j < result.SubCurves.Count)
                {
                    Curve offset   = result.SubCurves[j];
                    Curve original = originals[j];

                    if (IsOffsetCurveFlipped(original, offset))
                    {
                        if (result.SubCurves.Count > 0)
                        {
                            Curve previous   = result.SubCurves.GetWrapped(j - 1);
                            Curve subsequent = result.SubCurves.GetWrapped(j + 1);
                            bool  worked     = MatchEnds(previous.End, subsequent.Start);
                            //TODO: Deal with failed matches (for e.g. when adjacent edges are parallel)
                            //if (!worked)
                            //return null; //TEMP: prevents invalid offsets, but a bit overkilly!
                        }
                        result.SubCurves.RemoveAt(j);
                        originals.RemoveAt(j);
                        //j--;
                    }
                    else
                    {
                        j++;
                    }
                }
                if (!result.IsValid)
                {
                    return(null);
                }
            }

            return(result);
        }
예제 #7
0
파일: PolyCurve.cs 프로젝트: lulzzz/Nucleus
 /// <summary>
 /// Creates a new polycurve as a copy of another one
 /// </summary>
 /// <param name="other"></param>
 public PolyCurve(PolyCurve other)
     : this(other.FastDuplicateSubCurves(), other.Attributes)
 {
 }
예제 #8
0
파일: PolyCurve.cs 프로젝트: lulzzz/Nucleus
        /// <summary>
        /// Extract from this PolyCurve a chain of subcurves, starting with the
        /// longest and continuing to select the longest edge until no more can
        /// be added without adding an edge which is within an angle tolerance of
        /// an existing curve within the chain.
        /// </summary>
        /// <returns></returns>
        public PolyCurve ExtractLongCurveChain(Angle maxAngle)
        {
            Curve longest = SubCurves.GetLongest();

            if (longest != null)
            {
                var tangents = new List <Vector>();
                tangents.Add(longest.TangentAt(0.5));
                var  result  = new PolyCurve(longest);
                int  iL      = SubCurves.IndexOf(longest);
                int  i0      = iL - 1;
                int  i1      = iL + 1;
                bool closed  = Closed;
                bool tryPre  = closed || i0 >= 0;
                bool tryPost = closed || i1 < SubCurves.Count;
                while (tryPre || tryPost)
                {
                    Curve pre  = tryPre ? SubCurves.GetWrapped(i0, closed) : null;
                    Curve post = tryPost ? SubCurves.GetWrapped(i1, closed) : null;
                    if (post != null && (pre == null || post.Length > pre.Length))
                    {
                        Vector tang = post.TangentAt(0.5);
                        if (result.SubCurves.Contains(post.GUID) || tangents.MaximumAngleBetween(tang).Abs() > maxAngle)
                        {
                            tryPost = false;
                        }
                        else
                        {
                            result.SubCurves.Add(post);
                            tangents.Add(tang);
                            i1++;
                            if (!closed && i1 >= SubCurves.Count)
                            {
                                tryPost = false;
                            }
                        }
                    }
                    else if (pre != null)
                    {
                        Vector tang = pre.TangentAt(0.5);
                        if (result.SubCurves.Contains(pre.GUID) || tangents.MaximumAngleBetween(tang).Abs() > maxAngle)
                        {
                            tryPre = false;
                        }
                        else
                        {
                            result.SubCurves.Insert(0, pre);
                            tangents.Add(tang);
                            i0--;
                            if (!closed && i0 < 0)
                            {
                                tryPre = false;
                            }
                        }
                    }
                    else
                    {
                        tryPre  = false;
                        tryPost = false;
                    }
                }
                return(result);
            }
            return(null);
        }