Liner

tested with +


VRML2.0 comes with 12 so-called 'Browser methods'. One of them is 'createVrmlFromString()'. This method takes a string and creates VRML nodes out of it. Once this is done you can add these nodes to the scene. This can be useful if you want to write a PROTO which creates objects on the fly with you specifying only the number of objects you want to have. Let's say that you need to line up spheres for whatever obscure reason there might be. You start with a string which describes a sphere node:

sphereString = 	'Transform { '+

		'  children [ '+

		'    Shape { '+

		'      appearance Appearance { '+

		'        material Material { '+

		'          diffuseColor 1 0 0 '+

		'        } '+

		'      } '+

		'      geometry Sphere {} '+

		'    } '+

		'  ] '+

		'} ';

You see that I split up the string into 12 substrings. I do this only for readability in order to avoid syntax errors. You could write it in one line as well. The next step is to use createVrmlFromString() on that string:

xform = new MFNode();

xform = Browser.createVrmlFromString(sphereString):

The spec says (C.6.2) that createVrmlFromString() returns a MFNode object. Before I call createVrmlFromString() I can create an empty MFNode object with 'xform = new MFNode();'. This object then can be used to cache the result of createVrmlFromString(). An easier way to do this is to combine it into one statement: 'linerGroup.addChildren = Browser.createVrmlFromString('niceStringHere')'. And yes, all Browser methods need to be called on the Browser object (Browser.method...).
Let's put together what we've got so far and write a little .wrl file:

DEF linerGroup Group {}

Script {

  directOutput TRUE

  field	SFNode	linerGroup USE linerGroup

  url "javascript:

    function initialize() {

      sphereString = 'Transform { '+

		'  children [ '+

		'    Shape { '+

		'      appearance Appearance { '+

		'        material Material { '+

		'          diffuseColor 1 0 0 '+

		'        } '+

		'      } '+

		'      geometry Sphere {} '+

		'    } '+

		'  ] '+

		'} ';

      xform = new MFNode();

      xform = Browser.createVrmlFromString(sphereString);

      linerGroup.addChildren = xform;

    }

  "

}
The file starts with an empty Group 'linerGroup' which serves as a container for whatever we want to add to the scene. The Script knows about this Group via a field reference : 'field SFNode linerGroup USE linerGroup'. Group is of type SFNode and I use the same name for 'linerGroup' inside of the Script as outside. You could be lazy and type 'field SFNode lG USE linerGroup' instead, it makes no difference ... directOutput TRUE makes it possible to talk directly to nodes from within the script. The default is directOutput FALSE which means that the only way to communicate for the script is through external ROUTEs.
After the new node has been created the Script can add it to the scene with a addChildren call: 'linerGroup.addChildren = xform;'. ( liner source and liner.wrl).
OK, that's one node, how about more than one? How about a 'for' loop?


for(i=0;i<5;i++) {

  xform = new MFNode();

  xform = Browser.createVrmlFromString(sphereString);

  xform[0].set_translation = new SFVec3f(2*i,0,0);

  accumulatedTransforms[i] = xform[0];

}

linerGroup.addChildren = accumulatedTransforms;

Since we want to add more than one node to the scene, we need to store the nodes in a temporary node: 'accumulatedTransforms'. Each new sphere node will be stored in this temporary node: 'accumulatedTransforms[i] = xform[0]'. The MFNode will end up with five sphere nodes in it:


  accumulatedTransforms {

    i=0	Transform { sphere1 ... }

    i=1	Transform { sphere2 ... }

    i=2	Transform { sphere3 ... }

    i=3	Transform { sphere4 ... }

    i=4	Transform { sphere5 ... }

  }

In JavaScript arrays always start from '0'. That's why the 'for' loop starts at '0'.
What is '... = xform[0]' doing here? Can't I just say '... = xform'? The subscript '[0]' refers to the first node in the MFNode 'xform' which is a Transform (a SFNode!!). Each element in 'accumulatedTransforms' is a SFNode and expects a SFNode (the previously mentioned Transform, confused? read it again).
The next brain-cracker is 'xform[0].set_translation = new SFVec3f(2*i,0,0);'. Without this line all spheres would be on top of each other, speak you would see only one sphere. Lucky enough the SFNode 'xform[0]' is a Transform which means that I can set the translation of this node. The exposedField 'set_translation' expects a SFVec3f, so let's give it one: 'new SFVec3f(2*i,0,0);'. '2*i' is the x coordinate (means the spheres have a distance of 2 to each other). The y and z coordinates are '0'. Take a look at liner source and liner.wrl.
Wrapping the fruits of our work into a proto is easy:


PROTO LINER

[

field  SFInt32  numberOfSpheres	2

field  SFFloat  spacing         2

]

{

  DEF linerGroup Group {}

	

  Script {

    directOutput TRUE

    field  SFInt32  numberOfSpheres IS numberOfSpheres

    field  SFNode   linerGroup USE linerGroup

    field  SFFloat  spacing IS spacing

    field  MFNode   accumulatedTransforms []

	

    url "javascript:

      function initialize() {

        sphereString = 'Transform { '+

			'  children [ '+

			'    Shape { '+

			'      appearance Appearance { '+

			'        material Material { '+

			'          diffuseColor 1 0 0 '+

			'        } '+

			'      } '+

			'      geometry Sphere {} '+

			'    } '+

			'  ] '+

			'} ';

        for(i=0;i < numberOfSpheres;i++) {

          xform = new MFNode();

          xform = Browser.createVrmlFromString(sphereString);

          xform[0].set_translation = new SFVec3f(spacing*i,0,0);

          accumulatedTransforms[i] = xform[0];

        }

        linerGroup.addChildren = accumulatedTransforms;

      }

    "

  }

}

Two additions have been made to give the proto more usability: Both fields are ISed into the Script. The defaults can be overridden as you instance the proto, e.g. 'LINER { numberOfSpheres 8 spacing 4.5 }'. liner source and liner.wrl




Copyright © 1996-98 Markus Roskothen. All rights reserved.