Easing functions for my animation not working - javascript

I'm not the best at maths, and im sorta trying to guess how to implement this for my animation. But currently it is not working, and I believe i have misunderstood how to do my easing function for my animation.
I have a object which is meant to represent a plane, on my canvas of which has the follow properties:
Current Velocity = obj.velocity
Braking Distance = obj.stopDist
Current Position = obj.posX & obj.posY
Destination = obj.destX & obj.destY
So i then incorporate the maths to try to have the plane land on a runway with an easing function so it looks half decent visually even if its not real world physics like this:
function ease(easeDelta,accelerateBool){
if(accelerateBool){
// accelerating
return (easeDelta * easeDelta * easeDelta);
} else {
//decelerating
return ((easeDelta--) * easeDelta * easeDelta + 1);
}
}
function InRange(delta, minValue, maxValue){
var range = (maxValue - minValue);
var valueInRange = (range * delta);
var finalValue = (valueInRange + minValue);
return finalValue;
}
function landing(){ //part of animation loop
var delta = new Date().getTime() - obj.timer, //ms since previous frame
vectorX = obj.destX - obj.posX,
vectorY = obj.destY - obj.posY,
normal = Math.sqrt(vectorX*vectorX + vectorY*vectorY), //distance to destination
targetSpeed = 20,
easeDelta = (normal / obj.stopDist),
newSpeed = InRange(ease(easeDelta,false), obj.velocity, targetSpeed),
distance = delta * newSpeed;
obj.posX += (distance * vectorX);
obj.posY += (distance * vectorY);
obj.timer = new Date().getTime(); //ready for next frame
}
The problem is the plane doesn't slow down as it goes a long the runway towards its destination. It just stays really slow.
Have i confused my maths with how easing functions work ?

Here are some extremely simple easing in and out equations written in that you might find helpful.
// accelerating from zero velocity
function easeIn(t)
{
return t*t
}
// decelerating to zero velocity
function easeOut(t)
{
return t*(2-t)
}
so you could use them like so:
function ease(easeDelta,accelerateBool){
if(accelerateBool){
// accelerating
return (easeDelta * easeDelta);
} else {
//decelerating
return ((easeDelta*(easeDelta - 2)));
}
}

Related

Increase speed on start and slow down at end

I have simple function that moves a circle in specific direction:
var rad = (a) => Math.PI / 180 * a;
this.x += Math.cos(rad) * this.throttle();
this.y += Math.sin(rad) * this.throttle();
I am also calculating distance to a target:
var distance = (p1, p2) => Math.sqrt( (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) );
this.destination_distance = parseInt(distance( { x: this.x, y: this.y }, { x: x, y: y } ));
I started to work on this.throttle function but i cannot get my head around it.
I wanted to achieve simple thing, when circle starts to move i want to increase speed from min to max by some step and when it is close to destination i want it to start slow down until it reaches min.
This is my current approach:
this.min_speed = 0.1;
this.max_speed = 1.5;
this.current_speed = 0.1;
this.throttle = function() {
if(this.destination_distance > 300) {
this.current_speed += 0.002;
} else {
this.current_speed -= 0.002;
}
if(this.current_speed < this.min_speed) {
this.current_speed = this.min_speed;
}
if(this.current_speed > this.max_speed) {
this.current_speed = this.max_speed;
}
return this.current_speed;
};
This doesnt work, because if the distance is smaller then 300 it doesnt speed up at all its always on min speed, so i suppose it should be somehow related to the distance variable. Maybe someone could help me solve this problem.
You need to calculate the difference between target speed and current speed, then add some specific fraction of that.
this.throttle = function() {
var target_speed = this.destination_distance > 300 ? this.max_speed : this.min_speed;
var diff = target_speed - this.current_speed;
this.current_speed += diff * laziness;
return this.current_speed;
};
laziness is something between 0.0001 and 1; the greater the value the faster the change of velocity.
Your starting speed is your minimum speed, so if the starting distance is <300 the circle will never speed up or slow down. Why not make your starting speed depend on the initial distance? Here's a crude implementation
if (this.destination_distance<300) {
this.current_speed = 1.5; //start fast when in the deceleration zone
} else {
this.current_speed = 0.1; //start slow when in the acceleration zone
}
Why don't you use something like percent of total distance passed? That way you will avoid having hardcoded threshold of 300 and it should work with distances of arbitrary length (with some tweaking of the speeds). Your changed function would look something like this:
this.throttle = function() {
if(this.destination_distance > 0.5 * total_distance) {
this.current_speed += 0.002;
} else {
this.current_speed -= 0.002;
}
if(this.current_speed < this.min_speed) {
this.current_speed = this.min_speed;
}
if(this.current_speed > this.max_speed) {
this.current_speed = this.max_speed;
}
return this.current_speed;
};
Here I consider total_distance to be the distance from the start to the final destination. This way the circle will travel approximately half of the way with increasing speed and the other half with decreasing speed. If you replace 0.5 by let 0.8 then the speed will increase for the first 80% and decrease for the last 20% of the path.

