Creating a new node |
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.
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.
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.
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.
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.
/** 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).
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.
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 type | Java type |
| SFBool | boolean |
| SFDouble | double |
| SFFloat | float |
| SFInt32 | int |
| SFNode | VRMLNodeType |
| SFString | String |
| SFTime | long |
| MFBool | boolean[] |
| MFDouble | double[] |
| MFFloat | float[] |
| MFInt32 | int[] |
| MFNode | VRMLNodeType[] |
| MFString | String[] |
| MFTime | long[] |
| MFVec2f | float[] |
| MFVec3f | float[] |
The XML encoding will need a new DTD.
|
[
Xj3D Homepage |
Xj3D @ Web3d |
Screenshots |
Dev docs |
Dev Releases |
Conformance |
Contributors |
Getting Started
]
Last updated: $Date: 2005/10/12 16:39:41 $ |