Jump to content
Search Community

bezier tween autorotate behavior when in a timeline series

da2020 test
Moderator Tag

Recommended Posts

Hi,

 

In the development of my animation path authoring app, which uses the bezier plugin in "thru" mode, came across puzzling behavior that I can't pin down the cause of.. Here's the scenario:

 

Paths can be authored as a series of tweenmax bezier tweens (where each bezier tween has a thru-points curve of its own, with n points). These tweens are appended back to back in a timelinemax instance. thereby making one long "path" made up of a series of these bezier tweens, where the ending point of one tween is the starting point of the next. The app allows each of these tweens in the timeline to have its auto-rotate parameter set independently, so some tweens in the path will orient the animated object to the  path, while others do not.  When I have non-autorotated tween(s) at the beginning of the path, followed by an auto-rotated tween later in the path, the initial run of the timeline works as I'd expect: the object follows the beginning tween(s) without auto-rotating (ie, the object's initial rotation is maintained), and when the autorotated tween is reached, the object starts orienting to path --- great.

But, after reaching the end of the timeline (end of the full path), if the timeline is commanded to run again, using timelineInstance.restart(); the second time around those same beginning non-autorotated tweens move the object along the path with a fixed amount of rotation that is equal to the INITIAL amount of rotation that the FIRST autorotate tween would impose on the object at the start of its tween -- very weird, and very repeatable... it does not matter how many non-AR tweens exist before the first AR tween, and it does not matter if there is more than one AR tween down the line, the non-AR tweens at the beginning always apply the fixed amount of rotation that would get applied later when the first AR segment is entered, but only after the initial run... the first run through the timeline, also started by the same timelineInstance.restart() approach, works fine.. 

I even force the rotation property of the tweened object back to 0 just before calling restart, and this still happens, as if there is internal memory of the initial AR rotation value that is somehow contaminating the behavior of earlier non-AR tweens..

 

Example:

 

The complete "path" consists of 4 tweens in a row, appended back to back in a timeline. First 2 are non-AR, second 2 are AR.. On initial run, the object maintains initial rotation of 0 along the curves of the first 2 tweens as expected, and on the curves of the 3rd and 4th tweens the object rotates to stay oriented to the path -- perfect. Let's say  the curve starting the 3rd tween has an initial departure angle of 45 deg from its start point, and the 4th tween has a departure angle of 90 deg from its starting point, meaning when entering AR tween 3 the object initially shifts its rotation to 45 degrees to orient to path, and then continues to change rotation as expected to maintain that path orientation --  same for 4, starting at 90 deg.

 

Now, on the second and all subsequent runs, via timeline.restart(), the first 2 non-AR tweens still maintain fixed rotation, but instead of that rotation being 0 (the initial object rotation), the object gets rotated to a fixed 45 degrees, which is the INITIAL angle that the object takes on when entering AR tween 3...   AR tweens 3 and 4 continue to work as normal with variable rotations along their curves...  In experimenting with this, the fixed angle of the first two tweens track exactly with that INITIAL departure angle from the point that starts tween 3 -- if I move the points around to make that tween 3 initial angle some other angle, the first 2 tweens adopt that exact same new fixed angle... It makes no difference what the initial angle of LATER AR tweens is, or what the final angle at the end of the path is... it all keys off the initial angle of the first AR tween encountered in the timeline sequence...

 

I don't think it should matter, but note that I'm dynamically constructing the tween instances, and the appending of course is doen dynamically, prior to the first run, but not prior to the second run.. the second run is simply timeline.restart() being issued manually after the initial run completes successfully. Also note that I'm using the array approach for autorotate in the tween declarations, with variables used for the "rotation" property and for the degrees offset.  To enable AR, I set the property string in the array to "rotation" and to disable I set it to  "" (empty string). -- this appears to work fine as the tweens behave as either AR mode or not according to how this is set, but is empty string the best choice for fully "disabling"?.  The degrees offset was set to 0 in my example, but I've set it other values, and the offset works fine in the AR tweens, but the other behaviors are the same.  I'm not using any on-complete handlers.  As said earlier, attempts to set the object to 0 rotation before the timeline.restart() do not have any effect -- the fixed "error" in the early non-AR  tweens, as described above,  is still there

 

