//archived Cheeky Geek Archives with Qie Niangao, Resident Geek



Cheeky Geek with Qie Niangao (Archive)

Blue Meanies of Region Lag

Compared to private Estate owners, Mainlanders have fewer tools for finding sources of lag in their regions. In particular, we lack a "Top Scripts" list of objects that may contribute to sim-side lag. But even very busy scripts mostly lag other scripts. "Mostly." And there's a tool that can help identify one way scripts *can* hurt performance -- including viewer-side lag.

That's lag contributor is frequent object updates, which must be sent directly from the sim (no help from a Content Delivery Network) and received and processed by each viewer showing the updating object. This can be a particular burden on metered or low bandwidth connections. And then the viewer needs to draw each update, which can be a problem for complicated objects viewed on less powerful computers.

Fortunately, there's an easy, built-in way to find objects generating lots of object updates: Turn on "Develop" / "Show Info" / "Show Updates to Objects" and pan around your environment. (If you don't have a "Develop" menu bar item, turn it on in "Preferences" /"Advanced".) If you see something spewing a steady stream of blue squares skyward, that object needs closer inspection, and may even warrant removal if your situation is particularly vulnerable to network or viewer lag.

You'll probably see some individual red squares stream upward as you pan around. If they don't repeat, they can be ignored -- they've just come into view. (If they *do* repeat, especially accompanied by green squares indicating object deletion, they may be something temp-rezzing, which is another potential source of lag.)

This tool is also useful for determining which object updates are actually being processed by the viewer. Note that objects continue to get updates even when they themselves are hidden behind other surfaces, although that may not incur the same rendering cost as drawing updates to a visible object.

It's always a judgment call whether a frequently updating object is worth keeping despite whatever load it puts on the network and viewer. One of the charms of Second Life is the dynamic environment -- and we'll be seeing more of that with one of the Lab's upcoming projects, "Animesh" -- so don't throw out all your animated objects. Rather, just be aware of which objects are sending updates, try to understand why, and maybe assess whether they're worth the load.

Hiding Your Windlight Under a Bushel

Among the exciting Second Life features in development is the new Windlight environmental enhancements. There's no public schedule yet, but the draft spec has been kicking around since mid-June which was aptly summarized in Inara Pey's blog.

The most basic part of this is just the Lab implementing an official, more universal way for landowners and Estate managers to specify the sky and water settings viewers see by default over any parcel or region, similar in effect to what some third party viewers have done with text in parcel descriptions.

One step further is the ability to exchange Windlight settings as Inventory assets. Some will recall that this was promised years ago when Windlight was first introduced (there must be a Torley video somewhere, with a Real Soon Now assurance) and now it finally will be realized, uh, Real Soon Now.

That step is important as groundwork for the most exciting development: Scripted, per-user Windlight. And that's exciting because scripts can make fine-grained distinctions of where an avatar is located. The effects, then, can be specific not just to parcel boundaries but to location within a parcel: inside the peasant's hovel, far overhead in the sky castle, atop the roof garden, in the underwater lair, etc., all on the same patch of land.

Scripts will also be able to change the settings over time while the avatar remains in one place, as "weather conditions" change (precipitation still a separate scripting challenge). Or different avatars may be shown different conditions for the same location, perhaps corresponding to their different progress through a narrative.

Because this scripted Windlight could be disruptive if misused, it will be controlled by Experience permissions -- but that may help promote wider use of Experiences, generally enriching Second Life for participants.

Take a seat!

The word is spreading that the very popular AVsitter script system for animating furniture is going open-source from the end of this July (2017). The scripter, Code Violet, announced it publicly and it has been covered in online blogs and forums.

In earlier issues of this column I've discussed special uses of the system. particularly related to the "Experience" it offers to seamlessly attach props. That Experience is one of the more interesting challenges for open-sourcing the script. Fortunately, the existing Experience will continue working (which means the Experience owner will remain a Premium member, to keep the Experience valid). That's fortunate because it means stuff will keep working seamlessly for all the end users who've already agreed to enable the Experience, anywhere it's enabled on the grid.

It also demonstrates some forethought in the design of Experiences in SL. For an Experience to remain trusted by participants, someone must control what functionality is added. If instead it were possible for griefers to extend the AVsitter experience to randomly teleport people or be disruptive in some other way, residents wouldn't keep the Experience enabled, which would squander the "network effect" of its ever-growing base of enabled users.

Your Place Page or Mine?

I've found the recently introduced "Place Pages" a handy way to promote the best features of in-world locations. Any Second Life parcel that's set to "Show in Search" has a headstart in making such a page, so with relatively little effort that location can be appear a palatable destination, from wherever one might post links: blogs, profiles, group info, etc.

The steps are pretty easy to follow anyway, but there's also a good step-by-step introduction online here, written by Jeremy Linden.

A particularly cool feature of Place Pages is that you can use an interactive 360 Snapshot as a header graphic. At present, only a special "project viewer" creates those snapshots, but the results can be quite stunning.

I've had one disappointment, though, which is really a problem with SL's "Shared Media" feature: It doesn't seem to be practical to set up a gallery of many Place Pages, all visible at the same in-world site. I haven't tracked down exactly what goes wrong, but it seems that the pages demand enough connection capacity that only one or maybe two will reliably stay visible, at least in my trials with the Linden and Firestorm viewers. Again, that's not really a problem with Place Pages, and Shared Media has plenty of other limitations.

Reading between the lines of blog announcements, there may be hope of more functionality for Place Pages in future, too, so I'm investing some time in using them to improve discoverability of group parcels, even some that weren't Search listed before.

Notes on Notecards

A forums post first raised my interest in why notecards load so slowly into script memory. For example, we've all watched a complex multi-pose script take minutes to load its configuration notecard. It turns out that isn't a mystery: the function that reads a line of notecard text is artificially delayed by a tenth-second, for each line. That delay means there's no trick that can make a (single) script read a notecard faster.

That naturally leads to the temptation to add multiple scripts that share the task, spreading that built-in delay across the scripts. This multi-script approach was once common for many-stepped object animation, until LSL got a new function that did those operations without built-in delays. We could do the same with notecard-reading, but there's a reason the language changed to obviate that approach for object animation: There's a bunch of overhead involved in using multiple scripts, so it would lag all other scripts, too.

In fact, multiplexed reading of notecards could involve much *more* overhead inside the scripts, especially if the notecard lines must be processed in order. (That's in part because the process is "asynchronous": a script requests a notecard line in one function, and the response is available later in another part of the script.) So while it's certainly possible to speed up notecard reading using multiple scripts, it's better to minimize the delay by reducing the number of notecard lines a single script needs to read.

End users can take one simple step to get some limited relief: remove blank and comment lines from the notecard, each one shaving a tenth-second off load time.

For scripters, there are more powerful options. Simplest is to use a format that packs more information on fewer lines. A line can contain 255 characters -- that's a lot, and in practice most of it is unused.

Or the scripter can change the logic more deeply, to only read notecard lines when the information is needed. That's possible if the "when needed" information is indexed, where the "index" is held in notecard contents or implicit in the naming of separate notecards in the object's inventory.

A more radical change is to do away with notecard reading altogether and get the information from a web server, or from an Experience persistent store. Either approach adds some script complexity, limits usability and won't be as familiar to users as the simple notecard. Similarly, simply embedding the information in the script is very efficient, but makes it difficult or impossible for users to make changes.

A very important improvement is when scripts only re-read a notecard when it has indeed been changed (or when the script is reset), not every time the object's other contents change. This works because a notecard gets a new UUID ("key") every time is is saved -- but the script can only use this information if the notecard is full perm, so only full-perm notecards can be read only when needed.

Contents of a script-readable notecard in a modifiable object cannot be protected, regardless of permissions on the notecard itself. Some creators may mistakenly use restricted permission settings on configuration notecards, which is both futile and defeats scripts that could otherwise determine just when to re-read those notecards. Creators: Do not do that. And anybody with an object that either re-reads its configuration unnecessarily or needs to be reset to read a changed notecard: it may behave better if you replace the notecard with a full-perm one. In a modifiable object, an easy script extracts the contents of a notecard with more restrictive permissions, so a notecard is nowhere to try to hide secrets.

There's an exception, though. Notecards don't necessarily need to be included in the object that reads them. Instead, they can be read by referencing their key -- which we know from above always refers to a forever-static version of that notecard, even if it's been replaced in Inventory by another version. This is a bit tricky for creators, and gives a third party end-user no control at all, but it does hide the contents from everyone who doesn't know that notecard's key.

Aligning with the Alien

Suppose you want to align one of your objects with somebody else's object. Say, for example, you're making a railroad switch on somebody else's railroad (maybe even a Linden railroad!) and you can't just ask the creator which way the guide prims are pointed. What to do?

Scripts can find out all kinds of interesting facts about other people's objects (and avatars), but it can be tricky to identify to the script which object to report on. Picking from a list of sensor-detected items used to be a common solution to the problem, but it's far from handy. Now we can specify the target object by finding what's directly in front of our cam.

Once we've identified the target object, the handiest, most direct way to get at its orientation data is to put one's own "Indicator" object in the same position and rotation as the target -- and with that Indicator selected in the editor, its orientation can be copied directly to any to-be-aligned object, using a feature of some third-party viewers. Also, with that Indicator selected, the viewer's built-in origin axes are extremely handy for visualizing the object's rotation.

Here are a couple of scripts that each go in a fresh box prim and work together to get the position and rotation (and other fun statistics) about somebody else's objects. The first makes a little HUD you attach to figure out what object your cam is looking at. The second makes an unattached "Pos+Rot Indicator" object that will zip to the object the HUD thinks you're looking at, so you can see the position and rotation directly from that Indicator object.

For the HUD attachment: (log in to get this script from the inworld version of The Post!)

For the unattached Pos+Rot Indicator box: (log in to get this script from the inworld version of The Post!)

As convenient as this makes copying the parameters, the downside is that the Indicator must be able to enter and run scripts on the parcel that contains the target object, and will be subject to that parcel's auto-return. It would be an easy exercise to make the HUD report the information some way other than moving the Indicator around, but that would be much less convenient for cases where the land is accessible to the user's scripted objects.

Another Experience-experience: "Don't (mouse-)look now..."

Experiences have been around for a while now, and it's even been a few months since the most recent Experience-related feature, forced-sit control. Besides its ability to act as a "Bird Spike for Avatars" (as I described in the November 2016 column), its more intended usage is to direct Experience participants to take a particular seat immediately.

When an avatar sits -- forced or not -- its viewer can be set to Mouselook -- but only when they're first seated, after which they can press the Escape key to get back to normal cam control mode. (This is all independent of what an implementation of RLV may enforce in a third-party viewer.) Parts of some SL adventures may be more fun in Mouselook, so Experience designers may want to enforce Mouselook for those places, preventing distant cam views. That seems a natural fit: A Mouselook-forcing object that momentarily force-sits avatars any time they try to escape Mouselook.

So first, the scripts that do the deed, starting with one that goes inside an object that zips to the avatar's location, forces them to sit on it thereby putting them in Mouselook, and then disappears:

________________
/*
 2017 Qie Niangao released into Public Domain.

 This must be compiled to use an Experience enabled on the parcel.

 Goes in an object rezzed by "Rez one for each AV of interest"

 Negotiates the target agent,
 gets Experience perms from that agent
    (for real participants might be more verbose)
 then force-sits them just long enough to force mouselook

 BUT !!!
 IF USED WITH FLYING AVATARS THE FORCED SIT CAUSES THEM
 TO STOP FLYING IMMEDIATELY WHEN UNSEATED. See:
 https://jira.secondlife.com/browse/BUG-40984
 (A buoyancy kludge-around wouldn't actually restore flight mode)
 (so the rezzer script just skips already-seated AVs)

 ALSO
 IF USED WITH SEATED AVATARS THEY'LL BE UNSEATED
 UNLESS WHAT THEY WERE SITTING ON IS ALSO EXPERIENCE-SCRIPTED,
 THERE'S NO WAY TO AUTOMATICALLY RE-SEAT THEM AS BEFORE
 (so the rezzer script just skips already-seated AVs)

 Not sure why, but when trying to match the target AV's position and rotation,
 don't achieve intended effect with a rotated seat prim and zero_rot sit target
 but works fine with a zero_rot seat and rotated sit target.

*/

integer rezzedByObject = FALSE;

default
{
    state_entry()
    {
        llForceMouselook(TRUE);
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_SCRIPTED_SIT_ONLY, TRUE]);
        llSetClickAction(CLICK_ACTION_NONE);
        llSetCameraAtOffset(ZERO_VECTOR);   // Clear these, just to be safe
        llSetCameraEyeOffset(ZERO_VECTOR);
        llSetStatus(STATUS_PHANTOM, TRUE);
    }
    on_rez(integer rezChan)
    {
        if (rezChan)
        {
            key rezzer = llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_REZZER_KEY]), 0);
            llListen(rezChan, "", rezzer, "");
            llRegionSayTo(rezzer, rezChan, "1"); // tell rezzer I'm ready and listening
            llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_TEMP_ON_REZ, TRUE]);
            rezzedByObject = TRUE;
        }
    }
    listen(integer chan, string name, key id, string text)
    {
        llRequestExperiencePermissions((key)text, "");
    }
    experience_permissions(key agent)
    {
        list targetDetails = llGetObjectDetails(agent, [OBJECT_POS, OBJECT_ROT]);
        llSetRegionPos(llList2Vector(targetDetails, 0));
        // This the accepted approximation for best-fit of sit target to av's current pos+rot
        // from GetSitTarget in http://wiki.secondlife.com/wiki/LlSitTarget
        // (simplified by identical positions of av and prim)
        rotation tr = llList2Rot(targetDetails, 1);
        vector tp = llGetAgentSize(agent);
        float fAdjust = ((((0.008906 * tp.z) + -0.049831) * tp.z) + 0.088967) * tp.z;
        llSitTarget(llRot2Up(tr) * fAdjust - <0.0, 0.0, 0.4>, tr);
        llSleep(0.1);   // else observers see part of the llSetRegionPos translation
        llSitOnLink(agent, LINK_THIS);
    }
    changed(integer change)
    {
        if (CHANGED_LINK & change)
        {
            key av = llAvatarOnSitTarget();
            if (av)
            {
                llStopAnimation("sit");
                llSleep(0.2);   // maybe smoother dismount?
                llUnSit(av);    // Mouselook doesn't "take" if unseated before this event is posted
                if (rezzedByObject)
                    llDie();
            }
        }
    }
}
________________
Note that this script needs to be compiled with whatever Experience will be enabled on the land and by the participants.
              
