LlCastRay Test

From Second Life Wiki
Revision as of 17:25, 23 May 2011 by Dan Linden (talk | contribs)
Jump to navigation Jump to search


Purpose

Test the functionality of the llCastRay LSL command.

Sources

LlCastRay

Scope

(Servers shipped sometime after May 2011)

Test Plan

Setup

none

norm and position correctness test

When you place this test script inside a perfect sphere, it casts ~10,200 rays from every direction, from 1m outside the sphere edge into the center of the sphere. This script reports the following error conditions:

   Sim performance error (llCastRay() is disabled?)
   The ray didn't hit anything
   The ray hit a different object than the sphere
   The ray hit the sphere at wrong (>1mm error) coordinates
   The returned norm isn't the exact opposite of the ray cast direction

To run this test:

1) Rez a sphere of decently large size

2) Create the below script in it

3) Touch the sphere and wait for it to complete its computations. It will report any errors in llOwnerSay() chat.

4) Verify it does not report any errors.

default
{
    touch_start(integer total_number)
    {
       vector my_pos=llGetPos();
       vector my_scale=llGetScale();
       float my_radius=my_scale.x/2; // assume this is a sphere
       
       float scan_radius=my_radius+1.0; // how far away to be when casting rays
       
       integer theta_resolution = 100;  // scan in 100 parts
       integer phi_resolution = 100;  // scan in 100 parts
       
       integer phi_index;
       integer theta_index;
       
       float phi;
       float theta;
       integer errors;

       for(phi_index = 0; phi_index <= phi_resolution; phi_index++)
       {
           for(theta_index = 0; theta_index <= theta_resolution; theta_index++)
           {
               float theta = (theta_index * PI)/theta_resolution;
               float phi = (phi_index * TWO_PI)/phi_resolution;
               
               vector ray_start=my_pos;
               ray_start.x += scan_radius * llSin(theta) * llCos(phi);
               ray_start.y += scan_radius * llSin(theta) * llSin(phi);
               ray_start.z += scan_radius * llCos(theta);
               
               list hits = llCastRay(ray_start, my_pos, [RC_MAX_HITS, 1, RC_DATA_FLAGS, RC_GET_NORMAL]);
               
               integer hit_result=llList2Integer(hits, -1); // how many objects we hit

               string hit_message;
               integer hit_index=0;
               if(hit_result == RCERR_SIM_PERF_LOW)
               {
                   ++errors;
                   llOwnerSay("Error: RCERR_SIM_PERF_LOW @ "+(string)(ray_start-my_pos));
               }
               else if(hit_result == 0)
               {
                   ++errors; // we should always hit ourselves
                   llOwnerSay("Error: No hits @ "+(string)(ray_start-my_pos));
               }
               else
               {
                   // hit something...
                    key hit_object=llList2Key(hits, hit_index);
                    vector hit_position=llList2Vector(hits, hit_index+1);
                    vector hit_normal=llList2Vector(hits, hit_index+2);
                    if(hit_object!=llGetKey())
                    {
                        ++errors;
                        llOwnerSay("Error: Hit wrong object ("+ (string)hit_object +") @ "+(string)(ray_start-my_pos));
                    }
                    else
                    {
                        // hit ourselves
                        vector expected_hit_pos = my_pos + my_radius * < llSin(theta) * llCos(phi),  llSin(theta) * llSin(phi), llCos(theta) >;
                        vector expected_hit_norm = llVecNorm(ray_start - my_pos);
                        
                        if(expected_hit_norm * hit_normal < 0.99)
                        {
                            ++errors;
                            llOwnerSay("Error: Unexpected norm @ "+(string)(ray_start-my_pos)+"  Expected: "+(string)expected_hit_norm + " Got: "+(string)hit_normal);
                        }6.000
                        
                        if( llVecDist(expected_hit_pos, hit_position) > 0.001 )
                        {
                            ++errors;
                            llOwnerSay("Error: Unexpected hit pos @ "+(string)(ray_start-my_pos)+"  Expected: "+(string)expected_hit_pos + " Got: "+(string)hit_position);
                        }   
                    }
                    
                    integer progress = (theta_index+1) + (phi_resolution+1)*phi_index;
                    llSetText("Completed " + (string)progress + "/" + (string)((phi_resolution+1)*(theta_resolution+1)) + "\n" + (string)errors + " errors", <1,1,1>, 1);
                }
            }
        }
               
    }
}

