I have written a collision detection and resolution system for the game I am currently working on. The collision resolution doesn't work quite as intended though. Occasionally I spawn one circle inside of the other, when this happens I'd like the inside circle to slowly move out of the other's diameter, but currently the code that I have working does it quite quickly and jarringly. It instantly teleports outside of the circle, rather than a slow transition.
I know that the code I am currently using tells the circle where it SHOULD be, so I just need to slowly move the circle to that position. But that is proving to be difficult.
I've tried a couple of solutions, one is included in the code below. I have also tried using linear interpolation to move the circle to its current position, to where the collision algorithm is telling it it should be. That didn't work correctly either.
//r is the radius of the circle
var dx = cell.x - cell2.x;
var dy = cell.y - cell2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < cell.r + cells2.r && cell.r > cells2.r){
var unitX = dx/distance;
var unitY = dy/distance;
cell.x = cells2.x + (cell.r + cells2.r + 1) * unitX;
cell.y = cells2.y + (cell.r + cells2.r + 1) * unitY;
//Ive tried below, but the results were not correct.
//cell.x += (cells2.x + (cell.r + cells2.r + 1) * unitX)*0.5;
//cell.y += (cells2.y + (cell.r + cells2.r + 1) * unitY)*0.5;
}
To move cell apart from cell2, you don't need to add cells2 coordinates
cell.x += (cell.r + cells2.r + 1) * unitX;
To move slowly, make speed smaller
//calculate once:
v = 0.01 * (cell.r + cells2.r - distance);
// at every step:
cell.x += v * unitX;
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 working on visual editor with objects and user interactions around like move, resize, rotate, etc...
I have resize and rotate functionality in place. Now I have implemented multi-select functionality when user select multiple objects and resize objects keeping the original proportion.
That functionality works very well, however not for rotated objects. I've created a simplified codepen example. Basically the question is - how to adjust resize() function to make sure it works well for rotated objects. To reproduce an issue just click on "Rotate" and then "Increase width & height" once or multiple times.
function resize(incrementX, incrementY, offsetX, offsetY) {
...
}
I'm not sure if this is a valid solution for your problem, but you can undo the rotation before resizing, and reset the rotation afterwards. Like this.
function resize(incrementX, incrementY, offsetX, offsetY) {
var old_r = objmultiple.r
rotate(-objmultiple.r)
var ratioX = (objmultiple.w + incrementX) / objmultiple.w;
var ratioY = (objmultiple.h + incrementY) / objmultiple.h;
objmultiple.x += offsetX;
objmultiple.y += offsetY;
objmultiple.w = objmultiple.w + incrementX;
objmultiple.h = objmultiple.h + incrementY;
[obj1, obj2].forEach(function(obj) {
obj.x = (obj.x - objmultiple.x + offsetX) * ratioX + objmultiple.x;
obj.y = (obj.y - objmultiple.y + offsetY) * ratioY + objmultiple.y;
obj.w *= ratioX;
obj.h *= ratioY;
});
rotate(old_r)
}
Codepen here
I am trying to make something where a bunch of circles (divs with border-radius) can be dynamically generated and laid out in their container without overlapping.
Here is my progress so far - https://jsbin.com/domogivuse/2/edit?html,css,js,output
var sizes = [200, 120, 500, 80, 145];
var max = sizes.reduce(function(a, b) {
return Math.max(a, b);
});
var min = sizes.reduce(function(a, b) {
return Math.min(a, b);
});
var percentages = sizes.map(function(x) {
return ((x - min) * 100) / (max - min);
});
percentages.sort(function(a, b) {
return b-a;
})
var container = document.getElementById('container');
var width = container.clientWidth;
var height = container.clientHeight;
var area = width * height;
var maxCircleArea = (area / sizes.length);
var pi = Math.PI;
var maxRadius = Math.sqrt(maxCircleArea / pi);
var minRadius = maxRadius * 0.50;
var range = maxRadius - minRadius;
var radii = percentages.map(function(x) {
return ((x / 100) * range) + minRadius;
});
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
var coords = [];
radii.forEach(function(e, i) {
var circle = document.createElement('div');
var randomTop = getRandomArbitrary(0, height);
var randomLeft = getRandomArbitrary(0, width);
var top = randomTop + (e * 2) < height ?
randomTop :
randomTop - (e * 2) >= 0 ?
randomTop - (e * 2) :
randomTop - e;
var left = randomLeft + (e * 2) < width ?
randomLeft :
randomLeft - (e * 2) >= 0 ?
randomLeft - (e * 2) :
randomLeft - e;
var x = left + e;
var y = top + e;
coords.push({x: x, y: y, radius: e});
circle.className = 'bubble';
circle.style.width = e * 2 + 'px';
circle.style.height = e * 2 + 'px';
circle.style.top = top + 'px';
circle.style.left = left + 'px';
circle.innerText = i
container.appendChild(circle);
});
I have got them being added to the parent container but as you can see they overlap and I don't really know how to solve this. I tried implementing a formula like (x1 - x2)^2 + (y1 - y2)^2 < (radius1 + radius2)^2 but I have no idea about this.
Any help appreciated.
What you're trying to do is called "Packing" and is actually a pretty hard problem. There are a couple potential approaches you can take here.
First, you can randomly distribute them (like you are currently doing), but including a "retry" test, in which if a circle overlaps another, you try a new location. Since it's possible to end up in an impossible situation, you would also want a retry limit at which point it gives up, goes back to the beginning, and tries randomly placing them again. This method is relatively easy, but has the down-side that you can't pack them very densely, because the chances of overlap become very very high. If maybe 1/3 of the total area is covered by circle, this could work.
Second, you can adjust the position of previously placed circles as you add more. This is more equivalent to how this would be accomplished physically -- as you add more you start having to shove the nearby ones out of the way in order to fit the new one. This will require not just finding the things that your current circle hits, but also the ones that would be hit if that one was to move. I would suggest something akin to a "springy" algorithm, where you randomly place all the circles (without thinking about if they fit), and then have a loop where you calculate overlap, and then exert a force on each circle based on that overlap (They push each other apart). This will push the circles away from each other until they stop overlapping. It will also support one circle pushing a second one into a third, and so on. This will be more complex to write, but will support much more dense configurations (since they can end up touching in the end). You still probably need a "this is impossible" check though, to keep it from getting stuck and looping forever.
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;
I'm working on the Collision system for my game; which is a top down shooter, the character is always static - and everything else (Map/Level), moves around him.
The character also rotates so it's always facing the mouse position.
With that in mind, I can't seem to get my head around my collision system, which needs to take into account of the character rotation, right?
I basically just want to check if the given object is at all 'touching' my character/sprite. I'm not so sure if the maths I'm using is correct.
This is my collision detection (called every update):
function detectCollisions(){
//Detect for game props
if(collides(Map, TestProp)){
console.debug("Touching...");
}
}
function collides(a, b){
//ctxMap.setTransform(1, 0, 0, 1, -Map.x + gameWidth/2, -Map.y + gameHeight/2);
//var transX = -Map.x + gameWidth/2;
//var transY = -Map.y + gameHeight/2;
//need to take player rotation into account too!
//console.debug(a.x + " " + b.x + " " + b.width + " " + Player.width); //A Width
/*return a.x < b.x + b.width && a.x + Player.width > b.x &&
a.y < b.y + b.height && a.y + Player.height > b.y;*/
var xOffset = Math.cos(Player.characterRotation); //+ Player.width;
var yOffset = Math.sin(Player.characterRotation); //+ Player.height;
return Map.x + xOffset > b.x && Map.x + xOffset < b.x + b.width &&
Map.y + yOffset > b.y && Map.y + yOffset < b.y + b.height;
}
Also, not sure if this is relevant, but this is the transform used to move my Map Canvas:
ctxMap.setTransform(1, 0, 0, 1, -Map.x + gameWidth/2, -Map.y + gameHeight/2);
Would much appreciate it if someone helped me out here :) Thanks!
Personally, I wouldn't worry so much about the character colliding. The reason I say that is simple.
Let's saw you're walking real close to a wall. Then you turn to follow the mouse, and the sprite then overlaps the wall. What do you do now? Either you stop the turning, which would screw up movements, or you let the sprite overlap and the player gets completely stuck until they turn free again.
My preference would be to use a collision circle. If the player is closer than R pixels from the wall, count it as a collision and stop the player from moving. This way, even if the player turns, the sprite will never cause the player to get stuck and he will always be able to move away from the wall.
I entered ludum dare this time around and did a tutorial to explain my base code. The tutorials can be found here: http://www.philjeffes.co.uk/wordpress/?p=63
This demonstrates an example of circle based collision detection - please feel free to use any of the code. The following code is an adaptation of that code for general usage:
function CollisionCheck(obj1,obj2){
// If the two objects are less the sum of their collision radii apart then they have collided
// Note that one obj is obj (with a loc and a size) and the other is this.
// Returns true if the objects are touching
var dist = obj2.size + obj1.size; // The distance they must be apart to be not touching
if(obj1.x-obj2.x>dist || obj1.x-obj2.x<-dist)
return false; // Too far apart in x plane
if(obj1.y-obj2.y>dist || obj1.y-obj2.y<-dist)
return false; // Too far apart in y plane
var xDist = obj1.x-obj2.x;
var yDist = obj1.y-obj2.y;
var hyp = Math.sqrt((xDist*xDist)+(yDist*yDist));
if(hyp<dist)
return true;
return false;
}
EDIT
Removed the Math.abs calls as pointed out by vals in the comments.