| Java and VRML 2.0 Part 2: Putting theory into practice
|
| by Rodger Lea
|
|
Ok, so grace of last month's epistle you've got the basics of Java
and VRML 2.0 sorted, you sense the power, you see the vistas but you
need that little extra help to get you up and authoring. Fear not,
this article will show a few choice little snippets of code that will
burn off any lingering fog and open up those vistas. What's more,
after we've given you all the details, we'll point you towards an
authoring tool that will take care of the details, leaving you to fill
in the creative parts.
|
|
|
In last month's article we introduced you to the basic theory and
mechanisms for connecting a Java script to a VRML scene and
communicating between the two. In this article we'll put some of
that theory into practice by showing a few examples of what you
can do with these two powerful tools.
|
Java Tutorial
Sony
|
Like the previous article, this one is based on our book, Java for
3D and VRML worlds, (Lea, Matsuda and Miyashita, New Riders
Publishing, ISBN 1-56205-689-1) which covers in detail how to
use Java to build active VRML scenes. The book goes into more
detail and includes examples showing use of AWT, threads and
multi-user VRML servers using Java networking. The book is
accompanied by a CD with all source code, Sony's VRML 2.0
browser, a Java compiler and all instructions you need to get
started with Java and VRML.
|
|
|
A brief recap
In the previous article we showed you how the internal execution
model of VRML 2.0, based on events, could be used to
communicate with a piece of Java code we referred to as the event
handler class. The bridge between the VRML and Java worlds was
provided by the special Script node which passed events from the
VRML world across to the event handler class. Events generated in the
VRML scene, when routed to the eventIn fields of the Script node are
delivered into the event handler class of the associated Java program
as specified in the Script node. Conversely, within the Java code, an
event can be written into the Script node in the VRML scene which can
then be routed to other nodes in the scene.
|
|
|
Once you have mastered this basic mechanism then you are free
to use Java to manipulate VRML in anyway your imagination can
dream up. To help you on your way, here are a couple of examples
designed to let you see some of the possibilities.
|
|
|
Random movement of a scene object
The example VRML file below is taken from a larger example in the book
which shows how to create an intelligent agent that tracks your
location and follows you. This agent will also speak and gesture. In
this article we've restricted ourselves to a simple subset of that
functionality. Firstly we'll show how to create a randomly moving
object (representing the agent), then we'll show how to have that
object track your movement through the VRML scene and follow you.
|
|
|
Looking at the VRML 2.0 code below, at line 4 there is a TimeSensor
which invokes a Script node named "AGENT_SCRIPT" every 0.1
seconds. This is an example of a repeating timer. Because the start
and stop time have a default value of 0, and the loop field is set to
TRUE, the timer runs continuously from the point when the world is
loaded. Every 0.1 seconds it generates a cycleTime value, containing
the current time, which is routed to the Script Node. The associated
Java program uses this event to randomly move the Transform node which
includes the geometry representing the scene object to be moved. As a
result, the object appears to be floating back and forth in a random
fashion. Because the geometry for the object is relatively complex, it
is not included directly in the file, but indirectly by using the
Inline node (line 10) which points to another VRML file, in this case
ManDetailed.wrl. (you should insert your own file here) The browser
will automatically read in this description when needed.
|
|
|
1 #VRML V2.0 utf8
2 # an object is floating randomly.
3 #
4 DEF AGENT_TIME TimeSensor{
5 loop TRUE
6 cycleInterval 0.1
7 }
8 DEF AGENT Transform{ # floating agent
9 children[
10 Inline{url "ManDetailed.wrl"} # a model of agent.
11 ]
12 }
13 DEF AGENT_SCRIPT Script{
14 url "FloatingAgent.class"
15 eventIn SFTime interval
16 eventOut SFVec3f setAgentPosition
17 }
18 ROUTE AGENT_TIME.cycleTime TO AGENT_SCRIPT.interval
19 ROUTE AGENT_SCRIPT.setAgentPosition TO AGENT.set_translation
|
|
|
Line 18 routes the event from the Timesensor to the Script node's
eventIn field, interval. This is then passed across to the event
handler class "FloatingAgent.class".
|
|
|
Looking at the event handler class in the Java listing in detail
shows that the initialize() method is used to create a reference to
the Script node(IU(Bs setAgentPosition field. This field is used to
receive the agent's new position when it is calculated. In addition,
the agent's initial position is set up at line 18.
In the processEvent() method, you can see at line 32, that receipt
of the TimeSensor event causes the script to generate a random
number ranging from -0.1 to 0.1. The random number is
generated by using the randomNumberGenerator class which is
part of the java.util package.
|
|
|
1 // an agent is floating randomly.
2 //
3 import java.util.*;
4 import vrml.*;
5 import vrml.node.*;
6 import vrml.field.*;
7
8 public class FloatingAgent extends Script{
9 SFVec3f setAgentPosition;
10 float agentPosition[] = new float[3];
11 Random randomNumGenerator = new Random();
12
13 public void initialize(){
14 // get the reference of the event-out 'setAgentPosition'.
15 setAgentPosition =
16 (SFVec3f)getEventOut("setAgentPosition");
17 // initialize the agent position.
18 agentPosition[0] = 0.0f;
20 agentPosition[1] = 0.0f;
21 agentPosition[2] = 0.0f;
22 }
23 public void processEvent(Event e){
24 if(e.getName().equals("interval") == true){
25 moveAgent();
28 }
29 }
30 // generate random float value ranging between -0.1 to 0.1.
31 float generateRandomFloat(){
32 return(randomNumGenerator.nextFloat() * 0.2f - 0.1f);
33 }
34 // move the agent randomly.
35 void moveAgent(){
36 agentPosition[0] += generateRandomFloat();
37 agentPosition[1] += generateRandomFloat();
38 agentPosition[2] += generateRandomFloat();
39
40 // move the agent to the new position.
41 setAgentPosition.setValue(agentPosition);
42 }
43 }
|
|
|
Using this random number, the script moves the Transform that
represents the agent by simply setting its position co-ordinates to
a random position within a small distance of its current position.
This value is written back to the Script node at line 41 using the
setValue method on the setAgentPosition variable. Since this is a
handle holding a value retrieved from the VRML scene, setting a
new value also causes the browser to propagate the value across
to the VRML scene and into the setAgentPosition field of the
Script node.
|
|
|
In turn, because the setAgentPosition of the Script node is an
event out, the value is then routed through to the agent's
Transform node's translation field. This kind of random
movement, or a more complex behavior (for example, changing
the direction according to a state variable), is impossible to realize
with only a time sensor and an interpolator.
|
|
|
While this example is relatively simple, it shows you the basic
mechanism needed to move any object in your VRML scene in any
arbitrary manner. Just by substituting lines 36-38 with your own
algorithm you can animate objects in any way you wish.
|
|
|
Following the viewer
A very simple extension to the random agent example is to have
the agent follow the user as they navigate through the scene. To
achieve this, all that needs to be added to the VRML file is a means
to track the user's position and the mechanism to send this across
to the event handler class.
|
|
|
To track a user in the scene, a proximity sensor can be used. This
is initialized with a bounding box sufficiently large to encompass
the whole scene.
|
|
|
Then a route is set up that sends the user's position, as recorded
by the proximity sensor, to the script node.
|
|
|
Here is a code fragment:
|
|
|
3 # initial viewpoint
4 Viewpoint{
5 position 0 2 0
6 }
7 DEF PROX_SENSOR ProximitySensor{
8 size 100 100 100 #always in the sensor field.
9 }
...
24 DEF PROX_SCRIPT Script{
25 url "FollowingAgent.class"
26 eventIn SFVec3f currentPosition
27 eventOut SFVec3f setAgentPosition
29 }
30 ROUTE PROX_SENSOR.position_changed TO
31 PROX_SCRIPT.currentPosition
32 ROUTE PROX_SCRIPT.setAgentPosition TO AGENT.set_translation
|
|
|
This value is then used by the event handler class to calculate a
new position for the agent that allows the agent to appear to
follow or track the user as s/he moves through the scene. Here is
another code fragment which calculates the agents new position. The
calculation simply reads the user's current X, Y and Z positions and
adds a fixed offset held in agentDefaultPosition.
|
|
|
35 void moveAgent(){
36 // translate the agent according to your current position.
37 agentPosition[0] =
38 yourPosition[0] + agentDefaultPositionX;
39 agentPosition[1] =
40 yourPosition[1]+ agentDefaultPositionY;
41 agentPosition[2] =
42 yourPosition[2]+ agentDefaultPositionZ;
43 // move the agent to the new position.
44 setAgentPosition.setValue(agentPosition);
45 }
|
|
|
Dynamically adding to the scene
Being able to construct scenes and manipulate them is great fun, but
you are limited to the original scene. You may want to go further, to
dynamically add to existing scenes or even construct them on the
fly. The following example does just that. It automatically generates
a new piece of VRML code, in this case, a sphere node, which is stored
in a string. This string is then used with the browser method
createVRMLFromString() to dynamically add the Sphere node to the current
scene. For the sake of brevity we will not show the VRML scene. Assume
it contains a Script node with an eventIn field, touched, that is set
by a TouchSensor.
|
|
|
1 //
2 // dynamic generation of geometry nodes
3 //
4 import java.util.*;
5 import vrml.*;
6 import vrml.node.*;
7 import vrml.field.*;
8
9 public class DynamicGen extends Script{
10 MFNode addSphere;
11 Random randomNumGenerator = new Random();
12 float posX = 0.0f;
13
14 public void initialize(){
15 // get the reference of the event-out 'addSphere'.
16 addSphere = (MFNode)getEventOut("addSphere");
17 }
18
19 public void processEvent(Event e){
20 if(e.getName().equals("touched") == true){
21 String sphereDesc1 =
22 "Transform {" +
23 " translation ";
24 String sphereDesc2 =
25 " 0.0 0.0 " +
26 " children[" +
27 " Shape{geometry Sphere{}" +
28 " appearance Appearance{" +
29 " material Material{diffuseColor ";
30 String sphereDesc3 =
31 " }" +
32 " }" +
33 " }" +
34 " ]" +
35 "}";
36
37 float red = randomNumGenerator.nextFloat();
38 float green = randomNumGenerator.nextFloat();
39 float blue = randomNumGenerator.nextFloat();
40
41 Browser browser = getBrowser();
42 BaseNode baseNodes[];
43
44 // next position.
45 posX += 3.0f;
46
47 try{
48 baseNodes = browser.createVrmlFromString(
49 sphereDesc1 + posX + sphereDesc2 + red + " " +
50 green + " " +
51 blue + sphereDesc3);
52 if(null != baseNodes) {
53 addSphere.setValue(baseNodes);
54 }
55 } catch (Exception ex) {
56 ex.printStackTrace() ;
57 }
58 }
59 }
60 }
|
|
|
The key to this example is the dynamic creation of the VRML code
at lines 21 through 35. These simply create a string describing the
Transform and geometry that we wish to add. Lines 37 through
39 then generate a random RGB value for the sphere. Lastly lines
48 through 54 concatenate all of the these strings together and
add it to the scene using the createVrmlFromString() call at line
48.
|
|
|
You should note how lines 48 through 54 are enclosed in a try/catch
call. This ensures that if the call to the browser fails in any way,
you will catch the exception and exit gracefully. You should always
enclose any java API calls with try/catch. You will save yourself a
lot of hard work tracking down bugs if you follow this simple advice.
|
|
|
Obviously this code could be extended to read VRML from a file,
or to create arbitrary VRML based on data read from a database,
or event read from over the network. In fact in the book we have
an interesting example that uses this technique to create a 3D
representation of the local disk that can be interactively browsed.
Don't do it yourself
If your reaction to all of this is "Wow, looks really interesting, but
complicated" then help is at hand. We have just released a free
version of our authoring tool, Community Place Conductor, which
takes a lot of the grunt work out of authoring scenes and adding
Java. It even automatically generates Java templates for you
based on your VRML scene.
|
|
|
Conclusion
Java allow VRML scenes to become open ended in their interactive
capabilities. The limit is your
imagination. In this article we have only touched on the
possibilities and shown you a few small examples that show how
Java can manipulate the VRML scene either directly, or by
dynamically adding to the scene. However, we hope we have
shown you enough concrete code examples to allow you to feel
comfortable with the idea of going off and writing your own Java.
The authoring tool mentioned above will help you do that, but our
message to you all is - experiment!
|
|
Rodger Lea is a senior research scientist at Sony's Computer Science
lab in Tokyo . He holds a PhD in computer science, usually works on
distributed operating systems but has, over the last year, moved up
in the world and developed the distributed architecture for Sony's
multiuser VRML system. He can be reached at
rodger@csl.sony.co.jp
|