如何使曲线连接两个对象可编辑?

时间:2023-02-04 15:42:48

I'm involved in a project in which we're doing a visual editor (written in Java). Now, I'm trying to make curves that join two different objects that I'm painting in a class that extends JPanel (this class is what I'm using to paint, inside a JFrame, overriding the method paintComponent). I'm in troubles because I'm using the class QuadCurve2D to make this, but I cannot make it clickable (I'm using the method contains, but it doesn't work everytime), make it editable (for example, setting a square in its middle point to modify its curvature. The point that is used on the middle of the QuadCurve2D when the constructor is called is outside the curve) or something (method, variable, iterator, etc) that could tell me which Points are in the QuadCurve2D.

我参与了一个项目,我们正在做一个可视化编辑器(用Java编写)。现在,我正在尝试制作连接两个不同对象的曲线,我在一个扩展JPanel的类中绘制(这个类是我用来绘制的,在JFrame中,覆盖方法paintComponent)。我遇到了麻烦,因为我正在使用QuadCurve2D类来实现这一点,但是我不能使它可点击(我使用的方法包含但是每次都不起作用),使其可编辑(例如,设置一个在中间点使用square来修改它的曲率。当调用构造函数时,QuadCurve2D中间使用的点在曲线之外)或某些东西(方法,变量,迭代器等)可以告诉我哪些点在QuadCurve2D。

After looking for all of that some time, I have no answer, so I'm trying posting it here to find a solution. Is there anyway to make it with the QuadCurve2D class, or do I have to try with some external library?

在寻找所有这些时间之后,我没有答案,所以我试着在这里发布它以找到解决方案。无论如何使用QuadCurve2D类,或者我必须尝试使用​​一些外部库?

1 个解决方案

#1


First of all sorry for the long reply. I am now posting a complete answer to your question. I am sub classing the QuadCurve2D.Double class and with a little math now you define the curve with a start,end and a middle point instead of a control point. Also i have created a new method that checks whether a point is on the curve. The intersects method checks if the convex hull of the shape intersects with the provided shape so in the case of the concave curve this is functional but not accurate. Note that my implementation of the method to check whether a point is on the curve is rather computationally expensive and not 100% accurate since i am checking along the curve length with a specified resolution (0 is the beginning of the curve, 1 is the end. So in the example provided i am checking with a resolution of 0.01 meaning 100 checks are made along the curve). For that matter make sure that the provided step in the resolution is a divider of 0.5 (the middle point) so that you may be able to select it. If that makes no sense don't pay attention it doesn't really matter, you can use my example out of the box. Note that i also provide a check box to switch between the intersects method and my own for checking whether the mouse is on the curve. And when using my new method, i also provide a slider to specify the resolution so that you may see the effects of various values. Here are the classes.

首先很抱歉长时间的回复。我现在正在发布你问题的完整答案。我正在对QuadCurve2D.Double类进行子类化,现在您可以使用开始,结束和中间点而不是控制点来定义曲线。我还创建了一个新方法来检查一个点是否在曲线上。交叉方法检查形状的凸包是否与提供的形状相交,因此在凹曲线的情况下,这是有效但不准确的。请注意,我检查点是否在曲线上的方法的实现在计算上相当昂贵并且不是100%准确,因为我使用指定的分辨率检查曲线长度(0是曲线的开头,1是结束因此,在提供的示例中,我使用0.01的分辨率进行检查,这意味着沿着曲线进行了100次检查。为此,请确保分辨率中提供的步骤是0.5(中间点)的分隔符,以便您可以选择它。如果没有意义就不注意它并不重要,你可以开箱即用我的例子。请注意,我还提供了一个复选框,用于在intersects方法和我自己的方法之间切换,以检查鼠标是否在曲线上。当使用我的新方法时,我还提供了一个滑块来指定分辨率,以便您可以看到各种值的效果。这是课程。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;

@SuppressWarnings("serial")
public class CurvePanel extends JPanel implements MouseListener,MouseMotionListener{

    Point2D startPoint = new Point2D.Double(50, 50);
    Point2D middlePoint = new Point2D.Double(100,80);
    Point2D endPoint = new Point2D.Double(200, 200);
    Point2D[] points = new Point2D[] {startPoint,middlePoint,endPoint};
    QuadCurveWithMiddlePoint curve;
    private Point2D movingPoint;
    private boolean dragIt = false;
    private boolean showControls = false;
    JCheckBox useNewMethod;
    JSlider resolution;

    public CurvePanel() {
        setPreferredSize(new Dimension(300,300));
        addMouseListener(this);
        addMouseMotionListener(this);
        curve = new QuadCurveWithMiddlePoint();
        useNewMethod = new JCheckBox("Use new \"contains\" method");
        resolution = new JSlider(JSlider.HORIZONTAL,1,10,1);
        useNewMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                resolution.setEnabled(useNewMethod.isSelected());
            }
        });
        useNewMethod.setSelected(false);
        resolution.setEnabled(false);
        setCurve();
    }

    private void setCurve() {
        curve.setCurveWithMiddlePoint(startPoint, middlePoint, endPoint);
    }

    public static void main(String[] args) {
        JFrame f = new JFrame("Test");
        CurvePanel panel = new CurvePanel();
        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(panel.useNewMethod,BorderLayout.NORTH);
        f.getContentPane().add(panel,BorderLayout.CENTER);
        f.getContentPane().add(panel.resolution,BorderLayout.SOUTH);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
    }

    @Override
    public void mouseClicked(MouseEvent e)  {}
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
    @Override
    public void mousePressed(MouseEvent e) {
        for (Point2D point : points) {
            if (e.getPoint().distance(point) <= 2) {
                movingPoint = point;
                dragIt = true;
            }
        }
    }
    @Override
    public void mouseReleased(MouseEvent e) {
        dragIt = false;
    }
    @Override
    public void mouseDragged(MouseEvent e) {
        if (dragIt) {
            movingPoint.setLocation(e.getPoint());
            setCurve();
            repaint();
        }
    }
    @Override
    public void mouseMoved(MouseEvent e) {
        if (useNewMethod.isSelected())
            showControls = curve.pointOnCurve(e.getPoint(), 2, resolution.getValue()/100.0);
        else
            showControls = curve.intersects(e.getX()-2, e.getY()-2, 4, 4);
        repaint();
    }
    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setPaint(Color.white);
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setPaint(Color.black);
        g2.draw(curve);
        if (showControls)
            for (Point2D point : points) {
                g2.setPaint(Color.black);
                g2.drawOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
                g2.setPaint(Color.red);
                g2.fillOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
            }
    }
}

