Wednesday 30 November 2011

Hello World JBox2D with JavaFX 2.0

<<JBox2D Tutorial


This is a simple application to get your hands wet with JBox2D and JavaFX 2. I have chosen JavaFX 2.0 for GUI due to its power and simplicity. In this application user can draw different shapes, these shapes works as a hurdles. When user clicks on the  “Start” button, balls starts falling, bouncing and reacting to the hurdles in a very natural way.

You can click here to launch this applications! If JavaFX 2 runtime is not installed on your  PC then it will prompt you for installation. Launch this application and try drawing different hurdles and see how balls reacts to them. You can also click here to download the source code. Please note this is my first program in JBox2D and JavaFX so it might not be following all coding standards. So, your suggestions and feedback will help me to improve on this, so please leave your comments and rating at the end of this post.

If you are not able to launch the application from above link for any reason then this is a sample video of Hello World JBox2D with JavaFX 2.0application.





These are few sample snapshots of Hello World JBox2D with JavaFX 2.0 application.






JBox2D world, Ground, walls and balls are the main component of this application.

JBox2D World is the main component of any JBox2D application. Memory, objects and simulations are managed by the JBox2D World object. Gravity and the doSleep are two parameters of World object. If doSleep is set to true JBox2D world will allow bodies to sleep when they come to rest.

Ground is a bottom most part of application screen. If ground is not created then balls will travel downwards infinitely. Also left and right walls are created so balls will not move out of the viewable screen area. 

These are the code snippets for Hello World JBox2D with JavaFX 2.0.  Please let me know if you have any queries on some part of code. I will be happy to answer. 

Ball.java


package jfxwithjbox2d;

import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
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 Ball{

    //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;
    
    /**
     * There are three types bodies in JBox2D – Static, Kinematic and dynamic 
     * In this application static bodies (BodyType.STATIC – non movable bodies) 
     * are used for drawing hurdles and dynamic bodies (BodyType.DYNAMIC–movable bodies) 
     * are used for falling balls
     */
    private BodyType bodyType;

    //Gradient effects for balls
    private LinearGradient gradient;
    
    public Ball(float posX, float posY){
        this(posX, posY, Utils.BALL_SIZE, BodyType.DYNAMIC,Color.RED);
        this.posX = posX;
        this.posY = posY;
    }

    public Ball(float posX, float posY, int radius, BodyType bodyType, Color color){
        this.posX = posX;
        this.posY = posY;
        this.radius = radius;
        this.bodyType = bodyType;
        this.gradient = Utils.getBallGradient(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(gradient); //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));
       
        ball.setCache(true); //Cache this object for better performance
        
        //Create an JBox2D body defination for ball.
        BodyDef bd = new BodyDef();
        bd.type = bodyType;
        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.9f;
        fd.friction = 0.3f;        
        fd.restitution = 0.6f;

        /**
        * 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;
    }
}


JFXwithJBox2d.java


package jfxwithjbox2d;

import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyType;

/**
 *
 * @author dilip
 */
public class JFXwithJBox2d extends Application {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Application.launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        
        primaryStage.setTitle("Hello JBox2d World!");
        primaryStage.setFullScreen(false);
        primaryStage.setResizable(false);
        
        final Group root = new Group(); //Create a group for holding all objects on the screen
        final Scene scene = new Scene(root, Utils.WIDTH, Utils.HEIGHT,Color.BLACK);
        
        //Ball array for hold the  balls
        final Ball[] ball = new Ball[Utils.NO_OF_BALLS];
                
        Random r = new Random(System.currentTimeMillis());
        
        /**
         * Generate balls and position them on random locations.  
         * Random locations between 5 to 95 on x axis and between 100 to 500 on y axis 
         */
        for(int i=0;i<Utils.NO_OF_BALLS;i++) {
            ball[i]=new Ball(r.nextInt(90)+5,r.nextInt(400)+100);
        }
     
        //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
        
        
        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
                       for(int i=0;i<Utils.NO_OF_BALLS;i++) {
                            Body body = (Body)ball[i].node.getUserData();
                            float xpos = Utils.toPixelPosX(body.getPosition().x);
                            float ypos = Utils.toPixelPosY(body.getPosition().y);
                            ball[i].node.setLayoutX(xpos);
                            ball[i].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);

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

        //Add button to the root group
        root.getChildren().add(btn);

        //Add all balls to the root group
        for(int i=0;i<Utils.NO_OF_BALLS;i++) {
            root.getChildren().add(ball[i].node);
        }
        
        //Draw hurdles on mouse event.
        EventHandler<MouseEvent> addHurdle = new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me) {
                    //Get mouse's x and y coordinates on the scene
                    float dragX = (float)me.getSceneX();
                    float dragY = (float)me.getSceneY();
                    
                    //Draw ball on this location. Set balls body type to static.
                    Ball hurdle = new Ball(Utils.toPosX(dragX), Utils.toPosY(dragY),2,BodyType.STATIC,Color.BLUE);
                    //Add ball to the root group
                    root.getChildren().add(hurdle.node);
            }
        };
        
        scene.setOnMouseDragged(addHurdle);
        
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}



Utils.java


import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

/**
 *
 * @author dilip
 */
public class Utils {
    //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 = 800;
    public static final int HEIGHT = 600;
    
    //Ball radius in pixel
    public static final int BALL_SIZE = 8;
    
    //Total number of balls
    public final static int NO_OF_BALLS = 400; 
    
    //Ball gradient
    private final static LinearGradient BALL_GRADIENT = new LinearGradient(0.0, 0.0, 1.0, 0.0, true, CycleMethod.NO_CYCLE, new Stop[] { new Stop(0, Color.WHITE), new Stop(1, Color.RED)});
    
    //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);
    }
    
    //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);
    }
    
    //This gives a look and feel to balls
    public static LinearGradient getBallGradient(Color color){
        if(color.equals(Color.RED))
            return BALL_GRADIENT;
        else
            return new LinearGradient(0.0, 0.0, 1.0, 0.0, true, CycleMethod.NO_CYCLE, new Stop[] { new Stop(0, Color.WHITE), new Stop(1, color)});
    }
   
    //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;
    }
 
}

