【问题标题】:How to make a curve to join two objects editable?如何使连接两个对象的曲线可编辑?
【发布时间】:2009-07-01 14:22:44
【问题描述】:

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

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

【问题讨论】:

    标签: java editor drawing java-2d


    【解决方案1】:

    首先对冗长的回复感到抱歉。我现在发布您问题的完整答案。我正在对 QuadCurve2D.Double 类进行子类化,现在通过一些数学运算,您可以用起点、终点和中间点而不是控制点来定义曲线。我还创建了一个新方法来检查一个点是否在曲线上。 intersects 方法检查形状的凸包是否与提供的形状相交,因此在凹曲线的情况下,这是有效的,但不准确。请注意,我检查一个点是否在曲线上的方法的实现在计算上相当昂贵并且不是 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);
                }
        }
    }
    

    还有:

    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;
        }
    
    }
    

    如果你想知道我是如何制作这门课的,请搜索基本线性代数并在维基百科中搜索Bézier curves。

    【讨论】:

    • 感谢您的回答。这正是我所做的,但我需要的是相同的,但将 Ellipse2D 设置在曲线内的中间点。我怎样才能明白这一点?
    • 请查看我发布的完整答案。它将为您提供一个在曲线中间有一个控制点的工人阶级。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多