Asked  7 Months ago    Answers:  5   Viewed   17 times

I am trying to write a Java application which draws multiple balls on screen which bounce off of the edges of the frame. I can successfully draw one ball. However when I add the second ball it overwrites the initial ball that I have drawn. The code is:

import java.awt.*;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;

public class Ball extends JPanel implements Runnable {

    List<Ball> balls = new ArrayList<Ball>();   
Color color;
int diameter;
long delay;
private int x;
private int y;
private int vx;
private int vy;

public Ball(String ballcolor, int xvelocity, int yvelocity) {
    if(ballcolor == "red") {
        color = Color.red;
    }
    else if(ballcolor == "blue") {
        color = Color.blue;
    }
    else if(ballcolor == "black") {
        color = Color.black;
    }
    else if(ballcolor == "cyan") {
        color = Color.cyan;
    }
    else if(ballcolor == "darkGray") {
        color = Color.darkGray;
    }
    else if(ballcolor == "gray") {
        color = Color.gray;
    }
    else if(ballcolor == "green") {
        color = Color.green;
    }
    else if(ballcolor == "yellow") {
        color = Color.yellow;
    }
    else if(ballcolor == "lightGray") {
        color = Color.lightGray;
    }
    else if(ballcolor == "magenta") {
        color = Color.magenta;
    }
    else if(ballcolor == "orange") {
        color = Color.orange;
    }
    else if(ballcolor == "pink") {
        color = Color.pink;
    }
    else if(ballcolor == "white") {     
        color = Color.white;
    }
    diameter = 30;
    delay = 40;
    x = 1;
    y = 1;
    vx = xvelocity;
    vy = yvelocity;
}

protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D)g;
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.setColor(color);
    g.fillOval(x,y,30,30); //adds color to circle
    g.setColor(Color.black);
    g2.drawOval(x,y,30,30); //draws circle
}

public void run() {
    while(isVisible()) {
        try {
            Thread.sleep(delay);
        } catch(InterruptedException e) {
            System.out.println("interrupted");
        }
        move();
        repaint();
    }
}

public void move() {
    if(x + vx < 0 || x + diameter + vx > getWidth()) {
        vx *= -1;
    }
    if(y + vy < 0 || y + diameter + vy > getHeight()) {
        vy *= -1;
    }
    x += vx;
    y += vy;
}

private void start() {
    while(!isVisible()) {
        try {
            Thread.sleep(25);
        } catch(InterruptedException e) {
            System.exit(1);
        }
    }
    Thread thread = new Thread(this);
    thread.setPriority(Thread.NORM_PRIORITY);
    thread.start();
}

public static void main(String[] args) {
    Ball ball1 = new Ball("red",3,2);
    Ball ball2 = new Ball("blue",6,2);
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.getContentPane().add(ball1);
    f.getContentPane().add(ball2);
    f.setSize(400,400);
    f.setLocation(200,200);
    f.setVisible(true);
    new Thread(ball1).start();
    new Thread(ball2).start();
}
}

I wanted to create a List of balls and then cycle through drawing each of the balls but I'm still having trouble adding both balls to the Content Pane.

Thanks for any help.

 Answers

89

With your current approach...

  • The main problem I can see is that you are placing two opaque components on top of each other...actually you may find you're circumventing one of them for the other...
  • You should be using a null layout manager, otherwise it will take over and layout your balls as it sees fit.
  • You need to ensure that you are controlling the size and location of the ball pane. This means you've taken over the role as the layout manager...
  • You need to randomize the speed and location of the balls to give them less chances of starting in the same location and moving in the same location...
  • Only update the Ball within the context of the EDT.
  • You don't really need the X/Y values, you can use the panels.

.

public class AnimatedBalls {

    public static void main(String[] args) {
        new AnimatedBalls();
    }

