Difference between revisions of "User:Tapple Gao/3D Spinning Pendulum Motion"

From Second Life Wiki
Jump to navigation Jump to search
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
<div id="box">
== 3D Spinning Pendulum Motion ==
Generalized undamped pendulum motion in 3 dimensions. Can swing in simple arcs, circles, elipses, or lissajous curves, and spin at the same time. <br>
Generalized undamped pendulum motion in 3 dimensions. Can swing in simple arcs, circles, elipses, or lissajous curves, and spin at the same time. <br>
Suitable for regular pendulums, swings, spinning tire swings, spinning tops, bobbleheads, etc. <br>
Suitable for regular pendulums, swings, spinning tire swings, spinning tops, bobbleheads, rocking boats, etc. <br>
Script has a menu of demo motions


Two scripts are included on this page. The first one toggles the swinging on or off on touch. The second script presents a menu of various motions on touch. I recommend starting with the second, demo script, to see what motions it's capable of, then copying the configuration you like to the touch on/off script and tweaking it to suit your needs
<div id="box">
== 3D Pendulum 1.4 touch on/off ==
<source lang="lsl2">
<source lang="lsl2">
// Spinning pendulum script 1.2 by Tapple Gao
// 3D pendulum touch on/off script 1.4 by Tapple Gao
// http://wiki.secondlife.com/wiki/User:Tapple_Gao/3D_Spinning_Pendulum_Motion
// http://wiki.secondlife.com/wiki/User:Tapple_Gao/3D_Spinning_Pendulum_Motion


// Change these to configure the swing
// speed up or slow down all animations:
vector spinAxis = <0,0,1>;      // axis to spin on
float speed = 1.0; // Speed Multiplier. 2.0 for double speed, 0.5 for half speed
vector majorSwingAxis = <0,1,0>; // axis to swing about
 
float length = 8.0// how long is the animation, in seconds
tireSwingMotion() { // Eliptical swing; spins slower than it swings
integer animFrameCount = 40; // how many frames is the animation? more is smoother
    startPendulum(
integer majorSwingDegrees = 40// How far the swing moves, forward to back
    <0,0,1>, // spinAxis. axis to spin on
integer minorSwingDegrees = 20// How far from the vertical the swing moves, side to side
    <0,1,0>, // majorSwingAxis. axis to swing about
integer majorSwingCount = -2; // How many times I swing forward to back
    8.0, // animation length, in seconds
integer minorSwingCount = -2; // How many times I swing side to side. Usually set to the same as majorSwingCount
    5.0, // frames per second. Must be between 0.0 and 9.0
integer phaseDegrees = 90; // timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    40*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
integer spinCount = 1; // How many times the tire swing spins per animation
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    -2, // majorSwingCount. How many times I swing forward to back
    -2, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    1); // spinCount. How many times the tire swing spins per animation
}


// End of user configuration
// End of user configuration


/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
///////////// Spinning Pendulum Library /////////////////
///////////////// 3D Pendulum Library ///////////////////
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////


Line 36: Line 42:


