How to smoothly zoom a canvas? - java

How could I smoothly create a zoom animation for a canvas?
GWT provides a onMouseWheel(MouseWheelEvent evt) method and a evt.getDeltaY() to get the amount of scroll wheel.
Problem here is, that every wheel movement executes this method, and if I call a canvas redraw in the method itself, things get very laggy.
So I thought of somehow making an animation for the zooming. But how?
I thought about creating a Timer, but have no real idea, as there is only the mousewheelevent as starting point, but no end point that the user finished zooming with the wheel...

Here is my scalable image class that I use to zoom into images, it is very quick and responsive. I found an example somewhere and made some minor modifications to make it more responsive. I don't remember the original source or I would give them credit here.
public class ScalableImage extends Composite implements MouseWheelHandler, MouseDownHandler, MouseMoveHandler, MouseUpHandler {
Canvas canvas = Canvas.createIfSupported();
Context2d context = canvas.getContext2d();
Canvas backCanvas = Canvas.createIfSupported();
Context2d backContext = backCanvas.getContext2d();
int width;
int height;
Image image;
ImageElement imageElement;
double zoom = 1;
double totalZoom = 1;
double offsetX = 0;
double offsetY = 0;
boolean mouseDown = false;
double mouseDownXPos = 0;
double mouseDownYPos = 0;
public ScalableImage(Image image) {
initWidget(canvas);
//width = Window.getClientWidth() - 50;
width = image.getWidth() + 200;
height = image.getHeight() + 200;
//canvas.setWidth(width + "px");
//canvas.setHeight(height + "px");
canvas.setCoordinateSpaceWidth(width);
canvas.setCoordinateSpaceHeight(height);
//backCanvas.setWidth(width + "px");
//backCanvas.setHeight(height + "px");
backCanvas.setCoordinateSpaceWidth(width);
backCanvas.setCoordinateSpaceHeight(height);
canvas.addMouseWheelHandler(this);
canvas.addMouseMoveHandler(this);
canvas.addMouseDownHandler(this);
canvas.addMouseUpHandler(this);
this.image = image;
this.imageElement = (ImageElement) image.getElement().cast();
mainDraw();
}
public void onMouseWheel(MouseWheelEvent event) {
int move = event.getDeltaY();
double xPos = (event.getRelativeX(canvas.getElement()));
double yPos = (event.getRelativeY(canvas.getElement()));
if (move < 0) {
zoom = 1.1;
} else {
zoom = 1 / 1.1;
}
double newX = (xPos - offsetX) / totalZoom;
double newY = (yPos - offsetY) / totalZoom;
double xPosition = (-newX * zoom) + newX;
double yPosition = (-newY * zoom) + newY;
backContext.clearRect(0, 0, width, height);
backContext.translate(xPosition, yPosition);
backContext.scale(zoom, zoom);
mainDraw();
offsetX += (xPosition * totalZoom);
offsetY += (yPosition * totalZoom);
totalZoom = totalZoom * zoom;
buffer(backContext, context);
}
public void onMouseDown(MouseDownEvent event) {
this.mouseDown = true;
mouseDownXPos = event.getRelativeX(image.getElement());
mouseDownYPos = event.getRelativeY(image.getElement());
}
public void onMouseMove(MouseMoveEvent event) {
if (mouseDown) {
backContext.setFillStyle("white");
backContext.fillRect(-5, -5, width + 5, height + 5);
backContext.setFillStyle("black");
double xPos = event.getRelativeX(image.getElement());
double yPos = event.getRelativeY(image.getElement());
backContext.translate((xPos - mouseDownXPos) / totalZoom, (yPos - mouseDownYPos) / totalZoom);
offsetX += (xPos - mouseDownXPos);
offsetY += (yPos - mouseDownYPos);
mainDraw();
mouseDownXPos = xPos;
mouseDownYPos = yPos;
}
}
public void onMouseUp(MouseUpEvent event) {
this.mouseDown = false;
}
public void mainDraw() {
backContext.drawImage(imageElement, 100, 100);
buffer(backContext, context);
}
public void buffer(Context2d back, Context2d front) {
front.beginPath();
front.clearRect(0, 0, width, height);
front.drawImage(back.getCanvas(), 0, 0);
}
}

