"Create an application in JavaFX that exemplifies the appearance and behavior of a next-generation enterprise RIA (rich internet application)".
I am pleased to announce that the winning entry, the Beechcraft Cargoloader, was submitted by Abhilshit Soni. Here's a screen capture of Abhi's entry:
Abhilshit Soni is a 24 year old Software Engineer living in Mumbai, India. He holds a Bachelor of Technology in BioInformatics and is currently working with Zycus as a Software Engineer in the Product Engineering Team. He has been involved in developing web-based enterprise applications based on Java EE and Spring technologies for almost three years.
About Abhi's Application
According to Abhi, his Beechcraft Loader application "tries to solve a typical engineering problem of loading cargo containers into a Cargo aircraft. It needs a proper way in which your aircraft should be loaded with containers so that the resultant center of gravity of the aircraft is stable. For big cargo carriers like Boeing 747-400F or Airbus A380 there are set of calculations defined in their weight and balance handbook which are always calculated to ensure that center of gravity of the aircraft is and will be stable in all the conditions. This application tries to calculate the same for a relatively smaller Aircraft called BeechCraft 1900C by the Hawker-Beechcraft Corporation. The cargo aircraft manufacturer describes the operational center of gravity limits (varying based on the total aircraft weight) for a particular aircraft which can be viewed by the "Aircraft weight" vs "center of gravity" chart. The aim is to load aircraft in such a way that your current center of gravity falls between these operational limits, otherwise your aircraft is either nose heavy or tail heavy. This application considers only Beechcraft 1900C for cargo loading with a specific set of containers . All the equations and Aircraft master data are obtained from Chapter 7 of the FAA Aircraft Weight and Balance Handbook. The application currently does not cover all the functional use cases, like calculating lateral Center of Gravity and many other functional validations which are out of scope as of now".
Instructions for Using the Application
You can take the application for a test flight via Java Web Start by visiting Abhi's blog. Here are some pre-flight instructions from Abhi:
Drag a cargo container from the cargo panel on the left side.
Place it on any of the sections on bechcraft 1900C main deck in the upper center.
See the change in center of gravity in the data table.
Also see the change in weight v/s CG chart, and the weight coverage pie chart, in the lower right and left respectively.
To remove a dropped container double click it.
Change the container's position by by dragging from old position to new position.
To reset the plan click 'Reset Container' button on the toolbar in the center just below the Main deck.
To enable/disable Drag and Drop animation toggle 'Disable Animation' button on the toolbar in the center just below the Main deck.
To quick view/hide cargo weights toggle the "View Weights " button on the toolbar in the center just below the Main deck
Click on Weight Coverage chart or Weight vs CG chart to get a larger image.
The yellow dot in the Weight v/s CG chart represents the current center of gravity, and the polygons drawn in the chart are the operational limits provided by the aircraft manufacturer.
Check out Help and About buttons for more info about the application.
Closing Thoughts
Each of the participants were asked how their entry exemplifies the appearance and behavior of a next-generation enterprise RIA. Here is Abhi's response:
"To an end user the enterprise RIA is more about the way user sees the information shown in the application. All that an end user expects from an enterprise RIA is to access the information in the fastest and coolest looking way. The definition of coolest may be interpreted as drag drop interfaces, animation and effects, different data viewing formats, but not at the cost of performance. By using JavaFX I have tried to achieve the same to an extent by adding drag-drop spot data update in multiple formats like grid and charts, and also onclick zooms, animations and effects with performance as priority."
Also, per the contest rules, Abhi has submitted the source code of this application to the JFXtras open source project in the samples repository.
Congratulations to Abhi, and thanks to everyone who submitted an entry to this RIA Exemplar Challenge!
The previous blog post provided an example, named EarthCubeFX, of creating a 3D application with JavaFX 1.3.
Today's post highlights Oracle JavaFX engineer and author Jim Clarke's experience running EarthCubeFX on JavaFX-TV. To quote Mr. Clarke:
The tag line is "JavaFX: Bringing Rich Experiences to All the Screens of Your Life", so I decided to put it to the test. Jim Weaver created a 3D demo called EarthCubeFX, and I wanted to know what needed to be done for it to run on a true JavaFX-TV platform. Luckily, I had just obtained an Intel Canmore CE3100 system that runs JavaFX-TV.
Obviously, the TV does not have a mouse, so the first thing I had to do is use some of the keys on the remote control to invoke the same behavior that the mouse buttons activated in Jim's original code. The JavaFX-TV runtime maps the remote control keys to JavaFX KeyEvent objects. The first choice was to decide which keys to use for the pitch and roll rotations; the remote's arrow keys were the obvious choice. I arbitrarily picked the CHANNEL_UP and CHANNEL_DOWN keys to do the Z translation, which moves the cube further back or forward in the field of view. Lastly, I used the OK key to take the cube back to its home position. I only added 8 lines of code, compiled the application in NetBeans using the JavaFX-TV Emulator, then copied the Jar file over to the Canmore and ran it as shown in the video. The Google map tile images are still fetched over the Internet. The animation, and 3D features, run exactly as on the desktop.
Here's a short video that Jim Clarke created of EarthCubeFX running in JavaFX-TV on the Canmore set-top box:
Also, here is the code that Jim Clarke added to handle remote controller keys:
public function keyRotation(ke: KeyEvent) : Void {
if (ke.code == KeyCode.VK_RIGHT) {
angleY++;
}
else if (ke.code == KeyCode.VK_LEFT) {
angleY--;
}
else if (ke.code == KeyCode.VK_UP) {
angleX--;
}
else if (ke.code == KeyCode.VK_DOWN) {
angleX++
}
else if (ke.code == KeyCode.VK_CHANNEL_UP) {
translateZ -= 10;
}
else if (ke.code == KeyCode.VK_CHANNEL_DOWN) {
translateZ += 10;
}
else if (ke.code == KeyCode.VK_ENTER) {
goHomePosition();
}
}
Thanks, Mr. Clarke! Also, remember that the deadline for RIA Exemplar Challenge entries is 22-May-2010 so please submit it if you haven't already!
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.
"I am pleased to announce the 0.6 release of the open-source JFXtras project, the largest 3rd party library of JavaFX add-ons. This release is a very large undertaking that represents the work of several dozen contributors over the past 6 months.
JFXtras 0.6 will work with any JavaFX release in the 1.2 family. We recommend using the very latest release (currently JavaFX 1.2.3), which can be downloaded from JavaFX.com.
Starting immediately, we will be focusing on developing a JavaFX 1.3 compatible JFXtras release. Our plan is to release a 1.3 compatible version of JFXtras within 1 week of the official release announcement from Oracle."
Be sure to check out the JFXtras 0.6 announcement, which includes full details on this release, with single-click demos of the major features, such as the XShelfView [launch in Web Start]:
Congrats to Stephen Chin and JFXtras crew, which includes Keith Combs, Jim Clarke, Dean Iverson, David Armitage, Tom Eugelink, John Freeman, Peter Pilgrim, Christophe DUFOUR, Joshua Marinacci, Simon Morris, Andres Almiray, Jeff Friesen, Andrew Hughes, and Liu Huasong!
By the way, my next post on this blog will help announce the JavaFX SDK 1.3 release, which will start the 30-day clock ticking for the RIA Exemplar Challenge deadline. :-)
The JavaFXpert RIA Exemplar Challenge deadline will be extended to 30 days after the JavaFX 1.3 SDK is officially released. I'm making the deadline dependent upon the JavaFX SDK 1.3 release,
because based upon presentations at Devoxx and the Silicon Valley Java Users Group, the upcoming 1.3 release will have some features very relevant to the goals of this RIA Exemplar Challenge. For example, this release will have several new UI controls, as well as the ability to transform nodes on the Z axis.
As a reminder, although I still recommend that the participants of this contest consist of a developer and a graphic designer, I have removed this restriction. An individual developer can enter the contest, and win the 2000 USD. Please be aware that two of the four judges in the contest are graphic designers, so the user experience and visual design are important aspects of a winning entry.
If you have already submitted an entry to this challenge, please feel free to resubmit an updated entry closer to the new deadline.
Thanks for your understanding on extending the contest deadline. Although I can't officially enter the contest, this will also give me more time to work on my "entry" :-)
Recent Comments