stopPendulum() {
stopPendulum() {
    llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
     if (!isSwinging) return;
     if (!isSwinging) return;


     llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
     llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(), [PRIM_ROT_LOCAL, savedRot]);
    llSetLinkPrimitiveParamsFast(LINK_ROOT, [PRIM_ROT_LOCAL, savedRot]);
     isSwinging = FALSE;
     isSwinging = FALSE;
}
}
Line 47: Line 53:
// majorSwingAxis: The primary axis to swing about, in prim-local coordinates. must be nonzero
// majorSwingAxis: The primary axis to swing about, in prim-local coordinates. must be nonzero
// length: The length of the animation, in seconds. Must be positive
// length: The length of the animation, in seconds. Must be positive
// frameCount: The total number of frames that will be computed. Higher is smoother, but more memory intensive. Must be positive
// frameRate: The smoothness of the animation, in frames per second. Must be between 0.0 and 9.0
// majorSwingAngle: The angle that the prim swings around the majorSwingAxis, in radians. can be positive, negative, or zero
// majorSwingAngle: The angle that the prim swings around the majorSwingAxis, in radians. can be positive, negative, or zero
// minorSwingAngle: The angle that the prim swings around the minor swing axis, in radians. minor swing axis is perpendicular to majorSwingAxis. can be positive, negative, or zero
// minorSwingAngle: The angle that the prim swings around the minor swing axis, in radians. minor swing axis is perpendicular to majorSwingAxis. can be positive, negative, or zero
Line 54: Line 60:
// phase: timing difference between major and minor swinging, in radians. Set this to PI_BY_TWO for eliptical motion
// phase: timing difference between major and minor swinging, in radians. Set this to PI_BY_TWO for eliptical motion
// spinCount: Number of times to spin during the animation. Can be positive, negative, or zero. If zero, the prim will swing only, without spinning. if negative, spin the other way
// spinCount: Number of times to spin during the animation. Can be positive, negative, or zero. If zero, the prim will swing only, without spinning. if negative, spin the other way
spinningPendulum(vector spinAxis, vector majorSwingAxis, float length, integer frameCount, float majorSwingAngle, float minorSwingAngle, integer majorSwingCount, integer minorSwingCount, float phase, integer spinCount) {
startPendulum(vector spinAxis, vector majorSwingAxis, float length, float frameRate, float majorSwingAngle, float minorSwingAngle, integer majorSwingCount, integer minorSwingCount, float phase, integer spinCount) {
     stopPendulum();
     stopPendulum();
     savedRot = llGetRot();
     savedRot = llGetRootRotation();
     isSwinging=TRUE;
     isSwinging=TRUE;


Line 62: Line 68:
     majorSwingAxis = minorSwingAxis % spinAxis;
     majorSwingAxis = minorSwingAxis % spinAxis;
     rotation currentRot;
     rotation currentRot;
    length /= speed;
    integer frameCount = (integer)(length * frameRate);
     float frameTime = length / frameCount;
     float frameTime = length / frameCount;
     list frames = [];
     list frames = [];
Line 70: Line 79:
             llAxisAngle2Rot(minorSwingAxis, llCos(frame * TWO_PI / frameCount * minorSwingCount - phase) * minorSwingAngle);
             llAxisAngle2Rot(minorSwingAxis, llCos(frame * TWO_PI / frameCount * minorSwingCount - phase) * minorSwingAngle);
         //llSetRot(nextRot * savedRot); llSleep(0.1);
         //llSetRot(nextRot * savedRot); llSleep(0.1);
         if (frame == 0) llSetLinkPrimitiveParamsFast(LINK_ROOT,
         if (frame == 0) llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(),
             [PRIM_ROT_LOCAL, nextRot*savedRot]);
             [PRIM_ROT_LOCAL, nextRot*savedRot]);
         else frames += [NormRot(nextRot / currentRot), frameTime];
         else frames += [NormRot(nextRot / currentRot), frameTime];
Line 83: Line 92:
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////


default {
    state_entry() {
        llSetTouchText("Swing");
    }
    touch_start(integer num) {
        if(isSwinging) {
            stopPendulum();
            llSetTouchText("Swing");
        } else {
            llSetTouchText("Stop swing");
            tireSwingMotion();
        }
    }
    changed(integer change) {
        if(change & CHANGED_LINK) {
            stopPendulum();
            llSetTouchText("Swing");
        }
    }
}</source>
</div>
Demo script, demonstrating a variety of motions the script can produce. The configuration used in the touch on/off script above is the tire swing configuration in the demo script
<div id="box">
== 3D Pendulum 1.4 demo ==
<source lang="lsl2">
// 3D pendulum demo script 1.4 by Tapple Gao
// http://wiki.secondlife.com/wiki/User:Tapple_Gao/3D_Spinning_Pendulum_Motion
// speed up or slow down all animations:
float speed = 1.0; // Speed Multiplier. 2.0 for double speed, 0.5 for half speed
swingMotion() { // simple 2D swinging motion. No side to side motion, no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    4.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    50*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    0*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    1, // majorSwingCount. How many times I swing forward to back
    0, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
tireSwingMotion() { // Eliptical swing; spins slower than it swings
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    8.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    40*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    -2, // majorSwingCount. How many times I swing forward to back
    -2, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    1); // spinCount. How many times the tire swing spins per animation
}
tangledSwingMotion() { // Changes the axes to swing on the spin axes, and a very large swing, to simulate a springy spin
    startPendulum(
    <1,0,0>, // spinAxis. axis to spin on
    <0,0,1>, // majorSwingAxis. axis to swing about
    10.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    300*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    50*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    2, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    40*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
topMotion() { // Circular swing; spins faster than it swings
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    3.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    20*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    1, // majorSwingCount. How many times I swing forward to back
    1, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    3); // spinCount. How many times the tire swing spins per animation
}
bobbleheadMotion() { // Forward and back swinging with smaller, out of sync side to side swinging; no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    15.0, // animation length, in seconds
    3.0, // frames per second. Must be between 0.0 and 9.0
    50*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    4, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    30*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
rockingBoatMotion() { // Slow and subtle out of sync rocking in both directions; no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    60.0, // animation length, in seconds
    2.5, // frames per second. Must be between 0.0 and 9.0
    5*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    10*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    4, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    30*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
// End of user configuration
/////////////////////////////////////////////////////////
///////////////// 3D Pendulum Library ///////////////////
/////////////////////////////////////////////////////////
integer isSwinging = FALSE;
rotation savedRot;
rotation NormRot(rotation Q) {
    float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
    return <Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}
stopPendulum() {
    llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
    if (!isSwinging) return;
    llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(), [PRIM_ROT_LOCAL, savedRot]);
    isSwinging = FALSE;
}
// Causes a prim to swing like a pendulum or spin like a top, depending on parameters. Implements arbitrary pendulum motion in 3d
// spinAxis: The axis to spin about, in prim-local coordinates. must be nonzero
// majorSwingAxis: The primary axis to swing about, in prim-local coordinates. must be nonzero
// length: The length of the animation, in seconds. Must be positive
// frameRate: The smoothness of the animation, in frames per second. Must be between 0.0 and 9.0
// majorSwingAngle: The angle that the prim swings around the majorSwingAxis, in radians. can be positive, negative, or zero
// minorSwingAngle: The angle that the prim swings around the minor swing axis, in radians. minor swing axis is perpendicular to majorSwingAxis. can be positive, negative, or zero
// majorSwingCount: Number of times to swing about the major axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction
// minorSwingCount: Number of times to swing about the minor axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction. Set this to the same number as majorSwingCount for eliptical swinging motion. Set it to other values for unrealistic, but interesting motion
// phase: timing difference between major and minor swinging, in radians. Set this to PI_BY_TWO for eliptical motion
// spinCount: Number of times to spin during the animation. Can be positive, negative, or zero. If zero, the prim will swing only, without spinning. if negative, spin the other way
startPendulum(vector spinAxis, vector majorSwingAxis, float length, float frameRate, float majorSwingAngle, float minorSwingAngle, integer majorSwingCount, integer minorSwingCount, float phase, integer spinCount) {
    stopPendulum();
    savedRot = llGetRootRotation();
    isSwinging=TRUE;
    vector minorSwingAxis = spinAxis % majorSwingAxis;
    majorSwingAxis = minorSwingAxis % spinAxis;
    rotation currentRot;
    length /= speed;
    integer frameCount = (integer)(length * frameRate);
    float frameTime = length / frameCount;
    list frames = [];
    integer frame;
    for (frame = 0; frame <= frameCount; frame++) {
        rotation nextRot = llAxisAngle2Rot(spinAxis, frame * spinCount * TWO_PI / frameCount) *
            llAxisAngle2Rot(majorSwingAxis, llCos(frame * TWO_PI / frameCount * majorSwingCount) * majorSwingAngle) *
            llAxisAngle2Rot(minorSwingAxis, llCos(frame * TWO_PI / frameCount * minorSwingCount - phase) * minorSwingAngle);
        //llSetRot(nextRot * savedRot); llSleep(0.1);
        if (frame == 0) llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(),
            [PRIM_ROT_LOCAL, nextRot*savedRot]);
        else frames += [NormRot(nextRot / currentRot), frameTime];
        currentRot = nextRot;
    }
    //llOwnerSay(llList2CSV(frames));
    llSetKeyframedMotion(frames, [KFM_MODE, KFM_LOOP, KFM_DATA, KFM_ROTATION]);
}
/////////////////////////////////////////////////////////
//////////////////////// SCRIPT /////////////////////////
/////////////////////////////////////////////////////////
integer menuHandle;
integer menuHandle;
 
doMenu(key id) {
doMenu(key id) {
     llListenRemove(menuHandle);
     llListenRemove(menuHandle);
Line 90: Line 283:
     menuHandle = llListen(menuChannel, "", id, "");
     menuHandle = llListen(menuChannel, "", id, "");
     llDialog(id, "Choose a demo",
     llDialog(id, "Choose a demo",
         ["swing", "tire swing", "top", "bobblehead", "rocking boat", "custom", "stop"],
         ["swing", "tire swing", "tangled swing", "top", "bobblehead", "rocking boat", "stop"],
         menuChannel);
         menuChannel);
}
}
 
default {
default {
     touch_start(integer num) {
     touch_start(integer num) {
         doMenu(llDetectedKey(0));
         doMenu(llDetectedKey(0));
     }
     }
 
     listen(integer channel, string name, key id, string msg) {
     listen(integer channel, string name, key id, string msg) {
         doMenu(id);
         doMenu(id);
         if (msg == "swing") {
         if (msg == "swing") swingMotion();
            spinningPendulum(
         else if (msg == "tire swing") tireSwingMotion();
                <0,0,1>, // spinAxis
         else if (msg == "tangled swing") tangledSwingMotion();
                <0,1,0>, // majorSwingAxis
         else if (msg == "top") topMotion();
                4.0, // animation length, in seconds
         else if (msg == "bobblehead") bobbleheadMotion();
                20, // frameCount
         else if (msg == "rocking boat") rockingBoatMotion();
                50*DEG_TO_RAD, // majorSwingAngle
         else if (msg == "stop") stopPendulum();
                0*DEG_TO_RAD, // minorSwingAngle
                1, // majorSwingCount
                0, // minorSwingCount
                90*DEG_TO_RAD, // phase
                0); // spinCount
         } else if (msg == "tire swing") {
            spinningPendulum(
                <0,0,1>, // spinAxis
                <0,1,0>, // majorSwingAxis
                8.0, // animation length, in seconds
                40, // frameCount
                40*DEG_TO_RAD, // majorSwingAngle
                20*DEG_TO_RAD, // minorSwingAngle
                -2, // majorSwingCount
                -2, // minorSwingCount
                90*DEG_TO_RAD, // phase
                1); // spinCount
         } else if (msg == "top") {
            spinningPendulum(
                <0,0,1>, // spinAxis
                <0,1,0>, // majorSwingAxis
                3.0, // animation length, in seconds
                16, // frameCount
                20*DEG_TO_RAD, // majorSwingAngle
                20*DEG_TO_RAD, // minorSwingAngle
                1, // majorSwingCount
                1, // minorSwingCount
                90*DEG_TO_RAD, // phase
                3); // spinCount
         } else if (msg == "bobblehead") {
            spinningPendulum(
                <0,0,1>, // spinAxis
                <0,1,0>, // majorSwingAxis
                15.0, // animation length, in seconds
                40, // frameCount
                50*DEG_TO_RAD, // majorSwingAngle
                20*DEG_TO_RAD, // minorSwingAngle
                4, // majorSwingCount
                3, // minorSwingCount
                30*DEG_TO_RAD, // phase
                0); // spinCount
         } else if (msg == "rocking boat") {
            spinningPendulum(
                <0,0,1>, // spinAxis
                <0,1,0>, // majorSwingAxis
                60.0, // animation length, in seconds
                40, // frameCount
                5*DEG_TO_RAD, // majorSwingAngle
                10*DEG_TO_RAD, // minorSwingAngle
                4, // majorSwingCount
                3, // minorSwingCount
                30*DEG_TO_RAD, // phase
                0); // spinCount
         } else if (msg == "custom") {
            spinningPendulum(
                spinAxis, // spinAxis
                majorSwingAxis, // majorSwingAxis
                length, // animation length, in seconds
                animFrameCount, // frameCount
                majorSwingDegrees*DEG_TO_RAD, // majorSwingAngle
                minorSwingDegrees*DEG_TO_RAD, // minorSwingAngle
                majorSwingCount, // majorSwingCount
                minorSwingCount, // minorSwingCount
                phaseDegrees*DEG_TO_RAD, // phase
                spinCount); // spinCount
         } else if (msg == "stop") {
            stopPendulum();
        }
     }
     }
}</source>
}</source>
</div>


If you get the error "Only linksets which uses the new prim equivalency system may be animated.", set the object to physics type convex hull, or add the followin in the script:
If you get the error "Only linksets which uses the new prim equivalency system may be animated.", set the object to physics type convex hull, or add the followin in the script:
Line 191: Line 317:
I did not include that in the script since it would break mesh builds with custom physics shapes
I did not include that in the script since it would break mesh builds with custom physics shapes


</div>
{{LSLC|Library}}
{{LSLC|Library}}

Latest revision as of 16:59, 15 May 2017

Generalized undamped pendulum motion in 3 dimensions. Can swing in simple arcs, circles, elipses, or lissajous curves, and spin at the same time.
Suitable for regular pendulums, swings, spinning tire swings, spinning tops, bobbleheads, rocking boats, etc.

Two scripts are included on this page. The first one toggles the swinging on or off on touch. The second script presents a menu of various motions on touch. I recommend starting with the second, demo script, to see what motions it's capable of, then copying the configuration you like to the touch on/off script and tweaking it to suit your needs

3D Pendulum 1.4 touch on/off

// 3D pendulum touch on/off script 1.4 by Tapple Gao
// http://wiki.secondlife.com/wiki/User:Tapple_Gao/3D_Spinning_Pendulum_Motion

// speed up or slow down all animations:
float speed = 1.0; // Speed Multiplier. 2.0 for double speed, 0.5 for half speed

tireSwingMotion() { // Eliptical swing; spins slower than it swings
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    8.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    40*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    -2, // majorSwingCount. How many times I swing forward to back
    -2, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    1); // spinCount. How many times the tire swing spins per animation
}

// End of user configuration

/////////////////////////////////////////////////////////
///////////////// 3D Pendulum Library ///////////////////
/////////////////////////////////////////////////////////

integer isSwinging = FALSE;
rotation savedRot;

rotation NormRot(rotation Q) {
    float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
    return <Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}

stopPendulum() {
    llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
    if (!isSwinging) return;

    llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(), [PRIM_ROT_LOCAL, savedRot]);
    isSwinging = FALSE;
}

