Experiments With JavaFX 3D Scenes

Introduction

JavaFX is the next step in the evolution of Java as a rich
client platform. Announced in 2007, JavaFX 1.0 was delivered in
2008 with a domain specific language for GUI construction called
JavaFX Script as the entry point to the JavaFX runtime. A major
shift in strategy away from the JavaFX Script language in favor of
a set of plain Java APIs was announces in 2010 and delivered in
2011 as JavaFX 2.0. This move made JavaFX easily available to all
Java programmers as well as programmers of other JVM languages
such as Scala, Groovy, Clojure, and Jython. The JavaFX Script
language was deemphasized by the JavaFX team at Oracle, but is
picked up by the open source community under the new name
Visage.

Since JavaFX 2.0, the JavaFX team has announced the intention
to open source the JavaFX technology and indeed several
subsystems, including the controls library, has already been
contributed to the openjfx project under the OpenJDK umbrella
project. It has further been announced that JavaFX technology
will be integrated into the upcoming Java 8 release. As a
consequence, JavaFX will become the focal point of rich client
application development for the Java platform.

JavaFX 2.1 was released on April 26, 2012, just as I was
reviewing the final draft of this article. This release brings in
Max OS X as a supported operating system for JavaFX. This release
coincides with the release of JDK 7 Update 4, which also added Max
OS X support. What is more, the JavaFX 2.1 SDK is included in the
JDK 7 Update 4 installation for both Windows and Mac OS X.

Key innovations of the JavaFX technology include its hardware
accelerated graphics system, its media support, its properties and
bindings framework, its HTML5 support through the Webkit based
WebEngine and WebView, and its animation framework. Most of these
are well covered in the JavaFX online documentation, the two books
on JavaFX 2.x so far—JavaFX 2.0 Introduction by Example by
Carl Dea, and Pro JavaFX 2 by James Weaver, myself, Stephen Chin,
Dean Iverson and Johan Vos—and the popular JavaFX blogs.

In this short article, I will explore an area of JavaFX 2.x
that is not as well covered as the other popular features such as
the properties and bindings framework or the animation framework,
but nevertheless is just as fascinating and is capable of creating
beautiful visual displays. This area is JavaFX 3D scenes. I will
show you JavaFX 3D scenes in action through a series of simple
examples. And along the way I will explain some of the other
JavaFX features I employed in the examples, including the use of
the JavaFX 2.x builders, properties and bindings, and
animations.

Setting Up a JavaFX 2 Development Environment

The development environment for JavaFX 2.x consists of a
supported operating system, the JDK, the JavaFX SDK, and a Java
IDE. Windows is supported since JavaFX 2.0. Mac OS X support is
added in the just released JavaFX 2.1. Linux support is available
as a developer preview in JavaFX 2.1 and 2.2, and will go GA in
JavaFX 2.2 later in 2012.

My setup for writing this article is a Mac mini running Mac OS
X Lion, JDK 1.6.0_31 for Mac OS X, JavaFX 2.2 SDK developer
preview build b04, and IntelliJ IDEA 11.1.1. I setup a normal
Java project in the IDE and add the JavaFX runtime jar, found in
rt/lib/jfxrt.jar under the JavaFX SDK installation
directory, as a dependency. After the JDK 7 Update 4 and JavaFX
2.1 release, I tested all the programs in JDK 7 Update 4 and
everything worked. With JDK 7 Update 4, you don't need a separate
JavaFX download.

To verify that my environment works, I compiled and ran the
following small program:

Before I point out which line of code uses 3D features of
JavaFX, let me briefly explain some basic JavaFX application code
idioms.

The Application class

The Application class provides the application
lifecycle methods init(), start(Stage), and
stop(). Only start(Stage) is abstract and
must be overridden. This method injects a Stage into
the application. Stage is an application window.

launch(args) in main()

launch(String[]) is a static method in
Application. It will get the JavaFX rumtine going, and
eventually calls start(Stage) on the GUI thread named
"JavaFX Application Thread"

The builder classes

JavaFX provides builder classes for many types of objects
that you may want to create. The builder code

The builder code is more concise and is more descriptive than
normal code, especially code that calls a constructor with
unnamed parameters.

Properties

JavaFX properties and bindings framework is the most fun part
of JavaFX code. It allows a property of one object to be bound to
another property somewhere else so that whenever the latter
changes, the former is notified and, at the appropriate time,
updated.

Animation

JavaFX supports key frame animations, which allows you to
specify the value of a property at certain specified time points.
The JavaFX animation framework interpolates the values of the
property at intermediate times.