A copy of the object that holds that script goes into another, rezzer object that holds the following script:
________________
/*
 2017 Qie Niangao released into Public Domain.

 Rez one copy of the inventory object for each avatar currently "of interest"

 In this case, we're interested in everybody not in Mouselook on this land

*/

float SAMPLING_INTERVAL = 10.0; // longest a newly "of interest" agent can go undetected
integer OWNER_ONLY = TRUE;   // ONLY FOR TESTING. If TRUE, applies to owner only
integer LAND_SPECIFICITY =
    AGENT_LIST_PARCEL   // just this parcel
//    AGENT_LIST_PARCEL_OWNER   // owned by my owner (must deed object to match group-owned land)
//    AGENT_LIST_REGION // whole sim
    ;

integer of_interest(key agent)
{
    if (!( llGetAgentInfo(agent) &
        ( AGENT_MOUSELOOK  // not already in mouselook
        | AGENT_IN_AIR  // save the fliers (see above)
        | AGENT_SITTING // not already sitting on something (optional)
        )))
        if (-1 == llListFindList(rezzingForAgents, [agent]))    // Not yet rezzing for this agent
            if (!OWNER_ONLY || (llGetOwner() == agent))
                return(TRUE);
    return(FALSE);
}



string invObjectName;   // Could set a string here if there's more than one Object in Inventory
list rezzingForAgents;
integer rezChan;

default
{
    state_entry()
    {
        rezChan = -1000000000 - (integer)llFrand(1000000000.0);
        llListen(rezChan, "", NULL_KEY, "");   // Leave it open. If nobody's around, what's to lag?
        if ("" == invObjectName)
            invObjectName = llGetInventoryName(INVENTORY_OBJECT, 0);
        llSetTimerEvent(SAMPLING_INTERVAL);
    }
    changed (integer change)
    {
        if (CHANGED_INVENTORY & change)
            llResetScript();
    }
    timer()
    {
        list newAgents = llGetAgentList(LAND_SPECIFICITY, []);
        integer agentIdx = llGetListLength(newAgents);
        integer now = llGetUnixTime();
        while (0 <= --agentIdx)
        {
            key agent = llList2Key(newAgents, agentIdx);
            if (of_interest(agent))
            {
                rezzingForAgents += [now, (string)agent, NULL_KEY];
                llRezAtRoot(invObjectName, llGetPos(), ZERO_VECTOR, ZERO_ROTATION, rezChan);
            }
        }
        // tidy-up stranded record (shouldn't happen normally, but maybe temp-rez GC at sim restart)
        if (rezzingForAgents)   // non-zero length
            if (now > (llList2Integer(rezzingForAgents, 0) + SAMPLING_INTERVAL))
                rezzingForAgents = llDeleteSubList(rezzingForAgents, 0, 2);
    }
    object_rez(key obj)
    {
        // Associate this object with the first still-pending agent
        integer firstNull = llListFindList(rezzingForAgents, [NULL_KEY]);
        rezzingForAgents = llListReplaceList(rezzingForAgents, [obj], firstNull, firstNull);
    }
    listen(integer channel, string name, key obj, string text)
    {
        integer thisRezzing = llListFindList(rezzingForAgents, [obj]);
        if (-1 != thisRezzing)  // heard from one we rezzed
            llRegionSayTo(obj, rezChan,
                llList2String(rezzingForAgents, thisRezzing-1)); // send associated agent's key
    }
}
________________

Note that to actually impose this on anybody else, you'll need to switch the OWNER_ONLY variable to FALSE. Also, this approach can't prevent momentary glimpses of normal cam control, where the maximum length of those glimpses is set by the SAMPLING_INTERVAL variable.

Now, is any of this practical? Perhaps not, as the script comments reveal, and that may be of more general interest than the scripts themselves.

First, you can't sit on two places at once, and there's no way to return an already-seated avatar to that seat after being force-sat onto one of these. (Well, unless that prior seating location is also scripted to force-sit avatars.) So the script skips over anybody already seated.

Second, there's no llFly() function (yet?), so the script skips avatars that are already in the air. Otherwise, while it would indeed force them into Mouselook, it would also shoot them out of the sky.

Third, the forced sitting and unsitting (controlled by the first script above) isn't perfectly, seamlessly smooth, despite measures to minimize the disruption. Among those is the use of an arcane best-fit approximation of where to put a sit-target to match the avatar's current location (part of many furniture-animation setup scripts). Also, a very brief delay was added after the seat moves into position because if the avatar is seated immediately, an observer will see the events out-of-order, with the avatar momentarily seated at the rezzing location then zipping back to where they were before being unseated. In contrast, the seated agent's own viewer shows the events in order -- although it's also popping into Mouselook at the time.

For all the limitations, the scripts do show one perhaps unintended way to use Experience-based forced-sit, and more generally a reusable way to rez an object for each avatar of interest.

Linksets

I find myself with prims to spare after the increase. I've been gradually using them to spruce-up the abandoned land in the area of my own land by encroaching with plants linked to roots on my own land, thus trying to add landscaping content that (I hope) will be generally regarded as an improvement over barren land.

* Generally I label the linkset as eligible for return at any time. I mean, obviously they are anyway, but just to suggest to any neighbor that they could ask me (or Linden support) to "clear the brush" if they want.

* I try to use only unscripted objects and alpha-masking to minimize any contribution to lag (sim- or viewer-side, respectively). Note that alpha-masking invokes Mesh LI accounting, so sculpts are rarely an option.

* Everything should be set phantom (or "None" physics type).

* Linksets can have elements with origins 54m apart. Once an assembly is linked-up, it's impossible to unlink an element while any part of the linkset is on somebody else's land; sometimes it's easiest to edit the linkset in a sandbox, then put it back.

No questions asked: Wear it!

Simply touch food or beverage to instantly wear it and animate to "eat" or "drink" it, without it going to inventory nor asking permissions.

We're able to do that if the items are scripted with an Experience in which we're enrolled and that's permitted on the parcel. That's fine for grand-scale Experiences where our hunger or thirst might be part of a whole game or adventure.

But what about the casual nosh from a craft table or a quick coffee shop latte? Do we really want to approve a whole separate Experience for each one?

That's a problem for furniture makers, too, where it's just too much trouble for a sitter to temp-attach a quick prop when a pose changes. Fortunately, the same solution applies: a built-in pose engine Experience that works for touchers as well as sitters.

One common pose engine, AVsitter, includes just what's needed for both: a script interface to an Experience with a growing base of enrolled users who have already encountered AVsitter-scripted items. Furniture makers who license that pose engine already have instructions for using that Experience in their products. Here we lay out a simple recipe for using it to enable touch-to-add-attachment ability in your own items without needing to own the engine itself. (Of course, if you want to distribute the items that give the wearables, you need to own the engine, but not if you're just giving away the wearables themselves. This isn't an endorsement of a specific product; with some more scripting you can apply the same method to your own Experience.)

I've set out a little in-world sample here that you may want to dissect along with the description below. Please also use it any way it's helpful in your own content.

To start, we'll need a "giver" (e.g., a plate of donuts) and a "wearable" (e.g., a donut). The wearable usually has an animation to play while the item is worn, so it already needs to be scripted to run that animation. In addition, to temp-attach with the Experience it also needs the "[AV]object" script, which always needs Copy+Transfer permission (so you may already have props in your inventory containing that script with those permissions, but you'll want a recent version, so start with the one in the sample provided).

The giver must be stocked with that wearable to give out, and must also contain a copy of the "[AV]prop" script (Transfer only, which you also may already own inside some furniture item or another, but again use the current version in the sample). It also needs a tiny "AVpos" notecard and a tiny script to trigger [AV]prop's temp-attach process. For our donut example, these are as follows:

------ AVpos Script ------

    Released to public domain by Qie Niangao, 2016 ◆

WARN 2
    This "WARN 2" needed to allow non full-perm props ◆

PROP1 Donut|Donut (auto-attaching)|m0|<0.0, 0.0, 0.0>|<0.0, 0.0, 0.0>|right hand
    where "Donut" is the label sent by script ◆
    and "Donut (auto-attaching)" is the object in contents to attach as a prop ◆

------ Script: "dispense auto-attaching donuts"  --------

//    Released to public domain by Qie Niangao, 2016

default
{
    state_entry()
    {
        llSetTouchText("Get Donut");    // shows in right-click menu
    }
    touch_start(integer total_number)
    {
        llMessageLinked(LINK_THIS, 90220, "Donut", llDetectedKey(0));
        // "Donut" matches label of prop in AVpos notecard
    }
}

