Jump to content

Smooth Swinging Motion: A Better Way?


You are about to reply to a thread that has been inactive for 114 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

I've recently built a bench swing and am writing the script to create the swinging motion. Here's a Gyazo gif of the swing and motion:

https://gyazo.com/b46222dce20dab467a61015ace529543

 

Here is the high-level description of my swinging motion script:

set greatest angle in degrees from vertical (e.g., 30)

set a step in degrees (e.g., .2)

in state_entry in default compute values for three lists, pos, rot, and sleepOrNot for a full period (bottom, forward-top, bottom, backward-top, bottom)

I use sleep at varying intervals to change the speed appropriately.

when executing the motion I iterate through the lists and used the below at each step, throwing in a sleep as appropriate.

         llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POS_LOCAL,llList2Vector(posList,stepIndex), PRIM_ROT_LOCAL,llList2Rot(rotList,stepIndex)]);

The motion looks ok but isn't as smooth as I would like it. And, when there is lots going on in the sim the motion can get even choppier.

Is there another way to create smooth motion in SL?

I have investigated llTargetOmega and llSetKeyFrameMotion. I couldn't get either to work well.

Thanks in advance!

G

 

Link to post
Share on other sites

Maybe you could try building the swinging part, the bench and the ropes, with an extra invisible vertex (or an edge or a face) to make its horizontal central axis align with the pivotal axis you want to achieve. Made like this, you wouldn't need to change its position in each step, just its rotation, which might help make the motion smoother.

And then you might also have another look at keyframe motion. Maybe five frames, describing an arc that's smaller at the extremes, but still taking the same time, played ping-pong style. Although that'd mean starting from one of the extremes of the swing... To start from the bottom of the swing, I guess you'd need to keyframe the whole cycle and repeat it.

Link to post
Share on other sites

I put Dora's algorithm into my swing and in a plain cylinder. Plain cylinder looks good. But if I add another prim to the cylinder then both prims swing. In my swing the seat and ropes invert then the structure oscillates left to right. When I experimented with llSetKeyFrameMotion the problem was getting it to apply to only a single prim in a multi-set. Did not matter whether the prim was the root or a child. What we need would be a llSetLinkKeyFrameMotion.

 

Also the motion with llSetKeyFrameMotion is somewhat smoother than mine but still subject to pauses due to (likely) scheduling/dispatching on the server.

 

G

Link to post
Share on other sites
1 minute ago, GManB said:

What we need would be a llSetLinkKeyFrameMotion.

That would be nice.

Most of the code in my escalator steps, which are keyframe animated, is for setup. They have to be separate objects because they are keyframe animated. Placing them relative to the escalator frame, and moving them if the escalator is moved during installation, is over a hundred lines of LSL. The actual step movement for escalator operation is one line.

Link to post
Share on other sites
Posted (edited)
35 minutes ago, GManB said:

I put Dora's algorithm into my swing and in a plain cylinder. Plain cylinder looks good. But if I add another prim to the cylinder then both prims swing. In my swing the seat and ropes invert then the structure oscillates left to right. When I experimented with llSetKeyFrameMotion the problem was getting it to apply to only a single prim in a multi-set.

Yes, that can be a challenge.  One way around that, of course, is to make the swinging parts as a single mesh object, but that may not be a practical solution for you. In case you're still searching, here's a very different swing script that I wrote many years ago, playing off of Toy Wylie's Smooth Door Script:

// Based on the Smooth Door Script - Version 1.1  (modified by Rolig Loon)
// by Toy Wylie
// Distributed under the following licence:
// - You can use it in your own works
// - You can sell it with your work
// - This script must remain full permissions
// - This header notice must remain intact
// - You may modify this script as needed

integer ON;
integer OK2Go;
float openingTime=2.0;      // in seconds
float openingAngle=45.0;    // in degrees
float autocloseTime=0.5;    // in seconds
integer steps=4;            // number of internal rotation steps
 
float omega=0.0;
 
vector axis;
rotation closedRot;
rotation openRot;
 
integer swinging;
integer open;
 
openDoor(integer yes)
{
    vector useAxis=axis;
    open=yes;
 
    if(!yes)
        useAxis=-axis;
 
    llSetTimerEvent(openingTime/(float) steps);
    llTargetOmega(useAxis,omega,1.0);
}
 
