I have written a test program using AffineTransform to draw an arrowhead. The program takes the center of the window as the starting point of the arrowhead and the mouse position as the ending point. However, the resulting arrowhead position and angle are incorrect. When my mouse is at the location of the green dot, the correct arrowhead position should be as shown by the red arrowhead in the image below. I'm wondering what could be causing the incorrect arrowhead to be drawn.

The logic I used to draw the entire arrowhead is as follows:
First, I draw a horizontal line with a length of (length - arrowheadLength) starting from the position (0,0).

Then, I use (0,0) as the upper-left corner of a rectangle and apply shear and rotation to it before translating it to the right end of the horizontal line.
(shear)
(rotate)
(translate)

Next, the entire arrowhead is rotated based on the calculated angle between the mouse and the start rectangle positions.

Finally, the arrowhead is translated to the position of the start rectangle.

Below is the code for drawing the arrowhead:
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);
});
}
}
Could you please help me identify what might be causing the incorrect position and angle of the arrowhead to be drawn? Thank you.
I would reset the AffineTransform for the arrow head since needs an independent transformation on it before undergoing the main rotation and translation (which is already applied to the Graphics2D object.
Or alternatively, transform your head rectangle first, then the graphics coordinates, and then draw:
Either way, the skewed rectangle needs its own transformation.
But having said this, I wouldn't even use affine transforms for this but rather the
java.awt.geomlibrary including Path2D, and then using basic geometry draw my arrow head and shaft as it is needed. This way, you can still use effective RenderingHints to anti-alias the drawn shapes:As an aside, you will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.