    public AnimatedBalls() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new Balls());
                frame.setSize(400, 400);
                frame.setVisible(true);
            }
        });
    }

    public class Balls extends JPanel {

        public Balls() {
            setLayout(null);
            // Randomize the speed and direction...
            add(new Ball("red", 10 - (int) Math.round((Math.random() * 20)), 10 - (int) Math.round((Math.random() * 20))));
            add(new Ball("blue", 10 - (int) Math.round((Math.random() * 20)), 10 - (int) Math.round((Math.random() * 20))));
        }
    }

    public class Ball extends JPanel implements Runnable {

        Color color;
        int diameter;
        long delay;
        private int vx;
        private int vy;

        public Ball(String ballcolor, int xvelocity, int yvelocity) {
            if (ballcolor == "red") {
                color = Color.red;
            } else if (ballcolor == "blue") {
                color = Color.blue;
            } else if (ballcolor == "black") {
                color = Color.black;
            } else if (ballcolor == "cyan") {
                color = Color.cyan;
            } else if (ballcolor == "darkGray") {
                color = Color.darkGray;
            } else if (ballcolor == "gray") {
                color = Color.gray;
            } else if (ballcolor == "green") {
                color = Color.green;
            } else if (ballcolor == "yellow") {
                color = Color.yellow;
            } else if (ballcolor == "lightGray") {
                color = Color.lightGray;
            } else if (ballcolor == "magenta") {
                color = Color.magenta;
            } else if (ballcolor == "orange") {
                color = Color.orange;
            } else if (ballcolor == "pink") {
                color = Color.pink;
            } else if (ballcolor == "white") {
                color = Color.white;
            }
            diameter = 30;
            delay = 100;

            vx = xvelocity;
            vy = yvelocity;

            new Thread(this).start();

        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;

            int x = getX();
            int y = getY();

            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.setColor(color);
            g.fillOval(0, 0, 30, 30); //adds color to circle
            g.setColor(Color.black);
            g2.drawOval(0, 0, 30, 30); //draws circle
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(30, 30);
        }

        public void run() {

            try {
                // Randamize the location...
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        int x = (int) (Math.round(Math.random() * getParent().getWidth()));
                        int y = (int) (Math.round(Math.random() * getParent().getHeight()));

                        setLocation(x, y);
                    }
                });
            } catch (InterruptedException exp) {
                exp.printStackTrace();
            } catch (InvocationTargetException exp) {
                exp.printStackTrace();
            }

            while (isVisible()) {
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    System.out.println("interrupted");
                }

                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            move();
                            repaint();
                        }
                    });
                } catch (InterruptedException exp) {
                    exp.printStackTrace();
                } catch (InvocationTargetException exp) {
                    exp.printStackTrace();
                }
            }
        }

        public void move() {

            int x = getX();
            int y = getY();

            if (x + vx < 0 || x + diameter + vx > getParent().getWidth()) {
                vx *= -1;
            }
            if (y + vy < 0 || y + diameter + vy > getParent().getHeight()) {
                vy *= -1;
            }
            x += vx;
            y += vy;

            // Update the size and location...
            setSize(getPreferredSize());
            setLocation(x, y);

        }
    }
}

The "major" problem with this approach, is each Ball has it's own Thread. This is going to eat into your systems resources real quick as you scale the number of balls up...

A Different Approach

As started by Hovercraft, you're better off creating a container for the balls to live in, where the balls are not components but are "virtual" concepts of a ball, containing enough information to make it possible to bounce them off the walls...

enter image description here

public class SimpleBalls {

    public static void main(String[] args) {
        new SimpleBalls();
    }

    public SimpleBalls() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Spot");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                Balls balls = new Balls();
                frame.add(balls);
                frame.setSize(400, 400);
                frame.setVisible(true);

