So, I am trying to create a N-Body Gravity simulation in JavaScript:
http://jsfiddle.net/4M94x/
var Circle = function(c, r, cor, cof) { // Fix CoR & CoF // Had to add code for JSFiddle link :P
this.c = c
this.r = r
this.m = r * r * Math.PI
this.v = new Vector()
this.cor = cor
this.cof = cof
}
The problem's that when you spawn (click) and put 2 balls (accidentally renamed "particles") next to each other they start to generate velocity and faster and faster push eachother.. How do I fix this, btw is my gravity implementation correct?
This is easy to explain: You are implementing Euler forward as solver for the ODE, and in mechanical systems the systematic error of Euler forward increases the energy. Euler backward decreases the energy, so a combination of alternating the explicit and implicit Euler methods would leave the energy a little more constant.
But then you can implement with the same or even less effort the second order symplectic methods which preserve energy and other physical invariants, either the (implicit) midpoint method or the Verlet-(Stoermer-Cromer-...-Newton-)method.
Or even higher order Runge-Kutta, which will also preserve the energy to a higher order, despite not being symplectic.
See Hairer on the Stoermer-Verlet-...-Newton method, postprint or preprint and the "Moving stars around" tutorial text using C++ or Ruby.
A note to the physics: All in all the implementation is very minimal and well readable. But the gravitational force is
g*m1*m2*(p2-p1)/norm(p2-p1)^3
as the negative gradient of
g*m1*m2/norm(p2-p1)
you are only using the square of the norm, where the force would be the negative gradient of the gravitational potential energy
g*m1*m2*ln(norm(p2-p1))
which is right for flatland physics, but not for a 2D section of 3D space.
Working code
with velocity Verlet and energy preservation:
Add a new field a=Vector() to the circle object and replace the kitchen sink which is the update() function with the following collection of dedicated functions
function compute_forces() {
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
p.a.set(0);
for (var j = 0; j < i; j++) {
var p2 = particles[j];
var d = p.c.sub(p2.c);
var norm = Math.sqrt(100.0 + d.lengthSq());
var mag = gravity / (norm * norm * norm);
p.a.set(p.a.sub(d.mul(mag * p2.m)));
p2.a.set(p2.a.add(d.mul(mag * p.m)));
}
}
}
function do_collisions() {
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
for (var j = 0; j < i; j++) {
var p2 = particles[j];
if (checkCCCol(p, p2)) {
resCCCol(p, p2);
}
}
}
}
function do_physics(dt) {
// do velocity Verlet
// with consistent state at interval half points
// x += 0.5*v*dt
for (var i1 = 0; i1 < particles.length; i1++) {
var p1 = particles[i1];
p1.c.set(p1.c.add(p1.v.mul(0.5 * dt)));
}
// a = A(x)
compute_forces();
// v += a*dt
for (var i2 = 0; i2 < particles.length; i2++) {
var p2 = particles[i2];
p2.v.set(p2.v.add(p2.a.mul(dt)));
}
// x += 0.5*v*dt
for (var i3 = 0; i3 < particles.length; i3++) {
var p3 = particles[i3];
p3.c.set(p3.c.add(p3.v.mul(0.5 * dt)));
}
do_collisions();
}
function update() {
for (var k = 0; k < 4; k++) {
do_physics(1.0 / 4);
}
render();
RAF(update);
}
See http://jsfiddle.net/4XVPH/
Altered example with particles coloured based on their mass(hopefully better displaying their interaction), one bug fixed, and some additional comments: http://jsfiddle.net/24mg6ctg/12/
Related
I'm trying to make a visualizer and I have to draw 4 vertexes while using math to make each vertex so they can react with the music. Rather than just typing all 4 of them out and plotting them I would like to use a nested for loop to do this. I was testing using a regular ellipse with the translate function in p5.js and the translate seems to be translating itself and not just changing the values to how I would want it.
for (var i = 1; i <= 2; i++)
{
for (var j = 1; j <= 2; j++)
{
translate(200 * i,200 * j);
ellipse(0,0,100)
}
}
translate does not set a translation, but concatenates the current transformation matrix with the new translation. Therefore you need to push the transformation matrix before appending the new translation and pop the matrix after drawing the geometry:
for (var i = 1; i <= 2; i++) {
for (var j = 1; j <= 2; j++) {
push()
translate(200 * i, 200 * j)
ellipse(0, 0, 100)
pop()
}
}
I'm in dire need of help with a graduation project that I'm working on. What I'm trying to achieve here in the code below is -to put it very simply- to initialize a 3d array of box() objects (this works) and then introduce another box() object (the end goal is to have an array of these as well) to move about within the same 3D grid. The moving unit picks a random location for initialization and a random target for destination (both within the 3D grid).
But the problem is that I need the moving unit not to overlap with the static base units, also move orthogonally, and always move one unit size each time. If I run the code below the moving unit reaches the target but without any of the restrictions that I mentioned above. It just takes the shortest vector and goes there regardless.
PS: Since p5js doesn't support wireframe in webgl mode I tried to show them with some transparency for better visual legibility. Although, the loadImage() functions are still there and could be replaced with any image of your liking to better differentiate units from one another.
I'd sincerely appreciate help with this since I'm also very short on time. Thanks in advance.
Here's the verifiable code:
var matrixSize = 5;
var locations = new Array(matrixSize);
var locBool = new Array(matrixSize);
//unit stuff
var unitSize = 40;
var units_a = []
var units_b;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
//3D array of location vectors & booleans
for(var i = 0; i < locations.length; i++){
locations[i] = new Array(matrixSize);
locBool[i] = new Array(matrixSize);
for(var j = 0; j < locations[i].length; j++){
locations[i][j] = new Array(matrixSize);
locBool[i][j] = new Array(matrixSize);
for(var k = 0; k < locations[i][j].length; k++){
locations[i][j][k] =
createVector(i*unitSize, j*unitSize, k*unitSize);
locBool[i][j][k] = false;
}
}
}
//base units
var threshold = 2; //decides on the percentage to be initialized
for (var i = 0; i < matrixSize; i++) {
for(var j = 0; j < matrixSize; j++){
for(var k = 0; k < matrixSize; k++){
stateRndm = random(10);
if(stateRndm <= threshold){
state = 1;
locBool[i][j][k] = true;
}else{
state = 0
}
units_a.push(new UnitOne(
i*unitSize,j*unitSize,k*unitSize, state));
}
}
}
units_b = new UnitTwo();
}
function draw() {
background(20);
ambientLight(235);
orbitControl();
rotateX(10);
rotateY(-10);
rotateZ(0);
//center the window and display the units
push();
translate(-unitSize*matrixSize/2, -unitSize*matrixSize/2, 0);
for(var i = 0; i < units_a.length; i++){
units_a[i].display();
}
units_b.display();
units_b.update();
units_b.move();
pop();
}
function UnitOne (x, y, z, state){
this.x = x;
this.y = y;
this.z = z;
this.state = state;
//this.img = loadImage("assets/tex_1.jpg");
//basic movement parameters
this.acceleration = createVector();
this.velocity = createVector();
this.location = createVector(this.x, this.y, this.z);
this.update = function(){
this.velocity.add(this.acceleration);
this.location.add(this.velocity);
this.acceleration.mult(0);
}
this.display = function(){
if(this.state == 1){
push();
scale(1);
//texture(this.img);
ambientMaterial(50, 200, 100, 20);
translate(this.x, this.y, this.z);
box(unitSize);
pop();
}
}
}
function UnitTwo() {
//assign random initial location
this.selector;
for(var i = 0; i < locations.length; i++){
for(var j = 0; j < locations[i].length; j++){
for(var k = 0; k < locations[i][j].length; k++){
this.selector = createVector(
floor(random(i))*unitSize,
floor(random(j))*unitSize,
floor(random(k))*unitSize);
}
}
}
print(this.selector);
//assign random target
this.targetSelector;
for(var i = 0; i < locations.length; i++){
for(var j = 0; j < locations[i].length; j++){
for(var k = 0; k < locations[i][j].length; k++){
this.targetSelector = createVector(
floor(random(i))*unitSize,
floor(random(j))*unitSize,
floor(random(k))*unitSize);
}
}
}
print(this.targetSelector);
//basic movement parameters
this.location = createVector(
this.selector.x,
this.selector.y,
this.selector.z);
this.acceleration = createVector();
this.velocity = createVector();
this.maxSpeed = 1;
this.maxForce = 2;
//this.img = loadImage("assets/tex_2.jpg");
this.display = function(){
push();
//texture(this.img);
ambientMaterial(200, 100, 40);
translate(this.location.x,
this.location.y, this.location.z);
scale(1);
box(unitSize);
pop();
}
this.update = function(){
this.velocity.add(this.acceleration);
this.location.add(this.velocity);
this.acceleration.mult(0);
}
}
UnitTwo.prototype.move = function(){
var target = createVector(this.targetSelector.x,
this.targetSelector.y,
this.targetSelector.z);
var desired = p5.Vector.sub(target, this.location);
var d = desired.mag();
//check the distance to slow down
if (d < unitSize/2)
desired.setMag(this.maxSpeed/2);
else
desired.setMag(this.maxSpeed);
var steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxForce);
this.acceleration.add(steer);
}
You've outlined both things you need to do. Which part of them are you stuck on?
Step 1: Make your unit move one cube at a time. You could do this by simply adding or subtracting 1 to its x, y, or z coordinate. Get this working first. Don't worry about avoiding collisions with the other cubes yet.
Step 2: When you get that working, add code that detects when the cube it's about to move to is occupied, and go in a different direction. You might have to implement a basic path finding algorithm, and Google is your friend for that.
You might also want to consider the possibility that your random generation has blocked all of the paths to the goal. What do you want to do in that case? And one more note: you don't need a triple-nested for loop just to generate a random value.
If you get stuck on one of these steps, please narrow your problem down to a MCVE showing just that step before you post again. Good luck.
This is a bit complicated to describe, so please bear with me.
I'm using the HTML5 canvas to extend a diagramming tool (Diagramo). It implements multiple types of line, straight, jagged (right angle) and curved (cubic or quadratic). These lines can be solid, dotted or dashed.
The new feature I am implementing is a "squiggly" line, where instead of following a constant path, the line zigzags back and forth across the desired target path in smooth arcs.
Below is an example of this that is correct. This works in most cases, however, in certain edge cases it does not.
The implementation is to take the curve, use the quadratic or cubic functions to estimate equidistance points along the line, and draw squiggles along these straight lines by placing control points on either side of the straight line (alternating) and drawing multiple cubic curves.
The issues occur when the line is relatively short, and doubles back on itself close to the origin. An example is below, this happens on longer lines too - the critical point is that there is a very small sharp curve immediately after the origin. In this situation the algorithm picks the first point after the sharp curve, in some cases immediately next to the origin, and considers that the first segment.
Each squiggle has a minimum/maximum pixel length of 8px/14px (which I can change, but much below that and it becomes too sharp, and above becomes too wavy) the code tries to find the right sized squiggle for the line segment to fit with the minimum empty space, which is then filled by a straight line.
I'm hoping there is a solution to this that can account for sharply curved lines, if I know all points along a line can I choose control points that alternate either side of the line, perpendicular too it?
Would one option be to consider a point i and the points i-1 and i+1 and use that to determine the orientation of the line, and thus pick control points?
Code follows below
//fragment is either Cubic or Quadratic curve.
paint(fragment){
var length = fragment.getLength();
var points = Util.equidistancePoints(fragment, length < 100 ? (length < 50 ? 3: 5): 11);
points.splice(0, 1); //remove origin as that is the initial point of the delegate.
//points.splice(0, 1);
delegate.paint(context, points);
}
/**
*
* #param {QuadCurve} or {CubicCurbe} curve
* #param {Number} m the number of points
* #return [Point] a set of equidistance points along the polyline of points
* #author Zack
* #href http://math.stackexchange.com/questions/321293/find-coordinates-of-equidistant-points-in-bezier-curve
*/
equidistancePoints: function(curve, m){
var points = curve.getPoints(0.001);
// Get fractional arclengths along polyline
var n = points.length;
var s = 1.0/(n-1);
var dd = [];
var cc = [];
var QQ = [];
function findIndex(dd, d){
var i = 0;
for (var j = 0 ; j < dd.length ; j++){
if (d > dd[j]) {
i = j;
}
else{
return i;
}
}
return i;
};
dd.push(0);
cc.push(0);
for (var i = 0; i < n; i++){
if(i >0) {
cc.push(Util.distance(points[i], points[i - 1]));
}
}
for (var i = 1 ; i < n ; i++) {
dd.push(dd[i-1] + cc[i]);
}
for (var i = 1 ; i < n ; i++) {
dd[i] = dd[i]/dd[n-1];
}
var step = 1.0/(m-1);
for (var r = 0 ; r < m ; r++){
var d = parseFloat(r)*step;
var i = findIndex(dd, d);
var u = (d - dd[i]) / (dd[i+1] - dd[i]);
var t = (i + u)*s;
QQ[r] = curve.getPoint(t);
}
return QQ;
}
SquigglyLineDelegate.prototype = {
constructor: SquigglyLineDelegate,
paint: function(context, points){
var squiggles = 0;
var STEP = 0.1;
var useStart = false;
var bestSquiggles = -1;
var bestA = 0;
var distance = Util.distance(points[0], this.start);
for(var a = SquigglyLineDelegate.MIN_SQUIGGLE_LENGTH; a < SquigglyLineDelegate.MAX_SQUIGGLE_LENGTH; a += STEP){
squiggles = distance / a;
var diff = Math.abs(Math.floor(squiggles) - squiggles);
if(diff < bestSquiggles || bestSquiggles == -1){
bestA = a;
bestSquiggles = diff;
}
}
squiggles = distance / bestA;
for(var i = 0; i < points.length; i++){
context.beginPath();
var point = points[i];
for(var s = 0; s < squiggles-1; s++){
var start = Util.point_on_segment(this.start, point, s * bestA);
var end = Util.point_on_segment(this.start, point, (s + 1) * bestA);
var mid = Util.point_on_segment(this.start, point, (s + 0.5) * bestA);
end.style.lineWidth = 1;
var line1 = new Line(Util.point_on_segment(mid, end, -this.squiggleWidth), Util.point_on_segment(mid, end, this.squiggleWidth));
var mid1 = Util.getMiddle(line1.startPoint, line1.endPoint);
line1.transform(Matrix.translationMatrix(-mid1.x, -mid1.y));
line1.transform(Matrix.rotationMatrix(radians = 90 * (Math.PI/180)));
line1.transform(Matrix.translationMatrix(mid1.x, mid1.y));
var control1 = useStart ? line1.startPoint : line1.endPoint;
var curve = new QuadCurve(start, control1, end);
curve.style = null;
curve.paint(context);
useStart = !useStart;
}
this.start = point;
context.lineTo(point.x, point.y);
context.stroke();
}
}
}
I am currently writing my first Three.js / WebGL application and it runs very well on my PC (Chrome). Sadly, on many other PCs, the framerate often drops bellow 30 frames per second.
Since the application actually isn't to complex, I wanted to ask for some tips related to the application to improve the performance.
A version of the app can be found here:
www.wrodewald.de/StackOverflowExample/
The application contains a dynamic (morphing) plane using 64² vertices. A matrix is used to store a static heightmap and a wavemap. The wavemap is updated every frame to recalculate itself, some filters are used to "even out" every vertex compared to their neightbors.
So every frame the plane has to be updated (colors and vertex position) which could be a reason for the performance problem
The second object (rhombus) shouldn't be a problem, static, moving around a bit but nothing special.
There are three lights (ambient, directional, spherical), no shadows, a tilt shift shader and a vignette shader.
Here are functions which are called per frame:
var render = function() {
requestAnimationFrame( render );
var time = clock.getDelta();
world.updateWorld(time);
diamond.rotate(time);
diamond.update(time);
control.updateCamera(camera, time);
composer.render();
stats.update();
}
this is what world.updateWorld(time) does
//in world.updateWorld(time) where
// accmap: stores acceleration and wavemap stores position
// this.mapSize stores the size of the plane in vertices (64)
// UPDATE ACCELERATION MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
for(var iY = 1; iY < (this.mapSize-1); iY++) {
accmap[iX][iY] -= dT * (wavemap[iX][iY]) * Math.abs(wavemap[iX][iY]);
}
}
// SMOOTH ACCELERATION MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
for(var iY = 1; iY < (this.mapSize-1); iY++) {
tempmap[iX][iY] = accmap[iX-1][iY-1] * 0.0625
+ accmap[iX-1][iY ] * 0.125
+ accmap[iX-1][iY+1] * 0.0625
+ accmap[iX ][iY-1] * 0.125
+ accmap[iX ][iY ] * 0.25
+ accmap[iX ][iY+1] * 0.125
+ accmap[iX+1][iY-1] * 0.0625
+ accmap[iX+1][iY ] * 0.125
+ accmap[iX+1][iY+1] * 0.0625;
accmap[iX][iY] = tempmap[iX][iY];
}
}
// UPDATE WAVE MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
for(var iY = 1; iY < (this.mapSize-1); iY++) {
wavemap[iX][iY] += dT * accmap[iX][iY];
}
}
for(var i = 0; i < this.mapSize; i++) {
for(var k = 0; k < this.mapSize; k++) {
geometry.vertices[ i * this.mapSize + k ].y = wavemap[i][k] + heightmap[i][k];
}
}
for(var i = 0; i < geometry.faces.length; i++) {
var vertexA = geometry.vertices[geometry.faces[i].a];
var vertexB = geometry.vertices[geometry.faces[i].b];
var vertexC = geometry.vertices[geometry.faces[i].c];
var val = (vertexA.y + vertexB.y + vertexC.y) / 3.0;
val = (val / 200.) + 0.5;
geometry.faces[i].color.r = val;
geometry.faces[i].color.g = val;
geometry.faces[i].color.b = val;
}
geometry.colorsNeedUpdate = true;
geometry.verticesNeedUpdate = true;
These are the "diamond"-functions
this.rotate = function(dT) {
counter += 0.5 * dT;
counter % 1;
var x = 0.0025 * (Math.sin((counter) * 2 * Math.PI));
var y = 0.0025 * (Math.cos((counter) * 2 * Math.PI));
this.mesh.rotateOnAxis(new THREE.Vector3(1,0,0), x);
this.mesh.rotateOnAxis(new THREE.Vector3(0,0,1), y);
}
this.update = function(dT) {
for(var i = 0; i < geometry.faces.length; i++) {
geometry.faces[i].color.lerp(color, dT*(0.9));
}
geometry.colorsNeedUpdate = true;
}
Do you spot any reason for the framerate to be so inconsistent?
EDIT:
I have found 2 major things you have to improve:
Planes updates with GPU
speedup: high
Lets pick your code from plane.js
timer += dT;
if(timer > 0.1) {
var x = 2 + Math.floor(Math.random() * (this.mapSize - 4));
var y = 2 + Math.floor(Math.random() * (this.mapSize - 4));
//accmap[x][y] += 30000 * Math.random () - 15000
}
// UPDATE ACCELERATION MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
for(var iY = 1; iY < (this.mapSize-1); iY++) {
accmap[iX][iY] -= dT * (wavemap[iX][iY]) * Math.abs(wavemap[iX][iY]);
}
}
So you have 4096 vertices you would like to update every 17 ms with CPU. Notice you didnt use any GPU advantage. How it should be done:
First you create buffers, for vertex position, for normals, for texture cordinates, indices ... . This together is called mesh.
Then you create a model. Model is composed from one or more meshes. And modelViewMatrix. This is super important part, matrix 4x4 is algebraic representation of position, rotation and scale of this model.
With each render you do exactly this in vertex shader:
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
it is from your cg/shaders/VerticalTiltShiftShader.js
If you want to rotate your plane, you don't multiply each vertex, but you only multiply your model matrix once (js with THREE.js function):
projectionMatrix.makeRotationY(dT);
Then each vertex is multiplied in vertex shader with this matrix, which is like much more faster.
Javascript style
speedup: none - medium, but it will allow you to code faster
Lets pick your plane.js as example.
// this is your interface
function PlaneWorld () {
this.init = function() {};
this.updateVertices = function() {};
this.updateWorld = function(dT) {};
// ... and more
}
// and somewhere else:
var world = new PlaneWorld();
In case you have only one plane in your project, you can consider this as singleton and implementation is ~ok. But if you would like to create 2 or more planes, all functions are recreated again for every instance (new PlaneWorld()). Correct way how to do this is:
function PlaneWorld () {
...
}
PlaneWorld.prototype.init = function() {};
PlaneWorld.prototype.updateVertices = function() {};
PlaneWorld.prototype.updateWorld = function(dT) {};
// ... and more
var world = new PlaneWorld();
// calling methods works same
world.updateVertices();
or more complicated version with anonymous function:
var PlaneWorld = (function() {
// something like private static variables here
var PlaneWorld = function () {
...
}
PlaneWorld.prototype = {
init: function() {},
updateVertices: function() {},
updateWorld: function(dT) {}
// ... and more
}
return PlaneWorld();
})();
var world = new PlaneWorld();
// calling methods works same
world.updateVertices();
Then new instance cost is lowered. Now the thing which might be connected, every instance should share same mesh, but has its own modelViewMatrix.
I want to check if circles are colliding with each other.
I know I can do this by getting a distance between the two centers of the circles and subtracting the radius of each circle from that distance and seeing if 'distance' is > 1.
How can I do this efficiently though with say, 1000 circles? Maybe I can somehow get the nearest 20 circles or something like that and check these? I don't know how I would begin to go about that efficiently though either..
Any ideas?
Here is an example:
http://experiments.lionel.me/blocs/
Before you start calculating exact differences in distances, you can at least compare the x/y positions of the centers v.s. the radii. That information is implicitly available in the circle and requires just some simple comparisons and addition/subtraction.
That'll let you compare the simple distances in x/y between all the circle pairs, and throw away any that are obviously not collision candidates, e.g.
abs(x2 - x1) > (r2 + r1)
abs(y2 - y1) > (r2 + r1)
... if the distance in X or Y between the circle centers is greater than the sum of the radii, then they cannot be colliding.
Once you've whittled down the possible colliders, THEN you do the formal exact cartesian distance, which is where the 'heavy' multiplication/division stuff comes in.
Consider storing the coordinates of the circles' centers in a quad tree, then you would only need to check whether the circle intersects with other circles in that quadrant or adjacent quadrants.
The one caveat is that you need to sure the quad tree's leaf nodes have a minimal diameter of the radius of your largest circle, otherwise you will have to check more than just adjacent nodes for intersection.
http://en.wikipedia.org/wiki/Quadtree
If your circles are well scattered, then a simple optimization you can do is to store your circles sorted on the x or y axis, then you only need to check with circles who's x or y coordinate is within the radius of the circle.
The efficiency is going to be concerned with the speed of the algorithms you are using, for instance the speed of the square root algorithm that you calculate the distance with, and your data structures will determine the efficiency of memory, in addition to algorithms. Another way to speed up the calculations would be to reduce the precision of the distance calculations.
The best method to detect if the circles are colliding is, as you said, to store the circles' center coordinates and radius in variables and compute whether or not the distance between the centers is equivalent to 0 when the radii are subtracted.
I highly recommend Keith Peter's AdvancED ActionScript 3.0 Animation book, where you can find the concrete implementation of Quadtree algorithm in Actionscript.
Here are the basic steps:
First create a two dimensional grid and scatter all the balls randomly across the field.
private function createGrids():void {
_grids = new Array();
for (var i:int = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j:int = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Assign balls to grid cells
private function assignBallsToGrid():void {
for (var i:int = 0; i< numBalls; i++) {
var ball:Ball = Ball(_balls[i]);
var xpos:int = Math.floor(ball.x / GRID_SIZE);
var ypos:int = Math.floor(ball.y / GRID_SIZE);
_grids[xpos][ypos].push(ball);
}
}
Check if two balls are colliding in a single cell, then check the balls in adjacent cells. As Charles Ma mentioned the only consideration here the grid cells dimension must be greater or equal to the largest ball diameter.
private function checkOneCell(x1:Number, y1:Number):void {
var _cell:Array = _grids[x1][y1] as Array;
for (var i:int = 0; i< _cell.length-1; i++) {
var ballA:Ball = _cell[i] as Ball;
for (var j:int = i+1; j< _cell.length; j++) {
var ballB:Ball = _cell[j] as Ball;
checkCollision(ballA, ballB);
}
}
}
private function checkTwoCell(x1:Number, y1:Number, x2:Number, y2:Number):void {
if (x2 < 0) { return }
if (x2 >= _grids.length) { return }
if (y2 >= _grids[x2].length) { return }
var _cell0:Array = _grids[x1][y1] as Array;
var _cell1:Array = _grids[x2][y2] as Array;
for (var i:int = 0; i< _cell0.length; i++) {
var ballA:Ball = _cell0[i] as Ball;
for (var j:int = 0; j< _cell1.length; j++) {
var ballB:Ball = _cell1[j] as Ball;
checkCollision(ballA, ballB);
}
}
}
private function checkCollision(ballA:Ball, ballB:Ball):void {
var dx:Number = ballB.x - ballA.x;
var dy:Number = ballB.y - ballA.y;
var dist:Number = Math.sqrt(dx*dx + dy*dy);
if (dist < ballB.radius + ballA.radius) {
// do something
}
}
Here is how it looks like the main method:
private function checkBallsCollision():void {
for (var i:int = 0; i< _grids.length; i++) {
for (var j:int = 0; j< _grids[i].length; j++) {
checkOneCell(i, j);
checkTwoCell(i, j, i+1, j);
checkTwoCell(i, j, i, j+1);
checkTwoCell(i, j, i-1, j);
checkTwoCell(i, j, i+1, j+1);
}
}
}
NOTE:
The code is written in Actionscript but can be implemented quite easily in Javascript.