// Causes a prim to swing like a pendulum or spin like a top, depending on parameters. Implements arbitrary pendulum motion in 3d
// spinAxis: The axis to spin about, in prim-local coordinates. must be nonzero
// majorSwingAxis: The primary axis to swing about, in prim-local coordinates. must be nonzero
// length: The length of the animation, in seconds. Must be positive
// frameRate: The smoothness of the animation, in frames per second. Must be between 0.0 and 9.0
// majorSwingAngle: The angle that the prim swings around the majorSwingAxis, in radians. can be positive, negative, or zero
// minorSwingAngle: The angle that the prim swings around the minor swing axis, in radians. minor swing axis is perpendicular to majorSwingAxis. can be positive, negative, or zero
// majorSwingCount: Number of times to swing about the major axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction
// minorSwingCount: Number of times to swing about the minor axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction. Set this to the same number as majorSwingCount for eliptical swinging motion. Set it to other values for unrealistic, but interesting motion
// phase: timing difference between major and minor swinging, in radians. Set this to PI_BY_TWO for eliptical motion
// spinCount: Number of times to spin during the animation. Can be positive, negative, or zero. If zero, the prim will swing only, without spinning. if negative, spin the other way
startPendulum(vector spinAxis, vector majorSwingAxis, float length, float frameRate, float majorSwingAngle, float minorSwingAngle, integer majorSwingCount, integer minorSwingCount, float phase, integer spinCount) {
    stopPendulum();
    savedRot = llGetRootRotation();
    isSwinging=TRUE;

    vector minorSwingAxis = spinAxis % majorSwingAxis;
    majorSwingAxis = minorSwingAxis % spinAxis;
    rotation currentRot;

    length /= speed;
    integer frameCount = (integer)(length * frameRate);
    float frameTime = length / frameCount;
    list frames = [];
    integer frame;
    for (frame = 0; frame <= frameCount; frame++) {
        rotation nextRot = llAxisAngle2Rot(spinAxis, frame * spinCount * TWO_PI / frameCount) *
            llAxisAngle2Rot(majorSwingAxis, llCos(frame * TWO_PI / frameCount * majorSwingCount) * majorSwingAngle) *
            llAxisAngle2Rot(minorSwingAxis, llCos(frame * TWO_PI / frameCount * minorSwingCount - phase) * minorSwingAngle);
        //llSetRot(nextRot * savedRot); llSleep(0.1);
        if (frame == 0) llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(),
            [PRIM_ROT_LOCAL, nextRot*savedRot]);
        else frames += [NormRot(nextRot / currentRot), frameTime];
        currentRot = nextRot;
    }
    //llOwnerSay(llList2CSV(frames)); 
    llSetKeyframedMotion(frames, [KFM_MODE, KFM_LOOP, KFM_DATA, KFM_ROTATION]);
}

