help me improve my basic point to point move animation algorithm - javascript

Greetings,
With javascript, I am trying to make a very easy animation, an image moves from one X and Y coordination to another X Y coordination.
I have 4 constant such as:
var startX = 0; //starting X of an image
var startY = 0; //starting Y of an image
var endX = 100; //ending X of an image
var endY = 200; //ending Y of an image
//these 2 are used for keeping the "current" position of animated image,
var currentX = startX ;
var currentY = startY ;
//every 150 ms, updates the location of the coordinates
function move(){
if( (currentX == endX) && (currentY == endY) )
break;
if(currentX < endX){
currentX = currentX + step_amount;
}
if(currentX > endX){
currentX = currentX - step_amount;
}
if(currentY < endY){
currentY = currentY + step_amount;
}
if(currentY > endY){
currentY = currentY - step_amount;
}
setInterval("move()", 150);
}
This does the job, however it is not smooth, I will be grateful if you help me improve my naive algorithm for a better move function that is always going for the "shortest path".

Sounds like you need (a variation of) the Bresenham line drawing algorithm.

The shortest distance between two points is a straight line. So you should probably move along that.
What that would imply is that in your code, you should not use the same step amount for both X and Y coordinates. Instead compute Y step based on X step and the slope of the shortest path line.
slope = (startY - endY) / (startX - endX);
Y_step = X_step * slope;
Secondly, in your current algorithm, once your image reaches the destination point, it'll continue to oscillate about it. I think you should get rid of the statements that take a negative step.

Since you are always moving two coordinates together, you only need to check against one of them, e.g.
if (currentX < endX) {
currentX += xStep;
currentY += yStep;
}

Try something like this to move the object in a straight line:
var numberOfSteps = 100;
var stepDX = (endX - startX) / numberOfSteps;
var stepDY = (endY - startY) / numberOfSteps;
var step = 0;
Inside the move() function:
if (step <= numberOfSteps) {
currentX = startX + stepDX * step;
currentY = startY + stepDY * step;
step++;
}
Cast currentX/currentY to integer before applying to the object you want to move.

This is my implementation, many thanks to Frederik The Fool
Compute slope:
if(this.x === target.x){
this.slope = 1;
}else{
this.slope = (this.y - target.y)/(this.x - target.x);
}
Ystep:
if(this.y > this.target.y){
this.y = Math.max(this.target.y, this.y - Math.abs(this.slope * distance));
}else if(this.shape.y < this.target.y){
this.y = Math.min(this.target.y, this.y + Math.abs(this.slope * distance));
}

Related

Make a ball on a canvas slowly move towards the mouse

I am trying to make a ball move slowly towards my mouse.
Im using paper.js which is a simple animation library. Using this i have a ball moving on screen. These are some of the properties of the ball:
balls[0].vector.angle is its direction. 0 = right, 90 = down, 180 = left etc and everything in between
balls[0].point.x is its x position and .y for y position.
balls[0].vector.length is its speed.
I have put in a mouse move event and i think ive got the angle between them below:
canvas.addEventListener("mousemove", function(e){
var a = balls[0].point.y - e.clientY;
var b = balls[0].point.x - e.clientX;
var angleDeg = Math.atan2(a, b) * 180 / Math.PI;
});
So i have made the ball stationary to test this and moved my mouse around it. To the left of the ball gives me 0 degrees. Above gives me 90. To the right gives me 180. And below the ball gives me -90 etc and everything in between.
I then calculated the distance in the same event and changed the speed to reflect the distance giving it a cap as max speed:
var distance = Math.sqrt( a*a + b*b );
var maxSpeed = 20;
balls[0].vector.length = (distance/30 > maxSpeed) ? maxSpeed : distance/30;
So ive tested the speed and this and it works perfect. When i give the ball the angle from earlier its going in all sorts of directions. The speed still works, its just the ball is going in the wrong direction and im not sure what ive done wrong.
Frankly, you don't need trig functions. All you need is good old Pythagoras theorem.
var MAX_SPEED = 20;
var MIN_SPEED = 0.25; // Very slow if close but not frozen.
var ATTRACTION = 0.5;
var diff_y = e.clientY - balls[0].point.y;
var diff_x = e.clientX - balls[0].point.x;
var distance = Math.sqrt(diff_x * diff_x + diff_y * diff_y)
var speed = distance * ATTRACTION;
if (speed > MAX_SPEED) speed = MAX_SPEED;
if (speed < MIN_SPEED) speed = MIN_SPEED;
// The rates along axes are proportional to speed;
// we use ratios instead of sine / cosine.
balls[0].point.x += (diff_x / distance) * speed;
balls[0].point.y += (diff_y / distance) * speed;
Much more fun can be had by introducing forces and inertia.
Specify the direction in terms of deltas
var deltaX = e.clientX - balls[0].point.x;
var deltaY = e.clientY - balls[0].point.y;
var distance = Math.sqrt(deltaX*deltaX+deltaY*deltaY);
var maxSpeed = 20;
balls[0].vector.length = (distance/30 > maxSpeed ) ? maxSpeed : distance / 30;
balls[0].point.x = balls[0].point.x + (balls[0].vector.length * deltaX / distance);
balls[0].point.y = balls[0].point.y + (balls[0].vector.length * deltaY / distance);
I think that will work

