Friday, November 06, 2009

JavaFX Production Suite - More Suggestions


I was recently working with the JavaFX Production Suite and have another suggestion for making it better. The first thing I want to talk about is the coordinates assigned to Nodes when they exported. Or lack of coordinates to be precise. The second issue is the inability to clone items within fxz content.

Coordinate Issue

Lets take a look at the example illustrator project in the following picture.

We see a red rectangle and a blue circle, both items are positioned at some location from Illustrators origin. When this is exported to JavaFX, the upper left corner becomes the origin for all of the generated nodes. So, in our example the upper left corner of the rectangle is the origin. This sort of makes sense, since positioning each node from some other arbitrary position would lead to other difficulties. But what concerns me is how the nodes are position, lets take a look at what this looks like in JavaFX.

Group {
content: [
SVGPath {
fill: Color.rgb(0x0,0xae,0xef)
stroke: null
content: "M143.90,110.81 C143.90,133.63 125.40,152.13 102.59,152.13 C79.77,152.13 61.27,133.63 61.27,110.81 C61.27,87.99 79.77,69.49 102.59,69.49 C125.40,69.49 143.90,87.99 143.90,110.81 Z "
Rectangle {
fill: Color.rgb(0xbf,0x1e,0x2d)
stroke: null
x: 0.0
y: 0.0
width: 171.0
height: 57.0
}, ]

As we can see the Rectangle is at point (0,0), but what coordinate is the circle at? The Circle is drawn as a series of strokes relative to the origin. This works great for static content, but for things that move in the scene, this is sort of a pain. It means that some nodes in the scene are offset from their origin and other nodes added to the scene might not be. This means you can't query the position of all nodes in the same way.

This problem is in part do to the incoherent way JavaFX deals with coordinates. But Ill get into that in another post. What I want to show you here is away to get a reference to a node within a node tree created from fxz content. The following code snippet does just this.

public function offsetFromZero(node:Node):Group{
var xOffset = node.boundsInParent.minX + node.boundsInParent.width/2.0;
var yOffset = node.boundsInParent.minY + node.boundsInParent.height/2.0;

var parent = node.parent as Group;
var index = Sequences.indexOf(parent.content, node);


node.translateX = -xOffset;
node.translateY = -yOffset;

var group = Group{
translateX: xOffset;
translateY: yOffset;
content: node;
insert group before parent.content[index];

return group;

The node passed in is located in some node tree, it's location from its parent is recorded based on its bounds. Its index in the content of its parent is recorded as well. The node is then removed from its parent and translated to the origin of its parent. The node is than wrapped in a new Group. The Group is than translated by the original offset of the node. Lastly the group is inserted back into the parent's content at the index that the original node held.

In this way the visual location of the node is not changed, but you now have a node which reports its translateX and translateY relative to the origin of the entire fxz node. This allows the application to use a single API for working with the location of nodes. The disadvantage is that it adds another node to the scene, which might contribute to a performance problem.


It seems to me that the work flow from Illustrator to Netbeans is good, but could be better. Besides the naming issues I pointed out here, there are some other particulars that need addressing. There are basically two choices when it comes to organizing your Illustrator files, the first is to just create a single file with all of your assets in it, then rely on your JavaFX to position and hide nodes that are not used at a particular time. The other option is to break out all of your content into separate files and let your application do the work of positioning everything. There are disadvantages and advantages to both strategies.

When putting all of your assets into one file it makes it simple to use Illustrator to do your layout. This is really important, since it is much faster to lay out stuff in illustrator, the end result looks and works better. But it makes things difficult for dynamic content, say you are creating a game with power-ups in it. If the number of power-ups on the screen at a given time is dynamic, there is no easy way of creating new ones, as the content in the fxz file contains s0me fixed number of power-up nodes, usually one. The only way to create more is to create entire new fxz object and pull out the power-up, probably throwing away the rest of the objects. This is inefficient, and creates weird patterns in your code.

When using multiple files you loose the ability to do a lot of the layout in illustrator and is also harder to maintain. But this strategy makes it very simple to create multiple instances of nodes, you simply create a new fxz node and add it your scene.

I think a partial solution to this would be if JavaFX support an easy way to clone a node. This would allow you to create a single Illustrator file and the application could just clone items as needed. Being able to clone nodes would be handy even without the Illustrator to JavaFX work flow. I think this is a missing feature and would love to see it included.


PhiLho said...

JavaFX has already a clone tool, the little I did with FXD precisely relied on putting several assets in the same file and cloning them by name (ID) to put them in the scene (since we no longer can move them between the FXD group and the scene's group).
The clone tool is javafx.fxd.Duplicator
See also Game Design with the JavaFX Production Suite articles (and others on this site) for more information.

Benda said...

I believe there is a better way than to create to wrapping group manually in code. If you create a (sub) layer directly in Illustrator with some name set it will be converted into such Group wrapper that hides the inner structure and can be used for various manipulations - like translation and other transformations as well. It is important to set the layer name though otherwise the element will be optimized out in export.