[Opensim-users] [scripting] A llCastRay-based path finder

Jeff Kelley opensim at pescadoo.net
Tue Nov 19 17:47:44 UTC 2013


As you may have noticed, since 0.7.6 llCastRay is now functional. We 
can think about helping our NPC to find their way in a closed 
environment.

This method relies on marker prims, or beacons, defining waypoints. 
The following script computes the connexity matrix between a set of 
waypoints. A one in [ i,j ] means there is a direct path from i to j.

Build some walls on a flat ground. At corners and intersections, lay 
a number a thin prims named wp1, wp2 and so on.  Their altitude 
should be higher than or equal to the wall's base, so that rays 
casted between two waypoints will hit the walls. Here is a picture of 
my test rig: http://www.pescadoo.net/tmp/NPC_maze.png

It is advised to allow osSetPrimitiveParams. We'll get a nice demo 
for the price. Let's now browse the code.

First, we sense the waypoints. We iterate on llSense, incrementing 
the index until we get a no_sensor event. The waypoints names, 
locations and keys are stored in a strided list.

Then, we compute the matrix calling llCastRay for each couple of 
waypoints. We analyse the hits and declare the two waypoints are 
connex if we hit any object whose name IS NOT "wp*". This rule may be 
refined (using the object's height, volume, or some more 
sophisticated rule depending on the environment, a rug should no 
count as a wall). As the matrix is symmetric, we compute only a 
half-matrix and fill the transposed cells with the same value. We 
also fill the diagonal with ones. The matrix is stored as a string a 
'0' and '1', with pseudo-array accessors.

Finally, we enter a listening state. Saying "wp3" will highlight 
waypoint 3 in blue, and all connex waypoints in green (that's why we 
need osSetPrimitiveParams).That's the point your NPC can reach 
without bumping into the walls. Unreachable waypoints are colored red.

The script stops here for today. You guess it, the next step is to 
find a path from waypoint A to waypoint B. A classical graph theory 
exercise, the " transitive closure". I assume you know how to make a 
NPC move from point A to point B.

Have fun!




DEBUG (string msg) { llOwnerSay (msg); }
PRINT (string msg) { llSay (0, msg); }

//
// Waypoints list and accessors
//

list WayPoints; // Strided list (name, location, key)

string WPnam (integer i) { return llList2String (WayPoints, 3*i+0); }
vector WPLoc (integer i) { return llList2String (WayPoints, 3*i+1); }
key    WPKey (integer i) { return llList2Key    (WayPoints, 3*i+2); }
integer WPnumber ()      { return llGetListLength (WayPoints) / 3;  }

integer FindWP (string name) {
     integer i = llListFindList (WayPoints, [name]);
     if (i== -1) return i;
     else        return i/3;
}


//
// Connexity matrix and accessors
//

string Matrix; // Matrix NxN of booleans implemented as string of 0/1

MatrixNew (integer size) {
     integer n=0; for (n=0; n<size*size; n++)
         Matrix += "0";
}

MatrixSet (integer i, integer j, integer value) {
     integer size = (integer)llSqrt(llStringLength (Matrix));
     integer index = i*size+j;
     Matrix = llDeleteSubString (Matrix, index, index);
     Matrix = llInsertString (Matrix, index, (string)value);
}

integer MatrixGet (integer i, integer j) {
     integer size = (integer)llSqrt(llStringLength (Matrix));
     integer index = i*size+j;
     return (integer)llGetSubString (Matrix, index, index);
}

MatrixPrint () {
     integer size = (integer)llSqrt(llStringLength (Matrix));
     integer n=0; for (n=0; n<size; n++) {
         PRINT (llGetSubString(Matrix, n*size, n*size+size-1));
     }
}


//
// Colors, osFunctions to change color by ID
//

vector RED      = <1.0, 0.0, 0.0>;
vector GREEN    = <0.0, 1.0, 0.0>;
vector BLUE     = <0.0, 0.0, 1.0>;
vector BLACK    = <0.0, 0.0, 0.0>;
vector GREY     = <0.5, 0.5, 0.5>;
vector WHITE    = <1.0, 1.0, 1.0>;


SetIDColor (key id, vector color) {
     osSetPrimitiveParams(id, [PRIM_COLOR, ALL_SIDES, color, 1.0]);
}

BlinkIDColor (key id, vector color) {
     integer i; for (i=0; i<2; i++) {
         osSetPrimitiveParams(id, [PRIM_COLOR, ALL_SIDES, color, 
1.0]); llSleep (0.1);
         osSetPrimitiveParams(id, [PRIM_COLOR, ALL_SIDES, WHITE, 
1.0]); llSleep (0.1);
     }
}

ResetAll () {
     integer i; for (i=0; i<WPnumber(); i++)
         SetIDColor (WPKey(i), GREY);
}


