/// <summary> /// Gets the two portions of a closed contour corresponding to the part where the u-parameter is monotonic increasing and where it is monotonic decreasing. /// </summary> /// <param name="contourPoints">The set of points belonging to the contour. </param> /// <param name="increasingChunk">The <see cref="ExtrudedContourMonotonicChunk"/> with increasing u-parameters. </param> /// <param name="decreasingChunk">The <see cref="ExtrudedContourMonotonicChunk"/> with decreasing u-parameters. </param> /// <returns> True if the two monotonic chunks were found; False if the contour points didn't consist of exactly two monotonic (in u-parameter) portions, one increasing and the other decreasing. </returns> internal static bool GetExtrudedContourMonotonicChunks(IList <Vector2WithUV> contourPoints, out ExtrudedContourMonotonicChunk increasingChunk, out ExtrudedContourMonotonicChunk decreasingChunk) { increasingChunk = null; decreasingChunk = null; bool gotChunks = false; int minUIndex = -1, maxUIndex = -1; float minU = float.PositiveInfinity, maxU = float.NegativeInfinity; bool isPeriodic = contourPoints[0].Equals(contourPoints[contourPoints.Count - 1]); int numPoints = isPeriodic ? contourPoints.Count - 1 : contourPoints.Count; if (numPoints > 0) { for (int i = 0; i < numPoints; i++) { var point = contourPoints[i]; var u = point.UV.x; if (u < minU) { minU = u; minUIndex = i; } if (u > maxU) { maxU = u; maxUIndex = i; } } Vector2WithUV previousPoint = contourPoints[minUIndex]; List <Vector2WithUV> increasingPoints = new List <Vector2WithUV>() { previousPoint }; int maxIndexShifted = maxUIndex < minUIndex ? maxUIndex + numPoints : maxUIndex; bool correctUDirection = true; for (int increasingIndex = minUIndex + 1; increasingIndex <= maxIndexShifted; increasingIndex++) { var point = contourPoints[increasingIndex % numPoints]; if (point.UV.x >= previousPoint.UV.x) { increasingPoints.Add(point); } else { correctUDirection = false; break; } } if (correctUDirection) { increasingChunk = new ExtrudedContourMonotonicChunk(increasingPoints, minU, maxU, isReversed: false); } int minIndexShifted = minUIndex < maxUIndex ? minUIndex + numPoints : minUIndex; previousPoint = contourPoints[minIndexShifted % numPoints]; List <Vector2WithUV> decreasingPoints = new List <Vector2WithUV>() { previousPoint }; correctUDirection = true; for (int decreasingIndex = minIndexShifted - 1; decreasingIndex >= maxUIndex; decreasingIndex--) { var point = contourPoints[decreasingIndex % numPoints]; if (point.UV.x >= previousPoint.UV.x)//Since we're going through decreasing points in reverse order { decreasingPoints.Add(point); } else { correctUDirection = false; break; } } if (correctUDirection) { decreasingChunk = new ExtrudedContourMonotonicChunk(decreasingPoints, minU, maxU, isReversed: true); } gotChunks = increasingChunk != null && decreasingChunk != null && increasingChunk.Points.Count >= 1 && decreasingChunk.Points.Count >= 1; } return(gotChunks); }
/// <summary> /// Returns if two connected segment points are close enough to be considered essentially duplicates. /// </summary> /// <param name="connectedSegmentPoint">A connected segment point.</param> /// <param name="neighbouringConnectedSegmentPoint">A neighbouring connected segment point.</param> private static bool ConnectedSegmentPointsAreClose(Vector2WithUV connectedSegmentPoint, Vector2WithUV neighbouringConnectedSegmentPoint) { const float epsilonDistance = 1e-5f; const float epsilonU = 1e-5f; bool withinDistance = (connectedSegmentPoint.Vector - neighbouringConnectedSegmentPoint.Vector).magnitude < epsilonDistance; bool withinU = System.Math.Abs(connectedSegmentPoint.UV.x - neighbouringConnectedSegmentPoint.UV.x) < epsilonU; return(withinDistance && withinU); }
/// <summary> /// For a contour consisting of one portion monotonically increasing in u-parameter and the other monotonically decreasing, return the <see cref="SingleContourSegmentwiseCoverage"/> /// where there are matching points (at the same u-parameter) on both the increasing and decreasing portions. /// </summary> /// <param name="monotonicIncreasingChunk">Portion of contour with monotonically increasing u-parameters</param> /// <param name="monotonicDecreasingChunk">Portion of contour with monotonically decreasing u-parameters</param> internal static SingleContourSegmentwiseCoverage GetSingleContourSegmentwiseCoverage(ExtrudedContourMonotonicChunk monotonicIncreasingChunk, ExtrudedContourMonotonicChunk monotonicDecreasingChunk) { List <UParameterIndexAndMonotonicity> allContourPointUParameters = new List <UParameterIndexAndMonotonicity>(); List <Vector2WithUV> monotonicIncreasingPoints = monotonicIncreasingChunk.Points; List <Vector2WithUV> monotonicDecreasingPoints = monotonicDecreasingChunk.Points; //Skip the last point in each chunk so as to not double-count the 'endpoints' of min/max u-parameter for (int i = 0; i < monotonicIncreasingPoints.Count - 1; i++) { allContourPointUParameters.Add(new UParameterIndexAndMonotonicity(monotonicIncreasingPoints[i].UV.x, i, true)); } for (int i = 1; i < monotonicDecreasingPoints.Count; i++) { allContourPointUParameters.Add(new UParameterIndexAndMonotonicity(monotonicDecreasingPoints[i].UV.x, i, false)); } allContourPointUParameters.Sort(CompareUParameterIndexAndMonotonicityByUParameter); allContourPointUParameters.RemoveAt(allContourPointUParameters.Count - 1); allContourPointUParameters.RemoveAt(0); int numInBetweenUParameters = allContourPointUParameters.Count; var firstPoint = monotonicIncreasingPoints[0]; var lastPoint = monotonicDecreasingPoints[monotonicDecreasingPoints.Count - 1]; List <Vector2WithUV> increasingChunkPoints = new List <Vector2WithUV>(); List <Vector2WithUV> decreasingChunkPoints = new List <Vector2WithUV>(); for (int i = 0; i < numInBetweenUParameters; i++) { UParameterIndexAndMonotonicity uParameterIndexAndMonotonicity = allContourPointUParameters[i]; ExtrudedContourMonotonicChunk givenMonotonicChunk = uParameterIndexAndMonotonicity.IsMonotonicIncreasing ? monotonicIncreasingChunk : monotonicDecreasingChunk; ExtrudedContourMonotonicChunk otherMonotonicChunk = uParameterIndexAndMonotonicity.IsMonotonicIncreasing ? monotonicDecreasingChunk : monotonicIncreasingChunk; Vector2WithUV givenMonotonicChunkPoint = givenMonotonicChunk.Points[uParameterIndexAndMonotonicity.Index]; Vector2WithUV matchingMonotonicChunkPoint; bool gotMatchingPoint = otherMonotonicChunk.GetClosestPoint(uParameterIndexAndMonotonicity.UParameter, out matchingMonotonicChunkPoint); if (gotMatchingPoint) { var increasingPoint = uParameterIndexAndMonotonicity.IsMonotonicIncreasing ? givenMonotonicChunkPoint : matchingMonotonicChunkPoint; var decreasingPoint = uParameterIndexAndMonotonicity.IsMonotonicIncreasing ? matchingMonotonicChunkPoint : givenMonotonicChunkPoint; increasingChunkPoints.Add(increasingPoint); decreasingChunkPoints.Add(decreasingPoint); } } //Now remove connected segments pairs that are effectively duplicates of a neighbouring segment for (int i = increasingChunkPoints.Count - 1; i > 0; i--) { var increasingPoint = increasingChunkPoints[i]; var decreasingPoint = increasingChunkPoints[i]; var prevIncreasingPoint = increasingChunkPoints[i - 1]; var prevDecreasingPoint = increasingChunkPoints[i - 1]; bool increasingAreClose = ConnectedSegmentPointsAreClose(prevIncreasingPoint, increasingPoint); bool decreasingAreClose = ConnectedSegmentPointsAreClose(prevDecreasingPoint, decreasingPoint); if (increasingAreClose && decreasingAreClose) { increasingChunkPoints.RemoveAt(i); decreasingChunkPoints.RemoveAt(i); } } return(new SingleContourSegmentwiseCoverage(firstPoint, lastPoint, increasingChunkPoints, decreasingChunkPoints)); }