22.3.  Using the VRML Nodes

This section gets right down to the gritty technical details of using VRML in an Open Inventor application. This is mostly for programmers with some Open Inventor experience, but will give you a flavor of how Open Inventor works if you are somewhat familiar with VRML.

Many of the VRML nodes have the same (or very similar) name as an existing Open Inventor node, but have different fields and/or different behavior. For example, both VRML and Open Inventor have an IndexedFaceSet node, but their fields are quite different. In order to clearly differentiate the VRML nodes, their class names all begin with the string “ SoVRML”, while the traditional Open Inventor class names begin with just “So”. For example, the VRML version of IndexedFaceSet is implemented in class SoVRMLIndexedFaceSet SoVRMLIndexedFaceSet SoVRMLIndexedFaceSet , while the original Open Inventor IndexedFaceSet is implemented in class SoIndexedFaceSet SoIndexedFaceSet SoIndexedFaceSet . This also allows applications to easily distinguish these nodes in cases where it is important, for example, to write out only valid VRML nodes. The important thing to remember is that, in the Open Inventor tool kit, VRML nodes are Open Inventor nodes. They are created and manipulated the same way and share the same properties such as reference counting and persistence.

VRML has essentially the same scene graph concept as Open Inventor, with some notable exceptions:

One of the most significant differences in a VRML scene graph is that geometry and attribute nodes cannot be direct children of a grouping node. A geometry node and its associated attributes can only appear as part of a VRML Shape node (class SoVRMLShape SoVRMLShape SoVRMLShape ). Again, VRML scene graphs end up somewhat more deeply nested than traditional Open Inventor. However it is much easier to find the attributes that immediately affect a primitive, because they are always contained in the Shape node. In other words, you never have to search the hierarchy or traverse the scene graph to accumulate attributes.




Note first that SoVRMLTransform SoVRMLTransform SoVRMLTransform is a grouping node similar to SoSeparator SoSeparator SoSeparator , but unlike SoSeparator SoSeparator SoSeparator it does not handle children at the same syntactic level as fields. Instead the children are specified as the value of its children field, which has the type SoMFNode SoMFNode SoMFNode . (In the code example you will see that the familiar addChild (etc.) methods have been implemented in this class for convenience.) Next notice the SoVRMLShape SoVRMLShape SoVRMLShape node, which has two fields – appearance and geometry – each of which has the type SoSFNode SoSFNode SoSFNode . The only thing that can be put into the appearance field is an SoVRMLAppearance SoVRMLAppearance SoVRMLAppearance node. This may seem redundant but it is important to have Appearance as a node so that multiple Shape nodes can instance, or point to, the same Appearance node. Otherwise VRML hierarchies would use too much memory (if appearance information had to be duplicated for every Shape node). Next note that the Appearance node has a material field (type SoSFNode SoSFNode SoSFNode ) that can only contain an SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial node. The Material node contains fields diffuseColor, etc., similar to the traditional Open Inventor material node. (More about this later.) Finally, note that the geometry field of SoVRMLShape SoVRMLShape SoVRMLShape can be assigned any VRML geometry node. These include Cone ,Sphere ,Box(similar to SoCube SoCube SoCube ), and so on.



[Important]

We call pRoot->addChild to insert the geometry into the scene graph in both examples, because both SoSeparator SoSeparator SoSeparator and SoVRMLTransform SoVRMLTransform SoVRMLTransform are grouping nodes. However also note that we use “ addChild ” to insert “ pMat ” (the Materialnode) into the scene graph in the first example, but we use “ setValue” in the second example because the VRML Material node goes into the material field of the VRML Appearance node.

In general, VRML nodes do not inherit any attributes except transformation. However, for consistency, the VRML nodes do inherit (and respond correctly) to some very useful Open Inventor attributes that are not part of the VRML specification. These attributes include DrawStyle (note that line width and point size cannot be specified in a VRML97 file) and Complexity (no equivalent in VRML). Note that line properties were introduced in the X3D specification and are set using the SoVRMLLineProperties SoVRMLLineProperties SoVRMLLineProperties node. This node is in the lineProperties field of the SoVRMLAppearance SoVRMLAppearance SoVRMLAppearance node. Note also that the attributes of SoShapeHints SoShapeHints SoShapeHints are not inherited by VRML nodes because these values are specified directly in VRML geometry nodes.


