Monday, July 14, 2008

Using the Java3D libraries in JavaFX

What is Java3D? It is an API enabling application that calls functions of the OpenGL and DirectX drivers to create and render 3D scenes on the screen.

You can download the free API at the Java3D project page: https://java3d.dev.java.net.

Example

The following files will appear after successful installation of the distribution kit:

C:\Program Files\Java\Java3D\1.5.1\lib\ext\j3dcore.jar
C:\Program Files\Java\Java3D\1.5.1\lib\ext\j3dutils.jar
C:\Program Files\Java\Java3D\1.5.1\lib\ext\vecmath.jar

These files should be included in the NetBeans project.

The API enables you to render a 3D scene on a Canvas3D class panel derived from the java.awt.Panel class.

In this demo, you will create a class inherited from Panel, and its createComponent method will return a javax.swing.JPanel object with the embedded Canvas3d object. You can test this approach on the rotating cub demo code available at the following page: https://j3d-webstart.dev.java.net/test.

Add a coulple of buttons to alter rotation direction. The figure below shows the final form.

Since scene rendering is executed by the co-processor, the speed of the cub rotation does not influence rendering quality.

Source

package javafx3d;

import javafx.ui.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.lang.Math;
import java.awt.BorderLayout;
import javax.swing.JPanel;

class RenderPanel extends Panel {
public attribute rotator:RotationInterpolator;
}
operation RenderPanel.createComponent() {
var config = SimpleUniverse.getPreferredConfiguration();
var c = new Canvas3D(config);
var univ = new SimpleUniverse(c);
univ.getViewingPlatform().setNominalViewingTransform();
univ.getViewer().getView().setMinimumFrameCycleTime(5);
var objRoot = new BranchGroup();
var objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(objTrans);
objTrans.addChild(new ColorCube(0.4));
var rotationAlpha = new Alpha( -1, 999);
rotator = new RotationInterpolator(rotationAlpha, objTrans);
var t = new Transform3D();
t.rotX(2);
var axis = new Transform3D(t);
rotator.setTransformAxis(axis);
var bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);
rotator.setSchedulingBounds(bounds);
objRoot.addChild(rotator);
objRoot.compile();
univ.addBranchGraph(objRoot);
var panel=new JPanel();
panel.setLayout(new BorderLayout());
panel.add(c, BorderLayout.CENTER);
return panel;
}
var renderPanel=new RenderPanel();
Frame {
visible: true
screenx: 50
screeny: 100
width: 600
height: 400
content: BorderPanel {
border: EmptyBorder {left: 16 top: 16 right: 16 bottom: 16}
center: BorderPanel {
background: new Color(1,1,1,1)
}
center: renderPanel
right: BorderPanel {
border: EmptyBorder {left: 16 top: 0 right: 0 bottom: 0}
top: GridPanel {
rows: 4
columns: 1
cells: [
Button {
text: "Horizontal"
action: operation(){
var t = new Transform3D();
t.rotX(2);
var axis = new Transform3D(t);
renderPanel.rotator.setTransformAxis(axis);
}
}
,Button {
text: "Vertical"
action: operation(){
var t = new Transform3D();
t.rotZ(2);
var axis = new Transform3D(t);
renderPanel.rotator.setTransformAxis(axis);
}
}
]
}
}
}
}

Database connection using the JavaFX Script compiler

//These are simple examples for using a database with the JavaFX Script compiler. The hsqldb.jar of hsqldb must be in the classpath.
// save as "SimpleDatabase.fx"