//
// Waypoints sensing
//

integer wpindex;    // The current waypoint index

SenseNext () {
     string target = "wp"+(string)(++wpindex);
     llSensor (target, "", PASSIVE, 200.0, PI);
}

SenseDone (string theNam, key theKey, vector theLoc) {
     if (theKey == NULL_KEY) state deux;

     DEBUG (theNam +" "+(string)theLoc);
     WayPoints += [ theNam, theLoc, theKey ];
     SenseNext();
}

string Key2Name (key id) {
     list dtls = llGetObjectDetails (id, [OBJECT_NAME]);
     return llList2String (dtls, 0);
}

float Key2Height (key id) {
     list dtls = llGetObjectDetails (id, [OBJECT_POS]);
     vector pos = llList2Vector (dtls, 0);
     return pos.z;
}

//
// Connexity test
//

integer TestPath (integer wp1, integer wp2) {
     string nam1 = WPnam(wp1); vector loc1 = WPLoc(wp1); key key1 = WPKey (wp1);
     string nam2 = WPnam(wp2); vector loc2 = WPLoc(wp2); key key2 = WPKey (wp2);

     list hits = llCastRay (loc1, loc2, [RC_MAX_HITS,5]);
     integer found  = (llGetListLength(hits)-1) / 2;
     integer status = llList2Integer(hits, -1);

     DEBUG ("");
     DEBUG ("CastRay from " +nam1 +" to " +nam2 +", Hits/Status : "
                 +(string)found +" " +(string)status);

     integer numocclu = 0;
     integer i; for (i=0; i<found; i++) {
         key    hitKey = llList2Key    (hits, 2*i+0);
         vector hitLoc = llList2Vector (hits, 2*i+1);
         string hitNam = Key2Name (hitKey);
         float  hitHei = Key2Height (hitKey);
         float  hitDis = llVecDist (hitLoc, loc1);

         // Test d'occlusion. Ici, on se base sur le nom
         // On peut se baser sur la hauteur mais ne pas
         // utiliser hitLoc qui est le point d'interception

         integer occlusion = (llGetSubString(hitNam,0,1) != "wp");
         DEBUG ("["+(string)i+"] "+hitNam+" "+(string)hitHei+" 
"+(string)occlusion);
         if (occlusion) ++numocclu;
     }

     // Waypoints are connex if zero occlusion

     integer connex = (numocclu == 0);
     DEBUG (nam1 +" to " +nam2 +" : "+(string)connex);
     return connex;
}


//
// Highlight connex waypoints
//

ShowNeighbours (integer wp1) {
     ResetAll ();

     key key1 = WPKey (wp1);
     SetIDColor (key1, BLUE);

     integer wp2;  for (wp2=0; wp2<WPnumber(); wp2++) {
         integer reach = MatrixGet (wp1, wp2);
         key key2 = WPKey (wp2);
         if (wp1 != wp2)
             if (reach)  SetIDColor (key2, GREEN);
             else            SetIDColor (key2, RED);
     }
}


///////////////////////////////////////////////////////
// Step 1 : sense all waypoints and make a structure
///////////////////////////////////////////////////////

default {

     state_entry() {
         PRINT ("Sensing waypoints");
         SenseNext();
     }

     sensor(integer num) {
         key    hisKey = llDetectedKey  (0);
         vector hisPos = llDetectedPos  (0);
         string hisNam = llDetectedName (0);
         SenseDone (hisNam, hisKey, hisPos);
     }

     no_sensor() {
         SenseDone ("", NULL_KEY, ZERO_VECTOR);
     }

     state_exit() {
         PRINT ((string)WPnumber()+" found");
     }

}

///////////////////////////////////////////////////////
// Step 2 : compute the connexity matrix
///////////////////////////////////////////////////////

state deux {

     state_entry() {
         PRINT ("Computing Matrix");
         MatrixNew (WPnumber());

         integer wp1; integer wp2;
         for (wp1=0; wp1<WPnumber(); wp1++)
             for (wp2=wp1+1; wp2<WPnumber(); wp2++) {
                 integer reach = TestPath (wp1, wp2);
                 MatrixSet (wp1, wp2, reach);
                 MatrixSet (wp2, wp1, reach);
                 MatrixSet (wp1, wp1, TRUE);
         }

         MatrixPrint ();
         state trois;
     }

}

///////////////////////////////////////////////////////
// Step 3 : listen for waypoint and highlight result
///////////////////////////////////////////////////////

state trois {

     state_entry() {
         PRINT ("Listening to commands");
         llListen (0, "", llGetOwner(), "");
     }

     listen (integer channel, string name, key id, string message) {
         integer wp = FindWP (message);
         if (wp != -1) ShowNeighbours (wp);
     }

}





More information about the Opensim-users mailing list