I'm building an image preload animation, that is a circle/pie that gets drawn. Each 'slice' is totalImages / imagesLoaded. So if there are four images and 2 have loaded, it should draw to 180 over time.
I'm using requestAnimFrame, which is working great, and I've got a deltaTime setup to restrict animation to time, however I'm having trouble getting my head around the maths. The closest I can get is that it animates and eases to near where it's supposed to be, but then the value increments become smaller and smaller. Essentially it will never reach the completed value. (90 degrees, if one image has loaded, as an example).
var totalImages = 4;
var imagesLoaded = 1;
var currentArc = 0;
function drawPie(){
var newArc = 360 / totalImages * this.imagesLoaded // Get the value in degrees that we should animate to
var percentage = (isNaN(currentArc / newArc) || !isFinite(currentArc / newArc)) || (currentArc / newArc) > 1 ? 1 : (currentArc / newArc); // Dividing these two numbers sometimes returned NaN or Infinity/-Infinity, so this is a fallback
var easeValue = easeInOutExpo(percentage, 0, newArc, 1);
//This animates continuously (Obviously), because it's just constantly adding to itself:
currentArc += easedValue * this.time.delta;
OR
//This never reaches the full amount, as increments get infinitely smaller
currentArc += (newArc - easedValue) * this.time.delta;
}
function easeInOutExpo(t, b, c, d){
return c*((t=t/d-1)*t*t + 1) + b;
}
I feel like I've got all the right elements and values. I'm just putting them together incorrectly.
Any and all help appreciated.
You've got the idea of easing. The reality is that at some point you cap the value.
If you're up for a little learning, you can brush up on Zeno's paradoxes (the appropriate one here being Achilles and the Tortoise) -- it's really short... The Dichotomy Paradox is the other side of the same coin.
Basically, you're only ever half-way there, regardless of where "here" or "there" may be, and thus you can never take a "final"-step.
And when dealing with fractions, such as with easing, that's very true. You can always get smaller.
So the solution is just to clamp it. Set a minimum amount that you can move in an update (2px or 1px, or 0.5px... or play around).
The alternative (which ends up being similar, but perhaps a bit jumpier), is to set a threshold distance. To say "As soon as it's within 4px of home, snap to the end", or switch to a linear model, rather than continuing the easing.
Related
I am new to EaselJs.
I am rotating a wheel with 9x numbers and 3 (0x), the total of 12 numbers. I am able to rotate the wheel by calling function, but I want to stop it on predefined specific point/number of the wheel.
var _oContainer;
this._init = function(iXPos,iYPos){
_oWheel = s_oSpriteLibrary.getSprite("wheel_circle");
_oContainer = new createjs.Container;
_oContainer.x = CANVAS_WIDTH - 200;
_oContainer.y = CANVAS_HEIGHT - 350;
s_oStage.addChild(_oContainer);
img = createBitmap(_oWheel);
img.regX = _oWheel.width / 2;
img.regY = _oWheel.height / 2;
_oContainer.addChild(img);
}
this.spin = function(b, a){
//var h = new createjs.Bitmap(s_oSpriteLibrary.getSprite('wheel_circle'));
createjs.Tween.get(_oContainer).to({
rotation: _oContainer.rotation + a
}, 2600, createjs.Ease.quartOut).call(function() {
_oContainer.rotation %= 360;
})
}
I am calling the spin function as this.spin(5, 1131.7511808994204); on every time button is clicked.
Right now it is spinning and stopping randomly on every button click. How can stop it on a specific number/position on the wheel?
What value should I give in rotation:?
There are a lot of factors in play to do something like this. I made a quick demo to show how I would do it:
Draw the wheel (at center) with segments. It is important to know how many segments you have so you can choose a place to "end"
Start spinning. Just increment the rotation each tick depending on how fast you want it to go.
When "stopping", you have to do math to determine where to land
To get a realistic "slow down", make sure the remaining rotation in the tween is enough so it doesn't speed up or slow down too rapidly
Here is the fiddle: https://jsfiddle.net/lannymcnie/ych1qt8u/1/
// Choose an end number. In this case, its just a number between 0 and the number of segments.
var num = Math.random() * segments | 0,
// How many degrees is one segment?
// In my demo I use radians for `angle`, so I have to convert to degrees
angleR = angle * 180/Math.PI,
// Take the current rotation, add 360 to it to take it a bit further
// Note that my demo rotates backwards, so I subtract instead of add.
adjusted = c.rotation - 360,
// Determine how many rotations the wheel has already gone since it might spin for a while
// Then add the new angle to it (num*angleR)
// Then add a half-segment to center it.
numRotations = Math.ceil(adjusted/360)*360 - num*angleR - angleR/2;
Then I just run a tween to the new position. You can play with the duration and ease to get something you like.
createjs.Tween.get(c)
.to({rotation:numRotations}, 3000, createjs.Ease.cubicOut);
Technically, I should change the duration depending on the actual remaining spin, since depending on the result, it might not be super smooth. This came close enough, so I left it as-is.
Hope that helps! Let me know if I can clarify anything further.
I want to move the object in my case its a plane along the shown curve on page scroll step by step taking into consideration the amount of scroll value.firstly the object moves in straight line and then after a point it changes its direction and move in that direction.How to calculate those co-ordinates?
There are two ways you could get this one. I'll try to explain both in detail.
Scenario 1: Simple path like in the question.
Scenario 2: Arbitrary complex path.
Scenario 1:
In this case you can use a simple formula. Let's go with y = -x^2. This will yield a parabola, which has a similar shape as the path in the question. Here are the steps for what to do next (we assume your scrolling element is the body tag and I assume you have jquery):
Get the "y" value of the body using the following code:
var y = $("body").scrollTop();
Plug this value into the formula. I will give 3 examples where y is 0, 100 and 225 respectively.
//y=0
0 = -x^2
-x = sqrt(0)
x = +/- 0
So if we scroll and we are at the top of the page, then x will be zero.
//y=100
100 = -x^2
-x = sqrt(100)
x = +/- 10
The equation yieldsx as either positive of negative x but we only want positive so be sure to Math.abs() the result.
//y=225
225= -x^2
-x = sqrt(225)
x = +/- 15
From this you can see that the further we scroll down the more the object moves to the right.
Now set the "left" css of your object to the calculated value. This should be enough for this method.
Scenario 2
For more complex paths (or even random paths) you should rather put the x-values into an array ahead of time. Lets say you generate the array randomly and you end up with the following x-values:
var xvals = [0, 0.5, 1, 0.5, 0];
We use normalized x-values so that we can later calculate the position independent from screen size. This particular series of values will cause the object to zig-zag across the screen from left to right, then back to left.
The next step is to determine where our scroll position is at relative to the total scroll possibility. Lets say our page is 1000px in height. So if the scoll position is at zero then x = 0. If scroll = 500 then x = screenWidth. If scroll = 250 then x = 0.5 * screenWidth etc.
In the example I won't multiply with screen width for the sake of simplicity. But given the x value this should be simple.
The first thing you might want to get ready now is a lerping function. There is plenty of example code and so on so I trust that part to you. Basically it is a function that looks like this:
function lerp(from, to, prog);
Where from and to are any values imaginable and prog is a value between 0 and 1. If from is 100 and to is 200, a prog value of 0.5 will yield a return of 150.
So from here we proceed:
Get the scroll value as a normalized value
// scrollval = 200
var totalScroll = 1000;
var normScroll = scrollval/totalScroll; // answer is 0.2
Before we get to lerp we first need to get the x-values to lerp from and to. To do this we have to do a sort of lerping to get the correct index for the xvals array:
// normScroll = 0.2
var len = xvals.length; // 5
var indexMax = Math.ceil((len-1) * normScroll); // index = 1
var indexMin = Math.floor((len-1) * normScroll); // index = 0
Now we know the 2 x values to lerp between. They are xvals[0] which is 0, and xvals[1] which is 0.5;
But this is still not enough information. We also need the exact lerping "prog" value:
// We continue from the x indeces
scrollRange.x = totalScroll / len * indexMin; // 0
scrollRange.y = totalScroll / len * indexMax; // 250
var lerpingvalue = (scrollVal - scrollRange.x) / (scrollRange.y - scrollRange.x);// 0.8
Now we finally have everything we need. Now we know we need a value between xvals[0] and xvals[1] and that this value lies at 80% between these two values.
So finally we lerp:
var finalX = lerp(xvals[0], xvals[1], lerpingvalue);// 0.4
Now we know that the x coordinate is at 0.4 of the total screen size.
Trigger these calculations on each scroll event and you should be on your way.
I hope this was clear enough. If you try and try and can't get results and can show how hard you tried then I'll be happy to write a complete index.html sample for you to work from. Good luck!
EDIT: I made a mistake with the lerpingval calculation. Fixed now.
I am doing a small JavaScript animation hoping that the little div can move along a sine wave, and for the moment the horizontal path works fine (just straight line). I am almost sure that my math formula for the Y axis is wrong. I have tried to correct it with some examples I found, but none of them worked for me. In all the possibilities I tried, the Y axis is ignored and the little box just moves in straight line horizontally.
How can I fix this, so the movement goes along a sine wave? I know that it's possible to do it easier with jQuery or using html 5, but I just got wondering what is wrong in my original code... I would prefer to fix this if possible.
function animate() {
xnow = item.style.left;
item.style.left = parseInt(xnow)+1+'px';
ynow = item.style.top;
item.style.top = ynow + (Math.sin((2*Math.PI*xnow)/document.width))*document.heigth;
setTimeout(animate,20);
}
The complete code here:
JSFiddle
I see several problems with your code:
xnow contains a string in this format: ###px You cannot multiply it, so use parseInt() in your Math.sin() call.
Same goes for your code to grab ynow, it needs parseInt().
Better is to use other (global) variables to store the x and y coordinates as numbers. And add px when you update coordinates of the div-element.
When you multiply 2*Math.PI with xnow (which contains only integer numbers), the sin() function will always return 0. So you won't get a sine-like movement. You need to divide xnow by the number of x-steps you want to use to do a complete sine-like movement
Math.sin() returns a value between -1 and +1, so you need to multiply it by an amplitude to see a (more clear) effect.
To keep it as much as you designed it, it would become something like this (takes 50 x-movement steps to do a complete sine and uses an amplitude of 10 pixels):
function animate() {
xnow = parseInt(item.style.left);
item.style.left = (xnow+1)+'px';
ynow = parseInt(item.style.top);
item.style.top = (ynow+Math.sin(2*Math.PI*(xnow/50))*10) + "px";
setTimeout(animate,20);
}
As mentioned: it is much better to use some global variables containing the values (instead of using parseInt() all the time)
See my updated JSFiddle example.
A sin function is in the form of y = a * sin(b*x) + c, where c is the y-midpoint of the function (or the horizontal line across which the function oscillates), where a is the amplitude (maximal y-offset from y = c) and b is the period (number of x = 2*pi segments per wave).
Given that, and that we know a sin wave oscillates from -a to +a, we know our offset (c) should 1) be constant and 2) halfway between our upper and lower bounds. For this we can use
c = document.height / 2;
Your amplitude will be the same value as c, if you want the object to traverse the entire screen. On testing you will find that this makes it go past the bottom of the page, so let's just make it 90%.
a = 0.9 * c;
For a period of 1 for the entire page, you'll need to make b multiply x by a factor such that it will be the fraction of 2*pi. In this case
b = 2*Math.PI/document.width;
On each iteration, there is no need to get the value of ynow, it is a function of xnow. You can do something along the lines of
xnow = parseInt(item.top.left) + 5;
Then calculate the new y with
ynow = c + a * Math.sin(b * xnow);.
Then set the style of the item.
item.style.left = xnow + 'px';
item.style.top = ynow + 'px';
Let me know if anything was unclear. Regards.
You need to use parseInt() on xnow. You also need to add 'px' to the end of the of the number to make into a correctly formatted string.
This code works:
function animate() {
xnow = parseInt(item.style.left);
item.style.left = xnow+1+'px';
item.style.top = 200 + (Math.sin((2*Math.PI*xnow)/200))*document.height+'px';
setTimeout(animate,20);
}
There are a couple errors:
height is misspelled
Parse xnow as an int before taking the sine
Parse ynow as an int before adding it (though it's not actually necessary, see below)
Add "px" to the end of the assignment to item.style.top
The equation could use some tweaking:
I suggest starting with (400 + Math.sin(2*Math.PI*xnow/document.width) * 200) + "px" and then playing around with it. The 400 is the horizontal axis to base the sine wave on. If you use ynow instead of a constant, you get cumulative effects (the wave will be much taller than you intend or the horizontal axis will change over time).
document.width is the width of one full period. The 200 is the peak amplitude (distance from the horizontal to a peak - document.height would push the box off screen in both directions). Plug in this function in place of the current one and then you can play around with the numbers:
function animate() {
xnow = parseInt(item.style.left);
item.style.left = xnow+1+'px';
item.style.top = (400 + Math.sin(2*Math.PI*xnow/document.width) * 200) + "px";
setTimeout(animate,20);
}
First of all I want to mention two things,
One: My code isn't perfect (esspechially the eval parts) - but I wanted to try something for my self, and see if I could duplicate the jQuery Animation function, so please forgive my "bad" practices, and please don't suggest that I'll use jQuery, I wanted to experiment.
Two: This code isn't done yet, and I just wanted to figure out what makes it work badly.
So the animation runs for about 12 seconds while the duration parameter I entered was 15 seconds, What am I doing wrong?
function animate(elem, attr, duration){
if(attr.constructor === Object){//check for object literal
var i = 0;
var cssProp = [];
var cssValue = [];
for(key in attr) {
cssProp[i] = key;
cssValue[i] = attr[key];
}
var fps = (1000 / 60);
var t = setInterval(function(){
for(var j=0;j<cssProp.length;j++){
if(document.getElementById(elem).style[cssProp[j]].length == 0){
//asign basic value in css if the object dosn't have one.
document.getElementById(elem).style[cssProp[j]]= 0;
}
var c = document.getElementById(elem).style[cssProp[j]];
//console.log(str +" | "+c+"|"+cssValue[j]);
if(c > cssValue[j]){
document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));
}else if(c < cssValue[j]){
document.getElementById(elem).style[cssProp[j]] += 1/((duration/fps)*(c-cssValue[j]));
}else if(c == cssValue[j]){
window.clearInterval(t);
}
}
},fps);
}
}
animate('hello',{opacity:0},15000);
html:
<p id="hello" style="opacity:1;">Hello World</p>
Note: I guess there is a problem with the
(duration/fps)*(c-cssValue[j])
Part or/and the interval of the setInterval (fps variable).
Thanks in advance.
I'm not gonna try and refactor that and figure it out, cause it's pretty wonky. That said... a few things.
Don't rely on the value you are animating to let you know animation progress
In general your approach is unsound. You are better off keeping track of progress yourself. Also, as a result of your approach your math seems like it's trying too hard, and should be much simpler.
Think of it like this: your animation is complete when the time has elapsed, not when the animated value seems to indicate that it's at the final position.
Don't increment, set
Floating point math is inexact, and repeated addition cumulation like this is going accumulate floating point errors as well. And it's far more readable to make some variables to keep track of progress for you, which you can use in calculations.
animatedValue += changeOnThisFrame // BAD!
animatedValue = valueOnThisFrame // GOOD!
Don't do the positive/negative conditional dance
It turns out that 10 + 10 and 10 - (-10) is really the same thing. Which means you can always add the values, but the rate of change can be negative or positive, and the value will animate in the appropriate direction.
timeouts and intervals aren't exact
Turns out setTimeout(fn, 50) actually means to schedule the fn to be call at least 50ms later. The next JS run loop to execute after those 50ms will run the function, so you can't rely on it to be perfectly accurate.
That said it's usually within a few milliseconds. But 60fps is about 16ms for frame, and that timer may actually fire in a variable amount of time from 16-22ms. So when you do calculations based on frame rate, it's not matching the actual time elapsed closely at all.
Refactor complex math
Deconstructing this line here is gonna be hard.
document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));
Why for more complex break it up so you can easily understand what's going on here. refactoring this line alone, I might do this:
var style = document.getElementById(elem).style;
var changeThisFrame = duration/fps;
var someOddCalculatedValue = c-cssValue[j];
style[cssProp[j]] -= 1 / (changeThisFrame * someOddCalculatedValue);
Doing this makes it clearer what each expression in your math means and what it's for. And because you didn't do it here, I had a very hard time wondering why c-cssValue[j] was in there and what it represents.
Simple Example
This is less capable than what you have, but it shows the approach you should be taking. It uses the animation start time to create the perfect value, depending on how complete the animation should be, where it started, and where it's going. It doesn't use the current animated value to determine anything, and is guaranteed to run the full length of the animation.
var anim = function(elem, duration) {
// save when we started for calculating progress
var startedAt = Date.now();
// set animation bounds
var startValue = 10;
var endValue = 200;
// figure out how much change we have over the whole animation
var delta = endValue - startValue;
// Animation function, to run at 60 fps.
var t = setInterval(function(){
// How far are we into the animation, on a scale of 0 to 1.
var progress = (Date.now() - startedAt) / duration;
// If we passed 1, the animation is over so clean up.
if (progress > 1) {
alert('DONE! Elapsed: ' + (Date.now() - startedAt) + 'ms');
clearInterval(t);
}
// Set the real value.
elem.style.top = startValue + (progress * delta) + "px";
}, 1000 / 60);
};
anim(document.getElementById('foo'), 5000);
JSFiddle: http://jsfiddle.net/DSRst/
You cannot use setInterval for accurate total timing. Because JS is single threaded and multiple things compete for cycles on the one thread, there is no guarantee that the next interval call will be exactly on time or that N intervals will consume the exact duration of time.
Instead, pretty much all animation routines get the current time and use the system clock to measure time for the total duration. The general algorithm is to get the start time, calculate a desired finish time (starttime + duration). Then, as you've done, calculate the expected step value and number of iterations. Then, upon each step, you recalculate the remaining time left and the remaining step value. In this way, you ensure that the animation always finishes exactly (or nearly exactly) on time and that you always get exactly to the final position. If the animation gets behind the ideal trajectory, then it will self correct and move slightly more for the remaining steps. If it gets ahead for any reason (rounding errors, etc...), it will dial back the step size and likewise arrive at the final position on time.
You may also need to know that browsers don't always support very small timing amounts. Each browser has some sort of minimum time that they will allow for a timer operation. Here's an article on minimum timer levels.
Here's an article on tweening (the process of continually recalculating the step to fit the duration exactly).
I'd also suggest that you look at the code for doing animation in some libraries (jQuery, YUI or any other one you find) as they can all show you how this is done in a very general purpose way, including tweening, easing functions, etc...
I'm developing an HTML5 3D fps-like engine that already looks quite nice, but as this might be one of the worst language choices to make 3D there's noticeable lag sometimes.
I programmed movements (WASD) to be independent of rendering speed, so sometimes it's quite jerky, but other times is working at an acceptable 30+ fps (depending on CPU of course).
The only thing I can't wrap my mind around is jumping: currently the jumping is done by adding a positive constant to the falling variable (gravity is always negative and then corrected by collision detection) and then subtracting a constant, this is called every time a new frame is rendered, the thing is that when fps go low I feel like I'm on the moon. I prefer jerkiness to slow-mo effect.
If I use the same method like I do for moving (calculate time between current and last frame) the deducted variable gets too big sometimes and the jumping apex changes (to half of the value compared to high fps) - this is unacceptable as jumping height must be always the same.
Here's some pseudo-code to help understanding the problem (called during one rendering routine):
// when clicked on spacebar:
if(spacebar)
{
// this defines jumping apex
jump = 0.5
}
// constant added to y (vertical position) later in the code
cy += jump;
// terminal velocity = -2
if(jump > -2)
{
// gravity (apex multiple to get maximum height)
jump -= 0.05;
}
if(collision_with_floor)
{
// stop falling
cy = 0;
if(jump < 0)
{
jump = 0;
}
}
player.position.y += cy;
Now with time dependent jumping (replace in the code above):
// terminal velocity = -2
if(jump > -2)
{
// gravity, 0.4 is an arbitrary constant
jump -= (now - last_frame)*0.4;
last_frame = now;
}
To illustrate even better here's an image of what's going on:
Blue dots indicate frame renders.
I'm not even sure of this is the right way to program jumping routine. Basically jerkiness and constant jumping height is better than smoothness and slow-mo effect.
If the frame updates are coming too slowly to get accurate physics, then maybe you can hack in the jump apex so that the player always hits it. The cue here might be when the y velocity changes from positive to negative. If I'm reading your pseudocode right, then it looks like:
old_cy = cy;
cy += jump;
if(old_cy > 0 && cy <= 0)
player.position.y = jump_apex_height;
In terms of your graph, the idea is that you want to identify the blue dot that reaches the orange line, then bump it up to the dotted line.
And now that I'm thinking about it, if the player really has to reach the jump apex every time, then this might help even for high-rate updates.