5

I want to be able to reposition and reshape a cubic bezier curve drawn in Core Graphics by touching it and dragging it. I can draw the basic shape and I can use touch and drag to move the WHOLE of the shape but this isnt what I want to be able to do.

What I want is to be able to move and reshape the bezier curve it as if it were a piece of string lying on a table being pulled around by my finger. Ie touching on part of the bezier curve and pulling it in a direction to change the shape of the curve overall.

Does anyone know how to do this? Any help would be very welcome.

Thanks in advance

MiltsInit
  • 153
  • 7

2 Answers2

5

It is fairly easy to draw the control points and allow the user to drag them around. Unfortunately, the curve does not pass through all the control points, so the experience does not quite match what you're suggesting.

To do what you're suggesting, you first need to answer the question "is the user touching the curve?" This is the same as the question "is a given point within a certain distance of the curve." That is not a trivial question, but it can be calculated. Probably the simplest approach is to just calculate X points along the curve (where X is sufficiently high to give you reasonable precision), and check the distance for each. In principle, you could also take the derivative of the distance equation and solve it for its zeros, but this requires iteration. In my experience you can calculate the needed 1000 or so distances quickly enough (even on an iPad 1) that it may not be worth the extra complexity.

Once you find that the user is in fact touching the curve, it's easy to figure out which control point is closest. The hard thing at this point is deciding what to do about it. Some options:

  • Move the closest control point in the direction that the user is moving. You may need to do multiple calculations until you find a new curve that passes through the touch point. This is the simplest approach and probably where I'd start.
  • You could subdivide the curve at the touched point and move the newly created endpoints. (See De Casteljau's algorithm for this.)This will tend to create sharp corners unless you adjust the other control points to create matched slopes. This allows for more arbitrary curves, but it could become very hard to know what the user really wanted. You'd also almost certainly need to apply Ramer-Douglas-Peucker to keep your number of curves from exploding.

I'm currently quite interested in Bézier curve problems in Objective-C. You may be interested in my first post on the subject. My initial work in this area is available on GitHub in the iOS:PTL sample code. Hopefully I'll have another post up this week. Your particular problem is interesting, so I may see what I can build around that.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
5

Hmm, are you sure you need exactly Bezier curve? Or do you need just some smooth curve over a path? The behavior you need is very easy to implement using other curve types: Cardinal or it's sub-type like Catmull-Rom spline.

The benefit of the Cardinal splines over Bezier is in fact than splines always pass the control points, i.e. adding, moving or erasing a point from the path doesn't affect other points and the curve still looks fine. Plus, the math is much simpler (and faster to compute).

Cardinal splines are not presented in the CoreGraphics, but you can draw one approximating the curve with poly-line with some small t step. The rest (finding whether the touch is over the curve etc.) is explained in the Rob's answer, I can only add that having a poly-line approximation of a curve almost solve this task since all you need after that is to find a segment with a shortest distance to the touch point.

Gobra
  • 4,263
  • 2
  • 15
  • 20
  • Excellent pointer. I'd be a little nervous of trying to draw a polyline this way, however. My experience is that very large CGPaths tend to be extremely expensive to draw. But it is possible to convert a Catmull-Rom into a Bézier: http://stackoverflow.com/questions/1030596/drawing-hermite-curves-in-opengl. That could be a useful tool. Still leaves the complexity of splitting the curve and then simplifying it back to cubic, but an excellent starting point. – Rob Napier Feb 13 '12 at 16:37
  • I'd split the path to some segments to optimize the process (updates, drawing), plus there is one trick: the t parameter step could be variable. We can check the curve segment length and adjust the t, so the close points will get few "smoothing lines" while distance points can have a dozen of approximation lines. – Gobra Feb 13 '12 at 16:47
  • True. My linked code below includes a "give me the `t` for a given distance along the path," so you could make sure you never try to draw overly short lines (definitely never shorter than 1px, which can easily happen if you step over `t` linearly). My serious performance problems happened in the neighborhood of 4000-5000 line segments, and this technique could keep you well under that. – Rob Napier Feb 13 '12 at 17:28
  • Firstly can I thank you both (Rob and Gobra) for these excellent responses. The depth of your knowledge on this problem and the alternative options is deeply impressive. I had chosen the Bezier curve because its enabled through the core libraries but also because it is a close approximation to the overall effect I am seeking in the project I am working on. However, the user experience problem Rob has outlined would be a problem and being able to touch the curve would be a much better user experience, especially if I want the curve to get more "bent". – MiltsInit Feb 13 '12 at 21:05
  • Firstly can I thank you both (Rob and Gobra) for these excellent responses. The depth of your knowledge on this problem and the alternative options is deeply impressive. I had chosen the Bezier curve because its enabled through the core libraries but also because it is a close approximation to the overall effect I am seeking in the project I am working on. However, the user experience problem Rob has outlined would be a problem and being able to touch the curve would be a much better user experience, especially if I want the curve to get more "bent". – MiltsInit Feb 13 '12 at 21:06
  • Any easier and more comprehensive opinion for this answer? My app actually draws a custom shape using touchesBegan etc and converts it into a bezier curve which needs to be reshaped by touching the bezier curve by the user, Can't really find a more clear answer :( @RobNapier Can anyone help? – Reza.Ab Aug 26 '16 at 12:17