Lightradio

tested with ++


This tutorial was inspired by an online discussion about radiobuttons. A few people shared their implementations with the VAG/VRML community so now it is my time to share.
The radiobutton is a nice candidate for a PROTO. A PROTO allows you to write your own nodes. VRML2.0 gives you the possibility to extend the language from within a VRML file. Like any other node, a PROTO has eventIns, eventOuts, exposedFields, and regular fields, only you define them yourself!
This radiobutton has an eventIn ButtonReleaseP to deactivate a button, an eventOut Button_is_ActiveP and an eventOut Button_isnot_ActiveP to interact with other nodes. Let's start with defining the header of the PROTO:

PROTO RADIOBUTTON

[

eventIn SFBool Button_releaseP

eventOut SFBool Button_isActiveP

eventOut SFBool Button_isnot_ActiveP

]

PROTOs are declared at the beginning of a file but can also be inlined as EXTERNALPROTOs (if your favorite browser supports them!). The next step provides the geometry of a radiobutton. To keep it simple I use just a cylinder:

DEF radioButton Transform {

  center 0 -0.1 0

  children [

    Shape {

      appearance DEF buttonAppearance Appearance {

        material Material { 

          diffuseColor 1 1 0

        }

      }

      geometry Cylinder { 

        height .2 

        radius .1 

      }

    }

    DEF TCHS TouchSensor { }

  ]

}

The Transform and the Appearance nodes are DEFed because I need to ROUTE to them later. The cylinder is centered at its bottom because it will be downscaled each time it gets clicked. A TouchSensor is neccessary to provide usefullness to our button. A PROTO will always take the first node and derive its type from it. Right now the PROTO will think that it is a Transform node. If I move the TouchSensor node to the first line, the PROTO will behave like a TouchSensor node. When in doubt about the nature of your PROTO you should always embed your nodes within a Group node.
Now it's time for Scripting: when the button is clicked it has to a) shrink, b) change its color, and c) notify its enviroment about its activation. A function will help us:

function Button_was_pressed(pressed) {

  if (pressed) {

    shrinkButton[0] = 1;

    shrinkButton[1] = 0.5;

    shrinkButton[2] = 1;

    activeMaterial = onMaterial;

    Button_isActiveS = TRUE;

  }

}
The vector shrinkButton has the value 0.5 in its y-direction field. When the vector is ROUTEd to the button's center field it will scale it down to half of its original value. The field activeMaterial will give the active button a visual emphasis. The field Button_isActiveS is the only field which will actually leave the PROTO node and trigger other events.
A second function is needed once the button is released:

function Button_releaseS(released) {

  if (released) {

    shrinkButton[0] = 1;

    shrinkButton[1] = 1;

    shrinkButton[2] = 1;

    passiveMaterial = offMaterial;

    Button_isnot_ActiveS = FALSE;

  }

}
The scale vector shrinkButton is reset, the field emissiveColor is turned off and the original color of the button is restored. Both functions reside in different Scripts. For reasons unknown to the author they would not work in one Script node ;( .
The PROTO is completed with the appropriate ROUTing. The TouchSensor triggers the function Button_was_pressed:

ROUTE TCHS.isActive TO RADIO.Button_was_pressed
The active node properties are ROUTEd:

ROUTE RADIOactive.shrinkButton TO radioButton.set_scale

ROUTE RADIOactive.activeMaterial TO buttonAppearance.set_material

The passive node properties are ROUTEd:

ROUTE RELEASE.shortButton TO radioButton.set_scale

ROUTE RELEASE.passiveMaterial TO buttonAppearance.set_material

The final PROTO lightradio source sums it all up and also includes the eventIns, eventOuts, and fields of the Scripts. You will discover an unknown keyword in both Script nodes: IS. If you want Script events to be available outside of the PROTO you need to map between the PROTO events and the Script events. Look at the Script RELEASE:

DEF RELEASE Script {

eventIn SFBool Button_releaseS IS Button_releaseP

...

}

I am using the field Button_releaseS inside the Script and associate it with the field Button_releaseP which makes the Script field available outside of the PROTO.
With the PROTO declared we can use it in the following way:

Transform {

  children [

    DEF middle RADIOBUTTON { }

  ]

}

middle is now a RADIOBUTTON. The empty parenthesis reveal that there is more to a PROTO: you can pass fields to a PROTO during initialization and thus customize each implementation.
To demonstrate today's lesson I let the radiobuttons turn on/off SpotLights to illuminate primitives.

Transform {

  translation 0 1 -1

  children [

    DEF middleL SpotLight { 

      location 0 0 1 

      direction 0 0 -1 

      color 0 1 0 

      on FALSE 

    }

    Shape { 

      appearance Appearance { 

        material Material { 

          diffuseColor 0 0.2 0 

        } 

      }

      geometry Sphere { radius 0.2 } 

    }

  ]

}

The final ROUTing comes in two steps: Once a button is clicked the other buttons have to be turned off:

ROUTE first.Button_isActiveP TO second.Button_releaseP

ROUTE first.Button_isActiveP TO third.Button_releaseP

The function Button_releaseS does its clean-up (resets color and size) and sends out the event Button_isnot_activeP=FALSE which is used to end the action of the previous active button.
The next step turns on the active light and turns off the previous active light:

ROUTE left.Button_isActiveP TO leftL.on

ROUTE middle.Button_isnot_ActiveP TO middleL.on

ROUTE right.Button_isnot_ActiveP TO rightL.on


For each radiobutton you have to provide ROUTEs to ensure that only one is active and to turn on/off their assigned functionalities. The final lightradio source shows you how all these pieces work together.( lightradio.wrl)





Copyright © 1996-98 Markus Roskothen. All rights reserved.