I tried to make double buffered graphics for my canvas but it always disappears right after rendering, and sometimes it doesn't even render, Here is the code:
package initilizer;
import java.awt.AWTException;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import input.Keyboard;
public class Main extends Canvas{
static int width = 800;
static int height = 600;
int cx = width/2;
int cy = height/2;
boolean initilized = false;
double FOV = 0.5 * Math.PI;
Camera cam = new Camera(1.0, 5.0, 3.0);
Camera cam1 = new Camera(10.0, 50.0, 30.0);
long lastFpsCheck = System.currentTimeMillis();
public static JFrame frame = new JFrame("3D Engine");
Robot robot;
static Keyboard keyboard = new Keyboard();
Image img;
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas canvas = new Main();
canvas.setSize(width, height);
canvas.addKeyListener(keyboard);
canvas.setFocusable(true);
canvas.setBackground(Color.black);
frame.add(canvas);
frame.pack();
frame.setVisible(true);
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
// Set the blank cursor to the JFrame.
canvas.setCursor(blankCursor);
}
void init() {
try {
robot = new Robot();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
double[] rotate2D(double[] pos,double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
public void paint(Graphics MainGraphics) {
Point startMousePos = MouseInfo.getPointerInfo().getLocation();
double startMouseX = startMousePos.getX();
double startMouseY = startMousePos.getY();
if(img == null)
{
img = createImage(width, height);
}
Graphics g = img.getGraphics();;
// First run initialization
if (initilized == false) {
initilized = true;
init();
}
// Storing start time for FPS Counting
long startTime = System.currentTimeMillis();
// Clearing Last Frame
//g.clearRect(0, 0, width, height);
// Drawing Crosshair
g.setColor(Color.white);
g.fillRect(cx - 8, cy - 1, 16, 2);
g.fillRect(cx - 1, cy - 8, 2, 16);
// Drawing Debugger Menu
g.drawString("HI WASSUp", 0, 16);
g.dispose();
if (frame.isFocused() == true) {
robot.mouseMove(cx, cy);
Point endMousePos = MouseInfo.getPointerInfo().getLocation();
double endMouseX = endMousePos.getX();
double endMouseY = endMousePos.getY();
double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
cam.mouseMotion(rel);
}
// Limiting FPS
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Calculating FPS
long endTime = System.currentTimeMillis();
double delta_time = (endTime - startTime);
if ((lastFpsCheck + 1000) < endTime) {
lastFpsCheck = endTime;
frame.setTitle("3D Engine - FPS: " + (int) (1000/delta_time));
}
// Controlling camera movement
if (keyboard.getW() == true) {
cam.update(delta_time, "W");
}
if (keyboard.getA() == true) {
cam.update(delta_time, "A");
}
if (keyboard.getS() == true) {
cam.update(delta_time, "S");
}
if (keyboard.getD() == true) {
cam.update(delta_time, "D");
}
if (keyboard.getE() == true) {
cam.update(delta_time, "E");
}
if (keyboard.getQ() == true) {
cam.update(delta_time, "Q");
}
// Draw rendered frame
MainGraphics.drawImage(img, 0,0, null);
// Draw next frame
repaint();
}
}
I posted a question recently about this code, You could check keyboard java from that last post if you wanted to, But please help me with this I'm new to java programming (I still have some programming experience tho), Thank you
The answer to your question is complicated.
JPanel
(or JComponent
) are double buffered by defaultjava.awt.Canvas
is if you want to take complete control over the painting processThe first thing I would suggest you do is take a look at Performing Custom Painting and Painting in AWT and Swing to get a better idea of how painting works in Swing/AWT. This will provide you a better understanding of the API and whether you want to work with it or define your own.
Some other areas of concern:
Thread.sleep(1000);
in the paint
method, nothing will be rendered until AFTER the paint
method returns. You want to seperate the "update pass" from the "paint pass". Painting does nothing else by paints. All you decision making should be done as part of your "update pass" from your "main loop", which should be executed (in this case) off the Event Dispatching Thread, so as to prevent possible issues, but which then raises a bunch of other issues MouseInfo.getPointerInfo().getLocation()
is NOT how you should be tracking the position of the mouse. Swing already provides a number of mechanisms for tracking the mouse events. See How to write a Mouse Listener and How to Write a Mouse-Motion Listener for more details.The following example simply uses a JPanel
as it's primary rendering surface. It takes advantage of the pre-existing painting process and uses a Swing Timer
as the "main loop" mechanism, which is responsible for processing the user input and update the state before scheduling a new paint pass.
Remember, Swing is NOT thread safe and you should not update the UI or anything the UI might depend on, from outside the context of the Event Dispatching Thread.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
Main main = new Main();
frame.add(main);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
main.start();
}
});
}
// Decouple the input from the implementation
enum Input {
UP, DOWN, LEFT, RIGHT
}
public class Main extends JPanel {
boolean initilized = false;
double FOV = 0.5 * Math.PI;
private Instant lastFpsCheck = Instant.now();
private Point mousePosition;
private Timer timer;
private Set<Input> input = new HashSet<>();
public Main() {
MouseAdapter mouseHandler = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
// This is within the components coordinate space
mousePosition = e.getPoint();
}
@Override
public void mouseEntered(MouseEvent e) {
mousePosition = e.getPoint();
}
@Override
public void mouseExited(MouseEvent e) {
mousePosition = null;
}
};
addMouseMotionListener(mouseHandler);
addMouseListener(mouseHandler);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
actionMap.put("Pressed.up", new InputAction(input, Input.UP, true));
actionMap.put("Released.up", new InputAction(input, Input.UP, false));
actionMap.put("Pressed.down", new InputAction(input, Input.DOWN, true));
actionMap.put("Released.down", new InputAction(input, Input.DOWN, false));
actionMap.put("Pressed.left", new InputAction(input, Input.LEFT, true));
actionMap.put("Released.left", new InputAction(input, Input.LEFT, false));
actionMap.put("Pressed.right", new InputAction(input, Input.RIGHT, true));
actionMap.put("Released.right", new InputAction(input, Input.RIGHT, false));
timer = new Timer(15, new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
update();
}
});
}
public void start() {
startTime = Instant.now();
timer.start();
}
public void stop() {
timer.stop();
}
// The start time of a given cycle
private Instant startTime;
// The estimated number of frames per second
private double fps = 0;
// The number of acutal updates performed
// within a given cycle
private int updates = 0;
protected void update() {
if (startTime == null) {
startTime = Instant.now();
}
if (input.contains(Input.UP)) {
//cam.update(delta_time, "W");
}
if (input.contains(Input.LEFT)) {
//cam.update(delta_time, "A");
}
if (input.contains(Input.DOWN)) {
//cam.update(delta_time, "S");
}
if (input.contains(Input.RIGHT)) {
//cam.update(delta_time, "D");
}
// Don't know what these do, so you will need to devices
// your own action
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "E");
//}
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "Q");
//}
Instant endTime = Instant.now();
Duration deltaTime = Duration.between(startTime, endTime);
if (lastFpsCheck.plusSeconds(1).isBefore(endTime)) {
System.out.println(deltaTime.toMillis());
lastFpsCheck = endTime;
fps = updates;
updates = 0;
startTime = Instant.now();
}
updates++;
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
double[] rotate2D(double[] pos, double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Point startMousePos = MouseInfo.getPointerInfo().getLocation();
//double startMouseX = startMousePos.getX();
//double startMouseY = startMousePos.getY();
Graphics2D g2d = (Graphics2D) g.create();
// Drawing Debugger Menu
g2d.drawString("HI WASSUp", 0, 20);
if (mousePosition != null) {
g2d.drawString(mousePosition.x + "x" + mousePosition.y, 0, 40);
// Your old code is broken, because MouseInfo.getPointerInfo
// doesn't give you the position of the mouse from within
// the components coordinate space, but in the screen space
// instead
//robot.mouseMove(cx, cy);
//Point endMousePos = MouseInfo.getPointerInfo().getLocation();
//double endMouseX = endMousePos.getX();
//double endMouseY = endMousePos.getY();
//double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
//cam.mouseMotion(rel);
}
g2d.drawString(Double.toString(fps), 0, 60);
StringJoiner sj = new StringJoiner(", ");
for (Input item : input) {
switch (item) {
case DOWN:
sj.add("down");
break;
case UP:
sj.add("up");
break;
case LEFT:
sj.add("left");
break;
case RIGHT:
sj.add("right");
break;
}
}
g2d.drawString(sj.toString(), 0, 80);
g2d.dispose();
}
public class InputAction extends AbstractAction {
private final Set<Input> input;
private final Input direction;
private final boolean add;
public InputAction(Set<Input> input, Input direction, boolean add) {
this.input = input;
this.direction = direction;
this.add = add;
}
@Override
public void actionPerformed(ActionEvent evt) {
if (add) {
input.add(direction);
} else {
input.remove(direction);
}
}
}
}
}
Now, because of the way Swing's paint process works, the FPS is at best a "guesstimate" and I'd personally no rely to heavy on it. I might consider setting the Timer
to use a 5 millisecond delay instead and just go as fast as you can.
Now, if you absolutely, positively must have, without question, complete control over the painting process, then you will need to start with a java.awt.Canvas
and make use of the BufferStrategy
API.
This will give you complete control over the painting process. It's more complicated and will require you to take into consideration more edge cases, but will provide you with complete control over scheduling when a paint pass occurs and thus, better control over the FPS.
I would recommend having a look at the JavaDocs as the example is better.
I used the Thread.sleep(1000); to limit FPS only, It was 1000/60 but I changed it to this because I thought the problem may be in the speed of rendering
This is, to be frank, is a naive approach and demonstrates a lack of understanding with how the painting process works - no offensive, you've got to start somewhere. But a better place to start would be by reading the available documentation, which I've provided above so you can gain a better understanding of how the API actually works and make better decisions about whether you want to use it (ie JPanel
) or roll your own (ie Canvas
)