How to create parallelogram/diamond shape jpanels? - java

I am creating simple flowchart software.i want to create oval/parallelogram/diamond/rectangle shapes. I want to resize those shapes and drag those shapes.because of resizing I cant use images.I add drag events to rectangle shape japanel but I unable to create other shapes using jpanel. How to add parallelogram/diamond shapes to jpanel?
given "Resizing issue with canvas within jscrollpane within jsplitpane" question not answered for parallelogram shape and diamond shape i want to
create those shapes too
/* ResizableComponent.java */
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/* ResizableComponent.java */
public class ResizableComponent extends JFrame {
private JPanel panel = new JPanel(null);
private Resizable resizer;
public ResizableComponent() {
add(panel);
JPanel area = new JPanel();
area.setBackground(Color.white);
resizer = new Resizable(area);
resizer.setBounds(50, 30, 200, 150);
panel.add(resizer);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(new Dimension(350, 300));
setTitle("Resizable Component");
setLocationRelativeTo(null);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
requestFocus();
resizer.repaint();
}
});
}
public static void main(String[] args) {
ResizableComponent rc = new ResizableComponent();
rc.setVisible(true);
}
}
// Resizable.java
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
// Resizable.java
public class Resizable extends JComponent {
public Resizable(Component comp) {
this(comp, new ResizableBorder(8));
}
public Resizable(Component comp, ResizableBorder border) {
setLayout(new BorderLayout());
add(comp);
addMouseListener(resizeListener);
addMouseMotionListener(resizeListener);
setBorder(border);
}
private void resize() {
if (getParent() != null) {
((JComponent)getParent()).revalidate();
}
}
MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}
public void mouseExited(MouseEvent mouseEvent) {
setCursor(Cursor.getDefaultCursor());
}
private int cursor;
private Point startPos = null;
public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.getCursor(me);
startPos = me.getPoint();
requestFocus();
repaint();
}
public void mouseDragged(MouseEvent me) {
if (startPos != null) {
int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();
int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;
switch (cursor) {
case Cursor.N_RESIZE_CURSOR:
if (!(h - dy < 50)) {
setBounds(x, y + dy, w, h - dy);
resize();
}
break;
case Cursor.S_RESIZE_CURSOR:
if (!(h + dy < 50)) {
setBounds(x, y, w, h + dy);
startPos = me.getPoint();
resize();
}
break;
case Cursor.W_RESIZE_CURSOR:
if (!(w - dx < 50)) {
setBounds(x + dx, y, w - dx, h);
resize();
}
break;
case Cursor.E_RESIZE_CURSOR:
if (!(w + dx < 50)) {
setBounds(x, y, w + dx, h);
startPos = me.getPoint();
resize();
}
break;
case Cursor.NW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h - dy < 50)) {
setBounds(x + dx, y + dy, w - dx, h - dy);
resize();
}
break;
case Cursor.NE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h - dy < 50)) {
setBounds(x, y + dy, w + dx, h - dy);
startPos = new Point(me.getX(), startPos.y);
resize();
}
break;
case Cursor.SW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h + dy < 50)) {
setBounds(x + dx, y, w - dx, h + dy);
startPos = new Point(startPos.x, me.getY());
resize();
}
break;
case Cursor.SE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h + dy < 50)) {
setBounds(x, y, w + dx, h + dy);
startPos = me.getPoint();
resize();
}
break;
case Cursor.MOVE_CURSOR:
Rectangle bounds = getBounds();
bounds.translate(dx, dy);
setBounds(bounds);
resize();
}
setCursor(Cursor.getPredefinedCursor(cursor));
}
}
public void mouseReleased(MouseEvent mouseEvent) {
startPos = null;
}
};
}
// ResizableBorder.java
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
// ResizableBorder.java
public class ResizableBorder implements Border {
private int dist = 8;
int locations[] =
{
SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
SwingConstants.EAST, SwingConstants.NORTH_WEST,
SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
SwingConstants.SOUTH_EAST
};
int cursors[] =
{
Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR,
Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
};
public ResizableBorder(int dist) {
this.dist = dist;
}
public Insets getBorderInsets(Component component) {
return new Insets(dist, dist, dist, dist);
}
public boolean isBorderOpaque() {
return false;
}
public void paintBorder(Component component, Graphics g, int x, int y,
int w, int h) {
g.setColor(Color.black);
g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
if (component.hasFocus()) {
for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
g.setColor(Color.WHITE);
g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
g.setColor(Color.BLACK);
g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
}
}
}
private Rectangle getRectangle(int x, int y, int w, int h, int location) {
switch (location) {
case SwingConstants.NORTH:
return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
case SwingConstants.SOUTH:
return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist,
dist);
case SwingConstants.WEST:
return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);
case SwingConstants.EAST:
return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist,
dist);
case SwingConstants.NORTH_WEST:
return new Rectangle(x, y, dist, dist);
case SwingConstants.NORTH_EAST:
return new Rectangle(x + w - dist, y, dist, dist);
case SwingConstants.SOUTH_WEST:
return new Rectangle(x, y + h - dist, dist, dist);
case SwingConstants.SOUTH_EAST:
return new Rectangle(x + w - dist, y + h - dist, dist, dist);
}
return null;
}
public int getCursor(MouseEvent me) {
Component c = me.getComponent();
int w = c.getWidth();
int h = c.getHeight();
for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(0, 0, w, h, locations[i]);
if (rect.contains(me.getPoint()))
return cursors[i];
}
return Cursor.MOVE_CURSOR;
}
}

Related

Moving Objects along an arc path with java graphics

