4
Previously, I asked "How to draw an arrow using Java2d?" and now with the arrow drawn correctly and positioned within my circle, I would like to make the arrow rotate within the circle so that the center of the circle is the fixed frame of one of the arrow tips.
They told me that to calculate the rotation, I would need to use the formula:
A={x+L cos(θ),y+L sin(θ)}
where x
and y
are the coordinates at the new turning point, L
would be the size of the arrow and θ
would be the angle of the turn.
Although I understand the formula, I’m not able to apply in my class LineArrow
, because I draw it based on coordinates, and as it can change angle, I’m not sure how to calculate its size, regardless of the position in which it is in the circle. In the example I used the vertical position, but this position could be any one. I also do not know if it is the ideal to be applied in this code.
My class LineArrow
is as follows:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
public class LineArrow {
private int x;
private int y;
private int endX;
private int endY;
private Color color;
private int thickness;
private static final Polygon ARROW_HEAD = new Polygon();
static {
ARROW_HEAD.addPoint(0, 0);
ARROW_HEAD.addPoint(-5, -10);
ARROW_HEAD.addPoint(5, -10);
}
public LineArrow(int x, int y, int x2, int y2, Color color, int thickness) {
super();
this.x = x;
this.y = y;
this.endX = x2;
this.endY = y2;
this.color = color;
this.thickness = thickness;
}
public void draw(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// Calcula o ângulo da seta.
double angle = Math.atan2(endY - y, endX - x);
g2.setColor(color);
g2.setStroke(new BasicStroke(thickness));
// Desenha a linha. Corta 10 pixels na ponta para a ponta não ficar
// grossa.
g2.drawLine(x, y, (int) (endX - 10 * Math.cos(angle)), (int) (endY - 10 * Math.sin(angle)));
// Obtém o AffineTransform original.
AffineTransform tx1 = g2.getTransform();
// Cria uma cópia do AffineTransform.
AffineTransform tx2 = (AffineTransform) tx1.clone();
// Translada e rotaciona o novo AffineTransform.
tx2.translate(endX, endY);
tx2.scale(thickness / 2, thickness / 2);
tx2.rotate(angle - Math.PI / 2);
// Desenha a ponta com o AffineTransform transladado e rotacionado.
g2.setTransform(tx2);
g2.fill(ARROW_HEAD);
// Restaura o AffineTransform original.
g2.setTransform(tx1);
}
public void spin() {
// ????
}
}
Here is a compileable example:
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class SpinArrowTest extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JPanel board;
private JPanel controlsPane;
private JButton rotateButton;
public static void main(String[] args) {
EventQueue.invokeLater(() -> new SpinArrowTest().setVisible(true));
}
public SpinArrowTest() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(400, 300));
this.contentPane = new JPanel();
this.contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
this.contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(this.contentPane);
this.board = new Board();
this.contentPane.add(this.board, BorderLayout.CENTER);
this.controlsPane = new JPanel(new GridLayout(0, 1, 0, 0));
this.controlsPane.setBorder(new EmptyBorder(5, 1, 1, 1));
this.rotateButton = new JButton("Rotate");
this.rotateButton.addActionListener(e -> {
});
this.controlsPane.add(this.rotateButton);
this.contentPane.add(this.controlsPane, BorderLayout.SOUTH);
pack();
}
}
Class Main panel where the animation and drawing will take place:
class Board extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
private LineArrow line;
public void spin() {
line.spin();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int widthRectangle = getWidth();
int heightReclangle = getHeight();
int x, y, diameter;
if (widthRectangle <= heightReclangle) {
diameter = widthRectangle;
y = heightReclangle / 2 - diameter / 2;
x = 0;
} else {
diameter = heightReclangle;
x = widthRectangle / 2 - diameter / 2;
y = 0;
}
circle = new Circle(x, y, diameter, Color.red);
circle.draw(g);
line = new LineArrow(x + diameter / 2, y + diameter / 2, x + diameter / 2, y + diameter, Color.white, 3);
line.draw(g);
}
}
Class representing the circle:
class Circle {
int x;
int y;
int diameter;
Color color;
public Circle(int x, int y, int diameter, Color color) {
super();
this.x = x;
this.y = y;
this.diameter = diameter;
this.color = color;
}
public void draw(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(color);
g2.setPaint(new GradientPaint(x, y, color, x + diameter / 2, y + diameter / 2, color.darker()));
g2.fillOval(x, y, diameter, diameter);
}
}
Class representing the arrow that will rotate within the circle
class LineArrow {
private int x;
private int y;
private int endX;
private int endY;
private Color color;
private int thickness;
private static final Polygon ARROW_HEAD = new Polygon();
static {
ARROW_HEAD.addPoint(0, 0);
ARROW_HEAD.addPoint(-5, -10);
ARROW_HEAD.addPoint(5, -10);
}
public LineArrow(int x, int y, int x2, int y2, Color color, int thickness) {
super();
this.x = x;
this.y = y;
this.endX = x2;
this.endY = y2;
this.color = color;
this.thickness = thickness;
}
public void draw(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// Calcula o ângulo da seta.
double angle = Math.atan2(endY - y, endX - x);
g2.setColor(color);
g2.setStroke(new BasicStroke(thickness));
// Desenha a linha. Corta 10 pixels na ponta para a ponta não ficar
// grossa.
g2.drawLine(x, y, (int) (endX - 10 * Math.cos(angle)), (int) (endY - 10 * Math.sin(angle)));
// Obtém o AffineTransform original.
AffineTransform tx1 = g2.getTransform();
// Cria uma cópia do AffineTransform.
AffineTransform tx2 = (AffineTransform) tx1.clone();
// Translada e rotaciona o novo AffineTransform.
tx2.translate(endX, endY);
tx2.scale(thickness / 2, thickness / 2);
tx2.rotate(angle - Math.PI / 2);
// Desenha a ponta com o AffineTransform transladado e rotacionado.
g2.setTransform(tx2);
g2.fill(ARROW_HEAD);
// Restaura o AffineTransform original.
g2.setTransform(tx1);
}
public void spin() {
// ????
}
}
The result is the figure below (static):
This approach dispenses with the question formula, less mathematical! (hehe
– user28595
The interesting thing is, I thought I was going to have to sacrifice responsiveness, but that way, everything stays resized and looks the same. And I didn’t have to worry about deducing the size of
LineArrow
to calculate the angle.– user28595
A doubt: By "immutable", you mean that the classes that represent the two figures can have nothing changed after being instantiated, except the angle in the case of the Linearrow class?
– user28595
@An unchanging class is like
java.lang.String
or thejava.lang.Integer
. The values of all instance variables are defined only in the constructor and never change.– Victor Stafusa
@Nor the angle of
LineArrow
can be changed. What happens is that a new instance ofLineArrow
with a different angle is created in theBoard
.– Victor Stafusa
I see you’ve created an instance of
Graphics2D
on the Board to apply anti-aliasing, but Linearrow and Circle continue to receiveGraphics
and doing that same casting within them. If I change the two classes to receive the Graphics2d you already create on the Board it would be an optimization or it is indifferent?– user28595
@Article Actually I didn’t create an instance, I just used a cast. It’s the same object (ie,
g == g2
). If you want, you can change the type of theCircle
and ofLineArrow
forGraphics2D
to make it simpler. I wouldn’t call it optimization, but simplification. In fact, I’ll edit the answer to do just that.– Victor Stafusa