import java.sql.*;
import java.lang.*;
// Using the memory mode of hsqldb, so that the testdb is only
// created in RAM and no data is stored on disk.
var driverClassName = 'org.hsqldb.jdbcDriver';
var jdbcUrl = 'jdbc:hsqldb:mem:testdb';
var user = 'sa';
var password = '';
// Database objects
var driver:Driver = null;
var conn :Connection = null;
var stmt :Statement = null;
var rs :ResultSet = null;
var rows :Number;
try {
// Load driver
Class.forName(driverClassName);
try {
// Connect to database
conn = DriverManager.getConnection(jdbcUrl, user, password);
stmt = conn.createStatement();
// Create table for a Todo list
rows = stmt.executeUpdate("CREATE TABLE Todos(id BIGINT NOT NULL IDENTITY,
task VARCHAR(160))");
System.out.println("CREATE TABLE rows: {rows}");
// Insert three tasks
rows = stmt.executeUpdate("INSERT INTO Todos VALUES(1, 'do')");
System.out.println("INSERT rows: {rows}");
rows = stmt.executeUpdate("INSERT INTO Todos VALUES(2, 'did')");
System.out.println("INSERT rows: {rows}");
rows = stmt.executeUpdate("INSERT INTO Todos (task) VALUES('done')");
System.out.println("INSERT rows: {rows}");
// Select and print tasks
rs = stmt.executeQuery("SELECT * FROM Todos");
while(rs.next()) {
System.out.println("id: {rs.getInt('id')} task: {rs.getString('task')}");
}
} catch(e:SQLException) {
e.printStackTrace();
} finally {
if(null != rs){rs.close();}
if(null != stmt) {
try {
stmt.execute("SHUTDOWN"); // Clean up
} catch(e:SQLException) {
e.printStackTrace();
} finally {
stmt.close();
}
}// if(null != stmt)
if(null != conn){conn.close();}
}// finally
} catch(e:Exception) {
e.printStackTrace();
}

//A simple TODO list stored in a database.
//
save as "DatabaseTODO.fx"
import javafx.ext.swing.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import java.sql.*;
import java.lang.*;
/**
* Simple abstraction layer for the database.
*/
public class Database {
public attribute driverName: String;
public attribute jdbcUrl : String;
public attribute user : String;
public attribute password : String;
public attribute driver : Driver;
public attribute conn : Connection = null;
/**
* Connect to database.
*/
public function connect() {
// Load driver
Class.forName(this.driverName);
// Connect to database
this.conn = DriverManager.getConnection(this.jdbcUrl, this.user, this.password);
}// Database.connect
/**
* Close/shutdown connection to database.
*/
public function shutdown() {
var stmt: Statement = null;
if(null != this.conn) {
try {
stmt = this.conn.createStatement();
stmt.execute("SHUTDOWN"); // Clean up
} catch(e:SQLException) {
e.printStackTrace();
} finally {
if(null != stmt) {stmt.close();}
this.conn.close();
}
}// if(null != stmt)
}// public function.Database.shutdown
/**
* Check if the table exists.
* @param table Name of the table.
* @return true or false
*/
public function tableExists(table: String) {
var tableExists = false;
var dbmd = this.conn.getMetaData();
var rs = dbmd.getTables(null, null, '%', ['TABLE']);
while(rs.next()) {
if(table == rs.getString(3)) {
tableExists = true;
break;
}
}// while(rs.next())
return tableExists;
}// tableExists
}// Database
/**
* Single task in the Todo list
*/
class Task {
attribute id : Number;
attribute task: String;
}// Task
/**
* TODO list.
*/
class TODO {
attribute tasks : Task[];
attribute selectedTask: Integer;
attribute newTask : String;
attribute conn : Connection = null;
/**
* Load tasks from database.
*/
public function load() {
var stmt = this.conn.createStatement();
var rs = stmt.executeQuery("SELECT * FROM TODOS ORDER BY id ASC");
while(rs.next()) {
System.out.println("id: {rs.getInt('id')} task: {rs.getString('task')}");
insert Task{id: rs.getInt('id') task: rs.getString('task')} into this.tasks;
}
}// load
/**
* Add task to list/database.
*/
public function add() {
try {
var task = Task{task: this.newTask};
var stmt = this.conn.createStatement();
this.conn.setAutoCommit(false);
// Insert new task in database
var rows = stmt.executeUpdate("INSERT INTO TODOS (task) VALUES('{task.task}')");
System.out.println("INSERT rows: {rows} for {task.task}");
// Get id of the task from database
var rs = stmt.executeQuery('CALL IDENTITY()');
if(rs.next()) {
task.id = rs.getInt(1);
this.conn.commit();
insert task into this.tasks;
}// if(rs.next())
} catch(e:SQLException){
Dialog {
title : 'TODO - Add task'
background: Color.WHITE;
visible : true
content : Canvas{content: Text{content : "SQL: {e.getMessage()}"
textOrigin: TextOrigin.TOP}}
}// Dialog
/*MessageDialog {
messageType: ERROR
title : 'TODO - Add task'
message : "SQL: {e.getMessage()}"
visible : true
}// MessageDialog */
} finally {
this.conn.setAutoCommit(true);
}
}// add
/**
* Remove task from list/database.
*/
public function remove() {
if(sizeof this.tasks > 0) {
try {
var stmt = this.conn.createStatement();
var task = this.tasks[this.selectedTask];

var rows = stmt.executeUpdate("DELETE FROM TODOS WHERE id = {task.id}");
System.out.println("DELETE rows: {rows} for {task.task}");

delete this.tasks[this.selectedTask];
} catch(e:SQLException) {
Dialog {
title : 'TODO - Delete task'
background: Color.WHITE;
visible : true
content : Canvas{content: Text{content : "SQL: {e.getMessage()}"
textOrigin: TextOrigin.TOP}}
}// Dialog

/*MessageDialog {
//messageType: ERROR
title : 'TODO - Delete task'
message : "SQL: {e.getMessage()}"
visible : true
}// MessageDialog */
}
}// if(sizeof this.tasks > 0)
}// remove
}// TODO