Example 22.7. Inheritance of color in an Open Inventor scene graph

Example 22.6, “ Two red cones ”, is not necessarily a realistic example since this type of attribute inheritance makes it relatively difficult to interactively change the attributes of a specific object. It also makes it difficult to modify the scene graph itself since the attribute inheritance shown here depends on a specific ordering of the nodes under the top Separator. The following scene graph is more realistic and makes it easier to see the parallels with the analogous VRML scene graph:

  Separator
    {
    Separator
      {
      DEF Red Material { diffuseColor 1 0 0 }
      # Red
        Transform { translation -1 0 0 }
      # Translate left Cone
        {
        }
      }
    Separator
      {
      USE Red Transform { translation 1 0 0 }
      # Translate right Cone
        {
        }
      }
    }



[Important]

First, note that the children field of the first VRML Transform node has multiple values (children) so we must use the “[ ]” syntax (which is not required when there is only a single child). Next, notice how the VRML Transform node combines the function of the Separator and (Open Inventor) Transform nodes in the previous example, providing both a new level of hierarchy and a translation. Finally, notice that we named the Appearance node containing the color Red in the first object and referenced it in the second object’s Shape node, similar to the previous example. Alternatively we could have named the Material node in the first object and referenced it in the second object’s Appearance node. It all depends on what effect we are trying to achieve.

In general, the VRML node classes are derived from SoVRMLNode SoVRMLNode SoVRMLNode , which is derived from class SoNode SoNode SoNode . The abstract class SoVRMLNode SoVRMLNode SoVRMLNode was introduced to add the metadata field to all new and existing VRML nodes (metadata is part of the X3D specification). However the VRML grouping node classes are derived from class SoGroup SoGroup SoGroup (as are all the traditional Open Inventor grouping node classes). This is because many Open Inventor features depend on being able to identify a grouping node using isOfType(SoGroup::getClassTypeId()). Therefore the VRML grouping nodes also return TRUE for this query.

[Note]

The traditional Open Inventor grouping nodes maintain their children in a hidden list which can only be accessed by methods like addChild, removeChild, insertChild, and so on. The VRML grouping nodes maintain their children in an SoMFNode SoMFNode SoMFNode field, which can be accessed by the usual SoMF field methods like setValues and set1Value. However, for convenience, the VRML grouping nodes also support the familiar addChild (etc.) methods.

SoVRMLTransform *pTrans = new SoVRMLTransform;
SoVRMLShape *pShape = new SoVRMLShape;

pTrans->addChild(pShape);    // Is equivalent to...
pTrans->children.setValue(pShape);
         
SoVRMLTransform trans = new SoVRMLTransform();
SoVRMLShape shape = new SoVRMLShape();

trans.AddChild(shape);    // Is equivalent to...
trans.children.SetValue(shape);
         
SoVRMLTransform trans = new SoVRMLTransform();
SoVRMLShape shape = new SoVRMLShape();

trans.addChild(shape);    // Is equivalent to...
trans.children.setValue(shape);
         

In general, the VRML grouping nodes have a field named children and the corresponding method names addChild() (etc.) make sense. However SoVRMLSwitch SoVRMLSwitch SoVRMLSwitch maintains its children in a field named choice, therefore the access methods are named addChoice(), removeChoice(), and so on. Similarly SoVRMLLOD SoVRMLLOD SoVRMLLOD maintains its children in a field named levels, therefore the access methods are named addLevel(), removeLevel(), and so on.

In the X3D specification, the choice field in SoVRMLSwitch SoVRMLSwitch SoVRMLSwitch was changed to children as was the level field in SoVRMLLOD SoVRMLLOD SoVRMLLOD . Both fields were kept so that either VRML97 or X3D files can be read by Open Inventor.

