Customising Xj3D's Functionality

A customised loader is used when you want to build your own browser or have a more complete runtime environment than what a standard Java3D/Aviatrix3D loader will give you. Going down this route requires you to do a lot more work than the simple Loader code. Be prepared to get your hands very dirty (not to mention sometimes rapidly changing internal APIs)!

Note: If you would like to view the processes that are described here, the example browser code in the examples/browser and the class DIYBrowser are used as the basis for this commentary

Preparations to load

Running a custom loader requires a number of setup steps to make sure that all the right information can be found when required.

System properties

A number of system properties need to be set before executing any other code. The first system properties that you need control what sort of resolution services you will use for URN and URL handling. Internally the code uses a 3rd-party library for resolving URNs (for example, universal media references). This library needs to know what resolvers are available for it's own use, and these are defined in a set of system properties.

There are two properties to set uri.content.handler.pkgs and uri.protocol.handler.pkgs. The former is used to set the list of base package names to find content handlers (classes that transform raw bytes into Java objects). The latter is used to control the base package that holds the handlers for different network protocols, such as HTTP, FTP etc. The default install comes with a set of standard implementations and using the following two lines will allow you to run the code with no problems.

  System.setProperty("uri.content.handler.pkgs", "vlc.net.content");
  System.setProperty("uri.protocol.handler.pkgs", "vlc.net.protocol");
If you know how content handlers work, then these can easily be extended by adding more base packages to the lists, but separating each package name with a pipe character '|' in the property value.

Customer Handlers

If you need to load files from disk or support Inlines or Externprotos, then we also add to the system some of our own custom handlers. To enable this, code needs to be added to the underlying URN system to handle VRML files. Two factories need to be registered one for the content handler, and one for filename mapping. These two classes can be found in the org.web3d.net.content package. These are registered with the URI class from the org.ietf.uri package.
  ContentHandlerFactory fac = URI.getContentHandlerFactory();
  if(!(fac instanceof J3DContentHandlerFactory)) {
    fac = new J3DContentHandlerFactory(fac);
    URI.setContentHandlerFactory(fac);
  }

  FileNameMap fn_map = URI.getFileNameMap();
  if(!(fn_map instanceof VRMLFileNameMap)) {
    fn_map = new VRMLFileNameMap(fn_map);
    URI.setFileNameMap(fn_map);
  }
At the current point in time, these two classes only handle UTF8 format files. There is no capability to recognise XML format files. This is intended to be handled in the future after some more work has been done sorting the integration issues.

Parser Parts

Dealing with the raw file coming in takes place in a number of pieces of code. As part of the architecture, the code makes a distinct separation between raw syntax parsing, and the semantic meaning of the scene graph. This allows us change the parser for different versions of VRML (or different parsing schemes), without effecting the implementation of the renderer.

As part of the package, an abstract callback interface is created called the Simple API for VRML processing - SAV. This set of callbacks can be registered with a parser to feed file structure information into userland code. Internally, your code can make use of this to keep a common parser, but change renderer. We also make use of this internally within the Java3D code to swap around code for proto handling to build lightweight scenegraph structures.

Parsers are represented by the interface org.web3d.vrml.sav.VRMLReader and are accessed from the factory class org.web3d.vrml.parser.VRMLParserFactory. A default parser is available, but you can provide a custom parser by setting a system property org.web3d.vrml.parser.factory with the name of the class that is to be used.

Running the parser

As we said, the parser process consists of two parts - the file format reader and the SAV callback implementation. We start by creating a new parser.
  VRMLReader vrml_reader = parserFactory.newVRMLReader();
If you already have a reader instance, you can re-use that if you wish.

Next you need to register with the reader the class(es) that implement the SAV callback interfaces. Which classes you register here depends on the renderer that you want to use. For Java3D code this class is org.web3d.vrml.j3d.J3DVRMLSceneBuilder, which implements all of the SAV interfaces.

  sceneBuilder.reset();
  vrml_reader.setContentHandler(sceneBuilder);
  vrml_reader.setScriptHandler(sceneBuilder);
  vrml_reader.setProtoHandler(sceneBuilder);
  vrml_reader.setRouteHandler(sceneBuilder);
A very important step is the reset() call. This is used to make sure the internal state of the scene builder is ready to start a new document. This is important because the internal state may be messed up from an error in the previously parsed file. The next four lines just register each of the SAV methods.

The next step is telling the scene builder what it should and should not be loading. These flags are used by the Java3D loader code to send information through from the loader flags. Read the documentation for more details on this.

Finally, we tell the parser to start loading. This is done by passing and instance of org.web3d.vrml.sav.InputSource, which is constructed by passing in a stream, URL or filename depending on your weapon of choice. After constructing this,

  try {
    vrml_reader.parse(is);
  } catch(IOException ioe) {
    statusLabel.setText("I/O Error: " + ioe.getMessage());
  } catch(VRMLParseException vpe) {
    StringBuffer buf = new StringBuffer("Error Parsing VRML file\n");
    buf.append("Line: ");
    buf.append(vpe.getLineNumber());
    buf.append("\nStarting at column: ");
    buf.append(vpe.getColumnNumber());
    buf.append("Message: ");
    buf.append(vpe.getMessage());
    System.out.println(buf.toString());
  } catch(VRMLException se) {
    // everything else. Just a format exception
    System.out.println(se.getMessage());
  }

Building a running scene

The parse() method of the scene builder does not return you an instance of the scene. You must fetch this from the scene builder as a separate step. (J3DVRMLScene is the concrete representation of VRMLScene)
  J3DVRMLScene parsed_scene = sceneBuilder.getScene();
The scene contains all of the important pieces that are needed to complete a runtime environment. Now we need to place them all together to build a fully fuctional scene. There are two parts to this - common code for everyone, and renderer specific code. However, the Java3D code contains all of the rest of management. All you need to do is place together the major pieces.

The main working class for the Java3D runtime handling is org.web3d.vrml.j3d.browser.VRMLUniverse. This implementation of the Java3D Universe does all of the handling of routes, bindable nodes and user input. However, you must still choose some of the capabilities. These are the classes that handle the file caching and how routing is implemented - these come from the generalised implementations in the packages org.web3d.vrml.nodes.loader and org.web3d.vrml.nodes.runtime respectively. Again, check the documentation for the available options.

  ExternalLoadManager lm = new MemCacheLoadManager();
  RouteManager rm = new ListsOfListsRouteManager();

  universe = new VRMLUniverse(rm, lm);
To load the current scene into the custom universe requires only one method call:
  universe.setScene(parsed_scene);
This is enough to get the application showing VRML content.

Dealing with user feedback

VRML, being a web-based application, uses links to other documents. We need to know when this happens within a scene. As the VRMLUniverse is the centre of attention of runtime, that's where we look for getting feedback about requests to change the loaded scene.

Notification of the request to change scenes is through the LinkSelectionListener. As per standard listener methodology, this is registered with VRMLUniverse so that you can deal with the runtime telling you what has happened.

The important method here is linkSelected(). When this is called, you will be passed the instance of the VRMLLinkNodeType that was selected by the user. Once you have this link node, you will have the list of URLs to load, and you can start the entire process all over again.


[ Xj3D Homepage | Xj3D @ Web3d | Screenshots | Dev docs | Dev Releases | Contributors | Getting Started ]
Last updated: $Date: 2005-02-04 22:16:23 $