I'm using Raphael.js to animate an SVG circle's radius on hover. I like the stock elastic effect that the library offers, but I'd like to increase the amplitude - i.e., make the circle grow and shrink with a lot more gusto when it's hovered - not with extra speed, but to grow larger and shrink smaller when the effect runs.
I copied the elastic function and renamed it super_elastic, and have been tinkering with it here:
http://jsfiddle.net/ryWH3/
I have no idea how the function works, so I've just been tinkering with its numerical values to see what happens. So far I haven't found anything that appears to do what I want. Can anyone recommend any modifications to the function (or a different function altogether) that might do what I'm looking for?
Thanks!
UPDATE:
Thanks for the replies! Sorry, I may not have explained this very well. I'm guessing that the statement "grow larger and shrink smaller" was especially misleading.
I'm aware that the r property affects the final radius of the circle after the animation runs; what I'm trying to do, though, is make the elastic animation "bounce" with greater amplitude. That is, while the animation will still start and end at the same r values that I've set for the circle, I'd like the elastic transition to be a lot more dramatic - expand and contract the circle much more aggressively during the transition before arriving at the final r values. To do this, I'm assuming that I need to modify the equation used in the elastic function to make the effect more dramatic.
Hopefully that makes sense - it's kind of hard to explain without showing an example, but if I had an example, I wouldn't have needed to post this question. ;-)
OK, based on your clarification, here's a new answer. To expand the effect of the easing (amplification), you need to multiply the easing result with a multiplier like this.
return 6 * Math.pow(2, -10 * n) * Math.sin((n - .075) * (2 * Math.PI) / .3) + 1;
But, when you do that, you find that the large part of the amplification goes too fast. The small part goes slow and the large part goes fast. So, the pace when it's larger needs to be changed. My guess (which seems to work) was to change Math.sin() to Math.cos() because that shifts the phase and it seems to work as you can see here: http://jsfiddle.net/jfriend00/fuaNp/39/.
return 6 * Math.pow(2, -10 * n) * Math.cos((n - .075) * (2 * Math.PI) / .3) + 1;
Other things to understand about this easing function. This part:
(2 * Math.PI) / .3
determines how many bounce cycles there are. The larger that multipler is, the more bounces it will have (but the faster the bounces will go). The smaller that multipler is, the fewer bounces it will have (and the slower those bounces will go).
This part:
Math.pow(2, -10 * n)
determines how fast the bounce decays since this value gets smaller the larger n gets which negates the other multipliers as n gets large. So:
Math.pow(2, -5 * n)
makes it decay slower (you see more of the larger swings at the beginning and less of the smaller swings at the end.
To make the circle go larger when you hover over it, you change the hovered radius which I've upped to r: 100 here. To make the circle smaller, you change the initial size and the unhovered size from 25 to something smaller like this:
var paper = Raphael(0, 0, 300, 300),
circle = paper.circle(150, 150, 10); // <== change initial radius here to make it smaller
circle.attr({
'stroke': '#f00',
'stroke-width': 4,
'fill': '#fff'
});
circle.hover(function() {
this.animate({ r: 100 }, 1000, 'super_elastic'); // <== change enlarged size here
}, function() {
this.animate({ r: 10 }, 1000, 'super_elastic'); // <== change small size here
});
// no changes made to the easing function
Raphael.easing_formulas.super_elastic = function(n) {
if (n == !!n) {
return n;
}
return Math.pow(2, -10 * n) * Math.sin((n - .075) * (2 * Math.PI) / .3) + 1;
};
You can see it here: http://jsfiddle.net/jfriend00/fuaNp/.
The super_elastic() function is the easing function which controls what pace the animation moves at different parts of the cycle. Easing doesn't control the overall amplitude. That's done with the parameters to the animate() method.
If you wanted to slow down the animation, you would increase the time of the animation (make the two 1000 arguments to animate() be larger numbers. If you wanted to speed up the animation, you make those two numbers smaller. These are milliseconds for the animation. Smaller numbers means the animation runs in fewer milliseconds (which means faster).
Related
quick question about a game engine I am writing in Javascript in Khan Academy: i have on arc that represents the amount of reload time left. (in processing js, drawing an arc works like this: arc(x,y,width,height,start,stop);)
I already have the value of the time left in seconds stored in a variable, so how can I accuratly have the arc wedge = time currently reloading divided by total reload time?
When the arc is a circle, reload is complete. When the arc is nothing, it just started reloading.
Also, the reload arc wedge stroke starts on the center top, and moves clockwise around the outside.
if(weapon.reloading){
arc(300,300-player.size-player.size/2,40,40,-90,round((w.reloadCounter/w.reloadTime/100)*100)*6-95);
}
this redundant code works when fps(frames per second equals 60)
i already have a variable called fps that equals frames per second, so if any help is given, use that in example.
Thanks!
Edit: would this be accurate - from what i can see from testing, it is mostly accurate:
var time = round(w.reloadCounter/fps*10)/10;//seconds currently spent reloading(rounded to the nearest 10th)
var t = round((w.reloadTime-time)*10)/10;//time left to finish reloading
var timeLeftArc = map(t,0,w.reloadTime,270,-90);
arc(300,300-player.size-player.size/2,40,40,-90,timeLeftArc);
We know that 360 degree make up a full circle - which equals to a fully reloaded weapon in your case. To get the proper arc for in-between values we need to figure out how many percent of the reloading phase is processed.
This can simply be done by dividing the time passed by the total time required - so just like you already did:
w.reloadCounter / w.reloadTime
This will give a float between 0 and 1. If we then multiply this number by 360 we have the in-between angles for the arc.
arc(300, 300 - player.size - player.size / 2, 40, 40, -90, w.reloadCounter / w.reloadTime * 360 - 90);
As a simple example:
Say the time required is 1000ms and 500ms have elapsed:
500 / 1000 * 360 = 0.5 * 360 = 180
I'm trying to implement a feature where you can drag on your screen to change the Position of an Object.
Right now the object is moving similarly to the change of the mouseX.
What I want is the further down on the screen you drag your mouse, the slower the object moves.
I'm pretty bad at maths so i dont really now how to achieve that in a good way.
Right now I'm doing it like that
factor = Math.abs(e.deltaY)/4;
this.newX = this.currentX + (e.deltaX / factor);
currentX is the start X value of the object.
No idea if it is clear what i want to achieve or if i provided all information, but any help is appreciated!
Your arithmetics always makes step equal to 4.
Consider using some function like exponent. For example,
factor = Exp(- k * Abs(deltaX))
If you want to provide factor 1 at close distances and factor halves per every 100 pixels (I've got arbitrary reasonable values), then
0.5 = exp( - k * 100)
ln(0.5) = -k * 100
k = - ln(0.5) / 100 ~= 0.007
note that I mean multiplicative factor:
factor = Exp(- 0.007 * Abs(deltaX))
this.newX = this.currentX + (e.deltaX * factor);
Now speed will be 1 for small distances, 0.5 for 100, 0.25 for 200, 0.125 for 300 and so on.
If you want another dependence, it is possible to find appropriate function.
I am making a paperjs app where you have circles and each circle can move freely. Some circles are connected to each other by means of lines which would cause the circles to come nearer to one another - ie the line simulates an elastic band between circles. However the circles are not allowed to overlap, so I want to make some kind of collision repulsion. Currently I have implemented this as a repulsive force between circles. For each circle I check the location of all other circles, and each circle's velocity vector is increased in the opposite direction to the circle close to it, in proportion to how close it is to this one. So in effect something like velocityvector += -(vectorFromThereToHere / 10)
However this has the effect that between the attractive force between connected circles and the repulsive force between all circles, you end up with a continual back and forth jittering.
What then would be the best way to implement some kind of repulsion between circles that wouldn't cause any juddering but would simply allow the circles' edges to touch one another whilst not coming any closer together? In effect I want the circles to simply bump into each other, not be allowed to slide over one another, but they are allowed to slide along each other's outside edge frictionlessly to get to where their momentum carries them.
You could implement an inelastic collision, followed by a position-fixing step. The idea is to apply an impulse on the objects in the direction of the normal of the impact.
// vx: velocity vector of object x
// ux: velocity vector of object x after impact
// mx: mass of the object x (1 if all objects are the same size)
// n: normal of impact (i.e.: p1-p2 in the case of circles)
// I: the coefficient of the impulse
// Equation of an inelastic collision
u1 * n = u2 * n
// Equations of the velocities after the impact
u1 = v1 + I * n / m1
u2 = v2 - I * n / m2
// solved for I:
I = (v1 - v2) * n / ((n*n)*(1/m1 + 1/m2))
When you have I you just have to apply the velocity changes. You might as well check if I > 0 before applying the impulses, to prevent the shapes stick together. Let's see how it works, and add position iterations if the balls start to overlap slowly after all these anyway.
PS: You might repeat the whole collision step in a single timeframe as well, to get better results when objects are involved in many collisions (because they are stuck together in a big ball)
In a web app some task takes multiple sequential ajax call steps to complete. Each takes 12–18 seconds.
I want to present user a progress indicator that is making frequent little steps instead.
My first bet was to assume a linear progress = k * time function, self-adjusting the k on each new response. Fiddle emulated the response times with random values in a +- 4s range.
This approach appears wrong: in cases with a relatively long response after a serie of fast ones, progress takes a negative step to catch up with real pace.
Feels like a function should go in "waves": faster at beginning, slowing down closer to the end to extent in case of a delayed checkpoint.
What is the best practice for such a task?
I'm going to focus on the mathematical part of your request:
a function should go in "waves": faster at beginning, slowing down closer to the end to extent in case of a delayed checkpoint
Translation: a monotonic function (i.e. no backward steps) with a positive initial gradient and asymptotic behaviour for large x.
Consider atan(theta): it has a gradient of 1 for small x, and asymptotically approaches π/2 for large x. We can scale it so that the expected end happens when the chunk is at some fraction of the available length - that is, if you expect it to take 4s, and it takes 4s, it can jump the remainder. If it takes longer, this remainder is the bit we will asymptotically eat up. Thus:
function chunkProgressFraction(expectedEndT, currentT, expectationFraction) {
// validate
if(!expectedEndT) { return 0; }
// defaults
if(!expectationFraction) { expectationFraction = 0.85 }
// y = k atan(mx)
// to reach 1.0 at large x:
// 1.0 = k . atan(+lots) = k . pi/2
var k = 2.0 / Math.PI;
// scale the function so that the expectationFraction result happens
// at expectedEndT, i.e.
// expectationFraction = k * atan(expectedEndT * m)
// expectedEndT * m = tan(expectationFraction / k)
var m = Math.tan(expectationFraction / k) / expectedEndT;
return k * Math.atan(m * currentT);
}
So if we want to get to 100 pixels, and we expect it to take 4s, and we want 20% slack:
progressPixelsThisChunk = 100.0 *
chunkProgressValue(4000.0, thisChunkTimeInMilliseconds, 0.8);
By all means scale the expectedEndT using the time taken by the previous chunk.
This is in part an EaselJS problem and in part a Physics/animation programming question.
I'm trying to learn EaselJS by studying the examples included in the EaselJS zip file. Right now, I'm looking at the SimpleTransform example,(http://bit.ly/LebvtV) where the robot rotates and fades into the background and expands towards the foreground. I find this effect really cool, and would like to learn how to achieve it. However, when I came to this set of code, I'm lost:
function tick() {
angle += 0.025;
var value = (Math.sin(angle) * 360);
bmp.setTransform (bmp.x , bmp.y , bmp.scaleX , bmp.scaleY , value/2 , bmp.skewX, bmp.skewY , bmp.regX , bmp.regY );
bmp.scaleX = bmp.scaleY = ((value)/360) + 0.25;
stage.update();
}
(For those unfamiliar with EaselJS, tick() is a function that dictates the actions on each tick, whose interval is set with setFPS. So if I've set FPS to be 20, then tick() will execute its statements 20 times in a second. I believe. And bmp here is a Bitmap object that points to the robot image.)
I've never been a wizard in Math, but I do understand the basics. I can see that angle += 0.025; is used to increased the angle variable so that the value passed into setTransform can change with time. However, I can't understand why a) 0.025 is used. b) what (Math.sin(angle) * 360) and ((value)/360) + 0.25 means, and c) why value is not just passed into setTransform, but divided by 2 (value/2).
I know it might be a challenge to explain this here, but any help is appreciated. In fact, if anyone thinks I'm a noob and needs to go study some Physics first, I'll most appreciate if someone can point me to a resource (book/url) for me to turn to.
Thanks in advance.
I can understand why you are confused. The code isn't efficient and that makes it harder to figure out what is going on. But here is the gist of it:
a) 0.025 is used because it is approximately π/125. With a Ticker speed of 25FPS, this means that the angle value will start at 0 and get to π at just about 5 seconds. π is used because Math.sin uses radians, not degrees (π radians == 180 degrees)
b) Math.sin(angle) will essentially start at 0, increase until it hits 1, decrease until it hits -1, then increase back to 0 -- all over a period of 10 seconds with sinusoidal rhythm.
(Math.sin(angle) * 360) has the same behavior as Math.sin(angle), just with a range of -360 to 360.
((value)/360) + 0.25) has the same behavior as Math.sin(angle), just with a range of -0.75 to 1.25.
c) value/2 is there so the robot only rotates 180 degrees instead of 360 degrees. I know what you are thinking -- why multiply by 360 only to divide by 2 one line later? Well, there is no reason for it really.
Here's a slightly clearer version of tick:
function tick() {
angle += Math.PI/125;
var sineValue = Math.sin(angle);
bmp.rotation = sineValue * 180;
bmp.scaleX = bmp.scaleY = sineValue + 0.25;
stage.update();
}
b) The Math.sin(angle)*360 seems like a conversion between degrees and radians.
Math.sin( x ) always evaluates to -1>=x>=1,
and therefore
Math.sin( angle ) is also always -1>=angle>=1
(we just substituted x), and
var value = Math.sin( angle ) * 360 is always -360>=value>=360.
(In the context of degrees rotated that is thus 1 whole rotation left or one whole rotation right).
We can see that the setTransform function exists as follows:
p.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {}
Obviously, we can see that there is a direct connection between value & angle. What we further see is that both the transform & scaleX are again depending on value. We can pull the conclusion that each tick there will be -after some calculations- a changing transform and scaleX.
So as the variable 'value' is passed as a parameter, this means that we wish to rotate 'this' much, as much as value tells us (-360>=x>=360). That means, /2 and 0.025 is just configured like this.
Hope this is helpful :-)