Increase velocity while smooth interpolating move animation with a 60fps game loop - javascript

I have a model with x y positions and vx vy velocity.
var model = { x: 0, y: 0, vx: 1, vy: 0 };
I have an update function that is called every 16ms, that updates the position based on velocity.
function update() {
model.x += model.vx;
model.y += model.vy;
}
Now I want to speed up this model by multiplying velocity with a boost:
var boost = 10;
function update() {
model.x += model.vx * boost;
model.y += model.vy * boost;
}
This causes the model to jump between positions, instead of interpolating and moving smoothly.
How can I increase the velocity and keep the object moving smooth?

The problem here is that you are multiplying the boost with the velocity.
Look what is actually going on. The value of boost is 10. Now suppose the velocity is 10 pixels/sec, but due to boost, it will be 100 pixels/sec. This is a huge difference. And this is obvious that it will jump.
You never want do this usually. I guess you would want to add the boost value with the velocity.
I assume you know the formulae of kinematics you studied in school.
One of those is,
v = u + at
See, here also you actually add the acceleration (boost in your case) to the velocity and not multiply it.
So, your code will be as follows:
var boost = 10;
function update() {
model.x += model.vx + boost;
model.y += model.vy + boost;
}
Or you can just decrease the value of boost.
Or you can add acceleration in your model that would increase whenever you want to boost up and will gradually decrease and the velocity will be normal after some time.
Still, you will end up with the same problem if the velocity (or acceleration) becomes too high.
If you really want to increase the velocity 10 times, then there's not much you can do.
Also, if you are using setInterval, I would recommend you to switch to requestAnimationFrame for 60 FPS animation.

Related

Coming up with an Algorithm

I have a circle in my canvas. The mouse position is calculated in relation to the canvas. I want the circle to move when the mouse is at <=100px distance from it. The minimum distance to start moving is 100px, at 0.5px/tick. It goes up to 2px/tick at 20px distance.
Basically, the closer the mouse is to the circle, the faster the circle should move.
What I have so far moves the circle when distance is less or equal to 100 -- (I'm using easeljs library)
function handleTick() {
distance = calculateDistance(circle, mX, mY);
if (distance<=100) {
circle.x += 0.3;
stage.update();
}
}
What I want
function handleTick() {
distance = calculateDistance(circle, mX, mY);
if (distance<=100) {
circleSpeed = // equation that takes distance and outputs velocity px/tick.
circle.x += circleSpeed;
stage.update();
}
}
So I thought this was a mathmatical problem and posted it on math exchange, but so far no answers. I tried googling several topics like: "how to come up with an equation for a relation" since I have the domain (100, 20) and the range (0.5, 2). What function can relate them?
Thing is I'm bad at math, and these numbers might not even have a relation - I'm not sure what I'm looking for here.
Should I write a random algorithm "circleSpeed = 2x + 5x;" and hope it does what I want? Or is it possible to do as I did - "I want these to be the minimum and maximum values, now I need to come up with an equation for it"?
A pointer in the right direction would be great because so far I'm shooting in the dark.
If I understand it correctly, you want circleSpeed to be a function of distance, such that
circleSpeed is 0.5 when distance is 100.
circleSpeed is 2 when distance is 20.
There are infinity functions which fulfill that, so I will assume linearity.
The equation of the line with slope m and which contains the point (x₀,y₀) is
y = m (x-x₀) + y₀
But in this case you have two points, (x₁,y₁) and (x₂,y₂), so you can calculate the slope with
y₂ - y₁
m = ───────
x₂ - x₁
So the equation of the line is
y₂ - y₁
y = ─────── (x - x₁) + y₁
x₂ - x₁
With your data,
0.5 - 2
y = ──────── (x - 20) + 2 = -0.01875 x + 2.375
100 - 20
Therefore,
circleSpeed = -0.01875 * distance + 2.375
I assume you want a linear relation between the distance and speed?
If so, you could do something like circleSpeed = (2.5 - 0.5(distance/20)).
That would, however set the speed linearly from 0 to 2.5 on the range (100 to 0), but by using another if like this if (distance < 20) circleSpeed = 2 you would limit the speed to 2.0 at 20 range.
It's not 100% accurate to what you asked for, but pretty close and it should look ok I guess. It could possibly also be tweaked to get closer.
However if you want to make the circle move away from the mouse, you also need to do something to calculate the correct direction of movement as well, and your problem gets a tiny bit more complex as you need to calculate speed_x and speed_y
Here is a simple snippet to animate the speed linearly, what that means is that is the acceleration of the circle will be constant.
if distance > 100:
print 0
elseif distance < 20:
print 2
else:
print 2 - (distance -20 ) * 0.01875
Yet other relationships are possible, (other easings you might call them) but they will be more complicated, hehe.
EDIT: Whoops, I’d made a mistake.

Shouldn't this javascript animation produce a sine wave movement?

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);
}

Create smooth rotation in given time interval (+- x%)

