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.
Related
I'm trying to make some simple pool game in java script. I have made it but I do not love way of checking if two balls will collide in next frame. I would like to have more easier way to calculate coordinates of balls when collision occurs. I found lot of answers base on collision kinematics, how to handle velocities and directions after collision, but no calculating a position when collision occurs.
As you can see in sample diagram, gold ball is moving slower than a blue ball, and with distance that each ball will have to move on next frame will not be considered as collision. But, as you can see, they should collide (dashed lines).
In that cause I have divided each movement into sectors and calculating if distance between the points is equal or smaller than ball diameter, which is slowing down process when many balls (like in snooker) have to be calculated in each frame, plus that way is not always 100% accurate and balls can go in inaccurate angles after hit (not a big difference, but important in snooker).
Is there any easier way to calculate those (XAC,YAC) and (XBC,YBC) values with knowing start positions and velocities of each ball without dividing ball paths into sectors and calculating many times to find a proper distance?
It is worth to precalculate collision event only once (this approach works well with reliable number of balls, because we have to treat all ~n^2 pairs of balls).
The first ball position is A0, velocity vector is VA.
The second ball position is B0, velocity vector is VB.
To simplify calculations, we can use Halileo principle - use moving coordinate system connected with the first ball. In that system position and velocity of the first ball are always zero. The second ball position against time is :
B'(t) = (B0 - A0) + (VB - VA) * t = B0' + V'*t
and we just need to find solution of quadratic equation for collision distance=2R:
(B0'.X + V'.X*t)^2 + (B0'.X + V'.Y*t)^2 = 4*R^2
Solving this equation for unknown time t, we might get cases: no solutions (no collision), single solution (only touch event), two solutions - in this case smaller t value corresponds to the physical moment of collision.
Example (sorry, in Python, ** is power operator):
def collision(ax, ay, bx, by, vax, vay, vbx, vby, r):
dx = bx - ax
dy = by - ay
vx = vbx - vax
vy = vby - vay
#(dx + vx*t)**2 + (dy + vy*t)**2 == 4*r*r solve this equation
#coefficients
a = vx**2 + vy**2
b = 2*(vx*dx + vy*dy)
c = dx**2+dy**2 - 4*r**2
dis = b*b - 4*a*c
if dis<0:
return None
else:
t = 0.5*(-b - dis**0.5)/a ##includes case of touch when dis=0
return [(ax + t * vax, ay + t * vay), (bx + t * vbx, by + t * vby)]
print(collision(0,0,100,0,50,50,-50,50,10)) #collision
print(collision(0,0,100,0,50,50,-50,80,10)) #miss
print(collision(0,0,100,0,100,0,99,0,10)) #long lasting chase along OX axis
[(40.0, 40.0), (60.0, 40.0)]
None
[(8000.0, 0.0), (8020.0, 0.0)]
Regarding to MBo's solution, here is a function in java script that will calculate coordinates of balls on collision and time in which collision will happen:
calcCollisionBallCoordinates(ball1_x, ball1_y, ball2_x, ball2_y, ball1_vx, ball1_vy, ball2_vx, ball2_vy, r) {
let dx = ball2_x - ball1_x,
dy = ball2_y - ball1_y,
vx = ball2_vx - ball1_vx,
vy = ball2_vy - ball1_vy,
a = Math.pow(vx, 2) + Math.pow(vy, 2),
b = 2 * (vx * dx + vy * dy),
c = Math.pow(dx, 2) + Math.pow(dy, 2) - 4 * Math.pow(r, 2),
dis = Math.pow(b, 2) - 4 * a * c;
if (dis < 0) {
//no collision
return false;
} else {
let t1 = 0.5 * (-b - Math.sqrt(dis)) / a,
t2 = 0.5 * (-b + Math.sqrt(dis)) / a,
t = Math.min(t1, t2);
if (t < 0) {
//time cannot be smaller than zero
return false;
}
return {
ball1: {x: ball1_x + t * ball1_vx, y: ball1_y + t * ball1_vy},
ball2: {x: ball2_x + t * ball2_vx, y: ball2_y + t * ball2_vy},
time: t
};
}
}
I'm receiving all distances between a random number of points in a 2 dimensional coordinate system.
How can I visualize this as coordinates on a map in my browser?
In case there are many solutions I just want to see the first possible one that my algorithm can come up with.
So here's an extremely easy example:
PointCount = 3
Distances:
0-1 = 2
0-2 = 4
1-2 = 2
Does anyone know an easy way (existing solution/framework maybe) to do it using whatever is out there to make it easier to implement?
I was thinking maybe using the html canvas element for drawing, but I don't know how to create an algorithm that could come up with possible coordinates for those points.
The above example is simplified -
Real distance values could look like this:
(0) (1) (2) (3)
(0) 0 2344 3333 10000
(1) 0 3566 10333
(2) 0 12520
I'm not sure this is relevant for SO, but anyway...
The way to do this is quite simply to place the points one by one using the data:
Pick a random location for the first point (let's say it's 0,0).
The second point is on a circle with radius d(0,1) with the first point as its center, so you can pick any point on the circle. Let's pick (d(0,1),0).
The third point is at the intersection of a circle with radius d(0,2) and center point 1, and a circle with radius d(1,2) and center point 2. You will get either 0, 1, 2 or an infinity of solutions. If the data comes from real points, 0 shouldn't happen. 1 and infinity are edge cases, but you should still handle them. Pick any of the solutions.
The fourth point is at the intersection of 3 circles. Unless you're very unlucky (but you should account for it), there should be only one solution.
Continue like this until all points have been placed.
Note that this doesn't mean you'll get the exact locations of the original points: you can have any combination of a translation (the choice of your first point), rotation (the choice of your second point) and symmetry (the choice of your third point) making the difference.
A quick and dirty implementation (not handling quite a few cases, and tested very little):
function distance(p1, p2) {
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}
// adapted from https://stackoverflow.com/a/12221389/3527940
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0 * r0) - (a * a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h / d);
ry = dx * (h / d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [
[xi, yi],
[xi_prime, yi_prime]
];
}
function generateData(nbPoints) {
var i, j, k;
var originalPoints = [];
for (i = 0; i < nbPoints; i++) {
originalPoints.push([Math.random() * 20000 - 10000, Math.random() * 20000 - 10000]);
}
var data = [];
var distances;
for (i = 0; i < nbPoints; i++) {
distances = [];
for (j = 0; j < i; j++) {
distances.push(distance(originalPoints[i], originalPoints[j]));
}
data.push(distances);
}
//console.log("original points", originalPoints);
//console.log("distance data", data);
return data;
}
function findPointsForDistances(data, threshold) {
var points = [];
var solutions;
var solutions1, solutions2;
var point;
var i, j, k;
if (!threshold)
threshold = 0.01;
// First point, arbitrarily set at 0,0
points.push([0, 0]);
// Second point, arbitrarily set at d(0,1),0
points.push([data[1][0], 0]);
// Third point, intersection of two circles, pick any solution
solutions = intersection(
points[0][0], points[0][1], data[2][0],
points[1][0], points[1][1], data[2][1]);
//console.log("possible solutions for point 3", solutions);
points.push(solutions[0]);
//console.log("solution for points 1, 2 and 3", points);
found = true;
// Subsequent points, intersections of n-1 circles, use first two to find 2 solutions,
// the 3rd to pick one of the two
// then use others to check it's valid
for (i = 3; i < data.length; i++) {
// distances to points 1 and 2 give two circles and two possible solutions
solutions = intersection(
points[0][0], points[0][1], data[i][0],
points[1][0], points[1][1], data[i][1]);
//console.log("possible solutions for point " + (i + 1), solutions);
// try to find which solution is compatible with distance to point 3
found = false;
for (j = 0; j < 2; j++) {
if (Math.abs(distance(solutions[j], points[2]) - data[i][2]) <= threshold) {
point = solutions[j];
found = true;
break;
}
}
if (!found) {
console.log("could not find solution for point " + (i + 1));
console.log("distance data", data);
console.log("solution for points 1, 2 and 3", points);
console.log("possible solutions for point " + (i + 1), solutions);
console.log("distances to point 3",
distance(solutions[0], points[2]),
distance(solutions[1], points[2]),
data[i][2]
);
break;
}
// We have found a solution, we need to check it's valid
for (j = 3; j < i; j++) {
if (Math.abs(distance(point, points[j]) - data[i][j]) > threshold) {
console.log("Could not verify solution", point, "for point " + (i + 1) + " against distance to point " + (j + 1));
found = false;
break;
}
}
if (!found) {
console.log("stopping");
break;
}
points.push(point);
}
if (found) {
//console.log("complete solution", points);
return points;
}
}
console.log(findPointsForDistances([
[],
[2344],
[3333, 3566],
[10000, 10333, 12520],
]));
console.log(findPointsForDistances([
[],
[2],
[4, 2],
]));
console.log(findPointsForDistances([
[],
[4000],
[5000, 3000],
[3000, 5000, 4000]
]));
console.log(findPointsForDistances([
[],
[2928],
[4938, 3437],
[10557, 10726, 13535]
]));
var nbPoints, i;
for (nbPoints = 4; nbPoints < 8; nbPoints++) {
for (i = 0; i < 10; i++) {
console.log(findPointsForDistances(generateData(nbPoints)));
}
}
Fiddle here: https://jsfiddle.net/jacquesc/82aqmpnb/15/
Minimum working example. Remember that in canvas coordinates, the y value is inverted but you could do something like:
y = canvasHeight - y
If you also have negative points then if would take a little bit of extra work. Also it may be helpful in that case to draw lines and tick marks to visualize the axis.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let scale = 10;
let radius = 10;
function point(x, y) {
ctx.fillRect(x*scale, y*scale, radius, radius);
}
// test
point(10, 15);
point(20, 8);
<html>
<body>
<canvas id="canvas" width=1000 height=1000></canvas>
</body>
</html>
There are plenty of libraries out there.
chartist.js is easy to use and responsive JavaS cript library. I used it last year for basic charts after trying many others but it was the only one that scaling easily in different screen sizes.
chartJS is another better looking library.
And you can use html5 canvas it's easy and fun but it will take time especially in scaling.
To scale and position, you should use the minimum and maximum values for x and y.
Good luck
I've been trying to implement collision detection between circles and polygons based on Randy Gaul's C++ Impulse Engine, following the code pretty closely, but the algorithm never returns true.
Here's the JSFiddle. (the bodies are rendered using the HTML5 Canvas API for convenience)
A snippet of the code (just collision detection):
const circPoly = (a, b) => {
let data = {},
center = a.pos;
data.contacts = [];
center = b.mat.clone().trans().mult(center.clone().sub(b.pos));
let sep = -Number.MAX_VALUE,
faceNorm = 0;
for (let i = 0; i < b.verts2.length; ++i) {
let sep2 = b.norms[i].dot(center.clone().sub(b.verts2[i]));
if (sep2 > a.radius) return data;
if (sep2 > sep) { sep = sep2; faceNorm = i; }
}
let v1 = b.verts2[faceNorm],
v2 = b.verts2[faceNorm + 1 < b.verts2.length ? faceNorm + 1 : 0];
if (sep < 0.0001) {
data.depth = a.radius;
data.norm = b.mat.clone().mult(b.norms[faceNorm]).neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
return data;
}
let dot1 = center.clone().sub(v1).dot(v2.clone().sub(v1)),
dot2 = center.clone().sub(v2).dot(v1.clone().sub(v2));
data.depth = a.radius - sep;
if (dot1 <= 0) {
if (center.dist2(v1) > a.radius * a.radius) return data;
let norm = v1.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v1 = b.mat.clone().mult(v1.clone().add(b.pos));
data.contacts[0] = v1;
} else if (dot2 <= 0) {
if (center.dist2(v2) > a.radius * a.radius) return data;
let norm = v2.clone().sub(center);
norm = b.mat.clone().mult(norm);
norm.norm();
data.norm = norm;
v2 = b.mat.clone().mult(v2.clone().add(b.pos));
data.contacts[0] = v2;
} else {
let norm = b.norms[faceNorm];
if (center.clone().sub(v1).dot(norm) > a.radius) return data;
norm = b.mat.clone().mult(norm);
data.norm = norm.clone().neg();
data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
}
return data;
};
Note that b.verts2 refers to the polygon's vertices in real world coordinates.
I know for a fact that there are no problems with the Vector class but as I don't exactly have very much experience with transformation matrices, that class could be the root of these errors, although the code for it is pretty much entirely derived from the Impulse Engine as well, so it should work. As mentioned before, the algorithm always returns false, even when a collision really has occurred. What am I doing wrong here? I tried taking out the early returns, but that just returns weird results like contact points with negative coordinates which obviously is not quite correct.
EDIT: Modified my vector class's perpendicular function to work the same way as the Impulse Engine's (both ways are right, but I think one is clockwise and the other one counterclockwise -- I also modified my vertices to reflect the counterclockwise-ness). Unfortunately, it still fails the test.
https://jsfiddle.net/khanfused/tv359kgL/4/
Well the are many problems and I really dont understand what you are trying to do as it seem overly complex. Eg why does matrix have trans??? and why are you using the Y up screen as the coordinate system for the transform??? (rhetorical)
In the first loop.
The first is that you are testing the distance of the normal vectors
of each vert, should be testing the vert position.
Also you are finding the distance using the vec.dot function that
returns the square of the distance. But you test for the radius, you
should be testing for if(sep2 < radius * radius)
And you have the comparison the wrong way around you should be
testing if less than radius squared (not greater than)
Then when you do detect a vert within the radius you return the data
object but forget to put the vert that was found inside the circle on
the data.contacts array.
I am not sure what the intention of keeping the index of the most
distant vect is but then the rest of the function make zero sense to
me???? :( and I have tried to understand it.
All you need to do is
A check if any verts on the poly are closer than radius, if so then you have a intercept (or is completely inside)
Then you need to check the distance of each line segment
Can be done for each line segment with the following if you dont need the intercepts (or below that if you need intercepts) only use one or the other.
// circle is a point {x:?,y:?}
// radius = is the you know what
// p1,p2 are the start and end points of a line
checkLineCircle = function(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var v3 = {};
var u;
// get dist to end of line
v2.x = circle.x - p1.x;
v2.y = circle.y - p1.y;
// check if end points are inside the circle
if( Math.min(
Math.hypot(p2.x - circle.x, p2.y - circle.y),
Math.hypot(v2.x, v2.y)
) <= radius){
return true;
}
// get the line as a vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// get the unit distance of the closest point on the line
u = (v2.x * v1.x + v2.y * v1.y)/(v1.y * v1.y + v1.x * v1.x);
// is this on the line segment
if(u >= 0 && u <= 1){
v3.x = v1.x * u; // get the point on the line segment
v3.y = v1.y * u;
// get the distance to that point and return true or false depending on the
// it being inside the circle
return (Math.hypot(v3.y - v2.y, v3.x - v2.x) <= radius);
}
return false; // no intercept
}
Do that for each line.To save time transform the circle center to the polygon local, rather than transform each point on the poly.
If you need the points of intercept then use the following function
// p1,p2 are the start and end points of a line
// returns an array empty if no points found or one or two points depending on the number of intercepts found
// If two points found the first point in the array is the point closest to the line start (p1)
function circleLineIntercept(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var ret = [];
var u1,u2,b,c,d;
// line as vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// vector to circle center
v2.x = p1.x - circle.x;
v2.y = p1.y - circle.y;
// dot of line and circle
b = (v1.x * v2.x + v1.y * v2.y) * -2;
// length of line squared * 2
c = 2 * (v1.x * v1.x + v1.y * v1.y);
// some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line.
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius));
// will give a NaN if no solution
if(isNaN(d)){ // no intercept
return ret;
}
// get the unit distance of each intercept to the line
u1 = (b - d) / c;
u2 = (b + d) / c;
// check the intercept is on the line segment
if(u1 <= 1 && u1 >= 0){
ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 });
}
// check the intercept is on the line segment
if(u2 <= 1 && u2 >= 0){
ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2});
}
return ret;
}
I will leave it up to you to do the polygon iteration.
I have been wrestling with rendering an animation that fires a projectile accurately from an "enemy" node to a "player" node in a 2D 11:11 grid (0:0 = top-left) in JS/Canvas. After a lot of reading up I've managed to get the shots close, but not quite bang on. I think my velocity function is a little out but I really don't know why. This is the trigonometric function:
this.getVelocityComponents = function(speed){
// loc (location of enemy actor) = array(2) [X_coord, Y_coord]
// des (destination (ie. player in this instance)) = array(2) [X_coord, Y_coord]
var i, sum, hyp, output = [], dis = [];
var higher = false;
for (i in loc) {
sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
dis.push(sum);
}
hyp = Math.sqrt(Math.pow(dis[X], 2) + Math.pow(dis[Y], 2));
if (dis[X] > dis[Y]) {
output[X] = (speed * Math.cos(dis[X]/hyp))
output[Y] = (speed * Math.sin(dis[Y]/hyp))
} else if (dis[X] < dis[Y]) {
output[X] = (speed * Math.cos(dis[Y]/hyp))
output[Y] = (speed * Math.sin(dis[X]/hyp))
}
return output;
}
and this is the instruction that tells the X and the Y of the projectile frame to advance:
var distance = [];
for (i in loc) {
var sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
distance.push(sum);
}
if (distance[X] > distance[Y]) {
frm[X] += (loc[X] < des[X]) ? v[X] : -v[X];
frm[Y] += (loc[Y] < des[Y]) ? v[Y] : -v[Y];
} else {
frm[Y] += (loc[Y] < des[Y]) ? v[X] : -v[X];
frm[X] += (loc[X] < des[X]) ? v[Y] : -v[Y];
}
Below is a screenshot. Blue is player, pink enemy and the yellow circles are projectiles
as you can see, it's almost on the mark.
Have I done something wrong? what do I need to do?
To calculate the direction from enemy to player you can simplify the calculations a little.
Find direction angle
var diffX = Player.x - Enemy.x, // difference in position
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX); // atan2 will give the angle in radians
Notice also difference for Y comes first for atan2 as canvas is oriented 0° pointing right.
Velocity vector
Then calculate the velocity vector using angle and speed:
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed;
You might want to consider using time as a factor if that is important. You can see my answer from a while back here for an example on this.
Demo
Using these calculations you will be able to always "hit" the player with the projectile (reload demo to change enemy position to random y):
var ctx = document.querySelector("canvas").getContext("2d"),
Player = {
x: 470,
y: 75
},
Enemy = {
x: 100,
y: Math.random() * 150 // reload demo to change y-position
};
// calculate angle
var diffX = Player.x - Enemy.x,
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX);
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed,
x = Enemy.x, // projectil start
y = Enemy.y + 50;
// render
(function loop() {
ctx.clearRect(0, 0, 500, 300);
ctx.fillRect(Player.x, Player.y, 30, 100);
ctx.fillRect(Enemy.x, Enemy.y, 30, 100);
ctx.fillRect(x - 3, y -3, 6, 6);
x += vx;
y += vy;
if (x < 500) requestAnimationFrame(loop);
})();
<canvas width=500 height=300></canvas>
The solution is much simpler than that.
What should you do ?
1) compute the vector that leads from you enemy to the player. That will be the shooting direction.
2) normalize the vector : meaning you build a vector that has a length of 1, with the same direction.
3) multiply that vector by your speed : now you have a correct speed vector, with the right norm, aimed at the player.
Below some code to help you understand :
function spawnBullet(enemy, player) {
var shootVector = [];
shootVector[0] = player[0] - enemy[0];
shootVector[1] = player[1] - enemy[1];
var shootVectorLength = Math.sqrt(Math.pow(shootVector[0], 2) + Math.pow(shootVector[1],2));
shootVector[0]/=shootVectorLength;
shootVector[1]/=shootVectorLength;
shootVector[0]*=bulletSpeed;
shootVector[1]*=bulletSpeed;
// ... here return an object that has the enemy's coordinate
// and shootVector as speed
}
Then, since you don't use time in your computations (!! wrooong !! ;-) ) you will make the bullet move with the straightforward :
bullet[0] += bullet.speed[0];
bullet[1] += bullet.speed[1];
Now the issue with fixed-step is that your game will run, say, twice slower on a 30fps device than on a 60fps device. The solution is to compute how much time elapsed since the last refresh, let's call this time 'dt'. Using that time will lead you to an update like :
bullet[0] += dt * bullet.speed[0];
bullet[1] += dt * bullet.speed[1];
and now you'll be framerate-agnostic, your game will feel the same on any device.
As part of a word cloud rendering algorithm (inspired by this question), I created a Javascript / Processing.js function that moves a rectangle of a word along an ever increasing spiral, until there is no collision anymore with previously placed words. It works, yet I'm uncomfortable with the code quality.
So my question is: How can I restructure this code to be:
readable + understandable
fast (not doing useless calculations)
elegant (using few lines of code)
I would also appreciate any hints to best practices for programming with a lot of calculations.
Rectangle moveWordRect(wordRect){
// Perform a spiral movement from center
// using the archimedean spiral and polar coordinates
// equation: r = a + b * phi
// Calculate mid of rect
var midX = wordRect.x1 + (wordRect.x2 - wordRect.x1)/2.0;
var midY = wordRect.y1 + (wordRect.y2 - wordRect.y1)/2.0;
// Calculate radius from center
var r = sqrt(sq(midX - width/2.0) + sq(midY - height/2.0));
// Set a fixed spiral width: Distance between successive turns
var b = 15;
// Determine current angle on spiral
var phi = r / b * 2.0 * PI;
// Increase that angle and calculate new radius
phi += 0.2;
r = (b * phi) / (2.0 * PI);
// Convert back to cartesian coordinates
var newMidX = r * cos(phi);
var newMidY = r * sin(phi);
// Shift back respective to mid
newMidX += width/2;
newMidY += height/2;
// Calculate movement
var moveX = newMidX - midX;
var moveY = newMidY - midY;
// Apply movement
wordRect.x1 += moveX;
wordRect.x2 += moveX;
wordRect.y1 += moveY;
wordRect.y2 += moveY;
return wordRect;
}
The quality of the underlying geometric algorithm is outside my area of expertise. However, on the quality of the code, I would say you could extract a lot of functions from it. Many of the lines that you have commented could be turned into separate functions, for example:
Calculate Midpoint of Rectangle
Calculate Radius
Determine Current Angle
Convert Polar to Cartesian Coodinates
You could consider using more descriptive variable names too. 'b' and 'r' require looking back up the code to see what they are for, but 'spiralWidth' and 'radius' do not.
In addition to Stephen's answer,
simplify these two lines:
var midX = wordRect.x1 + (wordRect.x2 - wordRect.x1)/2.0;
var midY = wordRect.y1 + (wordRect.y2 - wordRect.y1)/2.0;
The better statements:
var midX = (wordRect.x1 + wordRect.x2)/2.0;
var midY = (wordRect.y1 + wordRect.y2)/2.0;