SoVRMLSwitch *pSwtch = new SoVRMLSwitch;
SoVRMLShape *pShape = new SoVRMLShape;

pSwtch->addChoice(pShape);           // Is equivalent to... for VRML97
pSwtch->choice.setValue(pShape);

pSwtch->addChild(pShape);            // Is equivalent to... for X3D
pSwtch->children.setValue(pShape);
         
SoVRMLSwitch swtch = new SoVRMLSwitch();
SoVRMLShape shape = new SoVRMLShape();

swtch.AddChoice(shape);           // Is equivalent to... for VRML97
swtch.choice.SetValue(shape);

swtch.AddChild(shape);            // Is equivalent to... for X3D
swtch.children.SetValue(shape);
         
SoVRMLSwitch swtch = new SoVRMLSwitch();
SoVRMLShape shape = new SoVRMLShape();

swtch.addChoice(shape);           // Is equivalent to... for VRML97
swtch.choice.setValue(shape);

swtch.addChild(shape);            // Is equivalent to... for X3D
swtch.children.setValue(shape);
         

In Open Inventor the VRML Transform node (class SoVRMLTransform SoVRMLTransform SoVRMLTransform ) has the same special capabilities as a Separator node (class SoSeparator SoSeparator SoSeparator ), including:

The fields associated with these capabilities are defined for SoVRMLTransform SoVRMLTransform SoVRMLTransform , but are not written when saving the scene graph to a VRML format file. See SoSeparator SoSeparator SoSeparator for more information.

VRML ROUTE statements are just a different way of specifying field-to-field connections in a file. As discussed in the the section called “ Files”, Open Inventor will automatically create any connections specified by ROUTE statements when it reads a VRML file. Conversely, Open Inventor will automatically generate ROUTE statements for any connections that exist when it writes a VRML file. In an application you can still use a field’s connectFrom() method to create a new connection (ROUTE) to it from another field. However you can also use the new static method SoDB::createRoute()to create a connection. This method takes pointers to each of the nodes and the names of the two fields to be connected. If you are working in a “pure” VRML mode, this is the recommended method. For example, creating a ROUTE from an SoVRMLTouchSensor SoVRMLTouchSensor SoVRMLTouchSensor to an SoVRMLPointLight SoVRMLPointLight SoVRMLPointLight , so that clicking on the geometry associated with the TouchSensor will turn on the light:

SoVRMLTouchSensor *pTouch = new SoVRMLTouchSensor;
SoVRMLPointLight *pLight = new SoVRMLPointLight;

SoDB::createRoute(pTouch, "isActive", pLight, "set_on");
        
SoVRMLTouchSensor touch = new SoVRMLTouchSensor();
SoVRMLPointLight light = new SoVRMLPointLight();

SoDB.CreateRoute(touch, "isActive", light, "set_on");
        
SoVRMLTouchSensor touch = new SoVRMLTouchSensor();
SoVRMLPointLight light = new SoVRMLPointLight();

SoDB.createRoute(touch, "isActive", light, "set_on");
        

The traditional Open Inventor nodes allow connections between fields of different types, as long as a reasonable conversion exists (Open Inventor does the conversion automatically). VRML only allows connections (ROUTEs) between fields with exactly the same type. For example, you cannot connect an SoSFFloat SoSFFloat SoSFFloat field to an SoSFInt32 SoSFInt32 SoSFInt32 field. Open Inventor enforces this restriction for VRML nodes when you use SoDB::createRoute() to create the ROUTE. Note that the fields in the example above both have type SoSFBool SoSFBool SoSFBool .

[Warning]

The VRML97 specification uses the term “field” in a much more specific sense than traditional Open Inventor does. In the VRML97 specification you will see several other terms used, including eventIn, eventOut, field, and exposedField. Open Inventor implements all these objects as fields (in the Open Inventor sense) which have certain properties associated with them. The properties are:

  • EventIn: A ROUTE can be connected to this field.

  • EventOut: A ROUTE can be connected from this field.

  • Field: No connections are allowed.

  • ExposedField: Combines the EventIn and EventOut properties.

