LlCastRay Test

From Second Life Wiki
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

Verify that llCastRay() can be disabled using simulator.xml.

1.1) Edit simulator.xml and set the CPU time throttles to 0. Specifically, verify that allowed_region_raycast_time=0 and allowed_agent_raycast_time=0.

1.2) Restart a region running on the sim host.

1.3) After the region starts up again, login to the region.

1.4) Using a simple llCastRay() test object (such as one from the other tests), and verify that llCastRay() always returns RC_ERR_CAST_TIME_EXCEEDED = -3. Verify that this is true for both attached and unattached scripts.

Verify basic resource pool behavior

2.1) Edit simulator.xml and change the sim is back to the default raycast settings. Specifically, set allowed_region_raycast_time=2000 and allowed_agent_raycast_time=50.

2.2) Restart a region running on the sim host.

2.3) God rez this coalesced set on Aditi: 'need to describe how to make this object'

   Object: 'llCastRay time limit test', AssetID fd53c9de-0a18-6efa-8c2c-aff65770db25

2.4) Touch the blue box. It casts a rather CPU-intensive ray through the tortured torii.

2.5) Copy the "ray start" and "ray end" vectors, and hardcode them as the 'start_pos' and 'end_pos' in the blue box's script. This way, we can move the blue box around and still cast the same ray.

2.6) With default throttle settings, the blue box is allowed to cast ~12 successful rays in "Round 1" before failing, if the blue box is located on a region-sized parcel. This number can vary depending on region performance. Determine the size of your parcel resource pool by opening 'About Land -> Script Info', and look at the available URLs count. If you're in a full region, there will be 15000 URLs for the whole region, and e.g. a parcel pool 1/2 of the region size will be able to host 7500 URLs. Verify that the "Round 1: Failed after N successful casts" message from (2.4) roughly reports a number that's proportional to your parcel pool size out of 12 maximum URLs.

2.7) Move the blue box to a parcel belonging a smaller parcel resource pool, and repeat (2.6). Verify that the number of successful casts in Round 1 changed accordingly (e.g. if the parcel pool in (2.6) was 48,000m^2 and had 9 successful casts, a parcel pool in (2.7) should only get ~3 successful casts).

2.8) Attach the blue box, and touch it again. This time, you should only get ~2 successful casts before running out of time.

Verify ray cast throttle settings

Internal test only.

Verify that ray casts are blocked when simulator performance is low

4.1) Rez a box and create this script in it, which just casts a ray to the ground once every 100ms:

    integer max_hits=1;
    default
    {
        state_entry()
        {
            while(1)
            {
                vector start_pos = llGetPos();
                // into the ground
                vector target_pos = start_pos - <0, 0, 5000>;
        
                list hits = llCastRay(start_pos, target_pos, [RC_MAX_HITS, max_hits]);
                integer hit_result = llList2Integer(hits,-1);
                
                llSetText("hit result: "+(string)hit_result, <1,1,1>,1);
                llSleep(0.1);
            }    
        }
    }

4.2) Verify that the box almost always shows "hit result: 1", meaning that the ray hit something (possibly the ground)

4.3) Rez several (20?) interlocking torii. Select the torii, and set them physical at once.

4.4) Watch the box's display, and verify that the display changes to RCERR_SIM_PERF_LOW=-2

4.5) After a few seconds (when simulator performance has recovered), verify that the box once again displays "hit result: 1".

Object filter test

This test goes through all the combinations of RC_REJECT_TYPES and RC_DETECT_PHANTOM, and verifies that the correct set of objects appears in the llCastRay() results.

1) Rez a box called "llCastRay hits filter test" near the eastern edge of a sim, and create this script in it:

list hits;
list test_object_description;
integer detect_phantom;
integer reject_mask;

checkHits(key item, integer expect_hit)
{
    integer item_index = llListFindList(test_object_description, [item]) - 1;
    string item_name = llList2String(test_object_description, item_index);
    string config = "detect phantom="+(string)detect_phantom + " reject mask="+(string)reject_mask + ": ";
    
    if( !expect_hit && ~llListFindList(hits, [item]) )
    {
        llOwnerSay(config+"ERROR: detected "+item_name+" when we shouldn't have!");
    }
    else if( expect_hit && llListFindList(hits, [item]) == -1 )
    {
        llOwnerSay(config+"ERROR: didn't detect "+item_name+" when we should have!");
    }
}