Three.js object moving to target

I have a object that moves to a target. The problem is that the x position is quicker than the z position or the z position is quicker than the x position.
What can I do that my object slows down for the x position if the z position needs more time to move?
My code in the animation function:
var distanceX = objectX - targetX;
var distanceZ = objectZ - targetZ;
if( distanceX < 0) {
visitor.translateX( 0.05 );
}else {
if( distanceX > 0) {
visitor.translateX( -0.05 );
}
}
if( distanceZ < 0) {
visitor.translateZ( 0.05 );
}else{
if( distanceZ > 0) {
visitor.translateZ( -0.05 );
}
}
I would recommend to compute the current value as an absolute value instead of incrementally. What you are doing is basically something like this:
x = xLast + constantValue;
Instead, you could do this without needing the last value (this is called linear interpolation):
progress = Math.min((Date.now() - startTime) / duration, 1);
x = xStart + progress * (xEnd - xStart);
The progress value in this case is a value from 0 (animation start) to 1 (animation end).
If you write your animations like this, you can just set the duration for both parts to the same value. Using the three.js math utils you can even do this all at once:
var end = new THREE.Vector3(1, 0, 2);
var start = new THREE.Vector3(2, 0, -1);
// in animation-loop
var tmp = new THREE.Vector3();
var progress = Math.min((Date.now() - startTime) / duration, 1);
// tmp = progress * (end - start)
tmp.copy(end).sub(start).multiplyScalar(progress);
object.position.copy(from).add(tmp);
That way you will have all components of the position move at a constant speed that arrives at the destination exactly at the same time.
You might want to have a look at animation-libaries like tween.js. They can handle a lot of this quite comfortably.

Javascript Canvas how to shoot in 360* from a rotating object