And also :

并且 :

import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D.Double;

@SuppressWarnings("serial")
public class QuadCurveWithMiddlePoint extends Double {

    private Point2D middlePoint = new Point2D.Double();
    private final double L = 0.5;

    public QuadCurveWithMiddlePoint(double x1,double y1, double xm, double ym, double x2, double y2) {
        super(x1,y1,xm,ym,x2,y2);
        setMiddlePoint(xm, ym);
    }

    public QuadCurveWithMiddlePoint() {
        this(0,0,0,0,0,0);
    }

    public Point2D getMiddlePoint() {
        calculateMiddlePoint();
        return middlePoint;
    }

    public void setMiddlePoint(double middleX, double middleY) {
        setCurve(getP1(), getControlPointByMiddle(middleX, middleY), getP2());
        calculateMiddlePoint();
    }

    public void setMiddlePoint(Point2D middle) {
        setMiddlePoint(middle.getX(),middle.getY());
    }

    private Point2D getControlPointByMiddle(double middleX,double middleY) {
        double cpx = (middleX-(L*L-2*L+1)*x1-(L*L)*x2)/(-2*L*L+2*L);
        double cpy = (middleY-(L*L-2*L+1)*y1-(L*L)*y2)/(-2*L*L+2*L);
        return new Point2D.Double(cpx,cpy);
    }

    private Point2D calculatePoint(double position) {
        if (position<0 || position>1)
            return null;
        double middlex = (position*position-2*position+1)*x1+(-2*position*position+2*position)*ctrlx+(position*position)*x2;
        double middley = (position*position-2*position+1)*y1+(-2*position*position+2*position)*ctrly+(position*position)*y2;
        return new Point2D.Double(middlex,middley);
    }

    public void calculateMiddlePoint() {
        middlePoint.setLocation(calculatePoint(L));
    }

    public void setCurveWithMiddlePoint(double xx1,double yy1, double xxm, double yym, double xx2, double yy2) {
        setCurve(xx1, yy1, xxm, yym, xx2, yy2);
        setMiddlePoint(xxm,yym);
    }

