Domanda

I would like to generate a bezier curve between two shapes (rectangles for the purposes of this question) such that the curve meets the shape at a 90 degree angle. Here's a MS Paint-generated example:

example curve

For my purposes, it can be assumed that the angle only needs to be perpendicular to an axis, not to something arbitrary. In other words, the angle of the curve at the endpoints only needs to be 0, 90, 180, or 270 degrees.

My understanding of bezier curves tells me that I need 2 endpoints and 2 control points. The endpoints are simple to calculate, but I have only a basic understanding of how to manipulate the control points.

Is there a convenient formula to accomplish this? I'm having surprising difficulty finding relevant tutorials.

I'm using Java and am currently drawing curves using java.awt.geom.Path2D.Double.curveTo(). If there's a pre-built Java class or method to accomplish this, that would be ideal, but I'm willing to implement an algorithm or equation myself if needed.

È stato utile?

Soluzione

Based on Sage's excellent answer and after playing around with http://www.openprocessing.org/sketch/2123 for a while, here's what I ended up with.

Putting the control point collinear with the angle you want to create pulls the curve in that direction. The farther the control point is from the endpoint, the more pronounced the effect becomes. Since I only deal with 90 degree angles, this means I can just alter the x or y coordinates of the control points by a set distance (which I call delta like Sage did) to produce the desired effect. I didn't use anything fancy to find delta - I just tried several values until I found one I was happy with.

Here's a snippet of my final code.

public class BezierCurve {
    private CubicCurve2D curve;

    private static final int delta = 100;

    private double x1, y1, x2, y2;
    private double ctrlx1, ctrly1, ctrlx2, ctrly2;

    public BezierCurve(Point p1, Side side1, Point p2, Side side2) {
        this.x1 = p1.x;
        this.y1 = p1.y;
        this.x2 = p2.x;
        this.y2 = p2.y;

        Point ctrl1 = getControlPoint(p1, side1);
        Point ctrl2 = getControlPoint(p2, side2);

        ctrlx1 = ctrl1.x;
        ctrly1 = ctrl1.y;
        ctrlx2 = ctrl2.x;
        ctrly2 = ctrl2.y;

        curve = new CubicCurve2D.Double();
        curve.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
    }

    private Point getControlPoint(Point p, Side s) {
        int x = p.x;
        int y = p.y;

        switch (s) {
        case Left:
            x -= delta;
            break;
        case Right:
            x += delta;
            break;
        case Bottom:
            y += delta;
            break;
        case Top:
            y -= delta;
            break;
        }
        return new Point(x, y);
    }

    public void draw(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.draw(curve);
    }
}

This handles each case of top, bottom, right, and left.

Here's a screenshot of the code in action.

enter image description here

Altri suggerimenti

intelligent use of the Class CubicCurve2D should be able achieve what you want: The CubicCurve2D class implements the Shape interface. This class represents a cubic parametric curve segment in (x, y) coordinate space. CubicCurve2D.Float and CubicCurve2D.Double subclasses specify a cubic curve in float and double precision.

This class's setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); allows to set two control point. If we set the control point right before the curve start (ctrlx1, ctrly1) and right after the curve end (ctrlx2, ctrly2). To maintain the angle of the curve to be multiple of 90 degree, We can calculate the control point similarly as follows (which is calculated for 90 deegree):

ctrlx1 = x1; // curve start x
ctrly1 = y2 - delta; // curve start y
ctrlx2 = x1 + delta; // curve end x
ctrly2 = y2; // curve end y

In the following example i have assumed delta = 10;

enter image description here

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g); 

    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    CubicCurve2D c = new CubicCurve2D.Double();
    int x1 = 150, y1 = 150;  //p1
    int x2 = 350, y2 = 300;//p3
    int ctrlx1, ctrly1, ctrlx2, ctrly2;
    int delta = 10;

    ctrlx1 = x1; // curve start x
    ctrly1 = y2 - delta; // curve start y
    ctrlx2 = x1 + delta; // curve end x
    ctrly2 = y2; 

    g2d.drawRect(x1-50, y1-100, 100, 100);
    c.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
    g2d.drawRect(x2, y2-50, 100, 100);
    g2d.draw(c);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top