The X3D specification uses a slightly different notation for indicating the meanings of eventIn, eventOut, field, and exposedField.

For the following discussions, only the VRML97 notation is used since both methods have the same meaning.

VRML only allows connections (ROUTEs) from an eventOut to an eventIn. In Open Inventor this means a ROUTE is only allowed from a field with the EventOut property to a field with the eventIn property. Fields declared as an exposedField in the VRML specification have both properties. Open Inventor enforces this restriction for VRML nodes when you use SoDB::createRoute()to create the ROUTE.

[Important]

The VRML specification says that an exposedField is a “shorthand” for declaring that an eventIn, a field, and an eventOut with the same base name exist in the node, implying that there are three separate objects. For example, an exposedField named “children” implies these objects:

  • An eventIn named: set_children

  • A field named: children

  • An eventOut named: children_changed

In the Open Inventor implementation, an exposedField is represented by a single field, which has both the eventIn and the eventOut properties, representing all three objects. In the example above (which is common to most of the VRML grouping nodes), the Open Inventor class has a single field named “children”. However, in this case either the base name or the eventIn / eventOut name is accepted by the SoDB::createRoute() method (just as it is by the ROUTE statement in a file). So the previous example with the SoVRMLTouchSensor SoVRMLTouchSensor SoVRMLTouchSensor and the SoVRMLPointLight SoVRMLPointLight SoVRMLPointLight could also look like this:

SoVRMLTouchSensor *pTouch = new SoVRMLTouchSensor;
SoVRMLPointLight *pLight = new SoVRMLPointLight;

SoDB::createRoute(pTouch, "isActive", pLight, "on");
        
SoVRMLTouchSensor touch = new SoVRMLTouchSensor();
SoVRMLPointLight light = new SoVRMLPointLight();

SoDB.CreateRoute(touch, "isActive", light, "on");
        
SoVRMLTouchSensor touch = new SoVRMLTouchSensor();
SoVRMLPointLight light = new SoVRMLPointLight();

SoDB.createRoute(touch, "isActive", light, "on");
        

The VRML specification also talks about “events” being sent from one node to another through the ROUTEs. These events are “conceptual” and only exist for the purpose of explaining the model. Open Inventor does not literally implement these events.

Rendering works essentially the same for VRML nodes as for traditional Open Inventor nodes. However, in addition to the difference in attribute inheritance discussed above, there are some specific differences required by the VRML specification, including:

Picking works for VRML geometry as well as traditional Open Inventor geometry. Both explicit SoRayPickAction SoRayPickAction SoRayPickAction and implicit SoSelection SoSelection SoSelection picking return a “pick path” (class SoPath SoPath SoPath ). For traditional Open Inventor geometry, the “tail” (last node) of the path will be the actual geometry node that was picked. However, for VRML geometry, the tail of the path will be the Shape node (class SoVRMLShape SoVRMLShape SoVRMLShape ) that contains the picked geometry. The actual geometry node is contained in the Shape node’s geometry field. Conveniently, the rendering attributes for this geometry are defined by the Appearance node (class SoVRMLAppearance SoVRMLAppearance SoVRMLAppearance ) that is contained in the Shape node’s appearance field. For example, in a pure VRML scene graph:

SoPath *pSelPath = <selection path from SoSelection node>

SoVRMLShape *pShape = pSelPath->getTail();

SoVRMLAppearance *pApp = pShape->appearance.getValue();
SoVRMLMaterial *pMat = NULL;
if (pApp != NULL)
  pMat = pApp->material.getValue();

          
SoPath selPath = <selection path from SoSelection node>;

SoVRMLShape shape = (SoVRMLShape)selPath.GetTail();

SoVRMLAppearance app = (SoVRMLAppearance)shape.appearance.GetValue();
SoVRMLMaterial mat = null;
if (app != null)
  mat = (SoVRMLMaterial)app.material.GetValue();
        
SoPath selPath = <selection path from SoSelection node>;