Please try this application and let me know your feedback/queries. Let me know if you find it difficult to build and run this application, I will be happy to help you.

JavaFX 2 books



10 comments:

  1. Amazing, I will give a try to this library !!

    ReplyDelete
  2. Thanks Seb, let me know if you need any help.

    ReplyDelete
  3. I think this is the first example using JBox2D with JavaFX on the Internet. It is Clear and excellent explanation. I tried it and everything OK. But I tried to restart (add restart button) all over again by destroying the ball and creating the ball but it not worked. I also tried to change the gravity to positive but nothing happened. I would appreciate if you kindly give me suggestion on this matter.

    ReplyDelete
  4. Hi, nice examples as very few of JBOX2D examples are available over internet.
    I am having some problem when using JBOX2D with java for android.In my app, i am having a ball, which is moving and colliding to other physical objects.
    but now i want to move that ball according to device rotation by accelerometer. i am not being able to figure it out, how to change the balls direction or speed based on accelerometer returned values.

    when i searched over net, i found some examples for Box2D(not for JBOX2D):-

    public void onAccelerationChanged(final AccelerationData pAccelerationData) {
    gravity = Vector2Pool.obtain(pAccelerationData.getX(), pAccelerationData.getY());
    this.mPhysicsWorld.setGravity(gravity);
    body.applyForce(new Vector2(-pAccelerationData.getX(), -pAccelerationData.getY()), new Vector2(body.getWorldCenter()));
    Vector2Pool.recycle(gravity);
    }



    but i couldnt being able to do this ("Vector2Pool.recycle(gravity)") in JBOX2D. as i couldnt found any class like this "Vector2Pool".
    its very urgent dude, pls help if u can
    Any help is appriciated.

    ReplyDelete
    Replies
    1. Hi Ankur, Gravity is a key here. As I have not explored this API on Android, I am not sure what AccelerationData returns. But I am guessing that it will return current gravity positions. If so then you can try following code.

      Vec2 gravity = new Vec2(pAccelerationData.getX(), pAccelerationData.getY());
      world.setGravity(gravity)

      Let me know if it works

      Delete
  5. Hi thank you for your helpful guide.
    I'm JBox2D beginner.
    I'd try to simulate this example, but it doesn't show me the balls...
    Could I know what is wrong?

    ReplyDelete
    Replies
    1. Actually, I copied the codes and pasted on the NetBeans.

      Delete
  6. if i add rectangular the conversion doesn't work
    the image of the object is elevated from the flour and the ball junb on top of nothing

    ReplyDelete