LSL Scripting — Whichever Way the Wind Blows

In the prior and fourth article in this series of articles on scripting in Second Life™, we looked at a script to change the color of an object based on the position of the SL sun.The notable properties of the script were use of a timer event and calling a built-in function to get the sun direction. Today’s example starts out quite similar in structure and then extends things a bit.

Within my small parcel of land in Caledon Stormhold, I have a wind vane, since SL simulates horizontal winds. Although they aren’t the best winds for sailing, one can even use the default SL winds for that purpose. So having a wind vane is more than just a silly exercise in decoration. Moreover, a wind vane is supposed to point in the direction of the wind, and the direction of the wind varies. With that thought, we come to needing a script and thus have an example to work through.

There’s not much to keep track of at the global level for this script, just the time interval on which to check the wind direction and align the x-direction of the wind vane to the wind. We can write

float INTERVAL = 3.0;   // Interval at which to update the wind vane direction (seconds)

and be done with our global variables (for the moment).

We will want a user-defined function, one that I’ll call uuAlign2Wind, but I’m going to skip over that at present and look at our default state and the needed event-handlers. This is going to look very familiar if you read the last article.

default {
   state_entry () {
      uuAlign2Wind ();
      llSetTimerEvent (INTERVAL);
   }

   timer () {
      uuAlign2Wind ();
   }
}

What we have is a common structure for scripts that are purely timer-driven: initialize and set a timer in state_entry (i.e. when the script enters the default state), then do an update on each timer event. The details that make one application different from another are all hidden in the user defined function, which makes the underlying structure of the script easy to see.

Now let’s make this a bit more interesting and assume we are writing the script for someone else. At this point, you get an IM from the person saying “I forgot to tell you I want to be able to turn this on and off and only I should be able to do this”. You make a reply like “Oh, okay” (eye-rolls are .optional). Let’s see how we can handle this.

I’m going to add a global variable to keep track of whether our wind vane is on or off and add an event-handler for the start of a touch. Let’s see what that gives us so far.

float   INTERVAL = 3.0;   // Interval at which to update the wind vane direction (seconds)
integer isActive = 0;     // Flag to keep track of on/off status; 0 means inactive

uuAlign2Wind () {
   ;   // Just a stub for now 
}

default {

   state_entry () {
      llOwnerSay ("Wind Vane Inactive");
   }

   on_rez (integer rez_param) {
      llResetScript ();
   }

   touch_start (integer num_touches) {

      if ( llDetectedKey(0) == llGetOwner() ) {
         // We were touched by our owner, so we respond

         // Flip our activity status: 1 goes to 0; 0 goes to 1
         isActive = 1 - isActive;

         if ( isActive ) {
            uuAlign2Wind ();
            llSetTimerEvent (INTERVAL);
         } else {
            llSetTimerEvent (0.0);
         }      
      }
   }

   timer () {
      uuAlign2Wind ()
   }
} 

We made some changes up above. Let’s run through them. We don’t have much to do in state_entry now. What was there before in now part of the touch_start event, With two possible modes, however, it’s good to let the owner know whether we are active or inactive when we start up. Sending a message to the object/script owner is the purpose of llOwnerSay.

Since we want a predictable status when we rez the wind vane, we use the on_rez event to reset the script. This occurs even before state_entry. We can rez the wind vane, it resets the script, lets the owner know it’s not active, and then it just sits there. Sitting there may be attractive, but it’s not why wind vanes were built (something similar has been said about ships). As a background note, the default thing for a script to do, when the object it’s in is taken into inventory and then rerezzed, is to simply continue from where it was.

The touch_start event is triggered on the start of a touch. It has the ability to handle multiple near simultaneous touches as one event and receives an integer parameter saying how many such touches were bundled. This is relatively rare and we can ignore that possibility for our application. We only look at the touch with an index of zero.

Touch, collision, and sensor events each have a set of llDetected… routines to gain more information about what was detected. The values returned by these routines are only valid while in the single event. If you want to save any values, you have to store them in global variables.

The built-in function llDetectedKey, called with an argument of zero, returns the key (or UUID) of the avatar that touched it. The built-in function llGetOwner returns the key of the owner. If those are equal (tested with the operator ==), then we’ve been touched by the owner. If not, we simply ignore the touch. You might want to review the if-else construct if you .are not familiar with such flow-control.

The new global variable isActive is an integer that was initialized to have the value zero. In LSL, a zero tests as false and a one tests as true. On a valid touch, we flip the value of isActive by setting it to one minus itself (1-0 equals 1; 1-1 equals zero). If the subsequent value of isActive is one, we do what we did before in state_entry, initializing the wind vane direction and setting a timer. If the value is zero, we turn off the timer by giving it an interval of zero. What remains unchanged is the timer event to update the wind vane direction. That completes everything we need except the function uuAlign2Wind, which is next.

There’s a built-in function llWind to get the vector wind (3 floats with the z component zero) and another function llVecNorm to normalize a vector to have a length of one (unit length). llWind takes a vector offset from the object as a parameter, but we give it a zero vector (there’s a built-in constant) for that, since we want the wind at the wind vane. Using the X and Y direction cosines resulting from the above, we can call llAtan2 to get the rotation of the wind direction about the Z–axis. Finally, there’s  a simple formula for directly writing the corresponding quaternion rotation for a rotation around the Z–axis. All that remains is to use that to set the wind vane rotation. Let’s put it all together.

float   INTERVAL = 3.0;   // Interval at which to update the wind vane direction (seconds)
integer isActive = 0;     // Flag to keep track of on/off status; 0 means inactive

uuAlign2Wind () {
   vector   wind   = llVecNorm(llWind(ZERO_VECTOR));
   float    zangle = llAtan2(wind.y, wind.x);
   rotation wndrot = <0.0, 0.0, llSin(0.5*zangle), llCos(0.5*zangle)>;
   llSetRot(wndrot);
}

default {

   state_entry () {
      llOwnerSay ("Wind Vane Inactive");
   }

   on_rez (integer rez_param) {
      llResetScript ();
   }

   touch_start (integer num_touches) {

      if ( llDetectedKey(0) == llGetOwner() ) {
         // We were touched by our owner, so we respond

         // Flip our activity status: 1 goes to 0; 0 goes to 1
         isActive = 1 - isActive;

         if ( isActive ) {
            uuAlign2Wind ();
            llOwnerSay ("Wind Vane Active");
            llSetTimerEvent (INTERVAL);
         } else {
            llSetTimerEvent (0.0);
            llOwnerSay ("Wind Vane Inactive");
         }
      }
   }

   timer () {
       uuAlign2Wind ();
   }
}

With that, we have a script for rotating a Wind Vane or a flag to align with the SL wind and that allows the owner to turn it on and off with a touch. If you’ve been following along with the series, you’ve learned a fair amount about scripting. Try your own ideas and see what you run into. Thank you for your attention. The sixth article will be on links and faces.

Caledon Oxbridge also has an inworld Basic Scripting class, Mondays at noon SLT in our main lecture hall. Please see our schedule page.

Leave a Reply

Your email address will not be published. Required fields are marked *