Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

Technical aspects

 

Round 1

Scene graph design

The system mentioned in my previous post utilizes JavaFX script language features to construct a lazy dependency graph, part of which is the scene graph that provides the infrastructure for rendering, picking, and collision detection. This scene graph does not use the trigger feature of JavaFX script, but rather relies on its lazy binding feature.

Thus, when you modify the scene, nothing happens other than that things that depend on your modifications are (implicitly) marked invalid. Only when you subsequently access the scene does (minimal) reevaluation occur. In addition, due to the nature of binding, results are implicitly "memo-ized" and you get fast cached access to them if their dependencies haven't changed. Ultimately when the render pass occurs, it touches everything, bringing it up to date if necessary as a side effect.

Linear Math

Animation of 2d or 3d geometry consists of animating the components of 2d or 3d affine transformations that are applied to such geometry or of deforming the geometry itself. Linear algebra provides truly elegant mechanisms to express such, however, that elegance is severely diminished in programming languages that cannot provide the expected arithmetic operators. Therefore one of the language and compiler modifications I made is to provide overloaded arithmetic operators for vector and matrix operations. For our purposes (vector graphics) we only require 2d, 3d, and 4d vectors and 2x2, 3x3, and 4x4 matrices. These types are provided in a new package called javafx.math, namely Vec2, Vec3, Vec4, Mat2, Mat3, Mat4. Since rotations may be represented as a pair of angle/axis, or quaternion form, in addition to matrix form, we also provide the types AngleAxis, and Quat. All of the types mentioned are conceptually immutable value types, like Java/JavaFX primitive types. Ideally, they should be implemented with VM level value types.

Transform and bounding volume hierarchies

With these prerequisites, implementation of the transform hierarchy is trivial (simplified, with some details omitted - local transform consists of a number of sub-components):

import javafx.math.Mat4;

public abstract class Node {

public var parentNode: Node;
public var transform: Mat4 = IDENTITY;

public def worldTransform: Mat4 =
bind lazy
if (parentNode == null)
then localTransform
else parentNode.worldTransform * localTransform;

public def localTransform = bind lazy transform * ...
...

The above simply states that a Node has an optional parent, which is also a Node, and that its local to world space transformation is that of its parent multiplied by its own local to parent transform or just its own transform if it's the root of the hierarchy.

Implementing the bounding volume hierarchy is equally trivial:

public class Bounds {

public-init var center: Vec3 = Vec3.ZERO;
public-init var extent: Vec3 = Vec3.ZERO;

public def width = bind lazy extent.x * 2;
public def height = bind lazy extent.y * 2;
public def depth = bind lazy extent.z * 2;
public def min = bind lazy center - extent;
public def max = bind lazy center + extent;
...
}

public function transformBounds(b:Bounds, transform:Mat4):Bounds { ... }

public abstract class Node {
...
public def worldBounds:Bounds = bind lazy Bounds.transformBounds(contentBounds,
worldTransform);
public def bounds:Bounds = bind lazy Bounds.transformBounds(contentBounds,
localTransform);

public-read protected var contentBounds:Bounds;
...
}

The above states that the world space bounding box of a Node is the bounding box of whatever it contains multiplied by its local to world transform, and similarly for its local bounds in its parent's coordinate system.

This approach has a worst case cost which is the same as one which used eager bindings, for example if I modify a node's transform and then immediately read the root node's bounds, then modify another node's transform, then read the root node's bounds, etc. However, for real programs the average case cost is fantastically less, since reading all the dependents during a sequence of modifications rarely happens in practice. With eager bindings as in javafx 1.x and in prism, you pay this price all of the time regardless. I believe the "boundsInScene" variable of the javafx 1.1 Node class was actually removed in 1.2 due to this type of overhead.

A similar lazy binding strategy is used throughout, for the shading network, 2d shape creation and composition, for complex compositions of weighted transformations such as aim, point, and orient constraints, etc, all in contrast to what we have in javafx 1.x.

I had to make some compiler modifications to make lazy binding operate correctly. In particular ensuring that it truly is lazy - for example, in the above bounding volumes are not created at all until they're used.