Home

Download
Install
Extend

Use

Conformance
Examples
Contact

      

How to add new Nodes to FreeWRL

This document will show examples of how to add nodes to FreeWRL.

There is a companion document containing some ideosyncracies of programming on FreeWRL: general coding notes for FreeWRL/FreeX3D.

Last change: October 14, 2009.

Questions

There is one decision, and two concepts you have to work through:

  • Is your new node graphics-oriented, or action-oriented? eg, are you expecting to put triangles to the screen?

  • Do you know what is meant by "tree traversal"? FreeWRL scene graph rendering was originally only tree-traversal, but now more and more functionality is being handled by a linear array of nodes.

  • Node structure in FreeWRL is generated from tables; much of the work you will have to do is to simply put the correct information in the table.

Example 1. Adding a node that takes an SFFloat, and returns the value, multiplied by two.

step 1) choose a node that is close in functionality to abstract some methodology from.

Lets choose the BooleanToggle node - it takes a boolean value and inverts it.

step 2) Look through the source for files that have "BooleanToggle" in them.

As of this time of writing:

  • a) codegen/VRMLNodes.pm
  • b) codegen/VRMLRend.pm
  • c) src/lib/main/headers.h
  • d) src/lib/scenegraph/Component_EventUtils.c
  • e) src/lib/scenegraph/GeneratedCode.c
  • f) src/lib/scenegraph/RenderFuncs.c
  • g) src/lib/vrml_parser/NodeFields.h
  • h) src/lib/vrml_parser/Structs.h

We will go through each of these files in turn.

a) codegen/VRMLNodes.pm

        BooleanToggle =>
        new VRML::NodeType("BooleanToggle", {
                        set_boolean =>[SFBool,undef,inputOnly],
                        toggle => [SFBool, FALSE, outputOnly],
                        metadata => [SFNode, NULL, inputOutput],
                        __oldmetadata => [SFNode, 0, inputOutput], # see code for event macro
        },"X3DChildNode"),

Copy and paste this node. At the end of VRMLNodes.pm, you will see a "testing" area. Paste in there.

Change "BooleanToggle" to "FloatMultiply", and "SFBool" to "SFFloat":

        FloatMultiply =>
        new VRML::NodeType("FloatMultiply", {
                        set_float =>[SFFloat,undef,inputOnly],
                        newFloat => [SFFloat, FALSE, outputOnly],
                        metadata => [SFNode, NULL, inputOutput],
                        __oldmetadata => [SFNode, 0, inputOutput], # see code for event macro
        },"X3DChildNode"),

and write out the file.

b) codegen/VRMLRend.pm

You will see the line:

        BooleanToggle           =>children,

in a grouping called "defaultContainerType".

We will keep the default as a child node so simply copy the "BooleanToggle" line, and edit it so it looks like:

        FloatMultiply           =>children,
Run the command "perl VRMLC.pm" and check for errors.

c) src/lib/main/headers.h

You will see a Function Prototype for "do_BooleanToggle" here - copy this, and change it to:

void do_FloatMultiply (void *node);

d) src/lib/scenegraph/Component_EventUtils.c

Look for BooleanToggle, and you will see the function do_BooleanToggle; copy this and change the code as shown:

/* testing... */
void do_FloatMultiply (void *node){
        struct X3D_FloatMultiply *px;

        if (!node) return;
        px = (struct X3D_FloatMultiply *) node;

        /* has the value changed? */
        if (!APPROX(px->set_float,px->newFloat)) {
                /* value has changed */
                printf ("FloatMultiply, value has changed\n");
                px->newFloat = px->set_float * 2.0;
                MARK_EVENT(node, offsetof (struct X3D_FloatMultiply, newFloat));
        }
}

e) src/lib/scenegraph/GeneratedCode.c

This file is generated from the Perl-formatted tables, DO NOT edit this file.

f) src/lib/scenegraph/RenderFuncs.c

BooleanToggle is an interpolator - it just modifies the input value and gives a new output value, nothing else. Lets copy, paste, and edit the one "BooleanToggle" line in this file, so we have:

        } else if (strcmp("BooleanToggle",x)==0) { return (void *)do_BooleanToggle;
        } else if (strcmp("FloatMultiply",x)==0) { return (void *)do_FloatMultiply;

g) src/lib/vrml_parser/NodeFields.h