/////////////////////////////////////////////////////////
//////////////////////// SCRIPT /////////////////////////
/////////////////////////////////////////////////////////

default {
    state_entry() {
        llSetTouchText("Swing");
    }

    touch_start(integer num) {
        if(isSwinging) {
            stopPendulum();
            llSetTouchText("Swing");
        } else {
            llSetTouchText("Stop swing");
            tireSwingMotion();
        }
    }

    changed(integer change) {
        if(change & CHANGED_LINK) {
            stopPendulum();
            llSetTouchText("Swing");
        }
    }
}

Demo script, demonstrating a variety of motions the script can produce. The configuration used in the touch on/off script above is the tire swing configuration in the demo script

3D Pendulum 1.4 demo

// 3D pendulum demo script 1.4 by Tapple Gao
// http://wiki.secondlife.com/wiki/User:Tapple_Gao/3D_Spinning_Pendulum_Motion
 
// speed up or slow down all animations:
float speed = 1.0; // Speed Multiplier. 2.0 for double speed, 0.5 for half speed
 
swingMotion() { // simple 2D swinging motion. No side to side motion, no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    4.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    50*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    0*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    1, // majorSwingCount. How many times I swing forward to back
    0, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
 
tireSwingMotion() { // Eliptical swing; spins slower than it swings
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    8.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    40*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    -2, // majorSwingCount. How many times I swing forward to back
    -2, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    1); // spinCount. How many times the tire swing spins per animation
}
 