I had 3 days looking for moving on objects on a spiral way but I figured that I have to look for the small part of problem which is moving objects on arc.
See my Question :https://stackoverflow.com/questions/36917560/moving-rectangle-spiral-animation-java?noredirect=1#comment61428911_36917560
Now, the problem is how i can calculate the points that are exists on the arc. this is my Approach to get the new X and Y Points( Algorithm not code )
1- Draw arc using this method in JAVA
g2d.fillArc(start_point_X_Arc,start_point_Y_Arc,width_of_arc,height_of_arc,start_angle,end_angle);
2- Draw the Object on the Same Start_point_X,Start_point_Y. And here I will draw a rectangle using this method
g2d.drawRect(start_point_X_Rect, Start_point_Y_Rect, 10, 10);
3- Because I'm using a timer and it needs an ActionListener the actionPerformed method will update the Values of Start_point_X, Start_point_Y for the rectangle
AND HERE IS THE PROBLEM I can't calculate the values of the New X,Y values for the object which will do the moving part of the problem ( I know that these word are not professional words ).
Because of that I search how to calculate points on arc and I find the Parametric Equations for a circle
x = center_X + radius * cos(angle)
y = center_y + radius * sin(angle)
and I know that these equation might be used in someway to get the new points but i'm not good in math.
Therefore,I need help with doing the object moving in arc path and i think this would help me to do an object moving in spiral path. If my algorithm is wrong or anything is wrong please give me advice to do it in a simple way.
This is a code that I made it to draw an arc & rectangle and the rectangle is moving in diagonal path.
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.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public class SpiralPath extends Path2D.Double {
public SpiralPath(int size) {
int numIterations = 5;
int arcGrowDelta = (size / numIterations) / 2;
int arcWidth = 0;
int centerX = size / 2;
int centerY = size / 2;
moveTo(centerX, centerY);
for (int i = 0; i < numIterations; i++) {
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
arcWidth += arcGrowDelta;
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
}
}
}
public class SpiralPath2 extends Path2D.Double {
public SpiralPath2(int size) {
int numIterations = 5;
int arcGrowDelta = (size / numIterations) / 2;
int arcWidth = 0;
int centerX = size / 2+200;
int centerY = size / 2;
moveTo(centerX, centerY);
for (int i = 0; i < numIterations; i++) {
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
arcWidth += arcGrowDelta;
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
}
}
}
public Test() {
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 SpiralPath spiralPath;
private final Rectangle box;
private List<Point2D> points;
private double angle;
private Point2D pos;
private int index;
private SpiralPath2 spiralPath2;
private final Rectangle box2;
private List<Point2D> points2;
private double angle2;
private Point2D pos2;
private int index2;
protected static final double PLAY_TIME = 5000; // 5 seconds...
private Long startTime;
public TestPane() {
spiralPath = new SpiralPath(150);
box = new Rectangle(0, 0, 10, 10);
points = new ArrayList<>(25);
PathIterator pi = spiralPath.getPathIterator(null, 0.01);
while (!pi.isDone()) {
double[] coords = new double[6];
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
points.add(new Point2D.Double(coords[0], coords[1]));
break;
}
pi.next();
}
spiralPath2 = new SpiralPath2(200);
box2 = new Rectangle(0, 0, 10, 10);
points2 = new ArrayList<>(25);
PathIterator pi2 = spiralPath2.getPathIterator(null, 0.01);
while (!pi2.isDone()) {
double[] coords = new double[6];
switch (pi2.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
points2.add(new Point2D.Double(coords[0], coords[1]));
break;
}
pi2.next();
}
pos = points.get(0);
pos2 = points2.get(0);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long playTime = System.currentTimeMillis() - startTime;
double progress = playTime / PLAY_TIME;
if (progress >= 1.0) {
progress = 1d;
((Timer) e.getSource()).stop();
}
int index = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);
int index2 = Math.min(Math.max(0, (int) (points2.size() * progress)), points2.size() - 1);
pos = points.get(index);
pos2 = points2.get(index2);
if (index < points.size() - 1) {
angle = angleTo(pos, points.get(index + 1));
}
if (index2 < points2.size() - 1) {
angle2 = angleTo(pos2, points2.get(index + 1));
}
repaint();
}
});
timer.start();
}
protected double angleTo(Point2D from, Point2D to) {
double angle = Math.atan2(to.getY() - from.getY(), to.getX() - from.getX());
return angle;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
g2d.translate(20, 50);
g2d.draw(spiralPath);
g2d.draw(spiralPath2);
AffineTransform at = new AffineTransform();
AffineTransform at2 = new AffineTransform();
if (pos != null &&pos2!=null) {
Rectangle bounds = box.getBounds();
at.rotate(angle, (bounds.width / 2), (bounds.width / 2));
Path2D player = new Path2D.Double(box, at);
g2d.translate(pos.getX() - (bounds.width / 2), pos.getY() - (bounds.height / 2));
g2d.setColor(Color.RED);
g2d.draw(player);
}
Rectangle bounds2 = box2.getBounds();
at2.rotate(angle2, (bounds2.width / 2), (bounds2.width / 2));
Path2D player2 = new Path2D.Double(box2, at2);
g2d.translate(pos2.getX() - (bounds2.width / 2)+50, pos2.getY() - (bounds2.height / 2));
g2d.setColor(Color.RED);
g2d.draw(player2);
g2d.dispose();
}
}
public static 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);
}
}
So based on this idea, you can take advantage of the functionality already available in the 2D Graphics API.
The difficult part would be to get your spiral shape setup as a Path object, lucky for us, the API is very flexible ...
public class SpiralPath extends Path2D.Double {
public SpiralPath(int size) {
int numIterations = 5;
int arcGrowDelta = (size / numIterations) / 2;
int arcWidth = 0;
int centerX = size / 2;
int centerY = size / 2;
moveTo(centerX, centerY);
for (int i = 0; i < numIterations; i++) {
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
arcWidth += arcGrowDelta;
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
}
}
}
Now we have that, the rest is (relatively) simple, as it follows a well know pattern...
package javaapplication1.pkg005;
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.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public class SpiralPath extends Path2D.Double {
public SpiralPath(int size) {
int numIterations = 5;
int arcGrowDelta = (size / numIterations) / 2;
int arcWidth = 0;
int centerX = size / 2;
int centerY = size / 2;
moveTo(centerX, centerY);
for (int i = 0; i < numIterations; i++) {
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
arcWidth += arcGrowDelta;
append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
}
}
}
public Test() {
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 SpiralPath spiralPath;
private final Rectangle box;
private List<Point2D> points;
private double angle;
private Point2D pos;
private int index;
protected static final double PLAY_TIME = 5000; // 5 seconds...
private Long startTime;
public TestPane() {
spiralPath = new SpiralPath(150);
box = new Rectangle(0, 0, 10, 10);
points = new ArrayList<>(25);
PathIterator pi = spiralPath.getPathIterator(null, 0.01);
while (!pi.isDone()) {
double[] coords = new double[6];
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
points.add(new Point2D.Double(coords[0], coords[1]));
break;
}
pi.next();
}
pos = points.get(0);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long playTime = System.currentTimeMillis() - startTime;
double progress = playTime / PLAY_TIME;
if (progress >= 1.0) {
progress = 1d;
((Timer) e.getSource()).stop();
}
int index = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);
pos = points.get(index);
if (index < points.size() - 1) {
angle = angleTo(pos, points.get(index + 1));
}
repaint();
}
});
timer.start();
}
protected double angleTo(Point2D from, Point2D to) {
double angle = Math.atan2(to.getY() - from.getY(), to.getX() - from.getX());
return angle;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
int x = (getWidth() - spiralPath.getBounds().width) / 2;
int y = (getHeight() - spiralPath.getBounds().height) / 2;
g2d.translate(x, y);
g2d.draw(spiralPath);
AffineTransform at = new AffineTransform();
if (pos != null) {
Rectangle bounds = box.getBounds();
at.rotate(angle, (bounds.width / 2), (bounds.width / 2));
Path2D player = new Path2D.Double(box, at);
g2d.translate(pos.getX() - (bounds.width / 2), pos.getY() - (bounds.height / 2));
g2d.setColor(Color.RED);
g2d.draw(player);
}
g2d.dispose();
}
}
public static 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);
}
}
Try a little program (with no timers no nothing just this plane):
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
g2d.drawArc(200,200,200,200,0,90);
g2d.setColor(Color.magenta);
for(double t=0; t<Math.PI/2; t+=Math.PI/100) {
int x = 300 + (int)(100 * Math.cos(t));
int y = 300 + (int)(100 * Math.sin(t));
g.fillOval(x, y , 5 , 5);
}
}
If your arc is 200 wide and high and the arc goes from 0 to 90 (from the right x axis) this should draw the points on the arc.
I think you can generalize this to whatever center you have what width/height etc.
You can also change the angle
int y = 300 + (int)(100 * Math.sin(-t));
if you want to draw backwards.