This file is generated from the Perl-formatted tables, DO NOT edit this file.

h) src/lib/vrml_parser/Structs.h

This file is generated from the Perl-formatted tables, DO NOT edit this file.

-------------------

step 3) Compile the source code.

If you do not know already, see the FAQ for info on how to compile FreeWRL.

step 4) Run an example.

Here is an example that shows the execution of our "FloatMultiply" function:


#VRML V2.0 utf8

DEF SPH1 Transform {
	translation -2  0  0
	children [
		Shape {
			appearance Appearance { material Material {} }
			geometry Cylinder {}
		}
	]
}
DEF SPH2 Transform {
	translation 2  0 0
	children [
		Shape {
			appearance Appearance { material Material {} }
			geometry Cylinder {}
		}
	]
}

# Animation clock
DEF Clock TimeSensor { cycleInterval 1.5 loop TRUE },

# Position 
# - note that the key goes from 0 to 2 for both:
# - note that POS1 and POS2 are identical, EXCEPT for the "x" component on each keyValue.
#   this places the resulting movement on left or right side of screen

DEF POS2 PositionInterpolator {
		key [ 0, 1, 2 ]
		keyValue [
			2 -2.0 0 # right side
			2 2.0 0  # right side
			2 -2.0 0 # right side
		]
	}
DEF POS1 PositionInterpolator {
		key [ 0, 1, 2]
		keyValue [
			-2 -2.0 0 # left side
			-2 2.0 0  # left side
			-2 -2.0 0 # left side
		]
	}

# our new node
DEF MYMUL FloatMultiply {}

# route the Clock for animation of left and right shapes.
ROUTE Clock.fraction_changed TO POS1.set_fraction
ROUTE Clock.fraction_changed TO MYMUL.set_float

#left shape, will have keyValue of 0->1
ROUTE POS1.value_changed TO SPH1.set_translation

# right shape, will have keyValue of 0->2
ROUTE MYMUL.newFloat TO POS2.set_fraction
ROUTE POS2.value_changed TO SPH2.set_translation

Example 2. A new interactive node.

coming...

Example 3. A new graphics node.

Ok, a new but old graphics node. We will show how to implement the VRML1 IndexedFaceSet node; it is moderately complex, and it uses the "PolyRep" internal structure, and, VRML1 support is requested, so it is a good candidate. We will call this node VRML1_IndexedFaceSet

step 1) choose a node that is close in functionality to abstract some methodology from.

Here is a table comparing the specification entries for VRML1 IndexedFaceSet, to the X3D IndexedFaceSet:

X3D VRML1 Field Type
set_colorIndexN/AMFInt32
set_coordIndexN/AMFInt32
set_normalIndexN/AMFInt32
set_texCoordIndexN/AMFInt32
attribN/AMFNode
colorMaterial->diffuseColorSFNode
coordCoordinate3->pointSFNode
fogCoordN/ASFNode
metadataN/ASFNode
normalNormal->vectorSFNode
texCoordTextureCoordinate2->pointSFNode
ccwShapeHints->vertexOrderingSFBool
colorIndexIndexedFaceSet->materialIndexMFInt32
colorPerVertexMaterialBinding->valueSFBool
convexShapeHints->faceTypeSFBool
coordIndexIndexedFaceSet->coordIndexMFInt32
creaseAngleShapeHints->creaseAngleSFFloat
normalIndexIndexedFaceSet->normalIndexMFInt32
normalPerVertexNormalBinding->valueSFBool
solidShapeHints->shapeTypeSFBool
texCoordIndexIndexedFaceSet->texCoordIndexMFInt32

You will notice that the VRML1 fields, if they apply, have a pointer. This is because, in VRML1, one codes as as siblings, separated by the Separator node:

Separator {
	MaterialBinding {...}
	ShapeHints {...}
	IndexedFaceSet {...}
}
while in X3D, the fields are specific child-style nodes, eg:
IndexedFaceSet {
	colorPerVertex TRUE
	creaseAngle 0.3
	...
}
We will have to take this into account later on.

step 2) Look through the source for files that have "IndexedFaceSet" in them.