tangledSwingMotion() { // Changes the axes to swing on the spin axes, and a very large swing, to simulate a springy spin
    startPendulum(
    <1,0,0>, // spinAxis. axis to spin on
    <0,0,1>, // majorSwingAxis. axis to swing about
    10.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    300*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    50*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    2, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    40*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}

topMotion() { // Circular swing; spins faster than it swings
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    3.0, // animation length, in seconds
    5.0, // frames per second. Must be between 0.0 and 9.0
    20*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    1, // majorSwingCount. How many times I swing forward to back
    1, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    90*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    3); // spinCount. How many times the tire swing spins per animation
}
 
bobbleheadMotion() { // Forward and back swinging with smaller, out of sync side to side swinging; no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    15.0, // animation length, in seconds
    3.0, // frames per second. Must be between 0.0 and 9.0
    50*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    20*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    4, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    30*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
 
rockingBoatMotion() { // Slow and subtle out of sync rocking in both directions; no spin
    startPendulum(
    <0,0,1>, // spinAxis. axis to spin on
    <0,1,0>, // majorSwingAxis. axis to swing about
    60.0, // animation length, in seconds
    2.5, // frames per second. Must be between 0.0 and 9.0
    5*DEG_TO_RAD, // majorSwingAngle. How far the swing moves, forward to back
    10*DEG_TO_RAD, // minorSwingAngle. How far the swing moves, side to side
    4, // majorSwingCount. How many times I swing forward to back
    3, // minorSwingCount. How many times I swing side to side. Usually set to the same as majorSwingCount
    30*DEG_TO_RAD, // phase. timing difference between major and minor swinging motion. Set to 90 for eliptical motion
    0); // spinCount. How many times the tire swing spins per animation
}
 
// End of user configuration
 
/////////////////////////////////////////////////////////
///////////////// 3D Pendulum Library ///////////////////
/////////////////////////////////////////////////////////
 
integer isSwinging = FALSE;
rotation savedRot;
 
rotation NormRot(rotation Q) {
    float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
    return <Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}
 
stopPendulum() {
    llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
    if (!isSwinging) return;
 
    llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(), [PRIM_ROT_LOCAL, savedRot]);
    isSwinging = FALSE;
}
 
