May 02, 2010

Isometric tile rendering in JavaFX

1) Introduction


I'm currently working on a strategy game in JavaFX and decided to use isometric projection for rendering the graphics. During development I encountered some performance issues specific to JavaFX 1.2. It was expensive to add many nodes in a single Group and it was furthermore expensive to add/remove a lot of nodes from a Group.

The first issue was resolved by having more levels of Groups. Instead of 1 Group containing all the nodes, I added several layers of Groups. Each Group in the bottom layer would then only hold a small portion of all the nodes.

Resolving the second issue was done by using a quadtree to quickly decide which nodes should be added and which nodes should be removed from the scene graph. This way I only had to add/remove a small portion from the scene graph during the render pass.

Of course, a few weeks ago, Oracle released JavaFX 1.3, which magically solved all these problems by itself. Rendering many nodes in one Group no longer was a problem. However, having the quadtree structure still has a major advantage. You only need to render the nodes that are actually visible on the screen. This should eat a lot less memory then when you would render all the nodes in your world. So, I'm still going to show you how I've done the quadtree implementation.

2) Brute force rendering


Before going over to using quadtrees, I will first show you the easy way: rendering all tiles in a brute force manner. My game world consists of a series of tiles laid out in a two-dimensional grid. Each tile has an X and a Y position to store it's position within that grid. Rendering brute force means that we will render all the tiles in the game world, even when they are not visible on screen.

var scene : Scene = Scene {
width : 1000
height : 800
content : BruteForceRenderer {
tilesWidth : 512
}
}

Stage {
scene : scene
}

def TILE_WIDTH = 32;
def TILE_HEIGHT = 16;

public class BruteForceRenderer extends CustomNode {
postinit {
var numberTiles = Math.pow(tilesWidth, 2);
tilesGroup.content = [
for (i in [0 .. numberTiles - 1]) {
var x = (i / tilesWidth) as Integer;
var y = (i mod tilesWidth) as Integer;
Polygon {
points : [ TILE_WIDTH / 2, 0.0, TILE_WIDTH, TILE_HEIGHT / 2, TILE_WIDTH / 2, TILE_HEIGHT, 0.0, TILE_HEIGHT / 2 ]
translateX : TILE_WIDTH / 2 * (x - y)
translateY : TILE_HEIGHT / 2 * (x + y)
fill : Color.GREEN
}
}
];
}

public-init var tilesWidth : Number = 64;

var tilesGroup : Group = Group {};

override protected function create () : Node {
tilesGroup
}
}

The BruteForceRenderer is a CustomNode that contains one Group in which all the tiles are rendered. The tilesWidth parameter defines how wide our grid must be. A value of 8 for instance means that we will have a grid that contains 64 tiles in total.

The tiles themselves are made up of a Polygon with a green color. They are added one by one into tilesGroup. The order in which the tiles are layed out is from top to bottom and from right to left. The following image, which is a render of 9 tiles, probably clarifies this a bit. The numbers between the brackets are the x and y coordinate of the tile that is being rendered.


The main advantage for using the brute force approach is that the code remains very simple to understand. The disadvantage will become clear when you start raising the tilesWidth parameter. Even though they've made lots of performance improvements in JavaFX 1.3, it's still becoming a bit sluggish for instance when implementing mousedragging to move around the world.

3) Using a QuadTree


With brute force rendering, we keep track of all tiles whether they are visible on screen or not. However, we should only render a tile when it is actually visible on the screen. On the next image we can see the tiles in green and our screen in blue.


This clearly shows how brute force rendering is done: all tiles are visible, even those that do not fall within the blue rectangle. To solve this, before rendering a tile, we could check to see if it's bounds fall within the screen view or not. The easiest way to do this is to check all the tiles one by one. However, a more elegant solution is to use a quadtree. A quadtree allows us to quickly discover which nodes are actually visible on screen without having to check every possible node.

The process is as follows:

  1. divide the world space into four equally sized squares

  2. for every square, check if it's bounds intersect with the screen bounds
    • if they don't intersect: skip this square

    • if they do intersect: divide this square into four equally sized squares and repeat step 2


