External Access - These are examples of modifying a X3D world from outside the world, similar to VRML97's EAI code.
All of these examples have been combined into a zip file. This file contains an Ant script that will automatically run Xj3D to show these examples. Please unzip this file into your Xj3D installation. It will make a directory called examples/sai. You can download Ant here: http://ant.apache.org/
The combined zip file is here: SAI Examples
To run the examples:
cd examples/sai
import org.web3d.x3d.sai.*;
public class SimpleSAIDemo extends JFrame {
public SimpleSAIDemo() {
Container contentPane = getContentPane();
// Create an SAI component
X3DComponent x3dComp = BrowserFactory.createX3DComponent(null);
// Add the component to the UI
JComponent x3dPanel = (JComponent)x3dComp.getImplementation();
contentPane.add(x3dPanel, BorderLayout.CENTER);
All the classes needed for sai access are located in the package org.web3d.x3d.sai. A handy reference
when writing SAI code is the Xj3D javadoc. You can access the online versions:
That's all there is to adding a 3D window to your application. The next section will show you how to a load a file.
The ExternalBrowser interface allows external SAI programs to access further functionality like pausing the rendering and adding browser listeners. You can only do these operations from external code. In order to load a file we will get a Browser reference from the X3DComponent we created.
// Get an external browser
ExternalBrowser x3dBrowser = x3dComp.getBrowser();
Once we have a browser reference we can use this to load a file into the browser. There a few methods of doing this. For this first example we will use the simplist version. Its a blocking call, createX3DFromURL which will not return till the file has been downloaded and is ready to display. This method return's an X3DScene object which encapsulates an X3D scene. We will then replace the current scene which is blank with this newly loaded scene.
// Create an X3D scene by loading a file
X3DScene mainScene = x3dBrowser.createX3DFromURL(new String[] { "moving_box.x3dv" });
// Replace the current world with the new one
x3dBrowser.replaceWorld(mainScene);
You can find the complete code for this example here SimpleSAIDemo.
![]() |
| Xj3D Component Example |
Once a world is loaded its typical to change something dynamically. If the node is DEFed in the X3D file then its easy to access it from SAI.
In this example we will find the Material named "MAT" and change its diffuseColor to blue. The file we will be modifying looks like this, basically a red moving box:
#X3D V3.0 utf8
PROFILE Interactive
DEF TS TimeSensor {
cycleInterval 10
loop TRUE
}
DEF TG Transform {
rotation 0 1 0 0.78
children Shape {
geometry Box {}
appearance Appearance {
material DEF MAT Material {
diffuseColor 1 0 0
}
}
}
}
DEF PI PositionInterpolator {
key [ 0 0.25 0.5 0.75 1 ]
keyValue [
0 0 0
-1 0 0
-1 1 0
0 1 0
0 0 0
]
}
ROUTE TS.fraction_changed TO PI.set_fraction
ROUTE PI.value_changed TO TG.translation
Notice the material node is DEFed as MAT, and its currently red. We can use the following code to change
the diffuseColor field from red to blue.
First we will find a node named MAT in the current scene. In this example we will use the main scene loaded from moving_box.x3dv. The getNamedNode method searches the specified scene and returns the named node or null if its not found.
// Find a node named MAT
X3DNode mat = mainScene.getNamedNode("MAT");
if (mat == null) {
System.out.println("Couldn't find material named: MAT");
return;
}
After we have the node we need to find the diffuseColor field and change its value. To get a field of a node
we call the getField method on the node. We then type that field to its X3D datatype, SFColor in this case.
Once we have the field we can query or set its value. Here we will create a local variable named blue and set the fields
value with it.
The X3D abstract specification defines the field names for each node. You must follow the X3D access model. This means that initializeOnly fields are only changeable before the node is realized. A node is realized once its added to a scenegraph or when its realized method is called. You can modify inputOnly and inputOutput fields anytime. outputOnly fields cannot be modified using the SAI.
// Get the diffuseColor field
SFColor color = (SFColor) mat.getField("diffuseColor");
// Set its value to blue
float[] blue = {0,0,1};
color.setValue(blue);
You can find the complete example in FieldAccessDemo.java
The first task when dynamically creating a world is to create a new X3DScene. Each X3DScene must pre-declare what Profile and Components it will use. When requesting a profile you must consider that the browser may not support that profile. If the profile you request is not supported it will throw a NotSupportedException. This example will exit if the Immersive profile is not supported.
ProfileInfo profile = null;
try {
profile = x3dBrowser.getProfile("Immersive");
} catch(NotSupportedException nse) {
System.out.println("Immersive Profile not supported");
System.exit(-1);
}
Once you have the ProfileInfo and ComponentInfo instances you need then you are ready to create the scene. The createScene
method creates an empty scene. It returns an X3DScene object.
X3DScene mainScene = x3dBrowser.createScene(profile, null);
We can now begin to create nodes and add them to our world. The createNode method is used to create new nodes. If the node
is not known it will throw a InvalidNodeException. This snippet shows a Shape node being created. Then a Box node is
created and set as the geometry field of the Shape. The shape node is then added to the root of our newly created scene.
X3DNode shape = mainScene.createNode("Shape");
SFNode shape_geometry = (SFNode) (shape.getField("geometry"));
X3DNode box = mainScene.createNode("Box");
shape_geometry.setValue(box);
mainScene.addRootNode(shape);
You can find the complete example in CreateNodeSAIDemo.java
#X3D V3.0 utf8
PROFILE Interactive
DEF TG Transform {
rotation 0 1 0 0.78
children [
Shape {
geometry Box {}
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
}
DEF TOUCH_SENSOR TouchSensor {}
]
}
The SAI code will use the addX3DEventListener method to listen for changes on the TouchSensor's touchTime
field. We will also need to implement the X3DFieldEventListener interface.
// Create an X3D scene by loading a file
X3DScene mainScene = x3dBrowser.createX3DFromURL(new String[] { "touchy_box.x3dv" });
// Replace the current world with the new one
x3dBrowser.replaceWorld(mainScene);
// Find a TouchSensor named TOUCH_SENSOR
X3DNode touch = mainScene.getNamedNode("TOUCH_SENSOR");
if (touch == null) {
System.out.println("Couldn't find TouchSensor named: TOUCH_SENSOR");
return;
}
SFTime ttime = (SFTime) touch.getField("touchTime");
// Listen for changes on the touchTime field
ttime.addX3DEventListener(this);
}
//----------------------------------------------------------
// Methods defined by X3DFieldEventListener
//----------------------------------------------------------
public void readableFieldChanged(X3DFieldEvent evt) {
System.out.println("Stop touching me!");
}
The complete example is located here: FieldEventDemo.java
This snippet of code finds the TouchSensor named TOUCH_SENSOR. It then gets its touchTime field and adds a X3DFieldEventListener. When this touchSensor is activated it will trigger the field listener.
X3DNode ts = mainScene.getNamedNode("TOUCH_SENSOR");
if (ts == null) {
System.out.println("Couldn't find TouchSensor named: TOUCH_SENSOR");
return;
}
// Get TOUCH_SENSOR.touchTime
SFTime touchTimeField = (SFTime) ts.getField("touchTime");
touchTimeField.addX3DEventListener(this);
Here we actually add the dynamic route. To add a route you need the two nodes to wire together and the
field names to use. Here we use getNamedNode to get the TimeSensor, PositionInterpolator and Transform that
we will route to. Two routes are added, one from the TimeSensor to the Interpolator. The other from the Interpolator
to the Transform. If you try to get an invalid route you will get one of the following exceptions: InvalidReadableFieldException,
InvalidWriteableFieldException or InvalidNodeException.
/**
* An X3D field has changed.
*
* @param evt The event.
*/
public void readableFieldChanged(X3DFieldEvent evt) {
// Find a timesensor named TIME_SENSOR
X3DNode TS = mainScene.getNamedNode("TIME_SENSOR");
X3DNode PI = mainScene.getNamedNode("PI");
X3DNode TG = mainScene.getNamedNode("TG");
mainScene.addRoute(TS,"fraction_changed",PI,"set_fraction");
mainScene.addRoute(PI,"value_changed",TG,"translation");
}
The complete example is located here: AddRouteSAIDemo.java
The main code of interest is in the findMaterial method. This method recursively walks the scengraph looking for Materal nodes.
/**
* Find all the material nodes and change them to red.
*/
private void findMaterial(X3DNode[] nodes) {
int len = nodes.length;
int types[];
X3DFieldDefinition[] decls;
int dlen;
X3DNode node;
for(int i=0; i < len; i++) {
node = nodes[i];
if (typeEquals(node, X3DNodeTypes.X3DMaterialNode)) {
// Get the diffuseColor field
SFColor color = (SFColor) node.getField("diffuseColor");
// Set its value to blue
float[] blue = {0,0,1};
color.setValue(blue);
}
The above code looks at each node and determines if its a Material node. If it is then it changes
its material color to blue.
decls = node.getFieldDefinitions();
dlen = decls.length;
int ftype;
int atype;
MFNode mfnode;
SFNode sfnode;
X3DNode[] snodes;
for(int j=0; j < dlen; j++) {
ftype = decls[j].getFieldType();
atype = decls[j].getAccessType();
if (atype == X3DFieldTypes.INPUT_OUTPUT || atype == X3DFieldTypes.INITIALIZE_ONLY) {
if (ftype == X3DFieldTypes.MFNODE) {
mfnode = (MFNode) node.getField(decls[j].getName());
snodes = new X3DNode[mfnode.size()];
mfnode.getValue(snodes);
findMaterial(snodes);
} else if (ftype == X3DFieldTypes.SFNODE) {
sfnode = (SFNode) node.getField(decls[j].getName());
snodes = new X3DNode[1];
snodes[0] = sfnode.getValue();
if (snodes[0] != null) {
findMaterial(snodes);
}
}
}
}
}
}
The above code gets the field declarations of a node. It determines that the field is readable(inputOutput or initializeOnly)
using the getFieldAccess method. Fields which are MFNode or SFNode are recursively traversed for further processing.
The getFieldType method returns a constant which is defined in the X3DFieldTypes interface.
The complete code for this example is located here: SAISGWalkDemo.java
| Method Summary | |
void |
eventsProcessed()
Notification that all the events in the current cascade have finished processing. |
void |
initialize()
Notificatoin that the script has completed the setup and should go about its own internal initialisation. |
void |
setBrowser(Browser browser)
Set the browser instance to be used by this script implementation. |
void |
setFields(X3DScriptNode externalView,
java.util.Map fields)
Set the listing of fields that have been declared in the file for this node. |
void |
shutdown()
Notification that this script instance is no longer in use by the scene graph and should now release all resources. |
These methods are called at the appropriate time in the X3D event model.
In this example we have defined the following interface to our script:
DEF S Script {
inputOnly SFTime pulse
outputOnly SFVec3f location
outputOnly SFRotation orientation
url [ "MoveObjectScript.class" ]
}
Here only the setFields and shutdown methods are needed. The setFields method gives the script access
to its fields. We will register interest for changes on the pulse field.
//----------------------------------------------------------
// Methods defined by X3DScriptImplementation
//----------------------------------------------------------
public void setBrowser(Browser browser) {
// ignored as we don't need any browser functionality
}
public void setFields(X3DScriptNode externalView, Map fields) {
// Get the fields we need for processing
location = (SFVec3f)fields.get("location");
rotation = (SFRotation)fields.get("orientation");
inputField = (SFTime)fields.get("pulse");
// Express interest in pulse changes
inputField.addX3DEventListener(this);
}
public void initialize() {
}
public void eventsProcessed() {
}
public void shutdown() {
inputField.removeX3DEventListener(this);
}
Every time the pulse field is changed we will flip between two locations. The following code shows this logic. Notice
that this interface is the same regardless whether you are internal or external to an X3D world.
//----------------------------------------------------------
// Methods defined by X3DFieldEventListener
//----------------------------------------------------------
public void readableFieldChanged(X3DFieldEvent evt) {
if(flip) {
flip = false;
location.setValue(otherLocation);
rotation.setValue(otherRotation);
}
else {
flip = true;
location.setValue(homeLocation);
rotation.setValue(homeRotation);
}
}
You can find the complete code for this example here MoveObjectScript
using this main world: move_object.x3dv
|
[
Xj3D Homepage |
Xj3D @ Web3d |
Download |
Screenshots |
Dev docs |
Dev Releases |
Conformance |
Contributors |
Getting Started |
Bug Tracking
]
Author: Alan Hudson |