    public void setCurveWithMiddlePoint(Point2D start, Point2D middle, Point2D end) {
        setCurveWithMiddlePoint(start.getX(),start.getY(),middle.getX(),middle.getY(),end.getX(),end.getY());
    }

    public boolean pointOnCurve(Point2D point, double accuracy, double step) {
        if (accuracy<=0)
            return false;
        if (step<=0 || step >1)
            return false;
        boolean oncurve = false;
        double current = 0;
        while (!oncurve && current <= 1) {
            if (calculatePoint(current).distance(point)<accuracy)
                oncurve = true;
            current += step;
        }
        return oncurve;
    }

}

If you want to know how i made the class, do a search for basic linear algebra and also search Wikipedia for Bézier curves.

如果你想知道我是如何上课的,可以搜索基本的线性代数,也可以搜索*的Bézier曲线。

#1


First of all sorry for the long reply. I am now posting a complete answer to your question. I am sub classing the QuadCurve2D.Double class and with a little math now you define the curve with a start,end and a middle point instead of a control point. Also i have created a new method that checks whether a point is on the curve. The intersects method checks if the convex hull of the shape intersects with the provided shape so in the case of the concave curve this is functional but not accurate. Note that my implementation of the method to check whether a point is on the curve is rather computationally expensive and not 100% accurate since i am checking along the curve length with a specified resolution (0 is the beginning of the curve, 1 is the end. So in the example provided i am checking with a resolution of 0.01 meaning 100 checks are made along the curve). For that matter make sure that the provided step in the resolution is a divider of 0.5 (the middle point) so that you may be able to select it. If that makes no sense don't pay attention it doesn't really matter, you can use my example out of the box. Note that i also provide a check box to switch between the intersects method and my own for checking whether the mouse is on the curve. And when using my new method, i also provide a slider to specify the resolution so that you may see the effects of various values. Here are the classes.

首先很抱歉长时间的回复。我现在正在发布你问题的完整答案。我正在对QuadCurve2D.Double类进行子类化,现在您可以使用开始,结束和中间点而不是控制点来定义曲线。我还创建了一个新方法来检查一个点是否在曲线上。交叉方法检查形状的凸包是否与提供的形状相交,因此在凹曲线的情况下,这是有效但不准确的。请注意,我检查点是否在曲线上的方法的实现在计算上相当昂贵并且不是100%准确,因为我使用指定的分辨率检查曲线长度(0是曲线的开头,1是结束因此,在提供的示例中,我使用0.01的分辨率进行检查,这意味着沿着曲线进行了100次检查。为此,请确保分辨率中提供的步骤是0.5(中间点)的分隔符,以便您可以选择它。如果没有意义就不注意它并不重要,你可以开箱即用我的例子。请注意,我还提供了一个复选框,用于在intersects方法和我自己的方法之间切换,以检查鼠标是否在曲线上。当使用我的新方法时,我还提供了一个滑块来指定分辨率,以便您可以看到各种值的效果。这是课程。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;

@SuppressWarnings("serial")
public class CurvePanel extends JPanel implements MouseListener,MouseMotionListener{

    Point2D startPoint = new Point2D.Double(50, 50);
    Point2D middlePoint = new Point2D.Double(100,80);
    Point2D endPoint = new Point2D.Double(200, 200);
    Point2D[] points = new Point2D[] {startPoint,middlePoint,endPoint};
    QuadCurveWithMiddlePoint curve;
    private Point2D movingPoint;
    private boolean dragIt = false;
    private boolean showControls = false;
    JCheckBox useNewMethod;
    JSlider resolution;

    public CurvePanel() {
        setPreferredSize(new Dimension(300,300));
        addMouseListener(this);
        addMouseMotionListener(this);
        curve = new QuadCurveWithMiddlePoint();
        useNewMethod = new JCheckBox("Use new \"contains\" method");
        resolution = new JSlider(JSlider.HORIZONTAL,1,10,1);
        useNewMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                resolution.setEnabled(useNewMethod.isSelected());
            }
        });
        useNewMethod.setSelected(false);
        resolution.setEnabled(false);
        setCurve();
    }

    private void setCurve() {
        curve.setCurveWithMiddlePoint(startPoint, middlePoint, endPoint);
    }

    public static void main(String[] args) {
        JFrame f = new JFrame("Test");
        CurvePanel panel = new CurvePanel();
        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(panel.useNewMethod,BorderLayout.NORTH);
        f.getContentPane().add(panel,BorderLayout.CENTER);
        f.getContentPane().add(panel.resolution,BorderLayout.SOUTH);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
    }

    @Override
    public void mouseClicked(MouseEvent e)  {}
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
    @Override
    public void mousePressed(MouseEvent e) {
        for (Point2D point : points) {
            if (e.getPoint().distance(point) <= 2) {
                movingPoint = point;
                dragIt = true;
            }
        }
    }
    @Override
    public void mouseReleased(MouseEvent e) {
        dragIt = false;
    }
    @Override
    public void mouseDragged(MouseEvent e) {
        if (dragIt) {
            movingPoint.setLocation(e.getPoint());
            setCurve();
            repaint();
        }
    }
    @Override
    public void mouseMoved(MouseEvent e) {
        if (useNewMethod.isSelected())
            showControls = curve.pointOnCurve(e.getPoint(), 2, resolution.getValue()/100.0);
        else
            showControls = curve.intersects(e.getX()-2, e.getY()-2, 4, 4);
        repaint();
    }
    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setPaint(Color.white);
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setPaint(Color.black);
        g2.draw(curve);
        if (showControls)
            for (Point2D point : points) {
                g2.setPaint(Color.black);
                g2.drawOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
                g2.setPaint(Color.red);
                g2.fillOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
            }
    }
}

