Skip to main content

Introducing the SceneGraph Project

Posted by hansmuller on January 8, 2008 at 6:50 PM PST

Introducing SceneGraph

I haven't written a blog entry since January when I advertised the fledgling
href="http://weblogs.java.net/blog/hansmuller/archive/2007/01/application_fra.html">
Swing Application Framework (JSR-296)

project with an uncharacteristically brief item. Work on that
kept me busy until summer, when the Java FX juggernaut got
underway here at Sun. Since then I've devoted much of my time
to leading a project we've called "Scenario" that provides the
graphical runtime for Java FX Script. We're now a public
project on java.net called

scenegraph.dev.java.net
.
You'll find downloads of the the 0.4.1 version of the Scenario
source and binaries and (very) sketchy javadoc on the site. The
0.x version number is intended to convey the fact that the API
hasn't stabilized yet. It's sufficient for experimentation and
the implementation was robust enough to support a port of the
Java FX Script interpreter, which we've showcased with a
Scenario version of the

FXPad Demo
.
Obviously, we don't recommend putting anything based on Scenario
into production. Not yet.

The code is being made available under the GPLv2 license.
Passion about open source licenses is not something I possess so
I'll leave the shouting about the implications of this choice to
others. Suffice it to say that, per my limited understanding of
these matters, you're free to share the code, and in the process of
developing it further. We're moving our discussions of the API
to the newly minted

Scene Graph java.net forum
.
If you're interested in the project's evolution, that would be a
good place to start looking.

There are quite a few engineers working on Scenario, most of
whom have made bigger contributions to the new software than I
have, and you'll be hearing from them in their own blogs before
too long. For now, what I'd like to do is to provide an
introduction to the new Java APIs and just one demo. The team
has written a whole raft of demos and we'll be opening up a
subproject before too long, that contains the entire
demo catalog.

Demo

All of the examples that follow are part of a demo, each one
occupies a tab. If you press "control-T" after clicking on a
demo, you'll get a nice interactive tree view of the scene
graph's structure, thanks to

Amy Fowler
for that! So press the orange button to launch the demo.


Launch Button src="http://weblogs.java.net/blog/hansmuller/archive/jws-launch-button.png"
width="88"
height="23"
style="border-style:none"/>

Note also: the demo scales the selected example scene to fit.
This is done with a small extension to JSGPanel,
take a look at the

code

if you're interested.

Basics, Hello World

Intro Screenshot1 src="http://weblogs.java.net/blog/hansmuller/archive/intro-screenshot1.PNG"
width="290"
height="195" />

A scene graph, really a tree for now, is a data structure you
create from leaf nodes that represent visual elements
like 2D graphics and Swing components, filter nodes that
represent visual state, like 2D transforms and composition, and
group nodes that manage a list of children. All nodes
are subclasses of SGNode and have a parent node,
rectangular bounds, visibility, and support for event
dispatching. Leaf nodes extend SGLeaf and have
paint/repaint methods similar to
AWT/Swing. SGFilter nodes have one child, and
SGGroups have a list of them. To display a scene
graph you set the scene property of a
JSGPanel, and add the panel to a Swing application
in the usual way. Here's a simple example:

SGText text = new SGText();
text.setText("Hello World");
JSGPanel panel = new JSGPanel();
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
// create a JFrame, add the panel to it, etc..
   

One unusual line from the previous example, from a Swing
programmer's perspective, is that we've explicitly set the
preferred size of the JSGPanel. Although the
JSGPanel will compute (and recompute...) a preferred size based
on bounds of its scene, it's usually a good idea to define a
reasonable fixed preferred size instead. To make the scene
slightly more interesting to look at, we can set the
SGText node's font and color, turn on antialiasing,
and configure the panel's background color:

SGText text = new SGText();
text.setText("Hello World");
text.setFont(new Font("SansSerif", Font.PLAIN, 36));
text.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
text.setFillPaint(Color.WHITE);
JSGPanel panel = new JSGPanel();
panel.setBackground(Color.BLACK);
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
   