Mouse click angle

So I'm creating a brick breaker game, and I need some help finding an angle.
Pretty much the game consists of blocks that, when hit, will cause you to lose 1 health. The point of the game is to hit the blocks with the balls to break them before they reach the bottom. If the ball hits a wall or a block, its trajectory is reversed.
I want the user to be able to click someone within the html canvas. Then the balls, which start in the center of the screen at the bottom of the canvas, will follow that angle. In other words, the user will click and the balls will move to that spot and then continue until it hits something.
I have some code here, But it probably won't help on how to achieve the angle thing.
function animate(callback) {
window.requestAnimationFrame(function() {
window.setTimeout(callback, 1000/60);
});
}
// canvas
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// variables
var ballList = [];
var maxBalls = 1;
var checkAmount = 0;
var interval;
// onload/refresh/update/render
window.onload = function() {
refresh();
}
function refresh() {
update();
render();
animate(refresh);
}
function update() {
document.addEventListener("click", spawn);
for(var i = 0; i < ballList.length; i++) {
ballList[i].move();
}
}
function render() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < ballList.length; i++) {
ballList[i].show();
}
}
// ball
function Ball() {
this.x = canvas.width / 2;
this.y = canvas.height - 50;
this.width = 10;
this.height = 10;
this.xVel = 5;
this.yVel = -10;
this.show = function() {
context.fillStyle = '#fff';
context.fillRect(this.x, this.y, this.width, this.height);
}
this.move = function() {
this.x += this.xVel;
this.y += this.yVel;
if(this.x >= canvas.width || this.x <= 0) {
this.xVel *= -1;
}
if(this.y >= canvas.height || this.y <= 0) {
this.yVel *= -1;
}
}
}
function spawn(event) {
var xVel = (event.clientX - canvas.width / 2) / 90;
if(ballList.length < maxBalls) {
if(checkAmount < maxBalls) {
interval = setInterval(function() {
ballList.push(new Ball((event.clientX)));
checkAmount++;
if(checkAmount > maxBalls) {
clearInterval(interval);
checkAmount = 0;
}
}, 10);
}
}
}
Thanks in advance.
Unit Vectors
To move an object from one point towards another you use a vector. A vector is just two numbers that represent a direction and a speed. It can be polar in that one number is an angle and the other is a distance, or cartesian that represent the vector as the amount of change in x and y.
Cartesian unit vector
For this you can use either but I prefer the cartesian vector and a particular type called a unit vector. The unit vector is 1 unit long. In computer graphics the unit is normally the pixel.
So we have a point to start at
var startX = ?
var startY = ?
And a point the we want to head towards
var targetX = ?
var targetY = ?
We want the unit vector from start to target,
var vectorX = targetX - startX;
var vectorY = targetY - startY;
The vector's length is the distance between the two points. This is not so handy so we will turn it into a unit vector by dividing both the x and y by the length
var length = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
var unitVectX = vectorX / length;
var unitVectY = vectorY / length;
Now we have a one pixel long unit vector.
The Ball will start at start
var ballX = startX
var ballY = startY
And will move at a speed of 200 pixels per second (assuming 60fps)
var ballSpeed = 200 / 60;
Now to move the ball just add the unit vector times the speed and you are done. Well till the next frame that is.
ballX += unitVectX * ballSpeed;
ballY += unitVectY * ballSpeed;
Using the cartesian makes it very easy to bounce off of walls that are aligned to the x or y axis.
if(ballX + ballRadius > canvas.width){
ballX = canvas.width - ballRadius;
unitVectX = - unitVectX;
}
Polar vector
You can also use polar coordinates. As we use a unit vector the polar unit vector just needs the direction. You use the trig function atan2
// get the direction in radians
var polarDirection = Math.atan2(targetY - startY, targetX - startX);
The direction is in radians, many poeple don't like radians and convert to degrees, but there is no need to know which way it is going just as long as it goes in the correct direction. To remember radians is easy. 360 degrees is 2 radian 180 is 1 randian 90 is 0.5. The actual units used are PI (not many people know many of the digits of pi but you don't need to). So 270 degree is 1.5 radians or as a number 1.5 * Math.PI.
The angles start at the 3 o'clock point (pointing to the right of screen) as 0 radians or 0 deg then clockwise 90deg is at 6 o'clock 0.5 radian, and 180deg 1 radian at 6 o'clock and so on.
To move the ball with the polarDirection you need to use some more trig.
// do this once a frame
ballX += Math.cos(polarDirection) * ballSpeed;
ballY += Math.sin(polarDirection) * ballSpeed;
// note that the cos and sin actually generate the cartesian unit vector
/**
* #param {number} x1 - x coordinate of the first point
* #param {number} y1 - y coordinate of the first point
* #param {number} x2 - x coordinate of the second point
* #param {number} y2 - y coordinate of the second point
* #return {number} - the angle (between 0 and 360)
*/
function getDirection(x1, y1, x2, y2) {
// might be negative:
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
// correct, positive angle:
return (angle + 360) % 360;
}
I wrote this function for a similar purpose. Don't forget that you might have to negate x.

