Wanderer
Note: If you are looking for an autonomous robot system that can roam freely in an enclosed space on x,y,z-axes, you may want to try the extension to Wanderer called: Great Wanderer. If you are looking for revolution trajectories, please try Orbitor. |
Created by Kira Komarov.
Contents
Movement-Related Tree
+-------------------------------+ | Permanent Primitive URL | +---------------+---------------+ | +---------------------+ +--------------+ | | Great Wanderer | | Wanderer | | +---------------------+ +------+-------+ | | | +----------------------------+----------------------------+ | | +------+------+ +-------------------+-------------------+ | Orbitor | | Population Genetics and Selection | +-------------+ +-------------------+-------------------+ | +------------+-------------+ | Interactive Bacteria | +--------------------------+
ChangeLog
- 28 June 2012
Added note on uniformity.
- 15 February 2012
Added orbiting extension Orbitor.
- 29 January 2012
Added note about Great Wanderer, an extension to Wanderer.
- 19 January 2012
Since I'm working on the PGS, I might as well correct this one as I go.
- 15 January 2012
Removed some extra, un-needed computations. FFS!
Introduction
I have noticed that there is frequently a need for a script that will move a primitive around randomly. Initially, I have seen this for animal breeders but later I have also seen robots and similar builds. The inspiration came from birds because I have not seen many bird scripts that move the birds by making them fly higher or lower. They all seem to move in a plane without changing altitude. For that, and other applications, I have made the script below to cover most geometrical areas in which a primitive can move. So far I have included:
- movement in a square.
- movement in a circle.
- movement in a sphere.
- movement in a sphere's upper hemisphere relative to the horizon.
- movement in a sphere's lower hemisphere relative to the horizon.
- movement in an elipsoid.
- movement in an upper hemielipsoid relative to the horizon.
- movement in a lower hemielipsoid relative to the horizon.
Also, I wanted to avoid using up the timer event in the script so it can be used by developers to write their own code using the timer event.
For a concise explanation of how Wanderer is designed, please see Wizardry and Steamworks/Population Genetics and Selection where we put it to some good use.
Uniformity
The trigonometric functions are decent enough to generate the needed points, however they come at the price of having a cluster of points build up near the centre of the circle or sphere.
The overly cited Wolfram method is to change the equations so that the square root of the radius is taken instead:
<math> x = \sqrt{r}\cos{\theta} \\ y = \sqrt{r}\sin{\theta} </math>
However, that is neither proven or explained. In fact, if we do it the Wolfram way, and plot the points, we get an accumulation of points around the bisectrices of the axes:
If you have a keen eye, you can observe that the points are clustered around the green lines. This does not happen with the sampling method.
In order to do it the sampling way: first, the circle is circumscribed within a square and then points are generated within that square. Then, we check to see whether each of the generated points within that square are within the circle and we eliminate those that are not.
This method is called sampling by rejecting the points that fall outside the bounds of the circle or sphere. The drawback using this method is a slight computational impact: if the randomly selected point falls outside the geometric shape, then a new point has to be generated, hopefully this time within the area of that shape.
The way we do this is using a recursive function that first generates the point, then tests if the point is within the bounds of the geometric shape and if it is, it is returned. If it falls outside the geometric shape, then we call the function again to generate a new point.
Circle
vector circlePoint(float radius) { float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) return <x, y, 0>; return circlePoint(radius); }
For an uniform Wanderer within a circle, we replace the nextCoordinates function with the circlePoint function.
Sphere
vector spherePoint(float radius) { float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); float z = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); if(llPow(x,2) + llPow(y,2) + llPow(z,2) <= llPow(radius,2)) return <x, y, z>; return spherePoint(radius); }
For an uniform Wanderer within a sphere, we replace the nextCoordinates function with the circlePoint function.
Requirements
The primitive this script resides in sets itself to physical. However, it is suggested that phantom should also be turned on to avoid collisions.
Applications
There are several applications I can think of based on the introduction:
- can be used for breeders to move animals around.
- can be used by robots to move around randomly.
- can be used to create birds that move around.
Setup
Configure the script by setting the parameters described in the header:
integer MOVEMENT_TYPE = 4; float MOVEMENT_RANGE = 5.0; float TARGET_AFFINITY = 0.8; float TARGET_DAMPENING = 3.0;
they are all documented in the script. After that, save the script and drop it in a primitive you wish to wander about.
Code
////////////////////////////////////////////////////////// // [K] Kira Komarov - 2011, License: GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // ////////////////////////////////////////////////////////// // This defines the movement type that the // primitive will have. The primitive will // generate coordinates within the following // types of geometric areas. // // Replace the MOVEMENT_TYPE below with one // of the following numbers representing: // 2 for a square. // 4 for a circle. // 8 for a sphere. // 16 for an upper hemisphere. // 32 for a lower hemisphere. // 64 for an elipsoid. // 128 for an upper hemi-elipsoid. // 256 for a lower hemi-elipsoid. integer MOVEMENT_TYPE = 32; // Maximum distance in meters from starting // coordinates that the primitive will be // allowed to reach. float MOVEMENT_RANGE = 5.0; // The minimum distance in meters when the // primitive will consider that it has reached // its next destination. float TARGET_AFFINITY = 0.8; // How fast will the primitive try to reach // the next coordinate. float TARGET_DAMPENING = 3.0; // Returns the next random coordinates // depending on the type of shape selected. // // IN: integer representing shape. // OUT: vector containing the coordinates // relative to the initial starting position. vector nextCoordinates(integer TYPE) { float driftRange = llFrand(MOVEMENT_RANGE); float a = llFrand(TWO_PI); float b = llFrand(TWO_PI); float c = llFrand(PI); if(TYPE == 2) return <iPos.x + driftRange, iPos.y + llFrand(MOVEMENT_RANGE), iPos.z>; if(TYPE == 4) return <iPos.x + driftRange * llCos(a), iPos.y + driftRange * llSin(b), iPos.z>; if(TYPE == 8) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), driftRange * llSin(a)>; if(TYPE == 16) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), driftRange * llSin(c)>; if(TYPE == 32) return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), -driftRange * llSin(c)>; if(TYPE == 64) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), driftRange * llSin(a)>; if(TYPE == 128) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), driftRange * llSin(c)>; if(TYPE == 256) return iPos + <driftRange * llCos(a) * llCos(b), llFrand(MOVEMENT_RANGE) * llCos(a) * llSin(b), -driftRange * llSin(c)>; return iPos; } //vector nextCoordinates(integer MOVEMENT_TYPE) { // float driftRange = llFrand(10); // float a = llFrand(TWO_PI); // float b = llFrand(TWO_PI); // return iPos + <driftRange * llCos(a) * llCos(b), driftRange * llCos(a) * llSin(b), driftRange * llSin(a)>; //} // Orientates the primitive's positive z axis // towards a position and moves the primitive // towards that position. // // IN: vector representing a position in region // coordinates. // OUT: nothing. moveTo(vector position) { llTargetRemove(targetID); targetID = llTarget(position, TARGET_AFFINITY); llLookAt(position, 0.6, 0.6); llMoveToTarget(position, TARGET_DAMPENING); } // Vector that will be filled by the script with // the initial starting position in region coordinates. vector iPos; // Integer that the script will use to detect // the next target position it will reach. integer targetID; // Begin script. default { state_entry() { iPos = llGetPos(); llSetStatus(STATUS_PHYSICS, TRUE); llSetForce(<0,0,9.81> * llGetMass(), 0); moveTo(nextCoordinates(MOVEMENT_TYPE)); } at_target(integer tnum, vector targetpos, vector ourpos) { if(tnum != targetID) return; moveTo(nextCoordinates(MOVEMENT_TYPE)); } } // End script.