More Hello World: Groups and Shapes

Intro Screenshot2 src="http://weblogs.java.net/blog/hansmuller/archive/intro-screenshot2.PNG"
width="290"
height="195" />

Most of the Scenario classes are simple Java Beans. They
provide null (no parameters) constructors and mutable
properties. The API is intended to be "minimal" in the sense
that it exposes the capabilities of the underlying Java 2D and
Swing classes but does attempt to substantially simplify or
abstract them. Scenario is intended to serve as the basis for
higher level abstractions that require a scene graph, notably
Java FX Script. For that reason, you may find programming
against the Scenario API directly to be a bit tedious. Creating
your own abstractions that simplify creating scene graphs is
definitely the order of the day.

To add a border to the simple Hello World scene, we'll write a
method that takes any scene graph node and puts a garish rounded
rectangle border behind it. This example demonstrates using the
SGShape leaf node which can render any Java 2D
href="http://java.sun.com/javase/6/docs/api/java/awt/Shape.html">
Shape
,
like lines, arcs, bezier curves, and of course rounded
rectangles. Just to highlight the support for both
filling and stroking (drawing the outline of)
shapes, we'll do both here. The createBorder
method creates a red and yellow rounded rectangle that's a
little bigger than the node it's given. It returns an
SGGroup scene graph node that contains both the
original node and the "border".

SGNode createBorder(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double borderWidth = 10;
    double x = nodeR.getX() - borderWidth;
    double y = nodeR.getY() - borderWidth;
    double w = nodeR.getWidth() + (2 * borderWidth);
    double h = nodeR.getHeight() + (2 * borderWidth);
    double a = 1.5 * borderWidth;
    SGShape border = new SGShape();
    border.setShape(new RoundRectangle2D.Double(x, y, w, h, a, a));
    border.setFillPaint(new Color(0x660000));
    border.setDrawPaint(new Color(0xFFFF33));
    border.setDrawStroke(new BasicStroke(borderWidth / 2.0));
    border.setMode(SGShape.Mode.STROKE_FILL);
    border.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON);
    SGGroup borderedNode = new SGGroup();
    borderedNode.add(border);
    borderedNode.add(node);
    return borderedNode;
}
   

Adding a the border doesn't change the code that creates the
scene graph very much:

SGText text = new SGText();
text.setText("Hello World");
// same as before ...
panel.setScene(createBorder(text));
   

Rotating Hello World: Transforms

Intro Screenshot3 src="http://weblogs.java.net/blog/hansmuller/archive/intro-screenshot3.PNG"
width="290"
height="195" />

As you can see in the previous example, the
SGNode#getBounds() method returns a bounding box
for the its node, in the way same way
Component#getBounds() does for AWT and Swing
components. In AWT and Swing, a component's parent node
recursively defines its origin in terms of a translation, which
is the value of getParent().getLocation(). Scene
graphs are much more flexible than that. The relationship
between a child and its parent node can be defined with any 2D
affine transformation.

To rotate a scene graph node around a point, you have to
assemble a chain of three transforms that: translate the node so
that the rotation point is at the origin, rotate the desired
amount, translate the node back to its original location.
Transforms are created with SGTransform nodes,
which are SGFilter subclasses because they have
just one child, which is the node the transform is to be
applied to. Here's a method that creates such a chain and uses
the node's bounds' center as the rotation point:

SGTransform createRotation(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double cx = nodeR.getCenterX();
    double cy = nodeR.getCenterY();
    SGTransform toOriginT = SGTransform.createTranslation(-cx, -cy, node);
    SGTransform.Rotate rotateT = SGTransform.createRotation(0.0, toOriginT);
    return SGTransform.createTranslation(cx, cy, rotateT);
}
   

To use the createRotation method we apply it to the
node that's going to spin around its center, and then add the returned
value to the scene instead of the node itself. The return
value is the chain of three transform nodes followed by the
original node. To specify a rotation you have to refer to the
second SGTransform.Rotate from the chain and change
its rotation property:

SGTransform scene = createRotation(node);
SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
rotateT.setRotation(...);
   

Images and More Layout

Intro Screenshot4 src="http://weblogs.java.net/blog/hansmuller/archive/intro-screenshot4.PNG"
width="290"
height="195" />

Images can be incorporated in a scene graph with the
SGImage node type. To add one to our scene so that
it appears to the right of the "Hello World" text, we'll have to
create an SGGroup node that contains the text and
the image, and then use SGTransform nodes to
arrange the group's children along a row. Here's a method that
creates such a group:

SGNode createRow(SGNode... nodes) {
    double rowHeight = 0.0;
    for(SGNode node : nodes) {
        rowHeight = Math.max(rowHeight, node.getBounds().getHeight());
    }
    SGGroup row = new SGGroup();
    double x = 0.0;
    double gap = 8.0;
    for(SGNode node : nodes) {       
        Rectangle2D nodeR = node.getBounds();
        double y = (rowHeight - nodeR.getHeight()) / 2.0;
        double dx = x - nodeR.getX();
        double dy = y - nodeR.getY();
        SGTransform xlate = SGTransform.createTranslation(dx, dy, node);
        row.add(xlate);
        x += nodeR.getWidth() + gap;
    }
    return row;
}
   

The code to create the SGImage node and the overall scene looks
like this:

SGNode createEarth() {
    BufferedImage image = null;
    // ... code to load the image file
    SGImage sgImage = new SGImage();
    sgImage.setImage(image);
    return sgImage;
}
// ...
SGNode row = createRow(createHelloWorldText(), createEarth());
SGNode scene = createBorder(row);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
   

Handling Input

Intro Screenshot5 src="http://weblogs.java.net/blog/hansmuller/archive/intro-screenshot5.PNG"
width="290"
height="195" />

All nodes can handle mouse and keyboard events and the support for
doing so is very similar to what AWT/Swing provides. For
example to make a node handle mouse events you add an
SGMouseListener. SGMouseListener
combines the methods from AWT's MouseListener and
MouseMotionListener and it adds an
SGNode argument to each one. SGKeyListener
and SGFocusListener are similar. The source of a scene
graph mouse or keyboard event is always a JSGPanel and the
additional node argument indicates the node the event was
actually dispatched to.

In this example, we've added a mouse listener to the
earth node in the scene from the previous example and restored
the support for rotating the scene. Dragging the earth with the
mouse rotates the scene. Here's the code that sets up the scene
and its SGMouseListener:

SGNode earth = createEarth();
SGNode row = createRow(createHelloWorldText(), earth);
SGTransform scene = createRotation(createBorder(row));
final SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
SGMouseListener changeRotation = new SGMouseAdapter() {
    @Override public void mouseDragged(MouseEvent e, SGNode node) {
        Rectangle2D r = e.getComponent().getBounds();
        double x = e.getX() - r.getCenterX();
        double y = e.getY() - r.getCenterY();
        rotateT.setRotation(Math.atan2(y, x));
    }
};
earth.addMouseListener(changeRotation);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
   

Although there's a great deal more to say by way of introducing
Scenario, this blog entry has grown long enough that I'd better
just take the same tack
href="http://weblogs.java.net/blog/chet/archive/2008/01/been_there_scen.html">
that Chet did
,
and declare this "Part 1".

The code for the examples can be found here:

http://weblogs.java.net/blog/hansmuller/archive/Intro.java

A easy to build version will appear along with the other
Scenario demos, later this month.

Comments

Posted by: diverson on January 09, 2008 at 01:37 PM Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs.

I agree: caching the relatively static parts of the scene graph in a form that's much closer to what's needed for rendering is definitely the path to better performance. We're working on that.

Posted by: pdoubleya on January 09, 2008 at 02:16 AM Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill?

For the black filled arcs that appear in the "smiley" demo I didn't describe, the AA flag will cause the value of pixels that only partially overlap the curve to have an interpolated value that's relative to the color already there. You can see it if you use a screen magnifier.