// Database vars
var db : Database = null;
var stmt: Statement = null;
var rs : ResultSet = null;
var rows: Number;
db = Database{driverName: 'org.hsqldb.jdbcDriver'
jdbcUrl : 'jdbc:hsqldb:testdb/TODOOperations'
user : 'sa'
password : ''};
var model = TODO {
conn: bind db.conn
};
try {
// Connect to database
db.connect();
stmt = db.conn.createStatement();
// Create table
if(not db.tableExists('TODOS'))
{
rows = stmt.executeUpdate("CREATE TABLE TODOS(id BIGINT NOT NULL IDENTITY,
task VARCHAR(160))");
System.out.println("CREATE TABLE rows: {rows}");

rows = stmt.executeUpdate("INSERT INTO TODOS VALUES(1, 'do')");
System.out.println("INSERT rows: {rows}");

rows = stmt.executeUpdate("INSERT INTO TODOS VALUES(2, 'did')");
System.out.println("INSERT rows: {rows}");

rows = stmt.executeUpdate("INSERT INTO TODOS (task) VALUES('done')");
System.out.println("INSERT rows: {rows}");
}// if(not db.tableExists('TODOS'))

model.load();

} catch(e:SQLException) {
e.printStackTrace();
}

Frame {
closeAction: function(): Void {db.shutdown(); java.lang.System.exit(0);}
title : 'Database: TODO list'
background : Color.WHITE;
visible : true
content: BorderPanel {
center: List {
selectedIndex: bind model.selectedTask with inverse
items: bind for (task in model.tasks)
ListItem {
text: task.task
}
}// List
bottom: FlowPanel {
content: [
TextField {
columns: 30
text : bind model.newTask with inverse
}, // TextField
Button {
text : 'Add'
enabled: bind model.newTask.length() > 0
action : function() {
model.add();
model.newTask = '';
}
}, // Button

Button {
text : 'Delete'
enabled: bind sizeof model.tasks > 0
action : function() {
model.remove();
}// Button
}
]// content
}// FlowPanel
}// BorderPanel
}// Frame

sample code on JavaFX i.e., Fullscreen window, center text in nodes, Dynamic menu with alert window, using functions as parameters, FXShell class prov

//*** Sample Code: Dynamic menu with alert window
//Dynamic menu. Alert window with item number is displayed when a menu item is clicked.

