Direct scene manipulations with Javascript
Keywords:
script,
javascript,
dynamic
Author(s): Michael Zoellner
Date: 2009-01-01
Summary: This tutorial shows you how to create, manipulate, add and remove nodes via Javascript
Introduction
The previous tutorial (Implementing Script nodes in Javascript) shows the basic mechanisms to integrate your Script-node with inputOnly/OutputOnly-fields and ROUTEs. This fits well in the overall X3D-design where nodes usually do not manipulate the scene-graph structure. However, for some applications it is necessary to change the scene-graph directly. But be warned. If you access and manipulate the graph with your code all automatic event-processing mechanisms are gone. What is even more important is the following fact: The X3D Browser has to switch all runtime optimizations off for the current executionContext (e.g scene, inline or ProtoInstance) since the system can not know which part of the scene will be manipulated by your script. Therefore you have to mark Script-nodes which are allowed to manipulate the scene by setting the directOutput-field to true.
Warning: You have to set Script.directOutput to true if you directly manipulate the scene inside of your script-code
This simple example will create a small subtree via Javascript. The resulting tree will include a Box and a single Transform as child of a given Group.
Code: The resulting nodes
<Transform> <Shape> <Appearance> <Material /> </Appearance> <Box /> </Shape> </Transform>
Basic Scene setup
We only need two nodes in the scene to get started. A Group node where we will put the dynamic objects in and a Script node that generates them.
Code: Basic Scene setup
<Group DEF='dynamic_group' /> <Script DEF='script' directOutput='true' />
Accessing exiting nodes
To start with our scene-changes we have to access at least a single node. There are two different ways to access exiting nodes within your script code. You can search for your node within the current executionContext using the Browser.currentScene.getNamedNode() method. This may take some milliseconds depending on how many named nodes you have in your executionContext.
Code: Searching for a existing node by name
var dynamic_group = Browser.currentScene.getNamedNode('dynamic_group');
The second method is to reference the node in a initializeOnly field.
Code: Referencing a existing node inside of the Script interface
<Group DEF='dynamic_group' /> <Script DEF='script' directOutput='true' > <field accessType='initializeOnly' name='dynamic_group' type='SFNode'> <Group USE='dynamic_group'/> </field> </Script>
Use whatever method is more adequate for your application.
Creating nodes
Next we will create these nodes from inside out and add them as children to their parent node. There are again two methods. First you can use the ISO-standard X3D field-accessor to create and assign those fields. The other method uses some InstantReality-specific addChild() calls.
Code: Creating and adding nodes using standard methods
var box = Browser.currentScene.createNode("Box"); var material = Browser.currentScene.createNode("Material"); material.diffuseColor = new SFColor(1, 0.5, 0); var appearance = Browser.currentScene.createNode("Appearance"); appearance.material = material; var shape = Browser.currentScene.createNode("Shape"); shape.geometry = box; shape.appearance = appearance; var transform = Browser.currentScene.createNode("Transform"); transform.children = new MFNode(shape); // finally add the transform node to the scene dynamic_group.children[0] = transform;
The second method utilizes the intelligent type-system by not adding the new nodes to a specific field but to the parent-node. The system therefore tries to find the correct field and adds the child. In addition it calls the SFNode constructor directly. But remember: This works only in the InstantReality runtime.
Code: Creating and adding nodes using InstantReality extensions
var box = new SFNode("Box"); var material = new SFNode("Material"); material.diffuseColor = new SFColor(1, 0.5, 0); var appearance = new SFNode("Appearance"); appearance.addChild(material); var shape = new SFNode("Shape"); shape.addChild (box, appearance); // create a transform node and put the shape into it var transform = new SFNode("Transform"); transform.addChild (shape); // finally add the transform node to the scene dynamic_group.addChild(transform);
The last line in both versions adds the new created transform-node to the given dynamic_group. Otherwise these nodes are not added to the scene and thus not visible.
Manipulating nodes
Once the dynamic nodes are in the Scene we can read and manipulate the field values directly.
Code: Accessing node values
// printing the number of children Browser.println( transform.children.length ); // printing the first child's translation Browser.println( transform.translation ); // changing the transformation transform.translation = new SFVec3f(1,0,0);
Removing nodes
One thing is important to know: You only can remove the node from a specific field or parent but you can not delete a node. It will be automatically deleted with the last reference. For the actual removal there are again different methods. For all nodes which have a children-field the safest method is to use the removeChildren field. This is independent of the actual position inside of the children container.
Code: Removing nodes via removeChildren-field
// remove transform dynamic_group.removeChildren = new MFNode( transform );
Another save but again non-standard method is the removeChild() function. This works for all nodes and SFNode/MFNode-links but is a InstantReality specific extension.
Code: Removing nodes via removeChild() function
// remove transform dynamic_group.removeChild ( transform );
SFNode fields you can set directly to null to remove the link
Code: Removing nodes in SFNode-fiels via null
// remove appearance shape.appearance = null;
Adding and deleting Routes
In addition to the nodes you can also dynamically add and delete routes
Code: Adding and deleting routes to given nodes
Browser.addRoute( my_timeSensor, 'fraction_changed', my_interpolator, 'set_fraction'); Browser.deleteRoute( my_TimeSensor, 'fraction_changed', my_interpolator, 'set_fraction');
You can even add and delete routes directly to the Script using the 'this' object
Code: Adding and deleting routes to the script node
Browser.addRoute( my_timeSensor, 'time', this, 'printTime');
This tutorial should give you a first idea how to manipulate your scene dynamically. For more inspiration look at the X3D specification and the online Javascript documentation.
Example Files
The first three example files are generating one Box and several Boxes at random positions. The next two are showing how to create dynamic nodes from Protos and advanced manipulation methods. The last one creates and connects a route directly to the script node.
Files:- dynamic_box_01.x3d - A simple dynamic Box
- dynamic_box_01_addChild.x3d - A simple dynamic Box using addChild()
- dynamic_box_02.x3d - Several dynamic Boxes at random positions
- dynamic_box_03.x3d - Dynamic eased Boxes via dynamic Proto
- dynamic_box_04.x3d - Orbital arrangement of dynamic created Boxes
- dynamic_route_01.x3d - Dynamic creation of a route to the script node