Related

How can I make two objects of the same class bounce when colliding?

I made a bouncing ball code, and so far as the bouncing itself the code works perfectly. I then created a second ball, and it also does what it's supposed to do. However, when I try to use an if condition to make the two balls bounce off one another as well as the edges, it doesn't work. Either they don't move or they just don't collide, and go through each other. This code was made in processing. Can anyone help me make ball1 and ball2 collide?
Moving ball1;
Moving ball2;
void setup(){
size(600,600);
ball1 = new Moving();
ball2 = new Moving();
}
void draw(){
background(255);
ball1.move();
ball1.display();
ball1.bounce();
ball2.move();
ball2.display();
ball2.bounce();
ball1.clash();
ball2.clash();
}
class Moving {
float speed = 7;
float x = random(0, width);
float y= random(0, height);
float xdirection = 1;
float ydirection = 1;
float ball_size = 50;
float radius = ball_size/2;
Moving() {
}
void move() {
x = x + (xdirection * speed);
y = y + (ydirection* speed);
}
void display() {
noStroke();
fill(50, 0, 50);
circle(x, y, ball_size);
}
void bounce() {
if ((x >= width - radius) || (x <= radius)) {
xdirection = xdirection * -1;
}
if ((y >= height - radius)|| (y<=radius)) {
ydirection = ydirection * -1;
}
}
void clash() {
if ((ball1.y+radius == ball2.y+radius) && (ball1.x+radius == ball2.x+radius)) {
ball1.ydirection = ball1.ydirection * -1;
ball2.ydirection = ball2.ydirection * -1;
ball1.xdirection = ball1.xdirection * -1;
ball2.xdirection = ball2.xdirection * -1;
x = x + (xdirection * speed);
y = y + (ydirection* speed);
if (ball1.x+radius == ball2.x+radius) {
xdirection = xdirection * -1;
}
}
}
}

Midpoint Ellipse Algorithm in Java

