User:Dora Gustafson/bezier toy

From Second Life Wiki
< User:Dora Gustafson
Revision as of 03:58, 12 June 2014 by Dora Gustafson (talk | contribs) (Bézier Toy script)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Bézier Toy

Strictly for fun!
The toy is made by one prim and a script.
The prim will move along a smooth closed curve in space until it is stopped.
The prim can be sat on and will then move you as a rollercoaster.
Experimenting with the: camera distance, camera angle and camera in mouselook is great fun.
Making a smoke tail is optional.
The curve travelled
The curve is made from a number of Bézier curves computed in the script.
The curves are put together seamlessly.
The points used for the curves are picked at random inside a box with editable size.
New curves are computed each time The button New in the dialog menu is pressed.
KeyFramed Motion
The Second Life technique used is called: KeyFramed Motion
This makes it possible to let the toy run forever without a script.
Just start the toy and remove the script with the Kill Script button in the dialog menu

<lsl> // Bezier Key Framed Motion script by Dora Gustafson, Studio Dora 2012 // Build on Cubic(3.order) Bezier chain by Dora Gustafson, Studio Dora 2008 // v1.01 Key Framed Motion 2012 // v1.02 introducing KFM // v1.03 KFM alone // v1.04 Appending more seamless bezier curves // v1.05 Box limited randomP // v1.06 Smoke on/off and script erase // v1.07 Parameter menu // v1.08 Particle size = f(prim scale). Range separate for X, Y and Z // v1.09 Same randomP as in: "Bezier Spot Light Key Framed Motion". No more rangeOffset // ... randomP box limits rotated as prim

float Tmotion = 8.0; // seconds vector range = < 5.0, 5.0, 5.0 >; // ± half range for each coordinate in meters rotation refFrame=<-0.5, -0.5, -0.5, 0.5>; // points Z forward string toolDialog = "\nNew, Compute new points\nBegin Key Framed Motion\nEnd Key Framed Motion\nSmoke on or off\nKill Script to remove script from object\n...a Studio Dora product"; list TOOL_MENU = [ "CurveNumb", "Kill Script", "Range", "Cycle Time", "Frames/Curve", "New", "Begin", "End" ]; integer dialogChannel; integer paramChannel; integer paramHandl; integer frames=16; vector Po0; vector Po1; vector P0; vector P1; vector P2; vector P3; vector Q1; vector Q2; vector Q3; vector LEFT=< 0.0, 1.0, 0.0 >; list KFMlist=[]; vector b1; vector b2; rotation r1; rotation r2; integer curvNumb=3; integer smoke=FALSE; string partTextur; rotation homeRot;

MakeParticles() {

   vector vsz = llGetScale();
   float sc = 0.5*(vsz.x+vsz.y);
   vsz = < sc, sc, 0.0 >;
   llParticleSystem([
   PSYS_PART_FLAGS,
   PSYS_PART_INTERP_COLOR_MASK       //Colors fade from start to end
   | PSYS_PART_INTERP_SCALE_MASK       //Scale fades from beginning to end
   ,PSYS_SRC_PATTERN,           PSYS_SRC_PATTERN_ANGLE_CONE
   ,PSYS_SRC_TEXTURE,           partTextur         //UUID of the desired particle texture, or inventory name
   ,PSYS_SRC_MAX_AGE,           0.0                //Time, in seconds, for particles to be emitted. 0 = forever
   ,PSYS_PART_MAX_AGE,          60.0               //Lifetime, in seconds, that a particle lasts
   ,PSYS_SRC_BURST_RATE,        0.02               //How long, in seconds, between each emission
   ,PSYS_SRC_BURST_PART_COUNT,  1                  //Number of particles per emission
   ,PSYS_SRC_BURST_RADIUS,      0.0                //Radius of emission
   ,PSYS_SRC_BURST_SPEED_MIN,   0.0                //Minimum speed of an emitted particle
   ,PSYS_SRC_BURST_SPEED_MAX,   0.0                //Maximum speed of an emitted particle
   ,PSYS_SRC_ACCEL,             <0.0,0.0,0.0>      //Acceleration of particles each second
   ,PSYS_PART_START_COLOR,      <1.0,1.0,1.0>      //Starting RGB color
   ,PSYS_PART_END_COLOR,        <1.0,1.0,1.0>      //Ending RGB color, if INTERP_COLOR_MASK is on
   ,PSYS_PART_START_ALPHA,      1.0                //Starting transparency, 1 is opaque, 0 is transparent.
   ,PSYS_PART_END_ALPHA,        0.0                //Ending transparency
   ,PSYS_PART_START_SCALE,      vsz                //Starting particle size
   ,PSYS_PART_END_SCALE,        1.5*vsz            //Ending particle size, if INTERP_SCALE_MASK is on
   ,PSYS_SRC_ANGLE_BEGIN,       3.1415             //Inner angle for ANGLE patterns
   ,PSYS_SRC_ANGLE_END,         3.1415             //Outer angle for ANGLE patterns
   ,PSYS_SRC_OMEGA,             <0.0,0.0,0.0>      //Rotation of ANGLE patterns, similar to llTargetOmega()
   ]);

}