go()
{
    if (ON)
    {
        if(swinging==0)
        {
            if(!open)
            {
                axis=llRot2Fwd(llGetLocalRot());
                closedRot=llGetLocalRot();
                openRot=llEuler2Rot(<openingAngle,0.0,0.0>*DEG_TO_RAD)*closedRot;
            }
            swinging=steps;
            openDoor(!open);
        }
    }
    else
    {
        swinging = 0;
        llTargetOmega(axis,0.0,0.0);
        if(open)
        {
            llSetLocalRot(openRot);
            llSetTimerEvent(autocloseTime);
        }
        else
        {
            llSetLocalRot(closedRot);
            llSetTimerEvent(0.0);
        }
    }
}
 
rotation slerp(rotation source,rotation target,float amount)
{
   return llAxisAngle2Rot(llRot2Axis(target/=source),amount*llRot2Angle(target))*source;
}
 
default
{
    state_entry()
    {
        llSetSitText("Swing");
        closedRot = llGetLocalRot();
        swinging=0;
        open=FALSE;
        omega=TWO_PI/360*openingAngle/openingTime;
        llTargetOmega(ZERO_VECTOR,1.0,1.0);
        llSitTarget(<0.70,-0.6,-0.55>*llEuler2Rot(<0,22.5,0>*DEG_TO_RAD),ZERO_ROTATION* llEuler2Rot(<0,0,-90>*DEG_TO_RAD));
    }
 
    touch_start(integer dummy)
    {
        if (OK2Go)
        {
            ON = !ON;
            go();
        }
    }
    
    changed (integer change)
    {
        if (change & CHANGED_LINK)
        {
            if (llAvatarOnSitTarget())
            {
                OK2Go = TRUE;
                llWhisper(0,"Touch the swing to start; touch again or stand up to stop");
                llWhisper(0,"A second person may sit on the newspaper next to you.");
            }
            else
            {
                ON = FALSE;
                go();
                OK2Go = FALSE;
            }
        }
    }
 
    timer()
    {
        if(swinging>0)
        {
            swinging--;
            if(swinging!=0)
            {
                float amount=(float) swinging/(float) steps;
                if(open)
                    amount=1.0-amount;
                llSetLocalRot(slerp(closedRot,openRot,amount));
                return;
            }
 
            llTargetOmega(axis,0.0,0.0);
            if(open)
            {
                llSetLocalRot(openRot);
                llSetTimerEvent(autocloseTime);
            }
            else
            {
                llSetLocalRot(closedRot);
            }
        }
        else // autoclose time reached
        {
            openDoor(!open);
            swinging=steps;
            go();
        }
    }
}

As I said, is was many years ago, before we had llLinkSitTarget and a couple of other functions that would have been handy. The swing I wrote this for was almost certainly a prim construction, since it was before we could import our own mesh. The action is fairly smooth and could probably be made smoother now, but it gives you one more to play with.

Edited by Rolig Loon
typos, of course
Link to post
Share on other sites

Thanks Rolig and animats,

I haven't been able to get ...TargetOmega to work well with on only a link in multi-linked object. I know it can be done but haven't taken the time to really investigate. Maybe that's next.

Another part of the issue is controlling the speed. I am using llSleep(0.01) which seems to give a smaller interval then llSetTimerEvent for which the minimum delay is 0.022. A swing's velocity when it is vertical will be, for a reasonably long suspension rope, much faster than a opening door. Thus, given this min delay, one has to make the steps larger resulting in less smooth motion. I would be happy if I could get a reliable minimum delay of 1ms and then use lots of steps... we could make wicked smooth motion then. But, from what I read, something to do with the simulator fame time of 22ms events can't get there. I might try the old embedded developer trick of something like

while(i<timeIntervalCount) { i++; cpuIntensiveComputation();}

then build a table of timeIntervalCount vs milliseconds delay

 

G

Link to post
Share on other sites
Posted (edited)
2 hours ago, GManB said:

Thanks Rolig and animats,

I haven't been able to get ...TargetOmega to work well with on only a link in multi-linked object. I know it can be done but haven't taken the time to really investigate. Maybe that's next.

Another part of the issue is controlling the speed. I am using llSleep(0.01) which seems to give a smaller interval then llSetTimerEvent for which the minimum delay is 0.022. A swing's velocity when it is vertical will be, for a reasonably long suspension rope, much faster than a opening door. Thus, given this min delay, one has to make the steps larger resulting in less smooth motion. I would be happy if I could get a reliable minimum delay of 1ms and then use lots of steps... we could make wicked smooth motion then. But, from what I read, something to do with the simulator fame time of 22ms events can't get there. I might try the old embedded developer trick of something like

while(i<timeIntervalCount) { i++; cpuIntensiveComputation();}

then build a table of timeIntervalCount vs milliseconds delay

