Note to reader: This "guest post" was written by Dr. James Thompson, who is using JavaFX to develop scientific applications. I've included a brief bio for Dr. Thompson after this post.
Java has a rich history in the scientific community. Applications and frameworks such as the NIH's ImageJ, CERN's Colt framework and Apache Commons Math library immediately spring to mind when working in scientific computing. My first exposure to Java in science came as a graduate student at Oxford. We needed to analyze many large noisy datasets, typically stacked (multi-page) TIFF image files. ImageJ and its excellent plugin framework was the environment of choice for this work. Other groups such as the Mosaic group at the ETH in Switzerland did similar work and have extended it beyond what we did a few years ago.
So where does JavaFX 2 come in? Well, in my recent work we've had a need to develop image analysis software yet again. It turns out this is a common theme in biology, physics and engineering labs nowadays. I decided to have a look into what was available on the JVM which was when I learned of the new Java FX 2 pure Java library. It instantly appealed as it was well documented, had plenty of examples and was free! Scientists are a diverse group too, so the ability to write once and run on Macs, Windows and Linux boxes is a massive bonus. In academic science these days you often find diverse backgrounds and amalgamations of people with broadly different skill-sets. A nice user interface, easy installation and pleasant work environment on the computer desktop is therefore imperative.
A few features stood out immediately as I began browsing examples and reading the API. These included in my order of priority: extensive graphing tools, ObservableList, easy integration with native file systems (open / save dialogs), a simple syntax, great API documentation, FXML and good Netbeans tooling. Later on Scene Builder came onto the scene (pardon the terrible pun). Having done some work with XCode for Cocoa on the Mac I thought it would be hard to stand up to it. I was wrong. Scene Builder was mature out of the box. Laying out 'AnchorPanes' or anything else for that matter is very straightforward. CSS integration also enables easy skinning for apps. This is generally much more difficult in other frameworks.
Living on the JVM and being pure Java, integration with other JVM languages is easy too. My recent forays into Scala have proven very fruitful with JavaFX. I've been building some fairly involved numerical analysis code, and it's very easy to code in Scala. I've been able to use JavaFX in pure Scala classes, extending the 'Initializable' interface and load FXML classes easily.
I've had to develop a few things building directly on the JavaFX framework, such as for example tools to load multi-page TIFF files and write various bit-depth TIFF data to JavaFX 'Image' objects. But generally plotting tools and data handling are a breeze with JavaFX 2.
IntelliJ IDEA, Netbeans and Simple Build Tool for Scala all enable mixed projects and help with building greatly. I stumbled across an sbt-javafx plugin a few days ago, which has enabled me to package my projects for distribution among my colleagues.
In short, if you have a need to develop applets for scientific work, need to load and save data with a GUI, plot graphs and get actual science done, JavaFX 2 has to be given some serious consideration! I can't imagine trying to do what I've been able to in any other framework. I hope more scientists decide to develop things with JavaFX 2, so that we can extend the body of open source code and make development even easier.
Please have a look at a couple of movies I made showing my GUIs in action. These projects are a work in progress, but demonstrate the power of JavaFX and its application to scientific computing front ends.
James did his PhD at Oxford in the Physical and Theoretical Chemistry Laboratory, researching membrane protein biophysics. After finishing, James went to Harvard Medical School for a year to work on an imaging project before moving to his present role as a post-doc at USC in Los Angeles this year. His work revolves around biophysics but uses techniques from molecular biology, chemistry, microscopy and computer science. James maintains interests in single molecule biophysics, membranes, imaging, protein structure and function and computation with Java, Scala and GPUs. His twitter handle is: drJamesThompson
As mentioned in the Your Calendar PWN3D post, the JavaFX 1.3 SDK, released 22-Apr-2010, contains some basic 3D-related features with which you can begin experimenting:
There is a new package, named javafx.runtime, which contains classes that allow an application to query the capabilities of the platform. One of these capabilities is represented by the SCENE3D constant of the ConditionalFeature class.
The Point3D class in the javafx.geometry package represents a point in 3D space, with variables named x, y and z.
The Node class contains scaleZ and translateZ variables, for scaling and translating (moving) a node on the Z-axis.
The Node class also contains a rotationAxis variable, which defines the rotation axis (X, Y or Z) on which a node will rotate at the angle specified in that node's rotate variable.
The Rotate and Scale classes contained in the javafx.scene.transform package have instance variables named pivotX, pivotY and pivotZ that define a rotation pivot point.
The RotationTransition, ScaleTransition, and TranslateTransition classes accept a three dimensional axis.
The PerspectiveCamera class, located in the javafx.scene package, defines a viewing volume (which is shaped like a truncated right pyramid) for a perspective projection. Its fieldOfView variable specifies the vertical angle of the camera's projection.
In this post I’d like to introduce you to these features in the context of the EarthCubeFX example.
The EarthCubeFX Example Program
The EarthCubeFX example program features a cube that contains Google Map tiles on its faces. You can download the NetBeans project containing this example here. After downloading and unzipping the file, open the project in NetBeans. To enable the stack that implements 3D capabilities, the NetBeans project contains the following JVM argument in the Run pane of its Project Properties dialog:
-Xtoolkit prism
Because the program access Google Map tiles from the Internet at runtime, verify that you have an Internet connection when running the program. Here are some instructions for using the EarthCubeFX example:
Taking EarthCubeFX for a Spin
When the EarthCubeFX application is invoked, it appears in a transparent (and undecorated) window after having requested image tiles via the Internet from Google Maps:
To make the appearance more interesting than a blank cube, the Google Map image tiles from a round planet are placed on the cube. In keeping with the Earth theme, the cube orients itself on the Y axis as if the Sun were behind the user, and then slowly turns to continue tracking the Sun. At the time the screen shot above was taken, the Sun was warming the Atlantic Ocean. The user may interact with the cube in the following ways:
Dragging the face of the cube causes it to rotate in the direction of the drag.
Pressing the spacebar key fades the Google Map image tiles, exposing the plain cube as shown in the screen shot below. Note that in this mode, the cube doesn’t track the Sun. Pressing the spacebar again causes the map tiles to fade in, and the tracking behavior to resume.
Dragging the face of the cube while holding the control key down moves the cube around the screen.
Dragging the face of the cube in the up or down direction while holding the alt key down causes it to move on the Z axis, toward or away from the user, respectively.
Double-clicking the face of the cube causes it to rotate to its original X and Y axis angles.
Pressing the esc key closes the EarthCubeFX application.
Here’s a short video of these interactions with the cube:
Examining the EarthCubeFX Code
Now that you are familiar with the behavior of EarthCubeFX, let’s examine its 3D related code together. First, here’s the code for the CubeNode itself:
/*
* CubeNode.fx
*
* A cube-shaped UI component upon whose faces other nodes may placed.
*
* Developed by James L. Weaver (jim.weaver#javafxpert.com) to demonstrate the
* use of SCENE3D conditional features in the JavaFX 1.3 API
*/
package javafxpert.cube;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.util.Math.*;
import javafx.util.Sequences;
def HOME_ANGLE_X = 20.0;
def HOME_ANGLE_Y = -30.0;
def MIN_ANGLE_X = -70.0;
def MAX_ANGLE_X = 70.0;
def MIN_ANGLE_Y = -720.0;
def MAX_ANGLE_Y = 720.0;
def MIN_TRANSLATE_Z = 0.0;
def MAX_TRANSLATE_Z = 10000.0;
public class CubeNode extends CustomNode {
postinit {
goHomePosition();
}
var r:Number;
public var edgeLength:Number = 100 on replace {
r = edgeLength * 0.5;
};
public-init var frontNode:Node;
public-init var rearNode:Node;
public-init var leftNode:Node;
public-init var rightNode:Node;
public-init var topNode:Node;
public-init var bottomNode:Node;
public var faceHue:Number = 211;
public var faceSat:Number = 0.25;
override var translateZ on replace {
if (translateZ > MAX_TRANSLATE_Z) {
translateZ = MAX_TRANSLATE_Z;
}
else if (translateZ < MIN_TRANSLATE_Z) {
translateZ = MIN_TRANSLATE_Z;
}
}
var angleX:Number on replace {
if (angleX > MAX_ANGLE_X) {
angleX = MAX_ANGLE_X;
}
else if (angleX < MIN_ANGLE_X) {
angleX = MIN_ANGLE_X;
};
arrangeFacesZOrder();
};
var angleY:Number on replace {
if (angleY > MAX_ANGLE_Y) {
angleY = MAX_ANGLE_Y;
}
else if (angleY < MIN_ANGLE_Y) {
angleY = MIN_ANGLE_Y;
}
arrangeFacesZOrder();
};
var dragPressedAngleX:Number;
var dragPressedAngleY:Number;
var dragStartOffsetX:Number;
var dragStartOffsetY:Number;
var rearFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: edgeLength
},
Rotate {
angle: 180.0
axis: Rotate.Y_AXIS
pivotX: edgeLength / 2
}
]
node: Group {
content: bind [
createFaceRectangle(rearFace),
rearNode
]
}
};
var bottomFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: edgeLength
},
Rotate {
angle: 90.0
axis: Rotate.X_AXIS
pivotY: edgeLength
}
]
node: Group {
content: bind [
createFaceRectangle(bottomFace),
bottomNode
]
}
};
var leftFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: edgeLength
},
Rotate {
angle: 90.0
axis: Rotate.Y_AXIS
pivotX: 0
}
]
node: Group {
content: bind [
createFaceRectangle(leftFace),
leftNode
]
}
};
var rightFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: edgeLength
},
Rotate {
angle: -90.0
axis: Rotate.Y_AXIS
pivotX: edgeLength
}
]
node: Group {
content: bind [
createFaceRectangle(rightFace),
rightNode
]
}
};
var topFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: edgeLength
},
Rotate {
angle: -90.0
axis: Rotate.X_AXIS
pivotY: 0
}
]
node: Group {
content: bind [
createFaceRectangle(topFace),
topNode
]
}
};
var frontFace:CubeFace = CubeFace {
transforms: [
Translate {
x: 0
y: 0
z: 0
}
]
node: Stack {
content: bind [
createFaceRectangle(frontFace),
frontNode
]
}
};
function arrangeFacesZOrder():Void {
var baseGroup = children[0] as Group;
rearFace.zPos = r * cos(toRadians(angleY + 0));
bottomFace.zPos = r * cos(toRadians(angleX + 270));
leftFace.zPos = r * cos(toRadians(angleY + 270));
rightFace.zPos = r * cos(toRadians(angleY + 90));
topFace.zPos = r * cos(toRadians(angleX + 90));
frontFace.zPos = r * cos(toRadians(angleY + 180));
baseGroup.content = (Sequences.sort(baseGroup.content as CubeFace[]) as Node[]);
}
function createFaceRectangle(face:CubeFace):Rectangle {
Rectangle {
width: edgeLength
height: edgeLength
fill: bind computeFaceHSB(faceHue, faceSat, face.zPos, r)
}
}
bound function computeFaceHSB(faceHue:Number, faceSat:Number,
zPos:Number, radius:Number):Color {
Color.hsb(faceHue, faceSat, abs(-zPos / (radius * 2)) + 0.40)
}
public function goHomePosition():Void {
var homeTimeline = Timeline {
keyFrames: [
KeyFrame {
time: 1000ms
values: [
angleX => HOME_ANGLE_X tween Interpolator.EASEBOTH,
angleY => HOME_ANGLE_Y tween Interpolator.EASEBOTH,
]
}
]
};
homeTimeline.play();
}
public function goPosition(angX:Number, angY:Number):Void {
var tempAngX = angX;
var tempAngY = angY;
if (tempAngX.isNaN()) {
tempAngX = HOME_ANGLE_X;
}
if (tempAngY.isNaN()) {
tempAngY = HOME_ANGLE_Y;
}
var goTimeline = Timeline {
keyFrames: [
KeyFrame {
time: 1000ms
values: [
angleX => tempAngX tween Interpolator.EASEBOTH,
angleY => tempAngY tween Interpolator.EASEBOTH
]
}
]
};
goTimeline.play();
}
override var children = bind [
Group {
content: [
rearFace,
bottomFace,
leftFace,
rightFace,
topFace,
frontFace
]
transforms: [
Rotate {
angle: bind angleX
axis: Rotate.X_AXIS
pivotX: edgeLength * 0.5
pivotY: edgeLength * 0.5
pivotZ: edgeLength * 0.5
},
Rotate {
angle: bind angleY
axis: Rotate.Y_AXIS
pivotX: edgeLength * 0.5
pivotY: edgeLength * 0.5
pivotZ: edgeLength * 0.5
}
]
onMousePressed: function(me:MouseEvent):Void {
dragPressedAngleX = angleX;
dragPressedAngleY = angleY;
dragStartOffsetX = me.screenX - scene.stage.x;
dragStartOffsetY = me.screenY - scene.stage.y;
}
onMouseDragged: function(me:MouseEvent):Void {
if (me.controlDown) {
scene.stage.x = me.screenX - dragStartOffsetX;
scene.stage.y = me.screenY - dragStartOffsetY;
}
else if (me.altDown) {
translateZ = translateZ + me.dragY * 10;
}
else {
angleX = (me.dragY / 2) + dragPressedAngleX;
angleY = (me.dragX / -2) + dragPressedAngleY;
}
}
onMouseClicked: function(me:MouseEvent):Void {
if (me.clickCount == 2) {
goHomePosition();
}
}
}
]
}
The first concept from the code that we’ll examine is the ability in JavaFX 1.3 to translate (move) nodes on the Z axis.
Translate Transformations on the Z Axis
There are three ways in JavaFX to perform a translate transformation on the Z axis. The first way is to use the javafx.scene.transform.Translate class. Another way involves the use of the translateZ instance variable of the node that you wish to translate. The third way is to use the TranslateTransition class, located in the javafx.animation.transition package. Each of these ways produces the same result: moving a node toward or away from the user.
Using the Translate Class
The code snippet below from the CubeNode.fx listing above demonstrates the use of the Translate class:
When the CubeNode is instantiated, six CubeFace instances are created and placed in the scene graph. The snippet above shows one of these instances (the rear face) being created and positioned at distance equal to the value of edgeLength (the length of the cube edges) on the Z axis of the cube. The Translate instance is used in the transforms sequence of the Node class, whose purpose is to express one or more transformations. In this case, the Translate is performed, followed by the Rotate, which will be discussed shortly.
Using the Node translateZ Variable
The following code snippet from CubeNode.fx demonstrates the use of the translateZ variable:
When the user drags the mouse while holding the alt key down, the translateZ variable of CubeNode (that it inherits from Node) is increased or decreased as the mouse is dragged up or down. Increasing the translateZ variable, for example, moves the cube away from the user, making it appear smaller.
The TranslateTransition Class
Another way to perform a translate transformation is to use the TranslateTransition class, located with other transition-related classes in the javafx.animation.transition package. See the JavaFX 1.3 API documentation for the TranslateTransition class for details.
Rotate Transformations
As with translate transformations, there are three ways in JavaFX to perform rotate transformations. One way is to use the javafx.scene.transform.Rotate class. Another way involves the use of the rotate and rotationAxis instance variables of the node that you wish to rotate. The third way is to use the RotateTransition class, located in the javafx.animation.transition package. Each of these ways produces the same result: rotating a node on a given axis.
Using the Rotate Class
The code snippet below from the CubeNode.fx listing above demonstrates the use of the Rotate class:
The snippet above shows the left CubeFace being instantiated and positioned at edgeLength on the Z axis of the cube, which as you may recall is where the rear face was placed. It is then rotated 90 degrees on the Y axis (clockwise as looking from above). Because the pivotX variable is assigned a value of 0, the rotation occurs around the leftmost point on the leftFace node. As with the Translate instance shown earlier, the Rotate instance is used in the transforms sequence of the Node class, whose purpose is to express one or more transformations.
The Node rotate and rotationAxis Variables
An alternative to using the Rotate class is to simply use the rotate and rotationAxis variables of the Node class. You can specify the angle of rotation and the axis of rotation, but this technique doesn’t allow defining multiple axes of rotation nor does it allow identifying a pivot point around which the rotation is to occur.
The RotateTransition Class
Another way to perform a rotate transformation is to use the RotateTransition class, located with other transition-related classes in the javafx.animation.transition package. See the JavaFX 1.3 API documentation for the RotateTransition class for details.
Scale Transformations
As with translate and rotate transformations, there are three ways in JavaFX to perform scale transformations. One way is to use the javafx.scene.transform.Scale class, which allows you to specify pivot points about which the scales are to occur. Another way involves the use of the scaleX, scaleY, and scaleZ variables of the node that you wish to scale. The third way is to use the ScaleTransition class, located in the javafx.animation.transition package.
Some Other 3D-Related Features in JavaFX 1.3
There are a couple more 3D capabilities of JavaFX 1.3 that I’d like to point out. First, however, here is the source code for EarthCubeMain.fx, the main script in this example:
/*
* EarthCubeMain.fx
*
* Main script for EarthCubeFX -- a program that uses the CubeNode component
* and superimposes map tiles from Google Maps web services.
*
* Developed by James L. Weaver (jim.weaver#javafxpert.com) to demonstrate the
* use of SCENE3D conditional features in the JavaFX 1.3 API
*/
package javafxpert.earthcube;
import javafx.animation.*;
import javafx.runtime.*;
import javafx.scene.*;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import javafxpert.cube.CubeNode;
if (not Platform.isSupported(ConditionalFeature.SCENE3D)) {
println("The SCENE3D conditional feature is not supported");
FX.exit();
}
def EDGE_LENGTH:Number = 512;
var cube:CubeNode;
var mapOpacity:Number = 0.0;
def showMapTimeline = Timeline {
keyFrames: [
KeyFrame {
time: 0s
values: mapOpacity => 0.0
action: function():Void {
cube.goPosition(Number.NaN, calculateAngleYForCurrentTime());
trackWithCurrentTime.playFromStart();
}
},
at(1s) {mapOpacity => 0.7 tween Interpolator.EASEBOTH;}
]
}
def hideMapTimeline = Timeline {
keyFrames: [
KeyFrame {
time: 0s
values: mapOpacity => 0.7
action: function():Void {
trackWithCurrentTime.stop();
}
},
at(1s) {mapOpacity => 0.0 tween Interpolator.EASEBOTH;}
]
}
def trackWithCurrentTime = Timeline {
keyFrames: [
KeyFrame {
time: 10m
action: function():Void {
cube.goPosition(Number.NaN, calculateAngleYForCurrentTime());
}
}
]
repeatCount: Timeline.INDEFINITE
};
function calculateAngleYForCurrentTime():Number {
def cal = new GregorianCalendar();
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
def hour:Number = cal.get(Calendar.HOUR_OF_DAY);
def minute:Number = cal.get(Calendar.MINUTE);
def angY:Number = (((hour / 24.0) + (minute / 1440.0)) * 360) mod 360;
return angY;
}
function createSideMapTiles(sideNum:Integer):Node {
def xOffset = sideNum * 2;
Tile {
columns: 2
rows: 2
content: for (y in [3..4], x in [xOffset..xOffset + 1]) {
ImageView {
var xm = (x + 1) mod 8
opacity: bind mapOpacity
image: Image {
url: "http://mt3.google.com/vt/v=w2.97&x={xm}&y={y}&z=3"
}
fitWidth: EDGE_LENGTH / 2
fitHeight: EDGE_LENGTH / 2
}
}
}
}
function createTopMapTiles(sideNum:Integer):Node {
def xOffset = sideNum * 2;
Tile {
rotate: (450 - sideNum * 90) mod 360
columns: 2
rows: 3
content: for (y in [0..2], x in [xOffset..xOffset + 1]) {
ImageView {
var xm = (x + 1) mod 8
opacity: bind mapOpacity
image: Image {
url: "http://mt3.google.com/vt/v=w2.97&x={xm}&y={y}&z=3"
}
fitWidth: EDGE_LENGTH / 2
fitHeight: EDGE_LENGTH / 3
}
}
effect: PerspectiveTransform {
ulx: EDGE_LENGTH * 0.375 uly: EDGE_LENGTH * 0.625
urx: EDGE_LENGTH * 0.625 ury: EDGE_LENGTH * 0.625
llx: 0 lly: EDGE_LENGTH
lrx: EDGE_LENGTH lry: EDGE_LENGTH
}
}
}
function createBottomMapTiles(sideNum:Integer):Node {
def xOffset = (sideNum + 1) * 2;
Tile {
rotate: (sideNum * 90) mod 360
columns: 2
rows: 3
content: for (y in [5..7], x in [xOffset..xOffset + 1]) {
ImageView {
var xm = (x + 1) mod 8
opacity: bind mapOpacity
image: Image {
url: "http://mt3.google.com/vt/v=w2.97&x={xm}&y={y}&z=3"
}
fitWidth: EDGE_LENGTH / 2
fitHeight: EDGE_LENGTH / 3
}
}
effect: PerspectiveTransform {
ulx: 0 uly: 0
urx: EDGE_LENGTH ury: 0
llx: EDGE_LENGTH * 0.375 lly: EDGE_LENGTH * 0.375
lrx: EDGE_LENGTH * 0.625 lry: EDGE_LENGTH * 0.375
}
}
}
var stageRef:Stage;
stageRef = Stage {
style: StageStyle.TRANSPARENT
scene: Scene {
fill: Color.TRANSPARENT
width: 800
height: 800
camera: PerspectiveCamera {
fieldOfView: 30
}
content: [
cube = CubeNode {
layoutX: 130
layoutY: 130
focusTraversable: true
onKeyPressed: function(ke:KeyEvent):Void {
if (ke.code == KeyCode.VK_SPACE) {
if (mapOpacity == 0.0) {
showMapTimeline.playFromStart();
}
else {
hideMapTimeline.playFromStart();
}
}
else if (ke.code == KeyCode.VK_ESCAPE) {
stageRef.close();
}
}
edgeLength: EDGE_LENGTH
leftNode: createSideMapTiles(0)
frontNode: createSideMapTiles(1)
rightNode: createSideMapTiles(2)
rearNode: createSideMapTiles(3)
topNode: Stack {
content: [
for (side in [0..3]) createTopMapTiles(side),
ImageView {
opacity: bind mapOpacity
image: Image {
url: "http://mt3.google.com/vt/v=w2.97&x=2&y=8&z=4"
}
fitWidth: EDGE_LENGTH / 4
fitHeight: EDGE_LENGTH / 4
}
]
}
bottomNode: Stack {
content: [
for (side in [0..3]) createBottomMapTiles(side),
ImageView {
opacity: bind mapOpacity
image: Image {
url: "http://mt3.google.com/vt/v=w2.97&x=2&y=15&z=4"
}
fitWidth: EDGE_LENGTH / 4
fitHeight: EDGE_LENGTH / 4
}
]
}
}
]
}
}
showMapTimeline.playFromStart();
Using the Scene PerspectiveCamera Class
Much of the code in the main script file shown above creates the nodes containing Google Map tiles that appear on the faces of the cube. Some of the code also causes the cube to rotate on the Y-axis periodically in order to track the position of the Sun, and handles keyboard input.
Some 3D related code that deserves more explanation is in the following snippet:
The PerspectiveCamera causes the scene to be rendered using a perspective projection, and its fieldOfView variable specifies the vertical angle of the camera’s projection.
Querying the Capabilities of the Platform at Runtime
There is a new package in JavaFX 1.3 named javafx.runtime that enables your program to query the runtime platform’s capabilities. Here’s a snippet from the EarthCubeMain.fx listing that demonstrates how to check for the platform’s support for a 3D scene graph:
if (not Platform.isSupported(ConditionalFeature.SCENE3D)) {
println("The SCENE3D conditional feature is not supported");
FX.exit();
}
Conclusion
For completeness, here is the listing for the third file in the EarthCubeFX example, which defines a cube face on a CubeNode:
/*
* CubeFace.fx
*
* Represents a face on the CubeNode UI component.
*
* Developed by James L. Weaver (jim.weaver#javafxpert.com) to demonstrate the
* use of SCENE3D conditional features in the JavaFX 1.3 API
*/
package javafxpert.cube;
import javafx.scene.*;
import java.lang.Comparable;
import java.lang.Object;
public class CubeFace extends CustomNode, Comparable {
package var node:Node;
package var zPos:Number;
override var children = bind node;
override public function compareTo(cubeFace:Object) {
return (cubeFace as CubeFace).zPos.compareTo(zPos)
}
}
As you may have noticed in the arrangeFacesZOrder() function of the CubeNode.fx listing earlier in the article, when the cube is rotated the Z order of the faces must be manipulated programmatically so that a cube face doesn’t obscure another cube face that is in front of it. In this example, the following statement located in the arrangeFacesZOrder() function is executed to sort the CubeFace instances within their Group:
baseGroup.content = (Sequences.sort(baseGroup.content as CubeFace[]) as Node[]);
To support being sorted with the Sequences.sort() function, the CubeFace class extends the java.util.Comparable interface.
Future versions of JavaFX will have 3D shapes such as cubes, and will support other 3D constructs such as texture mapping and lighting. The JavaFX 1.3 SDK, however, provides a great opportunity to experiment with some basic 3D capabilities now. Please leave a comment if you have a question about this EarthCubeFX example.
The folks at lodgON in Belgium have created a JavaFX application named iParticipate that enables companies
and organizations to interact with dedicated focus groups in a controlled
environment.
The screen shot above shows a portion of the application, which leverages JavaFX in its user interface and J2EE 6 technologies running in a Glassfish server. For more info on this application, see the related blog post by JavaFX developer Johan Vos.
One of the student winners of the the JavaFX Coding Challenge is Kazuki Hamasaki, who created the CalcFX program. CalcFX is a deceptively functional (and incredibly useful) calculator, and is now one of the Java Web Start shortcuts on my desktop.
Kazuki: If you're reading this, please create a desktop icon for CalcFX so that it doesn't blend in with the usual coffee-cup icons deposited by Java Web Start. For those wondering, just put the following line subordinate to the information element of the JNLP file:
If you've hung around senior [-citizen] programmers (or are one), you've no doubt heard tales such as "we only had 4K of memory, and had to make use of every byte/bit". For your amusement and edification, here's a picture from thegreatgeekmanual.com of the first Apple II (circa 1977), which sported 4K of RAM:
Josh Marinacci's latest JavaFX Studio Challenge is a bit reminiscent of these good old days, in that he is encouraging us to create a cool JavaFX program that "must not be more than 30 lines of code or 3000 characters (your choice)".
The deadline for submitting your entry for this JFXStudio
Challenge is midnight Wednesday night (30 September 2009). The challenge is to create
something cool in only 30 lines of JavaFX Script code, using the theme
of 'Time'. To give you some ideas Josh has
posted his own entry. Take a look for some inspiration!
Speaking of contests, the winners of the WidgetFX Developers Contest were announced yesterday, and I'd like to convey my congratulations to Pär Dahlberg (ScreenshotFX widget), Yannick Van Godtsenhoven (RadioFX widget), and Larry Dickson (Weather widget). A screenshot of these widgets in the WidgetFX dock is shown on the right. I'm using the RadioFX widget as I'm writing this to listen to an internet music channel that plays baroque music.
Regards, and congratulations again to these winners!
If you've been following this blog recently, you know that I've been developing an application in the SpeedReaderFX category that helps me quickly keep up on new happenings in world events, technology, gadgets, music, and social networks. SpeedReaderFX is located in the JFXtras open source project, and also serves as an example of using JFXtras classes with JavaFX.
One of the newest enhancements made to the SpeedReaderFX program takes advantage of the Picker control that David Armitage created in the JFXtras project. Here's a screen shot of a program in the JFXtras project that he created to demonstrate and test the Picker control varieties:
SpeedReaderFX uses the spinner-style variation of the Picker control, shown in the Thumb Wheel row of the screen shot above. This enables SpeedReaderFX users to quickly choose how many entries of a given feed-type (e.g. Yahoo News feeds), to display in the scrolling table. The screen shot below shows a portion of the SpeedReaderFX Criteria dialog in which the Pickers appear:
As noted previously in the Picker demo screen shot, the user can operate the mouse wheel, as well as the keyboard or mouse buttons, to select the desired number.
For more information about the SpeedReaderFX application, check out the first post in the SpeedReaderFX category of this blog. To run the application, click the SpeedReaderFX icon located on the left side of this paragraph.
By the way, since this dialog is getting pretty crowded, Dean Iverson is creating some UI design comps in which each feed type will have its own page. I've also received other ideas from readers that I plan to implement, and would like to express my appreciation for your continued input!
When adding more feeds to the SpeedReaderFX application's Criteria dialog, I found that some of them don't *quite* comply with RSS/Atom formats. For example, I thought it'd be cool to have RSS feeds from Engadget and Gizmodo (what self-respecting geek wouldn't want those feeds?) I also wanted to have a daily dose of Dilbert delivered, but in these three cases the JavaFX RSS API reported that the dates for the feed items were just prior to the OS epoch.
Naturally, I consulted the RSS/Atom Smasher himself (Rakesh Menon) and he instructed me in the ways of creating a custom feed parser, which I added to SpeedReaderFX. Here's a partial screenshot of the Criteria dialog where you can select the Gizmodo, Engadget, TechCrunch, and Dilbert feeds:
For more information about the SpeedReaderFX application, check out the first post in the SpeedReaderFX category of this blog. To run the application, click the SpeedReaderFX icon located on the left side of this paragraph.
By the way, one of the next things that SpeedReaderFX needs is a GUI makeover (I've received lots of helpful feedback in that regard, and my graphical skills are admittedly weak). Please leave a comment to this post if you have ideas or graphic comps that would make this look more like an iPhone app. SpeedReaderFX is a sample in the open source JFXtras project, and your contributions to the appearance of this application would be much appreciated and acknowledged on this blog.
Congratulations to the winners of the JavaFX Coding Challenge: Sten Anderson, Naoaki Suganuma, and Evgeni Sergeev! The apps written by all three winners are oustanding, and 1st-place winner Sten Anderson'sMusic Explorer FX app is IMHO brilliant! Here's a screenshot from the app:
Sten's JavaFX app enables the user to navigate among musical artists whose music has a degree of similarity by making use of Echo Nest web services. The Music Explorer FX app has a graphically pleasing user interface that helps the user explore interesting details about musical artists and the connections between them. The Help page even features one of my favorite groups: Liquid Tension Experiment :-)
Having recently start tweeting, I am pleased to report on a couple of Twitter clients under development: TwitterFX and TweetBox.
TwitterFX is an open source project led by Steve Herod of Australia. At this writing it is v0.17, and already has a nice feature set. Here is a screenshot of TwitterFX:
Who are the tweeters in the screenshots?
Steve Herod: The leader of the TwitterFX project, and prolific blogger.
Silveira Neto: The Sun Tech Lead and CS Student in Brazil that I've featured on this blog in the context of game development. Coincidentally, he has a recent post entitled Reading Twitter with JavaFX in which he shows very succinctly how to do just that!
Alex Ruiz: The leader of the FEST-Swing and Fest-JavaFX projects, which provide a fluent interface for functional Swing GUI, and JavaFX GUI testing, respectively. Alex is located in the US as am I.
Andres Almiray: Groovy guru, speaker, and GUI builder aficionado. Located in the US, and has a very enlightening blog.
TweetBox is a JavaFX Twitter client being developed by IT 2.0 evangelist and Blokmark blogger Mark Nankmanof of the Netherlands.
I look forward to seeing the progress of both of these JavaFX Twitter clients!
Recent Comments