Over the last few weeks, I'm been doing some quite heavy scripting in VRML, ECMAScript and Java. One of the things I've found is that it can be quite awkward to find a way to debug scripts and event chains in different browsers. In normal programming, you can trace values as you go througb your program, and find out where the errors are occuring. The problem is that VRML has no fixed way of writing text output. In this workshop, we're going to develop a way to get around this problem.
The simplest way to write text output is to write it to the VRML Console. Most browser have a console window that they write error messages and other information into. You can use this to output information from inside scripts. Cosmo Player, for instance, allows you to write to the console using the print() function in ECMAScript and the Browser.print() function in Java. I'm going to use these throughout this workshop. It may be that your browser doesn't support them. If this is the case, we're going to create a debugging method that doesn't use these later on. Otherwise, you can look up the function used by your browser in the release ntoes that came with it or in the FAQ section of this site.
These print functions take strings as arguments, although most of the time they can also convert data to strings automatically. So, you can write something like
print("vector: "); print(vector);
or
print("vector: " + vector);
in most cases. So, the simplest way to debug a script is to use the built-in print functions to write to the console. We can now use this to write a node that acts as an event debugger for standard VRML files. A few people have asked for a way to tell what events are being sent and when, in an effort to debug animation and interaction in their worlds. Well, now we're going to create a node that will do it for you.
What we want to do is create a node that will sit in an event chain and pass events through it, but also writes the value and time of that event to the console. So, the first thing that we'll need is a PROTO definition for our debug node. I'm going to concentrate on debugging an SFTime event chain, but just change the types and it'll work perfectly well for any VRML type. So, the PROTO:
PROTO SFTimeDebugger [ eventIn SFTime input eventOut SFTime output ]
So, this will give us a definition of a new node with one input and one output. Now, we need to implement it. This is done with a very simple piece of ECMAScript. If your browser doesn't support ECMAScript, try substituting "vrmlscript: for "javascript:. Right, here we go...
{ Script { eventIn SFTime input IS input eventOut SFTime output IS output url "javascript: function input(value,time) { print(value); output = value; } " } }
And that's it! Simple. The script will print the value, and then output the value so that the event chain can continue. All you do is plug it in between an event's source and destination, and watch the values come tumbling out.
Now, this is all well and good if you've got a print() function that works, but what if you don't? What if you've got a browser that doesn't have any way of writing to the console from a script? Then, surely, you're done for. But no, help is at hand. Now, we're going to forget our reliance on a print function and design a debugging console that works purely in VRML. This also has the advantage that you can have both your world and the console visible and active at the same time. How? Yes, you guessed it, we're going to recycle the HUD technology from the previous workshop and create a small text display that is permanently present in front of you actually in your virtual world. Before we do that, though, we're going to need an event debugger that can write either to the console or give a string output, otherwise we won't be able to get any output onto our console. We simply expand the old SFTimeDebugger by adding another eventOut to carry the strings we want to print. I'm also going to add a couple of fields to allow you to set the output style for the node.
PROTO SFTimeDebugger [ eventIn SFTime input field SFBool consoleOutput TRUE field SFBool stringOutput TRUE eventOut SFString debugString eventOut SFTime output ] { Script { eventIn SFTime input IS input field SFBool consoleOutput IS consoleOutput field SFBool stringOutput IS stringOutput eventOut SFString debugString IS debugString eventOut SFTime output IS output url "javascript: function input(value,time) { if (consoleOutput) print(value); if (stringOutput) debugString = value.toString(); output = value; } " } }
As you can see, this new event debugger gives you the option of writing the value that passes through it into either the console (if consoleOutput is TRUE) or a string output event (if stringOutput is TRUE). This is only a little more complex than the previous version, and it makes everything a lot more flexible. The file with this PROTO in is available as SFTimeDebugger.wrl for you to use and adapt.
Right then, now we need to create something that will take these string events and display them on our own VRML console. First of all we need the PROTO for the node.
PROTO DebugConsole [ eventIn SFString nextString field SFVec3f offset 0.11 0 -0.2 ]
This is pretty simple, isn't it? We've just got an input for the strings we want to display, an offset for the position of the console (which is by default in the correct position on a normal screen), and that's it. Well, as you've probably guessed, it's not so simple inside. Inside is that same sort of arragement we had before in the HUD workshop (have a read if you haven't already). However, the interesting bits come in the script inside the console. You can have a look at the complete code, or download the DebugConsole.wrl file containing the complete PROTO for the node. In the meantime, let's look at the script.
DEF CONTROLLER Script { eventIn SFString input IS nextString eventIn SFTime clear field MFString contents [] eventOut MFString output url "javascript: function initialize() { contents = new MFString('DebugConsole v1.0'); output = contents; } function input(value, time) { var length = contents.length; if (length > 25) { for (i=0; i<25; i++) { contents[i] = contents[i+1]; } contents[25] = value; } else { contents[length] = value; } } function clear(value, time) { contents = new MFString(); } function eventsProcessed() { output = contents; } " }
The text display is implemented as a Text node, which contains a MFString field with the text in. So, to set the text on the console, we simply need to assign an MFString value to the output, which is ROUTEd within the console to the string field. So, when we start the script, we make the text say a little bit of information about the console by making a new MFString and assigning it to the output. The same is done when the clear eventIn is activated, except this one creates an empty MFString.
The current contents of the string are stored in the contents field. So, when we receive a new string to display, we need to determine whether it can just be added at the end or whether everything else needs to be scrolled upwards. The maximum length is 26 strings, so if the length is less than or equal to 25, we're OK and can just add the string at the end. Otherwise, we need to go through the entire list of strings, copying the next string in the list into each position, thus scrolling the lines up. The new item can then be added at the end in position 25. The actual contents object is only sent out in the eventsProcessed() function, as this is normally called once per frame, which is the only time we need to update the screen.
So, that should all make sense to you. You can take a look at the results here (along with the world's code). As you can see, this test world is debugging the values sent from a simple TimeSensor. Also, the console moves with the user, so it always remains in view. There is one problem with the console, in that it will drop the frame rate of your worlds a fair amount, as there is a lot more geometry to render, with all the text and everything.
Please feel free to use both the SFTimeDebugger and the DebugConsole if you need to, I only hope they're of some use to you.