Posts
Search
Contact
Cookies
About
RSS

Using Dyn4j with javafx

Added 12 Aug 2015, 5:37 p.m. edited 19 Jun 2023, 2:58 a.m.
Dyn4j is a pure java 2D "physics" library to breath dynamic motion and collision into your 2D Java creations. While you can build it yourself, its probably easier to just use the precompiled Jar. Another option is to simply include the source into your project - while it does use Maven as its build process, fortunatly unlike other projects the source code is properly seperated from examples, tests and so forth, and not mired in the behemoth that is Maven! The result of this is that you can just drop the src/org folder into your project and it will just compile and work without fuss! One advantage of this is that when you decide to update Dyn4j in your project you can clone its repo elsewhere and then use a tool like meld so you can understand in detail how the changes will impact your work. Anyhow lets get into a bit of detail about using Dyn4j with JavaFX... Its not at all unusual that a dynamics library and a graphics library might use a different orientation, fortunatly its trivial to add a few simple transformations to your main Pane and forget there was ever any difference
mainPane = new Pane();
Scale s = new Scale(1, -1);
Translate t = new Translate(Settings.SCENE_WIDTH/2,-Settings.SCENE_HEIGHT);
mainPane.getTransforms().addAll(s,t);
applying a negative scale simply flips without rotating, and the translation puts the origin at the centre of the bottom of the pane. I've made (no suprise) a simple class to wrap together the dynamic and graphic elements together. This keeps everything together and makes it all a bit easier to work with. Occasionally we may want an invisible object that will effect objects, for example in the sample (downloadable below) the "floor" is just off the bottom of the screen...
// create the floor
Rectangle floorRect = new Rectangle(15.0, 1.0);
PhysObj floor = new PhysObj(); // invisible no image...
floor.addFixture(new BodyFixture(floorRect));
floor.setMass(Mass.Type.INFINITE);
        
// move the floor down a bit
floor.translate(0.0, -1.0);
this.world.addBody(floor);
Our PhysObj which extends the physics Body class needs to have at least one fixture attached to it so that it can collide with other objects, giving the floor an infinite mass makes it completely immovable. The final step is to add the floor to the world, arguably something that should be added to PhysObj but then there's a bunch of other functionallity that's missing...
for (int i=0; i<32; i++) {
    PhysObj rectangle = new PhysObj(img);
    BodyFixture f = new BodyFixture(rectShape);
    f.setDensity(1.2);
    f.setFriction(0.8);
    f.setRestitution(0.4);
    rectangle.addFixture(f);
    rectangle.setMass();
    rectangle.translate(rnd(-3,3), 9.0+rnd(-4,2));
    rectangle.getTransform().setRotation(rnd(-3.141,3.141));
    this.world.addBody(rectangle);
}
Creating a visual object is simple just create the PhysObj with an image - rectShape has already been created as a 1x1 rectangle, which will fit the image size of 64x64 (more about the scaling later). Setting properties like density, friction and restitution (bounciness!) is dead easy and I'd encourage you to mess around with these parameters (even creating different groups of objects), like any other dynamics library some care should be taken while assigning extreme values to these properties. The bodies (PhysObj) setMass will use the shape and density to calculate the bodies mass for you.
world.updatev(delta); 
PhysObj.update();
In the animation loop of the demo I'm using updatev to update the physics as while JavaFX's AnimationTimer is quite stable its not guarenteed and will occasionally vary in delta time. We'll look next at the static PhysObj's update method which updates all the visual elements to reflect the objects new positions
Iterator<PhysObj> pi = bodies.iterator();
while (pi.hasNext()) {
    PhysObj po = pi.next();
    if (po.iv != null) { // only attemt to update if it has a visual
        Transform t = po.getTransform();
        po.iv.setTranslateX(t.getTranslationX()*Settings.SCALE);
        po.iv.setTranslateY(t.getTranslationY()*Settings.SCALE);
        po.iv.setRotate(po.transform.getRotation()/0.017453292519943295);
    }
}
The physics coordinates are scaled (by a factor of 64) this means the 64x64 pixel image is 1x1 physics "metres" When rotating the ImageView the physics transforms angle (in radians) must be converted to degrees as this is what JavaFX is expecting (its almost a certainty that it is converted back to radians inside JavaFX...) While this simple PhysObj class only handles the bare minimum, it could easily be turned into a more fully featured physics factory... the Dyn4j API is clear and easy to use, having no other dependencies that Java itself it makes a great fit for JavaFX as any JDK that implements JavaFX will allow your application to be deployed without need for multiple native libraries per platfrom you wish to support. as per usual you can grab the sample here NB There is a bug with this example that show's up only when using non-uniform sized shapes - I've left the bug as an exersise for the reader (hint - where is the image origin... and how could this effect things)