                new Thread(new BounceEngine(balls)).start();

            }
        });
    }

    public static int random(int maxRange) {
        return (int) Math.round((Math.random() * maxRange));
    }

    public class Balls extends JPanel {

        private List<Ball> ballsUp;

        public Balls() {
            ballsUp = new ArrayList<Ball>(25);

            for (int index = 0; index < 10 + random(90); index++) {
                ballsUp.add(new Ball(new Color(random(255), random(255), random(255))));
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            for (Ball ball : ballsUp) {
                ball.paint(g2d);
            }
            g2d.dispose();
        }

        public List<Ball> getBalls() {
            return ballsUp;
        }
    }

    public class BounceEngine implements Runnable {

        private Balls parent;

        public BounceEngine(Balls parent) {
            this.parent = parent;
        }

        @Override
        public void run() {

            int width = getParent().getWidth();
            int height = getParent().getHeight();

            // Randomize the starting position...
            for (Ball ball : getParent().getBalls()) {
                int x = random(width);
                int y = random(height);

                Dimension size = ball.getSize();

                if (x + size.width > width) {
                    x = width - size.width;
                }
                if (y + size.height > height) {
                    y = height - size.height;
                }

                ball.setLocation(new Point(x, y));

            }

            while (getParent().isVisible()) {

                // Repaint the balls pen...
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        getParent().repaint();
                    }
                });

                // This is a little dangrous, as it's possible
                // for a repaint to occur while we're updating...
                for (Ball ball : getParent().getBalls()) {
                    move(ball);
                }

                // Some small delay...
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                }

            }

        }

        public Balls getParent() {
            return parent;
        }

        public void move(Ball ball) {

            Point p = ball.getLocation();
            Point speed = ball.getSpeed();
            Dimension size = ball.getSize();

            int vx = speed.x;
            int vy = speed.y;

            int x = p.x;
            int y = p.y;

            if (x + vx < 0 || x + size.width + vx > getParent().getWidth()) {
                vx *= -1;
            }
            if (y + vy < 0 || y + size.height + vy > getParent().getHeight()) {
                vy *= -1;
            }
            x += vx;
            y += vy;

            ball.setSpeed(new Point(vx, vy));
            ball.setLocation(new Point(x, y));

        }
    }

    public class Ball {

        private Color color;
        private Point location;
        private Dimension size;
        private Point speed;

        public Ball(Color color) {

            setColor(color);

            speed = new Point(10 - random(20), 10 - random(20));
            size = new Dimension(30, 30);

        }

        public Dimension getSize() {
            return size;
        }

        public void setColor(Color color) {
            this.color = color;
        }

        public void setLocation(Point location) {
            this.location = location;
        }

        public Color getColor() {
            return color;
        }

        public Point getLocation() {
            return location;
        }

        public Point getSpeed() {
            return speed;
        }

        public void setSpeed(Point speed) {
            this.speed = speed;
        }

        protected void paint(Graphics2D g2d) {

            Point p = getLocation();
            if (p != null) {
                g2d.setColor(getColor());
                Dimension size = getSize();
                g2d.fillOval(p.x, p.y, size.width, size.height);
            }

        }
    }
}

Because this is driven by a single thread, it is much more scalable.

You can also check out the images are not loading which is a similar question ;)

Tuesday, June 1, 2021
 
VitaCoco
answered 7 Months ago
82

There is a simple fix that works almost all the time: make your frame not resizable after having set visible. So only modifies your code this way:

public static void main(String[] args) {

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

    JFrame frame = new JFrame("Jedia");
    frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
    frame.setSize(screenSize);
    frame.setVisible(true);    // FIRST visible = true
    frame.setResizable(false); // THEN  resizable = false
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}

This way, the frame will start maximized and the maximize button will be greyed out, preventing user to use it. (I don't really know why you have to do this. I suppose the maximized state is really applied only when the window becomes visible, and if you make it unresizable before, it will not apply.)

It works almost all the time because on Windows 7 at least you can make the window goes out of the maximized state by clicking the title bar and dragging it. But it will be at the size you have set it earlier. Problem is that your user will not be able to maximize it again, and I haven't found the way with listeners to make the window back to maximized state. ( Edit: @David Kroukamp shows in the last part of his answer that it is possible to force the maximized state by using a ComponentListener. Therefore you don't have to use setResizable(false) This way you still have a problem with Windows 7 because the dragging action is not catched by this event for whatever reason but users will be able to use the maximized button to put it back where it should be.)

