A Simple createVrmlFromX Lesson

Revised 9/7/99 P. Fishwick to make all examples complete (without any extra coding) and work correctly.
All examples use Java in a Script Node, which works only in select browsers such as Cosmo and WorldView. Blaxxun currently does not support this method of scripting.

createVrmlFromString

Used to create a piece of VRML on the fly without referencing any external source.

To use simply create a string of the VRML that you want to add. For example we want to have a green box. The normal VRML file format for this is

Shape {
  appearance Appearance {
    material Material {
      emissiveColor 0 1 0
    }
  }
  geometry Box {}
}

So now you place this into a Java string:

String green_box =
  "Shape { " +
    "appearance Appearance { " + 
      "material Material { " +
        "emissiveColor 0 1 0 " +
      "} " +
    "} " +
    "geometry Box {} " +
  "}";

Then you need to turn this from string to VRML. Do this using the createVrmlFromString method of the Browser class. This method returns an array of BaseNodes (if using scripts). For a script you can then write

Browser b = getBrowser(); // this is part of the Script class
BaseNode[] vrml_box = b.createVrmlFromString(green_box);

This has now created a representation of the green box in java. But it has not yet been added to the scene, so you can't see it. Now you must add it to the scene. The only way that you can do this is to have a reference to a grouping node passed into the script. This means we need to have it included in the VRML script definition. For example:

#VRML V2.0 utf8
# This is the group where you will add the box, with
# extra lines for creating an event to drive 
# createVrmlFromString
DEF a_group Group {}

DEF my_script Script {
  url "create1.class"
  directOutput TRUE
  mustEvaluate TRUE
  eventIn SFBool activate
  field SFNode parentNode USE a_group
}

DEF Clock TimeSensor {loop TRUE}

ROUTE Clock.isActive TO my_script.activate
Now you need to write the script class for "create1.class" by create "create1.java" and then compiling the Java to create "create1.class": So:
// file "create1.java" when compiled yields "create1.class" 
import vrml.*;
import vrml.field.*;
import vrml.node.*;

public class create1 extends Script
{
    private SFNode the_parent;
    private Browser b;
    private BaseNode vrml_box [];

    public void initialize()
    {
        // get hold of the node reference
        the_parent = (SFNode) getField("parentNode");
        b = getBrowser();
    }

    public void processEvent(Event e)
    {
       // now in response to some input we want to create the new box:
       createNode();
    }

    private void createNode()
    {
        String green_box =
           "Shape { " +
            "appearance Appearance { " +
              "material Material { " +
                "emissiveColor 0 1 0 " +
              "} " +   
            "} " +
            "geometry Box {} " +
          "}";

        try
          {vrml_box = b.createVrmlFromString(green_box);}
        catch(vrml.InvalidVRMLSyntaxException theError)
	  {System.out.println("caught exception");}

        System.out.println(green_box);
        // Now we need to add it to the existing scene.
        // first need to get the eventIn of the Group
        MFNode add_kids = (MFNode) ( (Node) the_parent.getValue() ).getEventIn("addChildren");

        // now we can add this to the Node directly
        add_kids.setValue(vrml_box);
    }
}

createVrmlFromURL

Used to dynamically add external files to the current scene without the limitations of the Inline node.

The URL system is very similar to that used to create VRML content from a string. THe major difference is that instead of returning a node and adding that to the scene you actually get the information back through an event. That event must be an MFNode eventIn.

Lets look at our problem again. The box is now moved out to a separate VRML file (called box.wrl).

#VRML V2.0 utf8
Shape {
  appearance Appearance {
    material Material {
      emissiveColor 0 1 0
    }
  }
  geometry Box {}
}
Here is the create2.wrl file:
#VRML V2.0 utf8

# This is the group that you will be adding the box to.
DEF a_group Group {}

DEF my_script Script {
  url "create2.class"
  directOutput TRUE
  mustEvaluate TRUE
  eventIn SFBool activate
  field SFNode parentNode USE a_group
  field MFString target_url "box.wrl"
}

DEF Clock TimeSensor {loop TRUE}

ROUTE Clock.isActive TO my_script.activate

CreateVrmlFromURL takes three arguments: The first is an array of strings. This array is a list of URLs that can be retrieved. Like the rest of the nodes that use URL strings, this refers to the urls in decreasing order of preference.

The second argument is a BaseNode (Node for EAI) reference. This must be a pre-existing, legal VRML node reference. This node could be anything, including a node previously created with a createVrmlFromString call, that has not been added to the scene yet. You can use anything, so long as it is not null. Normally this is either the script itself (refered to using a "this" reference) or a Grouping node.

The third argument is a string. This is the name of the particular eventIn (or exposedField) that you want to send the new nodes to. If you had a Grouping node you could pick either the addChildren, removeChildren or children fields of the node as an example.

Lets look back at the example. Let's say that we will add the node directly to the Group node that we have a reference to (the_parent). The script code now becomes:

