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
Related
I'm trying to make a circle object move to a ball position but whenever it is near to the ball position, it slows down no matter where it starts to move. I can't seem to make it move at a constant speed without slowing it down.
I'm using lerp for linear interpolation and using it directly in my move function.
function lerp(v0, v1, t) {
return v0 * (1 - t) + v1 * t;
};
FPS = 60;
function Objects(/*parameters*/){
this.pos = new Vector2D(x, y);
this.move = function(x, y){
this.pos.x = lerp(this.pos.x, x, FPS/1000);
this.pos.y = lerp(this.pos.y, y, FPS/1000);
};
};
function update(){
//Move object to ball position
SuperObject.move(ball.pos.x, ball.pos.y);
drawObjects();
setTimeout(update, 1000/FPS);
};
DEMO link: http://codepen.io/knoxgon/pen/EWVyzv
This is the expected behavior. As you set the position by linearly interpolating from the current position to the target, it defines a convergent series.
Lets see a simpler example: Say you have only one dimension, and the circle is originally at x(0)=10 and the target is at tx=0. You define every step by x(n+1) = lerp(x(n), tx, 0.1) = 0.9 * x(n) + 0.1 * tx = 0.9 * x(n) (0.9 for simplicity). So the series becomes x(0) = 10, x(1) = 9, x(2) = 8.1, x(n) = 10 * pow(0.9, n), which is a convergent geometric progression, and will never describe a motion at constant speed.
You have to change your equation:
move(x, y) {
let deltax = x - this.pos.x;
let deltay = y - this.pos.y;
const deltaLength = Math.sqrt(deltax * deltax + deltay * deltay);
const speed = 10;
if (deltaLength > speed) {
deltax = speed * deltax / deltaLength;
deltay = speed * deltay / deltaLength;
}
this.pos.x += deltax;
this.pos.y += deltay;
}
http://codepen.io/anon/pen/LWpRWJ
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.
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;
I have a question about Faking 3d in HTML5/Canvas/Javascript...
Basically, I have a 2d image drawn on a <canvas> using drawImage().
What I would like to do, is draw the image, then displace the texture on the sphere to look... sphere-like...
see image below for clarity:
Any ideas?
I have googled myself to near-death over this, also it cannot be WebGL as it has to work on Mobiles... is there a way to do this?
You can check a working prototype here: http://jsbin.com/ipaliq/edit
I bet there's ton's of room for optimization and better/faster algorithms, but I hope this proof of concepts will point you in the right direction.
The basic algorithm goes through each pixel of the original image and calculates its new position if it was subject to an spherical deformation.
Here's the result:
Code:
var w = 150, h = 150;
var canvas=document.getElementById("myCanvas");
var ctx=canvas.getContext("2d");
ctx.fillStyle="red";
ctx.fillRect(0,0,w,h);
//-- Init Canvas
var initCanvas = function()
{
var imgData=ctx.getImageData(0,0,w,h);
for (i=0; i<imgData.width*imgData.height*4;i+=4)
{
imgData.data[i]=i%150*1.5;
imgData.data[i+1]=i%150*1.5;
imgData.data[i+2]=(i/imgData.width)%150*1.5;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,0,0);
};
initCanvas();
var doSpherize = function()
{
var refractionIndex = 0.5; // [0..1]
//refraction index of the sphere
var radius = 75;
var radius2 = radius * radius;
var centerX = 75;
var centerY = 75;
//center of the sphere
var origX = 0;
var origY = 0;
for (x=0; x<w;x+=1)
for (y=0; y<h;y+=1)
{
var distX = x - centerX;
var distY = y - centerY;
var r2 = distX * distX + distY * distY;
origX = x;
origY = y;
if ( r2 > 0.0 && r2 < radius2 )
{
// distance
var z2 = radius2 - r2;
var z = Math.sqrt(z2);
// refraction
var xa = Math.asin( distX / Math.sqrt( distX * distX + z2 ) );
var xb = xa - xa * refractionIndex;
var ya = Math.asin( distY / Math.sqrt( distY * distY + z2 ) );
var yb = ya - ya * refractionIndex;
// displacement
origX = origX - z * Math.tan( xb );
origY = origY - z * Math.tan( yb );
}
// read
var imgData=ctx.getImageData(origX,origY,1,1);
// write
ctx.putImageData(imgData,x+w,y+h);
}
};
doSpherize();
Note
I would advice agains such an intense operation to be handed by the frontend without webGL pixel shaders. Those are very optimized and will be able to take advantage of GPU accelaration.
You can definitely do something like this. I'm not sure if the quality and speed will be good enough though, particularly on mobile.
You're going to want to getImageData the pixels, perform some mathematical transformation on them I can't think of at the moment, and then putImageData them back.
It's probably going to look kind of blurry or pixelated (you could try interpolating but that will only get you so far). And you may have to optimize your javascript considerably to get it done in a reasonable time on a mobile device.
The transformation is probably something like an inverse azimuthal projection.
Basically I've managed to layout my DIV elements into a circle shape but I've not managed to work out how to calculate the deg of rotation need to have them face OUTWARD from the center of the circle.
$(document).ready(function(){
var elems = document.getElementsByClassName('test_box');
var increase = Math.PI * 2 / elems.length;
var x = 0, y = 0, angle = 0;
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
// modify to change the radius and position of a circle
x = 400 * Math.cos(angle) + 700;
y = 400 * Math.sin(angle) + 700;
elem.style.position = 'absolute';
elem.style.left = x + 'px';
elem.style.top = y + 'px';
//need to work this part out
var rot = 45;
elem.style['-moz-transform'] = "rotate("+rot+"deg)";
elem.style.MozTransform = "rotate("+rot+"deg)";
elem.style['-webkit-transform'] = "rotate("+rot+"deg)";
elem.style['-o-transform'] = "rotate("+rot+"deg)";
elem.style['-ms-transform'] = "rotate("+rot+"deg)";
angle += increase;
console.log(angle);
}
});
does anyone have to knowledge on how I can do this.
Cheers -C
Note that rot depends on angle, except angle is in radians.
DRY, so either convert from angle to rot:
// The -90 (degrees) makes the text face outwards.
var rot = angle * 180 / Math.PI - 90;
Or just use angle when setting the style (but use radians as a unit), and drop rot's declaration:
// The -0.5*Math.PI (radians) makes the text face outwards.
elem.style.MozTransform = "rotate("+(angle-0.5*Math.PI)+"rad)";
Try this:
var rot = 90 + (i * (360 / elems.length));
Demo at http://jsfiddle.net/gWZdd/
I've added the 90 degrees at the start there to ensure the baseline of the divs face towards the centre, however you can adjust this to suit your needs.