Now, there is almost never a reason to do this kind of things. Users don't really like when you prevent them to manipulate their windows (maximized windows can not be moved, for example, and that can be annoying when you have multiple screens). An exception is if you are making a game, which is typically full-screen. But then you wouldn't want a JFrame because you don't want all the decoration, but a Window.

If your problem is that the default window size is very small, it's normal. You have to put something in your frame first (some controls, buttons, what you want in your application), using layouts (that's important) then call the method pack() on your frame. It will chose a nice default size for your window.

Finally, a last word. I've put my example code in a main method as a shortcut, but you should always do Swing stuff in the Swing EDT by using SwingUtils.invokeLater().

Tuesday, August 17, 2021
 
Loui
answered 4 Months ago
39

Animating a bouncing ball

Below is an example of a simple bouncing ball. This answer is partly from another answer, but your question title is better suited to being found.

The Main Loop.

To animate you need to have a main loop. This is the function that is called once every time you need to update and draw everything you need onto the canvas. This update is normally called a frame. Then number of times the main loop is called a second is called the frame rate. The typical frame rate of HTML is 60 frames per second or lower. HTML5 provides a special event call requestAnimationFrame that is designed for animations and fires at the best time for you to run the main loop.

function mainLoop(){
    // clear the canvas
    // update the position of all the stuff in the animation

    // then request the next animation frame
    window.requestAnimationFrame( mainLoop ); // pase it the name of your main loop
    // the main loop is done. Exiting lets the browser know yo are done and 
    // it presents the redrawn canvas to the screen.
}

To start the animation just call the mainloop after you have everything set up

// starting the animation
mainLoop(); // call main loop

Request animation frame will do its best to maintain an even frame rate. It will also slow down if your mainloop takes to long.

Bouncing a ball

Below is an example of how to simulate a single bouncing ball and to correctly handle simple collisions will surfaces parallel to the canvas sides.

It demonstrates how to change the frame rate by switching between requestAnimationFrame and setTimeout to call the main loop function.

It has an example of drawing an image, and adding motion blur (NOTE motion blur is GPU (graphics processing unit) intensive and will not work well on large numbers of objects.)

Inter frame movement

The correct way to reflect an object from a surface.

You must take into account that the ball is moving between frames and that the collision may have happened at any time during the previous frame. The ball's distance from the wall after the collision is dependent on when during the previous frame it hit the wall. This is important if the ball moves slowly or quickly.

var dx = 10; // delta x velocity of object in pixels
var wx = 10; // width of object in pixels
var px = 90;  // position of object in pixels
var wallX = 105; // position of wall


px += dx;  // move the ball. Its position is now  100.
           // its right side is at px + wx = 110.
// test if it has it the wall
if(px+wx > wallX){
    dx = -dx; // reflect delta x
    // The object is 5 pixel into the wall.
    // The object has hit the wall some time during the last frame
    // We need to adjust the position as the ball may have been
    // traveling away from the wall for some time during the last frame.
    var dist = (px+wx)-wallX; // get the distance into the wall
    px -= dist*2; // the object hit the wall at position 95 and has been 
                  // traveling away since then so it is easy to just 
                  // subtract 2 times the distance the ball entered the wall
    // the above two lines can be done in one
    // px -= ((px+wx)-wallX)*2;
}

Why it matters

Below is a simulation of a ball bouncing inside the canvas.

To illustrate that the ball is moving between frames it has been motion blurred to show its motion between frames. Please note this is not the perfect solution as the bounce is assumed to occur while the ball is in linear motion while infact it is in freefall and under constant acceleration. But it still conserves energy.

In the correct test the height the ball bounces back to, stays around the same over time. No energy is lost or gained.