// file "create2.java" when compiled yields "create2.class" 
import vrml.*;
import vrml.field.*;
import vrml.node.*;

public class create2 extends Script
{
    private SFNode the_parent;
    private MFString target_url;
    private BaseNode tmp;
    private Browser b;

    public void initialize()
    {
        // get hold of the node reference
        target_url = (MFString) getField("target_url");
        the_parent = (SFNode) getField("parentNode");
        b = getBrowser();
    }

    public void processEvent(Event e)
    {
       // now in response to some input we want to create the new box:
       createNode();
    }

    private void createNode()
    {
        tmp = the_parent.getValue();
        String [] urls = new String[target_url.getSize()];
        target_url.getValue(urls);
        try
          {b.createVrmlFromURL(urls, tmp, "addChildren");}
        catch(vrml.InvalidVRMLSyntaxException theError)
	  {System.out.println("caught exception");}
        
    }
}
As you can see, there is a bit less code to do it this way.

An alternative is that we can send the information back to the script itself for later addition to the scene. We might want to do some other processing of it for example. To use the script instead of the node directly, some extra changes are needed.

First, the script now needs an MFNode eventIn:

#VRML V2.0 utf8
# This is the group that you will be adding the box to.
DEF a_group Group {}

DEF my_script Script {
  url "create3.class"
  directOutput TRUE
  mustEvaluate TRUE
  eventIn SFBool activate
  eventIn MFNode set_newChildren
  field SFNode parentNode USE a_group
  field MFString target_url "box.wrl"
}

DEF Clock TimeSensor {loop TRUE}

ROUTE Clock.isActive TO my_script.activate

Now we need to handle that in the script. This is an extra eventIn with some values, which are the nodes to add to the system. Using this, we can go back to the original script and use this as the event trigger. ie:

// file "create3.java" when compiled yields "create3.class" 
// file "create3.java" when compiled yields "create3.class" 
import vrml.*;
import vrml.field.*;
import vrml.node.*;

public class create3 extends Script
{
    private SFNode the_parent;
    private MFString target_url;
    private BaseNode tmp;
    private BaseNode vrml_box [];
    private Browser b;

    public void initialize()
    {
        // get hold of the node reference
        the_parent = (SFNode) getField("parentNode");
        b = getBrowser();
    }

    public void processEvent(Event e)
    {
       // now in response to some input we want to create the new box:
        if (e.getName().equals("set_newChildren"))
           nodesReady((ConstMFNode)(e.getValue()));
        else {
           target_url = (MFString) getField("target_url");
           createNode();
        }
    }

    private void createNode()
    {
        tmp = the_parent.getValue();
        String [] urls = new String[target_url.getSize()];
        target_url.getValue(urls);
        try
          {b.createVrmlFromURL(urls, tmp, "addChildren");}
        catch(vrml.InvalidVRMLSyntaxException theError)
	  {System.out.println("caught exception");}
        
    }
    private void nodesReady (ConstMFNode nodes)
    {
        BaseNode [] node_list = new BaseNode[nodes.getSize()];
        nodes.getValue(node_list);
        // Now we need to add it to the existing scene
        MFNode add_kids = (MFNode) ((Node) the_parent.getValue() ).getEventIn("addChildren");
        // now we add this to the node directly
        add_kids.setValue(node_list);
    }
}

create3.wrl will have the same behavior as create2.wrl since we did not process an set_newChildren eventIn (the ROUTE enabled the script using activate). To use set_newChildren, we can revise create3.wrl as follows:
#VRML V2.0 utf8
# This is the group that you will be adding the box to.
DEF a_group Group {
     children Shape {
  appearance Appearance {
    material Material {
      emissiveColor 0 1 0
    }
  }
  geometry Box {}
 }
}

DEF my_script Script {
  url "create3.class"
  directOutput TRUE
  mustEvaluate TRUE
  eventIn SFBool activate
  eventIn MFNode set_newChildren
  field SFNode parentNode USE a_group
}

ROUTE a_group.children_changed TO my_script.set_newChildren
There is a lot of extra code needed to do it this way. Notice how we need to first create an array of nodes before passing them to the getValue method. getValue then fills the array with the list of nodes which we can then pass on to the Group node a couple of lines later.

Well, that's it. All you need to know about the createVrmlFromX methods and how to use them within script nodes. If I'm feeling energetic I'll write one relating to use within the EAI at sometime in the not too distant future. always, if you have questions don't hesitate to email me. I'm doing a lot of travel recently so I might take a few days to respond to any queries.

-------------------------------------------------------------------
Justin Couch                           The Virtual Light Company
justin@vlc.com.au                 http://www.vlc.com.au/~justin/
-------------------------------------------------------------------
"Look through the lens, and the light breaks down into many lights.
 Turn it or move it, and a new set of arrangements appears... is it
 a single light or many lights, lights that one must know how to
 distinguish, recognise and appreciate? Is it one light with many
 frames or one frame for many lights?"      -Suocomandante Marcos 
-------------------------------------------------------------------