Thursday 29 December 2011

JBox2D With JavaFX : Write your first JBox2D with JavaFX 2 program

So far we have learned about JBox2D world, Bodies and world Simulation. With this we are ready to try our hands on JBox2D.

Today we will write one simple JBox2D application. In this application we will create one bouncy ball and release it into the JBox2D world. We will use JavaFX 2 for UI.

Our first step is to create JavaFX project. We will use NetBeans 7.1 for this. If you have not installed JavaFX SDK and NetBeans then you can download and install it form here. Once installed, open NetBeans IDE and follow the following steps

Click on File>New Project… menu. This will open “New Project” dialog box.
Select “JavaFX” under categories and then select JavaFX Application under projects
Click on “Next“ button.

In next window, type project name as “BouncyBallApp”.
Select project location.
Select “Create Application class” checkbox.
Select “Set as main project”
Click on finish.


This will create a project with main class “BouncyBallApp.java”

We will create two more class in this project – Utility.java and BouncyBall.java

Utility.Java – We will add all required constants and utility methods to this class.

We will add following constants to the Utility.java class
//Create a JBox2D world. 
    public static final World world = new World(new Vec2(0.0f, -10.0f), true);
    
    //Screen width and height
    public static final int WIDTH = 600;
    public static final int HEIGHT = 600;
    
    //Ball radius in pixel
    public static final int BALL_RADIUS = 8;


We will add method “addGround” to Utility class. This method will help us to add ground/platform for our JBox2D world. Ground is an essential component of our application. This is where our bouncy ball will land and bounce back. If we do not add ground ball will move infinitely downwards.

//This method adds a ground to the screen. 
public static void addGround(float width, float height){
    PolygonShape ps = new PolygonShape();
    ps.setAsBox(width,height);
        
    FixtureDef fd = new FixtureDef();
    fd.shape = ps;

    BodyDef bd = new BodyDef();
    bd.position= new Vec2(0.0f,-10f);

    world.createBody(bd).createFixture(fd);
}

As you can see we are using polygon shape for creating the ground. I will cover JBox2D shapes in detail in future post.

Next we will add method “addWall” to utility class. This method will allow us to add walls to our JBox2D world. Walls are required because we don’t want our bouncy ball to go outside the application viewable area.
//This method creates a walls. 
public static void addWall(float posX, float posY, float width, float height){
    PolygonShape ps = new PolygonShape();
    ps.setAsBox(width,height);
        
    FixtureDef fd = new FixtureDef();
    fd.shape = ps;
    fd.density = 1.0f;
    fd.friction = 0.3f;    

    BodyDef bd = new BodyDef();
    bd.position.set(posX, posY);
        
    Utils.world.createBody(bd).createFixture(fd);
}

Next we will add few utility methods which will allow us to convert JBox2D world coordinates to pixel coordinates and vise versa.
//Convert a JBox2D x coordinate to a JavaFX pixel x coordinate
public static float toPixelPosX(float posX) {
    float x = WIDTH*posX / 100.0f;
    return x;
}

//Convert a JavaFX pixel x coordinate to a JBox2D x coordinate
public static float toPosX(float posX) {
    float x =   (posX*100.0f*1.0f)/WIDTH;
    return x;
}

//Convert a JBox2D y coordinate to a JavaFX pixel y coordinate
public static float toPixelPosY(float posY) {
    float y = HEIGHT - (1.0f*HEIGHT) * posY / 100.0f;
    return y;
}

//Convert a JavaFX pixel y coordinate to a JBox2D y coordinate
public static float toPosY(float posY) {
    float y = 100.0f - ((posY * 100*1.0f) /HEIGHT) ;
    return y;
}

//Convert a JBox2D width to pixel width
public static float toPixelWidth(float width) {
    return WIDTH*width / 100.0f;
}

//Convert a JBox2D height to pixel height
public static float toPixelHeight(float height) {
    return HEIGHT*height/100.0f;
}


BouncyBall.java – Now we will create main actor of our application i.e. bouncy ball. First we will add class called as BouncyBall.java. We will add following attributes to this class.
//JavaFX UI for ball
    public Node node;
    
    //X and Y position of the ball in JBox2D world
    private float posX;
    private float posY;
    
    //Ball radius in pixels
    private int radius;

Then we will add method “private Node create()” to this class. The return type of method is set to javafx.scene.Node. We will use this method to create a ball. To create ball we will have to:

Create graphical representation of the ball and set its x,y position
//Create an UI for ball - JavaFX code
        Circle ball = new Circle();
        ball.setRadius(radius);
        ball.setFill(color); //set look and feel

        /**
         * Set ball position on JavaFX scene. We need to convert JBox2D coordinates 
         * to JavaFX coordinates which are in pixels.
         */
        ball.setLayoutX(Utils.toPixelPosX(posX)); 
        ball.setLayoutY(Utils.toPixelPosY(posY));

Create body definition of the ball i.e. ball body type (Dynamic in this case) & body position
//Create an JBox2D body defination for ball.
        BodyDef bd = new BodyDef();
        bd.type = BodyType.DYNAMIC;
        bd.position.set(posX, posY);

Create ball shape
CircleShape cs = new CircleShape();
        cs.m_radius = radius * 0.1f;  //We need to convert radius to JBox2D equivalent

Create fixture for ball
// Create a fixture for ball
        FixtureDef fd = new FixtureDef();
        fd.shape = cs;
        fd.density = 0.6f;
        fd.friction = 0.3f;        
        fd.restitution = 0.8f;
