What is the easiest way to calculate position of balls on collision? - javascript

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

Related

How can I offset a global directional force to be applied over a local axis?

I want to apply a forward force in relation to the object's local axis, but the engine I'm using only allows to me apply a force over the global axis.
I have access to the object's global rotation as a quaternion. I'm not familiar with using quats however (generally untrained in advanced maths). Is that sufficient information to offset the applied force along the desired axis? How?
For example, to move forward globally I would do:
this.entity.rigidbody.applyForce(0, 0, 5);
but to keep that force applied along the object's local axis, I need to distribute the applied force in a different way along the axes, based on the object's rotational quat, for example:
w:0.5785385966300964
x:0
y:-0.815654993057251
z:0
I've researched quaternions trying to figure this out, but watching a video on what they are and why they're used hasn't helped me figure out how to actually work with them to even begin to figure out how to apply the offset needed here.
What I've tried so far was sort of a guess on how to do it, but it's wrong:
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
//converted this from a python func on wikipedia,
//not sure if it's working properly or not
function convertQuatToEuler(w, x, y, z){
ysqr = y * y;
t0 = 2 * (w * x + y * z);
t1 = 1 - 2 * (x * x + ysqr);
X = Math.degrees(Math.atan2(t0, t1));
t2 = 2 * (w * y - z * x);
t2 = (t2 >= 1) ? 1 : t2;
t2 = (t2 < -1) ? -1 : t2;
Y = Math.degrees(Math.asin(t2));
t3 = 2 * (w * z + x * y);
t4 = 1 - 2 * (ysqr + z * z);
Z = Math.degrees(Math.atan2(t3, t4));
console.log('converted', {w, x, y, z}, 'to', {X, Y, Z});
return {X, Y, Z};
}
function applyGlobalShift(x, y, z, quat) {
var euler = convertQuatToEuler(quat.w, quat.x, quat.y, quat.z);
x = x - euler.X; // total guess
y = y - euler.Y; // total guess
z = z - euler.Z; // total guess
console.log('converted', quat, 'to', [x, y, z]);
return [x, y, z];
}
// represents the entity's current local rotation in space
var quat = {
w:0.6310858726501465,
x:0,
y:-0.7757129669189453,
z:0
}
console.log(applyGlobalShift(-5, 0, 0, quat));
Don't laugh at my terrible guess at how to calculate the offset :P I knew it was not even close but I'm really bad at math
Quaternions are used as a replacement for euler angles. Your approach, thus, defeats their purpose. Instead of trying to use euler angles, levy the properties of a quaternion.
A quaternion has 4 components, 3 vector components and a scalar component.
q = x*i + y*j + z*k + w
A quaternion therefore has a vector part x*i + y*j + z*k and a scalar part w. A vector is thus a quaternion with a zero scalar or real component.
It is important to note that a vector multiplied by a quaternion is another vector. This can be easily proved by using the rules of multiplication of quaternion basis elements (left as an exercise for the reader).
The inverse of a quaternion is simply its conjugate divided by its magnitude. The conjugate of a quaternion w + (x*i + y*j + z*k) is simply w - (x*i + y*j + z*k), and its magnitude is sqrt(x*x + y*y + z*z + w*w).
A rotation of a vector is simply the vector obtained by rotating that vector through an angle about an axis. Rotation quaternions represent such an angle-axis rotation as shown here.
A vector v can be rotated about the axis and through the angle represented by a rotation quaternion q by conjugating v by q. In other words,
v' = q * v * inverse(q)
Where v' is the rotated vector and q * v * inverse(q) is the conjugation operation.
Since the quaternion represents a rotation, it can be reasonably assumed that its magnitude is one, making inverse(q) = q* where q* is the conjugate of q.
On separating q into real part s and vector part u and simplifying the quaternion operation (as beautifully shown here),
v' = 2 * dot(u, v) * u + (s*s - dot(u, u)) * v + 2 * s * cross(u, v)
Where dot returns the dot product of two vectors, and cross returns the cross product of two vectors.
Putting the above into (pseudo)code,
function rotate(v: vector3, q: quaternion4) -> vector3 {
u = vector3(q.x, q.y, q.z)
s = q.w
return 2 * dot(u, v) * u + (s*s - dot(u, u)) * v + 2 * s * cross(u, v)
}
Now that we know how to rotate a vector with a quaternion, we can use the world (global) rotation quaternion to find the corresponding world direction (or axis) for a local direction by conjugating the local direction by the rotation quaternion.
The local forward axis is always given by 0*i + 0*j + 1*k. Therefore, to find the world forward axis for an object, you must conjugate the vector (0, 0, 1) with the world rotation quaternion.
Using the function defined above, the forward axis becomes
forward = rotate(vector3(0, 0, 1), rotationQuaternion)
Now that you have the world forward axis, a force applied along it will simply be a scalar multiple of the world forward axis.

