Animation easing utility for JMonkey objects

Edit: Keeping the post for posterity but you should just use Lemur library’s animation.

This is a utility for easing translation, rotation and scaling animations.

Here are some of the effects that can be achieved:

The card uses a bounce scale easing when it is picked up and dropped. The camera uses sine translation easing when it translates. The lookAt position of the camera uses sine translation easing to pan. The card uses cubic rotation easing when it rotates.

I didn’t see anything like this in my forum reading. Let me know if there is already something similar. Here is the code, feel free to use it and let me know if you find any bugs or ideas for extensions / changes.

http://pastebin.com/RbSMD2n1

A typical usage looks like this:
To start animation:

Animatable animatable = new Animatable();
// Instantly translate to 100,100,100
animatable.translateNow(new Vector3f(100,100,100));
// Translate to 0, 0, 0 over 10 seconds
animatable.translateByDuration(new Vector3f(0,0,0), 10);
// Scale to 2, 2, 2 over 5 seconds
animatable.scaleByDuration(new Vector3f(2,2,2), 5);

In update loop:

for (GameEntity entity : entities) {
    Animatable animatable = entity.getAnimatable();
    Spatial spatial = entity.getSpatial();

    animatable.interpolateTranslation(tpf, Easing.SINE_OUT, spatial);
    animatable.interpolateScale(tpf, Easing.ELASTIC_OUT, spatial);
}
4 Likes

Cool, just like MS PowerPoint animations.

I hate to glom onto someone else’s thread with an alternative but maybe it will give you ideas if nothing else.

Lemur has an animation system that is pretty powerful and works with any spatial. You can see some examples here:

Instead of fixed sets of Easings, the Lemur way would be to just wrap additional Tween implementations (of which there are some defaults) to provide curves for linear input and so on.

The parts are very flexible and can be used in other situations also. (For example, a Tween could be used to turn a linear drag into a rotation, etc., outside of an animation.)

Anyway, maybe it gives you some ideas. It’s very modular (and incorporates lots of lessons learned from a few failed attempts.)

Edit: and I really wish the forum would show the actual link and not the trimmed one. If you click on it takes you right to the effects and animation docs.

Awesome ya I was hoping people would post other solutions because I’m sure most projects need something like this. I hadn’t seen or at least recognized any effects like that in Lemur GUIs so never thought to look. Good tip.

The examples section is probably closest to what you’ve posted of yours:

Yep just took a look I like the sequencing idea. Very nice.

So I looked at all the source for the anim package of Lemur and nearly line for line is what my utility did. So when I needed to add more functionality to my implementation I just refactored my code to work with Lemur’s animations instead. For my use case it’s useful to have Vector3fTween and QuaternionTween so, that distinct from Spatials or Cameras or Panels, those values can still be interpolated on an individual basis. I could see other useful individualized tweens like ColorTween or AlphaTween. For instance, this let’s me use the utility to interpolate between 2 Vector3f’s for my camera’s lookAt() paired with a custom LookAtAnimation to rotate my camera rather than just rotating the camera with CameraTweens built in Quaternion rotation.

Here is what that code looks like:

I’d imagine the individualized tweens like Vector3fTween and QuaternionTween would be a nice general solution for the library if you ever wanted to add that.

I would also like to add more Curves as you mentioned but the class is a private class of Tweens. I think what you did with the sequencing and wrapping tweens together was really smart and more mature than what I was doing. Do you have a suggestion on adding my own Curves besides creating a local copy of it that isn’t private? I feel like someone looking to do animations would really want a decent set of stock functions in addition to being able to set custom easing functions. I wouldn’t mind adding any of this if you gave the go ahead.

To create your own Tweens… just create them as you have. You don’t really have to extend the “Tweens” class to do it. It’s just a convenient provider of stock tween implementations but in no way prevents additional tweens from being created.