Not at all easy to provide sample code for this right now, since this is buried in a fairly complex app.. but hoping you can shed some light on whether this might be expected for some reason or if there is something different I might want to do in the way I launch subsequent timeline runs (I realize what I'm doing here might be fairly unique, but this use case has proven to be a very effective one so I need to find a solution... I may need to resort to re-declaring the tweens dynamically prior to each run if I can't find another solution. 

 

Thanks much for any thoughts or insights,

 

Doug

Link to comment
Share on other sites

A demo really makes the problem a lot easier to look into (especially with a description as long as that one). You just need to include enough to show the issue; we don't need your entire app.

 

I've built one in javascript here

See the Pen gGoEh by jamiejefferson (@jamiejefferson) on CodePen

and included a fix that seems to reset the rotation correctly.

EDIT: I only realised it was for ActionScript, not JavaScript, after posting. The GSAP code should be familiar though as the API is the same for ActionScript - I only hope the fix applies too =)

 

It doesn't seem like correct behaviour regardless, so I'm sure GreenSock will be able to come up with a solution or fix for the plugin once he sees this.

  • Like 2
Link to comment
Share on other sites

Jamie,

 

Thanks for the quick reply. You did a nice job distilling this down to a demo of both the problem and a simple solution.  Yes, I'm working in actionscript, but the matrix setting solution you posted on codepen does do the trick for me as well -- that's very good to know, as I was not aware that rotational properties needed to be set in this way for more fool proof initialization of the bezier tweens. It would be nice if this was not required, but this might turn out to be a "feature" because it will allow me to have a setting for whether non-AR tweens that follow AR tweens adopt the last angle of AR rotation or not... since I'm making a path editor, there are situations where either behavior could be desired, now that there is a way to control that choice.

 

Note that, in my case, and maybe for actionscript in general, I was able to use the timeline.set method on the object to go straight to 0 rotation, without the need for a non-zero setting first as you found was necessary in JS...

 

Thanks again!

Link to comment
Share on other sites

I think I know exactly what's happening. When the playhead moves BACKWARD after you've played through once, the tweens in the timeline must each get rendered at their starting position (in order, backwards), so your autoRotate tweens did exactly what they were supposed to - rendered at the beginning which auto-rotated the object based on the angle of the very start of the path. The non-autoRotate tween didn't affect rotation at all (tweens only affect properties that you tell them to), so the starting tween (which, when we're rewinding, gets rendered last) didn't alter the rotation. So when the playhead arrives back at the beginning of the timeline, the rotation is effectively rendered at the "beginning" state of the first autoRotate tween. 

 