It's like you said. The sim has an update rate of 22ms or 45 FPS. Using llSleep with a value of less than 0.022 will unavoidably sleep for at least the current frame, so you'll get up to 22ms of delay at the very least. On top of that, the framerate of sims fluctuate, it will often drop below 45. On top of that, scripts are the last in the priority list for a sim. Not all scripts are guaranteed to run every frame, and it's rare to see 100% of scripts running every frame. So sleeps (and especially events) are not guaranteed to happen at fixed rates. In fact -- nothing is, even loops and user-defined functions will be interrupted.

If you want absolutely smooth motion, you have to rely on the viewer to generate that motion for you. That means using llTargetOmega since llSetKeyFramedMotion was already ruled out.

Edited by Wulfie Reanimator
Link to post
Share on other sites
Posted (edited)
6 hours ago, GManB said:

I would be happy if I could get a reliable minimum delay of 1ms and then use lots of steps... we could make wicked smooth motion then. But, from what I read, something to do with the simulator fame time of 22ms events can't get there

a way to do this is to set the timer to 0.222222. Then in the timer event, use a loop to rotate the swing part of the way along the arc from its last rotation

Edited by Mollymews
do this
Link to post
Share on other sites
12 hours ago, Wulfie Reanimator said:

 The sim has an update rate of 22ms or 45 FPS........ On top of that, scripts are the last in the priority list for a sim. Not all scripts are guaranteed to run every frame, and it's rare to see 100% of scripts running every frame. So sleeps (and especially events) are not guaranteed to happen at fixed rates. In fact -- nothing is, even loops and user-defined functions will be interrupted.

Yup, I see that now. I wrote this little timing test:

        string sstartMilliseconds;
        string sendMilliseconds;
        integer bigIntOne = 289;
        integer bigIntTwo = 345;
        integer junk;
        sstartMilliseconds = llGetTimestamp();
        //for(i=0;i<10;i++){junk = bigIntOne*bigIntTwo; bigIntTwo += i;}
        sendMilliseconds = llGetTimestamp();
        llOwnerSay((string)(Millisec(sendMilliseconds)-Millisec(sstartMilliseconds)));

and I get either 0ms or about 21+ms when I execute it. I assume that this means the the two llGetTimestamp calls happen either in the same frame or in frames 'n' and 'n+1'. Adding the for loop doesn't help with the variance... still get widely varying times depending on when the simulator executes the timestamp calls.

Bummer.

G

  • Like 2
Link to post
Share on other sites
On 5/23/2020 at 5:24 AM, Wulfie Reanimator said:

If you want absolutely smooth motion, you have to rely on the viewer to generate that motion for you. That means using llTargetOmega since llSetKeyFramedMotion was already ruled out.

With llSetKeyFramedMotion you can still take advantage of client-side motion, as with normal physics objects the client uses interpolation. The sim will try to avoid sending updates unless velocity has changed. A phantom KFM with little to no velocity changes would be very optimal.

Link to post
Share on other sites
20 hours ago, Nexii Malthus said:

With llSetKeyFramedMotion you can still take advantage of client-side motion, as with normal physics objects the client uses interpolation. The sim will try to avoid sending updates unless velocity has changed. A phantom KFM with little to no velocity changes would be very optimal.

Yes, but if you read the thread, KFM was already deemed unsuitable for this case.

Link to post
Share on other sites
6 hours ago, Wulfie Reanimator said:

Yes, but if you read the thread, KFM was already deemed unsuitable for this case.

Had to change my approach. In another thread in this forum I ask about av's not moving with the swing. Because we don't have object hierarchies when an av sits on a multilink object it becomes a child prim and thus moves only with the root prim. By using

llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POS_LOCAL,llList2Vector(posList,stepIndex), PRIM_ROT_LOCAL,llList2Rot(rotList,stepIndex)]);

to give the swing motion I was moving only the child prim not the root prim. Thus, any av sitting on the swing did not move.

The reason I did not want to use llSetKFM in the first place was because it did not work on an individual child prim (the swing) in a multilink object. And I did not know how to create a saleable item from two un-linked objects (the frame and swing in this case).  In that other thread someone advised that I could have the item 'self-assemble' when the owner rezzed it. I haven't gotten that far yet but it seems the right approach.

