This blog entry was posted by peter on August 19, 2007.
It is tagged with instantreality, interface, JavaScript, Scripting, Tutorial, and X3D.
So far there are 0 comments. Feel free to add one.
The previous entry chronologically is A Django template tag for integrating a Flash based mp3 audioplayer.
This article describes how to implement an X3D slider using the instantReality framework.
The final slider can be used horizontally or vertically, outputs float values and can be parameterized in value range and color. This slider example does not contain a label or a textual representation of the current value.
The article touches the following concepts:
The slider is made up of two boxes. One box represents the groove (or rail) of the slider and a second box is used as the handle of the slider. The boxes are wrapped by Transform nodes in order to be able to move them around.
When the handle is pressed it can be dragged horizontally until it is released again. A PlaneSensor is used to implement the dragging functionality and is placed under the handle’s Transform, so only the handle itself will be sensitive to dragging. To restrict it to the x-axis the minPosition and maxPosition fields of the PlaneSensor are parameterized accordingly. To move the handle, sensor translation output is routed to the handle’s translation field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <Scene DEF='scene'> <Transform DEF='grooveTrans' translation='0.5 0 0'> <Shape> <Box size='1 0.6 0.2' /> <Appearance> <Material diffuseColor='.1 .1 .1' /> </Appearance> </Shape> </Transform> <Transform DEF='handleTrans'> <PlaneSensor DEF='planeSensor' minPosition='0 0' maxPosition='1 0' /> <Shape> <Box size='0.2 1 0.4' /> <Appearance> <Material diffuseColor='0.1 0.1 0.1' /> </Appearance> </Shape> </Transform> <ROUTE fromNode='planeSensor' fromField='translation_changed' toNode='handleTrans' toField='translation'/> </Scene> |
This scene represents a functionable slider, delivering values between 0 and 1. Because the box representing the slider’s groove is exactly one unit wide it matches nicely with the position from the PlaneSensor. But the slider looks a little bit squashed and it should be made wider.
Things get worse when we change the maxPosition to a value different to the box’s size. When we set maxPosition to “5, 0” in order to deliver values in the interval 0 and 5, the handle can be dragged outside the groove’s geometry. In order to make the right border of the groove geometry represent the value 5 obviously some logic is needed to align the geometry and the sensor position is needed. A Script node will do the job.
The Script we are going to add uses inlined Ecmascript. Alternatively the script could reside in it’s own file and be referenced from the Script node’s url field. It’s often easier to have the script code inlined when implementing a prototype - you can put it in an external file when the implementation has stabilized.
The Script can be used to parameterize the slider, therefore it has several input fields and some output fields to inform about value changes. These fields will be discussed below, first have a look at the script code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <Script DEF='script'> <field name='planeSensor' accessType='inputOutput' type='SFNode'><PlaneSensor USE='planeSensor' /></field> <field name='grooveTrans' accessType='inputOutput' type='SFNode'><Transform USE='grooveTrans' /></field> <field name='handleTrans' accessType='inputOutput' type='SFNode'><Transform USE='handleTrans' /></field> <field accessType='initializeOnly' name='initialValue' type='SFFloat' value='0' /> <field accessType='initializeOnly' name='minVal' type='SFFloat' value='0'/> <field accessType='initializeOnly' name='maxVal' type='SFFloat' value='1'/> <field accessType='initializeOnly' name='grooveLength' type='SFFloat' value='1'/> <field accessType='inputOnly' name='set_value' type='SFFloat' /> <field accessType='outputOnly' name='value_changed' type='SFFloat' /> <field accessType='inputOnly' name='on_handle_translation' type='SFVec3f' /> <![CDATA[javascript: function initialize(value,time) { // planeSensor's minPosition is (0,0) by default grooveTrans.scale.x = grooveLength; grooveTrans.translation.x = grooveLength/2.0; planeSensor.maxPosition.x = grooveLength; planeSensor.maxPosition.y = 0; planeSensor.offset.x = mapInterval(initialValue, minVal, maxVal, 0, grooveLength);; set_value(initialValue); } function on_handle_translation(v) { set_value(mapInterval(v[0], 0, grooveLength, minVal, maxVal)); } function set_value(v) { value_changed = v; handleTrans.translation.x = mapInterval(v, minVal, maxVal, 0, grooveLength); } function mapInterval(val, srcMin, srcMax, dstMin, dstMax) { return (val-srcMin) / ((srcMax-srcMin)/(dstMax-dstMin)) + dstMin; } ]]> </Script> |
The Script interface consists of ten fields. The first three fields are of type SFNode and are used to pass the PlaneSensor and the two geometry subgraphs into the script. The USE attribute is used to reference the existing nodes:
1 2 3 | <field name='planeSensor' accessType='inputOutput' type='SFNode'><PlaneSensor USE='planeSensor' /></field> <field name='grooveTrans' accessType='inputOutput' type='SFNode'><Transform USE='grooveTrans' /></field> <field name='handleTrans' accessType='inputOutput' type='SFNode'><Transform USE='handleTrans' /></field> |
The minVal and maxVal fields define the slider’s minimum and maximum value, initialValue can be used to set the initial value of the slider. The grooveLength is simply the length of the groove. The final slider should be able to handle any combination of minVal/maxVal and grooveLength:
1 2 3 4 | <field name='initialValue' accessType='initializeOnly' type='SFFloat' value='0' /> <field name='minVal' accessType='initializeOnly' type='SFFloat' value='0'/> <field name='maxVal' accessType='initializeOnly' type='SFFloat' value='1'/> <field name='grooveLength' accessType='initializeOnly' type='SFFloat' value='1'/> |
In order to be able to set a new slider value and communicate value changes to the outside, the slider Script offers two fields called set_value and value_changed. Finally there’s an input field on_handle_translation which is meant for internal use and will be called whenever the PlaneSensor‘s translation has changed:
1 2 3 | <field accessType='inputOnly' name='set_value' type='SFFloat' /> <field accessType='outputOnly' name='value_changed' type='SFFloat' /> <field accessType='inputOnly' name='on_handle_translation' type='SFVec3f' /> |
The Script‘s initialize function is called once when the script node is going live.
First the groove geometry is scaled to the desired length:
1 | grooveTrans.scale.x = grooveLength; |
The Box used as the groove is originally centered in the origin but the slider should start at (0,0,0) and only extend to the right (the positive x-axis). So the Box needs to be translated to the right:
1 | grooveTrans.translation.x = grooveLength/2.0; |
Next the PlaneSensor is parameterized. Please note that we always use minPosition (0,0) and maxPosition = (grooveLength, 0) instead of using the values from the minVal/maxVal pair for initialization. The minVal and maxVal will be used in the set_value function:
1 2 | planeSensor.maxPosition.x = grooveLength; planeSensor.maxPosition.y = 0; |
The offset of the sensor is parameterized depending on the initialValue, minVal, maxVal and grooveLength fields. The initialValue is mapped from the min/max interval to the length of the groove. That way it is possible to use any combination of value ranges and groove length:
1 | planeSensor.offset.x = mapInterval(initialValue, minVal, maxVal, 0, grooveLength);; |
Finally the initial value is set via the set_value function:
1 | set_value(initialValue); |
The set_value function passes the given value to the output and maps it to the groove length in order to adjust the handle translation:
1 2 3 4 | function set_value(v) {
value_changed = v;
handleTrans.translation.x = mapInterval(v, minVal, maxVal, 0, grooveLength);
}
|
The translation value passed in from the PlaneSensor will always be in the range (0, 0) and (grooveLength, 0). It needs to be mapped to the interval (minVal,0) and “(maxVal,0) and this is done by the mapInterval utitlity function. The mapped value is then passed to the set_value function:
1 2 3 | function on_handle_translation(v) {
set_value(mapInterval(v[0], 0, grooveLength, minVal, maxVal));
}
|
In order to handle the PlaneSensor‘s translation changes they are routed to the on_handle_translation input field of the Script:
1 | <ROUTE fromNode='planeSensor' fromField='translation_changed' toNode='script' toField='on_handle_translation'/> |
The final scene containing the fully functional slider looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE X3D PUBLIC “ISO//Web3D//DTD X3D 3.0//EN” “http://www.web3d.org/specifications/x3d-3.0.dtd”> <X3D xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' profile='Full' version='3.0' xsd:noNamespaceSchemaLocation='http://www.web3d.org/specifications/x3d-3.0.xsd'> <Scene DEF='scene'> <Transform DEF='grooveTrans' translation='0.5 0 0'> <Shape> <Box size='1 0.6 0.2' /> <Appearance> <Material diffuseColor='.5 .5 .5' /> </Appearance> </Shape> </Transform> <Transform DEF='handleTrans'> <PlaneSensor DEF='planeSensor' minPosition='0 0' maxPosition='1 0' /> <Shape> <Box size='0.2 1 0.4' /> <Appearance> <Material diffuseColor='0.5 0.5 0.5' /> </Appearance> </Shape> </Transform> <Script DEF='script'> <field name='planeSensor' accessType='inputOutput' type='SFNode'><PlaneSensor USE='planeSensor' /></field> <field name='grooveTrans' accessType='inputOutput' type='SFNode'><Transform USE='grooveTrans' /></field> <field name='handleTrans' accessType='inputOutput' type='SFNode'><Transform USE='handleTrans' /></field> <field accessType='initializeOnly' name='initialValue' type='SFFloat' value='0' /> <field accessType='initializeOnly' name='minVal' type='SFFloat' value='0'/> <field accessType='initializeOnly' name='maxVal' type='SFFloat' value='1'/> <field accessType='initializeOnly' name='grooveLength' type='SFFloat' value='5'/> <field accessType='inputOnly' name='set_value' type='SFFloat' /> <field accessType='outputOnly' name='value_changed' type='SFFloat' /> <field accessType='inputOnly' name='on_handle_translation' type='SFVec3f' /> <![CDATA[javascript: function initialize(value,time) { // planeSensor's minPosition is (0,0) by default grooveTrans.scale.x = grooveLength; grooveTrans.translation.x = grooveLength/2.0; planeSensor.maxPosition.x = grooveLength; planeSensor.maxPosition.y = 0; planeSensor.offset.x = mapInterval(initialValue, minVal, maxVal, 0, grooveLength);; set_value(initialValue); } function on_handle_translation(v) { set_value(mapInterval(v[0], 0, grooveLength, minVal, maxVal)); } function set_value(v) { value_changed = v; handleTrans.translation.x = mapInterval(v, minVal, maxVal, 0, grooveLength); } function mapInterval(val, srcMin, srcMax, dstMin, dstMax) { return (val-srcMin) / ((srcMax-srcMin)/(dstMax-dstMin)) + dstMin; } ]]> </Script> <ROUTE fromNode='planeSensor' fromField='translation_changed' toNode='handleTrans' toField='translation'/> </Scene> </X3D> |
By adjusting the fields of the Script node (e.g. minVal, maxVal) different slider configurations can be realized. In order to make the slider code more reuseable, we will make an X3D prototype of it in the next article.
There are no comments yet.