(test for mesh) This test also works on a 'mesh' sphere (e.g. a sphere prim with 50% hollow), although I found I had to loosen my accuracy tolerances quite a bit. For a 6m hollow sphere, the test passed when my hit-position tolerance was 15cm and the norm-direction tolerance was 0.9; much tighter values resulted in many "errors".

Multiple hits correctness test

This test verifies that the RC_MAX_HITS option works correctly, and that the list of hits is in the correct sequence.

1) Rez a box

2) Shift-copy the box 7 times along the x-axis

3) Rename the boxes "target1" through "target8", with the higher-indiced box being in the +x direction

4) Rez another box immediately to the west of 'target1'. Place this script inside the box:

integer max_hits;
integer options;
default
{

    touch_start(integer total_number)
    {
        // RC_DATA_FLAGS options
        options=RC_GET_ROOT_KEY|RC_GET_LINK_NUM;  
        vector mypos = llGetPos();
        vector targetpos = mypos + <50, 0, 0>;
        
        max_hits = (max_hits+1) % 8; // test 0-7 hits
        //max_hits=256;
        list hits = llCastRay(mypos, targetpos, [RC_MAX_HITS, max_hits, RC_DATA_FLAGS, options]);
        //llSay(0, "raw output: "+llList2CSV(hits));
        
        integer hit_result = llList2Integer(hits,-1);
        string output = (string)max_hits + "max hits: ";
        output += "hit "+(string)hit_result +" objects: ";
        
         // by default, lLCastRay only returns the prim task_id and hit position.
         // but if we ask it to return link number information, the stride increases
        integer stride;
        if(options & RC_GET_LINK_NUM)
        {
            stride=3;
        }
        else
        {
            stride=2;
        }
        
        integer i;
        list hit_details;
        for(i=0; i < llGetListLength(hits)-1; i+=stride)
        {
            key hit_object=llList2Key(hits,i);
            vector hit_position;
            integer hit_link;
            if(stride==2)
            {
                hit_position = llList2Vector(hits,i+1);
                hit_details += [llKey2Name(hit_object)+" @ "+(string)hit_position];
            }
            else
            {
                hit_link = llList2Integer(hits,i+1);
                hit_position = llList2Vector(hits,i+2);
                hit_details += [llKey2Name(hit_object)+" @ "+(string)hit_position + " link# " + (string)hit_link];
            } 
        }
        llSay(0, output + llList2CSV(hit_details));
    }
}

5) Touch the scripted box 8 times. On the first touch, it should return information about target1 only, since llCastRay() stopped after 1 hit. On the 2nd touch, it should return details about target1 and target2. By the 7th touch, it should report information on the first 7 boxes. Verify that the data returned looks reasonable. Here's some example output:

[16:03] llCastRay max hits test: 1max hits: hit 1 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 2max hits: hit 2 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 3max hits: hit 3 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0, target3 @ <148.66260, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 4max hits: hit 4 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0, target3 @ <148.66260, 69.77858, 27.33796> link# 0, target4 @ <151.51950, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 5max hits: hit 5 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0, target3 @ <148.66260, 69.77858, 27.33796> link# 0, target4 @ <151.51950, 69.77858, 27.33796> link# 0, target5 @ <155.53380, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 6max hits: hit 6 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0, target3 @ <148.66260, 69.77858, 27.33796> link# 0, target4 @ <151.51950, 69.77858, 27.33796> link# 0, target5 @ <155.53380, 69.77858, 27.33796> link# 0, target6 @ <159.84640, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: 7max hits: hit 7 objects: target1 @ <143.52390, 69.77858, 27.33796> link# 0, target2 @ <146.02440, 69.77858, 27.33796> link# 0, target3 @ <148.66260, 69.77858, 27.33796> link# 0, target4 @ <151.51950, 69.77858, 27.33796> link# 0, target5 @ <155.53380, 69.77858, 27.33796> link# 0, target6 @ <159.84640, 69.77858, 27.33796> link# 0, target7 @ <163.48640, 69.77858, 27.33796> link# 0
[16:03] llCastRay max hits test: Hit 0 objects:
[16:03] llCastRay max hits test: 0max hits: hit 0 objects: 

Technically, all single prim objects only have link #0, so these results are correct.