In summary, I think what I know now is:

  • one can move a child prim relative to the root prim and relative to other child prims, easily
    • but only that child prim moves/rotates
  • if one moves or rotates the root prim all the child prims move/rotate with it
    • this was the fundamental problem with KFM in the first place, both the swing and the frame swung
  • any number of av's sitting on a multlink object become child prims
  • an av sitting on a multi-link object will move/rotate with the root prims (like all the children prims)
  • moving/rotating a child prim on which an av is sitting will move/rotate that particular child prim fine but the av will *not* move
  • it is possible to create a 'good' salable item comprising more than one unlinked object if the item self-assembles itself
  • my swing motion script, I discovered today, works fine in the root prim (small transparent cube linked to swing) and is simpler (I only need rotations)
    • in this case my script is functionally equivalent to KFM and avs sitting on the swing move with it.

 

So, I am now moving forward with the approach of creating a salable swing but with two objects (frame, and linked swing + root prim) with instructions to new owner how to properly rez and move it. I can use either KFM or my swing algorithm (with mine in the root it is equivalent to KFM (wrt av's moving on the swing)). I'll decide on which as I do a bit more experimentation.

Now, I have to figure out how to make it super easy for the owner to rez which means the frame and swing communicating.

 

Thanks,
G

Link to post
Share on other sites

I don't feel particularly protective of this short script, so I might as well share it.  It's the companion to the swing script that I posted earlier.  This script goes into the support frame that the swing hangs from ( sort of like the pipe framework that kids' swings hang from in a playground ). 

integer gDialog;
default
{
    touch_start(integer total_number)
    {
        if (llGetOwner() == llDetectedKey(0))
        {
            integer DCHAN = (-1) * ((integer)llFrand(100000) + 1);
            gDialog = llListen(DCHAN,"","","");
            llDialog(llDetectedKey(0),"Click \"OK\" to align swing parts", ["OK"],DCHAN);
        }
    }
    
    listen (integer channel, string name, key id, string msg)
    {
        if (msg == "OK")
        {
            llSensor("Smooth Swing","",SCRIPTED | ACTIVE | PASSIVE,10.0,PI);
        }
    }
    
    no_sensor()
    {
        llOwnerSay("I can't find the swing. Please be sure that it is within 10m of this frame.");
    }
    
    sensor ( integer num)
    {
        vector SwingPos = llDetectedPos(0);
        rotation SwingRot = llDetectedRot(0);
        llSetRot(llEuler2Rot(<0.0,90.0,-67.5>*DEG_TO_RAD)*SwingRot);
        llSetPos(SwingPos + <0.00,-0.03,0.665>*llGetRot());
    }
}

WARNINGS:

Like the swing script, this one is several years old. If I were taking the time to rewrite it today, I would probably take a different approach. It's nice and simple, though, so it's an example you can play with until it feels like something you would have created yourself.  I have not said anything about how the swing and the separate frame are constructed.  I'll leave that to your imagination.  You'll probably make a better one than mine was anyway.  This was never intended for sale, so it doesn't have the cosmetic bells and whistles or the security features that might prevent unwanted visitors from sitting on it.. It also doesn't delete the assembly script after the parts are aligned, which might be a nice touch (and save script time). As I recall, it was just one of those things that we all sometimes do on a rainy weekend afternoon.

Link to post
Share on other sites
24 minutes ago, GManB said:

So, I am now moving forward with the approach of creating a salable swing but with two objects (frame, and linked swing + root prim) with instructions to new owner how to properly rez and move it. I can use either KFM or my swing algorithm (with mine in the root it is equivalent to KFM (wrt av's moving on the swing)). I'll decide on which as I do a bit more experimentation.

Now, I have to figure out how to make it super easy for the owner to rez which means the frame and swing communicating.

for multi-part assemblies then look into REZZER BOX. There are a number of scripts available to do this

Link to post
Share on other sites
40 minutes ago, Mollymews said:

for multi-part assemblies then look into REZZER BOX. There are a number of scripts available to do this

Thanks Molly!

G

  • Like 1
Link to post
Share on other sites

A great start! Thanks Rolig!

 

Ah ha.. .just tested the script and I see that the frame aligns itself with the swing... I'll probably want to do the opposite. Have the new owner rez the frame somewhere they want then have the script pull the swing out of the inventory of the frame, rez it, and position it.

Thanks,

G

Link to post
Share on other sites

Ha ha... what fun moving the swing parts around! Starting to get the hang of positions and rotations! Finally!

Somewhat more complicated with this swing because when I imported it from Maya I didn't sway the y and z axes so I have to mentally do the swap and fiddle with the proper element in  the pos vector and the rotation. Once I get things settled I'll swap the axes in Maya before exporting.

Cheers,

G

Link to post
Share on other sites
You are about to reply to a thread that has been inactive for 114 days.

Please take a moment to consider if this thread is worth bumping.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...