Difference between revisions of "LlCastRay"

From Second Life Wiki
Jump to navigation Jump to search
m
 
(58 intermediate revisions by 26 users not shown)
Line 1: Line 1:
==llCastRay==
{{LSL_Function
===Introduction===
|inject-2={{Issues/VWR-1331}}
I was trying to think of a witty and sarcastic way to start this page, but I'm tired and I want to go home, so this will have to do. :)
|func=llCastRay
|func_id=?
|func_sleep
|func_energy


I've added a new LSL function to Oatmeal 9, 13, and 14 on our beta grid, Aditi. This is a very early prototype with no guarantee of shipping, etc., etc., disclaimers, warnings, may not apply in your state, terms and conditions may apply, etc. I've put it up in the hope that you (the SL scripting community) will take advantage of the long weekend (if you're in the United States...otherwise the regular-length weekend...unless it's one of those awesome Bank Holidays...oh, and I suppose France, too, has a day off...) to build some awesome stuff and help me understand your needs for the final llCastRay implementation. Without further ado, here is the API for llCastRay. Please be sure to read the NOTES section below it before you start working.
|func_desc=Cast a line from {{LSLP|start}} to {{LSLP|end}} and report collision data for intersections with objects.


===API===
|return_type=list
|return_text=of strided values on a successful hit, with an additional integer [[#status_code|status_code]] at the end.


list llCastRay( vector <b>start</b>, vector <b>end</b>, integer <b>filter</b>, integer <b>flags</b> )
Each stride consists of two mandatory values {[[key]] {{LSLP|uuid}}, [[vector]] {{LSLP|position}}} and optionally {[[integer]] {{LSLP|link_number}}, [[vector]] {{LSLP|normal}}}. (See [[#RC_DATA_FLAGS|RC_DATA_FLAGS]] for details.)
Return value: [UUID_1, {link_number_1}, hit_position_1, {hit_normal_1}, UUID_2, {link_number_1}, hit_position_2, {hit_normal_2}, ... , <b>status_code</b>] where {} indicates optional data.


''Start'' and ''end'' are vectors specifying the start and end point of the ray. The ray must both start and end within the region where the script is located or a script runtime error will be generated. (So be sure to clamp your values!)
A negative {{LSLP|status_code}} is an [[#error_code|error code]], otherwise it is the number of hits (and strides) returned.
|func_footnote=
Example return of successful raycast, using the default options:
<syntaxhighlight lang="lsl2">
[key object_uuid, vector hit_position, integer status_code]
</syntaxhighlight>
In the case of an error, or if the ray hits nothing, the resulting list only contains the status code:
<syntaxhighlight lang="lsl2">
[integer status_code]
</syntaxhighlight>
|p1_type=vector|p1_name=start|p1_desc=starting location
|p2_type=vector|p2_name=end|p2_desc=ending location
|p3_type=list|p3_subtype=instructions|p3_name=options|p3_desc=can consists of any number of [[#options|option flag]]s and their parameters.
|constants
|caveats=*Depending upon the value of {{LSLP|flags}} (provided via [[#RC_DATA_FLAGS|RC_DATA_FLAGS]]), the number and types of values in the strides will vary. See [[#RC_DATA_FLAGS|RC_DATA_FLAGS]] for details.
*[[llGetRot]] will not return an avatar's exact visual rotation because the viewer doesn't update the avatar's rotation under a threshold (see {{jira|VWR-1331}}). To get an avatar's exact looking direction while in mouselook, use [[llGetCameraRot]] instead.
*[[llCastRay]] will not detect prims having no physics shape ([[PRIM_PHYSICS_SHAPE_TYPE]] = [[PRIM_PHYSICS_SHAPE_NONE]]).
*[[llCastRay]] will not detect a prim if the line starts inside the prim. This makes it safe to use the prim position as the start location.
*[[llCastRay]] can detect the prim the script is in, if the start location is outside the prim.
* The result of this function has been noted to be '''unreliable when the end point is out-of-bounds''' (Occasionally returns status code 0 regardless of amount of objects hit). (See [https://community.secondlife.com/forums/topic/486603-llcastray-returning-zero-hits-seemingly-at-random/ this forum post])
** The random failures seem to happen if the ray begins or ends more than 8 meters outside of current region bounds. Changes in only the ray's angle, or only in its position, may change the result. The result does not change if the exact same ray is cast again.
|spec=
===={{LSL Param|status_code}}====


''Filter'' is a bitwise-or combination of the following constants: ''RC_REJECT_AGENTS'', ''RC_REJECT_PHYSICAL'', ''RC_REJECT_NONPHYSICAL'', and ''RC_REJECT_LAND'' except that if you select all 4 of them, a script runtime error will be generated (it makes no sense to cast a ray and reject everything!). Note that phantom and volume detect objects are never returned and that seated agents are treated like unseated agents. I.e., you either get seated and unseated agents in your results, or you use ''RC_REJECT_AGENTS'' and get neither. Using 0 as the filter value will return all hits.
{{LSLP|status_code}} is a number tacked onto the end of the strided list to give you extra information about the ray cast.


''Flags'' is a bitwise-or combination of: ''RC_GET_NORMAL'', ''RC_GET_ROOT_KEY'', and ''RC_GET_LINK_NUM''. These select whether you want link numbers and hit normals in your results list. By default, you will get the UUID (i.e., 'key') of the exact child prim hit. If instead you want the key of the root prim, set ''RC_GET_ROOT_KEY''. Oh, and also, a terrain hit will register as NULL_KEY.
If the cast succeeded, it will be &gt;=0 and will indicate the number of hits.


''Status_code'' is a number tacked onto the end of the strided list to give you extra information about the ray cast. If the cast succeeded, it will be >=0 and will indicate the number of hits. If the ray cast failed (which should only happen right now if the simulator performance is running low), you'll get a negative status code. ''RCERR_SIM_PERF_LOW'' will be used as the status code if the overall physics time in the simulator is too high to perform raycasts. The idea is that you will know to try your cast again in a few frames. When we finally release llCastRay, we will limit the total number of casts per linkset. To see if your cast failed because you've run out of casts this frame, you can check the status code and see if it is set to ''RCERR_MAX_CASTS_EXCEEDED''.
If the ray cast failed (which should only happen right now if the simulator performance is running low), you'll get a negative status code. [[RCERR_SIM_PERF_LOW]] will be used as the status code if the overall physics time in the simulator is too high to perform raycasts. The idea is that you will know to try your cast again in a few frames.


===Example Code===
<table class="lltable" border="1" id="error_code">
This is a very poor script that I wrote to test the ray casts. Use it as a starting point if you like.
<caption>'''status_code''' error codes and their meanings.</caption>
<tr>
<th>Status Code</th>
<th title='Value'>V</th>
<th>Description</th>
</tr>
<tr><td>{{LSL Const|RCERR_UNKNOWN|integer|-1}}</td><td>{{#var:value}}</td><td>The raycast failed for an unspecified reason. Please submit a bug report.</td></tr>
<tr><td>{{LSL Const|RCERR_SIM_PERF_LOW|integer|-2}}</td><td>{{#var:value}}</td><td>The raycast failed because simulator performance is low. Wait a while and then try again. If possible reduce the scene complexity.</td></tr>
<tr><td>{{LSL Const|RCERR_CAST_TIME_EXCEEDED|integer|-3}}</td><td>{{#var:value}}</td><td>The raycast failed because the parcel or agent has exceeded the maximum time allowed for raycasting. This resource pool is continually replenished, so waiting a few frames and retrying is likely to succeed.</td></tr>
</table>


<lsl>
=====[[RCERR_CAST_TIME_EXCEEDED]]=====
integer filter = 0;
'''Note:''' [https://jira.secondlife.com/browse/SCR-199 SCR-199] indicates that pools have been removed from the main grid, so this return code should not appear.
 
'''Tips for Efficient Raycasts:'''
*Keep the max number of hits returned as small as possible
*Set as many [[RC_REJECT_TYPES]] as possible (of factors you can control, this will likely have the largest impact). For example, if you only want to know where the nearest agent is along a ray, use <code>[[RC_REJECT_LAND]] {{!}} [[RC_REJECT_PHYSICAL]] {{!}} [[RC_REJECT_NONPHYSICAL]]</code>
*When possible, avoid raycasting through piles of prims and avoid raycasting against concave physics objects (anything with cut, hollow, twist, and so on, and any mesh object that has no decomposition and has physics type "prim"). Obviously this can't always be avoided, so some casts may take significantly longer than others. Plan for that with robust scripts that handle [[RCERR_CAST_TIME_EXCEEDED]] responsibly, namely by sleeping briefly after the call and waiting for a few frames to go by before trying again.
 
==== {{LSL Param|options}} parameter ====
 
<table class=lltable border=1 id="options">
<caption>{{LSLP|options}} flags and their parameters</caption>
<tr>
<th>Flag</th>
<th title='Value'>V</th>
<th>Parameters</th>
<th>Default Value</th>
<th>Description</th>
</tr>
<tr><td>[&nbsp;{{LSL Const|RC_REJECT_TYPES|integer|0}}&nbsp;] </td>
<td>{{#var:value}}</td>
<td>[&nbsp;[[integer]]&nbsp;{{LSLPT|filter}}&nbsp;]</td>
<td>[ 0 ]</td>
<td> Mask used to ignore specific types of objects (and avatars). </td>
</tr>
<tr><td>[&nbsp;{{LSL Const|RC_DATA_FLAGS|integer|2}}&nbsp;] </td>
<td>{{#var:value}}</td>
<td>[&nbsp;[[integer]]&nbsp;{{LSLPT|flags}}&nbsp;]</td>
<td>[ 0 ]</td>
<td> Described in the [[#RC_DATA_FLAGS|RC_DATA_FLAGS]] section. </td>
</tr>
<tr><td>[&nbsp;{{LSL Const|RC_MAX_HITS|integer|3}}&nbsp;] </td>
<td>{{#var:value}}</td>
<td>[&nbsp;[[integer]]&nbsp;{{LSLPT|max_hits}}&nbsp;]</td>
<td>[ 1 ]</td>
<td> Maximum number of hits to return. Maximum value is 256. ''To avoid performance issues, keep it small.''</td>
</tr>
<tr><td>[&nbsp;{{LSL Const|RC_DETECT_PHANTOM|integer|1}}&nbsp;] </td>
<td>{{#var:value}}</td>
<td>[&nbsp;[[integer]]&nbsp;{{LSLPT|detect_phantom}}&nbsp;]</td>
<td>[ [[FALSE]] ]</td>
<td>Set to [[TRUE]] (or nonzero) to detect phantom AND volume detect objects. It is not possible to detect only phantom objects or only volume detect objects. If set to [[TRUE]], phantom and volume detect objects will always be detected, even if [[RC_REJECT_NONPHYSICAL]] and [[RC_REJECT_PHYSICAL]] are set in [[RC_REJECT_TYPES]].</td>
</tr>
</table>
 
=====[[RC_REJECT_TYPES]]=====
 
{{LSLP|filter}} is a bitwise-or combination of the following constants: [[RC_REJECT_AGENTS]], [[RC_REJECT_PHYSICAL]], [[RC_REJECT_NONPHYSICAL]], and [[RC_REJECT_LAND]].
 
<table class="lltable" border="1" id="rc_reject_flags">
<caption>'''RC_REJECT_TYPES''' and their meanings.</caption>
<tr>
<th>Reject Type</th>
<th title='Value'>V</th>
<th>Description</th>
</tr>
<tr><td>{{LSL Const|RC_REJECT_AGENTS|integer|1}}</td><td>{{#var:value}}</td><td>Avatars won't be detected.</td></tr>
<tr><td>{{LSL Const|RC_REJECT_PHYSICAL|integer|2}}</td><td>{{#var:value}}</td><td>[https://wiki.secondlife.com/wiki/Physical Physical] objects won't be detected.</td></tr>
<tr><td>{{LSL Const|RC_REJECT_NONPHYSICAL|integer|4}}</td><td>{{#var:value}}</td><td>The opposite of the above flag. Objects without physics won't be detected. For phantom objects, see [[RC_DETECT_PHANTOM]].</td></tr>
<tr><td>{{LSL Const|RC_REJECT_LAND|integer|8}}</td><td>{{#var:value}}</td><td>Land won't be detected. This refers to actual [https://wiki.secondlife.com/wiki/LlGround ground] only.</td></tr>
</table>
 
If you reject everything, a script runtime error will be generated (as it makes no sense to do this). Using 0 as the filter value will accept all types (default).
 
Also note that seated agents are treated like unseated agents. As in, you either get seated and unseated agents in your results, or you use [[RC_REJECT_AGENTS]] and get neither.
 
=====[[RC_DATA_FLAGS]]=====
 
{{LSLP|flags}} is a bitwise-or combination of: [[RC_GET_NORMAL]], [[RC_GET_ROOT_KEY]], and [[RC_GET_LINK_NUM]].
 
<table class="lltable" border="1" id="rc_data_flags">
<caption>'''RC_DATA_FLAGS''' and their meanings.</caption>
<tr>
<th>Data Flag</th>
<th title='Value'>V</th>
<th>Description</th>
</tr>
<tr><td>{{LSL Const|RC_GET_NORMAL|integer|1}}</td><td>{{#var:value}}</td><td>Stride includes the surface normal that was hit.</td></tr>
<tr><td>{{LSL Const|RC_GET_ROOT_KEY|integer|2}}</td><td>{{#var:value}}</td><td>The hit {{LSLP|uuid}} will be replaced by the object's root instead of any child.</td></tr>
<tr><td>{{LSL Const|RC_GET_LINK_NUM|integer|4}}</td><td>{{#var:value}}</td><td>Stride includes the link number that was hit.</td></tr>
</table>
 
|examples=
This basic example will cast a ray from the center of the object, 10 meters forward, depending on the object's rotation.
<syntaxhighlight lang="lsl2">
default
{
    touch_start(integer total_number)
    {
        vector start = llGetPos();
        vector end = start + <10,0,0> * llGetRot();
 
        list data = llCastRay(start, end, []);
        llOwnerSay(llList2CSV(data));
    }
}
</syntaxhighlight>
 
This is an example attachment that casts a ray based on the owner's camera in mouselook. It has many applications for things like weapons, scripted interactions with the world (like allowing a HUD to display information about things the user is looking at), etc.
<syntaxhighlight lang="lsl2">
integer gTargetChan = -9934917;
 
default
{
    attach(key id)
    {
        if (id != NULL_KEY)
        {
            llRequestPermissions(id,PERMISSION_TAKE_CONTROLS|PERMISSION_TRACK_CAMERA);
        }
    }
 
    run_time_permissions (integer perm)
    {
        if (perm & PERMISSION_TAKE_CONTROLS|PERMISSION_TRACK_CAMERA)
        {
            llTakeControls(CONTROL_LBUTTON|CONTROL_ML_LBUTTON,TRUE,FALSE);
        }
    }
 
    control (key id, integer level, integer edge)
    {
        // User must be in mouselook to aim the weapon
        if (level & edge & CONTROL_LBUTTON)
        {
            llSay(0,"You must be in Mouselook to shoot.  Type \"CTRL + M\" or type \"Esc\" and scroll your mouse wheel forward to enter Mouselook.");
        }
        // User IS in mouselook       
        if (level & edge & CONTROL_ML_LBUTTON)
        {
            vector start = llGetCameraPos();
            // Detect only a non-physical, non-phantom object. Report its root prim's UUID.
            list results = llCastRay(start, start+<60.0,0.0,0.0>*llGetCameraRot(),[RC_REJECT_TYPES,RC_REJECT_PHYSICAL|RC_REJECT_AGENTS|RC_REJECT_LAND,RC_DETECT_PHANTOM,FALSE,RC_DATA_FLAGS,RC_GET_ROOT_KEY,RC_MAX_HITS,1]);
            llTriggerSound(llGetInventoryName(INVENTORY_SOUND,0),1.0);
            llSleep(0.03);
            key target = llList2Key(results,0);
            // Tell target that it has been hit.
            llRegionSayTo(target,gTargetChan,"HIT");
            // Target, scripted to listen on gTargetChan, can explode, change color, fall over .....
        }
    }           
}
</syntaxhighlight>
 
This example handles the caveat about rays extending outside of region bounds by calculating the point where the ray intersects with the region's edge.
<syntaxhighlight lang="lsl2">
vector GetRegionEdge(vector start, vector dir)
{
    float scaleGuess;
    float scaleFactor = 4095.99;
    if (dir.x)
    {
        scaleFactor = ((dir.x > 0) * 255.99 -start.x) / dir.x;
    }
    if (dir.y)
    {
        scaleGuess = ((dir.y > 0) * 255.99 - start.y) / dir.y;
        if (scaleGuess < scaleFactor) scaleFactor = scaleGuess;
    }
    if (dir.z)
    {
        scaleGuess = ((dir.z > 0) * 4095.99 - start.z) / dir.z;
        if (scaleGuess < scaleFactor) scaleFactor = scaleGuess;
    }
    return start + dir * scaleFactor;
}
 
default
{
    touch_start(integer total_number)
    {
        vector start = llGetPos();
        vector direction = <1,0,0> * llGetRot();
        vector end = GetRegionEdge(start, direction);
 
        list data = llCastRay(start, end, []);
        llOwnerSay(llList2CSV(data));
    }
}
</syntaxhighlight>
 
This example casts a ray from the center of the object, 25 meters north, while applying different [[RC_REJECT_TYPES]] each time.
<syntaxhighlight lang="lsl2">
integer filter;// default is 0


default
default
Line 28: Line 244:
     state_entry()
     state_entry()
     {
     {
         llSay(0, "Hello, Avatar!");
         string ownerName = llKey2Name(llGetOwner());
        llOwnerSay("Hello, " + ownerName + "!");
     }
     }


Line 34: Line 251:
     {
     {
         vector start = llGetPos();
         vector start = llGetPos();
         vector end = start - <0,-25,0>;
         vector end = start - <0.0, -25.0, 0.0>;
       
 
         if ( filter > 8 )
         if ( filter > 8 )
        {
             filter = 0;
             filter = 0;
        }
 
       
         llOwnerSay("Filter " + (string)filter);
         llOwnerSay("Filter " + (string)filter);


         list results = llCastRay(start, end, filter, 0);
         list results = llCastRay(start, end, [RC_REJECT_TYPES, filter, RC_MAX_HITS, 4] );
       
 
         integer hitNum = 0;
         integer hitNum = 0;
         // Handle error conditions here by checking llList2Integer(results, -1) >= 0
         // Handle error conditions here by checking llList2Integer(results, -1) >= 0
         for ( hitNum = 0; hitNum < llList2Integer(results, -1); hitNum++ )
         while (hitNum < llList2Integer(results, -1))
         {
         {
             // Stride is 2 because we didn't request normals or link numbers
             // Stride is 2 because we didn't request normals or link numbers
             key uuid = llList2Key(results, 2*hitNum);
             key uuid = llList2Key(results, 2*hitNum);
             string name;
 
            if ( uuid == NULL_KEY )
             string name = "Land"; // if (uuid == NULL_KEY)
             {
 
                name = "Land";
             if (uuid != NULL_KEY)
            }               
            else
            {
                 name = llKey2Name(uuid);
                 name = llKey2Name(uuid);
            }
 
             llOwnerSay("Hit " + name);
             llOwnerSay("Hit " + name + ".");
 
            ++hitNum;
         }
         }
       
 
         filter += 1;
         ++filter;
     }
     }
}
}
</lsl>
</syntaxhighlight>
 
|notes=
===Notes===
Use [[llDumpList2String]] to see what the output looks like when you try a new set of flags.
* Quick Tip #1: use llDumpList2String to see what the output looks like when you try a new set of flags
* Quick..no, not a quick tip. Only had one of those apparently. But I do have more notes.


# Some notes on how this prototype might differ from the final version:
To quickly get the status code use <code>[[llList2Integer]](result, -1)</code>.
#* The final version may never ship (just being honest!)
#* The final version will have strict throttling. I'm currently thinking up to 4 calls to llCastRay per linkset per frame, and that will be subject to sim performance (but will probably require very, very poor performance before they start returned an error code rather than executing). Part of this early preview is to help me learn what reasonable throttles might be
#* The final version will not return ALL hits. It may only return the first hit. Or, if performance doesn't seem to be much of a problem, it might give you the option of selecting the first hit only or up to N hits for some max N.
#* Temp on rez objects will likely not be allowed to cast rays, possibly with the exception of allowing them to cast rays if they have an agent seated on them (to allow vehicles to use it but not bullets)


# Please use discretion on these beta regions. The script calls are not currently throttled (much) and I will not be around every minute of the weekend if someone creates a griefer item and ruins it for everyone. But I will track down that person and make sure that I add a special check in the code that prevents him or her from ever using ray casts in their scripts. :)
'''Ideas for uses''':
# Please provide lots of feedback, preferably on the discussion page in this wiki. That feedback, as well as the things you build in world, will help me determine appropriate throttles but will also help me offer more optimized versions of the script call for particular uses, if they turn out to be important. For example, if lots of people need to cast several rays at a time from the same start point, there's an optimized version I could make for that. E.g., <code>llCastRay(vector start, list end_points, integer filter, integer flags);</code>


===Ideas===
* '''Weapons''' - Raycasts are the traditional tool used in game development for simulating projectile weapons. They are orders of magnitude more efficient than rezzing a prim and launching it from a weapon.
* Not that you folks need any ideas to get started, but here are a few:
* '''AI Objects''' - Line-of-sight detection of avatars and other objects, or for navigating an environment by tracing rays about themselves. For example; casting rays directly downwards to determine the height and angle (normal) of the current floor surface, useful for non-physical object movement.
# Weapons. I'm personally praying that llCastRay will make simulated projectile weapons essentially obsolete in SL. They're horrible for performance. Ray casts FTW!
* '''Intelligent Object Placement''' - Static objects can be placed in-scene, but adjust themselves to their environment. For example; an object rezzed too high up may adjust its height to floor-level, or a computer console placed low down may cause an avatar to kneel to use it rather than standing.
# AI objects
* '''Environment Analysis''' - Can be used to determine the limitations of a surrounding area, such as determining if an object has been placed within a closed room. Not a test to be performed frequently due to quantity of rays required, but could be used by objects to switch off effects if unobserved (no-one within the room). Auto-adjusting furniture or objects to snap to walls, floors, and ceilings.
# Vehicles. Try simulating the wheels using raycasts. Not sure if LSL will be fast enough, but I'd love for someone to try and report back.
|cat1=Physics
# Lots of other stuff.
|cat2=Light
|history = *Date of Release  [[ Release_Notes/Second_Life_Server/11#11.09.23.241511 | 23/09/2011 ]]
* {{jira|SCR-199}} - fixed - The throttle was too low and thus rendered the function not as useful as it could be.
}}

Latest revision as of 08:48, 5 November 2023

Summary

Function: list llCastRay( vector start, vector end, list options );

Cast a line from start to end and report collision data for intersections with objects.
Returns a list of strided values on a successful hit, with an additional integer status_code at the end.

Each stride consists of two mandatory values {key uuid, vector position} and optionally {integer link_number, vector normal}. (See RC_DATA_FLAGS for details.)

A negative status_code is an error code, otherwise it is the number of hits (and strides) returned.

• vector start starting location
• vector end ending location
• list options can consists of any number of option flags and their parameters.

Example return of successful raycast, using the default options:

[key object_uuid, vector hit_position, integer status_code]

In the case of an error, or if the ray hits nothing, the resulting list only contains the status code:

[integer status_code]

Specification

status_code

status_code is a number tacked onto the end of the strided list to give you extra information about the ray cast.

If the cast succeeded, it will be >=0 and will indicate the number of hits.

If the ray cast failed (which should only happen right now if the simulator performance is running low), you'll get a negative status code. RCERR_SIM_PERF_LOW will be used as the status code if the overall physics time in the simulator is too high to perform raycasts. The idea is that you will know to try your cast again in a few frames.

status_code error codes and their meanings.
Status Code V Description
RCERR_UNKNOWN-1The raycast failed for an unspecified reason. Please submit a bug report.
RCERR_SIM_PERF_LOW-2The raycast failed because simulator performance is low. Wait a while and then try again. If possible reduce the scene complexity.
RCERR_CAST_TIME_EXCEEDED-3The raycast failed because the parcel or agent has exceeded the maximum time allowed for raycasting. This resource pool is continually replenished, so waiting a few frames and retrying is likely to succeed.
RCERR_CAST_TIME_EXCEEDED

Note: SCR-199 indicates that pools have been removed from the main grid, so this return code should not appear.

Tips for Efficient Raycasts:

  • Keep the max number of hits returned as small as possible
  • Set as many RC_REJECT_TYPES as possible (of factors you can control, this will likely have the largest impact). For example, if you only want to know where the nearest agent is along a ray, use RC_REJECT_LAND | RC_REJECT_PHYSICAL | RC_REJECT_NONPHYSICAL
  • When possible, avoid raycasting through piles of prims and avoid raycasting against concave physics objects (anything with cut, hollow, twist, and so on, and any mesh object that has no decomposition and has physics type "prim"). Obviously this can't always be avoided, so some casts may take significantly longer than others. Plan for that with robust scripts that handle RCERR_CAST_TIME_EXCEEDED responsibly, namely by sleeping briefly after the call and waiting for a few frames to go by before trying again.

options parameter

options flags and their parameters
Flag V Parameters Default Value Description
RC_REJECT_TYPES ] 0 integer filter ] [ 0 ] Mask used to ignore specific types of objects (and avatars).
RC_DATA_FLAGS ] 2 integer flags ] [ 0 ] Described in the RC_DATA_FLAGS section.
RC_MAX_HITS ] 3 integer max_hits ] [ 1 ] Maximum number of hits to return. Maximum value is 256. To avoid performance issues, keep it small.
RC_DETECT_PHANTOM ] 1 integer detect_phantom ] [ FALSE ] Set to TRUE (or nonzero) to detect phantom AND volume detect objects. It is not possible to detect only phantom objects or only volume detect objects. If set to TRUE, phantom and volume detect objects will always be detected, even if RC_REJECT_NONPHYSICAL and RC_REJECT_PHYSICAL are set in RC_REJECT_TYPES.
RC_REJECT_TYPES

filter is a bitwise-or combination of the following constants: RC_REJECT_AGENTS, RC_REJECT_PHYSICAL, RC_REJECT_NONPHYSICAL, and RC_REJECT_LAND.

RC_REJECT_TYPES and their meanings.
Reject Type V Description
RC_REJECT_AGENTS1Avatars won't be detected.
RC_REJECT_PHYSICAL2Physical objects won't be detected.
RC_REJECT_NONPHYSICAL4The opposite of the above flag. Objects without physics won't be detected. For phantom objects, see RC_DETECT_PHANTOM.
RC_REJECT_LAND8Land won't be detected. This refers to actual ground only.

If you reject everything, a script runtime error will be generated (as it makes no sense to do this). Using 0 as the filter value will accept all types (default).

Also note that seated agents are treated like unseated agents. As in, you either get seated and unseated agents in your results, or you use RC_REJECT_AGENTS and get neither.

RC_DATA_FLAGS

flags is a bitwise-or combination of: RC_GET_NORMAL, RC_GET_ROOT_KEY, and RC_GET_LINK_NUM.

RC_DATA_FLAGS and their meanings.
Data Flag V Description
RC_GET_NORMAL1Stride includes the surface normal that was hit.
RC_GET_ROOT_KEY2The hit uuid will be replaced by the object's root instead of any child.
RC_GET_LINK_NUM4Stride includes the link number that was hit.

Caveats

  • Depending upon the value of flags (provided via RC_DATA_FLAGS), the number and types of values in the strides will vary. See RC_DATA_FLAGS for details.
  • llGetRot will not return an avatar's exact visual rotation because the viewer doesn't update the avatar's rotation under a threshold (see VWR-1331). To get an avatar's exact looking direction while in mouselook, use llGetCameraRot instead.
  • llCastRay will not detect prims having no physics shape (PRIM_PHYSICS_SHAPE_TYPE = PRIM_PHYSICS_SHAPE_NONE).
  • llCastRay will not detect a prim if the line starts inside the prim. This makes it safe to use the prim position as the start location.
  • llCastRay can detect the prim the script is in, if the start location is outside the prim.
  • The result of this function has been noted to be unreliable when the end point is out-of-bounds (Occasionally returns status code 0 regardless of amount of objects hit). (See this forum post)
    • The random failures seem to happen if the ray begins or ends more than 8 meters outside of current region bounds. Changes in only the ray's angle, or only in its position, may change the result. The result does not change if the exact same ray is cast again.

Examples

This basic example will cast a ray from the center of the object, 10 meters forward, depending on the object's rotation.

default
{
    touch_start(integer total_number)
    {
        vector start = llGetPos();
        vector end = start + <10,0,0> * llGetRot();

        list data = llCastRay(start, end, []);
        llOwnerSay(llList2CSV(data));
    }
}

This is an example attachment that casts a ray based on the owner's camera in mouselook. It has many applications for things like weapons, scripted interactions with the world (like allowing a HUD to display information about things the user is looking at), etc.

integer gTargetChan = -9934917;

default
{
    attach(key id)
    {
        if (id != NULL_KEY)
        { 
            llRequestPermissions(id,PERMISSION_TAKE_CONTROLS|PERMISSION_TRACK_CAMERA);
        }
    }

    run_time_permissions (integer perm)
    {
        if (perm & PERMISSION_TAKE_CONTROLS|PERMISSION_TRACK_CAMERA) 
        {
            llTakeControls(CONTROL_LBUTTON|CONTROL_ML_LBUTTON,TRUE,FALSE);
        }
    }

    control (key id, integer level, integer edge)
    {
        // User must be in mouselook to aim the weapon
        if (level & edge & CONTROL_LBUTTON)
        {
            llSay(0,"You must be in Mouselook to shoot.  Type \"CTRL + M\" or type \"Esc\" and scroll your mouse wheel forward to enter Mouselook.");
        }
        // User IS in mouselook        
        if (level & edge & CONTROL_ML_LBUTTON)
        {
            vector start = llGetCameraPos();
            // Detect only a non-physical, non-phantom object. Report its root prim's UUID.
            list results = llCastRay(start, start+<60.0,0.0,0.0>*llGetCameraRot(),[RC_REJECT_TYPES,RC_REJECT_PHYSICAL|RC_REJECT_AGENTS|RC_REJECT_LAND,RC_DETECT_PHANTOM,FALSE,RC_DATA_FLAGS,RC_GET_ROOT_KEY,RC_MAX_HITS,1]);
            llTriggerSound(llGetInventoryName(INVENTORY_SOUND,0),1.0);
            llSleep(0.03);
            key target = llList2Key(results,0);
            // Tell target that it has been hit. 
            llRegionSayTo(target,gTargetChan,"HIT");
            // Target, scripted to listen on gTargetChan, can explode, change color, fall over .....
        }
    }            
}

This example handles the caveat about rays extending outside of region bounds by calculating the point where the ray intersects with the region's edge.

vector GetRegionEdge(vector start, vector dir)
{
    float scaleGuess;
    float scaleFactor = 4095.99;
    if (dir.x)
    {
        scaleFactor = ((dir.x > 0) * 255.99 -start.x) / dir.x;
    }
    if (dir.y)
    {
        scaleGuess = ((dir.y > 0) * 255.99 - start.y) / dir.y;
        if (scaleGuess < scaleFactor) scaleFactor = scaleGuess;
    }
    if (dir.z)
    {
        scaleGuess = ((dir.z > 0) * 4095.99 - start.z) / dir.z;
        if (scaleGuess < scaleFactor) scaleFactor = scaleGuess;
    }
    return start + dir * scaleFactor;
}

default
{
    touch_start(integer total_number)
    {
        vector start = llGetPos();
        vector direction = <1,0,0> * llGetRot();
        vector end = GetRegionEdge(start, direction);

        list data = llCastRay(start, end, []);
        llOwnerSay(llList2CSV(data));
    }
}

This example casts a ray from the center of the object, 25 meters north, while applying different RC_REJECT_TYPES each time.

integer filter;// default is 0

default
{
    state_entry()
    {
        string ownerName = llKey2Name(llGetOwner());
        llOwnerSay("Hello, " + ownerName + "!");
    }

    touch_start(integer total_number)
    {
        vector start = llGetPos();
        vector end = start - <0.0, -25.0, 0.0>;

        if ( filter > 8 )
            filter = 0;

        llOwnerSay("Filter " + (string)filter);

        list results = llCastRay(start, end, [RC_REJECT_TYPES, filter, RC_MAX_HITS, 4] );

        integer hitNum = 0;
        // Handle error conditions here by checking llList2Integer(results, -1) >= 0
        while (hitNum < llList2Integer(results, -1))
        {
            // Stride is 2 because we didn't request normals or link numbers
            key uuid = llList2Key(results, 2*hitNum);

            string name = "Land"; // if (uuid == NULL_KEY)

            if (uuid != NULL_KEY)
                name = llKey2Name(uuid);

            llOwnerSay("Hit " + name + ".");

            ++hitNum;
        }

        ++filter;
    }
}

Notes

Use llDumpList2String to see what the output looks like when you try a new set of flags.

To quickly get the status code use llList2Integer(result, -1).

Ideas for uses:

  • Weapons - Raycasts are the traditional tool used in game development for simulating projectile weapons. They are orders of magnitude more efficient than rezzing a prim and launching it from a weapon.
  • AI Objects - Line-of-sight detection of avatars and other objects, or for navigating an environment by tracing rays about themselves. For example; casting rays directly downwards to determine the height and angle (normal) of the current floor surface, useful for non-physical object movement.
  • Intelligent Object Placement - Static objects can be placed in-scene, but adjust themselves to their environment. For example; an object rezzed too high up may adjust its height to floor-level, or a computer console placed low down may cause an avatar to kneel to use it rather than standing.
  • Environment Analysis - Can be used to determine the limitations of a surrounding area, such as determining if an object has been placed within a closed room. Not a test to be performed frequently due to quantity of rays required, but could be used by objects to switch off effects if unobserved (no-one within the room). Auto-adjusting furniture or objects to snap to walls, floors, and ceilings.

Deep Notes

History

  • Date of Release 23/09/2011
  • SCR-199 - fixed - The throttle was too low and thus rendered the function not as useful as it could be.

Signature

function list llCastRay( vector start, vector end, list options );