Right click to turn off the inter frame adjustment and you will notice that the ball begins to decrease its height each frame. This is because at each collision the ball loses a little energy because it motion during the previous frame is not taken into account when positioning it after the collision test. It will settle down to a constant rate when the collision occurres at precisely the frame time. When that will be is very hard to determine in advance.

Left click to slow the simulation frame rate, left click again to return to normal.

The code below is not really part of the answer, it is there to demonstrate the effect of not correctly adjusting the position during collision test on the overall accuracy of the simulation.

// helper functions. NOT part of the answer
var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");
var mouseButton = 0;
canvas.addEventListener('mousedown',function(event){mouseButton = event.which;});
canvas.addEventListener('mouseup'  ,function(){mouseButton = 0;});
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
var currentSurface = ctx;
var createImage = function (w, h) {// create an canvas image of size w,h and attach context 2d
    var image = document.createElement("canvas");  
    image.width = w;
    image.height = h !== undefined?h:w; 
    currentSurface = image.ctx = image.getContext("2d"); 
    return image;
}  
var setColour = function (fillC, strokeC, lineW) { 
    currentSurface.fillStyle = fillC !== undefined ? fillC : currentSurface.fillStyle;
    currentSurface.strokeStyle = strokeC !== undefined ? strokeC : currentSurface.strokeStyle;
    currentSurface.lineWidth = lineW !== undefined ? lineW : currentSurface.lineWidth;
}
var circle = function(x,y,r,how){
    currentSurface.beginPath();
    currentSurface.arc(x,y,r,0,Math.PI*2);
    how = how.toLowerCase().replace(/[os]/g,"l"); // how to draw
    switch(how){
        case "f":  // fill
            currentSurface.fill();
            break;
        case "l":
            currentSurface.stroke();
            break;
        case "lf":
            currentSurface.stroke();
            currentSurface.fill();
            break;
        case "fl":
            currentSurface.fill();
            currentSurface.stroke();
            break;
    }
}
function createGradImage(size,col1,col2){
    var image = createImage(size);
    var g = currentSurface.createLinearGradient(0,0,0,currentSurface.canvas.height);
    g.addColorStop(0,col1);
    g.addColorStop(1,col2);
    currentSurface.fillStyle = g;
    currentSurface.fillRect(0,0,currentSurface.canvas.width,currentSurface.canvas.height);    
    return image;
}
function createColouredBall (ballR,col) {
    var ball = createImage(ballR*2);
    var unit = ballR/100;
    setColour("black");
    circle(ballR,ballR,ballR,"f");
    setColour("hsl("+col+",100%,30%)");
    circle(ballR-unit*3,ballR-unit*3,ballR-unit*7,"f");
    setColour("hsl("+col+",100%,50%)");
    circle(ballR-unit*10,ballR-unit*10,ballR-unit*16,"f");
    setColour("White");
    circle(ballR-unit*50,ballR-unit*50,unit*16,"f");
    
    return ball;
}
//===================================    
//    _                          
//   /_  _ _  ____ __ _____ _ _ 
//  / _ | ' (_-< V  V / -_) '_|
// /_/ __||_/__/_/_/___|_|  
//                              
// ==================================
// Answer code

// lazy coder variables
var w = canvas.width;
var h = canvas.height;

// ball is simulated 5cm 
var pixSize = 0.24; // in millimeters for simulation

// Gravity is 9.8 ms^2 so convert to pixels per frame squared
// Assuming constant 60 frames per second. ()
var gravity = 9800*pixSize/60; 
gravity *= 0.101; // because Earth's gravity is stupidly large let's move to Pluto

// ball 5cm 
var ballR = (25/pixSize)/2;          // radius is 2.5cm for 5cm diamiter ball
var ballX = w/2;                     // get center of canvas
var ballY = ballR+3;                 // start at the top
var ballDX = (Math.random()-0.5)*15; // start with random x speed
ballDX += ballDX < 0 ? -5 : 5;       // make sure it's not too slow
var ballDY = 0;                      // star with no downward speed;
var ballLastX = ballX;
var ballLastY = ballY;