Rotating a polygon in Java

The program I am writing draws multiple stars on the screen and gives them random directions and speeds. The stars will bounce off the edges of the panel and stay inside. I need to have the stars rotating as they are moving. I have tried so many things and I cannot figure it out. Below is the code I use to draw the stars and move them.
Additional Information:
-the stars are in a collection called "stars"
-there are two classes that I wrote, "Star" and "MyJPanel"
Set the points: (in Star class)
for (double current = 0; current < nPoints; current += 1)
{
i = (int)current;
int[] X = new int[nPoints * 2];
int[] Y = new int[nPoints * 2];
cosX = -(Math.cos(current*((2*Math.PI)/(nPoints)))*radius[i % 2]);
sinY = -(Math.sin(current*((2*Math.PI)/(nPoints)))*radius[i % 2]);
X[i] = (int) cosX+x;
Y[i] = (int) sinY+y;
addPoint(X[i], Y[i]);
}
Move method: (in Star class)
public void move(int width, int height)
{
if (location.x <= radius[0] || location.x >= width - radius[0])
{
xIncr = -xIncr;
}
if (location.y <= radius[0] || location.y >= height - radius[0])
{
yIncr = -yIncr;
}
translate(xIncr, yIncr);
location.setLocation(location.x + xIncr, location.y + yIncr);
xInc = xIncr;
}
Paint the stars: (in MyJPanel class)
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for(int i = 0; i < stars.size(); i++)
{
g2d = (Graphics2D) g;
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, stars.get(i).getAlpha()));
g2d.setColor(stars.get(i).getColor());
g2d.fillPolygon(stars.get(i));
if(stars.get(i).alpha == 0)
{
stars.remove(stars.indexOf(stars.get(i)));
}
}
}
If any more code or information is needed please let me know, thank you!
I would have a look at Working with Geometry and Transforming Shapes, Text, and Images
Basically, I've created a custom Path2D shape which represents my start. This class carries some additional information with, included it's x/y offset and rotation. It then provides a helper method to create a transformed instance of this shape based on these properties, for example...
public Shape getTransformedInstance() {
AffineTransform at = new AffineTransform();
Rectangle bounds = getBounds();
at.rotate(Math.toRadians(angle), x + (bounds.width / 2), y + (bounds.height / 2));
at.translate(x, y);
return createTransformedShape(at);
}
Basically, this is where the magic happens.
There is a simple Swing Timer which applies a delta to the position and rotation.
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.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RotateShape {
public static void main(String[] args) {
new RotateShape();
}
public RotateShape() {
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 Star star;
private double rotationDelta = 5d;
private int xDelta = 0;
private int yDelta = 0;
public TestPane() {
star = new Star(50, 50);
star.moveLocatioBy(75, 75);
Random rnd = new Random();
xDelta = rnd.nextInt(4) + 1;
yDelta = rnd.nextInt(4) + 1;
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
star.moveLocatioBy(xDelta, yDelta);
Rectangle bounds = star.getBounds();
int x = star.getX();
int y = star.getY();
boolean bounced = false;
if (x < 0) {
x = 0;
xDelta *= -1;
bounced = true;
} else if (x + bounds.width > getWidth()) {
x = getWidth() - bounds.width;
xDelta *= -1;
bounced = true;
}
if (y < 0) {
y = 0;
yDelta *= -1;
bounced = true;
} else if (y + bounds.height > getHeight()) {
y = getHeight() - bounds.height;
yDelta *= -1;
bounced = true;
}
if (bounced) {
rotationDelta *= -1;
}
star.rotateByDegrees(rotationDelta);
star.setLocation(x, y);
repaint();
}
});
timer.start();
}
#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);
Shape shape = star.getTransformedInstance();
g2d.setColor(Color.BLUE);
g2d.fill(shape);
g2d.setColor(Color.RED);
g2d.draw(shape);
g2d.dispose();
}
}
public class Star extends Path2D.Double {
private double angle = 0;
private int x = 0, y = 0;
public Star(int width, int height) {
double heightPart = height / 3d;
double widthPart = width / 3d;
moveTo(width / 2, 0);
lineTo(widthPart * 2, heightPart);
lineTo(width, heightPart);
lineTo(widthPart * 2, height / 2);
lineTo(width, height);
lineTo(width / 2, heightPart * 2);
lineTo(0, height);
lineTo(widthPart, height / 2);
lineTo(0, heightPart);
lineTo(widthPart, heightPart);
closePath();
}
public double getAngle() {
return angle;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void moveLocatioBy(int xDelta, int yDelta) {
this.x += xDelta;
this.y += yDelta;
}
public void rotateByDegrees(double delta) {
angle += delta;
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
public Shape getTransformedInstance() {
AffineTransform at = new AffineTransform();
Rectangle bounds = getBounds();
at.rotate(Math.toRadians(angle), x + (bounds.width / 2), y + (bounds.height / 2));
at.translate(x, y);
return createTransformedShape(at);
}
}
}
It wouldn't take a lot to expand this idea so that each start had it's own delta values and simple update method (which passed in the width/height of the Container), so you could make lots-o-stars

How to draw an equilateral triangle when two points are already given?

I have to draw an EQ triangle, and the coordinates of two points are already given.
I'm using the drawPolygon method, and so I have to find the third point out.
int xCoord[] = {x1, x2, ?};
int yCoord[] = {y1, y2, ?};
g.drawPolygon(xCoord, yCoord, 3);
Given the two initial points, and the fact that the height of an equilateral triangle with edge length a is a*sqrt(3)/2, you can compute
the edge between the given points
the (normalized) direction of the perpendicular to this edge
the center of this edge
Then the tip points of the equilateral triangle are given as
center +/- height * perpendicularDirection
This is implemented here in the computeTipPoint method:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
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 EquilateralTriangleTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EquilateralTrianglePanel panel = new EquilateralTrianglePanel();
f.getContentPane().add(panel);
f.setSize(1000,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class EquilateralTrianglePanel extends JPanel implements MouseMotionListener
{
private final Point2D point0;
private final Point2D point1;
EquilateralTrianglePanel()
{
this.point0 = new Point2D.Double(600,500);
this.point1 = new Point2D.Double(400,600);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.BLACK);
g.draw(new Line2D.Double(point0, point1));
g.setColor(Color.RED);
draw(g, point0);
draw(g, point1);
int x0 = (int)point0.getX();
int y0 = (int)point0.getY();
int x1 = (int)point1.getX();
int y1 = (int)point1.getY();
Point2D point2a = computeTipPoint(point0, point1, true);
int x2a = (int)point2a.getX();
int y2a = (int)point2a.getY();
g.setColor(Color.BLUE);
int xCoorda[] = {x0, x1, x2a};
int yCoorda[] = {y0, y1, y2a};
g.drawPolygon(xCoorda, yCoorda, 3);
Point2D point2b = computeTipPoint(point0, point1, false);
int x2b = (int)point2b.getX();
int y2b = (int)point2b.getY();
g.setColor(Color.MAGENTA);
int xCoordb[] = {x0, x1, x2b};
int yCoordb[] = {y0, y1, y2b};
g.drawPolygon(xCoordb, yCoordb, 3);
}
private static void draw(Graphics2D g, Point2D p)
{
Ellipse2D e = new Ellipse2D.Double(
p.getX()-3, p.getY()-3, 6, 6);
g.fill(e);
}
#Override
public void mouseDragged(MouseEvent e)
{
point0.setLocation(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
point1.setLocation(e.getPoint());
repaint();
}
private static Point2D computeTipPoint(
Point2D p0, Point2D p1, boolean right)
{
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double length = Math.sqrt(dx*dx+dy*dy);
double dirX = dx / length;
double dirY = dy / length;
double height = Math.sqrt(3)/2 * length;
double cx = p0.getX() + dx * 0.5;
double cy = p0.getY() + dy * 0.5;
double pDirX = -dirY;
double pDirY = dirX;
double rx = 0;
double ry = 0;
if (right)
{
rx = cx + height * pDirX;
ry = cy + height * pDirY;
}
else
{
rx = cx - height * pDirX;
ry = cy - height * pDirY;
}
return new Point2D.Double(rx, ry);
}
}
You have to solve following system of equations to find coordinates of the third vertex:
(x-x1)^2 + (y-y1)^2 = R^2
(x-x2)^2 + (y-y2)^2 = R^2
where
R = dist(x1, y1, x2, y2)

How to obtain new height and width of resized image?

i am trying to save new resized image but i exactly don't have any idea to get the new height & width or axis of image.
if anyone knows it then i will be so thankful.
my code to resize image or to scale image is as follows :
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
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.Transparency;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
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;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ImageViewer {
public static void main(String[] args) {
new ImageViewer();
}
public ImageViewer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new ViewPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ViewPane extends JPanel {
public ViewPane() {
setLayout(null);
ImagePane imagePane = new ImagePane();
imagePane.setSize(imagePane.getPreferredSize());
imagePane.setLocation(0, 0);
add(imagePane);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class ImagePane extends JPanel {
private BufferedImage bg;
private BufferedImage scaled;
public ImagePane() {
try {
bg = ImageIO.read(new File("src.jpg"));
scaled = getScaledInstanceToFit(bg, new Dimension(100, 100));
} catch (IOException ex) {
ex.printStackTrace();
}
setBackground(Color.BLACK);
MouseHandler handler = new MouseHandler();
addMouseListener(handler);
addMouseMotionListener(handler);
}
#Override
public Dimension getPreferredSize() {
return bg == null ? new Dimension(200, 200) : new Dimension(scaled.getWidth(), scaled.getHeight());
}
#Override
public void invalidate() {
super.invalidate();
scaled = getScaledInstanceToFit(bg, getSize());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - scaled.getWidth()) / 2;
int y = (getHeight() - scaled.getHeight()) / 2;
g2d.drawImage(scaled, x, y, this);
g2d.dispose();
}
public enum MouseAction {
Move(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)),
ResizeSouth(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)),
ResizeNorth(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)),
ResizeEast(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)),
ResizeWest(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)),
ResizeNorthEast(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)),
ResizeNorthWest(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)),
ResizeSouthEast(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)),
ResizeSouthWest(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
private Cursor cursor;
private MouseAction(Cursor cursor) {
this.cursor = cursor;
}
public Cursor getCursor() {
return cursor;
}
}
public class MouseHandler extends MouseAdapter {
private MouseAction action;
private Point clickPoint;
private Point offset;
private boolean ignoreMoves;
protected void updateAction(MouseEvent e) {
int x = e.getX();
int y = e.getY();
int width = getWidth();
int height = getHeight();
if (x < 10 && y < 10) {
action = MouseAction.ResizeNorthWest;
} else if (x > width - 10 && y < 10) {
action = MouseAction.ResizeNorthWest;
} else if (y < 10) {
action = MouseAction.ResizeNorth;
} else if (x < 10 && y > height - 10) {
action = MouseAction.ResizeSouthWest;
} else if (x > width - 10 && y > height - 10) {
action = MouseAction.ResizeSouthEast;
} else if (y > height - 10) {
action = MouseAction.ResizeSouth;
} else if (x < 10) {
action = MouseAction.ResizeWest;
} else if (x > width - 10) {
action = MouseAction.ResizeEast;
} else {
action = MouseAction.Move;
}
setCursor(action.getCursor());
}
#Override
public void mouseMoved(MouseEvent e) {
if (!ignoreMoves) {
updateAction(e);
}
}
#Override
public void mousePressed(MouseEvent e) {
updateAction(e);
ignoreMoves = true;
clickPoint = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
clickPoint = null;
ignoreMoves = false;
}
#Override
public void mouseDragged(MouseEvent e) {
switch (action) {
case Move: {
Point p = e.getPoint();
p.x -= clickPoint.x;
p.y -= clickPoint.y;
p = SwingUtilities.convertPoint(ImagePane.this, p, getParent());
setLocation(p);
}
break;
case ResizeWest: {
Point p = e.getPoint();
int xDelta = p.x - clickPoint.x;
int width = getWidth() - xDelta;
int x = getX() + xDelta;
setSize(width, getHeight());
setLocation(x, getY());
revalidate();
}
break;
case ResizeEast: {
Point p = e.getPoint();
int xDelta = p.x - clickPoint.x;
int width = getWidth() + xDelta;
setSize(width, getHeight());
revalidate();
clickPoint = p;
}
break;
case ResizeNorth: {
Point p = e.getPoint();
int yDelta = p.y - clickPoint.y;
int height = getHeight() - yDelta;
int y = getY() + yDelta;
setSize(getWidth(), height);
setLocation(getX(), y);
revalidate();
}
break;
case ResizeSouth: {
Point p = e.getPoint();
int yDelta = p.y - clickPoint.y;
int height = getHeight() + yDelta;
setSize(getWidth(), height);
revalidate();
clickPoint = p;
}
break;
}
}
#Override
public void mouseExited(MouseEvent e) {
}
}
}
public static BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
double scaleFactor = getScaleFactorToFit(img, size);
return getScaledInstance(img, scaleFactor);
}
public static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
BufferedImage imgBuffer = null;
imgBuffer = getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
return imgBuffer;
}
protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint) {
BufferedImage imgScale = img;
int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);
if (dScaleFactor <= 1.0d) {
imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint);
} else {
imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint);
}
return imgScale;
}
protected static BufferedImage getScaledDownInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint) {
// System.out.println("Scale down...");
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
do {
if (w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
protected static BufferedImage getScaledUpInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
w = img.getWidth();
h = img.getHeight();
do {
if (w < targetWidth) {
w *= 2;
if (w > targetWidth) {
w = targetWidth;
}
}
if (h < targetHeight) {
h *= 2;
if (h > targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp = null;
} while (w != targetWidth || h != targetHeight);
return ret;
}
public static double getScaleFactorToFit(BufferedImage img, Dimension size) {
double dScale = 1;
if (img != null) {
int imageWidth = img.getWidth();
int imageHeight = img.getHeight();
dScale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
}
return dScale;
}
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
}
i can also move my image easily and also make alteration in size, but now i want to save new image but i have to get first of all its new height and width , if i will get it then i will easily save it.
anyone can assist me ???
Depending on what it's you want to achieve, I would be tempted to add a method to the ImagePane called (something like) save...
public void save(String path, String name) throws IOException {
if (scaled != null) {
name += scaled.getWidth() + "x" + scaled.getHeight();
ImageIO.write(scaled, "png", new File(path + File.separator + name + ".png"));
} else {
throw new NullPointerException("Scaled instance is null");
}
}
Take a look at Writing/Saving an Image
You could also simply pass back a scaled instance based on the ImagePane's state...
public BufferedImage getScaledInstance() {
return getScaledInstanceToFit(bg, getSize());
}
I wouldn't return the scaled object because you don't want people messing with it if you can help it...

Swing animation running extremely slow

I have a problem with my current animation that I'm running using Java Swing. It is a discrete event simulation and the text based simulation is working fine, I'm just having problems connecting the simulating to GUI output.
For this example I will have 10 cars to be simulated. The cars are represented by JPanels which I will elaborate on in a few moments.
So consider, the event process_car_arrival. Every time this event is scheduled for execution, I'm adding a Car object to an ArrayList called cars in my Model class. The Car class has the following relevant attributes:
Point currentPos; // The current position, initialized in another method when knowing route.
double speed; // giving the speed any value still causes the same problem but I have 5 atm.
RouteType route; // for this example I only consider one simple route
In addition it has the following method move() :
switch (this.route) {
case EAST:
this.currentPos.x -= speed;
return this.currentPos;
.
.
.
//only above is relevant in this example
This is all well. so in theory the car traverses along a straight road from east to west as I just invoke the move() method for each car I want to move.
Returning to the process_car_arrival event. After adding a Car object it invokes a method addCarToEast() in the View class. This adds a JPanel at the start of the road going from east to west.
Going to the View class now I have a ** separate** thread which does the following ( the run() method) :
#Override
public void run() {
while (true) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!cars.isEmpty()) {
cars.get(i).setLocation(
new Point(getModel.getCars().get(i).move()));
if (i == cars.size() - 1) {
i = 0;
} else {
i++;
}
}
}
}
The above does move the car from east to west smoothly at first. But after there is 3-4 cars moving it just ends up being EXTREMELY slow and when I have 10 cars moving it just ends up moving very little.
Just to clear up, at the moment in the Model class there's an ArrayList of Car objects, and in the View class there is also an ArrayList of JPanel objects representing the cars. I'm trying to match the Car objects to the JPanels, but I'm obviously doing a cra**y job.
I suspect that I'm doing something insanely inefficient but I don't know what. I thought initially maybe it's accessing the ArrayList so much which I guess would make it really slow.
Any pointers to what I can change to make it run smoothly?
Based on this previous answer, the example below simulates a fleet of three cabs moving randomly on a rectangular grid. A javax.swing.Timer drives the animation at 5 Hz. The model and view are tightly coupled in CabPanel, but the animation may provide some useful insights. In particular, you might increase the number of cabs or lower the timer delay.
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.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/**
* #see https://stackoverflow.com/a/14887457/230513
* #see https://stackoverflow.com/questions/5617027
*/
public class FleetPanel extends JPanel {
private static final Random random = new Random();
private final MapPanel map = new MapPanel();
private final JPanel control = new JPanel();
private final List<CabPanel> fleet = new ArrayList<CabPanel>();
private final Timer timer = new Timer(200, null);
public FleetPanel() {
super(new BorderLayout());
fleet.add(new CabPanel("Cab #1", Hue.Cyan));
fleet.add(new CabPanel("Cab #2", Hue.Magenta));
fleet.add(new CabPanel("Cab #3", Hue.Yellow));
control.setLayout(new GridLayout(0, 1));
for (CabPanel cp : fleet) {
control.add(cp);
timer.addActionListener(cp.listener);
}
this.add(map, BorderLayout.CENTER);
this.add(control, BorderLayout.SOUTH);
}
public void start() {
timer.start();
}
private class CabPanel extends JPanel {
private static final String format = "000000";
private final DecimalFormat df = new DecimalFormat(format);
private JLabel name = new JLabel("", JLabel.CENTER);
private Point point = new Point();
private JLabel position = new JLabel(toString(point), JLabel.CENTER);
private int blocks;
private JLabel odometer = new JLabel(df.format(0), JLabel.CENTER);
private final JComboBox colorBox = new JComboBox();
private final JButton reset = new JButton("Reset");
private final ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int ds = random.nextInt(3) - 1;
if (random.nextBoolean()) {
point.x += ds;
} else {
point.y += ds;
}
blocks += Math.abs(ds);
update();
}
};
public CabPanel(String s, Hue hue) {
super(new GridLayout(1, 0));
name.setText(s);
this.setBackground(hue.getColor());
this.add(map, BorderLayout.CENTER);
for (Hue h : Hue.values()) {
colorBox.addItem(h);
}
colorBox.setSelectedIndex(hue.ordinal());
colorBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Hue h = (Hue) colorBox.getSelectedItem();
CabPanel.this.setBackground(h.getColor());
update();
}
});
reset.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
point.setLocation(0, 0);
blocks = 0;
update();
}
});
this.add(name);
this.add(odometer);
this.add(position);
this.add(colorBox);
this.add(reset);
}
private void update() {
position.setText(CabPanel.this.toString(point));
odometer.setText(df.format(blocks));
map.repaint();
}
private String toString(Point p) {
StringBuilder sb = new StringBuilder();
sb.append(Math.abs(p.x));
sb.append(p.x < 0 ? " W" : " E");
sb.append(", ");
sb.append(Math.abs(p.y));
sb.append(p.y < 0 ? " N" : " S");
return sb.toString();
}
}
private class MapPanel extends JPanel {
private static final int SIZE = 16;
public MapPanel() {
this.setPreferredSize(new Dimension(32 * SIZE, 32 * SIZE));
this.setBackground(Color.lightGray);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
g2d.setColor(Color.gray);
for (int col = SIZE; col <= w; col += SIZE) {
g2d.drawLine(col, 0, col, h);
}
for (int row = SIZE; row <= h; row += SIZE) {
g2d.drawLine(0, row, w, row);
}
for (CabPanel cp : fleet) {
Point p = cp.point;
int x = SIZE * (p.x + w / 2 / SIZE) - SIZE / 2;
int y = SIZE * (p.y + h / 2 / SIZE) - SIZE / 2;
g2d.setColor(cp.getBackground());
g2d.fillOval(x, y, SIZE, SIZE);
}
}
}
public enum Hue {
Cyan(Color.cyan), Magenta(Color.magenta), Yellow(Color.yellow),
Red(Color.red), Green(Color.green), Blue(Color.blue),
Orange(Color.orange), Pink(Color.pink);
private final Color color;
private Hue(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
private static void display() {
JFrame f = new JFrame("Dispatch");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FleetPanel fp = new FleetPanel();
f.add(fp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
fp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
}
I couldn't resist...
I got 500 cars running on the screen with little slow down (it wasn't the fastest...about 200-300 was pretty good...
This uses panels to represent each vehicle. If you want to get better performance, your probably need to look at using a backing buffer of some kind.
public class TestAnimation10 {
public static void main(String[] args) {
new TestAnimation10();
}
public TestAnimation10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
final TrackPane trackPane = new TrackPane();
JSlider slider = new JSlider(1, 500);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
trackPane.setCongestion(((JSlider)e.getSource()).getValue());
}
});
slider.setValue(5);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(trackPane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TrackPane extends JPanel {
private List<Car> cars;
private int maxCars = 1;
private List<Point2D[]> points;
private Ellipse2D areaOfEffect;
public TrackPane() {
points = new ArrayList<>(25);
cars = new ArrayList<>(25);
setLayout(null);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = areaOfEffect.getBounds();
List<Car> tmp = new ArrayList<>(cars);
for (Car car : tmp) {
car.move();
if (!bounds.intersects(car.getBounds())) {
remove(car);
cars.remove(car);
}
}
updatePool();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
updateAreaOfEffect();
}
protected void updateAreaOfEffect() {
double radius = Math.max(getWidth(), getHeight()) * 1.5d;
double x = (getWidth() - radius) / 2d;
double y = (getHeight() - radius) / 2d;
areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
}
#Override
public void invalidate() {
super.invalidate();
updateAreaOfEffect();
}
protected void updatePool() {
while (cars.size() < maxCars) {
// if (cars.size() < maxCars) {
Car car = new Car();
double direction = car.getDirection();
double startAngle = direction - 180;
double radius = areaOfEffect.getWidth();
Point2D startPoint = getPointAt(radius, startAngle);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
double x = cx + (startPoint.getX() - car.getWidth() / 2);
double y = cy + (startPoint.getY() - car.getHeight() / 2);
car.setLocation((int)x, (int)y);
Point2D targetPoint = getPointAt(radius, direction);
points.add(new Point2D[]{startPoint, targetPoint});
add(car);
cars.add(car);
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Font font = g.getFont();
font = font.deriveFont(Font.BOLD, 48f);
FontMetrics fm = g.getFontMetrics(font);
g.setFont(font);
g.setColor(Color.RED);
String text = Integer.toString(maxCars);
int x = getWidth() - fm.stringWidth(text);
int y = getHeight() - fm.getHeight() + fm.getAscent();
g.drawString(text, x, y);
text = Integer.toString(getComponentCount());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(cars.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void setCongestion(int value) {
maxCars = value;
}
}
protected static Point2D getPointAt(double radius, double angle) {
double x = Math.round(radius / 2d);
double y = Math.round(radius / 2d);
double rads = Math.toRadians(-angle);
double fullLength = Math.round((radius / 2d));
double xPosy = (Math.cos(rads) * fullLength);
double yPosy = (Math.sin(rads) * fullLength);
return new Point2D.Double(xPosy, yPosy);
}
public class Car extends JPanel {
private double direction;
private double speed;
private BufferedImage background;
public Car() {
setOpaque(false);
direction = Math.random() * 360;
speed = 5 + (Math.random() * 10);
int image = 1 + (int) Math.round(Math.random() * 5);
try {
String name = "/Car0" + image + ".png";
background = ImageIO.read(getClass().getResource(name));
} catch (IOException ex) {
ex.printStackTrace();
}
setSize(getPreferredSize());
// setBorder(new LineBorder(Color.RED));
}
public void setDirection(double direction) {
this.direction = direction;
revalidate();
repaint();
}
public double getDirection() {
return direction;
}
public void move() {
Point at = getLocation();
at.x += (int)(speed * Math.cos(Math.toRadians(-direction)));
at.y += (int)(speed * Math.sin(Math.toRadians(-direction)));
setLocation(at);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (background != null) {
double radian = Math.toRadians(direction);
double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
int w = background.getWidth(), h = background.getHeight();
int neww = (int) Math.floor(w * cos + h * sin);
int newh = (int) Math.floor(h * cos + w * sin);
size = new Dimension(neww, newh);
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
g2d.drawImage(background, x, y, this);
g2d.dispose();
// Debug graphics...
// int cx = getWidth() / 2;
// int cy = getHeight() / 2;
//
// g2d = (Graphics2D) g.create();
// g2d.setColor(Color.BLUE);
// double radius = Math.min(getWidth(), getHeight());
// Point2D pointAt = getPointAt(radius, direction);
// g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//
// double xo = cx;
// double yo = cy;
// double xPos = cx + pointAt.getX();
// double yPos = cy + pointAt.getY();
//
// g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
// g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
// g2d.dispose();
}
}
}
Updated with optimized version
I did a little bit of code optimisation with the creation of the car objects (there's still room for improvement) and ehanched the graphics ouput (made it look nicer).
Basically, now, when a car leaves the screen, it's placed in a pool. When another car is required, if possible, it's pulled from the pool, otherwise a new car is made. This has reduced the overhead of creating and destorying so many (relativly) short lived objects, which makes the memory usage a little more stable.
On my 2560x1600 resolution screen (running maximised), I was able to get 4500 cars running simultaneously. Once the object creation was reduced, it ran relatively smoothly (it's never going to run as well as 10, but it didn't suffer from a significant reduction in speed).
public class TestAnimation10 {
public static void main(String[] args) {
new TestAnimation10();
}
public TestAnimation10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
final TrackPane trackPane = new TrackPane();
JSlider slider = new JSlider(1, 5000);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
trackPane.setCongestion(((JSlider) e.getSource()).getValue());
}
});
slider.setValue(5);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(trackPane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TrackPane extends JPanel {
private List<Car> activeCarList;
private List<Car> carPool;
private int maxCars = 1;
private List<Point2D[]> points;
private Ellipse2D areaOfEffect;
public TrackPane() {
points = new ArrayList<>(25);
activeCarList = new ArrayList<>(25);
carPool = new ArrayList<>(25);
setLayout(null);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = areaOfEffect.getBounds();
List<Car> tmp = new ArrayList<>(activeCarList);
for (Car car : tmp) {
car.move();
if (!bounds.intersects(car.getBounds())) {
remove(car);
activeCarList.remove(car);
carPool.add(car);
}
}
updatePool();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
updateAreaOfEffect();
}
protected void updateAreaOfEffect() {
double radius = Math.max(getWidth(), getHeight()) * 1.5d;
double x = (getWidth() - radius) / 2d;
double y = (getHeight() - radius) / 2d;
areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
}
#Override
public void invalidate() {
// super.invalidate();
updateAreaOfEffect();
}
protected void updatePool() {
if (activeCarList.size() < maxCars) {
int count = Math.min(maxCars - activeCarList.size(), 10);
for (int index = 0; index < count; index++) {
Car car = null;
if (carPool.isEmpty()) {
car = new Car();
} else {
car = carPool.remove(0);
}
double direction = car.getDirection();
double startAngle = direction - 180;
double radius = areaOfEffect.getWidth();
Point2D startPoint = getPointAt(radius, startAngle);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
double x = cx + (startPoint.getX() - car.getWidth() / 2);
double y = cy + (startPoint.getY() - car.getHeight() / 2);
car.setLocation((int) x, (int) y);
Point2D targetPoint = getPointAt(radius, direction);
points.add(new Point2D[]{startPoint, targetPoint});
add(car);
activeCarList.add(car);
}
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Font font = g.getFont();
font = font.deriveFont(Font.BOLD, 48f);
FontMetrics fm = g.getFontMetrics(font);
g.setFont(font);
g.setColor(Color.RED);
String text = Integer.toString(maxCars);
int x = getWidth() - fm.stringWidth(text);
int y = getHeight() - fm.getHeight() + fm.getAscent();
g.drawString(text, x, y);
text = Integer.toString(getComponentCount());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(activeCarList.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(carPool.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void setCongestion(int value) {
maxCars = value;
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
// #Override
// public void repaint(long tm, int x, int y, int width, int height) {
// }
//
// #Override
// public void repaint(Rectangle r) {
// }
// public void repaint() {
// }
#Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
System.out.println(propertyName);
// // Strings get interned...
// if (propertyName == "text"
// || propertyName == "labelFor"
// || propertyName == "displayedMnemonic"
// || ((propertyName == "font" || propertyName == "foreground")
// && oldValue != newValue
// && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
// super.firePropertyChange(propertyName, oldValue, newValue);
// }
}
#Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}
protected static Point2D getPointAt(double radius, double angle) {
double x = Math.round(radius / 2d);
double y = Math.round(radius / 2d);
double rads = Math.toRadians(-angle);
double fullLength = Math.round((radius / 2d));
double xPosy = (Math.cos(rads) * fullLength);
double yPosy = (Math.sin(rads) * fullLength);
return new Point2D.Double(xPosy, yPosy);
}
public class Car extends JPanel {
private double direction;
private double speed;
private BufferedImage background;
public Car() {
setOpaque(false);
direction = Math.random() * 360;
speed = 5 + (Math.random() * 10);
int image = 1 + (int) Math.round(Math.random() * 5);
try {
String name = "/Car0" + image + ".png";
background = ImageIO.read(getClass().getResource(name));
} catch (IOException ex) {
ex.printStackTrace();
}
setSize(getPreferredSize());
// setBorder(new LineBorder(Color.RED));
}
public void setDirection(double direction) {
this.direction = direction;
revalidate();
repaint();
}
public double getDirection() {
return direction;
}
public void move() {
Point at = getLocation();
at.x += (int) (speed * Math.cos(Math.toRadians(-direction)));
at.y += (int) (speed * Math.sin(Math.toRadians(-direction)));
setLocation(at);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (background != null) {
double radian = Math.toRadians(direction);
double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
int w = background.getWidth(), h = background.getHeight();
int neww = (int) Math.floor(w * cos + h * sin);
int newh = (int) Math.floor(h * cos + w * sin);
size = new Dimension(neww, newh);
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
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);
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
g2d.drawImage(background, x, y, this);
g2d.dispose();
// Debug graphics...
// int cx = getWidth() / 2;
// int cy = getHeight() / 2;
//
// g2d = (Graphics2D) g.create();
// g2d.setColor(Color.BLUE);
// double radius = Math.min(getWidth(), getHeight());
// Point2D pointAt = getPointAt(radius, direction);
// g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//
// double xo = cx;
// double yo = cy;
// double xPos = cx + pointAt.getX();
// double yPos = cy + pointAt.getY();
//
// g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
// g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
// g2d.dispose();
}
#Override
public void invalidate() {
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
#Override
public void repaint(long tm, int x, int y, int width, int height) {
}
#Override
public void repaint(Rectangle r) {
}
#Override
public void repaint() {
}
#Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// System.out.println(propertyName);
// // Strings get interned...
// if (propertyName == "text"
// || propertyName == "labelFor"
// || propertyName == "displayedMnemonic"
// || ((propertyName == "font" || propertyName == "foreground")
// && oldValue != newValue
// && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
// super.firePropertyChange(propertyName, oldValue, newValue);
// }
}
#Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}
}
ps - I should add 1- My 10 month old loved it 2- It reminded me of the run to work :P

Categories