User:Dora Gustafson/bezier toy

From Second Life Wiki
Jump to navigation Jump to search

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

The menu

New
Will compute new curves from new randomly picked points in space
The points will be picked inside a box with the prim in the center and rotated just like the prim
Do not press New when the prim is moving, if you want any control of the curve
Begin
Begin the prim travel
End
End the travel and reset prim to start position and start rotation
Range
The range in which random points will be picked for the Bézier curves
It is given by 3 coordinates X, Y and Z
The coordinates form a box with the prim in the middle: prim position ±X, ±Y and ±Z
This imaginary box is rotated just like the prim!
In edit mode you can see the prim’s axes when you choose local coordinates (as opposed to world coordinates)
Note that the prim will not stay inside the box on its journey, only the points used to compute the journey are guarantied to be inside
Cycle Time
The time it takes to complete one cycle from start to start
Frames/Curve
The number of keyframes for each Bézier curve
Smoke ON/OFF
Toggles the particle emitter ON/OFF
CurveNumb
The number of Bézier curves from start to start
Kill Script
Will remove the script and the toy can’t be controlled anymore
The toy will continue doing what it did when the script was removed