All my searching comes up with more general arc/sin/cos usage or shooting to the mouse position.
I am looking to aim and fire a projectile with the keyboard and have done a lot of it from scratch, as a noob in a web class doing a project, but I am stuck on this. My current math got me to this mess in firing the shot in the direction the line is currently pointing... (code names cleaned for readability):
this.x = x + len * Math.cos(angle);
this.y = y + len * Math.sin(angle);
this.xmov = -((x + len * Math.cos(angle)) - x) / ((y + len * Math.sin(angle)) - y);
this.ymov = ((y + len * Math.sin(angle)) - y) / ((x + len * Math.cos(angle)) - x);
if (Math.abs(this.xmov) > Math.abs(this.ymov)) {
this.xmove = (this.xmov * Math.abs(this.ymov));
} else {
this.xmove = this.xmov;
}
if (Math.abs(this.ymov) > Math.abs(this.xmov)) {
this.ymove = (this.xmov * this.ymov);
} else {
this.ymove = this.ymov;
}
(And here is the full thing http://jsbin.com/ximatoq/edit. A and D to turn, S to fire (on release). Can also hold S while turning.)
... but, you'll see that it only works for 3/8's of it. What is the math to make this fire from a complete circle?
Use this as shoot function:
this.shoot = function() {
if (this.fire > 0) {
this.x = P1gun.x2;
this.y = P1gun.y2;
this.xmove = (P1gun.x2 - P1gun.x)/100;
this.ymove = (P1gun.y2 - P1gun.y)/100;
this.fire = 0;
this.firetravel = 1;
}
}
The /100 can be removed, but you have to reduce the projectile speed.
If you want to shoot gun2 change the P1gun to P2gun.
Normalising a vector.
To control the speed of something using a vector, first make the length of the vector 1 unit long (one pixel). This is commonly called normalising the vector, and sometimes it's called the unit vector. Then you can multiply that vector by any number to get the desired speed.
To normalise a vector first calculate its length, then divide it by that value.
function normalizeVector(v){
var len = Math.sqrt(v.x * v.x + v.y * v.y);
v.x /= len;
v.y /= len;
return v;
}
Trig
When you use trig to create a vector it is also a unit vector and does not need to be normalised.
function directioToUnitVector(angle){ // angle in radians
return {
x : cos(angle),
y : sin(angle)
}
Why normalise
Many many reasons, you build almost everything from unit vectors.
One example, if you have two points and want to move from one to the next at a speed of 10 pixels per second with a frame rate of 60frame per second.
var p1 = {};
var p2 = {};
p1.x = ? // the two points
p1.y = ?
p2.x = ?
p2.y = ?
// create a vector from p1 to p2
var v = {}
v.x = p2.x -p1.x;
v.y = p2.y -p1.y;
// Normalize the vector
normalizeVector(v);
var frameRate = 1/60; // 60 frames per second
var speed = 10; // ten pixels per second
function update(){
// scale vec to the speed you want. keeping the vec as a unit vec mean
// you can also change the speed, or use the time for even more precise
// speed control.
p1.x += v.x * (speed * frameRate);
p1.y += v.y * (speed * frameRate);
// draw the moving object at p1
requestAnimationFrame(update)
}
NOTE when normalizing you may get a vector that has no length. If your code is likely to create such a vector you need to check for the zero length and take appropriate action. Javascript does not throw an error when you divide by zero, but will return Infinity, with very strange results to your animations.

Linear movement between two points that constantly move position

I currently have an issue with my code (written in Javascript); I have arrays objects that keep filling as the time goes. An example of an object:
monster.push({
range: 200,
attackSpeed: 500,
lastFire: 100,
id: 'ogre',
speed : 50,
pos:[canvas.width*Math.random(), canvas.height*Math.random()],
sprite: new Sprite('images/sheet_characters.png',[508,224],64,64],6,[0])
and
hero={
attackSpeed: 200,
lastGetHit: Date.now(),
lastFire: Date.now(),
health : 100,
speed: 256, //pixel/second
pos:[canvas.width/2,canvas.height/2],
sprite: new Sprite('images/sheet_characters.png',[256,0],[32,32],8,[0]) };
The position field of the objects change quite often and I want to add a function that determines the slope between the monster and the hero (we want the monster to fire at the hero) and then the attack should follow a linear movement.
What I currently have
for(var i=0; i<monster.length; i++){
var mob = monster[i];
mob.sprite.update(delta); //animatie
var newPos = moveTowards(mob, hero, delta);
mob.pos[0] = newPos[0]
mob.pos[1] = newPos[1]
if(checkBounds(mob.pos,mob.sprite.size)){
monster.splice(i,1);
}
mobAttacks(mob);
var attack = enemyAttacks[i]; //atacks updaten
attack.sprite.update(delta);
attack.pos[0] = attack.speed * Math.cos(attack.direction)));
attack.pos[1] = attack.speed * Math.sin(attack.direction)));
if(checkBounds(attack.pos,attack.sprite.sieze)){
enemyAttacks.splice(i,1);
}
}
In this for-loop I can access the position of the monster that fires and also the hero position as it is a global variable. Now the function to attack is :
function mobAttacks(object)
{
var distance = Math.sqrt(Math.pow((hero.pos[0]-object.pos[0]),2) + Math.pow((hero.pos[1]-object.pos[1]),2));
if( Date.now() - object.lastFire > object.attackSpeed && object.range >= distance)
{
deltaY = hero.pos[1] - object.pos[1];
deltaX = hero.pos[0] - object.pos[0];
var direction = Math.atan(deltaY/deltaX);
enemyAttacks.push({
pos:[(object.pos[0]+object.sprite.size[0]/2), (object.pos[1]+object.sprite.size[1]/2)],
direction: direction,
speed: 128, //pixel/s
sprite: new Sprite('images/sheet_objects.png', [231,3],[24,24],6,[0])
});
object.lastFire = Date.now();
}
}
The angle between both objects is calculated and I make a new object (the attack) with the start position of the monster.
The result is quite odd:
The slope is off, so is the Y position of the boulder. Also when the hero is on the left side of the monster, there is no boulder to be spotted.
After some hours of tinkering with the code I came to the conclusion that I couldn't solve my current problem.
EDIT:
attack.pos[0] += attack.speed * Math.cos(attack.direction)*delta;
attack.pos[1] += attack.speed * Math.sin(attack.direction)*delta;
Solved the issue that the boulders are no longer cast from a random position.
Now the angle is a not going negative when I'm in the 2nd or 3rd kwadrant (position left when viewed from the monster perspective)
Get all the trig out of your code, it's unnecessary. Let
deltaX = hero.pos[0] - object.pos[0];
deltaY = hero.pos[1] - object.pos[1];
then
distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
deltaX /= distance;
deltaY /= distance;
will make <deltaX,deltaY> a normalized vector (one with a length of 1).
Then you can update the position of the attack for delta time using simply:
attack.pos[0] += attack.speed * attack.deltaX * delta;
attack.pos[1] += attack.speed * attack.deltaY * delta;
If you don't have any use for the speed and direction separately, you can also pre-multiply speed into deltaX and deltaY when you initialize the attack, meaning that the update becomes only
attack.pos[0] += attack.deltaX * delta;
attack.pos[1] += attack.deltaY * delta;
which is nice and simple.

