I need to do a java code with Threads (implements Runnable()) for a college assigment. I need to draw 4 fractals at the same time ( with thread.sleep()). I already tried almost everything I knew, and this still doesn't working. So I cleaned up the source.
I have four classes ( four fractals). In my JPanel, i call a paint method to draw them ( they are recursive ). Can anyone save me please?
public class MainPanel extends JPanel {
FractalTree tree = new FractalTree();
FractalCircle circle = new FractalCircle();
FractalSquare square = new FractalSquare();
FractalCircle2 circle2 = new FractalCircle2();
#Override
public void paint(Graphics g) {
setBackground(Color.black,g);
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.WHITE);
DrawBounds(g);
tree.drawTree(g,200,290,-90,9);
circle.drawCircle(g2,675,175,300);
square.drawSquares(g, 200, 525, 100,7);
circle2.drawCircle(g2,675,518,300);
}
public void DrawBounds(Graphics g){
g.drawLine(0,350,900,350);
g.drawLine(450,0,450,700);
}
public void setBackground(Color c,Graphics g){
g.fillRect(0, 0, 900, 700);
}
}
public class FractalSquare{
public void drawSquares(Graphics g,int x, int y, int side ,int size){
g.setColor(Color.BLUE);
if(size >2){
size--;
g.fillRect(x-side/2, y-side/2, side, side);
side = side/2;
x = x-side;
y = y-side;
drawSquares(g,x,y,side,size);
drawSquares(g,x+side*2,y,side,size);
drawSquares(g,x,y+side*2,side,size);
drawSquares(g,x+side*2,y+side*2,side,size);
} else return;
}
}
public class FractalCircle {
public void drawCircle(Graphics2D g, float x, float y, float radius) {
g.setColor(Color.RED);
g.draw(new Ellipse2D.Float(x-radius/2, y-radius/2, radius,radius));
if(radius > 2) {
radius *= 0.75f;
drawCircle(g,x, y, radius);
} else return ;
}
}
public class FractalCircle2 {
public void drawCircle(Graphics2D g, float x, float y, float radius) {
Color color = new Color(255,0,255);
g.setColor(color);
g.draw(new Ellipse2D.Float(x-radius/2, y-radius/2, radius,radius));
if(radius > 1) {
radius *= 0.75f;
drawCircle(g,x + radius/2, y, radius/2);
drawCircle(g,x - radius/2, y, radius/2);
} else return ;
}
}
public class FractalTree {
public void drawTree(Graphics g, int x1, int y1, double angle, int depth) {
g.setColor(Color.GREEN);
if (depth == 0) return;
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 5.0);
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 5.0);
g.drawLine(x1, y1, x2, y2);
drawTree(g, x2, y2, angle - 20, depth - 1);
drawTree(g, x2, y2, angle + 20, depth - 1);
}
}
You'll have to render your fractals into separate images and copy them to the screen as the rendering progresses.
Swing will do most of the work for you if you wrap each fractal in a JComponent.
Here's an example using one of your fractals:
public class FractalPanel extends JPanel {
final FractalSquare square = new FractalSquare(450, 350);
public FractalPanel() {
add(square);
}
public static void main(String[] args) {
FractalPanel fractalPanel = new FractalPanel();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(fractalPanel);
frame.pack();
frame.setVisible(true);
Thread squareThread = new Thread(() -> {
try {
fractalPanel.square.render();
} catch (InterruptedException e) {
System.err.println("Interrupted");
}
});
squareThread.start();
}
}
class Fractal extends JComponent {
final BufferedImage image;
final Graphics2D offscreenGraphics;
public Fractal(int width, int height) {
setPreferredSize(new Dimension(width, height));
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
offscreenGraphics = image.createGraphics();
}
// Copy the offscreen image to the main graphics context
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
synchronized (image) { // synchronize with the render thread
g2.drawImage(image, 0, 0, null);
}
}
}
class FractalSquare extends Fractal {
public FractalSquare(int width, int height) {
super(width, height);
}
public void render() throws InterruptedException {
drawSquares(getWidth() / 2, getHeight() / 2, 100, 7);
}
public void drawSquares(int x, int y, int side, int size) throws InterruptedException {
// Sleep for 10ms between frames
Thread.sleep(10);
if (size > 2) {
size--;
synchronized (image) { // synchronize with the draw thread
offscreenGraphics.setColor(Color.BLUE);
System.out.printf("Filling [%d, %d, %d, %d]\n", x - side / 2, y - side / 2, side, side);
offscreenGraphics.fillRect(x - side / 2, y - side / 2, side, side);
}
// Tell Swing that we've updated the image and it needs to be redrawn
repaint();
side = side / 2;
x = x - side;
y = y - side;
drawSquares(x, y, side, size);
drawSquares(x + side * 2, y, side, size);
drawSquares(x, y + side * 2, side, size);
drawSquares(x + side * 2, y + side * 2, side, size);
}
}
}
In the JFrame which contains your MainPanel, add this code (a private Thread and its instance somewhere in the constructor):
private Thread threadRepaint;
...
threadRepaint = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
yourPanel.repaint();
FractalSquare.decreaseLimit();
FractalCircle.decreaseLimit();
FractalCircle2.decreaseLimit();
FractalTree.decreaseLimit();
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
threadRepaint.start();
The idea behind this, is that the Thread has to decrease the limit of your fractals each second (1000ms).
In each of your fractal classes you have to change the conditionals to something like this, and also add a static variable. That way, you'll see a progress with each iteration.
For your FractalSquare, it will stop when it gets to 2:
private static limit = 10;
...
public static void decreaseLimit() {
if (limit > 2) limit--;
}
...
// Inside your drawSquares method
if (size > limit){
...
} else return;
It's basically the same idea for each of your other fractals: you set an upper limit which will decrease over time (each iteration of the Thread). The decreasing limit will give the illusion of progression with each iteration.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 9 months ago.
Improve this question
I have a tank game where you rotate via A/D keys and move forward/backward with W/S. The turret follows the mouse cursor on the screen. I am attempting to get shooting to work when I click. I get the initial degree of the turret rotation on click, as well as the point at the end of the turret. I set isShooting() to check if I am currently shooting at the given moment, and it is set to true on click. I use shotFired() also set to true on click, to tell whether there is a shot on the screen.
I want to have the bullet moving along a straight line with a give slope every time I click, from the end of the barrel to off the screen.
Currently the bullet moves in the wrong directions and is influenced by movement of the turret. I have been working to find the issue and cannot find it.
public void update() {
playerTank.setCenterTurret(new Point2D.Double(playerTank.xPos() + 67, playerTank.yPos() + 125));
playerTank.setEndTurret(new Point2D.Double(playerTank.xPos() + ((Math.sin(Math.toRadians(playerTank.getTurretDegree())))) + 63, playerTank.yPos() + ((Math.cos(Math.toRadians(playerTank.getTurretDegree())))) - 25));
playerTank.setCenterBase(new Point2D.Double(playerTank.xPos() + (playerTank_PNG_WIDTH / 2), playerTank.yPos() + (playerTank_PNG_HEIGHT / 2)));
mouseLoc = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(mouseLoc, this);
mouseLocX = (int) mouseLoc.getX();
mouseLocY = (int) mouseLoc.getY();
mouseDistX = mouseLocX - playerTank.getCenterTurret().getX();
mouseDistY = mouseLocY - playerTank.getCenterTurret().getY();
mouseDegree = angleInRelation(mouseLoc, playerTank.getCenterTurret());
if(moveUp) {
playerTank.setLocation(playerTank.xPos() + (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() - MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(moveDown) {
playerTank.setLocation(playerTank.xPos() - (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() + MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(rotateLeft && playerTank.xPos() >= 0) {
playerTank.setBaseDegree(playerTank.getBaseDegree() - 5);
}
if(rotateRight && playerTank.xPos() + playerTank.getWidth() <= FRAME_WIDTH) {
playerTank.setBaseDegree(playerTank.getBaseDegree() + 5);
}
mouseDegree -= playerTank.getBaseDegree();
this.setBackground(Color.white);
repaint();
}
#Override
public void paint(Graphics g) {
this.setBackground(Color.white);
Graphics2D g2D = (Graphics2D) g;
g2D.setBackground(Color.white);
g2D.setColor(Color.white);
g2D.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
paintBase(g2D);
playerTank.setTurretDegree(mouseDegree);
g2D.rotate(Math.toRadians(playerTank.getTurretDegree()), playerTank.xPos() + 67, playerTank.yPos() + 125);
paintTurret(g2D);
paintBullet(g2D);
}
public void paintBullet(Graphics2D g2D) {
g2D.setColor(Color.black);
if(playerTank.isShooting()) {
playerTank.setBulletPos(playerTank.getEndTurret());
g2D.fillRect((int) playerTank.getEndTurret().getX(), (int) playerTank.getEndTurret().getY(), 8, 18);
playerTank.setShooting(false);
}
if (playerTank.shotFired()) {
double newX = (BULLET_SPEED * (Math.sin(Math.toRadians(playerTank.getInitialTurretDegree()))));
double newY = (BULLET_SPEED * (Math.cos(Math.toRadians(playerTank.getInitialTurretDegree()))));
playerTank.setBulletPos(playerTank.getBulletX() + newX, playerTank.getBulletY() - newY);
g2D.fillRect((int) playerTank.getBulletX(), (int) playerTank.getBulletY(), 8, 18);
if((playerTank.getBulletX() > FRAME_WIDTH || playerTank.getBulletY() > FRAME_HEIGHT) || (playerTank.getBulletX() < 0 || playerTank.getBulletY() < 0)) {
playerTank.setShotFired(false);
}
}
Thank you so much.
So, your problem basically boils down to a series of trigonometry problems ... yea (can you feel the sarcasm ๐).
But what do I mean? So, you want to find the end point of the barrel, based on it's current angle of rotation? Well, you will need to know the length of barrel, this will give you a radius around which the barrel can travel, from that you can simply perform a "point on a circle" calculation.
Want to know the path of the projectile, calculate the "point on a circle" based on a radius greater than the visible area and then draw a line between the barrel and this point, that's your path. Told you, trigonometry.
So, I started by creating a simple entity which would end up looking something like (with color guides)...
The turret and the body are seperate images, but when drawn on top of each other, they will appear as above, this is really important, as this saves so much effort.
The turret then has a specified radius and can rotate easily around the body
If you look really hard, the turret is actually not centered around the "natural" center of the image, but by laying it out this way, we can easily rotate around the mid point of the image - so much easier.
Okay, that all sounds fun, let's start with a basic concept of our "tank" entity...
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
The important things to note here are...
The x/y position of the tank represents it's "center" point
The body and turret can rotate independently of each other
Now, we can make the turret look at the mouse by using a MouseMotionListener and, you guessed it, some more trigonometry
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
}
g2d.dispose();
}
}
The "core" functionality is in the Timer and looks like...
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
It's important to note that the Graphics API and Math API have a different concept of 0 (yea).
Okay, but how do we find the projectile path?! Well, more trigonometry!
But first, we need some help...
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
So, all this does is provide some basic "point on circle" calculations. From this, we can calculate the "point" in the "world" we're looking for...
So, we can add...
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
to our Tank entity, this will return the "world" coordinates that represents the end of the barrel.
Let's add some "debug" graphics to our paintComponent...
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
This will add...
A guide which represents the target circle created by the mouse
A projectile line from the barrel to the mouse
A projectile line from the barrel to outside the visible area
You might be surprised that this actually now provides the answer to the question. You have the start point and end point of the projectile, now you just need some way to animate it...
When it comes to animation, I tend to prefer time based animations, but since the projectile might travel a variable distance, what we really need is a linear progression, so if it was to travel a long distance or a short distance, it would travel at the same speed.
Now, I banged by head against Google for a bit and was able to come up with...
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
This will basically calculate a x/y delta which needs to be applied to move the projectile from the current point to it's target point using a constant speed.
Now we can add a MouseListener...
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
Update the projectiles in our main loop...
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
And update our paintComponent...
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
Runnable example...
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new GamePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
private List<Projectile> projectiles = new ArrayList<>(8);
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
g2d.dispose();
}
}
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
}
I am attempting to draw Sierpinski's triangle on a pixel-by-pixel basis that resizes itself any time the window size is changed. I believe I have most of the project done but I don't quite know how to draw the rectangle from the separate recursive function that is outside of the paintComponent method.
public class SierpTriangle extends JPanel
{
public final int x = this.getWidth();
public final int y = this.getHeight();
public final int side = getsize();
public int getsize()
{
int width = this.getWidth();
int height = this.getHeight();
if (width <= height)
{
return width;
}
else
{
return height;
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
drawSierpTriangle(this.x, this.y, this.side);
g.drawRect(x,y,1,1);
}
public void drawSierpTriangle(int x, int y, int size)
{
if (size == 1)
{
//Draw rectangle? This is where I need help
g.drawRect(x,y,1,1); //this does not work, passing Graphics g into the method also does not work
}
else
{
drawSierpTriangle(x/2, y, size/2);
drawSierpTriangle(x,y/2,size/2);
drawSierpTriangle(x/2,y/2,size/2);
}
}
public static void main(String[] args)
{
new SierpFrame();
}
}
Pass the reference of Graphics from paintComponent to drawSierpTriangle
public void paintComponent(Graphics g)
{
super.paintComponent(g);
drawSierpTriangle(g, this.x, this.y, this.side);
g.drawRect(x,y,1,1);
}
public void drawSierpTriangle(Graphics g, int x, int y, int size)
{
if (size == 1)
{
//Draw rectangle? This is where I need help
g.drawRect(x,y,1,1); //this does not work, passing Graphics g into the method also does not work
}
else
{
drawSierpTriangle(g, x/2, y, size/2);
drawSierpTriangle(g, x,y/2,size/2);
drawSierpTriangle(g, x/2,y/2,size/2);
}
}
This results in: Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError at the first recursive call of the method. Any input?
public final int side = getsize();
will make side for ever 0.
Replace it with something more like...
public int getSide() {
int width = this.getWidth();
int height = this.getHeight();
if (width <= height) {
return width;
} else {
return height;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int side = getSide();
if (side == 0) return;
drawSierpTriangle(g, this.x, this.y, side);
g.drawRect(x, y, 1, 1);
}
This will evaluate side every time the component is painted. It will also skip painting the shape if side is 0
You'll also have the same problem with x and y, since there state is never changed
I tried to plot a circle using the java awt, what I get in output is just few small which are separated by much distance apart and doesn't look like a circle as a whole. Code is given below :
class DrawFrame extends JFrame {
int ั
ั, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = xc - r;
while (x <= (xc + r)) {
for (y = yc - r; y <= (yc + r); y++) {
p = x * x + y * y - r * r;
if (p == 0.0)
g.drawOval(x, y, 2, 2);
}
x++;
}
}
You should start by having a read of Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how the painting system works and how you should work with it.
You shouldn't override the paint method of top level components, apart from not been double buffered, they are actually compound components. This means that they have a number of additional components laid out on top of them which provide the overall functionality of the window.
This means that it's possible for what you've painted to the surface of the frame to be ignored, as the content on top of it paints over it.
You're also ignoring the complexity of the painting process, unless you're prepared to take over the responsibility of the paint method yourself, you should always call its super method.
A better place to start is with a JPanel (which is simpler) and override its paintComponent method
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
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(100, 100, 100));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
int xc, yc, r;
public TestPane(int rr, int c1, int c2) {
r = rr;
xc = c1;
yc = c2;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(r * 2, r * 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
circle(g);
}
public void circle(Graphics g) {
// Credit to LAD for the algorithm updates
int x = xc - r;
while (x <= (xc + r)) {
for (int y = yc - r; y <= (yc + r); y++) {
float p = (x - xc) * (x - xc) + (y - yc) * (y - yc) - (r * r);
if (p <= 0.0f)
{
g.drawOval(x, y, 2, 2);
}
}
x++;
}
}
}
}
Credit to LAD for the algorithm updates
The first thing to change is to make JFrame visible by adding
setVisible(true); to its constructor.
It is recommended to use names that have a clear meaning to make the code more readable. Make the fields scope as limited as possible, in this case make them private:
private int ัenterX, centerY, radius;
(x,y and p are method variables and do not need to be fields )
Avoid using magic numbers. Use constants instead:
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
Putting it together, using correct Java naming conventions and fixing the algorithm:
import java.awt.Graphics; //add imports to make tour code mcve
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
class DrawFrame extends JFrame {
private final int ัenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawFrame(int radius, int centerX, int centerY) {
setSize(W, H);
setTitle("circle drawing algo");
this.radius = radius;
ัenterX = centerX;
this.centerY = centerY;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
setVisible(true); //make frame visible
}
#Override
public void paint(Graphics g) {
super.paint(g);
circl(g);
}
public void circl(Graphics g) {
int x, y;
x = ัenterX - radius;
while (x <= ัenterX + radius) {
//calculate x
y = (int)Math.sqrt(radius*radius - (ัenterX -x)*(ัenterX -x ));
g.drawOval(x, centerY-y, 2, 2); // 2 y values for every x
g.drawOval(x, centerY+y, 2, 2);
x++;
}
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
The next improvement could be to refactor so the painting is done on a JPanel rather than on the JFrame itself, using Graphics.drawOval:
class DrawFrame extends JFrame {
DrawFrame(int radius, int centerX, int centerY) {
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
add(new DrawingPane(radius, centerX, centerY));
pack();
setVisible(true); //make frame visible
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
class DrawingPane extends JPanel{
private final int ัenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawingPane(int radius, int centerX, int centerY) {
setPreferredSize(new Dimension(W, H));
this.radius = radius;
ัenterX = centerX;
this.centerY = centerY;
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(ัenterX, centerY, radius, radius);
}
}
I edited your code a bit and changed the draw algorithm a little in order to completely draw the circle. Here is the refactored code:
class DrawFrame extends JFrame {
int xc, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Handles the window being closed
setVisible(true); // Makes the window visible
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
GradientPaint gp = new GradientPaint(0f,0f,Color.blue,0f,30f,Color.green); // Just sets a color for the paint
g2.setPaint(gp);
Circl(g2);
}
public void Circl(Graphics g) {
x = xc-r;
while (x <= (xc+r)) {
for (y = yc-r; y <= (yc+r); y++) {
p = (x-xc)*(x-xc)+(y-yc)*(y-yc)-(r*r); // Edited this line so that itโs now the correct circle formula
if (p <= 0.0f) // If the point is 0 or less, then the point is within the circle bounds
g.drawOval(x, y, 2, 2);
}
x++;
}
}
public static void main(String[] args) {
new DrawFrame(100, 500, 500);
}
}
The code is a bit slow, though, as the drawOval method seems to be quite slow when called a bunch of times. So, this code is mainly suited for the algorithm aspect, as you could easily fill out an oval by calling the following once:
g.drawOval(x, y, 200, 200);
g.fillOval(x, y, 200, 200);
You set initial value of x as x = xc - r, y as y = yc - r. And
converting Cartesian to Polar coordinate like p = x * x + y * y - r *
r . Assume that if xc=500, and yc=500, r=50, "p" will never be 0. So
I think you forgot to calculate xc, yc when you draw. I modified your
code little bit and attached the result.
package testProject;
import java.awt.*;
import javax.swing.*;
public class DrawFrame extends JPanel {
int xc=500, yc=500, r=150, x, y;
int real_x, real_y;
float p;
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("circle drawing algo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.white);
frame.setSize(1000, 1000);
DrawFrame panel = new DrawFrame();
frame.add(panel);
frame.setVisible(true);
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = -r;
while (x <= r) {
y = -r;
while (y <= r) {
p = x * x + y * y - r * r;
// here is the change
if (p>=0 && p<= xc) {
g.drawOval(x+xc, y+yc, 3, 3);
}
y++;
}
x++;
}
}
}
I'm trying to make a 2D racing game here by adding a Jpanel on top of a Jpanel. This is done by using 2 classes which I have posted down below.
The problem is the car is never appears on the track... I'm really not sure what I'm missing.. any help is more than welcome!
Thank you in advance!
Car.java
public class Car extends JPanel implements Runnable
{
private static final long serialVersionUID = 007;
private BufferedImage car = null;
private float x = 100F, y = 100F;
private Thread driveThread = new Thread(this);
private double currentAngle = 0; // angel of the car
private static int[] key = new int[256]; // keyboard input
private float MAX_SPEED = 7F;
private float speed = 0F; // speed of our racing car
private float acceleration = 0.15F;
private int player;
private boolean playable = true;
public Car(int player)
{
this.player = player;
this.setSize(super.getHeight(), super.getWidth());
this.setFocusable(true); // enables keyboard
try
{
if (player == 1)
{
//red car
car = ImageIO.read(this.getClass().getResource(
"/imagesCar/first-0.png"));
System.out.println(car.getColorModel());
} else if(player == 2)
{
//blue car
car = ImageIO.read(this.getClass().getResource(
"/imagesCar/second-0.png"));
x = x +30;
}
} catch (IOException e) {
System.out.println("dupi");
}
// starts the drive thread
startGame();
}
private void startGame() {
driveThread.start();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
this.setOpaque(false);
// rotation
Graphics2D g2d = (Graphics2D) g;
AffineTransform rot = g2d.getTransform();
// Rotation at the center of the car
float xRot = x + 12.5F;
float yRot = y + 20F;
rot.rotate(Math.toRadians(currentAngle), xRot, yRot);
g2d.setTransform(rot);
//Draws the cars new position and angle
g2d.drawImage(car, (int) x, (int) y, 50, 50, this);
}
protected void calculateCarPosition() {
//calculates the new X and Y - coordinates
x += Math.sin(currentAngle * Math.PI / 180) * speed * 0.5;
y += Math.cos(currentAngle * Math.PI / 180) * -speed * 0.5;
}
protected void carMovement() {
// Player One Key's
if (player == 1) {
if (key[KeyEvent.VK_LEFT] != 0) {
currentAngle-=2;
} else if (key[KeyEvent.VK_RIGHT] != 0) {
currentAngle+=2;
}
if (key[KeyEvent.VK_UP] != 0) {
if (speed < MAX_SPEED) {
speed += acceleration;
}
} else if (key[KeyEvent.VK_DOWN] != 0 && speed > -1) {
speed = speed - 0.1F;
}
speed = speed * 0.99F;
} else {
//Player Two Key's
if (key[KeyEvent.VK_A] != 0) {
currentAngle -= 2;
} else if (key[KeyEvent.VK_D] != 0) {
currentAngle += 2;
}
if (key[KeyEvent.VK_W] != 0) {
if (speed < MAX_SPEED) {
speed += acceleration;
}
} else if (key[KeyEvent.VK_S] != 0 && speed > -1) {
speed = speed - 0.1F;
}
//reduce speed when no key is pressed
speed = speed * 0.99F;
}
}
public void getUnderground() {
}
// get key events!
final protected void processKeyEvent(KeyEvent e) {
key[e.getKeyCode()] = e.getID() & 1;
}
#Override
public void run() {
while (true) {
repaint();
carMovement();
calculateCarPosition();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
RaceTrack.java
public class RaceTrack extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
Color c1 = Color.green;
g.setColor(c1);
g.fillRect(150, 200, 550, 300);
Color c2 = Color.black;
g.setColor(c2);
g.drawRect(50, 100, 750, 500); // outer edge
g.drawRect(150, 200, 550, 300); // inner edge
Color c3 = Color.yellow;
g.setColor(c3);
g.drawRect(100, 150, 650, 400); // mid-lane marker
Color c4 = Color.white;
g.setColor(c4);
g.drawLine(425, 500, 425, 600); // start line
}
}
main
public static void main(String[] args) {
JFrame mainFrame = new JFrame();
mainFrame.setSize(850,650);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = mainFrame.getContentPane();
RaceTrack track = new RaceTrack();
Car carP1 = new Car(1);
track.add(carP1);
content.add(track);
mainFrame.setVisible(true);
}
Car has no defined sizing hints, so it's default size is 0x0
Adding Car to RaceTrack, which is using a FlowLayout will layout Car to it's preferred size of 0x0
Swing is not thread safe so you're probably have a bunch of thread race conditions/violations as well
Not sure if this is the proper way to solve this
Don't use components for this purpose, this problem just screams custom painting all the way.
There are plenty of blogs and tutorials about basic game development, so I don't want to spend a lot of time going over the same material.
Basically, what you want is to define a series of "attributes" to the objects you want to use in your game (AKA "entities"). Not all entities need to be paintable, some might trigger other actions or simply act as "markers" for other entities to use.
In this example, I'm defining two basic entities, "movable" and "paintable". A "paintable" entity may be static (ie the track) or "movable" (ie the car)
The intention is to provide a isolated concept of functionality which can easily be applied to a verity objects in order to "describe" their functionality and purpose within the game.
For example...
public interface MovableEntity extends Entity {
public void update(Rectangle bounds);
}
public interface PaintableEntity extends Entity {
public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds);
}
So, in your case, a Car is both Paintable and Movable.
Your "engine" would then maintain one or more lists of these "entities" and process them accordingly.
This example simply makes use of a Swing Timer to act as the "main loop"
mainLoop = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
// Lots of collision detection and other awesome stuff
for (MovableEntity entity : movableEntitys) {
entity.update(bounds);
}
repaint();
}
});
mainLoop.start();
This provides a level of thread safety, as the Timer is triggered within the context of the Event Dispatching Thread, meaning that while we're updating the entities, they can't be painted.
Then we simply make use of the JPanel's paintComponent method to act as the rendering process...
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
for (PaintableEntity paintable : paintableEntities) {
Graphics2D g2d = (Graphics2D) g.create();
paintable.paint(g2d, this, bounds);
g2d.dispose();
}
}
This is a pretty broad example based on demonstrating basic concepts in a simple manner. There are far more complex possible solutions which would follow the same basic principles.
Personally, I'd define some kind of "path" which acts as the track, on which the cars would then calculate there positions based on different factors, but that's somewhat of a more complex solution then is required right now. But if you're really interested, it might look something like this
Runnable Example...
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.image.ImageObserver;
import java.util.ArrayList;
import java.util.List;
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();
}
/*
You could have entities which can collide which have collision detection
capabilities
Some entities don't need to be painted and may provide things like
visual or audio affects
*/
public interface Entity {
}
public interface MovableEntity extends Entity {
public void update(Rectangle bounds);
}
public interface PaintableEntity extends Entity {
public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds);
}
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 GamePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class GamePane extends JPanel {
// Could use a single list and filter it, but hay
private List<PaintableEntity> paintableEntities;
private List<MovableEntity> movableEntitys;
private Timer mainLoop;
public GamePane() {
paintableEntities = new ArrayList<>(25);
movableEntitys = new ArrayList<>(25);
paintableEntities.add(new TrackEntity());
CarEntity car = new CarEntity();
paintableEntities.add(car);
movableEntitys.add(car);
mainLoop = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
// Lots of collision detection and other awesome stuff
for (MovableEntity entity : movableEntitys) {
entity.update(bounds);
}
repaint();
}
});
mainLoop.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
for (PaintableEntity paintable : paintableEntities) {
Graphics2D g2d = (Graphics2D) g.create();
paintable.paint(g2d, this, bounds);
g2d.dispose();
}
}
}
public class CarEntity implements PaintableEntity, MovableEntity {
private int delta = 1;
private int xDelta = 0;
private int yDelta = delta;
private int xPos = 2;
private int yPos = 2;
private int size = 4;
#Override
public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds) {
g2d.translate(bounds.x, bounds.y);
g2d.setColor(Color.RED);
g2d.fillRect(xPos - size / 2, yPos - size / 2, size, size);
}
#Override
public void update(Rectangle bounds) {
xPos += xDelta;
yPos += yDelta;
if (xPos + (size / 2) > bounds.x + bounds.width) {
xPos = bounds.x + bounds.width - (size / 2);
xDelta = 0;
yDelta = -delta;
} else if (xPos - (size / 2) < bounds.x) {
xPos = bounds.x + (size / 2);
xDelta = 0;
yDelta = delta;
}
if (yPos + (size / 2) > bounds.y + bounds.height) {
yPos = bounds.y + bounds.height - (size / 2);
xDelta = delta;
yDelta = 0;
} else if (yPos - (size / 2) < bounds.y) {
yPos = bounds.y + (size / 2);
xDelta = -delta;
yDelta = 0;
}
}
}
public class TrackEntity implements PaintableEntity {
#Override
public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds) {
g2d.translate(bounds.x, bounds.y);
g2d.setColor(Color.BLUE);
g2d.drawRect(2, 2, bounds.width - 4, bounds.height - 4);
}
}
}
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