Javascript Canvas how to shoot in 360* from a rotating object

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.

Why isn't my homing missile algorithm working?

I've taken code that's heavily inspired by this answer but my projectile is not homing in the way I expect. The initial projectile direction is often perpendicular to the target. At which point, it does seem to home in on his direction, but if it "passes" him, it seems to get stuck in place like it's frozen at a point but then seems to follow the movements the target makes without moving at its intended speed. I've commented a line of code that I'm concerned about. He's using V3 and V4 in his algorithm which I suspect is a typo on his part but I'm not sure. If anyone can help me with what I'm doing wrong here, I'd be very grateful.
normalizedDirectionToTarget = root.vector.normalize(target.pos.x - attack.x, target.pos.y - attack.y) #V4
V3 = root.vector.normalize(attack.velocity.x, attack.velocity.y)
normalizedVelocity = root.vector.normalize(attack.velocity.x, attack.velocity.y)
angleInRadians = Math.acos(normalizedDirectionToTarget.x * V3.x + normalizedDirectionToTarget.y * V3.y)
maximumTurnRate = 50 #in degrees
maximumTurnRateRadians = maximumTurnRate * (Math.PI / 180)
signOfAngle = if angleInRadians >= 0 then 1 else (-1)
angleInRadians = signOfAngle * _.min([Math.abs(angleInRadians), maximumTurnRateRadians])
speed = 3
attack.velocity = root.vector.normalize(normalizedDirectionToTarget.x + Math.sin(angleInRadians), normalizedDirectionToTarget.y + Math.cos(angleInRadians)) #I'm very concerned this is the source of my bug
attack.velocity.x = attack.velocity.x * speed
attack.velocity.y = attack.velocity.y * speed
attack.x = attack.x + attack.velocity.x
attack.y = attack.y + attack.velocity.y
Edit: Code that Works
normalizedDirectionToTarget = root.vector.normalize(target.pos.x - attack.x, target.pos.y - attack.y) #V4
normalizedVelocity = root.vector.normalize(attack.velocity.x, attack.velocity.y)
angleInRadians = Math.acos(normalizedDirectionToTarget.x * normalizedVelocity.x + normalizedDirectionToTarget.y * normalizedVelocity.y)
maximumTurnRate = .3 #in degrees
maximumTurnRateRadians = maximumTurnRate * (Math.PI / 180)
crossProduct = normalizedDirectionToTarget.x * normalizedVelocity.y - normalizedDirectionToTarget.y * normalizedVelocity.x
signOfAngle = if crossProduct >= 0 then -1 else 1
angleInRadians = signOfAngle * _.min([angleInRadians, maximumTurnRateRadians])
speed = 1.5
xPrime = attack.velocity.x * Math.cos(angleInRadians) - attack.velocity.y * Math.sin(angleInRadians)
yPrime = attack.velocity.x * Math.sin(angleInRadians) + attack.velocity.y * Math.cos(angleInRadians)
attack.velocity = root.vector.normalize(xPrime, yPrime)
attack.velocity.x *= speed
attack.velocity.y *= speed
attack.x = attack.x + attack.velocity.x
attack.y = attack.y + attack.velocity.y
According to me, if you have a vector (x,y) and you want to rotate it by angle 'theta' about the origin, the new vector (x1,y1) becomes:
x1 = x*cos(theta) - y*sin(theta)
y1 = y*cos(theta) + x*sin(theta)
(the above can be derived using polar coordinates)
EDIT: I'm not sure if I understand correctly, but if you know the speed and the absolute value of the final angle (say phi), then why can't you simply do:
Vx = speed*cos( phi )
Vy = speed*sin( phi )
EDIT 2: also, while taking cos-inverse, there can be multiple possiblities for the angleinradians. You may have to check the quadrant in which both vectors lie. Your maximum turning rate is 50 degrees in either direction. Hence, the cosine for that angle shall always be positive. (cosine is negative only for 90 to 270 degrees.
EDIT 3: I think to get information about +ve turn direction or -ve turn direction, cross product is a better idea.
EDIT 4: Vx / Vy should work if you carry out the following:
initialAngleInRadians = Math.atan(normalizedVelocity.y / normalizedVelocity.x)
finalAngleInRadians = initialAngleInRadians + angleInRadians
Vx = speed*cos(finalAngleInRadians)
Vy = speed*sin(finalAngleInRadians)

Refactor word cloud algorithm

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;

Find column, row on 2D isometric grid from x,y screen space coords (Convert equation to function)

I'm trying to find the row, column in a 2d isometric grid of a screen space point (x, y)
Now I pretty much know what I need to do which is find the length of the vectors in red in the pictures above and then compare it to the length of the vector that represent the bounds of the grid (which is represented by the black vectors)
Now I asked for help over at mathematics stack exchange to get the equation for figuring out what the parallel vectors are of a point x,y compared to the black boundary vectors. Link here Length of Perpendicular/Parallel Vectors
but im having trouble converting this to a function
Ideally i need enough of a function to get the length of both red vectors from three sets of points, the x,y of the end of the 2 black vectors and the point at the end of the red vectors.
Any language is fine but ideally javascript
What you need is a base transformation:
Suppose the coordinates of the first black vector are (x1, x2) and the coordinates of the second vector are (y1, y2).
Therefore, finding the red vectors that get at a point (z1, z2) is equivalent to solving the following linear system:
x1*r1 + y1*r2 = z1
x2*r1 + y2*r2 = z2
or in matrix form:
A x = b
/x1 y1\ |r1| = |z1|
\x2 y2/ |r2| |z2|
x = inverse(A)*b
For example, lets have the black vector be (2, 1) and (2, -1). The corresponding matrix A will be
2 2
1 -1
and its inverse will be
1/4 1/2
1/4 -1/2
So a point (x, y) in the original coordinates will be able to be represened in the alternate base, bia the following formula:
(x, y) = (1/4 * x + 1/2 * y)*(2,1) + (1/4 * x -1/2 * y)*(2, -1)
What exactly is the point of doing it like this? Any isometric grid you display usually contains cells of equal size, so you can skip all the vector math and simply do something like:
var xStep = 50,
yStep = 30, // roughly matches your image
pointX = 2*xStep,
pointY = 0;
Basically the points on any isometric grid fall onto the intersections of a non-isometric grid. Isometric grid controller:
screenPositionToIsoXY : function(o, w, h){
var sX = ((((o.x - this.canvas.xPosition) - this.screenOffsetX) / this.unitWidth ) * 2) >> 0,
sY = ((((o.y - this.canvas.yPosition) - this.screenOffsetY) / this.unitHeight) * 2) >> 0,
isoX = ((sX + sY - this.cols) / 2) >> 0,
isoY = (((-1 + this.cols) - (sX - sY)) / 2) >> 0;
// isoX = ((sX + sY) / isoGrid.width) - 1
// isoY = ((-2 + isoGrid.width) - sX - sY) / 2
return $.extend(o, {
isoX : Math.constrain(isoX, 0, this.cols - (w||0)),
isoY : Math.constrain(isoY, 0, this.rows - (h||0))
});
},
// ...
isoToUnitGrid : function(isoX, isoY){
var offset = this.grid.offset(),
isoX = $.uD(isoX) ? this.isoX : isoX,
isoY = $.uD(isoY) ? this.isoY : isoY;
return {
x : (offset.x + (this.grid.unitWidth / 2) * (this.grid.rows - this.isoWidth + isoX - isoY)) >> 0,
y : (offset.y + (this.grid.unitHeight / 2) * (isoX + isoY)) >> 0
};
},
Okay so with the help of other answers (sorry guys neither quite provided the answer i was after)
I present my function for finding the grid position on an iso 2d grid using a world x,y coordinate where the world x,y is an offset screen space coord.
WorldPosToGridPos: function(iPosX, iPosY){
var d = (this.mcBoundaryVectors.upper.x * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.upper.y * this.mcBoundaryVectors.lower.x);
var a = ((iPosX * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.lower.x * iPosY)) / d;
var b = ((this.mcBoundaryVectors.upper.x * iPosY) - (iPosX * this.mcBoundaryVectors.upper.y)) / d;
var cParaUpperVec = new Vector2(a * this.mcBoundaryVectors.upper.x, a * this.mcBoundaryVectors.upper.y);
var cParaLowerVec = new Vector2(b * this.mcBoundaryVectors.lower.x, b * this.mcBoundaryVectors.lower.y);
var iGridWidth = 40;
var iGridHeight = 40;
var iGridX = Math.floor((cParaLowerVec.length() / this.mcBoundaryVectors.lower.length()) * iGridWidth);
var iGridY = Math.floor((cParaUpperVec.length() / this.mcBoundaryVectors.upper.length()) * iGridHeight);
return {gridX: iGridX, gridY: iGridY};
},
The first line is best done once in an init function or similar to save doing the same calculation over and over, I just included it for completeness.
The mcBoundaryVectors are two vectors defining the outer limits of the x and y axis of the isometric grid (The black vectors shown in the picture above).
Hope this helps anyone else in the future

Categories