SoVRMLShape shape = (SoVRMLShape)selPath.regular.getTail();

SoVRMLAppearance app = (SoVRMLAppearance)shape.appearance.getValue();
SoVRMLMaterial mat = null;
if (app != null)
  mat = (SoVRMLMaterial)app.material.getValue();
        

Open Inventor automatically recognizes the file headers “ #VRML V2.0 utf8”, indicating a VRML97 format file, and “ #X3D V3.0 utf8”, indicating an X3D format file with Classic VRML encoding. Open Inventor does not support the X3D XML encoding at this time. When reading a VRML file, Open Inventor recognizes nodes by their VRML name. For example, “IndexedFaceSet” will create an SoVRMLIndexedFaceSet SoVRMLIndexedFaceSet SoVRMLIndexedFaceSet node. If other Open Inventor nodes appear in the file (which would make the file non-compliant), their names must begin with “ So ”. For example, “ SoIndexedFaceSet SoIndexedFaceSet SoIndexedFaceSet ” will create an SoIndexedFaceSet SoIndexedFaceSet SoIndexedFaceSet node.

Any other (valid) file header is assumed to be an Open Inventor or VRML 1.0 file. When reading these files, Open Inventor recognizes nodes by their “classic” Open Inventor name. For example “Material” will create an SoMaterial SoMaterial SoMaterial node. If VRML nodes appear in the file (which is perfectly legal), their names must begin with “VRML”. For example, “VRMLMaterial” will create an SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial node.

VRML does not have a binary format (although one has been proposed and Open Inventor will support it when it is fully defined and accepted). You may wish to use the Open Inventor binary file format in some cases because the files are much smaller and load much faster.

About GZIP: Because VRML does not have a binary format, VRML files are often compressed with the gzip utility program to reduce their download time. The core Open Inventor library does not support gzip’d files. However on Win32 platforms (Windows), FEI offers “IVF”, a set of extension classes that integrates Open Inventor with MFC (the Microsoft Foundation Classes application framework). Applications built with MFC and IVF have built-in support for gzip’d files. IVF automatically detects gzip compression regardless of the file extension.

As discussed above under ROUTES, Open Inventor will automatically create field-to-field connections specified by ROUTE statements in the input file.

By default Open Inventor writes the scene graph to a file using the standard Open Inventor file header and standard Open Inventor naming conventions. For example, “ SoMaterial SoMaterial SoMaterial ” will be written out as “ Material ” and “ SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial ” will be written out as “ VRMLMaterial ”. This is the standard Open Inventor convention of dropping the leading “ So ” from the class name. All VRML nodes in the file will have class names beginning with “ VRML ”. This convention both identifies the VRML nodes in the file and distinguishes the VRML nodes, like “ Material ”, that are ambiguous with a traditional Open Inventor node.

In order to write a VRML compliant file and use VRML naming conventions, your application must call the SoOutput SoOutput SoOutput method:

setHeaderString("#VRML V2.0 utf8")
         
SetHeaderString("#VRML V2.0 utf8")
         
setHeaderString("#VRML V2.0 utf8")
         

or

setHeaderString("#X3D V3.0 utf8")
         
SetHeaderString("#X3D V3.0 utf8")
         
setHeaderString("#X3D V3.0 utf8")
         

using a pointer to your SoOutput SoOutput SoOutput object. If you only have an SoWriteAction SoWriteAction SoWriteAction object, use the getOutput() method to obtain a pointer to its SoOutput SoOutput SoOutput object. For example:

SoWriteAction wa;
wa.getOutput()->setHeaderString("#VRML V2.0 utf8");
         
SoWriteAction wa = new SoWriteAction();
wa.GetOutput().SetHeaderString("#VRML V2.0 utf8");
         
SoWriteAction wa = new SoWriteAction();
wa.getOutput().setHeaderString("#VRML V2.0 utf8");
         