openBox( integer n, string menuText) {

   paramChannel = dialogChannel + n;
   llListenRemove( paramHandl);
   paramHandl = llListen( paramChannel, "", llGetOwner(), "");
   llTextBox( llGetOwner(), menuText, paramChannel);

}

vector randomP() {

   return llGetPos() + < range.x*(llFrand( 2.0)-1.0), range.y*(llFrand( 2.0)-1.0), range.z*(llFrand( 2.0)-1.0) > * homeRot;

}

rotation Vec2Rot( vector FWD ) {

   FWD = llVecNorm( FWD );
   vector UP = < 0.0, 0.0, 1.0 >;
   if ( llFabs(FWD.z) < 1.0 ) LEFT = llVecNorm(UP%FWD);
   UP = llVecNorm(FWD%LEFT);
   return refFrame*llAxes2Rot(FWD, LEFT, UP);

}

tDialog() {

   string menuD = llGetScriptName( )+"\nMemory in use: "+(string)llGetUsedMemory();
   menuD += "\n\nRange = ±"+(string)range+" meters";
   menuD += "\nCycle time = "+(string)Tmotion+" Seconds";
   menuD += "\nFrames per curve = "+(string)frames;
   menuD += "\nNumber of curves = "+(string)curvNumb;
   list T_MENU = ["Smoke ON"];
   if (smoke) T_MENU = ["Smoke OFF"];
   llDialog( llGetOwner(), menuD + toolDialog, T_MENU + TOOL_MENU, dialogChannel);

}

integer random_channel() {// mask out 4 LSBits for other uses

   return ((128 * (integer)llFrand( 8388608.0 )) + (integer)llFrand( 8388608.0 ) + 0x80000000) & 0xFFFFFFF0;

}

coefisFirst() {

   P0 = llGetPos();
   Po0 = P0;
   P3 = randomP();
   P2 = randomP();
   P1 = randomP();
   Po1 = P1;
   Q1 = 3.0*P1-3.0*P0;
   Q2 = 3.0*P0-6.0*P1+3.0*P2;
   Q3 = 3.0*P1-P0+P3-3.0*P2;

}

coefisNext() {

   P0 = P3;
   P1 = 2.0*P3-P2;
   P3 = randomP();
   P2 = randomP();
   Q1 = 3.0*P1-3.0*P0;
   Q2 = 3.0*P0-6.0*P1+3.0*P2;
   Q3 = 3.0*P1-P0+P3-3.0*P2;

}

coefisLast() {

   P0 = P3;
   P1 = 2.0*P3-P2;
   P2 = 2.0*Po0-Po1;
   P3 = Po0;
   Q1 = 3.0*P1-3.0*P0;
   Q2 = 3.0*P0-6.0*P1+3.0*P2;
   Q3 = 3.0*P1-P0+P3-3.0*P2;

}

vector Bez( float x ) { return P0 + (Q1 + (Q2 + Q3*x)*x)*x; }

vector dBez( float x ) { return Q1 + (2.0*Q2 + 3.0*Q3*x)*x; }

vector sBez( float x ) { return 2.0*Q2 + 6.0*Q3*x; }

