Examples For VRML

6 Examples

Source code for a reference implementation of VRMLScript is available in the archive vrmlscript.zip. This source is freely available for use in adding VRMLScript to your browser. Please read the readme.txt file contained in that package. It lists the restrictions for use, which are:

  • you must credit Silicon Graphics for the code
  • you cannot sell the source
  • if you redistribute this package, you must do so intact, including the readme.txt file
  • you may not use the code for purposes other than implementing a VRMLScript compiler and interpreter.

The package contains everything you need to parse and interpret VRMLScript. Also included are classes to implement the data types, including vector and quaternion math, a general MF array class, string functions and time functions. Currently it is packaged for use on a PC with Windows 95 and Microsoft Developer Studio. If you port the code to other environments, please let us know and we will add links to your site

Accessing Fields

4 Accessing Fields

The fields, eventIns and eventOuts of a Script node are accessible from its VRMLScript functions. As in all other nodes the fields are accessible only within the Script. The Script’s eventIns can be routed to and its eventOuts can be routed from. Another Script node with a pointer to this node can access its eventIns and eventOuts just like any other node.

4.1 Accessing Fields and EventOuts of the Script

Fields defined in the Script node are available to the script by using its name. It’s value can be read or written. This value is persistent across function calls. EventOuts defined in the script node can also be read. The value is the last value sent.

4.2 Accessing Fields and EventOuts of Other Nodes

The script can access any exposedField, eventIn or eventOut of any node to which it has a pointer:

    DEF SomeNode Transform { }
    Script {
        field SFNode node USE SomeNode
        eventIn SFVec3f pos
        directOutput TRUE
        url "... 
            function pos(value) { 
                node.set_translation = value; 
            }"
    }

This sends a set_translation eventIn to the Transform node. An eventIn on a passed node can appear only on the left side of the assignment. An eventOut in the passed node can appear only on the right side, which reads the last value sent out. Fields in the passed node cannot be accessed, but exposedFields can either send an event to the “set_…” eventIn, or read the current value of the “…_changed” eventOut. This follows the routing model of the rest of VRML.

4.3 Sending EventOuts

Assigning to an eventOut sends that event at the completion of the currently executing function. This implies that assigning to the eventOut multiple times during one execution of the function still only sends one event and that event is the last value assigned.

EventIn Handling

3 EventIn Handling

Events sent to the Script node are passed to the corresponding VRMLScript function in the script. It is necessary to specify the script in the url field of the Script node. The function’s name is the same as the eventIn and is passed two arguments, the event value and its timestamp (See “Parameter passing and the EventIn function“). If there isn’t a corresponding VRMLScript function in the script, the browser’s behavior is undefined.

For example, the following Script node has one eventIn field whose name is start:

    Script { 
        eventIn SFBool start
        url "vrmlscript: function start(value, timestamp) { ... }"
    }

In the above example, when the start eventIn is sent the start() function is executed.

3.1 Parameter Passing and the EventIn Function

When a Script node receives an eventIn, a corresponding method in the file specified in the url field of the Script node is called, which has two arguments. The value of the eventIn is passed as the first argument and timestamp of the eventIn is passed as the second argument. The type of the value is the same as the type of the EventIn and the type of the timestamp is SFTime.

3.2 eventsProcessed() Method

Authors may define a function named eventsProcessed which will be called after some set of events has been received. Some implementations will call this function after the return from each EventIn function, while others will call it only after processing a number of EventIn functions. In the latter case an author can improve performance by placing lengthy processing algorithms which do not need to be executed for every event received into the eventsProcessed function.

Example:
The author needs to compute a complex inverse kinematics operation at each time step of an animation sequence. The sequence is single-stepped using a TouchSensor and button geometry. Normally the author would have an EventIn function execute whenever the button is pressed. This function would increment the time step then run the inverse kinematics algorithm. But this would execute the complex algorithm at every button press and the user could easily get ahead of the algorithm by clicking on the button rapidly. To solve this the EventIn function can be changed to simply increment the time step and the IK algorithm can be moved to an eventsProcessed function. In an efficient implementation the clicks would be queued. When the user clicks quickly the time step would be incremented once for each button click but the complex algorithm will be executed only once. This way the animation sequence will keep up with the user.

The eventsProcessed function takes no parameters. Events generated from it are given the timestamp of the last event processed.

3.3 initialize() Method

