AffineTransform 的箭头位置和角度不正确

问题描述 投票:0回答:1

我编写了一个使用 AffineTransform 绘制箭头的测试程序。程序以窗口中心为箭头起点,鼠标位置为终点。但是,生成的箭头位置和角度不正确。当我的鼠标位于绿点位置时,正确的箭头位置应如下图红色箭头所示。我想知道是什么导致绘制了错误的箭头。

我用来绘制整个箭头的逻辑如下:

  • 首先,我从位置 (0,0) 开始画一条长度为 (length - arrowheadLength) 的水平线。

  • 然后,我使用 (0,0) 作为矩形的左上角,并对其应用剪切和旋转,然后将其平移到水平线的右端。 (剪切) (旋转) (翻译)

  • 接下来,根据计算出的鼠标与起始矩形位置之间的角度旋转整个箭头。

  • 最后,箭头被平移到起始矩形的位置。

下面是绘制箭头的代码:

import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;

class CanvasPane extends JPanel {
    Rectangle start = new Rectangle();
    Line2D.Double arrowShaft = new Line2D.Double();
    Rectangle arrowhead = new Rectangle(new Dimension(16, 16));
    double shearMultiplier = 0.5;
    double arrowheadLength = arrowhead.width * Math.sqrt(2) * (1 + shearMultiplier);
    MouseEvent me;
    double length, angle;

    CanvasPane() {
        setPreferredSize(new Dimension(1000, 600));

        addMouseMotionListener(new MouseInputAdapter() {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                me = e;
                repaint();
            }
        });
    }

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

        start.setBounds(getWidth()/2 - 5, getHeight()/2 - 5, 10, 10);
        Graphics2D g2d = (Graphics2D) g;
        g2d.fill(start);

        if (me != null) {
            length = Point.distance(start.getCenterX(), start.getCenterY(), me.getX(), me.getY());
            angle = Math.atan2(me.getY() - start.getCenterY(), me.getX() - start.getCenterX());

            arrowShaft.setLine(0, 0, length - arrowheadLength, 0);
            AffineTransform storedTransform = g2d.getTransform();
            AffineTransform AT = new AffineTransform();

            AT.translate(start.getCenterX(), start.getCenterY());
            AT.rotate(angle);
            // For the arrowShaft, it is first rotated and then translated.
            g2d.transform(AT);
            g2d.draw(arrowShaft);

            AT.translate(length - arrowheadLength, 0);
            AT.rotate(Math.toRadians(-45));
            AT.shear(shearMultiplier, shearMultiplier);
            // For the arrowhead, it is first sheared, then rotated, translated, rotated again and finally translated.
            g2d.transform(AT);
            g2d.draw(arrowhead);

            g2d.setTransform(storedTransform);
        }
    }
}

public class DrawLine {
    Container createContentPane() {
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.setOpaque(true);
        contentPane.add(new CanvasPane(), BorderLayout.CENTER);
        return contentPane;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            DrawLine drawLine = new DrawLine();
            frame.setContentPane(drawLine.createContentPane());

            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

您能帮我确定是什么原因导致箭头绘制的位置和角度不正确吗?谢谢你。

java swing graphics graphics2d affinetransform
1个回答
0
投票

我会重置箭头的 AffineTransform,因为在进行主要旋转和平移之前需要对其进行独立变换(已应用于 Graphics2D 对象。

AT.translate(start.getCenterX(), start.getCenterY());
AT.rotate(angle);
g2d.transform(AT);
g2d.draw(arrowShaft);    
            
AT.setToIdentity();
AT.translate(length - arrowheadLength, 0);
AT.rotate(Math.toRadians(-45));
AT.shear(shearMultiplier, shearMultiplier);
g2d.transform(AT);
g2d.draw(arrowhead);  

或者,先变换头部矩形,然后变换图形坐标,然后绘制:

AffineTransform headTransform = new AffineTransform();
headTransform.translate(length - arrowheadLength, 0);
headTransform.rotate(Math.toRadians(-45));
headTransform.shear(shearMultiplier, shearMultiplier);
Shape arrowhead2 = headTransform.createTransformedShape(new Rectangle(new Dimension(16, 16)));

AT.translate(start.getCenterX(), start.getCenterY());
AT.rotate(angle);

g2d.transform(AT);
g2d.draw(arrowShaft);
g2d.draw(arrowhead2);

无论哪种方式,倾斜的矩形都需要自己的变换。

但是话虽如此,我什至不会使用仿射变换,而是使用包括 Path2D 的

java.awt.geom
库,然后使用基本几何图形根据需要绘制箭头和轴。这样,您仍然可以使用有效的 RenderingHints 来对绘制的形状进行抗锯齿:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class CanvasPane2 extends JPanel {
    private static final int PREF_W = 1000;
    private static final int PREF_H = 600;
    private static final int ARROW_HEAD_LENGTH = 36;
    private static final int ARROW_HEAD_WIDTH = 16;
    private static final Color ARROW_HEAD_COLOR = Color.RED;
    private static final Stroke ARROW_HEAD_STROKE = new BasicStroke(3f);
    private double theta;
    private double r;
    private Line2D arrowShaft;
    private Shape arrowHead;

    public CanvasPane2() {
        addMouseMotionListener(new MyMouse());
    }
    
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(PREF_W, PREF_H);
    }
    
    private class MyMouse extends MouseAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            int x1 = e.getX() - getWidth() / 2;
            int y1 = e.getY() - getHeight() / 2;
            theta = Math.atan2(y1, x1);
            r = Point.distance(0, 0, x1, y1);
            arrowHead = createArrowHead();
            arrowShaft = createShaft();
            repaint();
        }
    }
    