We keep dividing until we reach a specific depth. When this depth is reached, we will just individually check all tiles that are still available and render those that fall within the screen. Again, I'll use an image to make this more clear.


Step 1 divides the world into 4 squares. We check the bounds of square 1 and see that it intersects with the screen bounds (again the blue rectangle). In step 2 we divide this square again into 4 squares and check the bounds of square 1. We see that it doesn't intersect with the screen, so we check square 2. This time we have an intersection. So again, in step 3, we will divide this square number 2 into 4 equally sized squares and repeat our intersection checks. We can clearly see that square number 1 doesn't intersect, but square number 2 does.

At this moment we have reached our maximum depth of 3. So instead of dividing this last square into 4 smaller squares, we will go over the nodes that are contained in this square and perform the bounds check on each of these nodes. If a node intersects it will be added to the scene graph, otherwise it will be skipped. This is shown in step 4.

Below is a sample of this application rendering a world containing 256 by 256 tiles. You can also download the code as a NetBeans project.


March 20, 2010

Beware of JavaFX and null references

Yesterday I was trying to implement a simple Animation class, that could for example be used for animating sprites in a game. I wrote the following code:

public class Animation extends ImageView {
var index : Integer;
public-init var images : Image[] on replace {
timeline.start();
}

var timeline : Timeline = Timeline {
repeatCount : Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : 0.2s
action : function() {
if (index + 1 >= images.size()) index = 0 else index++
}
}
]
}

override var image = bind images[index];
}


Let's now create an Animation object as follows:

var animation : Animation = Animation {
images : drillingWell
}


You might expect that this would work just fine and that the program would display an animation of a drilling well. Since we are assigning the sequence drillingWell to Animation.images, this would call the on replace trigger. Which it does. However, during object instantiation the timeline variable is still null. This means that calling timeline.play() won't have any effect because JavaFX quietly ignores null references. Throwing a NullPointerException here would have saved me quite some time and work ;-).

To fix the code I had to assign the timeline in the on replace trigger itself, like this:

public class Animation extends ImageView {
public-init var images : Image[] on replace {
Timeline {
repeatCount : Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : 0.2s
action : function() {
if (index + 1 >= images.size()) index = 0 else index++
}
}
]
}.play();
}

var index : Integer;
override var image = bind images[index];
}

February 18, 2010

Using Jersey to (un)marshall Java objects

When developing a REST API in Java, it is straightforward when you do that using Jersey. At least when implementing the server side. For instance, we can have the following methods to manage a Foo object:

@GET
@Path("{fooId}")
@Produces("application/xml")
public Foo getFoo(@PathParam("fooId") int fooId) {
return FooBean.getInstance().findFoo(fooId);
}

@GET
@Path("/list")
@Produces("application/xml")
public List<Foo> getFoos() {
return FooBean.getInstance().findAllFoos();
}

@PUT
@Produces("application/xml")
public Foo putFoo(@FormParam("name") String name, @FormParam("bar") String bar) {
Foo foo = new Foo();
foo.setName(name);
foo.setBar(bar);
return FooBean.getInstance().createFoo(foo);
}

@POST
@Path("/{fooId}")
@Produces("application/xml")
public Foo updateFoo(@PathParam("fooId") int fooId, @FormParam("name") String name, @FormParam("bar") String bar) {
Foo foo = FooBean.getInstance().findFoo(fooId);
if (foo != null) {
foo.setName(name);
foo.setBar(bar);
return FooBean.getInstance().updateFoo(foo);
}

return null;
}

@DELETE
@Path("/{fooId}")
public void deleteFoo(@PathParam("fooId") int fooId) {
FooBean.getInstance().deleteFoo(fooId);
}


The Foo class must be annotated with @XmlRootElement, otherwise Jersey won't know how to marshall the object into XML or JSON.

@XmlRootElement
public class Foo implements Serializable {
private int id;
private String name;
private String bar;
...
}


Recently I discovered that you can also use Jersey when developing client applications that make use of a REST API. The Jersey Client API, as it is called, can be used to easily produce and consume REST requests and responses. The code below shows a quick overview of how to call the methods listed above.