// Causes a prim to swing like a pendulum or spin like a top, depending on parameters. Implements arbitrary pendulum motion in 3d
// spinAxis: The axis to spin about, in prim-local coordinates. must be nonzero
// majorSwingAxis: The primary axis to swing about, in prim-local coordinates. must be nonzero
// length: The length of the animation, in seconds. Must be positive
// frameRate: The smoothness of the animation, in frames per second. Must be between 0.0 and 9.0
// majorSwingAngle: The angle that the prim swings around the majorSwingAxis, in radians. can be positive, negative, or zero
// minorSwingAngle: The angle that the prim swings around the minor swing axis, in radians. minor swing axis is perpendicular to majorSwingAxis. can be positive, negative, or zero
// majorSwingCount: Number of times to swing about the major axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction
// minorSwingCount: Number of times to swing about the minor axis during the animation. Can be positive, negative, or zero. Negative numbers will cause the prim to swing in the oposite direction. Set this to the same number as majorSwingCount for eliptical swinging motion. Set it to other values for unrealistic, but interesting motion
// phase: timing difference between major and minor swinging, in radians. Set this to PI_BY_TWO for eliptical motion
// spinCount: Number of times to spin during the animation. Can be positive, negative, or zero. If zero, the prim will swing only, without spinning. if negative, spin the other way
startPendulum(vector spinAxis, vector majorSwingAxis, float length, float frameRate, float majorSwingAngle, float minorSwingAngle, integer majorSwingCount, integer minorSwingCount, float phase, integer spinCount) {
    stopPendulum();
    savedRot = llGetRootRotation();
    isSwinging=TRUE;
 
    vector minorSwingAxis = spinAxis % majorSwingAxis;
    majorSwingAxis = minorSwingAxis % spinAxis;
    rotation currentRot;
 
    length /= speed;
    integer frameCount = (integer)(length * frameRate);
    float frameTime = length / frameCount;
    list frames = [];
    integer frame;
    for (frame = 0; frame <= frameCount; frame++) {
        rotation nextRot = llAxisAngle2Rot(spinAxis, frame * spinCount * TWO_PI / frameCount) *
            llAxisAngle2Rot(majorSwingAxis, llCos(frame * TWO_PI / frameCount * majorSwingCount) * majorSwingAngle) *
            llAxisAngle2Rot(minorSwingAxis, llCos(frame * TWO_PI / frameCount * minorSwingCount - phase) * minorSwingAngle);
        //llSetRot(nextRot * savedRot); llSleep(0.1);
        if (frame == 0) llSetLinkPrimitiveParamsFast(!!llGetLinkNumber(),
            [PRIM_ROT_LOCAL, nextRot*savedRot]);
        else frames += [NormRot(nextRot / currentRot), frameTime];
        currentRot = nextRot;
    }
    //llOwnerSay(llList2CSV(frames)); 
    llSetKeyframedMotion(frames, [KFM_MODE, KFM_LOOP, KFM_DATA, KFM_ROTATION]);
}
 
