Find new control point when endpoint change in cubic bezier curve
Asked Answered
S

4

16

I'm implementing cubic bezier curve logic in my one of Android Application.

I've implemented cubic bezier curve code on canvas in onDraw of custom view.

// Path to draw cubic bezier curve
Path cubePath = new Path();

// Move to startPoint(200,200) (P0)
cubePath.moveTo(200,200);

// Cubic to with ControlPoint1(200,100) (C1), ControlPoint2(300,100) (C2) , EndPoint(300,200) (P1)
cubePath.cubicTo(200,100,300,100,300,200);

// Draw on Canvas
canvas.drawPath(cubePath, paint);

I visualize above code in following image.

Output of above code

[Updated]

Logic for selecting first control points, I've taken ,
baseX = 200 , baseY = 200 and curve_size = X of Endpoint - X of Start Point

Start Point     : x = baseX and y = baseY
Control Point 1 : x = baseX and y =  baseY - curve_size
Control Point 2 : x = baseX + curve_size and y =  baseY - curve_size
End Point       : x = baseX + curve_size and y = baseY

I want to allow user to change EndPoint of above curve, and based on the new End points, I invalidate the canvas.

But problem is that, Curve maintain by two control points, which needs to be recalculate upon the change in EndPoint.

Like, I just want to find new Control Points when EndPoint change from (300,200) to (250,250)

Like in following image :

New Image

Please help me to calculate two new Control Points based on new End Point that curve shape will maintain same as previous end point.

I refer following reference links during searching:

http://pomax.github.io/bezierinfo/

http://jsfiddle.net/hitesh24by365/jHbVE/3/

http://en.wikipedia.org/wiki/B%C3%A9zier_curve

http://cubic-bezier.com/

Any reference link also appreciated in answer of this question.

Sportscast answered 17/4, 2013 at 7:3 Comment(4)
how are you calculating the first two control points? Are you trying to draw according to user motion event?Latvia
@ArunCThomas : I updated the Question with logic for selecting default control pointSportscast
I'm pretty sure I cover that in pomax.github.io/bezierinfo/#polybezier =)Stilbestrol
Can you draw an example of what you want to achieve and how you want the curve to look after the end point is changed because I don't really understand? I'm guessing it's not very hard.Goodden
H
12

changing the endpoint means two things, a rotation along P1 and a scaling factor.

The scaling factor (lets call it s) is len(p1 - p0) / len(p2 - p0)

For the rotation factor (lets call it r) i defer you to Calculating the angle between three points in android , which also gives a platform specific implementation, but you can check correctness by scaling/rotationg p1 in relation to p0, and you should get p2 as a result.

next, apply scaling and rotation with respect to p0 to c1 and c2. for convenience i will call the new c1 'd1' and the new d2.

d1 = rot(c1 - p0, factor) * s + p0
d2 = rot(c2 - p0, factor) * s + p0