And finally we will create body, associate it with its graphical representation and return it.
/**
        * Virtual invisible JBox2D body of ball. Bodies have velocity and position. 
        * Forces, torques, and impulses can be applied to these bodies.
        */
        Body body = Utils.world.createBody(bd);
        body.createFixture(fd);
        ball.setUserData(body);
        return ball;

We will call this method from class constructor.
public BouncyBall(float posX, float posY, int radius, Color color){
        this.posX = posX;
        this.posY = posY;
        this.radius = radius;
        this.color = color;
        node = create();
    }

Finally BouncyBall.java should look like this:

package bouncyballapp;

import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;

/**
 *
 * @author dilip
 */
public class BouncyBall{

    //JavaFX UI for ball
    public Node node;
    
    //X and Y position of the ball in JBox2D world
    private float posX;
    private float posY;
    
    //Ball radius in pixels
    private int radius;
    
   
    private Color color;
    public BouncyBall(float posX, float posY, int radius, Color color){
        this.posX = posX;
        this.posY = posY;
        this.radius = radius;
        this.color = color;
        node = create();
    }
    
    /**
     * This method creates a ball by using Circle object from JavaFX and CircleShape from JBox2D
     */
    private Node create(){
        //Create an UI for ball - JavaFX code
        Circle ball = new Circle();
        ball.setRadius(radius);
        ball.setFill(color); //set look and feel 
        
        /**
         * Set ball position on JavaFX scene. We need to convert JBox2D coordinates 
         * to JavaFX coordinates which are in pixels.
         */
        ball.setLayoutX(Utils.toPixelPosX(posX)); 
        ball.setLayoutY(Utils.toPixelPosY(posY));
       
        //Create an JBox2D body defination for ball.
        BodyDef bd = new BodyDef();
        bd.type = BodyType.DYNAMIC;
        bd.position.set(posX, posY);
        
        CircleShape cs = new CircleShape();
        cs.m_radius = radius * 0.1f;  //We need to convert radius to JBox2D equivalent
        
        // Create a fixture for ball
        FixtureDef fd = new FixtureDef();
        fd.shape = cs;
        fd.density = 0.6f;
        fd.friction = 0.3f;        
        fd.restitution = 0.8f;

        /**
        * Virtual invisible JBox2D body of ball. Bodies have velocity and position. 
        * Forces, torques, and impulses can be applied to these bodies.
        */
        Body body = Utils.world.createBody(bd);
        body.createFixture(fd);
        ball.setUserData(body);
        return ball;
    }
    
}


Our final step is to modify main class i.e. BouncyBallApp.java class.

We will have to do following changes to “start()” method.

Set application title and application window size. Also set full screen and resizable properties to false so user will not be able to resize application window.
primaryStage.setTitle("Bouncy Ball");
        primaryStage.setFullScreen(false);
        primaryStage.setResizable(false);
        Group root = new Group(); //Create a group for holding all objects on the screen
        Scene scene = new Scene(root, Utils.WIDTH, Utils.HEIGHT);

Add ball, ground and walls
//create ball   
        final BouncyBall ball = new BouncyBall(45, 90, Utils.BALL_RADIUS, Color.RED);
         
        //Add ground to the application, this is where balls will land
        Utils.addGround(100, 10);
        
        //Add left and right walls so balls will not move outside the viewing area.
        Utils.addWall(0,100,1,100); //Left wall
        Utils.addWall(99,100,1,100); //Right wall

Add code for simulating the world.
final Timeline timeline = new Timeline();
        timeline.setCycleCount(Timeline.INDEFINITE);

        Duration duration = Duration.seconds(1.0/60.0); // Set duration for frame.
        
        //Create an ActionEvent, on trigger it executes a world time step and moves the balls to new position 
        EventHandler<actionevent> ae = new EventHandler<actionevent>() {
            public void handle(ActionEvent t) {
                        //Create time step. Set Iteration count 8 for velocity and 3 for positions
                        Utils.world.step(1.0f/60.f, 8, 3); 
                       
                        //Move balls to the new position computed by JBox2D
                        Body body = (Body)ball.node.getUserData();
                        float xpos = Utils.toPixelPosX(body.getPosition().x);
                        float ypos = Utils.toPixelPosY(body.getPosition().y);
                        ball.node.setLayoutX(xpos);
                        ball.node.setLayoutY(ypos);
           }
        };

                
         /**
         * Set ActionEvent and duration to the KeyFrame. 
         * The ActionEvent is trigged when KeyFrame execution is over. 
         */
        KeyFrame frame = new KeyFrame(duration, ae, null,null);

        timeline.getKeyFrames().add(frame);

Add code to start simulating the world on click of the button
//Create button to start simulation.
        final Button btn = new Button();
        btn.setLayoutX((Utils.WIDTH/2) -15);
        btn.setLayoutY((Utils.HEIGHT-30));
        btn.setText("Start");
        btn.setOnAction(new EventHandler<actionevent>() {
            public void handle(ActionEvent event) {
                        timeline.playFromStart(); 
                        btn.setVisible(false);
            }
        });

Finally add button and ball to the root group
root.getChildren().add(btn);
        root.getChildren().add(ball.node);
        primaryStage.setScene(scene);
        primaryStage.show();


Finally hit the “F6” to run the application.

<<Simulating JBox2D world                         Applying Force and Impulse on body>>

1 comment:

  1. great tutorial man ... You would not have this same tutorial but in JavaSE? With Java2D?

    ReplyDelete