I'm creating a 2D topdown shooter game with Java Swing in which I want circular hitboxes for my player and enemies as well as projectiles. For hit detection I need to figure out if there is an intersection between a projtile and a Sprite (player or enemy). My issue is that Ellipse2D's intersect function (that takes a position a width and height) creates a Rectangle out of the arguments. In its description it advises using Area for high precision and I was hoping it had an intersect function for any shape but that also casts its argument to a Rectangle.
Here's the jist of my Sprite object:
public class Sprite {
protected float worldX;
protected float worldY;
protected float drawX;
protected float drawY;
protected int width;
protected int height;
protected Image image;
...
}
In essence I'm storing their x and y coordinates as well as their width and height.(drawX and drawY are only used for rendering)
Is there a build-in method (preferably in Swing) to intersect Ellipses with other shapes (specifically other Ellipses and Rectangles) or is there no better option then implementing these by hand?
With a simple modification to the answer form Detecting collision of two sprites that can rotate
You can end up with something like...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Ellipse2D rect01;
private Rectangle rect02;
private int angle = 0;
public TestPane() {
rect01 = new Ellipse2D.Double(0, 0, 200, 50);
rect02 = new Rectangle(0, 0, 100, 100);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle++;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
AffineTransform at = new AffineTransform();
int center = width / 2;
int x = center + (center - rect01.getBounds().width) / 2;
int y = (height - rect01.getBounds().height) / 2;
at.translate(x, y);
at.rotate(Math.toRadians(angle), rect01.getBounds().width / 2, rect01.getBounds().height / 2);
GeneralPath path1 = new GeneralPath();
path1.append(rect01.getPathIterator(at), true);
g2d.fill(path1);
g2d.setColor(Color.BLUE);
g2d.draw(path1.getBounds());
at = new AffineTransform();
x = (center - rect02.width) / 2;
y = (height - rect02.height) / 2;
at.translate(x, y);
at.rotate(Math.toRadians(-angle), rect02.width / 2, rect02.height / 2);
GeneralPath path2 = new GeneralPath();
path2.append(rect02.getPathIterator(at), true);
g2d.fill(path2);
g2d.setColor(Color.BLUE);
g2d.draw(path2.getBounds());
Area a1 = new Area(path1);
Area a2 = new Area(path2);
a2.intersect(a1);
if (!a2.isEmpty()) {
g2d.setColor(Color.RED);
g2d.fill(a2);
}
g2d.dispose();
}
}
}
So I want to rotate this Rectangle I made
public void paint (Graphics g)
{
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.fillRect(10, 10, 30, 30);
g2.rotate(Math.toRadians(45)); //I tried this but doesn't seem to work...
}
How do I do that? Rotate as in rotate in 45* angle or 200* angle.
It really isn't that hard to rotate objects. Most of the code below is simply boiler plate to create the frames and panels. Here is a demo that shows two methods that were mentioned in the comments.
the left panel simply rotates the graphics context. This is, imo, the easiest method but it does not alter the object.
the right panel uses the AffineTransform to rotate the object. This actually changes the contents of the shape.
If the desire is to rotate an object in place it is necessary to ensure one is rotating about the middle of the image that is under rotation. In both cases below that would be (125,125) or the center of both the panels and the rectangle.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.LineBorder;
public class RotateRectangle extends JPanel {
JFrame frame = new JFrame();
double angle = 0;
MyPanel mypanel = new MyPanel();
public static void main(String[] args) {
SwingUtilities
.invokeLater(() -> new RotateRectangle().start());
}
public void start() {
Timer t = new Timer(0, (ae) -> {mypanel.rotateit(); frame.repaint();});
t.setDelay(30);
t.start();
}
public RotateRectangle() {
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
frame.add(mypanel);
setBorder(new LineBorder(Color.red,2));
mypanel.setBorder(new LineBorder(Color.red, 2));
frame.pack();
// center on screen
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// visually smooth the lines
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.BLACK);
g2d.rotate(angle, 125,125);
g2d.drawRect(75, 75, 100, 100);
// adjust the amount of rotation per timer interval
angle += Math.PI / 200;
g2d.dispose();
}
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
class MyPanel extends JPanel {
Polygon polygon = new Polygon();
// amount to rotate
double angle = Math.PI / 200;
Shape shape = polygon;
AffineTransform af = new AffineTransform();
public MyPanel() {
af.rotate(angle, 125,125);
polygon.addPoint(75,175);
polygon.addPoint(175,175);
polygon.addPoint(175,75);
polygon.addPoint(75,75);
}
public void rotateit() {
shape = af.createTransformedShape(shape);
}
public void paintComponent(Graphics g) {
if (shape == null) {
return;
}
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
// visually smooth the lines
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.BLACK);
g2d.draw(shape);
}
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
Following up on my comment, I created the following GUI.
I used math to calculate the four endpoints of a rotated rectangle and used the Graphics2D fillPolygon method to draw the rectangle.
The buttons at the bottom of the GUI allow you to rotate the rectangle on the center point or the upper left endpoint.
I created a drawing JPanel to draw the rectangle. All the paintComponent method of the drawing JPanel does is draw the Polygon returned by the application model.
The application model is a key part of this application. I create a plain Java getter / setter class. I start with a java.awt.Rectangle and use polar coordinates to rotate the rectangle. I convert the polar coordinates back to cartesian coordinates to get the four endpoints of the Polygon.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class RotatingRectangle implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotatingRectangle());
}
private DrawingPanel drawingPanel;
private JButton centerButton;
private JButton endPointButton;
private RotatedRectangle rotatedRectangle;
public RotatingRectangle() {
this.rotatedRectangle = new RotatedRectangle(Color.BLUE,
new Rectangle(200, 200, 100, 50));
}
#Override
public void run() {
JFrame frame = new JFrame("Rotating Rectangle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.drawingPanel = new DrawingPanel(rotatedRectangle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.AFTER_LAST_LINE);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));
ButtonListener listener = new ButtonListener(this, rotatedRectangle);
centerButton = new JButton("Rotate on center point");
centerButton.addActionListener(listener);
panel.add(centerButton);
endPointButton = new JButton("Rotate on end point");
endPointButton.addActionListener(listener);
endPointButton.setPreferredSize(centerButton.getPreferredSize());
panel.add(endPointButton);
return panel;
}
public void repaint() {
drawingPanel.repaint();
}
public JButton getCenterButton() {
return centerButton;
}
public JButton getEndPointButton() {
return endPointButton;
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private RotatedRectangle rotatedRectangle;
public DrawingPanel(RotatedRectangle rotatedRectangle) {
this.rotatedRectangle = rotatedRectangle;
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(400, 400));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Polygon polygon = rotatedRectangle.getRectangle();
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(rotatedRectangle.getColor());
g2d.fillPolygon(polygon);
}
}
public class ButtonListener implements ActionListener {
private final RotatingRectangle frame;
private final RotatedRectangle model;
private Timer timer;
public ButtonListener(RotatingRectangle frame, RotatedRectangle model) {
this.frame = frame;
this.model = model;
}
#Override
public void actionPerformed(ActionEvent event) {
if (timer != null) {
timer.stop();
}
JButton button = (JButton) event.getSource();
if (button.equals(frame.getEndPointButton())) {
model.setCenterPoint(false);
model.setAngle(180);
} else {
model.setCenterPoint(true);
model.setAngle(0);
}
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
model.incrementAngle(1);
frame.repaint();
}
});
timer.start();
}
}
public class RotatedRectangle {
private boolean centerPoint;
private int angle;
private final Color color;
private final Rectangle rectangle;
public RotatedRectangle(Color color, Rectangle rectangle) {
this.color = color;
this.rectangle = rectangle;
this.angle = 0;
this.centerPoint = true;
}
public int getAngle() {
return angle;
}
public void setAngle(int angle) {
this.angle = angle;
}
public void incrementAngle(int increment) {
this.angle += increment;
this.angle %= 360;
}
public Polygon getRectangle() {
Point rotatePoint = new Point(rectangle.x, rectangle.y);
if (isCenterPoint()) {
int x = rectangle.x + rectangle.width / 2;
int y = rectangle.y + rectangle.height / 2;
rotatePoint = new Point(x, y);
}
Point[] point = new Point[4];
int width = rectangle.x + rectangle.width;
int height = rectangle.y + rectangle.height;
point[0] = new Point(rectangle.x, rectangle.y);
point[1] = new Point(width, rectangle.y);
point[2] = new Point(width, height);
point[3] = new Point(rectangle.x, height);
Polygon polygon = new Polygon();
for (int i = 0; i < point.length; i++) {
point[i] = calculatePoint(rotatePoint, point[i], angle);
polygon.addPoint(point[i].x, point[i].y);
}
return polygon;
}
private Point calculatePoint(Point rotatePoint, Point point, int angle) {
double theta = Math.toRadians(angle);
int xDistance = rotatePoint.x - point.x;
int yDistance = rotatePoint.y - point.y;
double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);
double alpha = Math.atan2(yDistance, xDistance);
theta += alpha;
int x = (int) Math.round(Math.cos(theta) * distance) + rotatePoint.x;
int y = (int) Math.round(Math.sin(theta) * distance) + rotatePoint.y;
return new Point(x, y);
}
public Color getColor() {
return color;
}
public boolean isCenterPoint() {
return centerPoint;
}
public void setCenterPoint(boolean centerPoint) {
this.centerPoint = centerPoint;
}
}
}
I'm rather new to Java Swing, and working on a ladders and snaked project for my college course. The instructor has told us to implement a game in which the player can choose exactly how many snakes are on the game board, and where the snakes are. So is for the ladders! So I cannot use one, or several fixed images in my game, so that the player cannot change them anymore.
I need a way to draw such snakes and ladders in my game. The question is what is the best option to do this in Java? By which means can I draw user-desired snakes on my game board?
One thing you could do, is rotate the image by a given angle, this way, you could still use images and supply the ability to change their start and end points
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage ladder;
private double angle;
public TestPane() throws IOException {
ladder = ImageIO.read(getClass().getResource("Ladder.png"));
JSlider slider = new JSlider(0, 100, 0);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
angle = (360d * (slider.getValue() / 100d));
repaint();
}
});
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2d.setColor(Color.RED);
g2d.rotate(Math.toRadians(angle), x, y);
g2d.drawImage(ladder, x - (ladder.getWidth() / 2), y - ladder.getHeight(), this);
g2d.fillOval(x - 3, y - 3, 6, 6);
g2d.dispose();
}
protected void applyQualityRenderingHints(Graphics2D g2d) {
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
}
}
}
Now, because you're rotating the actual Graphics context, this can become very complicated very quickly, especially when you're trying to change the location and rotate a number of objects
Another option might be to rotate the image as a whole, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage ladder;
private double angle;
public TestPane() throws IOException {
ladder = ImageIO.read(getClass().getResource("Ladder.png"));
JSlider slider = new JSlider(0, 100, 0);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
angle = (360d * (slider.getValue() / 100d));
repaint();
}
});
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2d.setColor(Color.RED);
BufferedImage rotated = rotate(ladder, angle);
g2d.drawImage(rotated, x - (rotated.getWidth() / 2), y - (rotated.getHeight() / 2), this);
g2d.dispose();
}
public BufferedImage rotate(BufferedImage image, double byAngle) {
double rads = Math.toRadians(byAngle);
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = image.getWidth();
int h = image.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = rotated.createGraphics();
applyQualityRenderingHints(g2d);
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
int x = w / 2;
int y = h / 2;
at.rotate(Math.toRadians(byAngle), x, y);
g2d.setTransform(at);
g2d.drawImage(image, 0, 0, this);
g2d.dispose();
return rotated;
}
protected void applyQualityRenderingHints(Graphics2D g2d) {
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
}
}
}
Either way, you've got some serious management and maths ahead of you.
You might like to take a closer look at Painting in AWT and Swing and Performing Custom Painting and 2D Graphics for more details
There are many (many, many) degrees of freedom for answering this question. In fact, one could consider it as "too broad", as it is not much more specific than "how can I paint something in Swing?" (with "something" being a snake or a ladder here). There is a reason of why a significant part of game development is not only plain programming, but also the graphics design etc.
It is not clear how much the task of your course is focussed on exactly this point. If it is a general computer science course, then there likely is no need to spend dozens of hours for making the "prettiest" game. Instead, it could be sufficient to draw plain lines between the fields that the snakes/ladders should connect. Green lines for snakes, brown lines for ladders. However, maybe the priorities are different.
Regarding this question in particular, there are, broadly speaking, two options:
Paint the snakes as images
Paint the snakes as graphical objects
MadProgrammer showed in his answer the approach of using images. They can be rotated and drawn and scaled arbitrarily. In fact, when you have an image, say of size 100x1000, then you could make it span two arbitrary points. So if you have the points (200,400) and (700,1100) on the screen, then you can compute an orientation and scaling for the image so that the top center point of your image is located at (200,400), and the bottom center point is at (700,1100) - which is likely a requirement that could appear when you want to "draw a ladder starting at one field and ending at another".
The issue that I saw regarding the snakes was that the "contents" of the image would have to depend on the start- and end point. Namely, a snake that is painted between two fields that are close to each other might have a completely different shape than one that is painted between two distant fields.
(Similarly, a ladder: The number of steps that the ladder should have would certainly depend on the distance between the fields that it connects).
So, I did some "recreational programming" here, and created a snake painting class. The difference, compared to images, is that the snakes are graphical objects - particularly, they are composed of Shape objects. The tricky part is the body of the snake: It should have some waves, and a certain thickness, and the thickness should largely be constant along the body, except for the tail part....
Again: There are many degrees of freedom, and of course, this is just a snippet, quickly written down, to see (mainly for myself) of how one could tackle this problem of "drawing a snake body".
The result is a snake where you can drag around the head and tail between arbitrary points:
Some of the degrees of freedom that I mentioned are summarized as (compile-time) variables in the Snake class. One could, for example, adjust the number of "waves" based on the distance between the head and the tail point:
But these are things that I'll leave to the real artists ;-)
The code is a bit crude and largely uncommented, but maybe someone finds it helpful nevertheless:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SnakeDrawing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new SnakeDrawingPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class Snake
{
private Point2D point0 = new Point2D.Double(100,500);
private Point2D point1 = new Point2D.Double(700,500);
double bodyWidth = 10;
int waves = 4;
double waveHeight = 0.05;
double tailStart = 0.8;
double headLength = 20;
double headWidth = 16;
double eyeRadius = 6;
double irisRadius = 3;
private Shape body;
private Shape head;
private Shape eyeR;
private Shape eyeL;
private Shape irisR;
private Shape irisL;
void setPoints(Point2D point0, Point2D point1)
{
this.point0.setLocation(point0);
this.point1.setLocation(point1);
AffineTransform at = AffineTransform.getRotateInstance(
currentAngleRad(), point0.getX(), point0.getY());
at.translate(point0.getX(), point0.getY());
createBody(at);
createHead(at);
}
void draw(Graphics2D g)
{
g.setColor(new Color(0,128,0));
g.fill(body);
g.fill(head);
g.setColor(Color.WHITE);
g.fill(eyeR);
g.fill(eyeL);
g.setColor(Color.BLACK);
g.fill(irisR);
g.fill(irisL);
}
private void createBody(AffineTransform at)
{
double distance = point1.distance(point0);
int steps = 100;
Path2D body = new Path2D.Double();
Point2D previousPoint = null;
for (int i=0; i<steps; i++)
{
double alpha = (double)i/(steps-1);
Point2D point = computeCenterPoint(alpha, distance);
if (previousPoint != null)
{
Point2D bodyPoint =
computeBodyPoint(alpha, point, previousPoint);
if (i==1)
{
body.moveTo(bodyPoint.getX(), bodyPoint.getY());
}
else
{
body.lineTo(bodyPoint.getX(), bodyPoint.getY());
}
}
previousPoint = point;
}
previousPoint = null;
for (int i=steps-1; i>=0; i--)
{
double alpha = (double)i/(steps-1);
Point2D point = computeCenterPoint(alpha, distance);
if (previousPoint != null)
{
Point2D bodyPoint =
computeBodyPoint(alpha, point, previousPoint);
body.lineTo(bodyPoint.getX(), bodyPoint.getY());
}
previousPoint = point;
}
this.body = at.createTransformedShape(body);
}
private Point2D computeBodyPoint(
double alpha, Point2D point, Point2D previousPoint)
{
double dx = point.getX() - previousPoint.getX();
double dy = point.getY() - previousPoint.getY();
double rdx = -dy;
double rdy = dx;
double d = Math.hypot(dx, dy);
double localBodyWidth = bodyWidth;
if (alpha > tailStart)
{
localBodyWidth *= (1 - (alpha - tailStart) / (1.0 - tailStart));
}
double px = point.getX() + rdx * (1.0 / d) * localBodyWidth;
double py = point.getY() + rdy * (1.0 / d) * localBodyWidth;
return new Point2D.Double(px, py);
}
private Point2D computeCenterPoint(
double alpha, double distance)
{
double r = alpha * Math.PI * 2 * waves;
double verticalScaling = 1 - (alpha * 2 - 1) * (alpha * 2 - 1);
double y = Math.sin(r) * distance * waveHeight * verticalScaling;
double x = alpha * distance;
return new Point2D.Double(x,y);
}
private void createHead(AffineTransform at)
{
Shape head = new Ellipse2D.Double(
-headLength, -headWidth,
headLength + headLength,
headWidth + headWidth);
this.head = at.createTransformedShape(head);
Shape eyeR = new Ellipse2D.Double(
-headLength * 0.5 - eyeRadius,
-headWidth * 0.6 - eyeRadius,
eyeRadius + eyeRadius,
eyeRadius + eyeRadius);
Shape eyeL = new Ellipse2D.Double(
-headLength * 0.5 - eyeRadius,
headWidth * 0.6 - eyeRadius,
eyeRadius + eyeRadius,
eyeRadius + eyeRadius);
this.eyeR = at.createTransformedShape(eyeR);
this.eyeL = at.createTransformedShape(eyeL);
Shape irisR = new Ellipse2D.Double(
-headLength * 0.4 - eyeRadius,
-headWidth * 0.6 - irisRadius,
irisRadius + irisRadius,
irisRadius + irisRadius);
Shape irisL = new Ellipse2D.Double(
-headLength * 0.4 - eyeRadius,
headWidth * 0.6 - irisRadius,
irisRadius + irisRadius,
irisRadius + irisRadius);
this.irisR = at.createTransformedShape(irisR);
this.irisL = at.createTransformedShape(irisL);
}
private double currentAngleRad()
{
double dx = point1.getX() - point0.getX();
double dy = point1.getY() - point0.getY();
double angleRad = Math.atan2(dy, dx);
return angleRad;
}
}
class SnakeDrawingPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(100,500);
private Point2D point1 = new Point2D.Double(700,500);
private Point2D draggedPoint = null;
private Snake snake = new Snake();
SnakeDrawingPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
snake.setPoints(point0, point1);
snake.draw(g);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
EDIT:
As an example / extension of the answer by MadProgrammer, here is a program that contains a method that allows you to draw an image between two given points. So, for a given ladder image, you can basically drag around the top- and bottom center point of the image:
Coincidentally, the relevant method is called drawImageBetweenPoints:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LadderDrawing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new LadderDrawingPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LadderDrawingPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(300,300);
private Point2D point1 = new Point2D.Double(500,700);
private Point2D draggedPoint = null;
private BufferedImage ladderImage;
LadderDrawingPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
try
{
ladderImage = ImageIO.read(new File("ladder.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
paintDot(g, point0, 8);
paintDot(g, point1, 8);
drawImageBetweenPoints(g, ladderImage, point0, point1);
}
private static void paintDot(Graphics2D g, Point2D p, double radius)
{
g.fill(new Ellipse2D.Double(
p.getX() - radius, p.getY() - radius,
radius + radius, radius + radius));
}
private static void drawImageBetweenPoints(
Graphics2D g, BufferedImage image, Point2D p0, Point2D p1)
{
AffineTransform at = new AffineTransform();
at.concatenate(AffineTransform.getTranslateInstance(
p0.getX(), p0.getY()));
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double angleRad = Math.atan2(dy, dx) - Math.PI * 0.5;
at.concatenate(AffineTransform.getRotateInstance(angleRad));
double distance = p1.distance(p0);
double scalingY = distance / image.getHeight();
// Default: Uniform scaling
double scalingX = scalingY;
// For keeping the width of the image
//scalingX = 1.0;
// For scaling to a fixed width:
//double desiredWidth = 50;
//scalingX = desiredWidth / image.getWidth();
at.concatenate(AffineTransform.getScaleInstance(scalingX, scalingY));
at.concatenate(AffineTransform.getTranslateInstance(
-image.getWidth() * 0.5, 0));
AffineTransform oldAT = g.getTransform();
g.transform(at);
g.drawImage(image, 0, 0, null);
g.setTransform(oldAT);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
Again, I think that a manual drawing may be more flexible (and, particularly for the ladder, not much more difficult), because you can select the number of steps of the ladder to dynamically adjust based on the distance of the points. For example:
It boils down to a bit of math for computing the positions of the bars and steps, and playing a bit with strokes and shapes:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LadderDrawingManual
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new LadderDrawingManualPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LadderDrawingManualPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(300,300);
private Point2D point1 = new Point2D.Double(500,700);
private Point2D draggedPoint = null;
LadderDrawingManualPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
paintDot(g, point0, 8);
paintDot(g, point1, 8);
drawLadderBetweenPoints(g, point0, point1);
}
private static void paintDot(Graphics2D g, Point2D p, double radius)
{
g.fill(new Ellipse2D.Double(
p.getX() - radius, p.getY() - radius,
radius + radius, radius + radius));
}
private static void drawLadderBetweenPoints(
Graphics2D g, Point2D p0, Point2D p1)
{
final double ladderWidth = 40;
final double distanceBetweenSteps = 30;
final double barWidth = 5;
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double distance = p1.distance(p0);
double dirX = dx / distance;
double dirY = dy / distance;
double offsetX = dirY * ladderWidth * 0.5;
double offsetY = -dirX * ladderWidth * 0.5;
Line2D lineR = new Line2D.Double(
p0.getX() + offsetX,
p0.getY() + offsetY,
p1.getX() + offsetX,
p1.getY() + offsetY);
Line2D lineL = new Line2D.Double(
p0.getX() - offsetX,
p0.getY() - offsetY,
p1.getX() - offsetX,
p1.getY() - offsetY);
drawBar(g, lineL, barWidth);
drawBar(g, lineR, barWidth);
int numSteps = (int)(distance / distanceBetweenSteps);
for (int i=0; i<numSteps; i++)
{
double stepOffsetX = (i+1) * distanceBetweenSteps;
double stepOffsetY = (i+1) * distanceBetweenSteps;
Line2D step = new Line2D.Double(
p0.getX() + stepOffsetX * dirX - offsetX,
p0.getY() + stepOffsetY * dirY - offsetY,
p0.getX() + stepOffsetX * dirX + offsetX,
p0.getY() + stepOffsetY * dirY + offsetY);
drawBar(g, step, barWidth);
}
}
private static void drawBar(Graphics2D g, Line2D line, double barWidth)
{
Stroke stroke = new BasicStroke(
(float)barWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
Shape bar = stroke.createStrokedShape(line);
g.setColor(new Color(200,100,0));
g.fill(bar);
g.setColor(Color.BLACK);
g.draw(bar);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
I'm trying to draw a diamond (for a composition UML relationship). Right now, I am making a triangle like this:
but I want to make something like this:
And I do the triangle with this code:
private void drawArrowHead(Graphics2D g2, Point tip, Point tail,
Color color) {
g2.setPaint(color);
double dy = tip.y - tail.y;
double dx = tip.x - tail.x;
double theta = Math.atan2(dy, dx);
double x, y, rho = theta + phi;
Point p1 = new Point();
Point p2 = new Point();
p1.setLocation(tip.x - barb * Math.cos(rho), tip.y - barb * Math.sin(rho));
rho = theta - phi;
p2.setLocation(tip.x - barb * Math.cos(rho), tip.y - barb * Math.sin(rho));
int[] xPoints = new int[5];
int[] yPoints = new int[5];
xPoints[0] = tip.x;
xPoints[1] = p1.x;
xPoints[2] = p2.x;
yPoints[0] = tip.y;
yPoints[1] = p1.y;
yPoints[2] = p2.y;
g2.setPaint(Color.BLACK);
Shape shape = new Polygon(xPoints, yPoints, 3);
g2.fill(shape);
//tip.x - barb * Math.cos(rho);
//y = tip.y - barb * Math.sin(rho);
}
Does anyone have an idea on how to make a diamond out of this? Thanks :)
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawArrows {
public static void main(String[] args) {
new DrawArrows();
}
public DrawArrows() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager
.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException
| UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new UMLWindow();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(30, 30, 1000, 700);
frame.getContentPane().setBackground(Color.white);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
});
}
public static class UMLWindow extends JFrame {
Shapes shapeList = new Shapes();
Panel panel;
private static final long serialVersionUID = 1L;
public UMLWindow() {
addMenus();
panel = new Panel();
}
public void addMenus() {
getContentPane().add(shapeList);
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
JMenuItem lineMenuItem = new JMenuItem("New Line");
lineMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("adding line");
shapeList.addLine();
}
});
JMenuBar menubar = new JMenuBar();
menubar.add(lineMenuItem);
setJMenuBar(menubar);
}
}
public static class Shapes extends JPanel {
private static final long serialVersionUID = 1L;
private List<Line2D.Double> lines = new ArrayList<Line2D.Double>();
private Boolean drawing = false;
private Point lineStartingPoint = new Point();
private Point lineEndingPoint = new Point();
private Line2D.Double linePath;
double phi = Math.toRadians(40);
int barb = 20;
public Shapes() {
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
addMouseListener(myMouseAdapter);
addMouseMotionListener(myMouseAdapter);
this.setOpaque(true);
this.setBackground(Color.WHITE); // set canvas color
}
public void addLine() {
drawing = true;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
if (drawing) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(2));
g2.drawLine(lineStartingPoint.x, lineStartingPoint.y,
lineEndingPoint.x, lineEndingPoint.y);
drawArrowHead(g2, lineEndingPoint, lineStartingPoint,
Color.BLACK);
}
for (Line2D line : lines) {
g2.setColor(Color.BLACK);
Point sw = new Point((int) line.getX1(), (int) line.getY1());
Point ne = new Point((int) line.getX2(), (int) line.getY2());
g2.draw(line);
drawArrowHead(g2, ne, sw, Color.BLACK);
}
}
public Rectangle2D drawRect(int x, int y) {
return new Rectangle2D.Double(x - 4, y - 4, 8, 8);
}
private void drawArrowHead(Graphics2D g2, Point tip, Point tail,
Color color) {
g2.setPaint(color);
double dy = tip.y - tail.y;
double dx = tip.x - tail.x;
double theta = Math.atan2(dy, dx);
double x, y, rho = theta + phi;
Point p1 = new Point();
Point p2 = new Point();
p1.setLocation(tip.x - barb * Math.cos(rho), tip.y - barb * Math.sin(rho));
rho = theta - phi;
p2.setLocation(tip.x - barb * Math.cos(rho), tip.y - barb * Math.sin(rho));
int[] xPoints = new int[5];
int[] yPoints = new int[5];
xPoints[0] = tip.x;
xPoints[1] = p1.x;
xPoints[2] = p2.x;
yPoints[0] = tip.y;
yPoints[1] = p1.y;
yPoints[2] = p2.y;
g2.setPaint(Color.BLACK);
Shape shape = new Polygon(xPoints, yPoints, 3);
g2.fill(shape);
//tip.x - barb * Math.cos(rho);
//y = tip.y - barb * Math.sin(rho);
}
class MyMouseAdapter extends MouseAdapter {
int currentIndex;
Point2D.Double startPoint = new Point2D.Double();
Point2D.Double endPoint = new Point2D.Double();
Boolean resizing = false;
#Override
public void mousePressed(MouseEvent e) {
if (drawing) {
lineStartingPoint = e.getPoint();
lineEndingPoint = lineStartingPoint;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
lineEndingPoint = e.getPoint();
repaint();
System.out.println(lines.size());
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (drawing) {
drawLine(e);
}
drawing = false;
}
#Override
public void mouseMoved(MouseEvent e) {
}
public void drawLine(MouseEvent e) {
drawing = false;
lineEndingPoint = e.getPoint();
linePath = new Line2D.Double(lineStartingPoint.getX(),
lineStartingPoint.getY(), lineEndingPoint.getX(),
lineEndingPoint.getY());
lines.add(linePath);
repaint();
}
}
}
}
Personally, I would focus on generating the shape and the using the Graphics2D API to perform the translation and transformation...
The Shape API makes it really easy to generate complex shapes, for example, this is the diamond shape used in the following example...
public class DiamondShape extends Path2D.Double {
public DiamondShape(int width, int height) {
moveTo(width / 2, 0);
lineTo(width, height / 2);
lineTo(width / 2, height);
lineTo(0, height / 2);
closePath();
}
}
It's short, simple and (for the most part), easy to understand...
"But it needs to rotate" I hear you say, but of course, the Graphics API provides awesome and simple functionality to achieve that, let it do the heavy lifting. However, if you need to only transform the shape, the Shape API can do that to (transform the shape itself)
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FollowMe {
public static void main(String[] args) {
new FollowMe();
}
public FollowMe() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Point mousePoint;
private DiamondShape head;
public TestPane() {
head = new DiamondShape(10, 20);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
double rotation = 0f;
int width = getWidth() - 1;
int height = getHeight() - 1;
if (mousePoint != null) {
int x = width / 2;
int y = height / 2;
int deltaX = mousePoint.x - x;
int deltaY = mousePoint.y - y;
rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toDegrees(rotation) + 180;
}
g2d.rotate(Math.toRadians(rotation), width / 2, height / 2);
int x = width / 2;
int y = height / 2;
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.RED);
g2d.drawLine(x, y, x, y - height / 4);
y -= height / 4 + (head.getBounds().height);
x -= head.getBounds().width / 2;
g2d.fill(head.createTransformedShape(AffineTransform.getTranslateInstance(x, y)));
g2d.dispose();
g2d.dispose();
}
}
public class DiamondShape extends Path2D.Double {
public DiamondShape(int width, int height) {
moveTo(width / 2, 0);
lineTo(width, height / 2);
lineTo(width / 2, height);
lineTo(0, height / 2);
closePath();
}
}
}
I want to make a directed line and make it move. I am able to make a directed line and move the line but the arrow get displaced while i move the line
This is my paint method
Line2D.Double line = new Line2D.Double(startX, startY, endX, endY);
g2d.draw(line);
tx.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle - Math.PI / 2d));
Graphics2D gClone = (Graphics2D) g2d.create();
gClone.setTransform(tx);
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, 15);
arrowHead.addPoint(-15, -15);
arrowHead.addPoint(15, -15);
Area area = new Arear(arrowHead );
Area lineArea = new Area(line);
lineArea.subtract(area);
gClone.fill(area);
gClone.dispose();
and i have change the value of startx and start y at mouse pressedd
and endx and enny at mouse drag
This is a basic example that demonstrates the use of a Path2D, to define a custom shape and an AffineTransformation to transform the shape.
This example will cause the arrow to point towards the mouse as it moves over the panel
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RotateArrow {
public static void main(String[] args) {
new RotateArrow();
}
public RotateArrow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new RotatePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RotatePane extends javax.swing.JPanel {
private Point mousePoint;
private PointyThing pointyThing;
public RotatePane() {
pointyThing = new PointyThing();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
double rotation = 0f;
int width = getWidth() - 1;
int height = getHeight() - 1;
if (mousePoint != null) {
int x = width / 2;
int y = height / 2;
int deltaX = mousePoint.x - x;
int deltaY = mousePoint.y - y;
rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toDegrees(rotation) + 180;
}
Rectangle bounds = pointyThing.getBounds();
AffineTransform at = new AffineTransform();
at.translate((width - bounds.width) / 2, (height - bounds.height) / 2);
at.rotate(Math.toRadians(rotation), bounds.width / 2, bounds.height / 2);
Shape shape = new Path2D.Float(pointyThing, at);
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.RED);
g2d.fill(shape);
g2d.draw(shape);
g2d.dispose();
}
}
public class PointyThing extends Path2D.Float {
public PointyThing() {
moveTo(15, 0);
lineTo(30, 15);
lineTo(0, 15);
lineTo(15, 0);
moveTo(15, 15);
lineTo(15, 60);
}
}
}
Update with example two
Basically, this uses the PointyThing as the arrow head only, and draws the line separately.
The arrow is orientated so it will point down the line (towards the mouse)
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RotateArrow {
public static void main(String[] args) {
new RotateArrow();
}
public RotateArrow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new RotatePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RotatePane extends javax.swing.JPanel {
private PointyThing pointyThing;
private Point mouseStart;
private Point mouseEnd;
public RotatePane() {
pointyThing = new PointyThing();
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mouseEnd = e.getPoint();
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
mouseStart = e.getPoint();
repaint();
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (mouseStart != null && mouseEnd != null) {
double rotation = 0f;
int width = getWidth() - 1;
int height = getHeight() - 1;
if (mouseEnd != null) {
int x = mouseStart.x;
int y = mouseStart.y;
int deltaX = mouseEnd.x - x;
int deltaY = mouseEnd.y - y;
rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toDegrees(rotation) + 180;
}
Rectangle bounds = pointyThing.getBounds();
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.RED);
g2d.draw(new Line2D.Float(mouseStart, mouseEnd));
AffineTransform at = new AffineTransform();
at.translate(mouseEnd.x - (bounds.width / 2), mouseEnd.y - (bounds.height / 2));
at.rotate(Math.toRadians(rotation), bounds.width / 2, bounds.height / 2);
Shape shape = new Path2D.Float(pointyThing, at);
g2d.fill(shape);
g2d.draw(shape);
}
g2d.dispose();
}
}
public class PointyThing extends Path2D.Float {
public PointyThing() {
moveTo(15, 0);
lineTo(30, 15);
lineTo(0, 15);
lineTo(15, 0);
}
}
}