default
{

    touch_start(integer total_number)
    {
        
        // types 
        key phys_nonphantom_object; 
        key phys_phantom_object; 
        key nonphys_nonphantom_object; 
        key nonphys_phantom_object; 
        key standing_agent;
        key sitting_agent;        
        key land = NULL_KEY;
        
        vector mypos = llGetPos();
        vector targetpos = mypos + <50, 0, 0>;
        
        // first scan looks at all types
        hits = llCastRay(mypos, targetpos, [RC_MAX_HITS, 10, RC_DETECT_PHANTOM, TRUE]);
        
        integer hit_result = llList2Integer(hits,-1);
        
        
        
        integer i=hit_result;
        llOwnerSay("Initial scan: hit "+(string)hit_result +" targets (should hit 7)");//+llList2CSV(hits));
        while(--i >= 0)
        //for(i=0; i < hit_result; i++);llCastRay hits filter test
        {
            key hit_item = llList2Key(hits, i*2); // get the key (stride is 2 when you don't ask for extra data)
            
            if(hit_item == NULL_KEY)
            {
                land = hit_item; // land is always NULL_KEY
                test_object_description += ["\nland", NULL_KEY ];
            }
            else if(llGetAgentSize(hit_item) == ZERO_VECTOR)
            {
                string object_name = llKey2Name(hit_item);
                if(object_name == "phys nonphantom object") phys_nonphantom_object = hit_item;
                else if(object_name == "phys phantom object") phys_phantom_object = hit_item;
                else if(object_name == "nonphys nonphantom object") nonphys_nonphantom_object = hit_item;
                else if(object_name == "nonphys phantom object") nonphys_phantom_object = hit_item;
                else llOwnerSay("ERROR: unrecognized object "+object_name+ " for item index "+(string)i + (string)hit_item);
                test_object_description += [object_name, hit_item];
            }
            else
            {
                if(llGetAgentInfo(hit_item) & (AGENT_SITTING|AGENT_ON_OBJECT))
                {
                    sitting_agent = hit_item;
                    test_object_description += ["sitting agent", hit_item];
                }
                else
                {
                    standing_agent = hit_item;
                    test_object_description += ["standing agent", hit_item];
                }
            }
            
        }
        llOwnerSay("initial scan details: "+llList2CSV(test_object_description));
        

        for(detect_phantom = 0; detect_phantom <= 1; detect_phantom++)
        {
            // RC_REJECT_AGENTS = 1
            // RC_REJECT_PHYSICAL = 2
            // RC_REJECT_NONPHYSICAL = 4
            // RC_REJECT_LAND = 8
            // RC_REJECT_AGENTS| RC_REJECT_PHYSICAL| RC_REJECT_NONPHYSICAL| RC_REJECT_LAND = 15
            // we don't check RC_REJECT_{EVERYTHING} because that results in the following script error:
            // "Invalid filter settings for llCastRay."
            for(reject_mask = 0; reject_mask < 15; reject_mask++)
            {
                hits = llCastRay(mypos, targetpos, [RC_MAX_HITS, 10, RC_REJECT_TYPES, reject_mask, RC_DETECT_PHANTOM, detect_phantom]);
                            
                
                
                // agents
                if(reject_mask & RC_REJECT_AGENTS) checkHits(standing_agent, FALSE);
                else checkHits(standing_agent, TRUE);
                if(reject_mask & RC_REJECT_AGENTS) checkHits(sitting_agent, FALSE);
                else checkHits(sitting_agent, TRUE);
                
                // land
                if(reject_mask & RC_REJECT_LAND) checkHits(land, FALSE);
                else checkHits(land, TRUE);
                
                // nonphantom objects
                if(reject_mask & RC_REJECT_PHYSICAL) checkHits(phys_nonphantom_object, FALSE);
                else checkHits(phys_nonphantom_object, TRUE);
                if(reject_mask & RC_REJECT_NONPHYSICAL) checkHits(nonphys_nonphantom_object, FALSE);
                else checkHits(nonphys_nonphantom_object, TRUE);
                
                // phantom objects; these are special and ignore the RC_REJECT_TYPES filter
                if(!detect_phantom) checkHits(phys_phantom_object, FALSE);
                else checkHits(phys_phantom_object, TRUE);
                if(!detect_phantom) checkHits(nonphys_phantom_object, FALSE);
                else checkHits(nonphys_phantom_object, TRUE);
                
            }
        }
        llOwnerSay("Done scanning.");       
    }
}

2) Align the following objects to be in the +x direction of the scripted box:

   A standing avatar
   An avatar who is sitting on an object
   A non-physical, phantom object named "nonphys phantom object"
   A non-physical, non-phantom object named "nonphys nonphantom object"
   A physical, non-phantom object named "phys nonphantom object"
   A physical, phantom object named "phys phantom object"
   A hillside, for the ray to hit (terraform land if necessary)

3) Touch the box, and note whether it reports that it hit all 7 objects. Sometimes alignment can be tricky, so you should rearrange the objects if necessary, before proceeding.

4) Note whether the scripted box reports any errors. If the test completed successfully, you should see output like this:

   [17:41] llCastRay hits filter test: Initial scan: hit 7 targets (should hit 7)
   [17:41] llCastRay hits filter test: initial scan details: 
   land, 00000000-0000-0000-0000-000000000000, sitting agent, 39b44919-512a-40a5-8347-90f9125937e4, standing agent, e1a51880-d7b5-4c00-800d-91664f5b84c0, phys phantom object, a8448d3b-9d80-2377-b16f-8c1d9560cfe0, phys nonphantom object, 23a7fedd-a604-d4b1-f2b4-d0cce2c0d6f7, nonphys nonphantom object, f6207cc7-c43b-a541-4932-6bc5e31f178d, nonphys phantom object, c7213ac4-08fa-2b59-70bc-f52a8d26c273
   [17:41] llCastRay hits filter test: Done scanning.

5) Have your sitting avatar sit on the ground instead of on an object. Repeat the test (results should be the same).