Authors may define a function named initialize which is called when the corresponding Script node has been loaded and before any events are processed. This can be used to prepare for processing before events are received, such as constructing geometry or initializing external mechanisms.

The initialize function takes no parameters. Events generated from it are given the timestamp of when the Script node was loaded.

3.3 shutdown() Method

Authors may define a function named shutdown which is called when the corresponding Script node is deleted or the world containing the Script node is unloaded or replaced by another world. This can be used to send events informing external mechanisms that the Script node is being deleted so they can clean up files, etc.

The shutdown function takes no parameters. Events generated from it are given the timestamp of when the Script node was deleted.

Bitwise Operators

Bitwise Operators

Bitwise operators include and (‘&‘), or (‘|‘), exclusive or (‘^‘), left shift (‘<<‘), right shift (‘>>‘), and right shift, zero fill (‘>>>‘). These are all binary operators and are valid on any scalar type. When they are applied the scalar value is converted to an SFInt32 before the operation, and back to the original expression type after. Therefore roundoff error can occur when applying them to SFFloat or SFTime values. The shift operators shift the operand on the left side the number of bits specified by the operator on the right side. The difference between right shift and right shift, zero fill is that the former preserves the sign of the left operator and the latter always puts a zero in the most significant bits.

Examples:

a & 0x0FF       // clears upper 24 bits of 'a'
a >> 5          // shift 'a' 5 bits to the right, sign extend

1.4.4 Logical and Comparison Operators

Logical expressions include logical and (‘&&‘), logical or (‘||‘), logical not (‘!‘), and the comparison operators (‘<‘, ‘<=‘, ‘==‘, ‘!=‘, ‘>=‘, ‘>‘). Logical not is prefix unary, the rest are binary. Each evaluates to either 0 (false) or 1 (true). The constants true, false, TRUE, and FALSE can also be used.

Examples:

a < 5
b > 0 && c > 1
!((a > 4) || (b < 6))

1.4.5 String Operators

All the comparison operators can be used to compare Strings for lexicographic order. Additionally the operators ‘+’ and ‘+=’ can be used to concatenate 2 strings. Any expression involving a String and any scalar type will first convert the scalar to a string and then perform the concatentation. Conversion of a String to a scalar type can be done with the functions parseInt() and parseFloat().

Examples:

'A one and ' + 'a two'         // result is "A one and a two"
'The magic number is ' + 7     // result is 'The magic number is 7'
a = 5;                         // 'a' contains an SFTime
a += 'is correct';             // 'a' is now the String '5 is correct'

1.4.6 Operator Precedence

Precedence rules are used to order evaluation. In the above compound expression example multiplication (‘*’) is evaluated before addition (‘+’). For operations of equal precedence evaluation order is shown in the table below. The default rules can be overridden with the use of the ‘(‘ and ‘)’ characters to bracket operations to be performed first.

Example:

a = b + c * d;    // c * d is evaluated first
a = (b + c) * d;  // b + c is evaluated first
a = b * c / d;    // b * c is evaluated first 
                  // ('*' and '/' have equal precedence, evaluation
                  // order is left-to-right)

Order of precedence is (unless otherwise stated evaluation order is left-to-right):

Operator Type Operator Comments
comma ,
assignment = += -= *= /= %=
<<= >>= >>>= &= ^= |=
right-to-left
conditional ?: tertiary operator
logical-or ||
logical-and &&
bitwise-or |
bitwise-xor ^
bitwise-and &
equality == !=
relational < <= > >=
bitwise shift << >> >>>
add/subtract + –
multiply/divide * / %
negate/increment ! ~ – ++ — unary operators
call, member () [] .

2 Supported Protocol in the Script Node’s url Field

The url field of the Script node may contain a URL that references VRMLScript code:

 Script {  url "http://foo.com/myScript.vs"  }

The vrmlscript: protocol allows the script to be placed inline as follows:

    Script {  url "vrmlscript: function foo() { ... }"   }

The url field may contain multiple URLs and thus reference a remote file or in-line code:

    Script { 
        url [ "http://foo.com/myScript.vs",
              "vrmlscript: function foo() { ... }" ]
    }

2.1 File Extension

The file extension for VRMLScript source code is .vs.

2.2 MIME Type

The MIME type for VRMLScript source code is defined as follows:

        application/x-vrmlscript

Statements