import javafx.ui.*;
import java.lang.System;

class AlertModel {
attribute AlertText:String;
attribute ShowAlert:boolean;
}
var am = AlertModel {
AlertText: "Text"
ShowAlert: false
};
class MenuSource {
attribute items:MenuItem*;
attribute malert:AlertModel;
operation addNewItem(mtext:String,mcommand:String);
operation menuCommand(item:String);
}
trigger on new MenuSource {
for (i in [0..3]) {
addNewItem("Item {i}","{i}");
}
}
operation MenuSource.menuCommand(item:String) {
System.out.println(item);
malert.AlertText="Item {item} clicked";
malert.ShowAlert=true;
}
operation MenuSource.addNewItem(mtext:String , mcommand:String ) {
var mi = MenuItem {
text: mtext
action: operation() {
menuCommand(mcommand);
}
};
insert mi as last into items;
}
var ms = new MenuSource;
ms.malert=am;
var menu = MenuBar {
menus: Menu {
text: "Test"
items: bind ms.items

}
};
var del_button = Button {
text: "Press to delete"
action: operation() {
var n = sizeof ms.items - 1;
delete ms.items[indexof . == n];
}
};
var add_button = Button {
text: "Press to add"
action: operation() {
var n = sizeof ms.items;
ms.addNewItem("Item {n}" , "{n}");
}
};
var alert = Frame {
visible: bind am.ShowAlert
content: BorderPanel {
top: Label { text: bind am.AlertText }
bottom: Button {
text: "Close"
action: operation() {
am.ShowAlert=false;
}
}
}
};
Frame {
menubar: menu
content: BorderPanel { top: del_button
bottom: add_button }
visible: true
onClose: operation() {System.exit(0);}
}

//how to center text in nodes.

import javafx.ui.*;
import javafx.ui.canvas.*;

var contentText = "Sardegna"; //very nice island, go there once ;-)
var myFont = Font {face: VERDANA, style: [BOLD], size: 50};

Frame {
content:
Canvas {
var: canvas
height: 70
width: 450
border: null
content:
Group {
var w = bind canvas.width
var h = bind canvas.height
content:
[Rect {
width: bind w
height: bind h
fill: LinearGradient {
x1: 0
y1: 0
x2: 1
y2: 0
stops:
[Stop {offset: 0, color: green},
Stop {offset: .5, color: new Color(.5, 1, 0, 1)},
Stop {offset: 1, color: green}]
}
stroke: green
strokeWidth: 1
},
Text {
transform: bind translate(w/2, h/2)
//verticalAlignment: BASELINE
valign: MIDDLE
halign: CENTER
content: contentText
font: myFont
fill: darkgreen
}]
}
}
}

// This is a simple example showing "Hello World" centered on a fullscreen frame. This sample is broken due to language changes.
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;
import java.awt.Toolkit;

Frame {
var s = Toolkit.getDefaultToolkit().getScreenSize()
title: "Hello"
undecorated: true
background: white
width: s.width
height: s.height
content: Canvas {
content:
Text {
var: hello
x: bind (s.width / 2) - (hello.currentWidth / 2)
y: bind (s.height / 2) - (hello.currentHeight / 2)
content: "Hello World!"
font: Font {face: VERDANA, style: [ITALIC, BOLD], size: 60
}
}
}
visible: true
}
//This is a simple example about using functions as parameters.
import java.lang.System;
function reduce(f: function(a:*,b:*), list: **, nilArg: *) {
return if( list == [] ) then
nilArg
else
f( list[indexof . == 0],
reduce(f, list[indexof .> 0], nilArg));
}
function sum( x: Number, y: Number ): Number {
return x+y;
}
function product( x: Number, y: Number ): Number {
return x*y;
}
var list = [1,2,3,4];
operation p( s: String ) { System.out.println( s ); }
p("# sum : {reduce(sum,list,0)}");
p("# product : {reduce(product,list,1)}");
//***********************//