The HelloJavaFX program will create a window of size
500×500, and three small rectangles of size 10×10: one
red, one green, and one blue. The translateXProperty of
the red rectangle, the translateYProperty of the green
rectangle, and the translateZProperty of the blue
rectangle are bound to the same DoubleProperty called
translate. The translate property is then
animated for a duration of 2 seconds with two key frames: at time
0, the value of translate is -250; at time 2 seconds, the
value of translate is 250. The three rectangles as a
whole is translated 250 along the x-axis, and 250 along the
y-axis, just so that we can see the rectangles when their
coordinates are negative.

3D Scenes in JavaFX

For readers who ask "Where is the 3D code?" It's this
line:

node3.translateZProperty().bind(translate);

Even though node3 is a two-dimensional rectangle, you
can manipulate its coordinates in three-dimensional space.
Two-dimensional objects manipulated in three-dimensional space,
that is the state of 3D scenes in JavaFX. Full 3D objects are in
the roadmap for future versions of JavaFX.

"But the blue rectangle did not move at all. And the scene
definitely looks two-dimensional." It looks two-dimensional
because of two things: we are looking at the scene in the z-axis
direction, and we are using a parallel camera. Every scene has a
camera. It defaults to a parallel camera, which renders objects
at the same size regardless of its distance from the viewer.
Let's put a PerspectiveCamera on the scene:

Even though the blue rectangle still only occupies the center
of the screen, its size does change from larger to smaller as its
z coordinate is translated from -250 to 250. Since under a
perspective camera, nearer object looks bigger, we can conclude
that the positive z-axis direction is pointing into the screen and
away from the viewer. Together with the observation that the red
rectangle animates from the left of the window towards the right
of the window, and that the green rectangle animates from the top
of the window towards the bottom of the window, we infer the
orientation of the coordinate system to be: x increases from left
to right, y increases from top to bottom, and z increases from
closer to the viewer to further away from the viewer. This gives
us a right-hand orientation of the three-dimensional space: if you
stretch out the first three fingers of your right hand, and point
your thumb along the positive x-axis and your index finger along
the positive y-axis, then your middle finger points to the
positive z-axis.

If you do not see the changing size of the blue rectangle after
adding a perspective camera to the scene, the graphics card on
your computer may not be one supported by JavaFX. This line of
code:

The Depth Buffer

Without any further transforms, all JavaFX on screen objects
such as controls and shapes fall on the xy-plane where the z
coordinate is 0. Normally, these objects are drawn in the order
in which they are put into the scene, and later added objects will
obscure earlier added ones.

This is only slightly different from the previous experiment.
We changed the size and location of the red, green and blue
rectangles so that they are bigger and overlapping. Since order
of the three rectangles in the container is node1, node2,
node3, the green rectangle obscures a portion of the red
rectangle, and the blue one obscures a portion of the green one.
The scene will be drawn differently if we add the rectangles in a
different order:

Now we revert the order back to node1, node2, node3
and change the z coordinates of the three rectangles so that the
red rectangle is at z = -100, the green one remains at z = 0, and
the blue one is at z = 100.

Build and run the program and you will see the red rectangle
becoming bigger because it is closer to the viewer, the green
rectangle remaining the same size, and the blue one becoming
smaller because it is further away from the viewer. However,
contrary to our intuition, the green rectangle still obscures a
portion of the red one, and the blue one the green.

To fix this problem, we must tell the scene to use the depth
buffer, which will cause the objects to be drawn by their distance
from the viewer. Further away objects will be drawn first, and
nearer objects will be drawn later.

Now the scene looks realistic. We can animate the z
coordinates of the three rectangles so that their z-ordering
changes during the animation to see one rectangle jumps in front
another. To do that, we add property fields to the class:

3D Transforms

As I mentioned earler, the power of JavaFX's 3D scenes comes
from its 3D transforms. JavaFX's transforms consist of the most
general Affine transform, and four special case
transforms: Translate, Scale, Rotate,
and Shear. You have seen plenty of Translate's
in the last section. They move objects in a particular direction.
Scale expands or contracts an object by an factor. Both
Translate's and Scale's are easy to imagine.

Rotate transforms have the most dramatic effect on the
scene as it rotates the scene about a straight line. When you
apply a Rotate transform to an object, it rotates the
object around its center. You can also specify a different
pivot point to rotate around. For 2D rotations, you need to
specify an angle of rotation in degrees and the object will be
rotated around the pivot point in a clockwise direction. For 3D
rotations, you also need to specify a rotation axis. The rotation
axis is represented by a Point3D object. The rotation
will be done around a straight line in the 3-dimensional space
that passes through the pivot point and points in the direction of
the axis vector. The direction of the rotation follow the
right-hand rule: Stretch out your thumb and bend your four fingers
as if to grab something, if you point your thumb in the direction
of the axis, then your four fingers will point to the direction of
the rotation.