I am trying to write a program to allow a user to draw an ellipse by using clicks. The user left-clicks at first to select the radius, then right-clicks to select the horizontal radius, then right-clicks again to select the vertical radius. Nothing is drawn after clicking. I don't understand where the error is.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Ellipse extends JPanel implements MouseListener{
Graphics P;
public Ellipse()
{
addMouseListener(this);
}
static int Radius = 0;
int CenterX, CenterY, RadiusX, RadiusY;
public void paintComponent(Graphics g)
{
P=g;
EllipseMidpoint(CenterX, CenterY, RadiusX, RadiusY);
}
public void EllipseMidpoint(int Cx, int Cy, int Rx, int Ry)
{
int Rx2 = Rx * Rx;
int Ry2 = Ry * Ry;
int twoRx2 = 2 * Rx2;
int twoRy2 = 2 * Ry2;
int x = 0;
int y = Ry;
int p;
int px= 0;
int py = twoRx2 * y;
PlotEllipsePoint(Cx, Cy, x, y);
//Region 1
p = (int)(Ry2 - (Rx2 * Ry) + (0.25 + Rx2));
while (px < py)
{
x = x + 1;
px = twoRy2 + px;
if (p < 0)
{
p = Ry2 + px + p;
}
else
{
y = y - 1;
py = twoRx2 - py;
p = Ry2 + px - py + p;
}
PlotEllipsePoint(Cx, Cy, x, y);
}
//Region2
p = (int)(Ry2 * (x + 0.5) * (x + 0.5) + Rx2 * (y - 1) * (y - 1) - Rx2 * Ry2);
while (y > 0)
{
y = y - 1;
py = twoRx2 - py;
if (p > 0)
{
p = Rx2 - py + p;
}
else
{
x = x + 1;
px = twoRy2 + px;
p = Rx2 + px - py + p;
}
PlotEllipsePoint(Cx, Cy, x, y);
}
}
public void PlotEllipsePoint(int CX, int CY, int X, int Y)
{
drawPixel(CX + X, CY + Y);
drawPixel(CX - X, CY + Y);
drawPixel(CX + X, CY - Y);
drawPixel(CX - X, CY - Y);
}
public void drawPixel(int x, int y)
{
P.fillOval(x, y, 5, 5);
}
public void mousePressed(MouseEvent e)
{
if (e.getButton() == MouseEvent.BUTTON1)
{
CenterX = e.getX();
CenterY = e.getY();
}
else if (e.getButton() == MouseEvent.BUTTON3)
{
Radius = Radius + 1;
if (Radius == 1)
{
RadiusX = (int) Math.pow((Math.pow((e.getX() - CenterX), 2) + Math.pow((e.getY() - CenterY), 2)), 0.5);
}
else if (Radius == 2)
{
RadiusY = (int) Math.pow((Math.pow((e.getX() - CenterX), 2) + Math.pow((e.getY() - CenterY), 2)), 0.5);
}
PlotEllipsePoint(CenterX, CenterY, RadiusX, RadiusY);
}
}
public static void main(String[] args)
{
JFrame JF = new JFrame("Ellipse");
JF.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JF.setSize(500,500);
Ellipse E = new Ellipse();
JF.getContentPane().add(E);
JF.setVisible(true);
}
public void mouseClicked(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
Three things immediately jump out...
You should be calling super.paintComponent before doing any custom painting (and there is no need for paintComponent to be public), see Performing Custom Painting and Painting in AWT and Swing for more details
NEVER, EVER maintain a reference to the graphics context, you want something painted, you make a request to the repaint manager and you wait until one of your paint methods is called. Painting is controlled by the repaint manager an repaints may occur at any time, most of the time without yout knowledge or interaction.
You never call repaint to ask the repaint manager to repaint your component...At the end of your mousePressed method, call repaint()...

Getting a line to move along the tangent line of a circle in Processing

I have a point following the path of a circle, and at a determined time, I want that point to "break" off and travel along the tangent line. How do I find this? I've been told that the derivative is
x = -sin(time)
and
y = -sin(time)
(not sure if I understand the "time" part of what I was told), but I don't see how that is enough to get my point to travel along this line. Any tips? Here is what I have currently.
/*
Rotor draws circle for random period of time, then moves
in a straight direction for a random period of time, beginning a
new circle
*/
Rotor r;
float timer = 0;
boolean freeze = false;
void setup() {
size(1000, 600);
smooth();
noFill();
frameRate(60);
background(255);
timeLimit();
r = new Rotor(100, 100, random(40, 100));
}
void draw() {
timer = timer + frameRate/1000;
if(timer > timeLimit()) {
timer = 0;
timeLimit();
if(freeze == true) {
freeze = false;
} else {
freeze = true;
}
}
if(!freeze) {
r.drawRotor();
} else {
r.holdSteady();
}
}
float timeLimit() {
float timeLimit = random(100);
return timeLimit;
}
Rotor Class:
class Rotor {
color c;
int thickness;
float xPoint;
float yPoint;
float nXPoint;
float nYPoint;
float radius;
float angle = 0;
float centerX;
float centerY;
float pointSpeed = frameRate/100;
Rotor(float cX, float cY, float rad) {
c = color(0);
thickness = 1;
stroke(c);
strokeWeight(thickness);
centerX = cX;
centerY = cY;
radius = rad;
}
void drawRotor() {
angle = angle + pointSpeed;
xPoint = centerX + cos(angle) * radius;
yPoint = centerY + sin(angle) * radius;
ellipse(xPoint, yPoint, thickness, thickness);
strokeWeight(2);
ellipse(centerX, centerY, 5, 5);
}
void holdSteady() {
xPoint = -sin(angle);//need tangent of circle
yPoint = -cos(angle);
ellipse(xPoint, yPoint, 4, 4);
//then set new center x and y
}
void drawNewRotor(float cX, float cy, float rad) {
}
}
You can use tan()
int f =100;
size(300,300);
stroke(0);
translate(width/2, height/2);
for(int i = 0; i< 360; i++){
point(cos(radians(i))*f,sin(radians(i))*f);
point(f,tan(radians(i))*f);
point(tan(radians(i))*f,f);
}

Java Animations

I've started to take interest with making animations(slideshows, backgrounds etc) in Java. I know that JavaFX is much better for doing this, but I'm just to stubborn to bother switching over.
Here is what I got so far.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BlurredLightCells extends JPanel {
private static final long serialVersionUID = 4610174943257637060L;
private Random random = new Random();
private ArrayList<LightCell> lightcells;
private float[] blurData = new float[500];
public static void main(String[] args) {
JFrame frame = new JFrame("Swing animated bubbles");
frame.setSize(1000, 750);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BlurredLightCells(60));
frame.setVisible(true);
}
public BlurredLightCells(int amtOfBCells) {
setSize(1000, 750);
/**
* Below we initiate all the cells that are going to be drawn on screen
*/
Arrays.fill(blurData, 1f / 20f);
lightcells = new ArrayList<LightCell>(amtOfBCells);
for (int i = 0; i < amtOfBCells; i++) {
/**
* Below we generate all the values for each cell(SHOULD be random for each one)
*/
int baseSpeed = random(0, 3);
int xSpeed = (int) Math.floor((Math.random() * (baseSpeed - -baseSpeed + baseSpeed)) + -baseSpeed);
int ySpeed = (int) Math.round((Math.random() * baseSpeed) + 0.5);
int radius = random(25, 100);
int x = (int) Math.floor(Math.random() * getWidth());
int y = (int) Math.floor(Math.random() * getHeight());
int blurrAmount = (int) (Math.floor(Math.random() * 10) + 5);
int alpha = (int) ((Math.random() * 15) + 3);
/**
* Now we draw a image, and apply transparency and a slight blur to it
*/
Kernel kernel = new Kernel(blurrAmount, blurrAmount, blurData);
BufferedImageOp op = new ConvolveOp(kernel);
BufferedImage circle = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
Graphics2D circlegfx = circle.createGraphics();
circlegfx.setColor(new Color(255, 255, 255, alpha));
circlegfx.fillOval(20, 20, radius, radius);
circle = op.filter(circle, null);
LightCell bubble = new LightCell(x, y, xSpeed, ySpeed, radius, getDirection(random.nextInt(3)), circle);
lightcells.add(bubble);
}
}
public int random(int min, int max) {
final int n = Math.abs(max - min);
return Math.min(min, max) + (n == 0 ? 0 : random.nextInt(n));
}
#Override
public void paint(Graphics g) {
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
long start = System.currentTimeMillis();
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
System.out.println("Took " + (System.currentTimeMillis() - start) + " milliseconds to draw ALL cells.");
repaint();
}
public String getDirection(int i) {
switch (i) {
case 0:
return "right";
case 1:
return "left";
case 2:
return "up";
case 3:
return "down";
}
return "";
}
private class LightCell {
private int x, y, xSpeed, ySpeed, radius;
private String direction;
private BufferedImage image;
public LightCell(int x, int y, int xSpeed, int ySpeed, int radius, String direction, BufferedImage image) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.radius = radius;
this.direction = direction;
this.image = image;
}
public void process(Graphics g) {
switch (direction) {
case "right":
moveRight();
break;
case "left":
moveLeft();
break;
case "up":
moveUp();
break;
case "down":
moveDown();
break;
}
g.drawImage(image, x, y, null);
}
private void moveUp() {
x += xSpeed;
y -= ySpeed;
if (y + (radius / 2) < 0) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = radius + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveDown() {
x += xSpeed;
y += ySpeed;
if (y - (radius / 2) > getHeight()) {
y = 0 - (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveRight() {
x += ySpeed;
y += xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x - radius / 2) > getWidth()) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveLeft() {
x -= ySpeed;
y -= xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x + radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
}
}
If you run that, you will see the cells move at a very high speed, and if you look through the code, you see I call repaint() in the paint method in which I override. I know thats not good to do. But my question is, is their any other way in which I could draw each cell outside of the repaint() loop I have right now, because that causes other components to flash/flicker when I use this in a JFrame with other components.
FYI: Aventually I'd like to achieve something similar to this: Click Here
Thanks!
The issue of flicker is to do with the fact that top level containers are not double buffered. Instead of extending from JFrame (or other top level containers), you should consider using something more like JPanel and override it's paintComponent.
nb- Had it in my head that the OP was extending from JFrame...
Two issues could be causing the flickering. The first is overriding paint, the second is not calling super.paint(g) and the time between the updates. A better solution would be to override paintComponent and make sure you are calling super.paintComponent. Also using something like a javax.swing.Timer to schedule updates and regular intervals would also help...
Only call repaint when you want to encourage the RepaintManager to update you component. Don't call repaint from within any paintXxx method, this will cause a never ending loop of paint requests to schedule onto the event queue, eventually consuiming your CPU
I would avoid doing anything in your paintXxx methods that might take time to perform, this will slow down the rendering process. Instead, I would use a javax.swing.Timer for simple updates or for more complicated processing, a Thread which could be used to update the model before it is rendered to the screen.
This is an example of some simple optimisation process I did to take animation of 500 objects to 4500 with only a slight degration in the overall performance.
Updated
I changed you code slight and it works fine...
I changed your paint method to paintComponent and added super.paintComponent(g)
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
}
And at the end of your constructor I added...
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
To update the UI on a regular bases...

Why is java application running smoother when moving mouse over it? Video included

I'm working with tutorial from this site - "Fixed timestep" section.
Here's the code - http://pastebin.com/QaHgcLaR
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameLoopTest extends JFrame implements ActionListener
{
private GamePanel gamePanel = new GamePanel();
private JButton startButton = new JButton("Start");
private JButton quitButton = new JButton("Quit");
private JButton pauseButton = new JButton("Pause");
private boolean running = false;
private boolean paused = false;
private int fps = 60;
private int frameCount = 0;
public GameLoopTest()
{
super("Fixed Timestep Game Loop Test");
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,2));
p.add(startButton);
p.add(pauseButton);
p.add(quitButton);
cp.add(gamePanel, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
setSize(500, 500);
startButton.addActionListener(this);
quitButton.addActionListener(this);
pauseButton.addActionListener(this);
}
public static void main(String[] args)
{
GameLoopTest glt = new GameLoopTest();
glt.setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
Object s = e.getSource();
if (s == startButton)
{
running = !running;
if (running)
{
startButton.setText("Stop");
runGameLoop();
}
else
{
startButton.setText("Start");
}
}
else if (s == pauseButton)
{
paused = !paused;
if (paused)
{
pauseButton.setText("Unpause");
}
else
{
pauseButton.setText("Pause");
}
}
else if (s == quitButton)
{
System.exit(0);
}
}
//Starts a new thread and runs the game loop in it.
public void runGameLoop()
{
Thread loop = new Thread()
{
public void run()
{
gameLoop();
}
};
loop.start();
}
//Only run this in another Thread!
private void gameLoop()
{
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);
while (running)
{
double now = System.nanoTime();
int updateCount = 0;
if (!paused)
{
//Do as many game updates as we need to, potentially playing catchup.
while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
{
updateGame();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES)
{
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
drawGame(interpolation);
lastRenderTime = now;
//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
if (thisSecond > lastSecondTime)
{
System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
fps = frameCount;
frameCount = 0;
lastSecondTime = thisSecond;
}
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
{
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
try {Thread.sleep(1);} catch(Exception e) {}
now = System.nanoTime();
}
}
}
}
private void updateGame()
{
gamePanel.update();
}
private void drawGame(float interpolation)
{
gamePanel.setInterpolation(interpolation);
gamePanel.repaint();
}
private class GamePanel extends JPanel
{
float interpolation;
float ballX, ballY, lastBallX, lastBallY;
int ballWidth, ballHeight;
float ballXVel, ballYVel;
float ballSpeed;
int lastDrawX, lastDrawY;
public GamePanel()
{
ballX = lastBallX = 100;
ballY = lastBallY = 100;
ballWidth = 25;
ballHeight = 25;
ballSpeed = 25;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
public void setInterpolation(float interp)
{
interpolation = interp;
}
public void update()
{
lastBallX = ballX;
lastBallY = ballY;
ballX += ballXVel;
ballY += ballYVel;
if (ballX + ballWidth/2 >= getWidth())
{
ballXVel *= -1;
ballX = getWidth() - ballWidth/2;
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballX - ballWidth/2 <= 0)
{
ballXVel *= -1;
ballX = ballWidth/2;
}
if (ballY + ballHeight/2 >= getHeight())
{
ballYVel *= -1;
ballY = getHeight() - ballHeight/2;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballY - ballHeight/2 <= 0)
{
ballYVel *= -1;
ballY = ballHeight/2;
}
}
public void paintComponent(Graphics g)
{
//BS way of clearing out the old rectangle to save CPU.
g.setColor(getBackground());
g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
g.fillRect(5, 0, 75, 30);
g.setColor(Color.RED);
int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
g.fillOval(drawX, drawY, ballWidth, ballHeight);
lastDrawX = drawX;
lastDrawY = drawY;
g.setColor(Color.BLACK);
g.drawString("FPS: " + fps, 5, 10);
frameCount++;
}
}
private class Ball
{
float x, y, lastX, lastY;
int width, height;
float xVelocity, yVelocity;
float speed;
public Ball()
{
width = (int) (Math.random() * 50 + 10);
height = (int) (Math.random() * 50 + 10);
x = (float) (Math.random() * (gamePanel.getWidth() - width) + width/2);
y = (float) (Math.random() * (gamePanel.getHeight() - height) + height/2);
lastX = x;
lastY = y;
xVelocity = (float) Math.random() * speed*2 - speed;
yVelocity = (float) Math.random() * speed*2 - speed;
}
public void update()
{
lastX = x;
lastY = y;
x += xVelocity;
y += yVelocity;
if (x + width/2 >= gamePanel.getWidth())
{
xVelocity *= -1;
x = gamePanel.getWidth() - width/2;
yVelocity = (float) Math.random() * speed*2 - speed;
}
else if (x - width/2 <= 0)
{
xVelocity *= -1;
x = width/2;
}
if (y + height/2 >= gamePanel.getHeight())
{
yVelocity *= -1;
y = gamePanel.getHeight() - height/2;
xVelocity = (float) Math.random() * speed*2 - speed;
}
else if (y - height/2 <= 0)
{
yVelocity *= -1;
y = height/2;
}
}
public void draw(Graphics g)
{
}
}
}
After run this code, the ball has kind of lag, but there is still 60 FPS. After I move mouse over application's window and move it in random directions, the ball is moving smoothly. It happens even if window application isn't focused! What's wrong? Can it be fixed?
I'm using Ubuntu 13.04 with Oracle JDK7.
I've found that it happens with every application. Similar things happens even in LWJGL application, but effect of the "lag" is much less than in swing application.
17 sec video showing my problem
http://www.youtube.com/watch?v=J8SBjKncgRw
I had same problem under Kubuntu 13.04
I googled something which works for me: http://www.java-gaming.org/index.php?topic=19224.0
The basic idea is to put Toolkit.getDefaultToolkit().sync(); after drawing something. In your code it should be after drawGame(interpolation);.
The explanation seems to be that the window system manages the update intervals, so it is not Java's fault and only occures with some window mangers.
The repaints should be triggered from a Swing based Timer, which ensures that GUI updates are called on the EDT. See Concurrency in Swing for more details.
This appears to be a bug in the VM since Java 6. I had the same problem and found an ugly, but simple workaround: Create a Robot object and let it press a key or position the mouse in each cycle. The animation will then run smoothly. Example:
final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// update your image...
robot.keyPress(62);
}
});
See also: Java animation stutters when not moving mouse cursor
You could invoke setIgnoreRepaint(true) and use a BufferStrategy for drawing that particular component instead. A BufferStrategy allows you to perform the drawing whenever you want. You can also invoke paintComponent methods inside your drawing method.

Categories

Resources