//The simplest, though unsupported, way is by calling the main() method in the FXShell class provided by the JavaFX jars:
import net.java.javafx.FXShell;

public class FxMainLauncher {
public static void main(String[] args) throws Exception {
FXShell.main(new String[] {"HelloWorld.fx"});
}
}

WinFX: An All-Managed API

In Longhorn, Win32 will no longer be the principal API. It will, of course, continue to be supported; 20-year-old DOS applications still run on the latest version of Windows, and likewise, Win32 applications will also continue to work for the foreseeable future. But just as DOS and 16-bit Windows applications were superseded by Win32 applications, so in Longhorn will Win32 become the "old way" of doing things. In Win32's place is a new API called WinFX (pronounced "Win Effects"). WinFX is a significant milestone in the history of the Windows API, as it puts .NET at the center of the platform. While Win32, the main API for previous versions of Windows, is a C-style API, WinFX is designed primarily to be used by .NET languages. In other words, it is a managed API. Moreover, it is a superset of the existing .NET Framework.

Judging by the discussions on various newsgroups and mailing lists, many seasoned Windows developers seem to have a hard time believing that Longhorn's new APIs really will be pure .NET. The topic has come up almost every day on the various WinFX Microsoft newsgroups since the API's announcement, and it seems that many people think that a .NET-only API cannot possibly exist, and that there must be a 'real' Win32 API underneath it all.

The idea that Win32 must be lurking somewhere beneath the covers presumably originates in part because of the way today's .NET Framework is implemented. Many of the classes in the Framework Class Library are wrappers on top of Win32 functionality. For example, Windows Forms puts a .NET face on classic Win32 features such as HWND, MSG, and WndProc. Likewise, the various classes in System.Net and System.Net.Sockets ultimately wrap the services provided by the Windows Sockets API in Win32.

However, there's no technical requirement for new WinFX functionality to wrap a corresponding Win32 API. Indeed, we can look to existing Windows and .NET technology to see why this need not be the case.

Platform Layers

It is tempting for experienced Windows developers to think of Win32 as the fundamental API of Windows. All of the unmanaged language runtimes shipped by Microsoft to date rely on Win32, even though they may hide it behind some language-specific wrapper such as the standard C library, or the unique world view that is Visual Basic 6. Similarly, the .NET Frameworks that have shipped so far all rely on Win32 as well. However, Win32 is not an island.

Win32 itself relies on underlying services from the Windows kernel. If you look inside some of the DLLs that implement the Win32 API (such as User32.dll) you will find that many of the functions have very short implementations; they use the Pentium's INT instruction to make a system call that does the real work.

One of the reasons for this is that Windows NT was originally designed to support multiple 'subsystems'. Win32 was just one of these. The first versions of NT also shipped with an OS/2 subsystem and a POSIX subsystem, each of which presented a completely different API, but all of which ran on top of the same set of system services. Of course, OS/2 and POSIX are no longer especially relevant in Windows, making the subsystem support seems like a historical footnote. However, there are still good reasons for certain Win32 APIs to be implemented as little more than a system call, even if subsystems had never been invented, as there are some system services that can only be implemented in the kernel itself.

It has always been possible to bypass Win32 and use the Windows kernel services directly. However, developers rarely do this because such code is unlikely to be portable from one version of Windows to the next. Not only is this low-level API not documented, it can also differ from platform to platform. The kernel in the Windows NT, 2000, and XP product line is very different from the underlying kernel in Windows 95, 98, and ME. Each version of Windows has its own implementation of the Win32 DLLs (GDI32.dll, User32.dll, and so on) to bridge the gap between the common public Win32 API and the platform-specific, low-level system API.

For applications that need to run on more than one version of Windows (that is, most Windows applications) it makes no sense to try and use the low-level system calls. However, WinFX will be installed as part of the operating system just like Win32 is today. This means that unlike the current redistributable versions of the .NET Framework, any particular version of WinFX will not need to be able to work on multiple versions of Windows. The relationship between WinFX and the OS kernel can become much more like the relationship between, say, User32.dll and the OS kernel. (Nobody expects to be able to copy the Windows 98 version of User32.dll onto their Windows XP machine and have it work. The same will be true of the files that make up WinFX.)

