I am trying to create a 3D game engine. I am using the triangle rasterizing technique so I have a list of lines (that make up triangles) in my CanvasPanel class and each time that paintComponent() is called it goes through all the lines and displays them on the screen. The problem is in my main loop, since I want to remove all lines in order to update the scene but when I call it it's being called while the paintComponent() method is still running so I get a ConcurrentModificationException. I tried having a loop going that checks if it has finished drawing and only then clear the lines list but for some reason it works only when adding a print statement, otherwise it stops working after a few seconds. What should I do to make sure the timing is right?
Relevant code in Main:
import javax.swing.*;
import java.awt.*;
public class Main
{
public static int width = 1280, height = 720;
public static void main(String[] args)
{
JFrame window = new JFrame("3D Game");
CanvasPanel canvas = new CanvasPanel(width, height);
window.setLayout(new FlowLayout());
window.getContentPane().add(canvas);
window.pack();
window.setVisible(true);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Some irrelevant calculations
while (true) // Main loop
{
// Some irrelevant calculations
for (float[][] triangle: cubeMesh)
{
// Here I'm adding lines to the lines list.
}
canvas.repaint(); // update canvas
// My attempt at making sure that clearing the lines list happens only after drawing
while (true)
{
if (!canvas.isDrawing()}
{
// Only works if I print something
break;
}
}
canvas.clearLines(); // here is the problematic line
}
}
}
Relevant code in CanvasPanel:
public class CanvasPanel extends JPanel
{
BufferedImage canvas;
JLabel label;
private ArrayList<Line> lines;
private boolean drawing;
public CanvasPanel(int width, int height)
{
canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
label = new JLabel(new ImageIcon(canvas));
lines = new ArrayList<>();
add(label);
drawing = false;
}
public boolean isDrawing()
{
return drawing;
}
public void clearLines()
{
lines.clear();
}
#Override
protected void paintComponent(Graphics g)
{
drawing = true;
super.paintComponent(g);
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
for (Line line: lines)
{
g.setColor(line.color);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
drawing = false;
}
public void addLine(int x1, int y1, int x2, int y2, Color color)
{
lines.add(new Line(x1, y1, x2, y2, color));
}
First, go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works in Swing.
Swing is lazy and makes use of a passive rendering workflow. This means that painting is done when the system decides it needs to be done. You can "request" a paint pass via the RepaintManager (normal via calling repaint on the component)
What this does is, if the RepaintManager agrees, places a "paint" event on the Event Dispatching Queue, which is, at some point in the future picked up by the Event Dispatching Thread and a paint pass is executed (this may be consolidated so that multiple components are painted in one pass).
There are two problems you're facing:
Painting can occur for any number of reasons, many of which you don't control and you're notified by
There's no reliable way to determine if or when a paint pass will/has occurred.
Now, add in the issue that Swing is not thread safe and you run into a mountain of other issues.
See Concurrency in Swing for more details.
So, what's the answer?
Well, you could use Worker Threads and SwingWorker or a Swing Timer to synchronise the updates depending on your needs, but honestly, I don't think making use of Swing's paint process is what you really need.
Another consideration would be use a backing buffer of your own. That is, use two or more BufferedImages onto which you draw the "state" and then pass these to the canvas to be drawn at some point in the future.
However, I would suggest taking control of the painting process directly yourself. Instead, have a look at BufferStrategy and BufferCapabilities (and the JavaDocs because it has a nice example).
This will provide you with direct control over the painting process so you can decide when and how it should be painted.
Related
This question already has answers here:
Repaint without clearing
(2 answers)
Closed 3 years ago.
I am implementing a simple Canvas where items can be drawn like a person would in real life with a paper and a pencil, without clearing the entire page every time an object is drawn.
What I have so far...
A Canvas to implement the drawing:
public class Canvas extends JPanel {
private final Random random = new Random();
public Canvas() {
setOpaque(false); // I thought setting this flag makes the drawn pixels be preserved...
}
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(random.nextInt(getWidth()), random.nextInt(getHeight()), 5, 5);
}
}
The Window as an actual window:
public class Window extends JFrame {
public Window(Canvas canvas) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(canvas);
pack();
setVisible(true);
}
}
And the Controller with an entry-point to the application. Also starts a timer so the repaint on Canvas is called every second to force drawing another circle.
public class Controller {
public static void main(String[] args) {
Canvas canvas = new Canvas();
SwingUtilities.invokeLater(() -> new Window(canvas));
new Timer(1000, e -> canvas.repaint()).start();
}
}
The problem is that whenever a new circle is drawn, the previous one is cleared. Seems like there is still some process filling the JPanel or maybe the entire JFrame with white color.
Painting in Swing is destructive. It is an expected requirement that each time a component is painted, it is painted from scratch, again.
You need to define a model which maintains the information needed in order to restore the state from scratch.
Your paint routines would then iterate this model and draw the elements each time.
This has the benefit of allowing you to modify the model, removing or inserting elements, which would allow you to update what is been painted simply.
Alternatively, you could use a "buffer" (ie a BufferedImage) on to which all you painting is done, you would then simply paint the image to the component each time the component is painted.
This, however, means that you can't undo or layer the paintings, it's drawn directly to the image. It also makes resizing the drawing image area more difficult, as you need to make these updates manually, where as the "model" based implementation is far more adaptable
Consider calling the alternate constructor of repaint(...)
repaint(long tm, int x, int y, int width, int height)
This allows you to set a specified area to be repainted.
Also you can just store what you drew in a list and then reprint the drawing to the canvas after repaint is called.
I am trying to create a window frame to display a game window. I extended JFrame in my GameWindow class and created two methods: drawBackground, which fills the screen with a solid rectangle, and drawGrid, which draws successive lines using a for-loop to make a grid. Here is my code.
public class GameWindow extends JFrame {
// instance variables, etc.
public GameWindow(int width, Color bgColor) {
super();
// ...
this.setVisible(true);
}
public void drawBackground() {
Graphics g = this.getGraphics();
g.setColor(bgColor);
g.fillRect(0, 0, this.getWidth(), this.getWidth());
// I suspect that the problem is here...
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
public void drawGrid() {
Graphics g = this.getGraphics();
g.setColor(Color.BLACK);
for (int i = tileWidth; i < TILE_COUNT * tileWidth; i += tileWidth) {
g.drawLine(0, i * tileWidth, this.getWidth(), i * tileWidth);
g.drawLine(i * tileWidth, 0, i * tileWidth, this.getHeight());
}
// ... and here.
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
}
However, when I try to test this class in a program like this:
public class Main {
public static void main(String[] args) {
GameWindow game = new GameWindow(700);
game.drawBackground();
game.drawGrid();
}
}
the frame appears on screen but remains blank; neither the background nor the grid is being drawn. I tried Graphics g = this.getGraphics() to this.getContentPane().getGraphics(). I also tried using many different combinations and orders in drawBackground and drawGrid of revalidate, update, etc. None of these attempts seemed to work. How can I fix this issue?
Well, Graphics g = this.getGraphics(); would be a good place to start. Since repaint just schedules a paint pass to occur with the RepaintManager, all the code using getGraphics will simply be ignored.
This is not how custom painting works. getGraphics can return null and is, at best, a snapshot of the last paint cycle, anything you paint to it will be wiped clean on the next paint cycle.
Also, don't dispose of a Graphics context you did not create, on some systems this will stop other components from been able to use it
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing for a better understanding into how painting works and how you should work with it.
You're also, likely, going to want to have a read through Concurrency in Swing and How to Use Swing Timers for ideas on creating a "main loop" to update the UI at constant rate, as Swing is single threaded AND not thread safe
I'm very new to java.swing and I have looked at other answers but I'm struggling to understand how to properly double buffer in order to remove flickering.
Currently I have a JFrame class that has an action Listener
JButton northWest = new JButton("↖");
northWest.setPreferredSize(new Dimension(60, 60));
moveBorder.add(northWest);
northWest.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MainBoardJPanel mainBoardPanel = new MainBoardJPanel();
mainBoardPanel.movePlayer(painel.getGraphics(), -40, -40, playerInfo, currentPlayer.getUsername());
performMove();
}
});
When the button is pressed the movePlayer method (inside the MainBoardJPanel class) draws the background, the player icon at the new place and finally another layer which acts as fog of war.
public void movePlayer(Graphics g, int x, int y, PlayerInformation[] playerInfo, String username) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
try {
background = ImageIO.read(new File("Images\\BackgroundBoard.png"));
playerIcon1 = ImageIO.read(new File("Images\\PlayerIcon1.png"));
fogOfWar = ImageIO.read(new File("Images\\FogOfWar.png"));
} catch (Exception e) {
e.printStackTrace();
}
g.drawImage(background, 0, 0, this);
g.drawImage(playerIcon1, playerInfo[0].getPosX(), playerInfo[0].getPosY(), null);
int xFOW = x - 775;
int yFOW = y - 775;
g.drawImage(fogOfWar, xFOW, yFOW, this);
updateUserInfo(x, y, username);
setDoubleBuffered(true);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
(This is a very simplified version of it to cut down on irrelevant code)
Since each image is drawn consecutively the image keeps on flickering whenever the method is called, how would I go about double buffering this?
but I'm struggling to understand how to properly double buffer in order to remove flickering
I just want to make one thing very clear. When custom painting is done correctly, Swing components are automatically double buffered.
Calling super.paint in movePlayer is an inappropriate abuse of the painting system.
Don't use getGraphics, this is not how custom painting works. Also, don't dispose of a Graphics context you didn't create, this can actually prevent components which are schedule to painted after yours from been painted.
Swing uses a passive rendering algorithm, this means that it will schedule painting to occur when ever it decides it needs to be done. You don't have control over it, the best you can do is provide "hints" to the system that you would like a component or some part of the component repainted (ie repaint). The painting subsystem will then decide when and what should be painted. Your components will be requested to perform a paint operation via their paint methods.
When done this way, Swing will double buffer the paint operation, so all components been painted are buffered and when the paint pass is complete, the image updated to the screen
I would, highly recommend, reading through:
Performing Custom Painting
Painting in AWT and Swing
for more details about how the painting system works in Swing and how you are expected to work with it
As a side note, I would also avoid loading resources (or performing any time consuming tasks) in any paint functionality. Painting should run as fast as possible
I've been trying to implement double buffering in my game library, but it is dramatically slower than without it.
This is my class that implements it. Basically, it draws onto an image, and then draws the image onto the JPanel's graphics (pg).
public abstract class DrawingPanel extends GameLoop {
private BufferedImage image;
private Graphics2D ig;
private Graphics pg;
private int backgroundRGB;
public DrawingPanel(final int fps, final int ups, final JPanel panel, final Color background) {
super(fps, ups);
initialize(panel, background);
}
/**
* Creates a panel with 60 fps and 120 ups
*
* #param panel
* #param background
*/
public DrawingPanel(final JPanel panel, final Color background) {
this(60, 120, panel, background);
}
public DrawingPanel(final JPanel panel) {
this(panel, Color.WHITE);
}
public DrawingPanel(final int ups, final JPanel panel, final Color background) {
super(ups);
initialize(panel, background);
}
private void initialize(final JPanel panel, final Color background) {
image = GraphicsUtils.createImage(panel.getWidth(), panel.getHeight(), Transparency.OPAQUE);
ig = (Graphics2D) image.getGraphics();
pg = panel.getGraphics();
GraphicsUtils.prettyGraphics(ig);
backgroundRGB = background.getRGB();
panel.addMouseListener(this);
panel.addMouseMotionListener(this);
panel.addKeyListener(this);
panel.addMouseWheelListener(this);
panel.setFocusable(true);
panel.requestFocusInWindow();
}
#Override
public void draw() {
// set background
Arrays.fill(((DataBufferInt) image.getRaster().getDataBuffer()).getData(), backgroundRGB);
// draw on the buffer
draw(ig);
// draw our buffer to the screen
pg.drawImage(image, 0, 0, null);
}
public abstract void draw(Graphics2D g);
}
The results of this:
This means that 25% - 6% = 19% of the time is being spent in the draw method alone!
I thought it might be the fact that I was using Array.fill, but it turned out not to be a useless "premature optimization;" if I replaced that line with
ig.setColor(backgroundColor);
ig.fillRect(0, 0, image.getWidth(), image.getHeight());
The time it takes is even longer: 26 - 5% = 21%. Is there any way I can speed this method up?
By the way, the GraphicsUtils.createImage creates a compatible image from my GraphicsConfiguration.
This entire library is on github, if you want to look at the entire code.
This is NOT how painting works in Swing -> pg = panel.getGraphics(); this is a horribly bad idea. You are fighting between the passive rendering engine of Swing and your attempt to paint to a "snap shot" of the component.
Swing components are, when you use them properly, double buffered internally, but, you need to be working with the API.
Start by having a look at Performing Custom Painting and Painting in AWT and Swing for more details about how painting works in Swing
Basically, what's happening is, you are using the results from getGraphics to paint to the panel, the RepaintManager is coming along and decides it needs to update your component and repaints it, which produces a blank panel, repeat really fast. This is what's causing your flickering. In Swing, you do not control the paint process, you can only ask to be notified when a paint cycle occurs (overriding paintComponent for example) and make requests to the painting system that a repaint should occur, but it's up to the paint system to actually decide WHEN that paint cycle occurs.
Instead, start by overriding the panel's paintComponent method and perform ALL your custom painting within it. Use repaint to request that the component be updated.
If you "really" need a active painting process, then have a look at BufferStrategy and BufferStrategy and BufferCapabilities, which provides you the means to directly control when the output is pushed to the screen device.
I'm trying to make a Paint program using java , I have three events in the jPanel to draw my line.
my problem is that when I am drawing the new line , the first one removed (I think the problem in the dragged event!) .. and so on.
Note that while the mouse is dragged the line will be stucked to the mouse
here is my events code:
private void jPanel1MousePressed(java.awt.event.MouseEvent evt) {
g1=(Graphics2D) jPanel1.getGraphics();
p1=jPanel1.getMousePosition();
}
JLayer lpane;
private void jPanel1MouseDragged(java.awt.event.MouseEvent evt) {
if(p1!=null){
lpane = new JLayer();
jPanel1.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, 328, 257);
g2=(Graphics2D) lpane.getGraphics();
l=new Line(p1.x,p1.y,jPanel1.getMousePosition().x,jPanel1.getMousePosition().y);
l.draw(g2);
//lpane.repaint();
lpane.setVisible(false);
lpane.removeAll();
lpane.disable(); jPanel1.remove(lpane);
}
}
private void jPanel1MouseReleased(java.awt.event.MouseEvent evt) {
if(p1!=null)
{
g1=(Graphics2D) jPanel1.getGraphics();
p2=jPanel1.getMousePosition();
l=new Line(p1.x,p1.y,p2.x,p2.y);
g1.setColor(Color.red);
l.draw(g1);
p1=null;
}
}
Graphics2D g1,g2; Point p1=null,p2=null; Line l;
getGraphics is not how painting should be done in Swing, instead override the panels paintComponent and paint your components state there.
The paintComponent method needs to know what to paint whenever it is called, as it may be called any number of times, many times without your interaction or knowledge.
One approach is to build a List of shapes or Points, which can then be looped through and painted each time paintComponent is called. The benefit of this is you can remove these shapes/points should you wish.
See Pinting in AWT and Swing and Performing Custom Painting for more detals
Also take a look at this example for an idea
The usual way of doing this is to create a (Buffered)Image the size of your Component, fill the background color, and then draw each new line on the Image as well. In your paintComponent method, all you call is g.drawImage(...);
In your panel:
public void paintComponent(Graphics g) {
if (mSizeChanged) {
handleResize();
}
g.drawImage(mImg, 0, 0, null);
}
In your MouseMotionListener:
public void mouseDragged(MouseEvent me) {
Graphics g = mImg.getGraphics();
Point p = me.getPoint();
g.drawLine(mLastPoint.x, mLastPoint.y, p.x, p.y); }