And also :

并且 :

import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D.Double;

@SuppressWarnings("serial")
public class QuadCurveWithMiddlePoint extends Double {

    private Point2D middlePoint = new Point2D.Double();
    private final double L = 0.5;

    public QuadCurveWithMiddlePoint(double x1,double y1, double xm, double ym, double x2, double y2) {
        super(x1,y1,xm,ym,x2,y2);
        setMiddlePoint(xm, ym);
    }

    public QuadCurveWithMiddlePoint() {
        this(0,0,0,0,0,0);
    }

    public Point2D getMiddlePoint() {
        calculateMiddlePoint();
        return middlePoint;
    }

    public void setMiddlePoint(double middleX, double middleY) {
        setCurve(getP1(), getControlPointByMiddle(middleX, middleY), getP2());
        calculateMiddlePoint();
    }

    public void setMiddlePoint(Point2D middle) {
        setMiddlePoint(middle.getX(),middle.getY());
    }

    private Point2D getControlPointByMiddle(double middleX,double middleY) {
        double cpx = (middleX-(L*L-2*L+1)*x1-(L*L)*x2)/(-2*L*L+2*L);
        double cpy = (middleY-(L*L-2*L+1)*y1-(L*L)*y2)/(-2*L*L+2*L);
        return new Point2D.Double(cpx,cpy);
    }

    private Point2D calculatePoint(double position) {
        if (position<0 || position>1)
            return null;
        double middlex = (position*position-2*position+1)*x1+(-2*position*position+2*position)*ctrlx+(position*position)*x2;
        double middley = (position*position-2*position+1)*y1+(-2*position*position+2*position)*ctrly+(position*position)*y2;
        return new Point2D.Double(middlex,middley);
    }

    public void calculateMiddlePoint() {
        middlePoint.setLocation(calculatePoint(L));
    }

    public void setCurveWithMiddlePoint(double xx1,double yy1, double xxm, double yym, double xx2, double yy2) {
        setCurve(xx1, yy1, xxm, yym, xx2, yy2);
        setMiddlePoint(xxm,yym);
    }

    public void setCurveWithMiddlePoint(Point2D start, Point2D middle, Point2D end) {
        setCurveWithMiddlePoint(start.getX(),start.getY(),middle.getX(),middle.getY(),end.getX(),end.getY());
    }

    public boolean pointOnCurve(Point2D point, double accuracy, double step) {
        if (accuracy<=0)
            return false;
        if (step<=0 || step >1)
            return false;
        boolean oncurve = false;
        double current = 0;
        while (!oncurve && current <= 1) {
            if (calculatePoint(current).distance(point)<accuracy)
                oncurve = true;
            current += step;
        }
        return oncurve;
    }

}

If you want to know how i made the class, do a search for basic linear algebra and also search Wikipedia for Bézier curves.

如果你想知道我是如何上课的,可以搜索基本的线性代数,也可以搜索*的Bézier曲线。