On the 8th touch, RC_MAX_HITS=0, which is invalid. The script should report this script error:

You must request at least one result from llCastRay.

6) Edit the script and set options=RC_GET_LINK_NUM, to disable RC_GET_ROOT_KEY. Repeat step (5), and verify that the data returned remains unchanged.

7) Shift-copy the 8 target boxes in the +y direction. Link each copy to its original prim, so that you now have 8 pairs of 2-prim objects. Append "root" to each of the root prim names, and append "child" to each of the child prim names.

8) Position the ray-casting object in front of the targetNchild prims, and repeat step (5), with options=RC_GET_LINK_NUM. Touch the box 8 times. In this case, llCastRay() should report the child prim names, and each hit should report "link #2".

9) Position the ray-casting object in front of the targetNchild prims, and repeat step (5), with options=RC_GET_LINK_NUM|RC_GET_ROOT_KEY. Touch the box 8 times. In this case, llCastRay() should report the root prim names, but each hit should report "link #2".

10) Position the ray-casting object in front of the targetNroot prims, and repeat step (5), with options=RC_GET_LINK_NUM|RC_GET_ROOT_KEY. Touch the box 8 times. In this case, llCastRay() should report the root prim names, but each hit should report "link #1".

11) Position the ray-casting object in front of the targetNroot prims, and repeat step (5), with options=RC_GET_LINK_NUM. Touch the box 8 times. In this case, llCastRay() should report the same results as in (10).

llCastRay() resource limit test (work in progress):

Verify that a script can hit its pool's resource limit.

1) 'describe how to create this object' rez this coalesced set on Aditi: Object: 'llCastRay time limit test', AssetID b475cf57-a562-1205-2ace-6908b1b9bc5a


2) Touch the blue box. It will cast a ray through all the tortured torii, and hit 256 different times. For every run reported, this object casts 1000 such rays.

3) At the end of each run of 1000 cast rays, the object reports the most recent result. In a 'laggy' sim it will report RCERR_SIM_PERF_LOW, but usually you should see RCERR_CAST_TIME_EXCEEDED, indicating that the script exhausted the resource pool.

4) Verify that the blue object usually shows "RCERR_CAST_TIME_EXCEEDED".

5) Take a copy of the blue box, and wear it on your avatar. The avatar resource pool (at 0.050ms) is smaller than the region resource pool (at 2ms), and gets exhausted more easily. Verify that this also gets "RCERR_CAST_TIME_EXCEEDED" error, almost always.

6) Rez a physics-heavy object, such as physical interlocking torus chain. Verify that when physics time is high, both the attached and unattached blue boxes report RCERR_SIM_PERF_LOW briefly, and recover after the physics-heavy object settles down.

Verify that llCastRay() time resource pools are distinct

1) Leave the 'llCastRay time limit test' object from the previous test rezzed on a parcel.

2) Rez this object on the same parcel, which does a simple ray cast to the ground every 100ms:

Object: 'Not so heavy llRayCast object', AssetID 536925ac-a78c-2bf1-d65b-64df1003b785

3) Make a few dozen copies of the 'Not so heavy llRayCast object', and note the return code stats which are reported. Due to the script scheduler, we expect that some objects will be processed earlier in the frame and be successful with their ray cast, and that other objects will be processed later in the frame, and might prematurely hit the RCERR_CAST_TIME_EXCEEDED condition frequently.

4) Move the 'Not so heavy llRayCast object' prims to a parcel owned by a different avatar/group. Since the objects are no longer in the same parcel as the 'llCastRay time limit test', they should now have a very low rate of RCERR_CAST_TIME_EXCEEDED results.

5) Move the 'Not so heavy llRayCast object' to a different parcel owned by the same avatar who owns the parcel in (1). Verify that some of the moved objects now report RCERR_CAST_TIME_EXCEEDED, once again.

6) Attach one of the objects which had a high RCERR_CAST_TIME_EXCEEDED rate in (5) to your avatar. Verify that the successful rate rises, and that there are almost no errors.

7) Drop the attachment from (6), and verify that once again it has a high RCERR_CAST_TIME_EXCEEDED rate.

Overall, the accounting is said to follow the same rules as script memory and URL accounting, which is easier to measure than this CPU time. I need to figure out a way to verify that resource replenishment is working properly, but I'll save that for another day.