Creating a new node

Intro

So you want to create a new node? Take a seat and let us explain the process to ya and we'll have you up to speed in no time. We'll give ya a bit of advice: copy, steal, plagerize! The best programmers we know are lazy bastards. Reuse code as often as you can, within the license agreements of course! Xj3D is LGPL, so there is a wide selection of code you can borrow from. Of course the best place to borrow code is likely within Xj3D itself.

Here is a summary of the process:

Xj3D allows multiple renderers to be used with the VRML scene graph. For each node, you should implement the node for each renderer. You can find each renderer under the package org.web3d.vrml.renderer. However, to save you a lot of typing, we also suggest you define a common base class for the node and then extend it for each renderer implementation. This common base will do all of the field handling and data checking. This common base class should exist in the org.web3d.vrml.renderer.common.nodes package and then placed in the appropriate sub-package.

Copy the closest node by field definition

Find a node which has a similar field signature of your new node. In general you want a node that has all of the same field types(like SFNode, SFVec3f) that you are going to use. This will give you an example for each field type. Copy this node into the proper directory. For this example lets say we are creating the ImageTexture node. Its part of the texturing component so it will go in the renderer/norender/nodes/texturing. You would name the file [Renderer][NodeName] which would be NRImageTexture.

Determine the base class to extend

The class you extend from will either be the base class for the renderer or something that extends that. In the first case this would be named (Renderer)VRMLNode, for the norender case that would be NRVRMLNode. Our example extends the NRTexture2DNode. This class gives us some common functionality for all 2D textures in the system.

Implement all [Renderer]NodeType and VRML*NodeType interfaces needed

A node will need to implement its required interfaces. These interfaces are driven by what services the node needs from the system or what capabilities the node is providing. There are two major classes of interfaces a node is likely to implement.

The first class is render-specific interfaces. These interfaces are typically originally parented from VRML*NodeType interfaces, but they provide extra renderer specific methods or typing and generally some field handling. Your node will likely need to implement some of these. The ImageTexture example implements the NRTexture2DNodeType interface. In this case the norender renderer doesn't add extra methods, but most other renderers will need methods here.

The second class are interfaces that notify the system that you will need certain system features like asset loading. A node that needs assets loaded will have one or more URL fields. It will implement VRMLSingleExternalNodeType or VRMLMultiExternalNodeType.

Define field indices

Each field will need a unique number to that node. Internally Xj3D refers to fields using integer index values, which are much faster to process than strings of the original field names. Since nodes can have a deep heirarchy of partial node definitions that it is talking to each level will cover just the fields declared at that level. We tend to create a class hierarchy that matches that defined in the spec, and declare node indices at each level, extending on the index values of the previous level.

Start the count from either the LAST_NODE_INDEX or the LAST_[NODE]_INDEX of your base class. In the ImageTexture example this is LAST_TEXTURENODETYPE_INDEX. You will also need to define the NUM_FIELDS variable in the concrete node class. This is the total of all the fields in parent classes plus this nodes fields. Each node maintains a list of field declarations and a field name to index map. The intermediary classes like NRTexture2DNode do not create these variables. You will also need a LAST_[NODENAME]_INDEX field which will be the last assigned index value.

Every field in a node is represented by an instance of the class VRMLFieldDeclaration. This holds all the definition information about a field - it's name, access type, data type and so forth. For each index, you'll need to create a corresponding instance of this class. This is performed in the static constructor, as you'll see shortly.

Declare storage for VRML fields

Declare a variable for each field of the node. They should be named vf[FieldName]. For example repeatS would be named vfRepeatS. Pick a datatype that will be most convient for the current renderer. Basic types to Java are generally prefered instead of objects; they generate less garbage in the system.

Node fields

When dealing with SFNode fields, you have to consider that there are could be both a normal node or a proto instance used for that field. For this, we typically declare two internal variables - one of the type that extends from VRMLNodeType and one of the type VRMLProtoInstance. For example, here's how the appearance field of Shape looks:

/** Proto version of the appearance */
protected VRMLProtoInstance pAppearance;

/** SFNode appearance NULL */
protected VRMLAppearanceNodeType vfAppearance;
We do this because sometimes we need to know the real proto instance (anyone querying that field) and sometimes the real type is used for fast access. Always assume that one or the other may not be set yet. When a proto instance comes from an externproto, you may well have the proto version, but the normal version will have a null value, until the proto is loaded.

For MFNode fields, we normally just store them in an ArrayList.

Standard variable names

To make life easier when moving between nodes, we have a set of standard names that we use for every node. This is the stock set you'll see in every concrete node implementation class:

/** Array of VRMLFieldDeclarations */
private static VRMLFieldDeclaration[] fieldDecl;

/** Hashmap between a field name and its index */
private static HashMap fieldMap;

/** Listing of field indexes that have nodes */
private static int[] nodeFields;
The first is the list of the field declarations. The value at index i should correspond to the field index given.

The second variable maps the string version of the field name through to it's index represented as an Integer class. This allows a fast lookup of a field's index if you don't already know it (eg SAI or ROUTE processing).

The third variable is a list of the fields that are the list of indices for all the fields of this node that are either SFNode or MFNode. Internally Xj3D uses this for fast walking of the scene graph between frames (eg to detect in new pieces of scene graph things like sensors, bindable nodes etc).

Constructors

The system requires three constructors for each node. A static constructor for the class, a default public constructor with no parameters and a copy constructor.