Smoothest following animation in canvas

I want to make a coordinate (coord1) follow another coordinate (coord2) as smoothly as possible with these following conditions:
coord2 is able to move.
coord1 follows coord2 in the smoothest way possible (arc fashion).
coord1 follows at a constant speed.
I have an example here but it only succeeds condition 1 and 3 not 2. In the example, you can move the ball with your arrow keys.
Click here to go to the example
Here is my code for following:
Obstacle.prototype.follow = function () {
this.y += this.vSpeed
this.x += this.hSpeed
if (this.x < ball.x - 9) {
this.hSpeed = 1;
}
if (this.x > ball.x - 10) {
this.hSpeed = -1;
}
if (this.y > ball.y - 10) {
this.vSpeed = -1;
}
if (this.y < ball.y - 9) {
this.vSpeed = 1;
}
}
Anyone have a solution that succeeds all three conditions?
To follow an object create a vector from tone object to the other. A vector has direction and length. The length is the speed and the direction is where it is going.
I have forked your fiddle to show this working. The only change is in the follow function. https://jsfiddle.net/blindman67/ksu518cg/2/
// obj one
var x1 = 100;
var y1 = 100;
// object to follow
var x2 = 300;
var y2 = 200;
Every animation frame the distance
var dist = Math.sqrt(Math.pow(x2-x1,2) + Math.pow(y2-y1,2)); // distance
Create a vector with length 1 pixel
var dx = (x2-x1)/dist;
var dy = (y2-y1)/dist;
Multiply by the speed you want to move
dx *= speed;
dy *= speed;
THen add to the objects position
x2 += dx;
y2 += dy;