//create an image of the Ball
var ball = createColouredBall(ballR,Math.floor(Math.random()*360)); // create an image of ball

// create a background. Image is small as it does not have much detail in it
var background = createGradImage(16,"#5af","#08C");
// time to run for


// Function to draw ball without motion blur
// draws the ball with out motion blurred. 
// image is the image to draw
// px and py are the x and y position to draw the ball
var drawImage = function(image , px, py){
    ctx.drawImage(image, px, py);
}


// draws the ball motion blurred. This introduces extra complexity
var drawMotionBlur = function(image, px, py, dx, dy, steps){
    var i, sx, sy;
    sx = dx / steps;
    sy = dy / steps;
    px -= dx; // move back to start position
    py -= dy; 
    ctx.globalAlpha = 1 / (steps * 0.8); // set alpha to slightly higher for each step
    for(i = 0; i < steps; i+= 1){
        ctx.drawImage(image, px + i * sx, py + i * sy);
    }
    ctx.globalAlpha = 1; // reset alpha
    
}
// style for text
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.textAlign = "center";
ctx.lineJoin = "round"; // stop some letters getting ears.
ctx.lineWidth = 3;
ctx.textBaseline = "bottom";
var textCenterX = w/2;
var maxHeight = Infinity;
var lastMaxHeight = ballY;
var slowMotion = false;  // slow motion flag
var frameTravel = true;  // use frame travel in collision test 
const bSteps = 10;  // the fixed motion blur steps
var update = function(){
    var str, blurSteps;
    blurSteps = 10;  // motion blur ball render steps. This varies depending on the the collision inter frame time. 
     
    if(mouseButton === 1){
        slowMotion = ! slowMotion;
        mouseButton = 0;
    }
    if(mouseButton === 3){
        frameTravel = ! frameTravel;
        ballX = w / 2;                     // get center of canvas
        ballY = ballR + 3;                 // start at the top
        ballDY = 0;                      // start at 0 y speed
        mouseButton = 0;
    }
    // clear the canvas with background canvas image
    ctx.drawImage(background, 0, 0, w, h);
    
    ballDY += gravity; // acceleration due to grav
    // add deltas to ball position
    ballX += ballDX; 
    ballY += ballDY;
    // test for collision on left and right walls. Need to 
    // adjust for motion blur
    if (ballX < ballR) {
        ballDX = -ballDX; // refect delta x
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - ballR) / -ballDX));
            // get position it should have traveled since
            ballX -= (ballX - ballR) * 2;
        }else{
            ballX = ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    } else
    if (ballX > w - ballR) {
        ballDX = -ballDX;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - (w - ballR)) / -ballDX));
            ballX -= (ballX - (w - ballR)) * 2;
        }else{
            ballX = w - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }

    // Test ball hit ground
    if (ballY > h - ballR) {
        ballDY = -ballDY;
        // to show max height
        lastMaxHeight = maxHeight;
        maxHeight = Infinity;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballY - (h - ballR)) / -ballDY));
            ballY -= (ballY - (h - ballR)) * 2;
        }else{
            ballY = h - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }     
   
    // draw the ball motion blured
    drawMotionBlur(
        ball,                    // image to draw
        ballX - ballR,             // offset radius
        ballY - ballR,
        ballDX * (blurSteps / bSteps),  // speed and adjust for bounced
        ballDY * (blurSteps / bSteps),
        blurSteps                // number of blurs
    );

    // show max height. Yes it is min but everything is upside down.
    maxHeight = Math.min(maxHeight,ballY);
    lastMaxHeight = Math.min(ballY,lastMaxHeight);

    // show max height
    ctx.font = "12px arial black";
    ctx.beginPath();
    ctx.moveTo(0, lastMaxHeight - ballR);
    ctx.lineTo(w, lastMaxHeight - ballR);
    ctx.stroke();
    ctx.fillText("Max height.", 40, lastMaxHeight - ballR + 6);


    str = ""; // display status string
    if(slowMotion){   // show left click help
        str += "10fps."
        ctx.fillText("click for 60fps.", textCenterX, 43);
    }else{
        str += "60fps."
        ctx.fillText("click for 10fps.", textCenterX, 43);
    }

    if(frameTravel){ // show mode and right click help
        str += " Mid frame collision.";
        ctx.fillText("Right click for Simple collision", textCenterX,55);
    }else{
        str += " Simple collision.";
        ctx.fillText("Right click for mid frame collision", textCenterX,55);
    }

    // display help text
    ctx.font = "18px arial black";  
    ctx.strokeText(str, textCenterX, 30);
    ctx.fillText(str, textCenterX, 28);

    if(slowMotion){
        setTimeout(update, 100); // show in slow motion
    }else{
        requestAnimationFrame(update); // request next frame (1/60) seconds from now
    }

    // all done
}
update(); // to start the ball rolling
.canC { width:500px;  height:500px;}
<canvas class="canC" id="canV" width=500 height=500></canvas>
Monday, August 30, 2021
 
