////////////////////////////////////////////////////////////////////////////////
// LoopRez v0.6-01, written by Ged Larsen, 20 December 2006 (Thanks Ged!!!)
// Adapted by Blu Laszlo, June 18, 2007
//
// - rez a number of objects in an ellipse, whose size is determined by xRadius and yRadius
// - all facing "outwards", along a tangent to the ellipse
// - can set how much the objects flare outwards
// - properly handles object rotation corrections (for example, X-axis 180 degree rotation helps for flexi-prim skirts)
// - can also get a 'saddle' bend, to generate a bend that might help for necklaces (from Ariane Brodie)
//
// To use:
// 1) Create a prim
// 2) Put this script into the prim (it communicates on channel 100)
// 2) Put a skirt panel into the prim (any name, the script will use the first object it encounters)
// 3) Say" "/100 make" to make the skirt
// 4) When finished, say: "/100 Done" - this will remove the script from the prim
//
// Note:
// - this version does NOT insure equal spacing of objects along the perimeter of the ellipse
// - i.e., objects "bunch up" at the long ends of an ellipse; the effect should be acceptable for non-extreme ellipses
// - even spacing of objects can be accomplished, but requires simulation of integral calculus, which slows down the script
//
// References:
// - tangent formulas from: http://mathworld.wolfram.com/Ellipse.html
// notecard configulation as follows:
//
// numObjects | 20
// xRadius | 0.2
// yRadius | 0.4
// flareAngle | 45.0
// bendCoefficient | 0.0 // decided not to implement
//
////////////////////////////////////////////////////////////////////////////////
// DEFAULT CONFIGURATION PARAMETERS, these will be used if no notecard is found
string objectName = "object"; // object to use; will need to be in the inventory of the prim containing this script
integer numObjects = 20; // how many objects
float xRadius = .16; // ellipse x-axis radius in meters
float yRadius = .22; // ellipse y-axis radius in meters
float flareAngle = 45.0; // how many DEGREES the bottom of object will flare outwards, the "poof" factor
float bendCoefficient = 0.0; // makes a "saddle shape", bends DOWN this number of meters at extremes of X-axis
vector rotOffset = <0.0, 180.0, 0.0>; // rotation offset in DEGREES -- fixes the rotation of ALL objects; for flexi prims, often you will want <180.0, 0.0, 0.0>
vector posOffset = <0.0, 0.0, 1.0>; // position offset
////////////////////////////////////////////////////////////////////////////////
// No need to mess with anything below this line
// global variables
integer listen_handle;
integer channel = 100; // listen on this channel
string NotecardName;
key dataserver_key = NULL_KEY; // key of notecard
integer nLine = 0;
vector ObjectPosition;
integer SKIRTMADE = FALSE;
integer SKIRTMOVED = FALSE;
integer SKIRTLINKED = FALSE;
list SkirtPanelList = [];
//---------------------------------------------------------------------------------------------
makeLoop()
{
integer n; // which object is being placed
float theta; // angle in radians
vector pos; // position
rotation rot; // rotation in quaternion format
for(n = 0; n < numObjects; n++) {
theta = TWO_PI * ( (float)n / (float)numObjects );
pos.x = xRadius * llCos(theta); // ellipse: 2x xRadius meters wide
pos.y = yRadius * llSin(theta); // ellipse: 2x yRadius meters wide
pos.z = -bendCoefficient*llCos(theta)*llCos(theta); // saddle shape, bending downwards on X-axis
pos = pos + llGetPos() + posOffset;
rot = llEuler2Rot(<rotOffset.x*DEG_TO_RAD, rotOffset.y*DEG_TO_RAD, rotOffset.z*DEG_TO_RAD>); // user-chosen rotation offset correction
rot = rot * llEuler2Rot(<0, -1*flareAngle*DEG_TO_RAD, 0>); // flare generation (poof)
// the following make the objects face outwards properly for an ellipse; using theta alone is only correct for a circle
// the scary formula calculates a unit vector TANGENTIAL to the ellipse, and llRotBetween is used to figure out how much the object needs to rotate to lie parallel to the tangent
rot = rot * llRotBetween(<0.0,1.0,0.0>, <-1.0 * xRadius * llSin(theta) / ( llSqrt ( (yRadius*yRadius * llCos(theta) * llCos(theta)) + (xRadius*xRadius * llSin(theta) * llSin(theta))) ),yRadius * llCos(theta) / ( llSqrt ( (yRadius*yRadius * llCos(theta) * llCos(theta)) + (xRadius*xRadius * llSin(theta) * llSin(theta))) ),0.0>);
if ( n== (numObjects/2) ) // LSL's implementation of llRotBetween at theta = pi radians is reversed at 180 degrees, so this manually corrects it
rot = rot * llEuler2Rot( <0,PI,0> );
llRezObject(objectName, pos, ZERO_VECTOR, rot, 0);
}
}
// ------------------------------------------------------------------------------------------------
integer read_notecard()
{
if (llGetInventoryNumber(INVENTORY_NOTECARD) == 0)
{
llOwnerSay ("No configuration notecard was found. ");
return (FALSE);
}
llOwnerSay ("reading notecard ...");
NotecardName = llGetInventoryName (INVENTORY_NOTECARD, 0); // name of first notecard to be found
llOwnerSay ("Using notecard: " + NotecardName);
dataserver_key = llGetNotecardLine(NotecardName, 0);
return (TRUE);
}
next_line()
{
nLine++;
dataserver_key = llGetNotecardLine(NotecardName, nLine);
}
integer read_panelname()
{
integer nObjects = llGetInventoryNumber(INVENTORY_OBJECT); // number of objects in the prim
if (nObjects == 0)
{
llOwnerSay ("There are no skirt panels in the inventory...... ");
// to do: add some error handling code here
return (FALSE);
}
else if (nObjects > 1)
{
llOwnerSay ( "There are " + (string) nObjects + " objects in the prim. Only one is allowed");
// as the script is written, it will use the first object encountered in the inventory
// to do: put some error handling code here - go to a sleep state until touched?
}
objectName = llGetInventoryName(INVENTORY_OBJECT, 0);
llOwnerSay ( "The name of the SKIRT PANEL to be used is: " + objectName );
return (TRUE);
}
default
{
state_entry()
{
listen_handle = llListen(channel, "",llGetOwner(), "");
if ( ! read_notecard () )
state fix_notecard;
if ( ! read_panelname() )
state fix_object;
// ask for permissions to link
llRequestPermissions( llGetOwner(), PERMISSION_CHANGE_LINKS );
}
// now wait for a voice command, listens only to the owner
// what to do when the listen event is triggered
listen( integer channel, string name, key id, string message )
{
if (llGetOwnerKey(id)==llGetOwner())
{
list parsed = llParseString2List( message, [ " " ], [] );
// get the first part--the "command".
string command = llList2String( parsed, 0 );
if( command == "reset" )
{
llOwnerSay("Resetting .... ");
llResetScript();
}
if( command == "make" )
{
if ( !SKIRTMADE )
{
llOwnerSay("Making the skirt .... ");
makeLoop();
SKIRTMADE = TRUE;
}
else
{
llOwnerSay ("The skirt is already made");
if (!SKIRTMOVED)
llOwnerSay ("Move the box into the waist opening by saying '/100 move' ");
if (!SKIRTLINKED)
llOwnerSay ("Link the skirt by saying '/100 link' ");
}
}
if( command == "move" )
{
if ( SKIRTMADE && !SKIRTMOVED)
{
llSetScale( <0.05, 0.05, 0.05> );
llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_CYLINDER, 0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>]);
llSetPrimitiveParams([PRIM_TEXTURE, ALL_SIDES, "94e3d030-cca5-72ac-7d5b-66cc44d1ca00", <1,1,1>, <0,0,0>, 0]);
ObjectPosition = llGetPos();
ObjectPosition.z = ObjectPosition.z + posOffset.z + 0.5;
llSetPos(ObjectPosition);
llOwnerSay("Ready to link!");
SKIRTMOVED = TRUE;
}
else
{
if (!SKIRTMADE)
llOwnerSay ("Make the skirt first by saying '/100 make'" );
if (SKIRTMOVED)
llOwnerSay ("The 'move' command was used already. now link the skirt by saying '/100 link' ");
}
}
if( command == "link" )
{
if (SKIRTMADE && SKIRTMOVED && !SKIRTLINKED)
{
integer i;
for (i=0; i < numObjects; i++)
{
llCreateLink( llList2Key (SkirtPanelList, i) , TRUE );
llOwnerSay ( "Panel " + (string) (i+1) + " linked");
}
llOwnerSay ( "Linking complete. Now finish off by saying: '/100 done' ");
SKIRTLINKED = TRUE;
}
else
{
if (!SKIRTMADE)
llOwnerSay ("Make the skirt first by saying '/100 make' ");
if (!SKIRTMOVED)
llOwnerSay ("Move the box into the waist opening by saying '/100 move' ");
if (SKIRTLINKED)
llOwnerSay ("Already linked - finish by saying '/100 done' ");
}
}
if( command == "done" )
{
llSetObjectName("Flexi Skirt");
llOwnerSay("All done! ");
llRemoveInventory(llGetScriptName( ));
}
}
}
// event that is called on every llRezObject (in the make routine)
// read skirt panel keys into a list
object_rez(key id)
{
SkirtPanelList += id;
//llOwnerSay ("Panel key is: " + (string) id );
}
dataserver(key queryid, string data)
{
if(queryid != dataserver_key) // not correct call
return;
if(data != EOF)
{
if(llGetSubString(data,0,1) != "/")
{
if(llGetSubString(data,0,9) == "numObjects")
{
numObjects = (integer) llGetSubString(data,llSubStringIndex (data, "|")+1,-1);
llOwnerSay( "numObjects is: " + (string) numObjects );
next_line();
return;
}
if(llGetSubString(data,0,6) == "xRadius")
{
xRadius = (float) llGetSubString(data,llSubStringIndex (data, "|")+1,-1);
llOwnerSay( "xRadius is: " + (string) xRadius );
next_line();
return;
}
if(llGetSubString(data,0,6) == "yRadius")
{
yRadius = (float) llGetSubString(data,llSubStringIndex (data, "|")+1,-1);
llOwnerSay( "yRadius is: " + (string) yRadius );
next_line();
return;
}
if(llGetSubString(data,0,9) == "flareAngle")
{
flareAngle = (float) llGetSubString(data,llSubStringIndex (data, "|")+1,-1);
llOwnerSay( "flareAngle is: " + (string) flareAngle );
next_line();
return;
}
next_line();
}
}
}
}
state fix_object
{
state_entry()
{
llOwnerSay ("Please add a SKIRT PANEL object in the Contents tab and touch the box to continue");
}
touch_start(integer total_number)
{
state default;
}
}
state fix_notecard
{
state_entry()
{
llOwnerSay ("Please add a configuration notecard in the Contents tab and touch the box to continue");
}
touch_start(integer total_number)
{
state default;
}
}
}