Animating canvas shape to center from any position

So I am a bit confused on how I can make a shape animate to the center of a canvas. I can get the center value:
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
centerX = width / 2,
centerY = height / 2;
and a simple decrement or increment depending on whether the initial position is positive or negative can be done as well:
var x = 100;
var y = 100;
function fn (){
ctx.beginPath();
ctx.arc(x, y, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = '#444';
ctx.fill();
ctx.closePath();
x -= 1;
y -= 1;
}
The animation would be done using:
requestAnimationFrame(fn)
Problem with all this is. I need to manually adjust the x and y everytime. How can I better simply make the x and y values random for the shape and make it animate to the center, no matter from what direction and if the initial position is negative or positive. I was thinking of atang2 but honestly im not entirely sure.
You're basically on the right track. Use Math.sqrt for the distance and Math.atan2 to find the direction. Then its just the matter of how fast (velocity) you want the object to move to the target (centre of the canvas).
var tx = centerX - x,
tx = centerY - y,
distance = Math.sqrt(tx * tx + ty * ty),
radius = Math.atan2(ty, tx),
angle = (radius / Math.PI) * 180;
// Ensure we don't divide by zero if distance is 0
if (distance !== 0)
{
velX = (tx / distance) * velocity;
velY = (ty / distance) * velocity;
x += velX;
y += velY;
}
The answer given is flawed as there is no check for divide by zero. This error can easily be overlooked and then crop up in production code making it very hard to find out what has gone wrong.
Should be
var tx = centre.x - x;
var ty = centre.y - y;
var dist = Math.sqrt(tx * tx + ty * ty);
// or
var dist = Math.sqrt(Math.pow(tx, 2) + Math.pow(ty, 2));
if(dist !== 0){ // must have this test or when the coords get to the centre
// you will get a divide by zero
tx /= dist; // Normalise direction vector
ty /= dist;
}
tx *= speed; // set the magnitude to required speed;
ty *= speed; // Note that if at the centre this will be zero
x += tx;
y += ty;

Making a smoother jump animation

I am working on a little project on JSFiddle just for fun. I was inspired by a simple mario game. I'm just however making a ball move back and forth and jump.
The initial code is working. But what I can't get to happen is a smoother jump.
As you can see here, when you hit space (to make the ball jump) and move left, the jump doesn't look like it has real physics. It makes a triangle like jump instead of going smooth in a half circle type motion. My jump code is:
var top = false;
kd.SPACE.up(function () {
var gravity = 0.3;
var jump = setInterval(function () {
cc();
if (top) {
y += yv;
yv -= gravity;
if (y + r > height) {
y = height - r;
clearInterval(jump);
top = false;
}
} else {
y -= yv;
yv += gravity;
if (y < height - 60) {
top = true;
}
}
circle(x, y, r);
}, 1000 / 30);
});
In general if you want to model realistic "jumping" against the acceleration of gravity, you should be only accelerating from the "jump" in the upward direction initially. This can be modeled by adding a fixed value to your y-velocity at the first frame (when space is pressed). After this point, just subtract your gravity from the y-velocity each frame.
kd.SPACE.up(function () {
var gravity = 0.3;
yv -= 8; // or whatever your jump accel. should be
// you could also just assign this value to yv which is a different effect
var jump = setInterval(function() {
cc();
if (y + r < height) {
y += yv;
yv += gravity;
} else {
y = height - r;
clearInterval(jump);
}
circle(x,y,r);
}, 1000 / 30);
});
Something's weird with this fiddle, or my browser, but here it is I think.
You can have your mario follow a quadratic curve to present a pleasing jump.
Demo: http://jsfiddle.net/m1erickson/qDQBh/
This function returns an XY along a quadratic curve given a interval-value (T) between 0.00 and 1.00.
T==0 is at the start of the curve.
T==1.00 is at the end of the curve.
function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x;
var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y;
return( {x:x,y:y} );
}

Categories