private const int DIVBY_TEST = 20; // 5% of domain length /// <summary> /// Finds all kinks in a curve. /// </summary> /// <param name="curve"></param> /// <param name="crvLength">provide curve length if you have for speed optimization</param> /// <returns>List of kinks or Null </returns> public static List <CurveKinkData> _Kinks_Find(this Curve curve, double?crvLength = null) { // dont work with very small curves var crvNormal = new CurveNormalized(curve, crvLength); if (crvNormal.Length < 0.01) { return(null); } var crvDomain = curve.Domain; // lines dont have kinks if (crvNormal.Degree == 1) { return(null); } // Fast check var PstepFast = 1.0 / DIVBY_TEST; //step from start and end curve var pStart1 = 0; var pStart2 = PstepFast; var pEnd2 = 1 - PstepFast; var pEnd1 = 1; var pStart1Tangent = curve.TangentAtStart; //var pStart2Tangent = crvNormal.TangentAt(pStart2); //- v1 - slow //var pEnd2Tangent = crvNormal.TangentAt(pEnd2); //- v1 - slow var pStart2Tangent = curve.TangentAt(crvDomain.T0 + crvDomain.Length * PstepFast); //- v2 - faster var pEnd2Tangent = curve.TangentAt(crvDomain.T1 - crvDomain.Length * PstepFast); //- v2 - faster var pEnd1Tangent = curve.TangentAtEnd; var degreeStart = pStart1Tangent._AngleOfUnitizedVectors(pStart2Tangent)._RadianToDegree(); var degreeEnd = pEnd2Tangent._AngleOfUnitizedVectors(pEnd1Tangent)._RadianToDegree(); if (degreeStart < 5 && degreeEnd < 5) { return(null); } // Detailed check //lets calculate tangents var tangents = new Vector3d[DIVBY_TEST + 1]; tangents[0] = pStart1Tangent; //speed optimization - lets reuse tangent (skipp duplicate tangent calculation) tangents[tangents.Length - 1] = pEnd1Tangent; //speed optimization - lets reuse tangent (skipp duplicate tangent calculation) Point3d[] points; double[] ts; string failReason; if (curve._TryDivideByCount(DIVBY_TEST, out points, out ts, out failReason) && // try get 'ts' - it is faster then make separate call to curveNormal.T() points.Length == tangents.Length) { var Pstep = 1.0 / DIVBY_TEST; //5% of domain length for (int i = 1; i < tangents.Length - 1; i++) // from second for prelast for speed optimization (first and last is calculated just before) { //DEBUG - test if 'crvNormal.T()' works same as 'curve.DivideByCount()' //var p = Pstep * i; //var tsNormal = crvNormal.T(p); //var tdiff = Math.Abs(tsNormal - ts[i]); //if (tdiff > curve.Domain.Length/1000) //{ // var temp = 0; //} //ENDDEBUG tangents[i] = curve.TangentAt(ts[i]); } } else // if DivideByCount fails - lets use crvNormal - its slower but works same { var Pstep = 1.0 / DIVBY_TEST; //5% of domain length for (int i = 1; i < tangents.Length - 1; i++) // from second for prelast for speed optimization (first and last is calculated just before) { var p = Pstep * i; tangents[i] = crvNormal.TangentAt(p); } } //lets calculate an avarage tangent change // we asume that on middle of the curve it doesnt have kinks. string[] changesStr = null; var changesRadians = new double[DIVBY_TEST]; var changes = new double[DIVBY_TEST]; for (int i = 0; i < changes.Length; i++) { changesRadians[i] = tangents[i]._AngleOfUnitizedVectors(tangents[i + 1]); changes[i] = changesRadians[i]._RadianToDegree(); } if (DEBUG) { changesStr = changes.Select(o => o._ToStringAngle()).ToArray(); log.temp("AngleChanges = " + String.Join(", ", changesStr)); } List <CurveKinkData> res = null; foreach (var end in ends) { var DIVBY25 = DIVBY_TEST / 4; var iFirst = 0; var iLast = changes.Length - 1; var iO = (end == CurveEnd.Start) ? iFirst : iLast; var iA = (end == CurveEnd.Start) ? iFirst : iLast - DIVBY25; var iB = (end == CurveEnd.Start) ? iFirst + DIVBY25 : iLast; double maxChange = 0; //25% of start double summChange = 0; int summCount = 0; for (int i = iA; i <= iB; i++) { if (i == iO) { continue; //skip first and last } maxChange = Math.Max(maxChange, changes[i]); summChange += changes[i]; summCount++; } var avgChange = summChange / DIVBY25; var maxDeviation = maxChange - avgChange; var endDeviation = changes[iO] - avgChange; var deviationTimes = (int)(endDeviation / maxDeviation._GetNonZeroForDevisionOperation()); if (deviationTimes >= 10 && endDeviation >= 5) // if end change is really bigger from previous changes - we have kink { var iTangent = (end == CurveEnd.Start) ? 0 : DIVBY_TEST; var kink = new CurveKinkData { Crv = curve, CrvEnd = end, DegreeChange = changes[iO], DegreeDeviation_FromAvg_Closest25ofCurve = endDeviation, Closest25ofCurve_DegreeAvgChange = avgChange, Closest25ofCurve_DegreeDeviationMaxFromAvg = maxDeviation, //Point = (end == CurveEnd.Start ? crv.PointAt((tStart1 + tStart2) / 2) : crv.PointAt((tEnd1 + tEnd2) / 2)), Point = crvNormal.PointAt(end), TangentCurrent = tangents[iTangent], TangentExcepted = tangents[iTangent + ((end == CurveEnd.Start) ? 1 : -1)], DegreesChangesStr = String.Join(", ", changes.Select(o => o._ToStringAngle()).ToArray()), DeviationBiggerNTimes = deviationTimes }; if (res == null) { res = new List <CurveKinkData>(2); } res.Add(kink); } } if (res != null && DEBUG) { if (DEBUG) { log.temp("================"); } log.temp("AngleChanges = " + String.Join(", ", changesStr)); foreach (var kink in res) { var problem = String.Format("Kink at {0} by {1}", kink.CrvEnd, kink.DegreeDeviation_FromAvg_Closest25ofCurve._ToStringAngle()); log.temp(problem); log.temp("endDeviation/maxDeviation = {0:0.000}", (kink.DegreeDeviation_FromAvg_Closest25ofCurve / kink.Closest25ofCurve_DegreeDeviationMaxFromAvg._GetNonZeroForDevisionOperation())); } if (DEBUG) { log.temp("================"); } } return(res); }