/////////////////////////////////////////////////////////
//////////////////////// SCRIPT /////////////////////////
/////////////////////////////////////////////////////////
 
integer menuHandle;
 
doMenu(key id) {
    llListenRemove(menuHandle);
    integer menuChannel = -1 - (integer)llFrand(329847);
    menuHandle = llListen(menuChannel, "", id, "");
    llDialog(id, "Choose a demo",
        ["swing", "tire swing", "tangled swing", "top", "bobblehead", "rocking boat", "stop"],
        menuChannel);
}
 
default {
    touch_start(integer num) {
        doMenu(llDetectedKey(0));
    }
 
    listen(integer channel, string name, key id, string msg) {
        doMenu(id);
        if (msg == "swing") swingMotion();
        else if (msg == "tire swing") tireSwingMotion();
        else if (msg == "tangled swing") tangledSwingMotion();
        else if (msg == "top") topMotion();
        else if (msg == "bobblehead") bobbleheadMotion();
        else if (msg == "rocking boat") rockingBoatMotion();
        else if (msg == "stop") stopPendulum();
    }
}

If you get the error "Only linksets which uses the new prim equivalency system may be animated.", set the object to physics type convex hull, or add the followin in the script:

    state_entry()
    {
        llSetLinkPrimitiveParamsFast(LINK_ROOT,
                [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX,
            PRIM_LINK_TARGET, LINK_ALL_CHILDREN,
                PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_NONE]);
    }

I did not include that in the script since it would break mesh builds with custom physics shapes