1.3 Statements

VRMLScript statements are block scoped the same as other C-like languages. A statement can appear alone in the body of an if or for statement. A body with multiple simple statements, or compound statement, must be placed between ‘{‘ and ‘}’ characters. This constitutes a new block and all variables defined in this block go out of scope at the end of the block. All simple statements must end with the ‘;’ character.

Example:

if (a < b)
    c = d;      // simple statement, c is local to the if statement

else {          // compound statement, c is no longer defined here
    e = f;      // e is local to the else block
    c = h + 1;
}               // e is no longer defined here

1.3.1 Conditional Statements

The if statement evaluates an expression, and selects one of two statements for execution. A simple if statement executes the statement following the condition if the result of the expression evaluation is not 0. The if…else statement additionally executes the statement following the else clause if the result of the expression evaluation is 0. For nested if…else statements, the else clause matches the nearest if statement. Braces can be used to override this.

Example

if (a < 0)  // simple if statement
    <statement>

if (a == 0)
    if (b > 5)  // if...else statement
        <statement>
    else        // this else clause matches the 'if (b > 5)' statement
        <statement>

if (a == 0) {
    if (b > 5)
        <statement>
}
else            // this else clause matches the 'if (a == 0)' statement
    <statement>

1.3.2 Looping Statements

The for statement contains 3 expressions which control the looping behavior, followed by a statement to which the loop is applied. It executes its first expression once before loop execution. It then evaluates its second expression before each loop and, if the expression evaluates to 0, exits the loop. It then executes the statement, followed by evaluation of the third expression. The loop then repeats, until looping is terminated, either by the second expression evaluating to 0 or until a break statement is encountered. In typical use, the first expression initializes a loop counter, the second evaluates it, and the third increments it.

Example:

for (i = 0; i < 10; ++i)
    <statement>

The while statement contains a single expression which controls the looping behavior, followed by a statement to which the loop is applied. Before each loop it evaluates the expression and, if the expression evaluates to 0, exits the loop. Otherwise it executes the statement and tests the expression again. The loop continues until terminated by the expression evaluating to 0 or until a break statement is encountered.

Example:

while (i < 10)
    <statement>

1.3.3 Expression Statements

Any valid expression in VRMLScript can be a statement. The 2 most common expressions are the function call and the assignment expression (see below).

1.3.4 Return Statement

The return statement does an immediate return from the function regardless of its nesting level in the block structure. If specified, its expression is evaluated and the result is returned to the calling function.

Example:

if (a == 0) {
    d = 1;
    return 5 + d;
}

1.3.5 Break Statement

The break statement exits the deepest enclosing looping statement. Execution continues at the statement following the looping statement.

Example:

while (i < 0) {
    if (q == 5) 
        break;
    <other statements>
}

// execution commences here upon break.

1.3.6 Continue Statement

The continue statement jumps to the end of the deepest enclosing looping statement. Execution continues at the end of the loop. In the case of the for statement, the third expression is evaluated and then the second expression is tested to see if the loop should be continued. In the case of the for…in statement the next element is assigned to the variable and the loop is continued. In the case of the while statement the expression is tested to see if the loop should be continued.

Example:

for a in colorArray {
    if (a[0] > 0.5)
        continue;
    <other statements>

    // loop commences here upon continue.
}

1.4 Expressions

Expressions combine variables, objects, constants and the results of other expressions with operators. The result of an expression evaluation is always another expression. In this way compound expressions can be built out of simpler ones. Operators combine one (unary), two (binary) or three (tertiary) values. Unary operators are placed before (prefix) or after (postfix) the value to be opertated on. Binary operators are placed between the values to be operated on. Tertiary operators always consist of 2 symbols, with one symbol place between each pair of values to be operated on.

Example:

a = -b;          // unary prefix operator
a = b++;         // unary postfix operator
a = b + c;       // binary operator
a = b ? c : d;   // tertiary operator
a = b * c + d;   // compound expression
                 // the product of b * c produces a value which
                 // is added to d

1.4.1 Assignment Operators

An expression of the form expression = expression assigns the result of the right-hand expression to the expression on the left-hand side. The left-hand expression must result in a variable into which a value may be stored. This includes simple identifiers, subscripting operators, members of objects, and the return value of a function call.

Examples:

a = 5;          // simple assignment
a[3] = 4;       // subscripted assignment
foo()[2] = 3;   // function returning an MFField

