I have a specific question. I have two rectangles and I'm calculating the line between these two rectangles. Now I want to draw a circle on that line, which is moving forward on the line with a specific speed. I'm redrawing the circle always with new coordinates, that's how i solved the movement.
Now I'm adding to the x-coordinate of the Circle always 1 and then calculate the y-coordinate with my equation. There problem here is, the higher my line slope is, the faster the circle moves.
So how can I calculate the x-coordinate, that the speed of the ball is always the same?
Here is my following code. posX and posY are the positions of the ball that I overwrite. Gun and Ammo are my two rectangles.
this.posX = this.posX - 1;
this.posY = ((this.gunY - this.ammoY) / (this.gunX - this.ammoX)) * (this.posX - this.ammoX) + this.ammoY;
image to understand my calculation and my thoughts
Unit vector
Use the line's Unit (normalized) vector. The normalized vector is always one unit long (unless the line has no length) You can scale the vector to what ever speed you require
Normalize line
Example ? should be numbers
// the line start and end
const startX = ?
const startY = ?
const endX = ?
const endY = ?
function getLineNormal() {
// get the line vector
const vx = endX - startX
const vy = endY - startY
// get the line length
const len = Math.hypot(vx, vy)
// Only if the line has length
if (len > 0) {
// calculate normal of vector
return {x: vx / len, y: vy / len}
}
return return {x: 0, y: 0}
}
Scale vector and add unit vector
To use the vector to move at a constant speed. The speed scales the normal vector.
// set circle at start of line.
var circleX = startX
var circleY = startY
const lineVec = getLineNormal()
function moveCircle(speed) { // speed in pixels
circleX += lineVec.x * speed
circleY += lineVec.y * speed
}
Related
I'm trying to rotate an SVG object (a rectangle, which is also draggable using x and y attributes) via rotate(angle x y), x and y is the midpoint of the object which I calculate with this function:
getMidPoint(x, y, width, height, angle, oldMidPointX, oldMidPointY, drawPoint) {
let angleRad = angle * Math.PI / 180;
let cosa = Math.cos(angleRad);
let sina = Math.sin(angleRad);
// Find new x/y values after rotation
let tempX = x - oldMidPointX;
let tempY = y - oldMidPointY;
let xAfterRotation = (tempX * cosa) - (tempY * sina);
let yAfterRotation = (tempX * sina) + (tempY * cosa);
drawPoint(x, y, "aqua");
// translate back
x = xAfterRotation + oldMidPointX;
y = yAfterRotation + oldMidPointY;
drawPoint(x, y, "green");
let wp = width / 2;
let hp = height / 2;
let px = x + (wp * cosa) - (hp * sina);
let py = y + (wp * sina) + (hp * cosa);
drawPoint(x, y, "red");
return {
px: px,
py: py };
}
Consecutive rotations are no problem. The jumping occurs if the object gets rotated, resized (for example increasing the width of the object) and then rotated again. While the function above gives the correct midpoint, the object first jumps and rotates around the the calculated point.
I don't know what I'm missing here, maybe someone can spot the mistake or give me some ideas to try out.
EDIT
Here is an image to showcase my problem.
1: Starting position; red dot marks the midpoint of the object and green dot is the position used for calculating the midpoint.
2: Rotating the object with no problem.
3: Resizing the object.
4: Calculating new midpoint on start; blue dot marks x and y coordinates of the object.
5: Rotating the second time, "jumping" occurs.
6-8: The object rotates (the same rotation started at previous step) around the midpoint but with an offset.
9: 360° rotation puts the top left corner of the object to it's x/y position
Maybe someone can spot the mistake now.
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.
I have a line [from (x1, y1) to (x2, y2)] on the canvas that acts like a gun. I want the bullet to travel in the direction of the line (gun). Let the bullet also be a line. I know that from x1, y1 and x2, y2 I can find the slope of the line m and the y-intercept b. I'm also aware that the equation of a line is y = mx + b. I want the bullet to travel along the equation y = mx + b.
I do not want my bullet to look like a long line that starts from the end of my gun all the way to the boundary of the canvas. I want it to be a small line redrawn multiple times along the equation y = mx + b.
Can someone please guide me on how to draw my bullet's movement? Thanks in advance!
You can use a simple interpolation formula where you animate it by adjusting the factor f.
The formula is (shown only for x):
x = x1 + (x2 - x1) * f
An example on how to implement -
AN ONLINE DEMO
/// add click callback for canvas (id = demo)
demo.onclick = function(e) {
/// get mouse coordinate
var rect = demo.getBoundingClientRect(),
/// gun at center bottom
x1 = demo.width * 0.5,
y1 = demo.height,
/// target is where we click on canvas
x2 = e.clientX - rect.left,
y2 = e.clientY - rect.top,
/// factor [0, 1] is where we are at the line
f = 0,
/// our bullet
x, y;
loop();
}
Then we provide the following code for the loop
function loop() {
/// clear previous bullet (for demo)
ctx.clearRect(x - 2, y - 2, 6, 6);
/// HERE we calculate the position on the line
x = x1 + (x2 - x1) * f;
y = y1 + (y2 - y1) * f;
/// draw some bullet
ctx.fillRect(x, y, 3, 3);
/// increment f until it's 1
if (f < 1) {
f += 0.05;
requestAnimationFrame(loop);
} else {
ctx.clearRect(x - 2, y - 2, 6, 6);
}
}
To draw a "longer" bullet that follows the line you can either store an older value of the x/y pair and draw a line between that and current, or less optimal, calculate the position separately or even calculate the angle and use a fixed length.
Also worth to be aware of: the longer the line is the faster the bullet goes. You can calculate a delta value for f based on length (not shown in demo) to get around this.
I was trying to do a perspective grid on my canvas and I've changed the function from another website with this result:
function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
var h = img.height,
w = img.width,
numSlices = Math.abs(pixelHeight),
sliceHeight = h / numSlices,
polarity = (pixelHeight > 0) ? 1 : -1,
heightScale = Math.abs(pixelHeight) / h,
widthScale = (1 - scalingFactor) / numSlices;
for(var n = 0; n < numSlices; n++) {
var sy = sliceHeight * n,
sx = 0,
sHeight = sliceHeight,
sWidth = w;
var dy = y + (sliceHeight * n * heightScale * polarity),
dx = x + ((w * widthScale * n) / 2),
dHeight = sliceHeight * heightScale,
dWidth = w * (1 - (widthScale * n));
ctx.drawImage(img, sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight);
}
}
It creates almost-good perspective grid, but it isn't scaling the Height, so every square has got the same height. Here's a working jsFiddle and how it should look like, just below the canvas. I can't think of any math formula to distort the height in proportion to the "perspective distance" (top).
I hope you understand. Sorry for language errors. Any help would be greatly appreciatedRegards
There is sadly no proper way besides using a 3D approach. But luckily it is not so complicated.
The following will produce a grid that is rotatable by the X axis (as in your picture) so we only need to focus on that axis.
To understand what goes on: We define the grid in Cartesian coordinate space. Fancy word for saying we are defining our points as vectors and not absolute coordinates. That is to say one grid cell can go from 0,0 to 1,1 instead of for example 10,20 to 45, 45 just to take some numbers.
At the projection stage we project these Cartesian coordinates into our screen coordinates.
The result will be like this:
ONLINE DEMO
Ok, lets dive into it - first we set up some variables that we need for projection etc:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
To adjust the grid we don't adjust the loops (see below) but alter the fov and viewDist as well as modifying the grid to increase or decrease the number of cells.
Lets say you want a more extreme view - by setting fov to 128 and viewDist to 5 you will get this result using the same grid and angle:
The "magic" function doing all the math is as follows:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
And that's it. Worth to mention is that it is the combination of the new Y and Z that makes the lines smaller at the top (at this angle).
Now we can create a grid in Cartesian space like this and rotate those points directly into screen coordinate space:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
Also notice that position 0,0 is center of screen. This is why we use negative values to get out on the left side or upwards. You can see that the two center lines are straight lines.
And that's all there is to it. To color a cell you simply select the Cartesian coordinate and then convert it by calling rotateX() and you will have the coordinates you need for the corners.
For example - a random cell number is picked (between -10 and 10 on both X and Y axis):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
An animated version that can help see what goes on.
I'm trying to create a html canvas where the user can define a start- and endpoint, between the start and endpoint I want to draw a waved line, I'm doing this by drawing bezierCurveTo.
a sample:
the code I use to draw this is the following:
var wave = new Kinetic.Shape({
drawFunc: function (canvas) {
var ctx = canvas.getContext();
ctx.beginPath();
ctx.moveTo(50, 50);
var waveCount = 0;
var controlPoint1X = 55;
var controlPoint2X = 60;
var endPointX = 65;
while(waveCount < 10) {
ctx.bezierCurveTo(controlPoint1X, 35, controlPoint2X, 65, endPointX, 50);
controlPoint1X += 20;
controlPoint2X += 20;
endPointX += 20;
waveCount++;
}
ctx.stroke(_this);
},
stroke: '#000000',
strokeWidth: 2
});
I can make this work as long as only the x or only the y coordinate changes. Now I want to be able to create a waved line like shown above but with a different x,y coordinate. For example startpoint x: 50 y: 50 and endpoint x: 100 y: 100. I know I have to calculate the controlpoints, but I can't find out what formula I have to use. Can someone help me out?
Let's simulate a circle and sinewave on a straight line. For a semi-circle, each "period" consists of two segments, with segment one being:
cDist = 4/3 * amplitude
(we know this from http://pomax.github.com/bezierinfo/#circles_cubic)
S = (x1, 0),
C1 = (x1, cDist)
C2 = (x2, cDist)
E = (x2, 0)
and segment two being:
S = (x2, 0),
C1 = (x2, -cDist)
C2 = (x3, -cDist)
E = (x3, 0)
For a sine wave, the control points are almost the same; the y coordinate stays at the same height, but we need to shift the x coordinates so that the shape has the corrected angle at the start and end points (for a circle they're vertical, for a sine wave they're diagonal):
S = (x1, 0),
C1 = (x1 + cDist/2, cDist)
C2 = (x2 - cDist/2, cDist)
E = (x2, 0)
and segment two is:
S = (x2, 0),
C1 = (x2+cDist, -cDist)
C2 = (x3-cDist, -cDist)
E = (x3, 0)
I put up a demonstrator of this at: http://jsfiddle.net/qcUyC/6
If you want these lines to be at a fixed angle, my advice is: rotate your context. Don't actually change your coordinates. Just use context.rotate(...) and you're done. See http://jsfiddle.net/qcUyC/7
But, if you absolutely need coordinates that aren't just drawn in the right place, but have coordinates that represent a real angled line, then start with your angle:
angle = some value you picked, in radians (somewhere between 0 and 2*pi)
with that angle, we can place our points:
dx = some fixed value we pick
dy = some fixed value we pick
ox = the x-offset w.r.t. 0 for the first coordinate in our line
oy = the y-offset w.r.t. 0 for the first coordinate in our line
x1 = ox
y1 = oy
x2 = (dx * cos(angle) - dy * sin(angle)) + ox
y2 = (dx * sin(angle) + dy * cos(angle)) + oy
x3 = (2*dx * cos(angle) - 2*dy * sin(angle)) + ox
y3 = (2*dx * sin(angle) + 2*dy * cos(angle)) + oy
...
xn = ((n-1)*dx * cos(angle) - (n-1)*dy * sin(angle)) + ox
yn = ((n-1)*dx * sin(angle) + (n-1)*dy * cos(angle)) + oy
you then have to treat your control points as vectors relative to the start point in your segments, so C1' = C1-S, and C2' = C2-S, and then you rotate those with the same transformation. You then add those vectors back up to your starting point and you now have the correctly rotated control point.
That said, don't do that. Let the canvas2d API do the rotation for you and just draw straight lines. It makes life so much easier.