grep -r "_IndexedFaceSet" * | grep -v NodeFields.h | grep -v GeneratedCode.c | grep -v Structs.h | more You will see that there are less than 40 lines. The files that matter to us are:

  • a) codegen/VRMLNodes.pm
  • b) codegen/VRMLRend.pm
  • c) codegen/VRMLC.pm
  • d) src/lib/main/headers.h
  • e) src/lib/scenegraph/Component_Geometry3D.c
  • f) src/lib/scenegraph/Component_VRML1.c
  • g) src/lib/scenegraph/GenPolyRep.c
  • h) src/lib/scenegraph/Polyrep.c
  • i) src/lib/scenegraph/StreamPoly.c
  • j) src/lib/opengl/RenderTextures.c

We will go through each of these files in turn.

a) codegen/VRMLNodes.pm

defines the node type; this is a "Perl-ized" copy of the node from the spec. You will see the following lines near the end of the VRMLNodes.pm file:

        VRML1_IndexedFaceSet => new VRML::NodeType("VRML1_IndexedFaceSet", {
                coordIndex => [MFInt32, [0],inputOutput,"SPEC_VRML1"],
                materialIndex => [MFInt32,[-1],inputOutput,"SPEC_VRML1"],
                normalIndex => [MFInt32,[-1],inputOutput,"SPEC_VRML1"],
                textureCoordIndex =>[MFInt32,[-1],inputOutput,"SPEC_VRML1"],
		...
        }, "X3DChildNode"),

Compare the above, to the VRML1 spec. You will notice the field names, the types, and the default values. There will be other fields in here - that start with an underscore - look for these below.

b) codegen/VRMLRend.pm

In here, you will see the VRML1_IndexedFaceSet keyword in a few places - FreeWRL has an object-oriented scene graph, so we have to define and fill in some methods in order to have a node operate properly. These methods will get defined in more detail later; just remember that we need to put our VRML1_IndexedFaceSet in some of these tables in order to get our methods called.

The VRML1_IndexedFaceSet keyword is found in the following tables:

  • RendCnodes that render on screen, and have a "render_xxx" method;
  • GenPolyRepC nodes that use our common "PolyRep" structure, and have a method "make_xxx";
  • CollisionC nodes that we can collide with, and have a method "collide_xxx";
  • RendRayC nodes that we can click on, and have a method "rendray_xxx";
  • VRML1_C a list of nodes specific to VRML1 parsing;
  • defaultContainerType used by the XML parser to determine where this node gets "plugged in".

c) codegen/VRMLC.pm

This file is a Perl file that is called during the build process to create "C" code for calling methods. We care in that this file contains some Macros for casting types, and an extremely important definition for the PolyRep structure that we will be using.

Do the following from within the codegen directory to see if your additions to the above were syntactically correct:

perl VRMLC.pm

It should run with no visible output. (It will, however, create a bunch of tables that we now have to fill in)

d) src/lib/main/headers.h

This file holds function prototypes; we will need prototypes for:

void render_VRML1_IndexedFaceSet (struct X3D_VRML1_IndexedFaceSet *this);
#define rendray_VRML1_IndexedFaceSet render_ray_polyrep 
#define make_VRML1_IndexedFaceSet make_genericfaceset
#define collide_VRML1_IndexedFaceSet collide_genericfaceset
The observant will notice that most of these are calls to generic functions. We will HAVE to make sure that all of these functions properly handle our code.

e) src/lib/scenegraph/Component_Geometry3D.c

Just note the function render_IndexedFaceSet - we will have to clone this in the next step.

f) src/lib/scenegraph/Component_VRML1.c

This file contains the one full function prototype expansion that we require - render_VRML1_IndexedFaceSet.

Remember the table above, where it was shown that the VRML1 IndexedFaceSet node fields were actually in sibling nodes? Now is where the petal hits the metal - we have to gather these nodes up and place references here in our node. How do we do this?

  • add hiden fields to the VRMLNode structure;
  • copy pointers from sibling nodes to our hidden fields;
  • run the "compile" function, if required.
