User:Dora Gustafson/bezier toy
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
Bézier Toy
- Strictly for fun!
- The toy is made of 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>