In addition, a set of shorthand operators exist for doing an binary operation using the left-hand expression and the right-hand expression, then assigning the result to the left-hand expression. These operators are plus-equal (‘+=‘), minus-equal (‘-=’), times-equal (‘*=‘) divide-equal (‘/=‘), mod-equal (‘%=‘), and-equal (‘&=‘), or-equal (‘|=‘), xor-equal (‘^=‘), shift-left-equal (‘<<=‘), shift-right-equal (‘>>=‘), shift-right-fill-zero-equal (‘>>>=‘).

Examples:

a += 5;              // adds 5 to the value of a and assigns it to a
a[3] &= 0x0000FFFF;  // performs bitwise-and of a[3] and 0x0000FFFF
                     // assigning result to a[3]

1.4.2 Arithmetic Operators

Arithmetic operators include negation (‘‘), ones-complement (‘~‘), increment (‘++‘), decrement (‘‘) and the operators (‘+‘, ‘‘, ‘*‘, ‘/‘, ‘%‘). Negation and ones-complement are prefix unary. Increment and decrement are prefix or postfix unary. The rest are binary.

Examples:

5 + b
(c + 5) * 7
(-b / 4) % 6
(c & 0xFF) | 256

The increment an decrement operators behave differently depending on whether they are used as prefix or postfix operators. In either case, if the expression to which the operator is applied is a variable, the value of that variable is incremented or decremented. A value is also returned from the expression. When used as a prefix operator the value returned is that of the expression after the increment or decrement. When used as a postfix operator the value returned is that of the expression before the increment or decrement.

Examples

a = 5;         // Value of 'a' is 5
b = a++;       // Value of 'b' is 5, value of 'a' is 6
c = ++b;       // Value of 'c' is 6, value of 'b' is 6

Language

The script syntax has similarities to JavaScript as well as several other scripting languages. VRMLScript was designed to be parsed by YACC and is therefore an LALR(1) grammar.

1.1 BNF of script syntax

script :
functions
NULL
functions:
functions function
function
function:
function beginFunction ( args ) statementBlock
beginFunction:
identifier
args:
args , identifier
identifier
NULL
stmntblk:
{ statements }
{ }
statement
statements :
statements statement
statement
statement :
ifStatement
forStatement
whileStatement
returnStatement ;
breakStatement ;
continueStatement ;
compoundExpression ;
ifStatement :
if ( compoundExpression ) statementBlock
if ( compoundExpression ) statementBlock else statementBlock
forStatement :
for ( optionalExpression ; optionalExpression ; optionalExpression ) statementBlock
whileStatement :
while ( compoundExpression ) statementBlock
returnStatement :
return compoundExpression
return
breakStatement :
break
continueStatement :
continue
compoundExpression :
expression , compoundExpression
expression
optionalExpression:
compoundExpression
NULL
expression : ( compoundExpression )
expression
! expression
~ expression
leftVariable = expression
leftVariable += expression
leftVariable -= expression
leftVariable *= expression
leftVariable /= expression
leftVariable %= expression
leftVariable &= expression
leftVariable |= expression
leftVariable ^= expression
leftVariable <<= expression
leftVariable >>= expression
leftVariable >>>= expression
++ expression
expression
expression ++
expression
expression ? expression : expression
expression == expression
expression != expression
expression < expression
expression <= expression
expression >= expression
expression > expression
expression + expression
expression expression
expression * expression
expression / expression
expression % expression
expression && expression
expression || expression
expression & expression
expression | expression
expression ^ expression
expression << expression
expression >> expression
expression >>> expression
string
number
objectMethodCall
objectMemberAccess
functionCall
new constructor
arrayDereference
variable
functionCall :
identifier ( params )
constructor :
identifier ( params )
objectMethodCall :
expression . identifier ( params )
objectMethodAccess :
expression . identifier
params :
params , expression
expression
NULL
arrayDereference :
expression [ compoundExpression ]
leftVariable :
objectMethodAccess
arrayDereference
variable
variable :
identifier
string:
utf8
number:
0{0-7}+
… ANSI C floating point number …
0X{ 0-9 }+
0x{ 0-9 }+
TRUE
true
FALSE
false
identifier:
utf8Character { utf8 }*
utf8Character:
… any legal UTF8 character except 0-9 …
utf8:
utf8Character
0-9