Optionally set the stocked wearable Temporary (it's a good idea), take it into inventory, and put it into the giver. Then rez the giver on land that permits the AVsitter Experience, and touch it. If you've already approved the Experience, you'll wear the wearable as soon as the giver rezzes it; or, if you haven't already, you'll be prompted to permit the Experience.

Now, what about permissions for the wearable and its contents? This is often overlooked. Unless it's your own content and you don't care about limiting distribution, you'll need to be careful your item permissions comply with any licensing terms. Don't give away full-perm items or their contents as full-perm for the next owner unless that's allowed by the license (and it almost never is). Always test permissions with an alt or friend. (The sample is set as permissive as possible, generously permitted by the creators; yours will likely need more restrictive permissions.)

_____________________
"Givers" as Products
==============

The permissions question is much more complicated if you want to distribute products that are themselves givers -- that is, if you want to sell trays of donuts that the next owner can set out to supply donuts to third parties. (You can set next-owner permissions, but you really want more restrictive permissions on the NEXT-next-owner.) It's not really possible to fully protect the IP of those donuts and their contents, but it's possible to use the obscure "slam bit" function of SL permissions to somewhat help with this. You're still responsible to comply with any license terms, and you may demand tighter protection for your own content, too, so this is merely an idea to consider:

 - In a rezzed copy of the wearable, set the next-owner permissions to transfer-only (no-Copy) on it and any contents you want to protect.
 - Take that wearable into Inventory and from its Properties dialog change it to Transfer + Copy permission (which sets the "slam bit" on permissions because they changed while the item was in Inventory, not rezzed).
 - Put that wearable from Inventory into a copy of the giver, set permissions on the giver to the desired product permissions (probably Copy+Modify, no-Transfer) and take it into Inventory.
 - Pass it to an alt or friend for testing to make sure it's ready for your usual product distribution channels. (Note that for slam-bit testing, it's generally best to use another alt as the donut-eating third party.)

This way the product buyer rezzes out a copy of the giver, inside of which the wearable is still Copy+Transfer; then when a third party touches it, a copy of the wearable is rezzed -- but its "slam bit" was set so now the rezzed wearable gets set no-Copy, and transfers to the toucher to whom it temp-attaches.

(For more background on the "slam bit" see http://wiki.secondlife.com/wiki/Debug_Permissions .)

This does not totally protect content from intentional IP infringement by the *buyer* of the giver object (so it won't suffice for licensed content) but it does reduce risk of infringement by the *wearer*. It's at least possible to impose some EULA on the buyer, but it's hopeless to pass that down to the third-party wearer, so some mechanical constraint like this slam-bit approach may be the best we can do given the limitations of SL permissions and attachments. (There are ways to further protect content but they're beyond the scope here.)

_____________
Hat-tips:
 - Plato Novo of %Percent for all the yummy donuts, inspiration, and patience
 - Code Violet for giving AVsitter such a useful Experience and API
 - Prokofy Neva for https://community.secondlife.com/t5/LSL-Scripting/A-script-that-disappears-objects/td-p/3088196
 - Robin (Sojourner) Wood for everyone's favorite sipping anims (in the self-sipping donut attachments)

Entitlement

Group "Roles" and their corresponding "Titles" are all about discriminating among sub-groups, but until recently, scripts haven't been able to make that discrimination, but now they can.

This comes up when only certain roles should be treated specially: perhaps be given role-specific gifts, teleport to role-sorted destinations, or be sent messages only their role should receive.

The feature has uses for in-world commerce, too, but it's particularly important for making group Roles more useful for role-play.

A slight twist in the LSL implementation, OBJECT_GROUP_TAG, is that it can only know the Title (or "tag") text worn by the avatar and visible to all. That means the corresponding group will need to be active at the time, and usually the script will want to be sure that's really the case so as not to be spoofed by another group using the same tag. (Back in October we explored trickier ways to determine an avatar's active group, but the example below uses the basic llSameGroup match.)

So here's a silly example to demonstrate the basics of how to use the feature. (It also uses some chat history links, clickable in most viewers, so the user can see the group's name and interact with the group.)

------- SAMPLE SCRIPT --------
default
{
    // Some triggering event, we'll use touch
    touch_end(integer total_number)
    {
        key toucher = llDetectedKey(0);
        if (!llSameGroup(toucher))  // Not the same group, so fuss and ignore
        {
            llRegionSayTo(toucher, 0, "To be recognized, you must activate group secondlife:///app/group/"
                +(string)llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_GROUP]), 0)+"/inspect"
                +"\nIf you're already a member, select it from your list: "
                +"secondlife:///app/group/list/show");  // fuss...
            return; // ... and ignore.
        }
        string tag = llList2String(llGetObjectDetails(toucher, [OBJECT_GROUP_TAG]), 0);
        if ("Noble" == tag)
        {
            llRegionSayTo(toucher, 0, "Welcome, ye whose honored role is titled '"+tag+"'.");
        }
        else
        if ("Peasant" == tag)
        {
            llRegionSayTo(toucher, 0, "Your presence is tolerated, lowly '"+tag+"'-tagged one.");
        }
        else
        {
            llRegionSayTo(toucher, 0, "Unrecognized title: '"+tag+"'. Perhaps select another in group secondlife:///app/group/"
                +(string)llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_GROUP]), 0)+"/about");
        }
    }
}


 

 

 

Bird Spike for Avatars

Among recent server updates was a way to "protect" an object from avatars sitting on it, rather like the "anti-roosting" spike strips that deter birds from overhead railings, etc.

The feature can be used independently of Experiences, although it was introduced as part of a "forced sit" bundle of Experience-enabled features and was mentioned without further explanation in the Lab's "What's happening now" blog post.

Here's a simple, one-function script that sets this in the scripted prim:
________

default
{
    state_entry()
    {
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_SCRIPTED_SIT_ONLY, TRUE]);
    }
}
________

This "scripted sit only" is a true prim property that persists after the script is removed from the object, and when the object is shift-drag copied, taken into inventory, etc. Note that "scripted" does not include simple sit targets, another prim property set by script -- that is, even seats with sit targets will be rendered un-sittable by the function. Only scripts that use forced-sit will be able to seat avatars on such objects, using the Experience-dependent llSitOnLink() function.

Incidentally, it's perfectly possible to disable sitting on objects that also are set for "Click to Sit", thereby inviting the familiar "no suitable surface to sit on" warning, a newbie favorite.


More Groups Get Gifts - October 2016

A repeated request: How can my inventory-giver be set to distribute to multiple groups? or just to a different group than the land group to which the giver must be set?

There's a tricky old solution that still works: use separate giver prim(s) set to the target group(s) and link them to a root prim set to the land group. With this approach, each of the giver prims needs their own copy of the script and the inventory items to be given.

In the past year or so, however, we've gotten the ability to scan through all visible attachments, and one thing we can learn about them is the group to which those attachments are set -- which is almost always the active group of the avatar wearing the attachments.

That means an inventory-giver script can now check the active group of the avatar touching it, and match that against a list of groups who should all be able to receive the contents. That's what this script does:
________________________________________________________

/*
Sometimes we want to be able to give a bunch of stuff to multiple groups, or to a different group from the one to which an object is set (e.g., objects safe from auto-return). One long-standing trick for doing that involves linking child objects set to different groups from the root. That continues to work (for now?) but it's cumbersome, especially if there are a bunch of groups all to get the same stuff. This script relies on avatars having at least one visible (non-HUD) attachment, and checks if it recognizes the group setting of any such attachment. It "learns" to recognize groups the script's owner has active when s/he touches it, and remembers them all until reset. (The owner must touch it with the desired group active -- it will NOT assume it should match the object's own group until owner-touched for that group. Attachments are almost always set to the same group the avatar has active; one exception is stuff worn directly from the ground without first being taken into inventory, just during that login session.So usually all attachments will belong to the same, active group, but this script checks them all, just in case.
*/

list groups;
list invList;
string folderName;

getInvList()
{
    integer invIdx = llGetInventoryNumber(INVENTORY_ALL);
    while (--invIdx >= 0)
    {
        string invName = llGetInventoryName(INVENTORY_ALL, invIdx);
        if (invName != llGetScriptName())   // not this script
            if (!llGetInventoryPermMask(invName, MASK_OWNER) & PERM_COPY)
                llWhisper(DEBUG_CHANNEL, "Will not distribute NO-COPY inventory item: " + invName);
            else
                invList += invName;
    }
}


default
{
    state_entry()
    {
        getInvList();
        folderName = llGetObjectName(); // might want to customize this
    }
    changed(integer change)
    {
        if(CHANGED_INVENTORY & change)
            getInvList();
    }
    touch_start(integer total_number)
    {
        key toucher = llDetectedKey(0);
        list attachments = llGetAttachedList(toucher);
        integer attIdx = llGetListLength(attachments);
        while (0 <= --attIdx)
        {
            key thisAtt = llList2Key(attachments, attIdx);
            key thisAttGroup = llList2Key(llGetObjectDetails(thisAtt, [OBJECT_GROUP]), 0);
            if (-1 == llListFindList(groups, [thisAttGroup]))
            {
                if (llGetOwner() == toucher)
                {
                    llOwnerSay("Adding secondlife:///app/group/"+(string)thisAttGroup+"/inspect");
                    groups += thisAttGroup;
                    attIdx = -98;   // exit loop and flag completion
                }                  
            }
            else
            {
                llGiveInventoryList(toucher, folderName, invList);
                attIdx = -98;   // exit loop and flag completion
            }      
        }
        if (-99 != attIdx)  // auto-decremented from -98
            llRegionSayTo(toucher, 0, "Sorry, you don't seem to have a recipient group active.");
    }
}

_______________________________________________________________________

The comment at the top of the script details how the owner sets recipient groups, among other specifics.

It's a very simple script, and lacks any record-keeping or other features some folks want with their inventory-givers. Anybody is welcome to add functionality or otherwise change the script, commercialize it or anything they like.

Finally, although this script is not itself a creepy spy device, it does demonstrate something folks should realize: their active group is no secret, and every detail of any visible attachment is easily discovered.

 

Where has the summer Land Impact gone?

September 2016

Those who like to tinker with the content on our parcels -- that's everyone, right? -- sometimes find that something, somewhere is using more Land Impact than expected. Sometimes we do that to ourselves, as with adding Materials to items best left to Prim accounting, not Mesh. If it's a big enough mistake, the item gets returned, but what about the mistakes that aren't quite bad enough? How to find those Land Impact "hogs"?

One thing that can help is Build / Pathfinding / Linksets. Sorting by the Impact column reveals high L.I. objects that may warrant closer inspection. It's also handy because you can "Show beacon" and Teleport to it, making that closer inspection easy.

Another tool is a short script that can be worn as you wander your land to identify objects with higher Land Impact than prim count. This is overkill, of course, because many Mesh items have very low prim count, and even some prim-based objects can legitimately consume extra Land Impact -- so such a simple comparison leads to some false alarms, but very few misses. This works for other owner's objects, too, unlike the Pathfinding / Linksets method.

The script shows how to display object names in a way the user can click, get owner info, and teleport to the item. It can't highlight the item as in the Linksets window, so it can help to use the two methods together. (Similar scripts cast a beam of particles toward a target, but that's confusing for objects with void space at their origin, which is common for LI "hogs".)

---------------- TEXT OF SCRIPT --------------

// Report objects with higher Land Impact than Prim Count
float RANGE = 96.0;
integer CACHE_LEN = 100;
float SENSOR_INTERVAL = 1.0;

list alreadyReported;
integer reportedIdx = -1;

string fixedPrecision(float input, integer precision)
{   // from wiki
    if((precision = (precision - 7 - (precision < 1))) & 0x80000000)
        return llGetSubString((string)input, 0, precision);
    return (string)input;
}