Because WinFX will be a part of the OS, it will be able to have a much closer relationship with the low-level system services. In theory, WinFX could act as a peer of Win32 rather than having to be its client; it could effectively be a distinct subsystem. In practice, that's unlikely to happen any time soon for two reasons. First, where Win32 already provides the necessary services, there seems little point in WinFX reinventing the wheel. So expect those parts of the .NET Framework that are wrappers around Win32 (such as Windows Forms) to remain so for the foreseeable future. The second reason for not making WinFX an entirely independent subsystem is that P/Invoke would be tricky to implement if Win32 wasn't still there somewhere.

Nevertheless, although we are likely to carry on seeing wrappers where Win32 already provides appropriate services, there's no reason for new services to be exposed at the Win32 level and then wrapped by WinFX. For platform services that are new to Longhorn, their only public API will be in WinFX. There may be corresponding undocumented system calls used by WinFX (just as there are today for many Win32 APIs) but there is no reason for there to be an equivalent new public Win32 API; Longhorn can cut out the middle man and have WinFX make system calls directly. In the long term, we may even see Win32 relegated to an isolated subsystem just as the old 16-bit world is today, only supported for the benefit of legacy applications that still depend on it. We have seen this kind of transformation where the wrapper and the underlying API swap places once before in Windows. Remember Win32s include a set of wrappers for the Windows 3.1 API, enabling 32-bit applications to run on 16-bit systems. In some ways, the current .NET Frameworks are reminiscent of Win32s: they allow applications to use the new API even though the underlying OS is fundamentally rooted in the old way of doing things. Over time, we saw DOS and 16-bit Windows cease to be the underlying platform, while Win32 transformed from a wrapper into a native API. WinFX marks the start of a similar transition, with .NET turning from a wrapper into the native API. .NET need not be a set of wrappers any more than Win32 needs to be a set of wrappers for 16-bit versions of Windows.

Pure Managed Functionality

The ability of WinFX to link directly to system services, bypassing Win32, is not the only way in which new Longhorn features can be exposed in WinFX but not Win32. Many features will be implemented entirely in managed code with no need for any help from lower levels of the platform at all.

There are already many examples of pure managed features in the shipping versions of .NET Framework. For example, you can make extensive use of the XML functionality in the current versions of .NET without the framework ever making a call into Win32. Likewise, although ASP.NET integrates with IIS in the Win32 world, the vast majority of the value that it adds over the unmanaged ISAPI extension mechanism is implemented in managed code. The ASP.NET page caching mechanism has no Win32 equivalent, neither do the key parts of its object model. (HttpRequest, HttpResponse, and friends may look reminiscent of the classic ASP objects, but they are not wrappers. They were designed to look very similar to the ASP object, in order to ease the transition to ASP.NET, but they are all implemented entirely in managed code.)

The same will be true for much of the new functionality in Longhorn. There will, of course, be some things which involve new platform features all the way from WinFX's public API right down to kernel mode-- the new 'Avalon' graphics model being the most obvious example. However, there will be many more features implemented entirely in managed code. For example, although Avalon relies on platform support at all levels for performance reasons, a considerable amount of its functionality will reside entirely in managed code. (It would be rather inefficient if every single interaction your code had with Avalon objects caused calls all the way down into the lowest levels of the OS.)

Conclusion

To view the .NET Framework as being merely a wrapper around Win32 is to ignore a large part of the benefit that it offers. Even with the versions shipping today, there is a considerable amount of functionality that is implemented entirely in managed code. This will be true to an even greater extent in WinFX on Longhorn. Moreover, where new features require support from the lower layers of the platform, WinFX will not need to have a Win32 API that it can wrap, as it will be able to use the low-level system API directly, just as Win32 does today. For anyone writing Windows applications today, the message is clear: managed code is the way of the future.