So, edit VRMLNodes.pm, and look for the following fields in the VRML1_IndexedFaceSet structure:
        ....
        _color => [SFNode, NULL, inputOutput, 0],
        _coord => [SFNode, NULL, inputOutput, 0],
        _normal => [SFNode, NULL, inputOutput, 0],
        _texCoord => [SFNode, NULL, inputOutput, 0],
        _ccw => [SFBool, TRUE, initializeOnly, 0],
        ....
(note the "_" character - this makes these fields hidden from normal parsing, as X3D names can not start with the underscore character)

Now, we create the render_VRML1_IndexedFaceSet function like this:

void render_VRML1_IndexedFaceSet (struct X3D_VRML1_IndexedFaceSet *node) {

        /* if we need to remake this IndexedFaceSet */
        /* this is like the COMPILE_POLY_IF_REQUIRED(a,b,c,d) macro, with additional steps */
        if(!node->_intern || node->_change != ((struct X3D_PolyRep *)node->_intern)->irep_change) {
                copyPointersToVRML1IndexedFaceSet(node);
                compileNode ((void *)compile_polyrep, node,
                        node->_coord, node->_color, node->_normal, node->_texCoord);
        }
        /* something went wrong... */
        if (!node->_intern) return;

        CULL_FACE(node->_solid)
        render_polyrep(node);
}
This is much like the render_IndexedFaceSet found in Component_Geometry3D.c, except that when we need to "compile" the node, we have added in a call to a function
copyPointersToVRML1IndexedFaceSet(node);
that fills in the "_color" ... hidden fields mentioned above.

g) src/lib/scenegraph/GenPolyRep.c

The code in here takes specific fields from all of the PolyRep nodes, and makes the internal PolyRep structure. You will see the code gathering information in the code:

                case NODE_VRML1_IndexedFaceSet:
                        orig_coordIndex= &VRML1_INDEXEDFACESET(node)->coordIndex;
                        cpv = VRML1_INDEXEDFACESET(node)->_cpv;
                        npv = VRML1_INDEXEDFACESET(node)->_npv;
                        ccw = VRML1_INDEXEDFACESET(node)->_ccw;

h) src/lib/scenegraph/Polyrep.c

Nothing needed!

i) src/lib/scenegraph/StreamPoly.c

The Function defaultTextureMap collects information for creating a texture mapping, if a mapping has not been supplied.

i) src/lib/opengl/RenderTextures.c

Nothing needed!

step 3) Compile the source code.

If you do not know already, see the FAQ for info on how to compile FreeWRL.

step 4) Run an example.

Here is an example for you:

#VRML V1.0 ascii

Separator {
        Material {
                ambientColor  1 1 0
                diffuseColor  1 1 0
                specularColor 1 1 0
        }
        ShapeHints {
                creaseAngle 0.0
                shapeType SOLID
                shapeType UNKNOWN_SHAPE_TYPE
                faceType CONVEX
                vertexOrdering COUNTERCLOCKWISE

        }

        Coordinate3 {
                point [

                        -3.0 1.5 1.0,
                        3.0 1.5 1.0,
                        3.0 1.5 -1.0,
                        -3.0 1.5 -1.0,
                        -3.0 -1.5 1.0,
                        3.0 -1.5 1.0,
                        3.0 -1.5 -1.0,
                        -3.0 -1.5 -1.0,
                        ]
                }

        IndexedFaceSet {
                coordIndex [
                        0, 1, 2, 3, -1,
                        7, 6, 5, 4, -1,
                        0, 4, 5, 1, -1,
                        1, 5, 6, 2, -1,
                        2, 6, 7, 3, -1,
                        3, 7, 4, 0,

                ]
        }
}

Trailing thoughts

If you want to help develop FreeWRL to use in an interesting project, join the mailing list (send mail to freewrl-join@crc.ca). Anyone is welcome.

Contact

If you want to help, please email John Stewart (freewrl-09 -at- rogers.com) - an employee of the Communications Research Centre (CRC), Canada.

FreeWRL is produced by dedicated volunteers, and employees of CRC, and is released under the LGPL License as Open Source to the world community.

DISCLAIMER: All information and programs presented on these pages is presented strictly on an as-is basis without an explicit or implicit warranty or guarantee of any kind, not even for fitness for any particular purpose. The FreeWRL logo is based on the Linux Penguin logo by Larry Ewing. All trademarks are owned by their respective owners.

SourceForge.net Logo