    private Line2D createShaft() {
        double x = (r - ARROW_HEAD_LENGTH) * Math.cos(theta);
        double y = (r - ARROW_HEAD_LENGTH) * Math.sin(theta);
        
        Line2D line = new Line2D.Double(transX(0), transY(0), transX(x), transY(y));
        return line;
    }
    
    private Shape createArrowHead() {
        Path2D path = new Path2D.Double();
        Point2D tip, base, right, left;

        double tipX = r * Math.cos(theta);
        double tipY = r * Math.sin(theta);
        tip = new Point2D.Double(transX(tipX), transY(tipY));
        
        double baseR = (r - ARROW_HEAD_LENGTH);       
        double baseX = baseR * Math.cos(theta);
        double baseY = baseR * Math.sin(theta);        
        base = new Point2D.Double(transX(baseX), transY(baseY));
        
        double sideR = r - ARROW_HEAD_LENGTH / 2.0;       
        double deltaTheta = Math.atan2(ARROW_HEAD_WIDTH / 2.0, sideR); 
        
        double rightX = sideR * Math.cos(theta + deltaTheta);
        double rightY = sideR * Math.sin(theta + deltaTheta);        
        right = new Point2D.Double(transX(rightX), transY(rightY));
        
        double leftX = sideR * Math.cos(theta - deltaTheta);
        double leftY = sideR * Math.sin(theta - deltaTheta);        
        left = new Point2D.Double(transX(leftX), transY(leftY));
        
        path.moveTo(tip.getX(), tip.getY());
        path.lineTo(right.getX(), right.getY());
        path.lineTo(base.getX(), base.getY());
        path.lineTo(left.getX(), left.getY());
        path.closePath();
        return path;
    }
    
    public double transX(double x0) {
        return x0 + getWidth() / 2;
    }

    public double transY(double y0) {
        return y0 + getHeight() / 2;
    }

    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int delta = 5;
        int x = getWidth() / 2 - delta;
        int y = getHeight() / 2 - delta;
        g2d.fill(new Rectangle(x, y, 2 * delta, 2 * delta));
        
        if (arrowShaft != null) {
            g2d.draw(arrowShaft);
        }
        
        if (arrowHead != null) {
            Graphics2D g2dB = (Graphics2D) g2d.create();
            g2dB.setColor(ARROW_HEAD_COLOR);
            g2dB.setStroke(ARROW_HEAD_STROKE);
            g2dB.fill(arrowHead);
            g2dB.dispose();
        }
    }
    

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            CanvasPane2 mainPanel = new CanvasPane2();

            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

顺便说一句,您将需要学习和使用 Java 命名约定。变量名应全部以小写字母开头,而类名应以大写字母开头。学习并遵循这一点将使我们更好地理解您的代码,并使您更好地理解其他人的代码。

© www.soinside.com 2019 - 2024. All rights reserved.