/// <summary> /// If the existing number of bends is 2 mod 3 (i.e. the bends are consistent with /// what the bezier style expects), /// this implementation creates a triple of collinear bends and adjust the neighboring bends /// in a way that the shape of the curve is not changed initially and returns the middle bend. /// If there are no bends at all, it creates a triple plus two initial and final control bends, all of them collinear. /// Otherwise, the fallback bend creator is used to create a bend with its default strategy. /// </summary> /// <returns>The index of middle bend of a control point triple if such a triple was created, /// or the index of the newly created single bend.</returns> public int CreateBend(IInputModeContext context, IGraph graph, IEdge edge, PointD location) { switch (edge.Bends.Count) { case 0: var spl = edge.SourcePort.GetLocation(); var tpl = edge.TargetPort.GetLocation(); //a single linear segment... we just insert 5 collinear bends adjusted to the angle of the linear segment, //approximately evenly spaced graph.AddBend(edge, (location - spl) / 4 + spl, 0); graph.AddBend(edge, -(location - spl) / 4 + location, 1); graph.AddBend(edge, location, 2); graph.AddBend(edge, (location - spl) / 4 + location, 3); graph.AddBend(edge, location + (tpl - location) * 3 / 4, 4); return(2); case 1: //Use the default strategy to insert a single bend at the correct index return(fallBackCreator.CreateBend(context, graph, edge, location)); default: var pathPoints = edge.GetPathPoints(); if (pathPoints.Count % 3 == 1) { //Consistent number of existing points //Try to insert a smooth bend //I.e. a triple of three collinear bends and adjust the neighbor bends //Various quality measures and counters var segmentIndex = 0; var pathCounter = 0; var bestDistanceSqr = Double.PositiveInfinity; double bestRatio = Double.NaN; //The index of the segment where we want to create the bend in the end int bestIndex = -1; //Find the best segment while (pathCounter + 3 < pathPoints.Count) { //Get the control points defining the current segment var cp0 = pathPoints[pathCounter++]; var cp1 = pathPoints[pathCounter++]; var cp2 = pathPoints[pathCounter++]; //Consecutive segments share the last/first control point! So we may not advance the counter here var cp3 = pathPoints[pathCounter]; //Shift a cubic segment // //Here we assume that the path is actually composed of cubic segments, only. //Alternatively, we could inspect the actual path created by the edge renderer - this would also //allow to deal with intermediate non cubic segments, but we'd have to associate those //path segments somehow with the correct bends again, so again this would be tied to the actual //renderer implementation. var fragment = new GeneralPath(2); fragment.MoveTo(cp0); fragment.CubicTo(cp1, cp2, cp3); //Try to find the projection onto the fragment var ratio = fragment.GetProjection(location, 0); if (ratio.HasValue) { //Actually found a projection ratio //Determine the point on the curve - the tangent provides this var tangent = fragment.GetTangent(0, ratio.Value); if (tangent.HasValue) { //There actually is a tangent var d = (location - tangent.Value.Point).SquaredVectorLength; //Is this the best distance? if (d < bestDistanceSqr) { bestDistanceSqr = d; //Remember ratio (needed to split the curve) bestRatio = ratio.Value; //and the index, of course bestIndex = segmentIndex; } } } ++segmentIndex; } if (bestIndex != -1) { //Actually found a segment //For the drag, we want to move the middle bend return(CreateBends(graph, edge, bestIndex, bestRatio, pathPoints).GetIndex()); } //No best segment found (for whatever reason) - we don't want to create a bend so that we don't mess up anything return(-1); } else { //No consistent number of bends - just insert a single bend //We could also see whether we actually would have a cubic segment on the path, and treat that differently //However, why bother - just create the edge with a correct number of points instead return(fallBackCreator.CreateBend(context, graph, edge, location)); } } }