This Rotate transform will cause the object it is
applied to to rotate around the x-axis by 60°.

The experiment of this section is a slight modification of the
experiment from the last section. I have changed the colored
rectangles into colored circles to stop you from being bored. I
have also added a Rotate transform that is applied to the
root, which is a Group that contains all three
circles. This causes them to be rotated as a combined unit. I
have added another property that is used to animate the
rotation.

Build and run the program and you will see the animation that
you saw in the last section, compounded with a rotation of the
whole scene around the x-axis. The motion of the objects is like
the wheel of a car that is driving towards you. Dodge!

We can change the axis of the Rotate transform to that
of the y-axis, represented by the directional vector (0, 1,
0):

This time the rotation will be around the y-axis. As a matter
of fact, the Rotate class has constants X_AXIS,
Y_AXIS, and Z_AXIS that you can use in this
situation. The above snippet of code is equivalent to:

Notice that in the midst of all the animated translations and
rotation the depth buffer worked flawlessly. Objects closer to us
always obscure objects further away, never the other way
around.

3D Objects

All the experiments that you have seen so far feels a little
bit "flat." And that is because the three rectangles or the three
circles that we dealt with are all parallel to each other. Rotate
them about different axis, and they will form the surface of a 3D
looking solid. Here's a cube, obtained from six squares each of a
different color and each of which undergoes a different
transform. In the beginning, all six sides were at the same
position, but one by one, the side animates to their final
position in the cube:

Did you see it? Well, you can see the cube forming as it
happens. But because of the viewing angle is directly over the
cube, when the animation is done, all you see is the last face of
the cube. To cure this dullness, we add a Rotate
transform to the root of the scene graph and animate its angle as
we form the cube. First we add a property:

Notice that the pivot point of the rotation is the center of
the cube, which is at (0, 0, -100). We also translated the
root along the z-axis so that the rotation's center
appears to be at the center of the screen.

Build and run the program and you will see the forming of the
cube more clearly. If you have never done this sort of programs
before, you have to admit it's pretty cool.

Platonic Solids

The cube is one of five Platonic solids. Platonic solids are
regular convex polyhedrons whose faces are congruent regular
polygons. There are five Platonic solids: the tetrahedron (4
faces, each a equilateral triangle), the hexahedron (or the cube,
6 faces, each a square), octahedron (8 faces, each a triangle),
dodecahedron (12 faces, each a pentagon), and icosahedron (20
faces, each a triangle.)

Using the same trick from the last section we can construct the
other Platonic solids. I'll show you a tetrahedron. The only
difference between the tetrahedron and the cube is that instead of
using six squares, we use four equilateral triangles, and instead
of rotating the squares out by 90°, we rotate the triangles
out by 70.53°—the dihedral angle of the tetrahedron.

Lighting, BoundingBoxes, and PerspectiveTransforms

There are a few more JavaFX features that interact well with 3D
scenes. Lighting is an effect that can be
applied to any Node. When applied to 3D objects, the
effect is more dramatic.

Each JavaFX Node knows its bounds. As a matter of
fact, each Node knows three bounds:
boundsInLocal, boundsInParent, and
layoutBounds. The boundsInLocal takes into
account only the geometry of the Node, but not any
transforms. The boundsInParent does take into account of
transforms. For example, the local bounds of a rectangle is just
the rectangle itself. If you don't apply any transforms, the
parent bounds is the same as the local bounds. However, if you do
apply transforms, say, a Rotate of 45°, then the
parent bounds will become bigger to accommodate the slanted
rectangle. When a Node contains 3D objects, its bounds
are also 3 dimensional.

The PerspectiveTransform class is not a
Transform like Translate, Scale, or
Rotate. But rather it is an effect. And
according to the javadoc, "Most typically
PerspectiveTransform is used to provide a 'faux'
three-dimensional effect for otherwise two-dimensional
content."

Summary

As you can see from the experiments I made in this article,
JavaFX 2.x contains easy to use facilities for building simple 3D
scenes that can be used to produce quite interesting results.
Although not everyone will be doing 3D scenes in JavaFX, many of
JavaFX's other facilities are just as fun to use.

With JavaFX slated to be open sourced and to be tightly
integrated with the JDK and JRE, it makes a lot of sense to
familiarize your self with the JavaFX technologies, and have some
fun while exploring them.

I would like to thank Stephen Chin, Carl Dea, and Brian
Coyner for their help in reviewing this article.

OCI partners with clients to assess, design, architect, engineer, manage and support Mission-Critical,
High Performance and Real Time systems. Our goal is to make IT solutions more open, scalable, reusable,
interoperable, and affordable. Please visit www.ociweb.com to learn
more about our service offerings, open source middleware technologies, and professional IT training.