This is the sixth in a Caledon Oxbridge series of articles on scripting in Second Life™. The previous article in the series was on scripting a wind vane. This article focuses on a script for determining the links and faces of an object.
For classic prims (primitive objects), a scripter can simply look up the face number for each prim. They are all preset by the prim type. Mesh faces aren’t preset, however, and depend on choices the creator makes.
In making a mesh piece in Blender for upload to SL, the creator can assign different parts of the piece to up to eight different materials. After uploading, each material can be textured independently The names of the materials in Blender are creator settable and the alphabetic ordering of the material names determines the numeric ordering of the faces after uploading. What, however, do we do if we have a mesh piece or linkset and don’t know the ordering of the faces. That’s where this article’s script example comes into play.
Let’s suppose that when the script starts we want to tell the owner of the object the number of links in the object, not including avatars, and then, for each link, tell the number of faces. On a touch, we want to tell the owner which link and what face within the link was touched.There’s nothing required to be kept between events, so we won’t need global variables and we may not need any user-defined functions. We likely do want to catch when the object is rezzed and any changes to its link structure and reset the script. Just this amount of definition lets us lay out a template for the script.
default { state_entry () { } on_rez (integer rez_param) { llResetScript(); } changed (integer change) { if ( change & CHANGED_LINK ) llResetScript(); } touch_start (integer num_touches) { } }
That structure doesn’t look too bad, so let’s move forward. The touch_start event is likely the simplest since it only involves one link and one face per touch, so let’s start with that.
The touch events are among those have information available from a series of llDetected… functions. That makes things especially simple, get the detected link, get the detected face, and tell the owner this information. That looks like:
touch_start (integer num_touches) { // Get the touched link number and face number and tell the owner integer link = llDetectedLinkNumber(0); integer face = llDetectedTouchFace(0); llOwnerSay ("Link " + (string) link + ": You touched my face " + (string) face"); }
On the llOwnerSay statement, remember that statements are ended by a semicolon, not by the end of a line. We can split the statement between two lines. What we have left to do is the state_entry event. I’m going to simply lay that out and then go over it, but the code itself has documentation in the comments.
state_entry () { integer allLinks = llGetNumberOfPrims(); // The number of all links integer start = allLinks > 1; // The first link's number integer lnk; // Something to use as an index in a loop integer nlinks; // The number of non-avatar links integer nfaces; // Number of faces for the current link key id; // A key for testing each link list primLinks = []; // A list just for prims (non avatars) // Create a list of links that are just prims by going through the // list of all links, including avatars. The agent size will be a // zero vector if the link is not an avatar. In that case, add the // link's key to the list of prim links. for (lnk = start; lnk < allLinks; ++lnk) { id = llGetLinkKey(lnk); if ( llGetAgentSize(id) == ZERO_VECTOR ) { primLinks += [id]; } } nlinks = llGetListLength(primLinks); llOwnerSay ("I have " + (string) nlinks + " links"); // Go through the links and output the number of faces each has for (lnk = 0; lnk < nlinks; ++lnk) { nfaces = llGetLinkNumberOfSides(lnk); llOwnerSay("Link " + (string) lnk + " (" + llGetLinkName(lnk) + ") with " + (string) nfaces + " faces"); } }
I start out by defining all the variables local to state_entry at the top of the event-handler. That way, they are easy to see. In local variable definitions, unlike global ones, we can use function calls and operators within the definition itself. So allLinks
receives the total number of links, including those for any sitting avatars. The variable start
takes the value zero or one, depending on whether this is a single prim (0) or a linkset (1). This is because the link number of a single prim is zero and the link number of the root prim in a linkset is one. So, as the name implies, this gives us the starting value for a loop over all the links in the object. The variable primLinks
is initialized as an empty list.
The first for loop runs through all of the links. For each link, it gets the key (UUID) of the link and then requests the agent size for that key. If the link is an avatar, then a valid size will be returned. If the link isn’t an avatar, a zero vector will be returned. The documentation notes that this is a useful way of determining if a link is a sitting avatar or not. We build a list of all the non-avatar links in primList
. Note my use of the +=
operator to add each UUID to the existing list. primList += [id];
is simply shorthand for primList = primList + [id];
.
The second for loop is relatively simple, we run through the list of non-avatar links and request the number of faces for each, outputing the information to the owner. Putting all of this together and adding a few comments to the top so that I know what it is for in six months:
// ***************************************************************************** // touchMyFace // Placed in a linkset, this script provides information on the links // and how many faces each link has. On a touch, it tells the link number // and face number that was touched. This is particularly useful in // determining the face numbers of mesh pieces with multiple faces. // // Wordsmith Jarvinen -- 09 Sep 2018 -- v1.0.0 // ***************************************************************************** default { state_entry () { integer allLinks = llGetNumberOfPrims(); // The number of all links integer start = allLinks > 1; // The first link's number integer lnk; // Something to use as an index in a loop integer nlinks; // The number of non-avatar links integer nfaces; // Number of faces for the current link key id; // A key for testing each link list primLinks = []; // A list just for prims (non avatars) // Create a list of links that are just prims by going through the // list of all links, including avatars. The agent size will be a // zero vector if the link is not an avatar. In that case, add the // link's key to the list of prim links. for (lnk = start; lnk < allLinks; ++lnk) { id = llGetLinkKey(lnk); if ( llGetAgentSize(id) == ZERO_VECTOR ) { primLinks += [id]; } } nlinks = llGetListLength(primLinks); llOwnerSay ("I have " + (string) nlinks + " links"); // Go through the links and output the number of faces each has for (lnk = 0; lnk < nlinks; ++lnk) { nfaces = llGetLinkNumberOfSides(lnk); llOwnerSay("Link " + (string) lnk + " (" + llGetLinkName(lnk) + ") with " + (string) nfaces + " faces"); } } on_rez (integer rez_param) { llResetScript(); } changed (integer change) { if ( change & CHANGED_LINK ) llResetScript(); } touch_start (integer num_touches) { // Get the touched link number and face number and tell the owner integer link = llDetectedLinkNumber(0); integer face = llDetectedTouchFace(0); llOwnerSay ("Link " + (string) link + ": You touched my face " + (string) face"); } }
Once again, thank you for your attention.
Caledon Oxbridge also has an inworld Basic Scripting class, Mondays at noon SLT in our main lecture hall. Please see our schedule page.