tween.js
tween.js copied to clipboard
tween jumps (jitters) to the final value at the end of animation
Hi, thanks for this great lib its really useful!
But i've fallen into some trouble that i cant figure out. Within my three.js scene i have camera and avatar objects in a container object that is moved with my controller. On collision with another object in my scene the camera is meant to tween back and up slightly but instead of any animation it just jumps to the finished position.
This only happens while the avatar is being moved, if i initiate the tween straight away when the avatar isn't being moved the animation runs fine.
Is anybody aware of what might be causing this? and a possible fix? I'm truly stumped
This is the tween setup code i'm using incase its just a simple setup issue i've missed:
var camPosition = { x : 0, y : 5, z : 50 };
var camTarget1 = { x : 0, y : 10, z : 75};
tweenCamToOrig = new TWEEN.Tween(camTarget1)
.to(camPosition, 2000)
.easing( TWEEN.Easing.Back.InOut )
.onUpdate(function(){
camera.position.x = camPosition.x;
camera.position.y = camPosition.y;
camera.position.z = camPosition.z;
})
.onComplete(function(){
});
It's hard to say what's going on with just a bit of code which has nothing to do with the moment where you actually start the tween. You should provide some working online example (with codepen or jsfiddle or similar) so I can see it happening.
If anything, I'd guess you're trying to tween to absolute values but what you want is a relative tween to the camera position when it collides with something. Try using something like camTarget = { x: "+0", y: "+10", z: "+75" }
?
Ah okay sorry, i was hoping it might of been a regular or known issue. Thanks for your suggestion, I implemented it but it made no change, I've created a much simpler version of my game and made a codePen out of it: http://codepen.io/Keeley/pen/Kpdyl
However looking in the chrome javascript console it says TWEEN is undefined, but nothing has change from my local file so it should work. I tried various different host for tween.js as well but it still doesn't recognise TWEEN.
This has puzzled me even more, i cant see any reason for it not to work within the pen. If you wouldnt mind taking a look for me i would appreciate it immensely.
If you manage to get it to recognise Tween and it runs, when you move the avatar into an object in front of you you'll see the tween jump to its finished position. (its using the Three.js fly controller so WASD and mouse to look)
I also highlighted the tween.js code with large ////////////Comments\\\ so they will be easy to spot.
Thanks again!
I thought it unfair, unlikely and a bit of a waste of time for me to ask you to debug codepen, so i found out how to create a Git repo and have added my working simplified code to it.
It should be much easier to read on there heres the repo: https://github.com/KeeleyMoore/Conquer-the-Cosmos-simplified
The reason for asking you to use codepen or similar is so that I don't have to download/clone the repository then open the folder in my local drive, see what the structure is, figure out what's the error and send you a Pull Request - with codepen I can just save a new version and that should be it!
That aside, your code will never work because you're not including any of the libraries upon which your code depends :-P
So a couple of <script>
tags with the right source should be added ;-)
Ah my apologies, I'm fairly new to this style of coding and haven't really asked for help before!
I also thought that if i was to use the "add external resource" link in codepen that it would of worked as a source tag equivalent.
Anyhow turns out that the reason it didn't recognise TWEEN in the first place was because i was stupidly linking to a create.js's tweenJs. I just assumed when looking for a hosted version that it was the same thing, my bad.
I hosted my own version of tween.js just so i was positive its was the correct file and everything works now :P The codepen URL: http://codepen.io/Keeley/pen/Kpdyl
Just hold down the left mouse button and guide the avatar until you collide with another sphere and you'll see the camera jumps to the target position
Hey
Sorry I don't have the time/patience to make it collide but make sure you use "+10"
and not just +10
when specifying the desired values to tween to. The quotes matter in this particular case. I'm afraid I still haven't got to document this yet, but have a look at the relative values example to see the syntax in use: https://github.com/sole/tween.js/blob/dev/examples/09_relative_values.html
Hey again,
i implemented your suggestion but had no luck, i also studied the link your proved but that didnt help either. I have completely stripped the codePen down so now the camera tween is called on a 2 second delay from initialisation. http://codepen.io/Keeley/pen/Kpdyl
Im pretty sure that my error is with the code inside my Update function but i cant figure out the format i'm meant to use.
Sorry to keep on bugging you but i'm desperate!
Im going to start off again by apologise for persistently bugging you. But i'm sure you'll be glad to hear that it will stop now :P
I managed to find a fix, once i knew the problem was in the update function it was easier to search for a solution. I stumbled upon this for anyone who may see this and have the same issue: http://www.96methods.com/2012/01/three-js-moving-objects/
also here is the working code's setUp function:
function tweenSetUp(){
var camPosition = { x : 0, y : 5, z : 50 };
var camTarget1 = { x : 0, y : 10, z : 75};
var camTarget2 = { x : 0, y : 100, z : 1000};
var actualXpos= 0;
var actualYpos = 5;
var actualZpos= 50;
tweenCam1 = new TWEEN.Tween(camPosition)
.to(camTarget1, 2000)
.easing( TWEEN.Easing.Quadratic.InOut )
.onUpdate(function(){
// Calculate the difference between current frame number and where we want to be:
var differenceX = Math.abs(camPosition.x - actualXpos);
actualXpos = camPosition.x;
var differenceY = Math.abs(camPosition.y - actualYpos);
actualYpos = camPosition.y;
var differenceZ = Math.abs(camPosition.z - actualZpos);
actualZpos = camPosition.z;
// Moving in -Z direction:
camera.translateX( +differenceX);
camera.translateY( +differenceY);
camera.translateZ( +differenceZ);
});
}
Thanks again for your help, it's much appreciated, along with the effort you've put into this great lib :)
Ah, that would make sense :-)
Glad you found where the error was and I'm thanking you for posting the solution here so more people can take advantage of it!
Thanks a lot KeeleyMoore this post helped me !!
Perhaps we need to make an example / mention this in the docs! @rbary what was your issue exactly and how did this issue or post link help you? I'd like to clarify whatever is it that is not clear! thanks!
Hi my issue was perspective camera (three) animation;
https://github.com/NephtysOrg/projet_sia/other_dev/js/app/game.js
Game.prototype.cameraTranslate = function(){
var actualXpos =this.current_camera.position.x;
var actualYpos =this.current_camera.position.y;
var actualZpos =this.current_camera.position.z;
console.log(this.current_camera.position);
var differenceX = Math.abs(this.current_camera.position.x - actualXpos);
actualXpos = this.current_camera.position.x ;
var differenceY = Math.abs(this.current_camera.position.y - actualYpos);
actualYpos = this.current_camera.position.y;
var differenceZ = Math.abs(this.current_camera.position.z - actualZpos);
actualZpos = this.current_camera.position.z;
this.current_camera.position.x += differenceX;
this.current_camera.position.y += differenceY;
this.current_camera.position.z += differenceZ;
};
Game.prototype.cameraTransition = function (position,look) {
console.log("camera transition function using tweenjs");
var tweenCam = new TWEEN.Tween(this.current_camera.position).to({
x:position.x,
y:position.y,
z:position.z}, 600)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.onUpdate(this.cameraTranslate())
.start();
};
Finaly i found that it isn't necessary to call a translate function in .onUpdate; this code works too:
Game.prototype.cameraTransition = function(position, look) {
var that = this;
var tweenCam = new TWEEN.Tween(that.current_camera.position).to({
x: position.x,
y: position.y,
z: position.z}, 3000)
.easing(TWEEN.Easing.Linear.None)
.onUpdate(function() {
that.current_camera.lookAt(look);
})
.onComplete(function() {
//that.current_camera.rotation.y = 180 * Math.PI / 180;
//that.current_camera.rotation.z = -45 * Math.PI /180;
})
.start();
console.log("tween to player pos");
};
This seems to be an issue with all tweens. The issue become apparent when a small change in the tweened number (the small amount that the number should change in one tick) translates to a visibly large rendering difference.
What I mean is, if suppose we change the rotation of a circle with a texture on it. If the circle has a large radius, then a small change in the rotation of the circle (by Tween.js) can make the jumping become obvious.
TweenMax has the exact same problem (see the live example there): https://greensock.com/forums/topic/14874-long-animations-jumping-at-the-startend-when-using-easing/
In many cases, this is not noticeable because if the small change in number value corresponds to a small visual difference, then you can't really see it with your eye.
But in that example TweenMax, the jump is obvious.
Here's some numbers of a tween that is slowing down, generated by Tween.js, and we can see the jump at the end:
10.046829364239343
10.042912961057219
10.039487976809298
10.036269246798945
10.033420811990638
10.030853057216234
10
This is an Exponential.Out
you see, the last few numbers. There's clearly a jump at the last value that is out of proportion compared to the distance between the other values.
If this tween is the position of a pixel, you may definitely not notice it. But it this tween is tweening across a scaled distance, or rotating a huge object, you can definitely see it.
What causes this? Is it some sort of floating point error? Hmmmmm.......
Re-opening, needs to be solved.
For reference, here it is in the wild: https://stackoverflow.com/questions/34772151/how-to-avoid-camera-jumps-during-after-tween-animation
Alright, so, Popmotion doesn't have the issue. Here's two pens.
The first pen uses Popmotion with a Tween.js easing curve, and the jump is visible:
https://codepen.io/anon/pen/OvROWN
The second pen uses a Popmotion easing curve. This time, the easing curve is nothing fancy the like the "Penner" equations here, just k ** power
here for the exponential curve, and there's no jump:
https://codepen.io/anon/pen/bvwYww
The math is actually "wrong", it's off by .05%
https://www.wolframalpha.com/input/?i=0.5++(-(2)%5E(-10++(k+-+1))+%2B+2)+for+k+%3D+2
Likewise there is a jump at the beginning. One solution is to stretch the function about 0.5 so that the extreme values are exactly at 0 and 1:
g(x) = (f(x) - 0.5) * (0.5 / 0.49951171875) + 0.5
A quick solution which worked in a project I just finished, was to just pass in other easing curves. For example, using Popmotion:
import {easing} from 'popmotion'
import {Tween} from 'tween.js'
// similar to TWEEN.Easing.Exponential.InOut
const curve = popmotion.easing.createMirroredEasing(
popmotion.easing.createExpoIn(4)
),
new Tween(someObject)
.to(...)
.easing( curve )
.start()
Under the hood, Popmotion's expo function is literally just k ** power
where power
is 4
in the above example. Are there any disadvantages with this? I am getting nice results with no jitter.
I did some research on jitter in Exponential
function.
Cause of jump
Easing.Exponential
function, unlike other easing functions, does not interpolate from 0 to 1.
https://github.com/tweenjs/tween.js/blob/ae24c58c8b57b2079151ae2c3c63f2bb501c8b2b/src/Easing.ts#L84
The core of the Exponential function's processing is in this line.
Math.pow(1024, amount - 1)
The interpolation process is here, where amount is a value between 0 and 1.
amount : 0 = 1024^-1 = 1/1024 amount : 1 = 1024^0 = 1
If the amount is 0, the function will not return 0, it will return 1/1024 ( = 0.0009765625 ). This is the cause of the jumps.
https://www.desmos.com/calculator/xbgt04gh4v
If you zoom in on the area around 0 in the graph, you can see a jump of 1/1024.
Difference from popmotion
The expo function of popmotion is implemented as follows.
https://github.com/Popmotion/popmotion/blob/a1903c808757b31d26d876dc2d8d3dea2d131c12/packages/popmotion/src/easing/utils.ts#L14
The interpolation process is as follows
Math.pow(amount, power)
No jumps will occur in this process. If you set the power to about 5.8
, you will get results similar to Exponential. But the results aren't exactly the same.
Proposal
Exponential and linear interpolation can be used to adjust for jumps.
Math.pow(1024, amount - 1) - 1 / 1024 * ( 1 - amount )
This method breaks Exponential's compatibility and slightly slows down its performance. I cannot decide if this is a good idea or not.
I hope this information helps you. Thanks.
@MasatoMakino Hello! Thanks for looking into this. I think proper behavior should come before any optimization, so if you would like to open a pull request with the change you think is best, it would be greatly appreciated.
This method breaks Exponential's compatibility
Can you expand on that?
I really like popmotion's ability to create arbitrary curves. Maybe we can adopt that approach.
@trusktr Thanks for your advice.
I really like popmotion's ability to create arbitrary curves. Maybe we can adopt that approach.
I also think that this approach is the best solution for this issue. I have made a PR that adopts this approach.
This method breaks Exponential's compatibility
Can you expand on that?
I add an explanation to this point.
The easing function is repeatable, always returning the same value when given the same value. But if we patch it, this function returns slightly different values for all amount
. This difference will affect, for example, animation projects that use intermediate values of easing.
This function is inherited from Robert Penner's Easing Functions, which is the base of jquery-easing. I think the reason it hasn't been a problem in the past is because jitter has been acceptable to users. ( The original easing function may have been even older, but I was unable to research it. )
For this issue, I think patching the exponential function does not have the right balance of advantages and disadvantages.
@MasatoMakino I came up with an alternative that is very close to Exponential
, but touches 0,0
:
https://www.desmos.com/calculator/pioplwo3zq
I'll make a PR later, time for bed. :)
I made the PR anyway. :smile:
#619 provides an alternative for Exponential. Need to verify if any other easings still need to a fix.
so this is still not solved yet right? I get the same behavior with popmotion.. same jumpin in the end; how can this be solved?
import { createMirroredEasing, createExpoIn } from "@popmotion/easing";
const expoIn = createExpoIn(5.8)
const curve = createMirroredEasing(expoIn)
new TWEEN.Tween(camera.position)
.to(
{
x: target.position.x,
y: 2,
z: target.position.z + 2
},
1500
)
.start();
@Zaniyar in your code you made a curve
variable and you didn't use it anywhere.