I didn’t include generic Quaternion and Vector3f tweens because generally they are not useful on their own. (ie: it’s usually something else you want to ‘tween’ and often there is additional convenience setup in those cases… for example grabbing the initial rotation of the object.) For Vector3f especially it’s a bit dangerous as people might use it for direction vectors and then it would produce bad results.

…still some base classes could be useful, I guess, so that camera and spatial interpolation might share the same interpolation code. I also didn’t find the code to be more than 1-2 lines and my DRY (Don't repeat yourself - Wikipedia) instincts have a threshold. :slight_smile:

But sure if you create some curve and other basic tweens that seem generally useful then I don’t mind including them if you provide them. I included kind of a minimum amount to do interesting things and always planned to go back and expand them over time.

I’m making some of those changes I’ll let you know when I’m finished. Quick question and wondered this last night as well. Generally JME uses floats for time. In some of the Tween classes you use double internally and cast back to float when interacting with JME like RotateSpatial:

@Override
protected void doInterpolate( double t ) {
    // Interpolate
    value.slerp(from, to, (float)t);
    target.setLocalRotation(value);
}

As I’m implementing a suite of easing functions the original CurveFunction class’ time was also typed on double and math was done with Java Math. Would you rather use floats there and FastMath or stick with double and Math?

Definitely double.

In general, the only down side of double in Java is that it’s twice as big, ie: takes twice the memory… but it’s just as fast (or faster) than 32 bit floats as on many platforms the 32 bit floats will be done using the same 64 bit math anyway. (my understanding)

JME uses floats mostly because OpenGL does, basically. For my own code, I use doubles for most things.

Kind of hit a roadblock when it came to testing. Not really familiar with how to get the Lemur Gradle project to play nice with my Maven project so I can test the results. Just trying to build the project at the moment in Eclipse but getting:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ‘:javadoc’.
Caused by: org.gradle.process.internal.ExecException: Process ‘command ‘C:\Program Files\Java\jdk1.8.0_121\bin\javadoc.exe’’ finished with non-zero exit value 1

Do you have another contact that I might be able to reach you if the forum isn’t the best place for this type of interaction? Feel free to PM me.

You shouldn’t have to build Lemur… just use the jars, eh?

I gotta tell you… it really burns me up that maven is causing me problems in this aspect of my life now, too. I deal with it’s BS every single day of the week. I really hate that tool with the furied heat of a thousand million suns. It needs to burn in a hot fire in the bowels of hell. (Right now I cannot think of a tool I’ve ever hated more than maven… and that includes Oracle. So…)

But anyway, now that I have that out of my system … why not just build Lemur with gradle if you need to. gradle install will install the jars in your local maven repository and it will be available to any other projects you need.

…but if your reason for building Lemur is because you are trying to modify lemur for code inclusion then you will need to build it with gradle anyway. Because I outright refuse 1000% to debug issues that are caused by someone building with a different build tool… especially that POS that is maven.

I’ve made some significant changes to the animation library in Lemur. I would like to work with you on integrating the changes I’ve made and would appreciate you describing what the process would be like and the time expectation for getting these changes into the library even if not in a stamped release version. Let me know if you have any package structures, styling conventions, implementation changes you’d like to see.

Here is an example of it running live:

Here is a dropbox containing the changes so far: Dropbox - File Deleted

Although you’ve probably seen them before here is a site that has many easing functions so that we are on the same page: http://easings.net/

I think the only untouched file was Animation.java but most existing files didn’t change much. The highlights of my changes are opening up what used to be the Curves class to a more canonically (in terms of animation jargon) Easing class. You had initial easing functions like sine in out and hermite in easing. I’ve added many of the ones you see on that page and with the way the code is changed many more of them can be added. Most went in without a problem. Here are the easing functions that were added:

PowerEasings (In/Out/InOut) can do any t^power
ExponentialEasings (In/Out/InOut) can do any base^t
SineEasings (In/Out/InOut) Added in and out versions of the inOut that was already there
ElasticEasings (Out) can be customized for how elastic you want the effect to be

Many presets easing functions are in the EasingFunctions class and the 2 you started with are still there. Feel free to let me know changes to the defaults you’d like to make. Users can now easily create their own easing functions to achieve any effect as the EasingFunction class is public.

On the easing website I linked you can see certain easing effects can go beyond 0 to 1 to achieve an interesting result like elasticity. I only implemented one of them for now as a proof of concept in ElasticOutEasing. That required being able to interpolate with 2 variables: time (to determine when to stop like it was already doing) and modulatedTime (which is the result of the easing function allowing the value to go outside of the 0 to 1 time bounds). You can reference these changes in Tween, AbstractTween, SpatialTweens and the like.

Here is how I’m using from the users perspective:

Tween scale = SpatialTweens.scale(this, null, pickupScale, .5);
Tween easing = Tweens.ease(EasingFunctions.SINE_IN_OUT, scale); // New function for default and custom easings

Tween scale = SpatialTweens.scale(this, null, normalScale, .5);
Tween easing = Tweens.ease(new ElasticOutEasing(.5), scale); // Custom easings

Tween rotate = SpatialTweens.rotate(this, null, rotation, Widget.rotationDuration);
Tween easing = Tweens.ease(EasingFunctions.ELASTIC_OUT, rotate);
2 Likes

I will have to look at this in more detail when I have time. It may be that we need to split the curve stuff out into a separate utility class. I’ll have to see.

Didn’t want to pester at the time and you are a busy fellow but was wondering if you ever took a look at these changes and if you wanted to add them into the library. Not sure how much drift has occurred between then and now but would be cool. I’ve always just been running with a custom lemur jar. Let me know.

What I don’t understand is why you had to modify Lemur’s classes to do this and couldn’t have just done it as an add on utility. I haven’t had time to download a source brick to poke through (and probably won’t for 18 months or so.)

If you are willing to explain why you had to make Lemur modifications then maybe we can discuss it from there.

To answer your question I spent some time looking over the code to refresh my memory and as an exercise left the changes in a separate branch locally on lemur and moved the important files into my project.

Initially I wanted to modify the library so that other people could use it if they found it helpful. Some libraries have a great suite of Easings that you can apply and I thought it would also be useful to bring that to Lemur users. Now why did I have to make changes to the underlying library? If you look on the page I linked some of the easings go below 0 and above 1. The way lemur is currently written seems to support easings exclusively between 0 and 1 or length. The majority of the changes I made were to accommodate that. There are some other organizational things I tried to change like package structure since there could be many Easings now, changing names like Curve to Easing etc but those are more aesthetic.

As a concrete example:

   @Override
    protected void doInterpolate( double t ) {
        // Interpolate
        value.interpolateLocal(from, to, (float)t);
        target.setLocalScale(value);
    }

became

   @Override
    protected void doInterpolate( double t ) {
        doInterpolate(t, t);
    }
    
    @Override
    protected void doInterpolate(double time, double modulatedTime) {
    	// Interpolate
        value.interpolateLocal(from, to, (float)modulatedTime);
        target.setLocalScale(value);
    }

which allowed for the scale to change below 0 and above 1 or length.

Yeah, from Lemur’s animation perspective, interpolations outside of 0…1 don’t make sense. 0 is the start, 1 is the end. What does “after the end” or “before the beginning” mean? it would break some things for sure.

I was just making sure there weren’t core modifications required to make your stuff work since it could be a sign that I missed something.

Being able to point users to your add-on is the quickest way to get feedback before I get off my butt and integrate anything into Lemur itself.

After your input and looking at it harder and testing I think I can make it work without changing the underlying library. I think I based my Easing class too much off of your Curve class and by making some changes can make it work without underlying library changes. There was a subtlety between time and the result of the easing function that I didn’t see could be handled in a certain way that I think I understand better now. If I get it to a decent point I’ll upload my add ons.

2 Likes