default
{
    state_entry()
    {
        llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
        llSensorRepeat("", NULL_KEY, PASSIVE|ACTIVE, RANGE, PI, SENSOR_INTERVAL);
        while (++reportedIdx < CACHE_LEN)
            alreadyReported += NULL_KEY;
    }
    attach(key id)
    {
        if (NULL_KEY != id)
            llResetScript();
    }
    sensor(integer num_detected)
    {
        while (--num_detected >= 0)
        {
            key objKey = llDetectedKey(num_detected);
            if (~llListFindList(alreadyReported, [objKey]))
                return;
            list objDetails = llGetObjectDetails(objKey, [OBJECT_PRIM_COUNT, OBJECT_PRIM_EQUIVALENCE
                , OBJECT_SERVER_COST, OBJECT_STREAMING_COST, OBJECT_PHYSICS_COST, OBJECT_GROUP]);
            integer objPrimCount = llList2Integer(objDetails, 0);
            integer objLI = llList2Integer(objDetails, 1);
            if (objPrimCount < objLI)
            {
                string outStr = (string)objLI +" / "+ (string)objPrimCount
                    +" : Download="+fixedPrecision(llList2Float(objDetails, 3), 1)
                    +", Physics="+fixedPrecision(llList2Float(objDetails, 4), 1)
                    +", Server="+fixedPrecision(llList2Float(objDetails, 2), 1)
                    ;
                vector pos = llDetectedPos(num_detected);
                key objOwner = llDetectedOwner(num_detected);
                string groupOwnedString;
                if (NULL_KEY == objOwner)
                {
                    objOwner = llList2Key(objDetails, 5);
                    groupOwnedString = "&groupowned=true";
                }
                outStr += "\nsecondlife:///app/objectim/"+(string)objKey
                    + "?name="+llDetectedName(num_detected)
                    + "&owner="+(string)objOwner
                    + groupOwnedString
                    + "&slurl="+llGetRegionName()
                        + "/"+fixedPrecision(pos.x, 2)
                        + "/"+fixedPrecision(pos.y, 2)
                        + "/"+fixedPrecision(pos.z, 2)
                    ;
                llOwnerSay(outStr);
                reportedIdx = (reportedIdx + 1) % CACHE_LEN;
                alreadyReported = llListReplaceList(alreadyReported, [objKey], reportedIdx, reportedIdx);
            }
        }
    }
    run_time_permissions(integer perms)
    {
        if (PERMISSION_TAKE_CONTROLS & perms)
           llTakeControls(FALSE, FALSE, TRUE);    // Continue running on no-script land
    }
}

 _______________________________________________

Experiences, Déjà vu

August 2016

Experience Tools were covered in this column back in September 2014 when they were just about ready for general availability. Since then they've been used for many things, only some of which correspond to their stated intent.

Experience permissions are being put to notable use as part of the AVsitter package of animation-driving scripts. (Disclosure: Because of the Experience integration, your reporter become a satisfied user of this package, but is not involved in its development nor business.) If a user sits on certain AVsitter-scripted furniture, they'll be asked to enroll in the Experience, and if they grant permissions, they'll get automatic attachments from similarly-scripted furniture without needing to grant further permissions nor juggle inventory "gifts". The furniture-driven attachments are simply attached and detached as needed.

Of course, all the usual Experience considerations apply: if the user doesn't grant permissions, the script has to fall back to the pre-Experiencee way of managing attached props individually, and a user can revoke these permissions from all AVsitter-scripted furniture at once by simply disabling the Experience. Also it all only works on land where the Experience has been enabled.

Besides being very handy for the sitter, this application demonstrates Experiences can be bundled with tools that help spread an Experience across the grid (not to be confused with the very few special "grid scope" Experiences, none of which is nearly as popular as the normal, "land scope" AVsitter Experience).

A different approach would instead deliver source code for the Experience scripts so the furniture owner could compile them with their own local Experience. That would automatically grant permissions to the script as soon as a participant joins that local Experience. For some applications that would be an advantage, but it can't match the virality of the "tool bundled" approach, which applies across many different locations and landowners, if only to those items built using that Experience-enabled tool.

Transferring Modifications to the Non-Transferable

July 2016

Part of getting the most from SL content is customizing modifiable items. Sometimes we may even want to share our changes with others, even though the base object isn't transferable. If the base object is something others can obtain, there's hope of transfering just the changes for them to apply to their copy. I recently went through this process for a spacecraft I converted from flown vehicle to autonomous KeyFramed Motion shuttle, which I wanted to pass on to another owner of that same vehicle.

It took some puzzling-thru to arrive at a reasonably simple process for transfering the modifications by script, so I'll outline the process for anybody else facing this task. As we'll see, some steps are only needed for certain kinds of modifications.

As scripters would expect, the workhorse is the LSL function llSetLinkPrimitiveParamsFast(), called from a script in the root prim, pushing changes to all the other prims, switching among them with PRIM_LINK_TARGET, aided by various llSetLink* and llLink* functions.

This, however, cannot change texture alignment without specifying the texture (which is typically unavailable), for which we need a helper script to be pushed out and run in the affected individual links. The texture alignment functions, llScale-, -Offset-, and -RotateTexture are just part of this link-local script, which also moves or deletes prim contents, sets default seated cam settings and per-prim default click actions, and deletes extraneous prims (with llDie) when de-linked.

Getting helper scripts running involves copying them out to the links, which a master script can do, and then Recompiling them, which must be done by hand. (Coioncidentally, this operation breaks in the Linden viewer just now but still works in Firestorm and is fixed in the Linden RC viewer.)

For this case, the spacecraft also gets extra copies of a prim linked-in and modified. It seems easier to use a separate script for this link-manipulation process, rather than complicating a single script. (One such complication: setting an external flag to prevent a single script from starting over when all scripts are recompiled; incidentally, CHANGED_INVENTORY is raised in a root script every time a child link script is recompiled.) The second master script, then, uses llBreakLink to make the to-be-copied "seed" prim available to the user, who must manually drop it into the object's contents in order for the script to rez copies and llCreateLink them to the object for further modification. (That seed prim also needs the link helper script if its linked-in copies get any of those special, link-local changes.)

Although the scripting is a bit complicated, it's all in service of simplifying and minimizing manual user steps. And that's to promote in-world creation with very large building blocks of content -- in this case, non-transferable products made relevant to new uses (and markets) by handy-to-apply modification scripts from third parties.

Let's Do "The Twist"!

True geeks may still appreciate some retro prim tricks, so here we explore the (not so) latest craze: The Twist.

Probably the most oft-twisted prim is the Torus, but only because it made impressively complex (and difficult to render) hairstyles. Otherwise, though, the Torus is less interesting to twist than some other primitives because -- well, it's complicated.

The Torus's Profile Cut starts at the top center of the cross-section, not a corner as with the Tube and Ring prims, and that asymmetry makes those others interesting to twist. The cross-section of an *untwisted* Tube can be concave on the outside (profile cutting upward from the Beginning) or convex on the inside (profile cutting downward from the End) -- but twisted 180 (both begin and end), that's reversed.

The untwisted Ring is a shape of limited appeal but twisted 90 degrees it gets more useful, like a Tube with tapered cross-section. Try both + and - to get tapering towards the center or towards the edge, and notice how the hollow shape twists with the prim's cross-section.

The twisted cross-section and hollow is also interesting for the simple Box and Prism. An untwisted Prism is another shape limited by its symmetry: always at least two equal sides, and an equilateral "square" hollow -- but twisted 45 degrees, its sides can stretch independently, and its square hollow stretches as a rectangle instead of a rhombus.

The Box has perhaps the most useful 45-degree twist, stretching from the corners instead of the sides. The outside of a Cylinder looks the same however it's twisted, but like other prims, its square hollow shape can be rhomboid (untwisted) or rectangular (twisted 45 degrees).

The Sphere's 180-degree twisted form shows up "in the wild" for swapping the outside surface to instead line the sphere's inside -- and the lining of a hollow appears on that hollow's outside. This makes for a once-popular and efficient "cel-shaded" effect.

One precaution: Don't use "Materials" surface effects (specular- or normal-maps or non-Blended alpha) on these twisted shapes without testing first in a sandbox (or, at least, setting the prims' Physics Type to "Convex Hull" or "None"). These shapes are not all that difficult to render (contrary to popular superstition), but can have quite complex physics.

Of course, any of these shapes is trivial to construct as Mesh. The difference, however, isn't merely nostalgic: Each shape at each stage of twisting must be a wholly separate Mesh. As prims, they're all just a scripted transformation away from any other.

 

Who's Your (rezzer) Daddy?

Scripters know the problem: A scripted "parent" object rezzes another scripted "child" object, and needs to send along more information than can squeeze into the single integer that is passed during rezzing. The usual approach is to use that integer to pass a communication channel on which further information can be transmitted.

Until recently, the child had to listen to anything that used that channel -- not necessarily the parent. That's not a big security worry because the channel can be randomly assigned for each use, and it's not even a performance concern because there's little risk of cross-talk and the duration is usually very brief. Nonetheless it would be tidier to only listen to the rezzer, and now that's possible using the OBJECT_REZZER_KEY flag in llGetObjectDetails.

As one example, I recently used this approach to tell an Experience HUD the avatar to which it should try to temp-attach. It's not *necessary* to make it work, it's just "nice to have."

In this usage, the script is getting the key of its *own* rezzer, but the function is more general and can be used to get the rezzer of any other object in the region (if that object was rezzed since late January 2016, when the function was enabled on all sims).

Objects can be rezzed by other objects, or by avatars. Usually a rezzing *avatar* will also be the owner, except objects deeded to Group or sold "Original". So if you find a group-owned object and wonder whence it came, this function reveals all. (An object's *prior* owner is knowable, too, both using OBJECT_LAST_OWNER_ID and in some viewers, but that may not be the true rezzer, following a series of Original sales.)

A rezzing *object* may be of interest in tracking down the origin of griefer objects. Just knowing whether something was rezzed by an avatar or by another object can help an investigation.

 

Be a Glitter Bomb!

(Note: the scripts mentioned in this article can only be found inside the in-world edition of the Bay City Post. Pick up your copy by touching a newspaper box here.)

For something a little out of the ordinary: Go somewhere you can rez, rez a fresh prim, drop in this script: 􀀈, then take it into inventory quickly, before it disappears. It will be called "temp glitter" and will be phantom, temporary, and unscripted.

The script that applies this glitter, 􀀇, is best put in the contents of an attachment -- along with the "temp glitter" -- to keep it close to your avatar (the range is 10 meters, to discourage, ummm, excessive enthusiasm). It will start running when put in an object.

With the script running, Control-Alt-Pan your cam around your standing avatar.  Watch the "glitter" coat a shape around your avatar.

That's your avatar's physics shape. It's very different when you sit, and for that you'll need to aim lower than you may expect. It doesn't depend on the animation, so if you sit on something like a pose stand, you'll still have the physics shape of a sitting avatar even when a standing animation is used.

You might try glitter-coating nearby friends, too, until they demand their own glitter bomb attachment -- or tell you to stop being so annoying.

To turn it off either remove the attachment or deselect "Running" from the Script Editor. Do turn it off when you're done because it's moderately resource-intensive. (But don't worry, you'll definitely be reminded if the script is still active on no-rez land.)

A Target, Moving

March, 2016

"Key-framed motion" (KFM) is a handy, low-lag way to script objects to move around. As with most scripting functions, it's not always intuitive and there are obscure "gotchas" to avoid, but once learned, it's a handy part of a scripter's toolbox.

That, I thought, was that. In the past couple weeks, though, I've learned how very handy it is to combine "targets" with KFM. To appreciate why, it helps to know that KFM is specified by a list of relative movements, and that once the simulation starts running through that list, the motion will just keep going until interrupted or the list ends (which may be forever, if the list specifies a loop or "ping-pong" effect).

Part of what makes KFM so efficient is that the script doesn't need to intervene while that motion is ongoing.

For some uses, though, other things (particles, sounds, etc.) should happen during the motion, at particular locations. To trigger those other things, one might break up the motion into a sequence of stages -- but the script doesn't automatically know when KFM has completed. One approach would add a timer that triggers when the KFM should have had enough time to run. That works, but there can be fluctuations in timing, so it's smoother to detect that the motion has made it to the desired location (or rotation) -- and fortunately, scripts can use "targets" for just such detection.

(Besides timing, other things can go awry too, so using a timer to detect "stuck" conditions is good practice -- and not only for KFM.)

I've used scripted targets to detect the *end* of KFM for years, but somehow only just realized how very handy they can be for triggering effects while in mid-motion. For example, a space shuttle pod needs a "booster" stage with sound and particle effects; it should always happen at the same place, so instead of breaking up the KFM, a target triggers those effects as the motion continues.

Such use of targets is not only more efficient, it simplifies the script's whole flow of control.

An Accidental Prim Pentagon

February 2016


Idly avoiding productivity, I was playing around editing a box prim containing the script I was meant to be writing, and was surprised to see a regular pentagon appear.

Okay, to be honest, it's only "passably regular" but I'm convinced that a perfect choice of parameter settings could get all its sides and angles identical to arbitrary precision -- I'm just too lazy to solve for those values (and anyway, a serious pentagon-maker would simply use mesh instead).

And yet, for anybody with the prim-abuse addiction, here's a tiny script that makes a little desk-top pentagonal paperweight:

default
{
    state_entry()
    {
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX,
            PRIM_HOLE_TRIANGLE,     // Hollow Shape
            < 0.425, 0.575, 0.0 >,    // Path Cut (begin / end)
            0.67,   // Hollow (67.0 percent)
            < 0.25, 0.25, 0.0 > ,  // Twist (begin / end) (45 degrees)
            < 1.0, 1.0, 0.0>,    // Top size (X & Y tapers = 0.0)
            < 0.0, 0.0, 0.0>,   // Top shear
            PRIM_SIZE, < 0.5, 0.383, 0.1>
            ]);
    }
}