Random natural movement jquery

How can I recreate this type movement with jquery for images: http://www.istockphoto.com/stock-video-12805249-moving-particles-loop-soft-green-hd-1080.php
I'm planning to use it as a web page background. If it is not possible with jquery I'll go with flash as3. But I prefer jquery.
Edit: Raphael is definitely better suited for this, since it supports IE. The problem with jQuery is that the rounded corners are a pain to do in IE due to CSS constraints... in Raphael cross browser circles are no sweat.
jsFiddle with Raphael - all browsers:
(though it might look nicer speeded up in IE)
(function() {
var paper, circs, i, nowX, nowY, timer, props = {}, toggler = 0, elie, dx, dy, rad, cur, opa;
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function ran(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function moveIt()
{
for(i = 0; i < circs.length; ++i)
{
// Reset when time is at zero
if (! circs[i].time)
{
circs[i].time = ran(30, 100);
circs[i].deg = ran(-179, 180);
circs[i].vel = ran(1, 5);
circs[i].curve = ran(0, 1);
circs[i].fade = ran(0, 1);
circs[i].grow = ran(-2, 2);
}
// Get position
nowX = circs[i].attr("cx");
nowY = circs[i].attr("cy");
// Calc movement
dx = circs[i].vel * Math.cos(circs[i].deg * Math.PI/180);
dy = circs[i].vel * Math.sin(circs[i].deg * Math.PI/180);
// Calc new position
nowX += dx;
nowY += dy;
// Calc wrap around
if (nowX < 0) nowX = 490 + nowX;
else nowX = nowX % 490;
if (nowY < 0) nowY = 490 + nowY;
else nowY = nowY % 490;
// Render moved particle
circs[i].attr({cx: nowX, cy: nowY});
// Calc growth
rad = circs[i].attr("r");
if (circs[i].grow > 0) circs[i].attr("r", Math.min(30, rad + .1));
else circs[i].attr("r", Math.max(10, rad - .1));
// Calc curve
if (circs[i].curve > 0) circs[i].deg = circs[i].deg + 2;
else circs[i].deg = circs[i].deg - 2;
// Calc opacity
opa = circs[i].attr("fill-opacity");
if (circs[i].fade > 0) {
circs[i].attr("fill-opacity", Math.max(.3, opa - .01));
circs[i].attr("stroke-opacity", Math.max(.3, opa - .01)); }
else {
circs[i].attr("fill-opacity", Math.min(1, opa + .01));
circs[i].attr("stroke-opacity", Math.min(1, opa + .01)); }
// Progress timer for particle
circs[i].time = circs[i].time - 1;
// Calc damping
if (circs[i].vel < 1) circs[i].time = 0;
else circs[i].vel = circs[i].vel - .05;
}
timer = setTimeout(moveIt, 60);
}
window.onload = function () {
paper = Raphael("canvas", 500, 500);
circs = paper.set();
for (i = 0; i < 30; ++i)
{
opa = ran(3,10)/10;
circs.push(paper.circle(ran(0,500), ran(0,500), ran(10,30)).attr({"fill-opacity": opa,
"stroke-opacity": opa}));
}
circs.attr({fill: "#00DDAA", stroke: "#00DDAA"});
moveIt();
elie = document.getElementById("toggle");
elie.onclick = function() {
(toggler++ % 2) ? (function(){
moveIt();
elie.value = " Stop ";
}()) : (function(){
clearTimeout(timer);
elie.value = " Start ";
}());
}
};
}());​
The first attempt jQuery solution is below:
This jQuery attempt pretty much failes in IE and is slow in FF. Chrome and Safari do well:
jsFiddle example for all browsers (IE is not that good)
(I didn't implement the fade in IE, and IE doesn't have rounded corners... also the JS is slower, so it looks pretty bad overall)
jsFiddle example for Chrome and Safari only (4x more particles)
(function() {
var x, y, $elie, pos, nowX, nowY, i, $that, vel, deg, fade, curve, ko, mo, oo, grow, len;
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function ran(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function moveIt()
{
$("div.spec").each(function(i, v) {
$elie = $(v);
if (! $elie.data("time"))
{
$elie.data("time", ran(30, 100));
$elie.data("deg", ran(-179, 180));
$elie.data("vel", ran(3, 10));
$elie.data("curve", ran(0, 1));
$elie.data("fade", ran(0, 1));
$elie.data("grow", ran(-2, 2));
}
vel = $elie.data("vel");
deg = $elie.data("deg");
fade = $elie.data("fade");
curve = $elie.data("curve");
grow = $elie.data("grow");
len = $elie.width();
if (grow > 0)
len = Math.min(len + grow, 50);
else
len = Math.max(len + grow, 20);
$elie.css("-moz-border-radius", len/2);
$elie.css("border-radius", len/2);
$elie.css("width", len);
$elie.css("height", len);
pos = $elie.position();
$elie.data("time", $elie.data("time") - 1);
if (curve)
$elie.data("deg", (deg + 5) % 180);
else
$elie.data("deg", (deg - 5) % 180);
ko = $elie.css("-khtml-opacity");
mo = $elie.css("-moz-opacity");
oo = $elie.css("opacity");
if (fade)
{
$elie.css("-khtml-opacity", Math.max(ko - .1, .5));
$elie.css("-moz-opacity", Math.max(mo - .1, .5));
$elie.css("opacity", Math.max(oo - .1, .5));
} else
{
$elie.css("-khtml-opacity", Math.min(ko - -.1, 1));
$elie.css("-moz-opacity", Math.min(mo - -.1, 1));
$elie.css("opacity", Math.min(oo - -.1, 1));
}
if (vel < 3)
$elie.data("time", 0);
else
$elie.data("vel", vel - .2);
nowX = pos.left;
nowY = pos.top;
x = vel * Math.cos(deg * Math.PI/180);
y = vel * Math.sin(deg * Math.PI/180);
nowX = nowX + x;
nowY = nowY + y;
if (nowX < 0)
nowX = 490 + nowX;
else
nowX = nowX % 490;
if (nowY < 0)
nowY = 490 + nowY;
else
nowY = nowY % 490;
$elie.css("left", nowX);
$elie.css("top", nowY);
});
}
$(function() {
$(document.createElement('div')).appendTo('body').attr('id', 'box');
$elie = $("<div/>").attr("class","spec");
// Note that math random is inclussive for 0 and exclussive for Max
for (i = 0; i < 100; ++i)
{
$that = $elie.clone();
$that.css("top", ran(0, 495));
$that.css("left", ran(0, 495));
$("#box").append($that);
}
timer = setInterval(moveIt, 60);
$("input").toggle(function() {
clearInterval(timer);
this.value = " Start ";
}, function() {
timer = setInterval(moveIt, 60);
this.value = " Stop ";
});
});
}());
​
[Partial answer, just for the physics.]
[I just saw the previous answer, mine is somewhat along the same lines.]
You may try to simulate some sort of Brownian motion, i.e. a movement deriving
from the combination of a random force and a viscous damping. Pseudocode:
initialize:
x = random_position();
v_x = random_velocity(); // v_x = velocity along x
// and same for y
for (each time step) {
x += v_x;
v_x += random_force() - time_step / damping_time * v_x;
// and same for y
}
Keep the damping time long (~ 1 second) and the amplitude of the random force small. Otherwise the movement may be too jerky.
For an easy to implement Gaussian random number generator, look up Box-Muller in Wikipedia.
For the mathematics of it, you give every object a starting position and velocity. The "random walk" is achieved by computing a random angle that is constrained by some amount (experiment). Then change the angle of the velocity vector by this angle. You can also compute a random speed delta and change the magnitude of the vector by that amount. Because you're working with velocity, the movements will be somewhat smooth. A slightly more advanced approach to to work with acceleration directly and compute velocity and position based off that.
For your random steering value, a binomial distribution is preferable to a uniform one. Binomial distributions are concentrated around 0 instead of uniformly spread out. You can just do random() - random() (psuedocode)
Vector math is extensively documented but if you run into a snag, leave a comment.
very late answer from my side, but I thought I might give an approach...
I personally would use an svg vector image.
Create a jquery plugin which accepts opacity, size. and makes them move in a random direction.
Then do a javascript loop in creating a set of those particles (where opacity and size are random, plus the start location is random)
Then make the jquery plugin to initiate a new instance of itself when the particle is unloaded.
(If you look at the little movie you will see that they move in 1 direction and fade out, then another fades in.)
The opacity effect will give the depth perspective.
Not sure if my answer helps, but I would go in that direction.

Categories