OpenGL VRML Shockwave 3D
Home Tutorials Books Applications Tools Docs Models Textures

Send me bugs and suggestions, please
VRML Interactive Tutorial
Full list

Script Tutorial

Introduction and Sintax
Defining Fields and Events
Accessing Fields and Events
When are scripts executed
Script Example: Highlight
Browser Script Interface
Appendix: Data types

Script Examples: Highlight


In this section it is shown how to highlight a shape when the cursor is over it or when the mouse is pressed. There are many ways of accomplishing this task, in here three different methods will be presented.

  • Using a Switch node
  • Changing the materials of a shape
  • Accessing a field directly
  • 1.Using a Switch node

    One way of changing the attributes of a shape is using a switch node in which each choice contains the shape with certain attributes. When the mouse is over the shape the whichChoice field of the switch is changed, and when the mouse is no longer over the whichChoice field is changed back to its initial value.

    Putting it in an algorithmic form would result in something like:

    Example:
    if (mouse is over)
      whichChoice = 1;
    else
      whichChoice = 0; // initial value


    This is probably the simplest class of examples of scripts. As you've noticed before you can route events from one node to another, however the values associated with the events must be of the same type, i.e. you can route booleans to booleans but not booleans to integers. In this example we want to route a boolean value to an integer event. You can't do this directly in VRML, you can only route events if they have the same data type. Using a script is a simple way to overcome this limitation.

    First lets take a look at our world description dealing with the script details afterwards.



    Example:
    #VRML V2.0 utf8
    
    Group {
        children [
    	DEF sw Switch {
    		whichChoice 0
    		choice [
    			Shape { appearance Appearance { 
    					material Material { 
    						emissiveColor 0.2 0.20.2
    					}
    				}
    				geometry Sphere {}
    			}
    			Shape { appearance Appearance { 
    					material Material { 
    						emissiveColor 0.5 0.50.5
    					}
    				}
    				geometry Sphere {}
    			}
    		]
    	}
    	DEF ts TouchSensor {}
    ....]
    }
    


    In the above described world there is a group containing a Switch and a TouchSensor. The Switch node contains two shapes which differ in the emissive color, one is brighter than the other as you can see by the values in the source code. By default the darker shape is displayed because whichChoice is zero indicating that it is the first children the one which is active.

    When the mouse is over the shape selected in the Switch node the TouchSensor will generate the boolean event isOver with a value TRUE, otherwise, i.e. if the mouse isn't over the shape, the event isOver has a value FALSE.

    The highlight script must set the value of whichChoice to 1 when it receives a boolean event with the value TRUE, and set whichChoice to 0 when it receives a boolean event with the value FALSE.

    To define the script node one must define the fields, events it receives and sends. From the description above one knows that the script must receive a boolean event, and generate a scalar, or integer, event. Therefore the first lines of the script can be:

    Example:
    DEF highlight Script {
      eventIn SFBool in
      eventOut SFInt32 out
      ....
    }


    So far so good. Now lets define the script function. As mentioned before, this function has the same name as the eventIn which is supposed to handle. We also know that the functions to handle events have two parameters: the value associated with the event, and its timestamp. Having this in mind we can now write the header of the function:

    Example:
    function in(value, timestamp) { ... }


    The body of the function is also simple in this case. When the value TRUE is received an eventOut with a value of 1 is generated, and when the value received is FALSE the eventOut generated is 0. As mentioned above in order to generate events one needs only to make an attribution to the respective eventOut as defined in the header of the script. The complete function is:

    Example:
    function in2(value, timestamp) {
      if (value)
        out = 1;
      else
        out = 0;
    }


    The only thing remaining is to include the function in the script. In this case, because the script is so small, we are going to inline the function so that it will appear in the same file as the rest of the VRML world. The complete Script is as follows:

    Example:
    DEF switchScript Script {
      eventIn SFBool in2
      eventOut SFInt32 out
      url "javascript:
        function in(value, timestamp) {
          if (value)
            out = 1;
          else
            out = 0;
        }"
    }


    When inlining a function or set of functions in a Script node, the url field value must be within quotes. Also note that the language in which the script is written must be present.
    Syntax:

    url :"language_name: list_of_functions"


    where language_name is the name of a supported language by your browser/plugin (check the manual!).

    Finally we just have to establish the routes between the nodes. We want to send the event isOver from the TouchSensor to the Script node eventIn in, and to send the eventOut out to the Switch node whichChoice eventIn. The required routes are as follows:

    Example:
    ROUTE ts.isOver TO switchScript.in2
    ROUTE switchScript.out TO sw.set_whichChoice


    This is it!

    Conclusion

    You can use the switch node to change parts of your world in runtime. The use of a Switch node to change a single attribute of a shape is not the simplest way of doing things. This particular example only requires you to change an attribute in the material of a shape, namely the emissiveColor. Because the emissiveColor is an exposedField, i.e. it can receive events (set_emissiveColor) there is a more direct way of doing it as we shall see in the next section. In order to justify the use of a Switch node you should have at least several attributes different not to mention completely different geometries.

    2.Changing the material of a shape

    In this example one is going to change the emissiveColor field of a material using routes. Because one is not using a Switch node the static world is much simpler. Let's take a look at our world description dealing with the script details afterwards.

    Example:
    #VRML V2.0 utf8
    
    Group {
        children [
    	Shape { appearance Appearance { 
    			material DEF mat Material { 
    				emissiveColor 0.2 0.2 0.2
    			} 
    		}
    		geometry Sphere {}
    	}
    	DEF ts TouchSensor {}
        ]
    }


    Note that the material of the shape is DEF'ed, this is because we need to route the new emissive color to it.

    The script to change the emissive color of the shape upon receiving the event isOver from the TouchSensor needs to receive a boolean event and generate a SFColor event (the data type of emissiveColor). Therefore the first lines of the script can be something like:

    Example:
    DEF highlight Script {
      eventIn SFBool in2
      eventOut SFColor out
      ....
    }


    The function to process the eventIn in2 can be defined in rough terms as:

    Example:
    function in2(value, timestamp) {
      if (value)
        generate event with light color;
      else
        generate event with dark color;
    }


    In order to generate the event, and as seen before, one has only to set a value to out. A SFColor value is the equivalent of a three element array in JavaScript, see data types. In order to set a value to the eventOut out you have to set each component of the array in a separate statement. The complete function is:

    Example:
    function in2(value, timestamp) {
      if (value) {
        out[0] = 0.5;
        out[1] = 0.5;
        out[2] = 0.5
      }
      else {
        out[0] = 0.2;
        out[1] = 0.2;
        out[2] = 0.2
      }
    }


    Note: Recall that in JavaScript indexing arrays starts from zero, not from one.

    The necessary routes to complete this world are:

    Example:
    ROUTE ts.isOver TO switchScript.in2
    ROUTE switchScript.out TO mat.set_emissiveColor


    Only the second route differs from the first example. In the former example one was trying to change the whichChoice field of a Switch node, whereas now one is changing the emissiveColor of a Material node (previously defined as mat).

    Conclusion

    This is a far more direct way of doing things than in the previous example. Although the script is slightly longer, the static world is far more simpler, and the reasoning more direct.

    However there is yet an easier way of doing this, by accessing the field emissiveColor directly. The details are presented in the next section.

    3. Accessing a field directly

    In this section it is shown how to change the value of a field without using routes, i.e. directly accessing the fields value.

    The static world is the same as in the previous example. The only differences are in the script itself and in the number of routes required.

    The script for this example still receives an event when the mouse is over the shape, however no event is generated because the value of the emissiveColor field is changed directly from within the script. We still need a field SFColor, but this time an eventOut is not required. In order to change a field of a node directly we also need to have access to the node, i.e. to have a field SFNode with the desired node as its initial value. There is one more requirement to access a field directly, to have the scripts field directOutput set to TRUE. The first lines of the Script look like:

    Example:
    DEF highlight Script {
      eventIn SFBool in2
      field SFColor out 0 0 0
      field SFNode m USE mat
      directOutput TRUE
      ....
    }


    The function in2 can be defined as:

    Example:
    function in2(value, timestamp) {
      if (value) {
        out[0] = 0.5;
        out[1] = 0.5;
        out[2] = 0.5;
      }
      else {
        out[0] = 0.2;
        out[1] = 0.2;
        out[2] = 0.2;
      }
      m.emissiveColor = out;
    }


    There is only one route required:

    Example:
    ROUTE ts.isOver TO switchScript.in2


    Note that, when comparing with the previous examples, there is a route missing. In this last example we do not need to create a route to change the emissiveColor field of the Material since this is done in the Scritp directly.

    Conclusion

    Using direct access to fields in other nodes is an alternative to routing. As far as I know there are no disadvantages or advantages to either in terms of performance. In terms of design both have their pros and cons as you can see from the code.



    Site designed and maintained by António Ramires Fernandes
    Your comments, suggestions and references to further material are welcome!

    Lighthouse 3D privacy statement