rob mayoff
answered 4 Months ago
72

There are many things wrong with your code.

It starts with wrong comparison of strings ( see here ).

But your actual problem: you are sleeping the event dispatcher thread ( see there )

So your idea of sleeping with an event listener is simply the wrong approach. You can't do it this way. You have to re-think your approach there.

And the real answer here is: you are lacking basic knowledge of Java. Consider learning such basic things first before further overburdening yourself if Swing UI coding.

Your requirement seems to be: after that button was hit - and a certain amount of time passed you want to print something on the console. You can do that like:

public void actionPerformed(ActionEvent event) {
   String command = event.getActionCommand();
   command = event.getActionCommand();
   Runnable printChoice = new Runnable() {
     @Override
     public void run() {
      try{
        Thread.sleep(500);
        if ("choice1".equals(command)){
            System.out.println("choice1");
        }
        else if("choice2".equals(command)){
            System.out.println("choice2");
        }
        else{
            System.out.println("no choice");
        }
      }   
      catch(InterruptedException ex){
        Thread.currentThread().interrupt();
      }
    });
    new Thread(printChoice).start();
}

The above:

  • creates a Runnable that simply waits to then print something
  • uses a new Thread (not the event dispatcher thread) to do that

I didn't run this through the compiler - it is meant as "pseudo code" to give you some ideas how to solve your problem.

But further thinking about this - I think this is going in the wrong direction. You don't want that anything is waiting when you develop a reasonable UI application.

In other words: you think of:

  • button A is clicked - some code starts "looping"
  • when the user makes another choice, that "looping" code notices that and does something

Wrong approach. Instead work along these lines:

  • user clicks a button, and maybe that enables other UI components
  • user does something with those other UI components - triggering other actions

Example: changing radiobuttons causes events to be fired. You should not have a loop that regularly keeps checking those buttons. Instead you define a clear flow of actions/events the user has to follow in order to trigger a certain action.

Tuesday, August 31, 2021
 
geocodezip
answered 4 Months ago
94
  1. Yes, I believe that should be correct
  2. Restitution and friction can be set per object by supplying them to the btRigidBodyConstructionInfo object passed into the btRigidBody constructor

For example:

btBoxShape * box = new btBoxShape(0.5f,0.5f,0.5f);
btVector3 inertia;
float mass = 10.0f;
box->calculateLocalInertia(mass,inertia);
btRigidBodyConstructionInfo info(10.0f,null,mass,inertia); //motion state would actually be non-null in most real usages
info.m_restitution = 1.3f;
info.m_friction = 1.5f;
btRigidBody * rb = new btRigidBody(info);
Wednesday, November 24, 2021
 
Steven Frank
answered 2 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share