When writing a VRML file, Open Inventor writes the scene graph to a file using VRML naming conventions. For example, “ SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial ” will be written out as “ Material ” and “ SoMaterial SoMaterial SoMaterial ” will be written out as “SoMaterial SoMaterial SoMaterial ”. In other words, VRML nodes have their normal VRML class name and traditional Open Inventor nodes have a “ So ” prefix to their class name. Please note however that although Open Inventor can read VRML files containing non-VRML nodes, these are not conforming VRML files and will cause errors in most VRML viewers. On the other hand, as noted previously, it can be useful to use a “mixed” file when saving a project between sessions and “publish” a pure VRML file when the project is completed.

[Tip]

As discussed above under ROUTEs, Open Inventor will automatically generate ROUTE statements for any field-to-field connections that exist in the scene graph. Please note that unlike the traditional Open Inventor file syntax for field-to-field connections, ROUTE statements require both the “from” and “to” nodes to have names. We recommend explicitly naming all nodes that will have field connections in them. This makes your VRML file much easier to interpret if it becomes necessary to examine it by hand. However, Open Inventor will automatically create “synthetic” names for nodes, if necessary, when generating the ROUTE statements. These names have the form “_n” where “n” is an integer value. For example, “_1” and “_2” will normally be the first two names created. These names are automatically removed when the file is read back in by Open Inventor.

[Warning]

Open Inventor guarantees that synthetic names are unique within the current scene graph. However it does not guarantee that application specified names are unique (we assume you know what you are doing in this case). Both Open Inventor and the VRML specification allow names to be multiply defined in an input file. They simply use the most recently encountered definition of the name.

The Material Editor (SoXtMaterialEditor on Unix, SoWinMaterialEditor SoWinMaterialEditor on Win32) has been enhanced to allow attaching to SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial nodes (in addition to SoMaterial SoMaterial SoMaterial nodes). An SoVRMLMaterial SoVRMLMaterial SoVRMLMaterial node contains essentially the same information as an SoMaterial SoMaterial SoMaterial node, except:

[Important]

When a Material Editor is attached to a VRML Material node, you will notice that the ambient intensity slider does not have buttons beside it (like the other color sliders do). This is because it is not possible to edit the VRML ambient color directly. You can only change the ambient intensity (using the slider) or change the diffuse color.

As discussed elsewhere in this discussion, it is generally easier to attach a Material Editor when working with VRML geometry (no need to search for the appropriate Material node). The pick (selection) path will have an SoVRMLShape SoVRMLShape SoVRMLShape node as its tail node. This node has five fields, two of which are the appearance and geometry fields. So typically the attach algorithm will look something like the following:

the section called “ Attach Material Editor” shows the source code (or see the demo program SceneViewer).

The manipulators (SoTransformerManip SoTransformerManip SoTransformerManip and so on) have not been specifically modified for VRML. However they are just as useful for VRML geometry as they are for traditional Open Inventor geometry. Because manipulators are usually added to the scene graph by replacing an SoTransform SoTransform SoTransform node, all the manipulator classes are derived from SoTransform SoTransform SoTransform (by way of SoTransformManip SoTransformManip SoTransformManip ). To be able to replace an SoVRMLTransform SoVRMLTransform SoVRMLTransform node, it would have been necessary to (essentially) duplicate the manipulator classes and derive them from SoVRMLTransform SoVRMLTransform SoVRMLTransform . This would have significantly increased the size of the Open Inventor toolkit. There is an easier way: temporarily add an SoTransform SoTransform SoTransform node to the VRML scene graph. So the algorithm for a VRML scene graph, given a path to some selected geometry, is roughly:

To insert a manipulator:

the section called “ Attach Manipulator” shows the source code (or see the demo program SceneViewer).

To remove a manipulator:

  1. Remove the manipulator from the scene graph (that is, literally remove the manipulator from the scene graph, not using the replaceManip() method, because we don’t want an SoTransform SoTransform SoTransform node left in a VRML scene graph).

  2. Concatenate the transformation in the manipulator into the Shape node’s parent node (which we previously made sure was an SoVRMLTransform SoVRMLTransform SoVRMLTransform node).

the section called “ Detach Manipulator” shows the source code (or see the demo program SceneViewer).