Table of Contents
Movement Along a Line
I had an issue where I wanted an NPC to move toward an object, but not actually trample all over or bump into it. This is particularly important if the NPC is moving toward an agent (an avatar with a hooman behind it). I came up with several solutions which are detailed below.
For almost all of these solutions, you need to know:
-
The starting vector
-
The target vector (of the object you want to move toward)
-
The distance between the two
-
The distance you want to stop short of the target
vector start = llGetPos(); vector target = llList2Vector( llGetObjectDetails( uuid_of_target_object, [OBJECT_POS] ), 0 ); vector direction = target - start; float distance = llVecDist( start, target ); float short = 1.0; // in meters float target_distance = distance - short;
Sphere and Ray Intersection
For this to work, you need to know where the beginning of the line is (the vector where the NPC/Object you want to move is), where the endpoint is (the vector the target object is) and how far short of the target you want to move.
The idea is you imagine a sphere about the start vector, with a radius equal to target_distance. Then you cast a ray from the start vector toward the target vector, then determine where the intersection of the ray and the sphere is.
The SecondLife wiki has some geometric functions in the Geometric Library, and for this we'll use the function gSRxX().
vector gSRxX( start, target_distance, start, direction );
Notes: This code is complex and the other methods are simpler.
Polling Position
This works by sending the object/NPC toward the target and then polling the current position.
// assumes this script is in an NPC attachment, hence use of llGetOwner() to identify NPC vector target; float short; default { state_entry { list data = llGetObjectDetails( uuid_of_target_object, [OBJECT_POS] ); target = llList2Vector( data, 0 ); short = 1.0; // in meters osNpcMoveToTarget( llGetOwner(), target, OS_NPC_NO_FLY ); llSetTimerEvent(0.1); } timer() { vector p = llGetPos(); if( llVecDist( p, target ) <= short ){ llSetTimerEvent(0.0); osNpcStopMoveToTarget( llGetOwner() ); llSetTimerEvent(0.0); } } }
Notes: This is fairly simple code, but inefficient because of the polling. Also, the NPC could stop anywhere between the short distance and the target, and may even overshoot, depending on simulator FPS and script-thread time-dilation.
Starting Position Plus Direction
This is my favourite solution.
integer targetHandle; // llVecNorm( vector direction ) is mathematically equivalent to: // vector norm = vec / llSqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z ); // vector endVector( vector start, vector dir, float distance ){ float z = start.z; vector udir = llVecNorm( dir ); // turn direction into a unit vector vector displace = udir * distance; // scale unit vector vector end_vec = start + displace; end_vec.z = z; // restore elevation, we only want to move in x/y plane return end_vec; } default { state_entry() { vector start = llGetPos(); vector target = llList2Vector( llGetObjectDetails( uuid_of_target_object, [OBJECT_POS] ), 0 ); float short = 1.0; // in meters vector direction = target - start; float distance = llVecDist( start, target ) - short; vector newTarget = endVector( start, direction, distance ); targetHandle = llTarget( newTarget, 0.2 ); osNpcMoveToTarget( llGetOwner(), newTarget, OS_NPC_NO_FLY ); } at_target( integer tnum, vector targetpos, vector curpos){ if( tnum == targetHandle ){ llTargetRemove( targetHandle ); osNpcStopMoveToTarget( llGetOwner() ); } } }
Notes: This is very efficient. It calculates the location once, then waits for the at_target() event to occur. Note there isn't a not_at_target() event. If there were, this would reduce efficiency by continuously triggering not_at_target() events until the target was reached.
Note too, the endVector() only returns a difference in the x/y planes by restoring the start.z value to the end_vec result. If you want your NPC to change elevation, you'll want to change that (and probably change OS_NPC_NO_FLY to OS_NPC_FLY|OS_NPC_LAND_AT_TARGET.