default {

   state_entry()
   {
       llSetPrimitiveParams([PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
       dialogChannel = random_channel();
       llListen( dialogChannel, "", llGetOwner(), "");
       refFrame.s = -refFrame.s;
       string textureName = llGetInventoryName(INVENTORY_TEXTURE, 0 );
       if ( llGetInventoryType( textureName ) == INVENTORY_TEXTURE ) partTextur = textureName;  // use extern, inventory texture
       else partTextur = "";
   }
   touch_end(integer n)
   {
       if ( llDetectedKey(0) == llGetOwner() )
       {
           if ( llGetListLength( KFMlist) > 5 ) tDialog();
           else llDialog( llGetOwner(), llGetScriptName( )+"\n\nMust make a new", ["New"], dialogChannel);
       }
   }
   listen(integer channel, string name, key id, string message)
   {
       if ( message == "CurveNumb" ) openBox( 1, "Enter number of appended Bezier curves= 2,3,4,...,18");
       else if ( message == "Range" ) openBox( 2, "Specify ±range relative to curent position\nCube: [X range, Y range, Z range]");
       else if ( message == "Cycle Time" ) openBox( 3, "Time in seconds for one cycle");
       else if ( message == "Frames/Curve" ) openBox( 4, "Number of key frames per Bezier curve [2;100]");
       else if ( message == "Kill Script" ) llRemoveInventory( llGetScriptName());
       else
       {
           if ( message == "New" )
           {
               homeRot = llGetRot();
               float liN = (float)frames;
               float dT = Tmotion/liN/(float)curvNumb;
               dT = llRound(45.0*dT)/45.0;
               if ( dT < 0.11111111 ) dT = 0.11111111;
               KFMlist=[];
               coefisFirst();
               b1 = Bez(0);
               r1 = Vec2Rot( dBez(0));
               integer i;
               for ( i=1; i<=frames; i++ )
               {
                   b2 = Bez( i/liN); r2 = Vec2Rot(dBez( i/liN));
                   KFMlist += [ b2-b1, r2/r1, dT];
                   b1 = b2; r1 = r2;
               }
               integer j = curvNumb;
               while ( j-- > 2 )
               {
                   coefisNext();
                   for ( i=1; i<=frames; i++ )
                   {
                       b2 = Bez( i/liN); r2 = Vec2Rot(dBez( i/liN));
                       KFMlist += [ b2-b1, r2/r1, dT];
                       b1 = b2; r1 = r2;
                   }
               }
               coefisLast();
               for ( i=1; i<=frames; i++ )
               {
                   b2 = Bez( i/liN); r2 = Vec2Rot(dBez( i/liN));
                   KFMlist += [ b2-b1, r2/r1, dT];
                   b1 = b2; r1 = r2;
               }
           }
           else if ( message == "End" )
           {
               llSetKeyframedMotion( [], []);
               llSleep(0.2);
               llSetRegionPos( Po0);
               llSetPrimitiveParams([PRIM_POSITION, Po0, PRIM_ROTATION, homeRot]);
           }
           else if ( message == "Begin" && llGetListLength( KFMlist))
           {
               llSetKeyframedMotion( [], []);
               llSleep(0.2);
               llSetRegionPos( Po0);
               llSetPrimitiveParams([PRIM_POSITION, Po0, PRIM_ROTATION, Vec2Rot( Po1-Po0)]);
               llSetKeyframedMotion( KFMlist, [KFM_MODE, KFM_LOOP]);
           }
           else if ( message == "Smoke ON" ) { MakeParticles(); smoke = TRUE; }
           else if ( message == "Smoke OFF" ) { llParticleSystem([]); smoke = FALSE; }
           else if ( channel == dialogChannel + 1 )
           {
               curvNumb = (integer)message;
               if ( curvNumb < 2 || curvNumb > 18 ) curvNumb = 2;
           }
           else if ( channel == dialogChannel + 2 )
           {
               list L = llCSV2List( message);
               if ( llGetListLength(L) == 3 )
               {
                   range.x = llList2Float( L, 0);
                   range.y = llList2Float( L, 1);
                   range.z = llList2Float( L, 2);
               }
               else if ( llGetListLength(L) == 1 ) range = (vector)llList2String( L, 0);
               else if ( range.x<=0.0 || range.y<=0.0 || range.z<=0.0 ) llOwnerSay("Range must be entered as a vector with positive elements\nor as three comma separated positive numbers");
           }
           else if ( channel == dialogChannel + 3 )
           {
               Tmotion = (float)message;
               if ( Tmotion < 1.0 ) Tmotion = 1.0;
           }
           else if ( channel == dialogChannel + 4 )
           {
               frames = (integer)message;
               if ( frames < 2 || frames > 100 ) frames = 16;
           }
           llListenRemove( paramHandl );
           tDialog();
       }
   }
   on_rez(integer param) { llResetScript(); }

} </lsl>