Client client = Client.create();

WebResource wr = client.resource("http://localhost:8080/jm/rest/foo");

// getting a single Foo object
Foo foo = wrGetFoo.path("/1").get(Foo.class);

// getting a list of Foo objects
List<Foo> foos = wr.path("/list").get(new GenericType<List<Foo>>() {});

// creating a Foo object
MultivaluedMap params = new MultivaluedMapImpl();
params.add("name", "Foo2");
params.add("bar", "bar2");
Foo foo2 = wr.put(Foo.class, params);

// updating a Foo object
params.clear();
params.add("name", "Foo2");
params.add("bar", "bar2updated");
foo2 = wr.path("/" + foo2.getId()).post(Foo.class, params);

// deleting a Foo object
wr.path("/" + foo2.getId()).delete();


As you can see, the methods on the WebResource class map perfectly with the HTTP methods generally used in a REST API: GET, POST, PUT and DELETE. You can download the sample project, jerseymarshall.zip, which contains two NetBeans projects: a web project for the server side and a plain java project for the client side.

January 06, 2010

Simple animation in JavaFX

Having two weeks off during the Christmas holidays gives you some time to create something in JavaFX. I had already used JavaFX before, but animations were a part I didn't yet have any experience in. So I was thinking of remaking a simple banner application that a client once needed for his website. That version was written in Flash and was created by a third party. You can have a look at that animation at http://vi.be.

The application is quite simple. It reads in an XML-file which contains a list of banners. Each banner has an image, a title, a description and a URL. After the XML-file has been read, it displays the banners one after another. The transition between two different banners is done by using a simple fading out/fading in animation. Doing this animation in JavaFX is simple.

First I create a variable to hold the current opacity and bind this to the opacity of the banner. The SingleBanner class used below is nothing more than a CustomNode in which the image, the title and the description are placed.

var bannerOpacity = 1.0;

insert SingleBanner {
banner : banner // the POJO containing all banner info
visible : false // initially all banners are invisible
opacity : bind bannerOpacity // bind the opacity variable to the banner

// when the mouse is hovering over the banner, pause the timeline
onMouseEntered : function(event : MouseEvent) {
timeline.pause();
}
onMouseExited : function(event : MouseEvent) {
timeline.play();
}
} into banners;


To do the actual animation in JavaFX, we need to create a Timeline. A Timeline does nothing more than perform actions at specified "keyframes". You can execute this Timeline once, a few times or even infinitely. Here's the code for our Timeline:

var timeline : Timeline = Timeline {
repeatCount : Timeline.INDEFINITE
keyFrames: [
at (4.75s) {
bannerOpacity => 1.0
},
at (4.85s) {
bannerOpacity => 0.0 tween Interpolator.EASEOUT
}
at (5.0s) {
bannerOpacity => 1.0 tween Interpolator.EASEIN
}
]
}


The syntax is easy to understand and you can clearly see what the Timeline does. 4.75 second after the Timeline was started, it will assign 1.0 to the variable bannerOpacity. Between 4.75 and 4.85 seconds the opacity will be brought down to zero using an EaseOut interpolation. Finally, the opacity will be brought back up to 1.0 with an EaseIn interpolation. The trick is now to make the current banner invisble when the opacity reaches 0. At the same time, the next banner in the array will be made visible. We can do that easily using a replace trigger.

var bannerOpacity = 1.0 on replace {
if (bannerOpacity == 0.0) {
counters[index].active = false;
if (index + 1 >= sizeof(banners)) {
index = 0;
} else {
index++;
}
counters[index].active = true;
}
}

var index = 0;
var banner = bind banners[index] on replace oldBanner {
oldBanner.visible = false;
banner.visible = true;
}


That's all that is required to do some simple animation. The code for the project can be downloaded here. There are only two problems that I couldn't resolve:

  • I have not yet found a way how to open a URL from within JavaFX. This can be achieved with: AppletStageExtension.showDocument(banner.link, "_new");

  • In browser mode the loading of the images takes a long time. In desktop mode it seems to go a lot quicker.



Below you can have a look at the banner application itself.