Table of Contents

Note

This script was tested and works on OpenSim version 0.7.4!

This is the OpenSim version of wanderer that uses repositioning instead of physical movement.

Parameters

Ideally, the parameter TIME and STEP should be as low as possible in order to have a smooth movement effect. They cannot be calibrated automatically since they both depend on the size of the object and the simulator's capability to move and render the object. Since that can only be determined empirically, you may have to tweak the code in oder to get it to work on a satisfactory level for your object.

Crudely, the STEP size should be something lower than the diameter of the object and TIME should be as low as possible. The timer event is, in fact, used semantically as a recursive function that reenters as fast as possible. However, due to the fact that event handlers seem to be automatically killed after some time in OpenSim, moving the timer code to a recursive function is not possible. With the lack of a properly implemented simulator software, the result is not pretty.

With the current settings, using the vanilla script below, on Second Life the script moves a vanilla box ($0.5m \times 0.5m \times 0.5m$ in size) smoothly. On OpenSim, due to the slow re-entry of event handlers and improper scheduling of the timer events, the box seems to stutter.

The object's behavior on collisions may differ between implementations when colliding with other objects, even if STATUS_PHANTOM is set to TRUE.

Code

oswanderer.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////	 
 
//////////////////////////////////////////////////////////	 
//                  CONFIGURATION                       //	 
//////////////////////////////////////////////////////////	 
 
// This defines the movement type that the 	 
// primitive will have. The primitive will 	 
// generate coordinates within the following	 
// types of geometric areas.	 
//	 
// Replace the MOVEMENT_TYPE below with one	 
// of the following numbers representing:	 
// 2 for a square.	 
// 4 for a circle.	 
// 8 for a sphere.	 
// 16 for an upper hemisphere.	 
// 32 for a lower hemisphere.	 
// 64 for an elipsoid.	 
// 128 for an upper hemi-elipsoid.	 
// 256 for a lower hemi-elipsoid.	 
integer MOVEMENT_TYPE = 4;	 
// Maximum distance in meters from starting 	 
// coordinates that the primitive will be 	 
// allowed to reach.	 
float MOVEMENT_RANGE = 20.0;	 
// The minimum distance in meters when the	 
// primitive will consider that it has reached	 
// its next destination.	 
float TARGET_AFFINITY = 0.8;	 
//// Although these could be used to calculate	 
//// the average velocity, they are provided 	 
//// separately because the "feeling" of watching 	 
//// an object go from point to point has to be 	 
//// tweaked in order to be fluent (ie: without 	 
//// big noticeable jumps between the points).	 
//// Time between steps, measured in seconds. This 	 
//// differs between environments and can only be	 
//// determined empirically at the level of a script.	 
// Time between steps in seconds, Ideally as low 	 
// as possible	 
float TIME = 0.1;	 
// Step distance, measured in meters. Ideally as	 
// low as possible.	 
float STEP = 0.1;	 
 
//////////////////////////////////////////////////////////	 
//                     INTERNALS                        //	 
//////////////////////////////////////////////////////////	 
 
// Returns the next random coordinates	 
// depending on the type of shape selected.	 
//	 
// IN: integer representing shape.	 
// OUT: vector containing the coordinates	 
// relative to the initial starting position.	 
vector nextCoordinates(integer TYPE) {	 
    float driftRange = llFrand(MOVEMENT_RANGE);	 
    float a = llFrand(TWO_PI);	 
    float b = llFrand(TWO_PI);	 
    float c = llFrand(PI);	 
    if(TYPE == 2) return <iPos.x + driftRange, iPos.y + llFrand(MOVEMENT_RANGE), iPos.z>;	 
    if(TYPE == 4) return <iPos.x + driftRange * llCos(a), iPos.y + driftRange * llSin(b), iPos.z>;	 
    if(TYPE == 8) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), driftRange * llSin(a)>;	 
    if(TYPE == 16) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), driftRange * llSin(c)>;	 
    if(TYPE == 32) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), -driftRange * llSin(c)>;	 
    if(TYPE == 64) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), driftRange * llSin(a)>;	 
    if(TYPE == 128) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), driftRange * llSin(c)>;	 
    if(TYPE == 256) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), -driftRange * llSin(c)>;	 
    return iPos;	 
}	 
 
// Vector that will be filled by the script with	 
// the initial starting position in region coordinates.	 
vector iPos = ZERO_VECTOR;	 
// Storage for destination position.	 
vector dPos = ZERO_VECTOR;	 
 
// Begin script.	 
default	 
{	 
    state_entry() {	 
        // Avoid problems with other objects.	 
        llSetStatus(STATUS_PHANTOM, TRUE);	 
        iPos = llGetPos();	 
        dPos = nextCoordinates(MOVEMENT_TYPE);	 
        llSetTimerEvent(TIME);	 
    }	 
 
    timer() {	 
        if(llVecDist(llGetPos(), dPos) < TARGET_AFFINITY) {	 
            dPos = nextCoordinates(MOVEMENT_TYPE);	 
            llLookAt(dPos, 0.1, 0.1);	 
        }	 
        vector cPos = llGetPos();	 
        vector sPos = cPos + STEP * (dPos-cPos)/llVecDist(cPos,dPos);	 
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, sPos]);	 
        llSetTimerEvent(TIME);	 
    }	 
}	 
// End script.