However, I can see why this isn't the behavior you'd expect intuitively (even if it's technically "right"). I agree. So I've attached an updated BezierPlugin that records the starting rotation values at the very beginning of the tween and then if/when it gets rewound completely, it'll apply those values instead of doing any kind of autoRotate calculation.

 

Would you mind testing it and letting us know if it works as you'd expect? (I assume you need the AS3 flavor)

BezierPlugin.as.zip

  • Like 2
Link to comment
Share on other sites

Jack,

 

Thanks very much for this updated plugin and the fast response. I did a few test runs with the new plugin, and backed out the rotation matrix set statements that I added per Jamie's solution,  and it does work now as I was originally expecting and as you said it should --  on repeated runs, the first non-AR tweens run with the original non-rotated orientation of the object, rather than "backwards-inheriting" the initial rotation of the first AR tween. 

 

Switching gears to what happens AFTER an AR tween (I think this is the same with both the old and new plugin, and is really a different question), when there is a transition from an AR to a non-AR tween, the non-AR tween inherits a fixed rotation that equals the amount of rotation that existed at the very end of the preceeding AR (vs starting with 0 degress rotation that might have been set on the object at the time that non-AR tween was declared and appended). Is that the expected behavior, in that the non-AR tween simply accepts the existing object rotation, as left by the preceeding AR tween, and just moves it along with no other rotational initalization? I guess that makes sense as the default behavior... I notice though that if I apply Jamie's suggested "set" method to set the object to 0 rotation prior to the append declarations, any non-AR tween that follows an AR tween will shift the object to 0 degrees regardless of how the preceeding AR left it... is that what you'd expect?

 

And, one more gear switch on the subject of auto-rotation if you don't mind.. In my path authoring app when the object has been chosen as the path object and has become  "attached" to the path starting point (meaning that if that point is dragged around, the object moves with it), and the first tween happens to be set to be an AR tween, the behavior I want is for the object, in the static state before the timeline runs, to assume the inital rotation for path orientation (ie, to assume the initial amount of rotation that the AR tween will apply at time zero (for the current displayed curves) when the tween runs -- you can imagine why this is nice... it allows the author to shape the curve and have the rotation of the object (in pre-run, when not moving) always update in realtime as the path shape changes to match the initial tween's AR orientation to avoid a jump in rotation and to allow for proper positioning of points, etc.  

Anyway, I've made this work using this somewhat crude but seemingly effective approach:  when ever the points affecting AR tween 1 are moved or its curves are  changed in editing, I run a short tweenTo for some very small time interval, like .0001 or so, which is enough to make the object rotate to the starting orientation of the path.... so, when the points are being dragged, or curviness is being adjusted, etc.. this fires repeatedly, which makes the object's rotation smoothly adjust to match the departure angle of the path from the first point. So, this works but is kind of an ugly and somewhat flawed approach -- it requires that very short "init" tween that is short enough that it does not noticeably move the object, but is long enough for the tween code to set the initial rotation (ie, tweenTo zero is too "short" :) --- so, I'm just running this by you to see if you'd agree that this is the best/maybe only way to do this, or if you could suggest a better way...  as I see it now, what would really be cool is a "getInitialRotation()" method that would run the necessary calcs and return that initial auto-rotation value without the messiness of having to actually run a short tween...   (note I'm also running a bunch of curve distance approximation calcs to support constant speed settings across all the tweens that make up the full path, and I might be able to tap into those loops to extract the initial angle of that first tween, but hoping to avoid that)

 

Thanks again for your thoughts and for  the quick help and revision on the bezier plugin.

 

- Doug

Link to comment
Share on other sites

As far as part 1 of your question, yes, the way you described the current behavior is exactly how it should work. After an autoRotate tween finishes, the rotation shouldn't suddenly jump to a completely different value - it should stay where it ended unless you change it or have another tween change it. I'm a little confused - did you think it was supposed to act differently? 

 

For part 2 of your question, it sounds like you're doing it almost right :) Instead of a tweenTo() with a super short duration, there's no need to tween at all (if I understood the question correctly) - you can simply jump to a very small time in the tween so that it applies the autoRotation you're looking for. For example:

TweenLite.to(...).time(0.00001);

Or if it's in a timeline:

timeline.time( yourTween.startTime() + 0.00001 );

Does that answer your question? 

 

As far as applying a constant speed across all the curves, did you know that as of v12, BezierPlugin does that for you by default? You just need to make sure they're all in one bezier tween. 

Link to comment
Share on other sites

Thanks Jack. I'll try out those direct time settings.. I had tried jumping to small "progress" amounts before, and as I recall, the rotation would not get set with that approach, but I have not used .time before I don't believe..

 

You're right, the pick-up where the last tween's rotation left off behavior makes perfect sense as default behavior... if I was confused about anything I guess it was the effect and timing of declaring the rotation to be some value via .set prior to the appends which occur dynamically, to override the natural behavior of inheriting the final rotation of the previous tween... I've made my code to optionally run a .set on rotation prior to appending each tween in the series if the user has selected "fixed rotation" of a certain value for a certain tween (path "segment") -- that overrides the default behavior of inheriting prior rotation from the last tween, which allows there to be choice -- so that's all fine.

 

Re the constant speed, I'm aware you can set the time resolution param to a high value in a single tween to make speed constant regardless of point spacing, but I was referring to my challenge of needing a constant speed across the multiple series of tweens that make up the overall path -- that was discussed in a prior post, and I ended up solving that by doing approximations of each tweens curve length and calculating durations that would achieve the same speed regardless of each tweens length, and of course they all have different lengths.

Link to comment
Share on other sites

Jack,

 

As an update, wanted to let you know that the use of the .time setting method you suggested above did work out fine for what I needed.

 

Since last posting, I've run into yet another interesting autorotation (AR) behavior that I need to more fully understand:  

I've recently added features to the bezier plug-in based motion path editor app I'm developing to allow for various properties of the animated object (the object that is tweened along the bezier paths) to also be tweened in parallel (simultaneously). The bezier tween series is in an inner timeline, and that timeline is nested inside an outer timeline that also now contains tweens for rotation, scale, and alpha on the animated object. So, for example, if bezier AR is turned off, the parallel rotation tween can make the object rotate continuously while the object moves along the path, etc. These parallel tweens seem to work fine along side the bezier tweens. 

In this design, the bezier tweens and the "parallel" rotation tween  are not directly controlling the targeted (visible) animation object, but are instead controlling a proxy object (normally invisible) that allows for various animated object offsets to be achieved, etc.  In the Bezier's onUpdate handler, the x, y, and rotation of the tweened proxy object are assigned to the targeted animation object's properties, which then make it track the proxy object as it is being bezier-tweened (and autorotated if enabled)-- this all works fine with or without AR.

When I added the parallel tween in the outer timeline to rotate the proxy object while it was also being bezier tweened, I expected this independent rotation action to work fine with bezier AR disabled (which it does), but I expected some sort of conflict/ tug of war if I were to enable AR, since there would then be two different  tweens simultaneously trying to control the rotation of the proxy object...

When AR was enabled (which I'd prefer to override the other rotation tween if there was a conflict) the AR did override in the sense that the TARGET object DID then follow the path... BUT, much to my surprise, the proxy object (which has a visual marker in one corner so I can see how it's moving) was smoothly rotating according to the parallel rotation tween (not per AR)...  So, this begs the question:   with AR on, how can the target object actually be following the path, while the proxy object (the bezier-tweened object that the animated target object follows via onUpdate) is, at the same time, appearing to rotate independently per the other rotation tween??

The only theory I have is that the bezier tween is grabbing control of the proxy object when it needs to, autorotating it to path, running onUpdate to auto-orient the target object to path as well (as in normal AR) and then control of the proxy is released such that the parallel rotation tween can ALSO then change the proxy's rotation as it needs (but without affecting the target object since the bezier onUPdate handler has already run).... and it so happens that visually I'm only seeing the proxy rotate per the parallel rotation (with no flicker) , rather than seeing it trying to also track to the path, due to the relative timing of the screen refresh that keeps me from seeing the short period when bezier has control of the proxy....  whew...   but that's just a guess...

So, it so happens that it works the way I need it to under these conditions, but my concern is that I don't necessarily understand WHY it's working this way, or whether it would always reliably work this way.... ie, I may be just getting lucky and something could be on the edge of not working without me realizing it, if that makes sense...

Would you expect the behavior I described, and if so, is my theory in the ballpark or is there another reason?

 

UPDATE: since the above posting, I noticed that the observed behavior depends on the order in which my outer timeline adds are done.  If I .add the parallel rotation tween AFTER I .add the nested timeline containing the bezier tweens, I see what I described above.  If I reverse the order, such that the beziers are added to the outer timeline last, I get the more "expected" visual -- ie, BOTH the proxy AND the animation object track with AR..   So, this seems to support the theory that both tweens (the independent rotation tween and the AR action within the bezier tween) take their turn manipulating rotation of the proxy object... and, the one that is added last, also apparently runs last, and therefore determines which of the two rotation effects is made visible at time of screen refresh.. (?)

 

Thanks,

 

Doug

Link to comment
Share on other sites

Like Jamie, I'm really struggling to visualize how your code is set up and what is happening when it runs.

 

I'm confident a very basic demo that shows how you are nesting timelines/tweens and using this proxy object will help us better explain the behavior.

Link to comment
Share on other sites

I'll try to find some time to distill it to some sort of demo in AS..

 

I guess the stripped down crux of the question is this, which is pretty simple:

 

Imagine a timeline with 2 concurrent tweens, both starting at time 0, and both targeting the same object. One is a bezier tween with autoRotate enabled, the other is a simple rotation tween. So, both are acting (or would act) on the same object's rotation property. Both tweens are added dynamically to the timeline, via two separate .add statements, while the timeline is paused. Then, later the timeline is kicked off to run from time 0.  So, I'm wondering which tween would be expected to "win" as far as controlling the rotation of the object, and why (ie, what are the factor(s) that would allow me to know which one should win).   That's the core of the question.

 

Now that I see the various options under "overwrite" (that Jamie pointed out), I'm sure that is part of the answer, but I'm still not clear on the order of evaluation -- that seems to be tied to the order that the tweens are added to the timeline in the first place, but not sure.

 

You may wonder why I'd want to set up such a condition... well, the UI for this app gives the user a choice to have an independent rotational tween set on the object as it moves along the bezier path, OR to use the beziers autorotate for orienting to path, OR possibly even some combination of both that would shift at different points along the path (which is possible since my path is made up of a series of separate bezier tweens)... so,  I'm trying to understand this interaction so that I can provide predictable and repeatable options via the UI, and know for sure when I could expect one or the other to win out if they both happen to be enabled at the same time for a certain segment of the object's path.

Link to comment
Share on other sites

I wouldn't recommend creating two competing tweens that are scheduled to run at the same time in the first place. That's kinda asking for trouble. However, GSAP does automatically try to help you by implementing intelligent overwriting. Basically, the "auto" (default) mode works like this:

 

When a tween renders for the first time, it looks for other tweens of the same target and then analyzes whether or not the timing overlaps - if it finds an overlapping one, it'll kill just the property of that tween that overlaps (if any exist). And if two tweens are placed directly on top of each other (same start time), the order that you insert them is honored (so the one you add last runs last). 

 

Does that answer your question? 

 

If you want to force one to run after the other (for whatever reason), you could always add a super small amount of time to offset it, like if one starts at 2 seconds, you could make the other one start at 2.00000001. 

 

But again, I wouldn't recommend creating competing tweens to begin with if you can possibly avoid it. 

 

Oh, and by the way, when it's looking for "competing" tweens, it ignores paused tweens (for obvious reasons). 

Link to comment
Share on other sites

Jack,

 

Thanks much... that's exactly the insight I was looking for,  particularly : 

"...if two tweens are placed directly on top of each other (same start time), the order that you insert them is honored (so the one you add last runs last)..." 

 

That probably confirms the theory mentioned earlier as to why I was seeing my proxy object rotate per the concurrent rotation property tween, while the targeted animation object (which is made to track the proxy object's position and rotation via the bezier's onUpdate), was, "surprisingly", able to rotate according to the bezier's autoRotate (AR) at the same time...  Seems the proxy object first was rotated per AR, since the bezier tween was added first, then the bezier's onUpdate handler was run which made the animated object track that AR-driven proxy rotation, then the concurrent rotation tween (added second) was allowed  to re-rotate the proxy object , and then eventually would come the next screen redraw -- net result: the proxy object visually appeared to rotate according to the rotation tween, while the animated target object rotated per AR...

 

I'll take the advice to avoid this exact circumstance, but bumping into it definitely resulted in getting a better understanding of property tween priority determination, which will help in managing other cases --- such cases are somewhat inevitable when there is an app UI that allows a wide variety of options in defining how the object is tweened along the path.

 

Thanks again,

 

Doug

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...