java 仿射变换中箭头的位置和Angular 不正确

7hiiyaii  于 2023-08-02  发布在  Java
关注(0)|答案(1)|浏览(115)

我写了一个测试程序,使用AffineTransform绘制箭头。程序以窗口的中心作为箭头的起点,以鼠标的位置作为终点。但是,生成的箭头位置和Angular 不正确。当鼠标位于绿色位置时,正确的箭头位置应如下图中的红色箭头所示。我想知道是什么原因导致箭头画错了。x1c 0d1x的数据
我用来绘制整个箭头的逻辑如下:

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

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

    (剪切)

    (旋转)x1c4d 1x(平移)

  • 接下来,根据计算出的鼠标和开始矩形位置之间的Angular 旋转整个箭头。

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

下面是绘制箭头的代码:

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

字符串
你能帮我找出是什么原因导致箭头的位置和Angular 不正确吗?- 谢谢-谢谢

lrl1mhuk

lrl1mhuk1#

我会重置箭头的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命名约定。变量名都应该以小写字母开始,而类名则以大写字母开头。学习并遵循这一点将使我们更好地理解您的代码,并使您更好地理解其他人的代码。

相关问题