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:
- 'field SFInt32 numberOfSpheres 2': the number of spheres you want to create, data type is a
SFInt32 (Integer).
- 'field SFFloat spacing 2': specifies the distance between spheres, data type is a 'float' (e.g.
spacing 2.58)
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.