////////////////////////////////////////////////////////////////////////////////
   // 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;
   }
   }
   }