(Or follow the comments to do it all in the Build tool.)

If making the basic prim pentagon doesn't squander enough time, one might think of making it spin smoothly with the familiar llTargetOmega script -- but not so fast! The pentagon is far offset from the origin of the prim from which it was carved; set spinning it will revolve around that origin, rather than rotate about the middle of the pentagon.  Easily fixed, though: link it to a hidden root prim with its origin at the pentagon's center, then instead spin the linkset with that root prim.


(A final amusement: Alt-zoom from a surface of the spinning pentagon and observe how the cam moves.)

 

Whither Poseballs?

January 2016

I've been extending a multi-sit-animation system, prompting me to ponder "Whatever Happened To: Poseballs." They used to be everywhere; now they're rare; where did they go? Poseballs must now be mysterious anachronisms, at least to newer residents.

In fact, the history is much older than I'd remembered: In early 2007, a new scripting function (llSetLinkPrimitiveParams) was introduced that allowed a script in one prim to control properties in any other prims to which it was linked -- and notably, to reposition avatars seated on the object. That meant, for the first time, scripts could position seated avatars directly, rather than moving a scripted, unlinked seat (poseball).

Before that, avatars could certainly sit on objects, and scripts could specify (with llSitTarget) a single, fixed position and rotation where the seated avatar would be located and where all animations would play. Furniture often used "poseball" objects as those seats because poseballs were easy to move around while the avatar was seated -- much easier, anyway, than changing the scripted sit target and having the avatar reseat themselves.

Furniture scripts could also instruct those scripted poseballs to move themselves around, and this was how the first generation of multi-sit-animation systems worked, including the once-ubiquitous MLP. They allowed multiple avatars to sit on separate, unlinked poseballs that could be repositioned while the avatars remained seated. (One complication: they must explicitly request permissions because the animations are contained in the furniture rather than in the poseball where the avatar sits.)

Those systems are still in service; indeed, new furniture is still introduced using those systems. But once llSetLinkPrimitiveParams outgrew some early glitches, second-generation multi-sit-animation systems were developed and poseballs lost much of their appeal. Now the avatar could sit directly on the same scripted object that contains the animations, automatically granting permissions, and without the clutter of poseballs.

There are still specialized uses for poseballs. I use them myself, for example, as temp-rez, on-demand seats on natural terrain. Perhaps unexpectedly, Mesh furniture often contains a throwback to poseballs: Because a single Mesh gets just one dedicated sit target regardless of Land Impact, it's common to augment the mesh with extra links (e.g., separate shadow prims) to get enough seats. Unlike old-school poseballs, though, those extra sit-target links don't need to be separately scripted.


    *****

December 2015

Forecast: Light Flurries


Those of us who live in lands where snow is a regular event for much of the year may not have quite the same romantic attachment to the sight of a steady snowfall. But there's still some charm in seeing a few snowflakes fall, briefly, here and there.


Here's a free-to-use formula for such occasional "light flurries":