About enabling AA in general: I agree that doing so per node is clunky, albeit flexible. It would be useful to be able to enableAA(), as you say, for a subtree.

The problem with control-T appeared to have been that the demo never requested focus. That was easily fixed however when I tried the (supposedly) updated app, no soap. I was somewhat busy with another project at the time and asked Igor Kushnirskiy to take a look at the problem. He pointed out that I hadn't been updating the right .jar file. He might have also pointed out that I'm a knucklehead, but tactful man that he is, he let the facts speak for themselves.

So the good news is that Amy's scene graph viewer now appears on control-T, if you've clicked on the demo first.

java.security.AccessControlException: access denied (java.util.PropertyPermission com.sun.scenario.animationTimeline.nogaps read) at java.security.AccessControlContext.checkPermission(Unknown Source) at java.security.AccessController.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPropertyAccess(Unknown Source) at java.lang.System.getProperty(Unknown Source) at java.lang.Boolean.getBoolean(Unknown Source) at com.sun.scenario.animation.MasterTimer.(MasterTimer.java:54) at com.sun.scenario.animation.Timeline.addFrameJob(Timeline.java:52) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:53) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:45) at com.sun.scenario.scenegraph.JSGPanel.markDirty(JSGPanel.java:525) at com.sun.scenario.scenegraph.JSGPanel.setScene(JSGPanel.java:102) at demo.intro.Intro$ScalingSGPanel.setScene(Intro.java:366) at demo.intro.Intro.createMainPanel(Intro.java:336) at demo.intro.Intro.startup(Intro.java:347)

OK: control-T doesn't work for me either! It does work when I launch the application locally, but not with the web started version. Will figure what's wrong, sorry about that.

If you check the MasterTimer.java source (Joe-Bob says check it out!), you'll find that the exception is a harmless diagnostic. I've asked that it be removed, since everyone (including myself) assume it is a fatal error.

Control-T doesn't seem to do anything? (at least on my system: Windows XP, Java 6 (1.6.0_03))

CTRL T does not work on OSX either. And I have the same exception

Hi Hans--nice overview, thanks.

I have the same problems with the demo--Ctrl T doesn't work for me. But the panels display correctly.

Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill?

The AA method call makes me think there's room for (maybe the community to offer) some cover methods like "enableAA()" as an alternative to "setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON)". The number of method calls involved makes me think an "extension" API designed with fluent interfaces might make this more fun to program with--JMock has been a real inspiration for me as regards fluent interfaces. Those are minor, and something a community member could take on to simplify use of the API.

Looking forward to looking into the framework further, keep up the good writing.

Thanks, Patrick

I get the same exception under linux Java Web Start 1.6.0_03
Using JRE version 1.6.0_03 Java HotSpot(TM) Server VM and ctrl+t doesn't work for me either.

Also the spacing between the characters in rotated text seems to be affected by rounding errors of some kind, this is most noticeable under the mouse tab where the characters seem to 'wobble' when rotated by small values. Other than that I'm looking forward to what comes out of this project.

Datapoint- Ctrl-T worked fine. mouse didn't respond to mouse at all. XP64 / 6N / run as webstart

Nice, Definately Nice...Sun is definately stepping is its game with this JavaFx thing...No frills just JAVA

Thanks for the intro, Hans.

Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs.

When I resize the frame I notice some lines flashing around the edges, giving it a not so smooth resizing feel. Otherwise it looks great. I am using Windows XP with Update N b08.

I've removed the alarming stack trace print that occurs when MasterTimer can't read it's System properties. It was harmless and unneccessary. I wish we had a better way to test sandboxed applications.

It is rather difficult to build and manipulate with scene graph using AffineTransform nodes. I think it may be simplified for example by integrating of SGNode with SGTransform allowing user to use simple bean methods like setX(), setY(), rotate(), scale(), etc... The main reason is that every "visual" node must be positioned inside scene and thus will always have corresponding translation node.