I have got a cube in my scene which I want to rotate at a specific start velocity and in a given time interval.
In addition, the cube's end angle should be the same as the start angle.
Therefore, I thought of allowing +- 5% deviation of the time interval.
Here is my current status: http://jsfiddle.net/5NWab/1/.
Don't wonder that is currently working. The problem occurs if I change the time interval, e.g. by '3000': http://jsfiddle.net/5NWab/2/.
The essential move() method of my cube:
Reel.prototype.move = function (delta) {
if (this.velocity < 0 && this.mesh.rotation.x == 0) {
return false;
}
// Create smooth end rotation
if (this.velocity < 0 && this.mesh.rotation.x != 0) {
this.mesh.rotation.x += Math.abs(delta * this.speedUp * 0.5 * this.timeSpan);
if (Math.abs(this.mesh.rotation.x - 2 * Math.PI) < 0.1) {
this.mesh.rotation.x = 0;
}
}
else {
this.mesh.rotation.x += delta * this.velocity;
this.time -= delta;
this.velocity = this.speedUp * this.time;
}
}
The problem is that I cannot think of a solution or method in order to accomplish my topic.
It would not be so complex if I the variable delta would be constant.
It should be around 60fps = 1000/60 because I'm using requestAnimationFrame().
I have also found this question which could help finding the solution.
I think the code should either
slow down the velocity before the actual end is reached.
That should be the case if the final angle is a little bit greater than the desired (start) angle.
or should speed up the rotation speed after the actual end is reached.
That should be the case if the final angle is a little bit smaller than the desired (start) angle.
But what is when the angle is a hemicycle away from the desired one (i.e. 180° or PI)?
In order to clarify my question, here are my knowns and unknowns:
Known:
Start velocity
Time interval
Start angle (usually 0)
I want the cube to have the same start angle/position at the end of the rotation.
Because the FPS count is not constant, I have to shorten or lengthen the time interval in order to get the cube into the desired position.
If you want the rotation to end at a particular angle at a particular time, then I would suggest that instead of continually decrementing the rotation as your current code (2012-09-27) does, set the target time and rotation when you initialise the animation and calculate the correct rotation for the time of the frame recalculation.
So if you were doing a sine-shaped speed curve (eases in and out, linearish in the middle, nice native functions to calculate it), then (pseudocode not using your variables):
//in init
var targetTime = now + animationTime;
// normalize the length of the sine curve
var timeFactor = pi/animationTime;
var startAngle = ...
var endAngle = ...
var angleChange = endAngle - startAngle;
// inside the animation, at some time t
var remainingT = targetTime - t;
if(remainingT <= 0) {
var angle = endAngle;
} else {
var angle = startAngle + cos(remainingT * timefactor) * angleChange;
}
[Edited to add startAngle into andle calculation]
Because the cos function is odd (i.e. symmetric about the origin), as t approaches targetTime, the remainingT approaches zero and we move backward from pi to 0 on the curve. The curve of the sin shape flattens toward zero (and pi) so it will ease out at the end (and in at the beginning. There is an explicit zeroing of the angle at or past the targetTime, so that any jitter in the framerate doesn't just push it into an endless loop.
Here's one possible method, although it's not quite as good as I'd like: http://jsfiddle.net/5NWab/8/
The idea is to decrease the speed gradually, as you were doing, based on the amount of time left, until you get to a point where the amount of distance that the cube has to rotate to reach it's starting rotation (0) becomes greater than or equal to the amount of rotation that can possibly be made given the current velocity and the current amount of time left. After that point, ignore the time left, and slow down the velocity in proportion to the amount of rotation left.
This works fairly well for certain timeSpans, but for others the ending slowdown animation takes a little long.

Game programming: remove movement lag (jumping)

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.

My collision detection algo seems to trigger even before the objects touched

I wrote a very simple collision detection demo:
http://jsfiddle.net/colintoh/UzPg2/5/
As you can see, the objects sometimes doesn't connect at all but yet the collision is being triggered. The radius for the balls are 10px so the algo triggered the collision whenever the distance between two balls center is less than 20px. I reduced it to 18px for a better visual but the empty collision still happens randomly. Am I doing something wrong?
It looks like you are not using the right formula for distance between two points. See http://www.purplemath.com/modules/distform.htm for a full explanation.
You are doing this:
this.ballCollide = function(balli) {
if (Math.abs((this.x) - (balli.x)) < (2*radius - buffer)) {
if (Math.abs((this.y) - (balli.y)) < (2*radius - buffer)) {
// Do collision
}
}
};
That's a square bounding box, not a circular one. To get a circular bounding box, you can do something like this, based on the formula in the referenced web page:
this.ballCollide = function(balli) {
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
if (Math.sqrt(deltax * deltax + deltay * deltay) < 2 * radius - buffer) {
// Do collision
}
};
See http://jsfiddle.net/UzPg2/14/ for a working example.
Note that a perfect circular bounding box is a much slower algorithm than a square bounding box approximation.
Following Jarrod Roberson's point (a perfect circle is always inside a perfect square), you'd do that by basically combining your original code with the code I posted, like this (and you could combine them both into one conditional switch if you wanted to):
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
var dist = 2 * radius - buffer;
if (Math.abs(deltax) < dist && Math.abs(deltay) < dist) {
if (Math.sqrt(deltax * deltax + deltay * deltay) < dist) {
// Do collision
}
}
See http://jsfiddle.net/UzPg2/21/ for a working example (I've left the buffer as your variable is called at 2, but I personally think it looks better with a value of 1).
There are also many other ways you can optimize this for speed if you need to, but Jarrod's suggestion gives you the biggest immediate speed boost.
You're only checking for collisions on two axis, x and y. You need to use Pythagoras' theorem to detect on all axis at the cost of efficiency. For example.
Your algorithm will detect a collision around the point where these two balls are, since if you draw a tangent line along the x or y axis from one ball it goes through the other ball: http://jsfiddle.net/XpXzW/1/
Here you can see where they should actually collide:
http://jsfiddle.net/wdVmQ/1/
If you change your collision detection algorithm to check for perfect collisions (it will be less efficient) you can get rid of your buffer too:
http://jsfiddle.net/ucxER/
(Using Pythagoras' theorem the formula for a collision is:
Math.sqrt((this.x - balli.x)*(this.x - balli.x)
+ (this.y - balli.y)*(this.y - balli.y)) < 2*radius
Also what Jarrod commented is very smart. You can speed it up by using a technique like that. Since the square root is only calculated when the balls are close to each other:
http://jsfiddle.net/bKDXs/

Categories