The static constructor is used to setup the field declarations and field name mappings. This constructor is called the first time this class is used. So every instance of the class will share these values. In this method you will define each field used in the node. Fields maybe handled by parent classes, but you must define the field declarations in the terminal node instance. When putting names into the fieldMap you should add set and _changed versions for each exposed field. So for an exposed field named foo you would add "foo", "set_foo" and "foo_changed" entries into the fieldMap table.

An example static constructor is like this one taken from BaseShape:

static {
	nodeFields = new int[] {
		FIELD_APPEARANCE,
		FIELD_GEOMETRY,
		FIELD_METADATA
	};

	fieldDecl = new VRMLFieldDeclaration[NUM_FIELDS];
	fieldMap = new HashMap(NUM_FIELDS*3);

	fieldDecl[FIELD_METADATA] =
		new VRMLFieldDeclaration(FieldConstants.EXPOSEDFIELD,
								 "SFNode",
								 "metadata");
	fieldDecl[FIELD_APPEARANCE] =
		new VRMLFieldDeclaration(FieldConstants.EXPOSEDFIELD,
								 "SFNode",
								 "appearance");
	fieldDecl[FIELD_GEOMETRY] =
		new VRMLFieldDeclaration(FieldConstants.EXPOSEDFIELD,
								 "SFNode",
								 "geometry");
	fieldDecl[FIELD_BBOX_CENTER] =
		new VRMLFieldDeclaration(FieldConstants.FIELD,
								 "SFVec3f",
								 "bboxCenter");
	fieldDecl[FIELD_BBOX_SIZE] =
		new VRMLFieldDeclaration(FieldConstants.FIELD,
								 "SFVec3f",
								 "bboxSize");

	Integer idx = new Integer(FIELD_METADATA);
	fieldMap.put("metadata", idx);
	fieldMap.put("set_metadata", idx);
	fieldMap.put("metadata_changed", idx);

	idx = new Integer(FIELD_APPEARANCE);
	fieldMap.put("appearance", idx);
	fieldMap.put("set_appearance", idx);
	fieldMap.put("appearance_changed", idx);

	idx = new Integer(FIELD_GEOMETRY);
	fieldMap.put("geometry", idx);
	fieldMap.put("set_geometry", idx);
	fieldMap.put("geometry_changed", idx);

	fieldMap.put("bboxSize",new Integer(FIELD_BBOX_SIZE));
	fieldMap.put("bboxCenter",new Integer(FIELD_BBOX_CENTER));
}
Note how the fieldMap is populated based on the access types of the fields.

The default constructor is called each time a new node is created. The first line should be a call to the base class constructor with the node name. Then it should initialize the default values of each class and do any per instance initialization needed. All vf* variables should have non-null values. For arrays this means making a [0] entry.

The copy constructor is used to copy a node from one renderer to another. The most common case is when you are instancing a prototype the system copies the norender version into the real renderer. Do not assume that the field index values for one renderer are the same for another. Always use getFieldIndex to get the correct index value.

Implement all NodeType & VRMLNodeType interfaces

Now comes the meat of the implementation. Fill in all the required methods for the NodeType interfaces you specified.

The VRMLNodeType interface is the core of the VRML field handling. We'll detail the major methods and some implementations notes:

getPrimaryType and getSecondaryType - These methods are used to speed up class comparisons. The instanceof operator is rather slow. These definitions comes from the org.web3d.vrml.lang.TypeConstants class. Generally you should not need to add new entries here unless you are extending the core functionality of the system. These have a strong corelation with the VRML* interfaces that you implemented. In the ImageTexture example we specify the primary type as VRMLTextureNodeType. The secondary type is used in two cases currently. The first is in specifying the type of a prototype; its primary type is VRMLProtoInstance and its secondary is the first node's type. The second case is with VRMLExternalNodeType's.

getFieldValue - This method wraps all of the nodes fields in a fieldData wrapper. This is used in instances where an object representation of the field is required. All fields must be handled here or in the parent's getFieldValue.

sendRoute - This is called whenever a value needs to be routed from this node to another. Handle all field types, even field and eventIn values which normally are not routed.

setRawValue - These methods convert a string to a field value. This is the primary route that parsing values are converted. Field value verification should be performed on these values. Check the inSetup flag to determine if this is parsetime or not.

setValue - These methods set a field's value from a Java primitive type. This is the primary mechanism for setting field values at runtime. Most nodes do not need every setValue permutation. The following table shows which methods you need for each VRML field type:
VRML typeJava type
SFBoolboolean
SFDoubledouble
SFFloatfloat
SFInt32int
SFNodeVRMLNodeType
SFStringString
SFTimelong
MFBoolboolean[]
MFDoubledouble[]
MFFloatfloat[]
MFInt32int[]
MFNodeVRMLNodeType[]
MFStringString[]
MFTimelong[]
MFVec2ffloat[]
MFVec3ffloat[]

Setup encodings

Some encodings need to be updated when a new node type is added to the system. This section is not complete yet.

The XML encoding will need a new DTD.

Copy to create other renderer implementations

Once the norender version is created you can then use this file as a base for the other renderers. These renderers might use different datatypes for the vf* fields as needed for performance. They will also have other [Renderer]NodeType interfaces to implement.


[ Xj3D Homepage | Xj3D @ Web3d | Screenshots | Dev docs | Dev Releases | Conformance | Contributors | Getting Started ]
Last updated: $Date: 2005-10-12 16:39:41 $