to define some pseudocode for rot() (rotation http://en.wikipedia.org/wiki/Rotation_%28mathematics%29)

rot(point p, double angle){
  point q;
  q.x = p.x * cos(angle) - p.y * sin(angle);
  q.y = p.x * sin(angle) + p.y * cos(angle);
}

Your bezier curve is now scaled and rotated in relation to p0, with p1 changed to p2,

Historical answered 19/4, 2013 at 10:40 Comment(3)
the 'factor' as in my answer is a scaling factor, so it quantifies hoe many times longer/shorter the line p1-p3 is then the line p1-p2.Historical
Ok, Factor is just for increase or decrease curve area based on scale of new point.Sportscast
factor, scale... it comes down to the same thing. Suppose you have P1 at (0, 0) P2 at (0, 1) and a curve length of 2; multiplying by a factor of two results in p2 at (0, 2) with a curve length of 4. It is basically the same image on a scale of 1:2Historical
C
6

Firstly I would ask you to look into following articles :

  1. Bezier Curves
  2. Why B-Spline Curve
  3. B-Spline Curve Summary

What you are trying to implement is a piecewise composite Bézier curve. From the Summary page for n control points (include start/end) you get (n - 1)/3 piecewise Bézier curves.

The control points shape the curve literally. If you don't give proper control points with new point, you will not be able to create smoothly connected bezier curve. Generating them will not work, as it is too complex and there is no universally accepted way.

If you don't have/want to give extra control points, you should use Catmull-Rom spline, which passes through all control points and will be C1 continous (derivative is continuous at any point on curve).

Links for Catmull Rom Spline in java/android :

Bottom line is if you don't have the control points don't use cubic bezier curve. Generating them is a problem not the solution.

Candor answered 20/4, 2013 at 6:59 Comment(0)
P
6

It seems that you are here rotating and scaling a square where you know the bottom two points and need to calculate the other two. The two known points form two triangles with the other two, so we just need to find the third point in a triangle. Supose the end point is x1, y1:

PointF c1 = calculateTriangle(x0, y0, x1, y1, true); //find left third point
PointF c2 = calculateTriangle(x0, y0, x1, y1, false); //find right third point

cubePath.reset();
cubePath.moveTo(x0, y0);
cubePath.cubicTo(c1.x, c1.y, c2.x, c2.y, x1, y1);


private PointF calculateTriangle(float x1, float y1, float x2, float y2, boolean left) {
                PointF result = new PointF(0,0);
                float dy = y2 - y1;
                float dx = x2 - x1;
                float dangle = (float) (Math.atan2(dy, dx) - Math.PI /2f);
                float sideDist = (float) Math.sqrt(dx * dx + dy * dy); //square
                if (left){
                    result.x = (int) (Math.cos(dangle) * sideDist + x1);
                    result.y = (int) (Math.sin(dangle) * sideDist + y1);                    
                }else{
                    result.x = (int) (Math.cos(dangle) * sideDist + x2);
                    result.y = (int) (Math.sin(dangle) * sideDist + y2);
                }
                return result;
            }

...

There is other way to do this where it does not matter how many points you have in between the first and the last point in the path or event its shape.

//Find scale
Float oldDist = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
Float newDist = (float) Math.sqrt((x2 - x0) * (x2 - x0) + (y2 - y0) * (y2 - y0)); 
Float scale = newDist/oldDist;

//find angle
Float oldAngle = (float) (Math.atan2(y1 - y0, x1 - x0) - Math.PI /2f);
Float newAngle = (float) (Math.atan2(y2 - y0, x2 - x0) - Math.PI /2f);
Float angle = newAngle - oldAngle;

//set matrix
Matrix matrix = new Matrix();
matrix.postScale(scale, scale, x0, y0);
matrix.postRotate(angle, x0, y0);

//transform the path
cubePath.transform(matrix);
Peary answered 24/4, 2013 at 17:38 Comment(0)
G
4

A small variant on the suggestion by Lumis

// Find scale
Float oldDist = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
Float newDist = (float) Math.sqrt((x2 - x0) * (x2 - x0) + (y2 - y0) * (y2 - y0)); 
Float scale = newDist/oldDist;

// Find angle
Float oldAngle = (float) (Math.atan2(y1 - y0, x1 - x0));
Float newAngle = (float) (Math.atan2(y2 - y0, x2 - x0));
Float angle = newAngle - oldAngle;

Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
matrix.postRotate(angle);

float[] p = { c1.x, c1.y, c2.x, c2.y };
matrix.mapVectors(p);
PointF newC1 = new PointF(p[0], p[1]);
PointF newC2 = new PointF(p[2], p[3]);
Gorget answered 25/4, 2013 at 20:35 Comment(1)
I would use matrix.mapPoints(p). I thought of this myself, but he needs to do this only if he wants to use control points for something else or store them for later use, for they can always be derived from p0, p1 and p3.Peary

© 2022 - 2024 — McMap. All rights reserved.