1.  Create two new prims on your parcel
2.  Link them together (select both and Control-L)
3.  Make sure it's set for the land group (so it won't auto-return)
4.  Move it up to 10m above the land
    (Just add 10 to the Position Z in the Editor's Object tab)
5.  Drop this script inside:


// float MOVE_INTERVAL = 4.0;
vector myPos;
rotation INCROT;
key myParcelDetail;
integer PARCEL_DETAIL = // chooose one:
     PARCEL_DETAILS_OWNER
//     PARCEL_DETAILS_ID
//     PARCEL_DETAILS_GROUP
     ;
key FALLING_SNOW_TEXTURE = "96fd270c-d630-6870-2ec0-6c850db3982f";


default
{
    state_entry()
    {
        if(2 != llGetNumberOfPrims()) // could spoof with sitter, but pfft.
            state await_relink;
        llSetLinkPrimitiveParamsFast    // set up for KFM
            ( LINK_ROOT, 
                [ PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX
            , PRIM_LINK_TARGET, LINK_ALL_CHILDREN
                , PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_NONE
            ]);
        llSetStatus(STATUS_BLOCK_GRAB_OBJECT, TRUE);    // Doesn't stop owner, etc.
        myPos = llGetPos();
        myPos.x = 2.0 + 4 * ((integer)myPos.x / 4);
        myPos.y = 2.0 + 4 * ((integer)myPos.y / 4);
        llSleep(1.0);   // avoid error from KFM (sim bug)
        llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
        llSetLinkPrimitiveParamsFast 
            ( LINK_ROOT, 
                [ PRIM_ROTATION, <0.70711, 0.00000, 0.00000, 0.70711>
                , PRIM_POSITION, myPos
                , PRIM_SIZE, <2.0, 2.0, 2.0>
                , PRIM_PHANTOM, TRUE
                , PRIM_NAME, "Wandering Snow Emitter"
            , PRIM_LINK_TARGET, 2
                , PRIM_ROT_LOCAL, ZERO_ROTATION
                , PRIM_POS_LOCAL, <2.0, 2.0, 0.0> * <0.70711, 0.00000, 0.00000, 0.70711>
                , PRIM_OMEGA, <0.0, 1.0, 0.0>, -0.5, 1.0
            , PRIM_LINK_TARGET, LINK_SET
                , PRIM_TEXTURE, ALL_SIDES, FALLING_SNOW_TEXTURE, ZERO_VECTOR, ZERO_VECTOR, 0.0
                , PRIM_COLOR, ALL_SIDES, ZERO_VECTOR, 0.0
            ]);
        llSetTimerEvent(4.0);   // MOVE_INTERVAL
        llLinkParticleSystem(LINK_SET,
            [ PSYS_PART_FLAGS, FALSE
              | PSYS_PART_RIBBON_MASK
              | PSYS_PART_EMISSIVE_MASK
              | PSYS_PART_INTERP_COLOR_MASK
              | PSYS_PART_INTERP_SCALE_MASK
            , PSYS_PART_START_SCALE, <4.0, 0.0, 0.0>    
            , PSYS_PART_END_SCALE, <4.0, 4.0, 0.0>
            , PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE
            , PSYS_PART_START_COLOR, <1.0, 1.0, 1.0>
            , PSYS_PART_END_COLOR, <0.9, 0.92, 1.0>   
            , PSYS_PART_START_ALPHA, 0.0
            , PSYS_PART_END_ALPHA, 1.0
            , PSYS_SRC_ANGLE_BEGIN, 0.0
            , PSYS_SRC_ANGLE_END, 0.0
            , PSYS_PART_START_GLOW, 0.0
            , PSYS_PART_END_GLOW, 0.05
            , PSYS_SRC_TEXTURE, FALLING_SNOW_TEXTURE
            , PSYS_SRC_ACCEL, <0.0, 0.0, -1.0>
            , PSYS_SRC_BURST_RATE, 1.0
            , PSYS_PART_MAX_AGE, 6.0
            , PSYS_SRC_BURST_RADIUS, 2.0
            , PSYS_SRC_BURST_SPEED_MIN, 0.0
            , PSYS_SRC_BURST_SPEED_MAX, 0.5
            ]);
        INCROT = llEuler2Rot(<0, PI_BY_TWO, 0>);
        myParcelDetail = llList2Key(llGetParcelDetails(myPos,[PARCEL_DETAIL]), 0);
    }
    timer()
    {
        vector nextPos;
        llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
        llSetPos(myPos);
        do
            nextPos =
                < myPos.x + 4 * ((integer)llFrand(3.0) - 1)
                , myPos.y + 4 * ((integer)llFrand(3.0) - 1)
                , myPos.z
                >;
        while ((nextPos == myPos) ||
            (myParcelDetail != llList2Key(llGetParcelDetails(nextPos, [PARCEL_DETAIL]), 0)));
        llSetKeyframedMotion(
            [ ( nextPos - myPos)
              , INCROT  // [KFM'd root PRIM_OMEGA is ugly!]
              , 3.9     // a little less than MOVE_INTERVAL
            ], []);
        myPos = nextPos;
    }
}
state await_relink
{
    state_entry()
    {
        llSay(DEBUG_CHANNEL, "This script must be in a linkset of exactly 2 prims");
    }
    changed(integer change)
    {
        if (CHANGED_LINK & change)
            llResetScript();
    }
}


(Editor's note: this is available as a script document in the inworld copy of the Post!)

Use Control-Alt-T to toggle "highlight transparent" and watch the emitter object randomly explore your contiguous land, somewhat favoring edges and corners. (Being able to see it is also useful, come The Thaw.) You can add copies for larger parcels.

This snow uses particles, which means there's nothing to get in the way of your mouse. (I always get tangled-up in mesh snow!) If there are lots of other particles in view, however, you may not see the snow -- so maybe highlight particle emitters (World / Show / Beacons: Particle sources) and make a sweep for old holiday decorations with particle-heavy candle flames or flickering particle "glow" on Christmas lights.

That's a good idea anyway, because too many particles can lag a viewer. That's how particle snow earned a reputation as laggy -- but that's using individual particles for each snowflake. This script, in contrast, uses one particle *ribbon* for each emitter, so it should be lag-free even for low-end machines.

Some (even more) techy details: The wandering pattern will snow "inside", too, when the emitter passes over a roof; it wouldn't be difficult to instead follow a fixed path, but that's a different project. Staying "outside" while preserving the randomness would be a natural for Pathfinding -- but for the huge Land Impact penalty.

The 10m height (and corresponding 6 second particle life) isn't completely arbitrary: An emitter much higher than that is apt to be culled from the interest list, and then the viewer wouldn't see the particles.


    *****






November 2015

New In-World Order

Even experienced scripters are often too focused on the performance of an indivdual script during testing, rather than the script's effect in actual use.

For example, scripts that are only rarely used are only rarely a problem, even if they use a lot of resources each time. This is especially true if the script is only used when there's (almost) nobody in the region: Even if the script causes massive lag for every other script in the sim, it makes no difference if nobody experiences the lag.


What's often overlooked is the opposite condition: Scripts that only run when a region is crowded -- and worse, scripts that have to work a little harder with each additional avatar in the region. This is typical of scripts that scan every avatar, including pretty much every script that calls llGetAgentList.

For example, a script may consider each avatar in a region, searching for new arrivals to greet (or more, perhaps screening account age, drawing weight, script time, etc). This is (more or less) a "linear" function of avatar count: add more avatars and the script works proportionately harder. That's already a potential problem because the more avatars in the region, the *less* time the sim can afford to spend on scripts, so the more each script's load affects lag -- and, of course, the more people around to experience it.

What's much, much worse is when the script does more processing *per* *avatar* for each avatar added. For example, imagine a script that, for each avatar, searches every *other* avatar for some matching quality. Such algorithms are typically "order" n-squared (or worse) where "n" is the number of avatars in the region.


Even a simple linear algorithm, however, can have a super-linear *system* effect. For example, imagine our linear, per-avatar screener script in a HUD to be attached to each avatar in the region. Now the system effect of that linear algorithm is order n-squared because each new arrival both adds another instance of the script *and* adds a bit of work for everybody else's script, too.


Systems and programs can be designed to scale to more avatars, but first it's important to note that script performance is a matter of scale and it's only at scale that script performance issues really matter.

    *****


October 2015


Alpha Types and FullBright Scholars


We've all used textures in which the "alpha channel" indicates which parts of the surface should be transparent. An alpha channel, however, is really just eight bits per pixel, same as the red, blue, and green channels, and those bits can be used for other things instead.

Besides the familiar "Diffuse Map" texture, the other two "Materials" bitmaps (Normal and Specular) both use the alpha channel for things having nothing to do with transparency. Those are interesting, too, as discussed on the wiki, but here we're considering a alternate use of the good ol' Diffuse Map's alpha channel: the "Emissive mask" alpha mode. 

The effect of an emissive mask is that parts of the texture are full-bright while at the same time that other parts of the same texture are lit normally, and yet other parts appear partially self-lit.

This has several practical applications. I've used it, for example, on maps, to light an "overlay" of full-bright features and labels with a background of normally lit terrain, all with just the one texture, whereas before I used two separate diffuse map textures to get nearly the same effect. I've also re-textured a single-face mesh lamp, so it could turn on and off just the parts of the surface that would be bright when the lamp is on. (Hint: Toggling PRIM_ALPHA_MODE between _EMISSIVE and _NONE is analogous to toggling PRIM_FULLBRIGHT between TRUE and FALSE.)

Another handy use is in combination with texture animation. A string of lights, for example, or some neon tubes, can flip on and off in sequence by animating a single texture containing a series of images with different parts lit, as coded by the Emissive mode alpha channel. The best part about texture animation is that it will continue even after the script is removed.

At first it can be tricky to make these Emissive masks, partially because we so strongly associate the alpha channel with transparency -- and so do our usual image-manipulation tools. (Hint: for GIMP, Colors / Components / Decompose and -/ Compose are very useful!)

Note that a diffuse map texture can only use its alpha channel for transparency *or* for emissive lighting, so partially transparent textures cannot be only partially self-lit.

For those seeking efficient yet advanced lighting effects, the Emissive mask alpha mode is good to know for that day when it's the perfect solution.


    *****

August 2015


The Wisdom for Crowds

An oft-cited limitation of the Second Life simulation architecture is the inability to support large numbers of avatars in close proximity sharing a common virtual experience. In fact, SL is hardly alone in that regard: This is an inherently challenging problem for non-trivial values of "proximity", "sharing", and "experience" -- never mind the target "large number."

Nonetheless, the Lindens appear to be working on increasing the number of simultaneous avatars that a region can handle successfully. As Inara Pey reports ( https://modemworld.wordpress.com/2015/05/27/second-life-project-updates-221-server-viewer-avatar-rendering/ ), Simon is experimenting with avatar counts above one hundred. Although progress in recent years has made this practical server-side, it can still easily overload viewers, so current efforts center on controlling how many avatars are rendered and how much detail is shown for avatars at different distances and with different complexity of attachments.

Inara's article quoted Simon's crib sheet for improving viewer performance in avatar-intensive environments; the advice is especially valuable to many in Bay City with busy in-world social calendars, so it bears repeating here:

    From Advanced > Show Debug Settings, set:

    RenderAutoHideSurfaceAreaLimit   0
    RenderAutoMuteByteLimit  0
    RenderAutoMuteFunctions  7
    RenderAutoMuteLogging  False
    RenderAutoMuteRenderWeightLimit  350000
    RenderAutoMuteSurfaceAreaLimit  150

    In preferences / graphics, change "Max # of non-imposter avatars" to something like 8. Also try ctrl-alt-shift-4 to hide avatars, or ctrl-alt-shift-2 for alphas.

Some of these settings may be subsumed by an upcoming viewer intended to improve handling of avatar complexity, again for dealing with more avatar-crowded scenes.

    *****


May 2015


Scripts Get Sexy

The system avatar shape editor has a "Male" or "Female" setting that affects how other settings affect the overall shape. For a very long time, scripters have wished their scripts, too, could respond to this setting. That's now possible, as "OBJECT_BODY_SHAPE_TYPE" reveals that information to the llGetObjectDetails function. The standard values are 0.0 for female and 1.0 for male, and intermediate values can be created manually to affect the other shape settings with varying levels of androgyny. 

Although it may work in some cases, it won't be generally accurate to assign gender-specific poses -- or really anything else -- based on this value. (Among other special cases, a "female" setting is sometimes used for smaller male avatars.) A smarter use might be to more precisely adjust how size affects the position of an animated avatar, or at least how to make a better first guess about where to put the avatar and props for any particular animation.

Similarly, OBJECT_HOVER_HEIGHT is now availabe, and is another piece of information that may help make a better initial guess about positioning the animated avatar.

Soon scripts will also be able to know the identity of an object's previous owner. This is the same information that has long been shown in third-party viewers.  Scripts will use this to authenticate products and purchasers, and perhaps as part of future "loot-trading" games.

Also coming soon: easier ways for scripts to identify whether they're running on Mainland or Estate,whether they're on a full sim or Homestead or OpenSpace, and the maximum number of avatars allowed in the region. Among other uses, scripts can adapt their demand for resources based on such information.

    *****

April 2015


Seasonal Shrubbery

There are lots of different trees and other landscaping items available in SL, yet I'm often searching for more bushes, brush, and undergrowth of all kinds. It's become a bit of an obsession for me. Long ago, I shared a trick for using Linden trees as shrubs by burying more of them than the standard viewer allows (hint: resize them first, then squash them down). I've also applied "bushy" sculptmaps to mod+copy tree branch prims, but different tricks become more relevant as plants are increaingly mesh-based, so what follows is another way to re-use some vegetation textures to expand the undergrowth options.

When it works, this practice gets more not only from our purchases of content but also from videocard memory, for ourselves and our visitors. If we use the same underlying texture asset both for tree branches and for shrubbery in the same scene, we're saving a bit of that texture memory -- a particularly precious resource on Mainland.

Increasingly, vegetation suppliers provide plants scripted to change textures with the seasons. When those plants are modifiable, it's sometimes possible to link on a bushy mesh or sculpty and try cycling seasons to see if the linked-on parts take the new season's texture. With perhaps a little scaling and alignment, the result may be a whole new bush using that seasonal tree texture.

There are a host of reasons this may not work in a particular case. In fact, it's just lucky when a script will apply seasonal textures to the appropriate face(s) of every linked prim. Hence, it's very easy for a creator to prevent this if they don't want their customers doing it. 

Any creators needing help with this are welcome to contact me.


    *****

November 2014


Polluting Linden Water


Did you ever notice that waves on Linden water move eastward? I can't prove that's always the case, but it appeared everywhere I looked, on every Windlight setting I tried with any waves at all.

You might wonder why anyone would ever notice such a thing. The reason was a study of pollution in Daley Bay. Intrepid marine biologists may examine http://maps.secondlife.com/secondlife/Bay%20City%20-%20Moosehead/5/181/20 for one pollution source.

The study revealed that the following script in a hollow cylinder "pipe" prim at water level generates quite a nasty little northbound stream of dirty pollution that spreads over the waves and fades into an oily sheen downstream to the east. Once set up, the script can be removed and the prim will continue to stream the pollution, and the prim can be rotated to emit a southbound stream instead. (In Linden water, orientations other than north or south aren't quite as compelling because the particle texture is stretched in the emitting direction, so it looks best when that aligns with the Linden waves.)

_______

default
{
    state_entry() 
    {
        // Orient prim so a horizontal ribbon emits (local +z) northward
        if (1 < llGetLinkNumber())  // child prim
            llSetLocalRot(<-0.5, 0.5, 0.5, 0.5> / llGetRootRotation());
        else
            llSetLocalRot(<-0.5, 0.5, 0.5, 0.5>);
        llParticleSystem(
            [ PSYS_PART_FLAGS
              , PSYS_PART_RIBBON_MASK
                // (maybe for some textures) | PSYS_PART_FOLLOW_VELOCITY_MASK
              | PSYS_PART_INTERP_COLOR_MASK
              | PSYS_PART_INTERP_SCALE_MASK
            , PSYS_PART_START_SCALE, <0.5, 0.5, 0.0>    
            , PSYS_PART_END_SCALE, <4.0, 4.0, 0.0>
            , PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE
            , PSYS_PART_START_COLOR, <0.4, 0.3, 0.1>
            , PSYS_PART_END_COLOR, <0.6, 0.5, 0.7>
            , PSYS_PART_START_ALPHA, 0.5
            , PSYS_PART_END_ALPHA, 0.0
            , PSYS_SRC_ANGLE_BEGIN, -0.25
            , PSYS_SRC_ANGLE_END, 0.25
            , PSYS_PART_START_GLOW, 0.02
            , PSYS_PART_END_GLOW, 0.00
            , PSYS_SRC_TEXTURE,  "d53f241b-496e-6ada-936d-40d4c90541b8"
                // Library / Textures / Waterfalls / Particle System / Water Particle 3
            , PSYS_SRC_ACCEL, <-0.015, 0.0, 0.0>    // Drift eastward with Linden waves
            , PSYS_SRC_BURST_RATE, 1.0
            , PSYS_PART_MAX_AGE, 25.0
            , PSYS_SRC_BURST_SPEED_MIN, 0.25
            , PSYS_SRC_BURST_SPEED_MAX, 0.29
            ]);
     }
}
_______

Particle aficionados will notice that this uses "ribbon" particles to lie flat at the water surface rather than the "sprite" display of regular particles.


    *****

October 2014

How to Blind Your Best Customers


Griefers could only dream of causing the inconvenience that in-world merchants and club owners are doing to visitors every time they offer a group-only sale or invite new arrivals to their groups.

That's because of a bug (BUG-6299) filed back in June: "Scenes will never load completely if changing active group tag to a different group while a scene is loading."

If a new arrival changes to a new group for any reason, they'll not be able to see anything more that hasn't already loaded in their viewer.

Apparently, nothing short of relogging will cause the missing items to load.

As we've all experienced, accepting a group invitation will immediately activate the new group, so group invitations to new visitors might seriously hurt business, as can group-only offers. (I've been tricked this way myself, bugging a merchant about just such an offer, having changed my group to grab it, only to have it never appear -- even though it was right where I was standing.)

Merchants and club owners may want to "Watch" that jira, and until it's fixed, refrain from group-only offers or invitations, or at least explain to visitors why so much stuff is hidden if they accept.

    *****

September 2014


Experiencing Experiences


The new Second Life "Experience Tools" functionality is nearing graduation from its current beta trial.  "Experience Tools" is the Lab's term for permissions and other functions that can be useful for creating games and other extended script-supported interactions. Linden Realms was an early example of this kind of functionality, implemented long before Experience Tools were ready; the new Cornfield game is a sample application of the current Experience feature set.

Before the beta, I knew that Experience permissions would be special so that a user could grant the ability for a whole set of scripts to interact in certain ways. There is, however, another less well-known feature, the "experience persistent store," that has surprised me with its usefulness.

Intended especially for tracking the progress of avatars through the experience (think game levels), it stores arbitrary key-value pairs for associative retrieval. Put simply, that means it doesn't support anything like relational queries, but does store anything a script knows to ask for by name. (That includes, for example, JSON-structured values.)

The part that may not be obvious is that the storage can be used for anything, not merely game progress, and thus provides something scripters have long wanted: a way for a script to store data, as well as make that stored data readily available to other scripts.  This greatly reduces the reasons scripts need to access external databases and services.

One need this satisfies is a "directory service," a way for scripted objects to find each other in order to send real-time messages back and forth. In the past, this was done either by an external server or by ugly and fragile email messaging through redundant in-world server objects. Instead, the experience persistent store now makes it easy for scripts to register for each other's messaging, as well as communicate other non-message data directly in the store itself.

Another example is management of common configuration data. My beta test of Experiences is a sequence of interconnected teleport sites, and I wanted to avoid configuring each one locally to know about the others, spread out across a continent. Instead, they register themselves in the experience persistent store and access a shared set of configuration data to know which pairs are to be linked, and their respective teleport destinations -- all without notecards nor external databases.

     *****

August 2014

Oh, Shiny!


I've recently set up some newer purchased items from talented creators, and discovered that those creations were not using Materials. Because these were Modify-permissive, I was able to tinker with Materials settings to improve their effect and fit them better to my environment. You can, too.

As always, Materials effects depend on enabling "Advanced Lighting Model" in Graphics preferences. (Although Shadows enhances everything, they're not required for Materials, and Advanced Lighting without Shadows usually incurs only a negligible performance penalty.)

I've written here before about adjusting the Alpha mode of legacy content; this time I'm referring to "specular maps"  -- adjustable in the Build Tool on the Texture tab, with the "Shininess (specular)" choice of Materials settings (as opposed to "Texture (diffuse)" or "Bumpiness (normal)").

As it says, this has to do with a surface's "shininess", and the simple amount of "Shiny" is all that we could affect before Materials. That's still available for compatibility with legacy content, but the effect isn't very satisfying, and is greatly enhanced by the ability to use a "Texture" (technically, a specularmap) to control shininess.

There's a lot one can do with specularmaps; the details are described in http://wiki.secondlife.com/wiki/Material_Data but they sort of assume that you have access to the diffusemap ("texture") of the surface. If you do, then you can make a custom specularmap that applies shininess just where you want it on the surface. 

But that's all pretty advanced, and for purchased items, we rarely have the textures anyway. That doesn't prevent us from taking a few simple steps to get good effects where the whole surface is equally shiny. Here's how to start:

Choose the "X"d-out "Texture" box, and select "Blank" instead. You'll immediately see that the surface is shiny, but in a very different and more appealing way than it was just by choosing a level of old-fashioned Shiny.

Next you can play with the "Glossiness" and "Environment" levels to make the effect more appealing. (The old Shiny corresponds to a very low Glossiness and high Environment setting.) The viewer controls can be a little bouncy, so be aware that the range is 0 through 255.

Most fun is the Color setting. Usually, white is perfect for this, but try setting it to the color of the surface as textured... that is, if the tinted diffusemap is gold colored, choose a (highly saturated) yellow-orange color for the specular effect. Yep, that's how metals reflect light! (Other materials that look like metal also reflect their own colors -- metallic Mylar balloons, for example.)

For an iridescent effect, try using highly saturated colors that contrast with the color of the textured surface.

     *****

April 2014

The Unworkable Workaround

Sometimes there's just no way of getting around a bug. And sometimes one must explore a very long maze before concluding that there's just no way of getting around a bug.

For over a month, I've been annoyed by an old bug (SVC-7739) which causes all Linden grasses and groundcovers to revert to one type of grass following a sim restart. (This bug must be especially maddening to Moles.) It was never a high priority bug, and at this point it seems pretty unlikely anybody will ever try to fix it, but it would be nice to be able to use those other groundcovers and have them stay put as rezzed.

Linden groundcovers have the handy property of automatically adjusting their planting height to match terrain after terraforming -- even if the terrain has changed because of the neighbor's terraforming.

After investigating and elaborating on the bug report (just in case anybody ever looks at it), I began exploring possible ways to work around it. The basic idea was to replant the groundcovers after they're corrupted by a sim restart. It's easy enough for a script to know when a sim has been restarted, and it can keep track of any plants it has rezzed; especially useful is the new ability for a script with the right permission to return specific objects it knows about. So, theoretically, a script might weed out corrupted plants and replace them with fresh copies...

... Except a script can't return objects owned by the landowner (other than the very objects in which they're contained, but Linden plants aren't "real objects" and can't contain scripts, nor anything else). On an individually-owned parcel, usually the landowner would be the one with permission to return the corrupted plants, so there would need to be two coordinated scripts in two separate objects, one owned by the landowner to return the plants, and one owned by somebody else with Linden plant-rezzing permissions, doing the re-planting. That's pretty ugly, but might be possible in a few locations.

What about group-owned land? Only a group-deeded object can return anything from a group-owned parcel. And again, it cannot return anything owned by the landowner -- in this case, the group. So the group-deeded object that returns plants cannot plant the replacements because the replacement plants would then be owned by the group, so they couldn't be returned by script.

(As it happens, I discovered that group-owned scripts can't actually rez Linden plants on group-owned land anyway. That's not documented and it's a little surprising because they can rez other stuff on that land, and scripts owned by others with the right permissions can rez Linden plants on that land.)

At first one might think that the "weeding" (plant-returning) script could be group-deeded, and the replanting script could be owned by an individual with permission to plant on that parcel.

That's correct, but there's a problem: group-owned scripts must obtain their object-return permission from a group owner -- typically the very same group members with permission to rez Linden plants on that parcel.

Not only that, but (also undocumented) group-deeded scripts aren't merely unable to return objects owned by the group, they also cannot return objects owned by a group owner -- the only ones who could grant the script object-return permission.

For the workaround to work, then, the plants and the script that replants them would have to be owned by a group member with permission to rez Linden plants on group-owned land, but without the ability to grant object-return permission. And the other, "weeding" script would have to be owned and granted permission by a group owner.

That might work, but really, who's ever going to set up separate group roles and coordinate different owners of separate scripted objects?


So, if someone mentions that they only ever see the same old Linden grass despite all the different kinds in the Library, well, you can tell them it's a long story. A really long, geeky story.

     *****

November 2013

Hairy Ribbons

Some new particle effects have been available in Linden test viewers for several months and the feature now seems to be pretty stable, so expect third party viewers to catch up in their next releases.
These new effects for particles are threefold: glow, advanced blending, and ribbons.

Particle glow is pretty simple and behaves the same as regular surface texture glow. (That is, it's way, way too intense when combined with "emissive" particles, same as glow on full-bright surfaces.) As with other particle effects such as color and alpha, glow has a start value that controls how it looks on a freshly emitted particle, and an end value to which it varies over the lifetime of each particle.

In contrast, the blending effects are tremendously complex -- and not very intuitive -- but it helps to know that what's being "blended" is the particle texture with the textures visible behind it. Particles then can be used to perform bitwise operations on how the surfaces behind them are rendered, making it possible for example to invert the colors of the scene viewed through such a particle. There's a range of 8 blending effects for the particle texture itself and the same 8 again for the scene behind the particle for a total of 64 possible blending effects. (Not all of these effects are interesting -- sort of the particle-rendering version of www.willitblend.com.) This is great psychedelic fun to play with, although personally I've yet to find any compelling practical use for particle blending.

The ribbon effect is straightforward in concept -- particles are either ribbons or they're not -- but the effect may be a little surprising, at least for those accustomed to old non-ribbon particles. That's because the ribbon effect changes what used to be a defining feature of particles: they were always drawn facing the screen, sprite-like, regardless of camera position. Ribbon particles, in contrast, are like regular surfaces in the scene, so the cam has to move around them to see them head-on. So they really are like ribbons, infinitely thin, with height and length determined by other parameters of the particle effect.


Specifically, a ribbon particle texture is repeated horizontally along the length of the ribbon, so it will look most continuous if the texture is tiled in the "U" or horizontal dimension. The vertical ("V") dimension of the particle texture is oriented along the Z axis of the emitter. If the emitter is rotated 90 degrees on the X or Y axis, then, so the Z axis is horizontal, the ribbon will be horizontal, too. Hence, multiple co-located emitters with different rotations of their Z axis will emit multiple particle ribbons -- sort of similar to how we used to make "plants" from multiple flat prims with different rotations about a central "stem". Similarly, spinning the emitter with llTargetOmega about a non-Z axis generates a helix.


     *****

October 2013

Coding a Dress Code

A new scripting feature always gets scripters interested; there's one coming that venue managers, too, may be happy to see.

For years it's been possible to see an estimate of how hard one's viewer has to work to draw each avatar on the screen. In the Linden viewer, for example, this is currently shown by Advanced / Performance Tools / Show Draw Weight for Avatars. The pending feature makes these numbers available to LSL scripts, too, using the OBJECT_RENDER_WEIGHT parameter to llGetObjectDetails().

That's kind of analogous to how scripts get memory and CPU usage of an avatar's combined attachments, and I expect busy venues to do similar things with the new rendering metrics: provide a gentle hint that a visitor may be causing lag -- or boot them out of the sim until they reduce off-the-charts resource demands.
Technically, compared to memory and CPU usage numbers, the implementation is different for avatar rendering weight: it's collected from the viewers connected to the sim, not measured by the sim itself. Among other implications, that means the metrics won't be available quite immediately upon arrival, nor quite instantly updated when an avatar's outfit changes.

Any metric has limited accuracy. The old script memory measurement was notoriously bad, and folks have always complained about estimates of avatar rendering cost, so there's bound to be controversy when this number is used aggressively. Nonetheless, used judiciously, this new measure can be both educational for responsible residents, and some automated protection from rendering-lag griefers.


At the moment, this feature is only available in a candidate viewer -- the same one with new particle features including eagerly anticipated "ribbon" particles


     *****

September 2013

Griefer Madness
Several anticipated changes will curb some of the worst griefer-caused annoyances.
First is a change to parcel access, which can currently be limited either to group members only *or* to anyone with payment info on file. There are cases where neither of these is quite right, however, so a change is in the works to permit access by both group members *and* payment-info-on-file folks, while excluding everyone else. (Right now, the Release Candidate regions have the server side of this change, but a viewer change is still needed to enable the setting.)
Group membership itself -- even of open-enrollment groups -- will be gaining a management control with the pending "group bans". I've heard this mistaken for an ability to restrict specific groups from entering a parcel which would be a silly feature and has nothing to do with what's actually in development. Rather, it will be possible to exclude named individuals from joining a group. This means that even open enrollment groups can be useful, even possibly as land groups, but certainly for chat, because the change will mean spammers can be excluded. That still requires active group management, of course, but the change is necessary for open-enrollment groups to be used for much of anything.
The ability of scripts to return objects from parcels, described in last month's issue, may work together with these upcoming group bans. In the past, it was a very bad idea to set parcels to public groups (e.g., store groups), because then every group member would have land permissions; notably, if the land permitted group rezzing, there was no way for auto-return to remove the stuff rezzed by random group members. It's still not a very flexible land setting, but at least now scripts can tidy-up such parcels automatically -- and once group bans can be added, it will be harder to repeatedly grief parcels with such settings.
Speaking of parcel settings, a change that's being considered is to make Group-only be the default Build setting whenever land transfers ownership, in contrast to the current default behavior which is to allow "Everyone" to build. Again, this is intended to reduce griefing, without the drawbacks of restricting Object Entry or enabling non-zero Auto-Return whenever land changes ownership.

Finally, another upcoming anti-griefing change is to further restrict self-replicating objects. Long ago, these created huge, sim-crashing problems, but they're mostly minor annoyances now that the "gray goo fence" limits how often copies of objects can be rezzed. The further restriction would be to limit the conditions where objects can transfer their contents to other objects--a function scripts use for this kind of self-replicating griefing, but also (in simple form) for product-updaters. The planned change will still allow this function for the landowner's own objects (and any others that would be immune if auto-return were enabled), so it shouldn't affect much legitimate content.

     *****

August 2013

More Happy Returns

In the past month or so, functions were introduced that make it possible for scripts to return objects from parcels.
It's relatively uncommon that this is really what's desired; rather, in the vast majority of cases, the old tried-and-true auto-return parcel setting is simpler and more efficient.
That's especially true for countering griefer attacks, for which the scripted approach has far too much overhead. Wisely, the functions are limited in the number of objects they can return per unit time, so they won't play into an escalating war of rez-and-return. (Specifically, they're throttled to return no more objects per hour than the total land impact capacity of parcels owned in the region.)
There are, however, some occasions when these functions are particularly useful. One such case is that of land deeded to a store's customer group. Now, this isn't generally the best idea anyway, but some stores do it this way, with parcel settings that allow rezzing only by that group. The problem is that this defeats auto-return: any customer group member can rez junk on the parcel, requiring manual clean-up.
The llReturnObjectsByOwner function can automate that clean-up. This sample script, (*see below) illustrates how that might work. To use it, you'd rez out a transferable object, drop the script in, deed it to the landowning group, touch it, and grant permissions; then it should check every five minutes and return anything owned by anybody who didn't have stuff on the parcel when it was first activated. (The script isn't intended to compete with "product quality" scripts; it comes with no guarantee that it will work as intended, nor that it won't delete everything on the parcel. Among other things, a product should handle error conditions, and might not return objects immediately if they're freshly rezzed just as the timer happens to expire.)


Of course, this isn't the only use of these functions. For example, they're useful for certain land rentals or other arrangements in which group membership can't easily match whose objects should stay rezzed on a parcel. Also, the other function, llReturnObjectsByID, is especially handy for scripted rezzing of non-temp objects that can be removed by the rezzing script without requiring a separate script in the rezzed object(s).

*sample script:
/*
    Public Domain, all rights waived, Qie Niangao, 2013,
    per CC0, http://creativecommons.org/publicdomain/zero/1.0/
*/

/*
    This script returns objects belonging to accounts who don't have objects already rezzed
    on the parcel when the script is activated.

    Trickiest and least satisfying part of this is keeping track of whose permissions to get
    for doing the returning. It's messy because owner of the scripted object isn't relevant
    (eventually) for use on group-owned land, where the script needs to be deeded to the group,
    too -- and that's probably the most useful application of the script. So, a quick-and-dirty
    approach: Get a touch from the person on whose behalf we'll return stuff.
   
    Of course, just because that person grants permission doesn't mean the person is in fact
    authorized to return stuff. No real way to tell that until trying llReturnObjects* -- and then
    it's kinda too late to do anything about it.
   
    Once we get the touch, we'll transition to a state that doesn't allow subsequent touches,
    to reduce confusion while preventing any new toucher from screwing up the already-set permissions.
*/

float AUTO_CHECK_INTERVAL = 300.0; // Seconds between checking for new object owners.

integer correctlyOwned;
list ownersAtActivation;
key onBehalfOf;

errOut(string outString)
{
    llRegionSayTo(llGetOwner(), 0, outString);
    llWhisper(DEBUG_CHANNEL, outString);
}

default
{
    state_entry()
    {
        // Are we owned by the landowner?
        list parcelDetails = llGetParcelDetails( llGetPos(),
            [ PARCEL_DETAILS_OWNER
            , PARCEL_DETAILS_GROUP
            ]);
        key parcelOwner = (key)llList2String(parcelDetails, 0);
        key parcelGroup = (key)llList2String(parcelDetails, 1);
        if (llGetOwner() != parcelOwner)    // Nope, not the landowner
        {
            if (parcelOwner == parcelGroup)
                errOut("/me must be deeded to the group that owns the land.");
            else
                errOut("/me must be owned by the landowner.");
            return;
        }
        // else yes, the landowner, so proceed
        llWhisper(0, "Good: Correctly owned by the landowner. Touch to proceed.");
        llSetText("Touch to proceed", llGetColor(ALL_SIDES), 1.0);
        correctlyOwned = TRUE;
    }
    changed(integer change)
    {
        if (CHANGED_OWNER & change)
            llResetScript();
    }
    on_rez(integer start_param)
    {
        llResetScript();
    }
    touch_start(integer total_number)
    {
        onBehalfOf = llDetectedKey(0);
        if (!correctlyOwned)
        {
            llRegionSayTo(onBehalfOf, 0, "/me is not owned by parcel owner; cannot operate.");
            return;
        }
        llRequestPermissions(onBehalfOf, PERMISSION_RETURN_OBJECTS);
    }
    run_time_permissions(integer perms)
    {
        if (PERMISSION_RETURN_OBJECTS & perms)
            state active;
    }
}

state active
{
    state_entry()
    {
        llSetText("", ZERO_VECTOR, 0.0);
        ownersAtActivation = llGetParcelPrimOwners(llGetPos());
        // strip out primcounts, leaving just the owner keys:
        integer ownerListIdx = llGetListLength(ownersAtActivation) - 1;
        while (ownerListIdx >= 0)
        {
            ownersAtActivation = llDeleteSubList(ownersAtActivation, ownerListIdx, ownerListIdx);
            ownerListIdx -= 2;
        }
        llSetTimerEvent(AUTO_CHECK_INTERVAL);
    }
    timer()
    {
        list currentOwners = llGetParcelPrimOwners(llGetPos());
        integer currentOwnerListIdx = llGetListLength(currentOwners) - 2;
        while (currentOwnerListIdx >= 0)
        {
            key thisOwner = llList2Key(currentOwners, currentOwnerListIdx);
            if (-1 == llListFindList(ownersAtActivation, [thisOwner]))
            {
                integer returned = llReturnObjectsByOwner(thisOwner, OBJECT_RETURN_PARCEL);
                string retString = "Returned "+(string)returned;
                if (returned < 0)
                    retString = "ERROR "+(string)returned+ " returning";
                llRegionSayTo(onBehalfOf, 0, retString+" objects belonging to "+(string)thisOwner);
            }
            currentOwnerListIdx -= 2;
        }
    }
    changed(integer change)
    {
        if (CHANGED_OWNER & change)
            llResetScript();
    }
    on_rez(integer start_param)
    {
        llResetScript();
    }
}



July 2013

Clearing Cache Superstitions
Reading a recent post in Inara Pey's superb blog, I learned of one bad superstition and was reminded of another. You can get it all and more from Inara directly at Living in a Modemworld but I'll reiterate the points here:

1. Clearing cache never reduces lag; in fact, it always creates some lag, and not only for the person clearing their cache. That's because everything that was cached must be downloaded all over again, using up network bandwidth for everybody sharing the same network -- everybody on the sim, and to a lesser degree, everybody using any SL services. Among the worst things one can do in a crowded sim is to login with an empty cache. (Theoretically, for the local viewer, a very large cache can become less efficient than a smaller one, but clearing a large cache is not at all the same as using a smaller one.)


2. Clearing cache still helps when textures or objects won't load correctly.  Recently, some folks have been claiming that the viewer cache never needs to be cleared anymore. That's wishful thinking. It's true that there have been times in SL's history when the cache was even buggier than it is now, but it is still very far from reliable, and a host of mysterious problems with in-world content are still cured by clearing cache and relogging. Don't let them try to tell you otherwise.



***

June 2013

Materials on the Move

The Materials processing code has now matured from "project viewer" to a beta release. One recent fix (MATBUG-29) has a benefit of broader interest than it may seem at first.

You may have noticed that using the Build Tool to set the repeats and offset parameters of a traditional texture ("diffusemap") does not cause a corresponding change to the normal- and specularmap. At first glance, that may seem wrong because the maps can get misaligned, but it's actually a feature: it enables using a diffusemap texture that contains multiple images with the displayed image chosen by texture scale and offset, while at the same time using unscaled Materials maps specific to just the image displayed on that one surface.

This is kind of important because some images can share Materials maps--such as leaves that change color with the seasons but retain the same bumpiness. Also, specular- and normalmaps can generally be lower resolution than the diffusemap images, so it's good to be able to control them separately...

... except when you don't *want* to control them separately, such as house numbers that change their bumpiness along with their diffusemap texture.

There is currently no direct scripting interface to Materials properties, so it's not just the Build Tool that's limited. Indeed, scripts are especially nerfed because they can't replace the specular- and normalmaps on a surface the way they can replace the diffusemap texture, and this recent fix doesn't add such a feature.

The fix, however, gives scripts a "sneaky" way to lock the Materials properties to the image's scale and offset. It's done using frame-by-frame ("cel") texture animation (not the more common "smooth" texture animation that continuously slides the image, under which Materials properties have been locked to the image from the start).

Using cel animation, scripts can adjust the repeats and offset of specular-, normal-, and diffusemaps all at the same time. That's particularly important because, in lieu of an explicit scripting interface to Materials properties, this trick is the only scripted way to change a surface's displayed image and also show specular- and normalmaps that correspond to the specific displayed image.

That's not the same as being able to completely replace the Materials properties at script runtime, but it does allow scripts to choose among a limited number of pre-defined specular-, normal-, and diffusemaps for a surface.

What's unexpected is that, while the cel animation is active (ANIM_ON), the offsets can be adjusted in lockstep from within the Build Tool, too (at least in the current project viewer). Very handy!


May 2013

A Material Difference

The much anticipated "Materials" feature is now live in the form of a "project viewer" that shows and manipulates surface properties of Second Life objects. To oversimplify, this means we can give surfaces highly customized "bumpiness" and "shininess" at the cost of an extra image download for each of these properties. That "bumpiness" can be so compelling that objects can look good with many fewer vertices, with a corresponding Land Impact savings, compensating for that extra image download.

Much has been written elsewhere about this feature, and experienced Mesh modellers will already be familiar with this functionality. Getting less attention, however, are advances in how transparency can be handled now, including the ability to completely avoid the common "alpha-sorting" problem of overlapping, partially transparent surfaces "fighting" for which one gets drawn in front. Surprisingly, this works in the project viewer even if "Advanced Lighting" is not enabled. (This must be enabled to see the effect of the other Materials properties, although even for these, Shadows are *not* a prerequisite, so entry-level graphics cards can display everything without much rendering lag.)

Besides being an advance for content creators, these features make it possible to breathe new life into our existing inventory of (modifiable) objects. Free tools can make basic "bumpiness" (normalmaps) to match existing surface textures, but for the advanced transparency features, the new viewer's Build Tool is all that's needed. So if you have, for example, a copy/mod plant with leafy parts that jump back and forth on top of each other (or inside / outside a window), you can fix this problem for yourself.

There will surely be tutorials about using the new Build Tool features, so for now, just experiment with the "Alpha mode" settings for semi-transparent textured surfaces. (Caution: For obscure technical reasons, this can hugely inflate the Land Impact of certain prims that use the "Prim" Physics Shape Type; generally, "None" or "Convex Hull" is what's really wanted anyway, and to be on the safe side, I recommend working in a sandbox with lots of spare prims to avoid exceeding parcel capacity. Also, for now, it's best to experiment with copies because there are still some rough edges in this feature.)

"Alpha blending" is what we've had all along, and that's what triggers the "alpha-sorting" problem we can now defeat using the "Alpha masking" mode in which each pixel is displayed either fully opaque or fully transparent. What's especially cool about this mode is that we can set the "Mask cutoff" threshold--the degree of transparency at which the pixels switch from opaque to transparent. Applying an artist's eye to a foliage texture, one can adjust this threshold and get very different effects, making the edges blocky or feathery. Future content can optimize the effect with specially crafted textures, but this feature also lets us refresh precious legacy content.

No comments:

Post a Comment

Calendar



Park Plaza Hotel

Park Plaza Hotel
Please visit our Sponsor!

Luxury Living in Bay City


Park Plaza residents enjoy beautiful views, fine dining, and a roof top patio with hot tub; all this near route 66, and within walking distance to Hairy Hippo Fun Land and aquarium. Rates start at 55L a week for studios, and 185L a week for full size apartments. Free furnishings available. Contact Roc Plutonium for more information!

Archive