Improve discontinuity detection algorithm - javascript
I'm trying to create an algorithm that detects discontinuities (like vertical asymptotes) within functions between an interval for the purpose of plotting graphs without these discontinuous connecting lines. Also, I only want to evaluate within the interval so bracketing methods like bisection seems good for that.
EDIT
https://en.wikipedia.org/wiki/Classification_of_discontinuities
I realize now there are a few different kinds of discontinuities. I'm mostly interested in jump discontinuities for graphical purposes.
I'm using a bisection method as I've noticed that discontinuities occur where the slope tends to infinity or becomes vertical, so why not narrow in on those sections where the slope keeps increasing and getting steeper and steeper. The point where the slope is a vertical line, that's where the discontinuity exists.
Approach
Currently, my approach is as follows. If you subdivide the interval using a midpoint into 2 sections and compare which section has the steepest slope, then that section with the steepest slope becomes the new subinterval for the next evaluation.
Termination
This repeats until it converges by either slope becoming undefined (reaching infinity) or the left side or the right side of the interval equaling the middle (I think this is because the floating-point decimal runs out of precision and cannot divide any further)
(1.5707963267948966 + 1.5707963267948968) * .5 = 1.5707963267948966
Example
function - floor(x)
(blue = start leftX and rightX, purple = midpoint, green = 2nd iteration midpoints points, red = slope lines per iteration)
As you can see from the image, each bisection narrows into the discontinuity and the slope keeps getting steeper until it becomes a vertical line at the discontinuity point at x=1.
To my surprise this approach seems to work for step functions like floor(x) and tan(x), but it's not that great for 1/x as it takes too many iterations (I'm thinking of creating a hybrid method where I use either illinois or ridders method on the inverse of 1/x as it those tend to find the root in just one iteration).
Javascript Code
/* Math function to test on */
function fn(x) {
//return (Math.pow(Math.tan(x), 3));
return 1/x;
//return Math.floor(x);
//return x*((x-1-0.001)/(x-1));
}
function slope(x1, y1, x2, y2) {
return (y2 - y1) / (x2 - x1);
}
function findDiscontinuity(leftX, rightX, fn) {
while (true) {
let leftY = fn(leftX);
let rightY = fn(rightX);
let middleX = (leftX + rightX) / 2;
let middleY = fn(middleX);
let leftSlope = Math.abs(slope(leftX, leftY, middleX, middleY));
let rightSlope = Math.abs(slope(middleX, middleY, rightX, rightY));
if (!isFinite(leftSlope) || !isFinite(rightSlope)) return middleX;
if (middleX === leftX || middleX === rightX) return middleX;
if (leftSlope > rightSlope) {
rightX = middleX;
rightY = middleY;
} else {
leftX = middleX;
leftY = middleY;
}
}
}
Problem 1 - Improving detection
For the function x*((x-1-0.001)/(x-1)), the current algorithm has a hard time detecting the discontinuity at x=1 unless I make the interval really small. As an alternative, I could also add most subdivisions but I think the real problem is using slopes as they trick the algorithm into choosing the incorrect subinterval (as demonstrated in the image below), so this approach is not robust enough. Maybe there are some statistical methods that can help determine a more probable interval to select. Maybe something like least squares for measuring the differences and maybe applying weights or biases!
But I don't want the calculations to get too heavy and 5 points of evaluation are the max I would go with per iteration.
EDIT
After looking at problem 1 again, where it selects the wrong (left-hand side) subinterval. I noticed that the only difference between the subintervals was the green midpoint distance from their slope line. So taking inspiration from linear regression, I get the squared distance from the slope line to the midpoints [a, fa] and [b, fb] corresponding to their (left/right) subintervals. And which subinterval has the greatest change/deviation is the one chosen for further subdivision, that is, the greater of the two residuals.
This further improvement resolves problem 1. Although, it now takes around 593 iterations to find the discontinuity for 1/x. So I've created a hybrid function that uses ridders method to find the roots quicker for some functions and then fallback to this new approach. I have given up on slopes as they don't provide enough accurate information.
Problem 2 - Jump Threshold
I'm not sure how to incorporate a jump threshold and what to use for that calculation, don't think slopes would help.
Also, if the line thickness for the graph is 2px and 2 lines of a step function were on top of each other then you wouldn't be able to see the gap of 2px between those lines. So the minimum jump gap would be calculated as such
jumpThreshold = height / (ymax-ymin) = cartesian distance per pixel
minJumpGap = jumpThreshold * 2
But I don't know where to go from here! And once again, maybe there are statistical methods that can help to determine the change in function so that the algorithm can terminate quickly if there's no indication of a discontinuity.
Overall, any help or advice in improving what I got already would be much appreciated!
EDIT
As the above images explains, the more divergent the midpoints are the greater the need for more subdivisions for further inspection for that subinterval. While, if the points mostly follow a straight line trend where the midpoints barely deviate then should exit early. So now it makes sense to use the jumpThreshold in this context.
Maybe there's further analysis that could be done like measuring the curvature of the points in the interval to see whether to terminate early and further optimize this method. Zig zag points or sudden dips would be the most promising. And maybe after a certain number of intervals, keep widening the jumpThreshold as for a discontinuity you expect the residual distance to rapidly increase towards infinity!
Updated code
let ymax = 5, ymin = -5; /* just for example */
let height = 500; /* 500px screen height */
let jumpThreshold = Math.pow(.5 * (ymax - ymin) / height, 2); /* fraction(half) of a pixel! */
/* Math function to test on */
function fn(x) {
//return (Math.pow(Math.tan(x), 3));
return 1 / x;
//return Math.floor(x);
//return x * ((x - 1 - 0.001) / (x - 1));
//return x*x;
}
function findDiscontinuity(leftX, rightX, jumpThreshold, fn) {
/* try 5 interations of ridders method */
/* usually this approach can find the exact reciprocal root of a discountinuity
* in 1 iteration for functions like 1/x compared to the bisection method below */
let iterations = 5;
let root = inverseRidderMethod(leftX, rightX, iterations, fn);
let limit = fn(root);
if (Math.abs(limit) > 1e+16) {
if (root >= leftX && root <= rightX) return root;
return NaN;
}
root = discontinuityBisection(leftX, rightX, jumpThreshold, fn);
return root;
}
function discontinuityBisection(leftX, rightX, jumpThreshold, fn) {
while (true) {
let leftY = fn(leftX);
let rightY = fn(rightX);
let middleX = (leftX + rightX) * .5;
let middleY = fn(middleX);
let a = (leftX + middleX) * .5;
let fa = fn(a);
let b = (middleX + rightX) * .5;
let fb = fn(b);
let leftResidual = Math.pow(fa - (leftY + middleY) * .5, 2);
let rightResidual = Math.pow(fb - (middleY + rightY) * .5, 2);
/* if both subinterval midpoints (fa,fb) barely deviate from their slope lines
* i.e. they're under the jumpThreshold, then return NaN,
* indicating no discountinuity with the current threshold,
* both subintervals are mostly straight */
if (leftResidual < jumpThreshold && rightResidual < jumpThreshold) return NaN;
if (!isFinite(fa) || a === leftX || a === middleX) return a;
if (!isFinite(fb) || b === middleX || b === rightX) return b;
if (leftResidual > rightResidual) {
/* left hand-side subinterval */
rightX = middleX;
middleX = a;
} else {
/* right hand-side subinterval */
leftX = middleX;
middleX = b;
}
}
}
function inverseRidderMethod(min, max, iterations, fn) {
/* Modified version of RiddersSolver from Apache Commons Math
* http://commons.apache.org/
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
let x1 = min;
let y1 = 1 / fn(x1);
let x2 = max;
let y2 = 1 / fn(x2);
// check for zeros before verifying bracketing
if (y1 == 0) {
return min;
}
if (y2 == 0) {
return max;
}
let functionValueAccuracy = 1e-55;
let relativeAccuracy = 1e-16;
let oldx = Number.POSITIVE_INFINITY;
let i = 0;
while (i < iterations) {
// calculate the new root approximation
let x3 = 0.5 * (x1 + x2);
let y3 = 1 / fn(x3);
if (!isFinite(y3)) return NaN;
if (Math.abs(y3) <= functionValueAccuracy) {
return x3;
}
let delta = 1 - (y1 * y2) / (y3 * y3); // delta > 1 due to bracketing
let correction = (signum(y2) * signum(y3)) * (x3 - x1) / Math.sqrt(delta);
let x = x3 - correction; // correction != 0
if (!isFinite(x)) return NaN;
let y = 1 / fn(x);
// check for convergence
let tolerance = Math.max(relativeAccuracy * Math.abs(x), 1e-16);
if (Math.abs(x - oldx) <= tolerance) {
return x;
}
if (Math.abs(y) <= functionValueAccuracy) {
return x;
}
// prepare the new interval for the next iteration
// Ridders' method guarantees x1 < x < x2
if (correction > 0.0) { // x1 < x < x3
if (signum(y1) + signum(y) == 0.0) {
x2 = x;
y2 = y;
} else {
x1 = x;
x2 = x3;
y1 = y;
y2 = y3;
}
} else { // x3 < x < x2
if (signum(y2) + signum(y) == 0.0) {
x1 = x;
y1 = y;
} else {
x1 = x3;
x2 = x;
y1 = y3;
y2 = y;
}
}
oldx = x;
}
}
function signum(a) {
return (a < 0.0) ? -1.0 : ((a > 0.0) ? 1.0 : a);
}
/* TEST */
console.log(findDiscontinuity(.5, .6, jumpThreshold, fn));
Python Code
I don't mind if the solution is provided in Javascript or Python
import math
def fn(x):
try:
# return (math.pow(math.tan(x), 3))
# return 1 / x
# return math.floor(x)
return x * ((x - 1 - 0.001) / (x - 1))
except ZeroDivisionError:
return float('Inf')
def slope(x1, y1, x2, y2):
try:
return (y2 - y1) / (x2 - x1)
except ZeroDivisionError:
return float('Inf')
def find_discontinuity(leftX, rightX, fn):
while True:
leftY = fn(leftX)
rightY = fn(rightX)
middleX = (leftX + rightX) / 2
middleY = fn(middleX)
leftSlope = abs(slope(leftX, leftY, middleX, middleY))
rightSlope = abs(slope(middleX, middleY, rightX, rightY))
if not math.isfinite(leftSlope) or not math.isfinite(rightSlope):
return middleX
if middleX == leftX or middleX == rightX:
return middleX
if leftSlope > rightSlope:
rightX = middleX
rightY = middleY
else:
leftX = middleX
leftY = middleY
Related
How this implementation of Bresenham's line for all cases relates to the 'original' one?
I found an implementation on the internet that draws perfect lines and it claims to be Bresenham's. Seeing the results it indeed resembles a lot Bresenham's, I can't spot any difference, but the code really seems far away from the original one. This is the code my professor showed in class and you can find it in any youtube tutorial: void lineBres (int x0, int y0, int xEnd, int yEnd) { int dx = fabs (xEnd - x0), dy = fabs(yEnd - y0); int p = 2 * dy - dx; int twoDy = 2 * dy, twoDyMinusDx = 2 * (dy - dx); int x, y; /* Determine which endpoint to use as start position. */ if (x0 > xEnd) { x = xEnd; y = yEnd; xEnd = x0; } else { x = x0; y = y0; } setPixel (x, y); while (x < xEnd) { x++; if (p < 0) p += twoDy; else { y++; p += twoDyMinusDx; } setPixel (x, y); } } This code below is not necessarily correct, but that's what makes the most sense to me in how Bresenham's line would look for all cases. https://github.com/ashiagarwal73/Bresenham-s-line-algorithm-for-all-quadrants/blob/master/Bresenhams.c And here is the implementation I found (I am using javascript): const drawBresenhamsLine = (startPoint, endPoint, width, colour, ctx) => { let dx = Math.abs(endPoint.x - startPoint.x); let dy = Math.abs(endPoint.y - startPoint.y); let incY = startPoint.y < endPoint.y ? 1 : -1; let incX = startPoint.x < endPoint.x ? 1 : -1; let err = dx - dy; while (true) { putPixel(startPoint, width, colour, ctx); if (startPoint.x === endPoint.x && startPoint.y === endPoint.y) break; let p = 2 * err; if (p > -dy) { err -= dy; startPoint.x += incX; } if (p < dx) { err += dx; startPoint.y += incY; } } }; So I just need to prove it is indeed Bresenham's and how it relates to the 'original' implementation, but it seems to me it's using a different approach somehow. dx and dy are just the same, in order to adapt to each quadrant it is comparing the differences between each axis and keeping in incY and incX. So here is when it becomes weird. First, it keeps the difference between dx and dy in err, this value changes with each interaction. It has a while (true) and it's drawing a pixel at the beginning of each loop instead of the end, the stopping condition is when startPoint is equal to endPoint. It doubles the err value and then compares it with -dy, if it's true then it will update err with dy and increment the x-axis. Then it compares err with dx, if it's true then it will update err with dx and increment the y-axis. In the original, the x-axis is always incremented and the y-axis is sometimes incremented. After running the new version sometimes, I believe that one of them (x xor y) is always incremented, I don't think both conditions can be true or false at the same time due to the math being applied. The original version is using p which is 2 * dy - dx to control the logic and it is modifying its value at each interaction. The new version is using err which is dx - dy to control the logic and it is modifying its value at each interaction. I am starting to think they differ quite a lot and the new version may not be Bresenham's implementation. So I am asking you, my dear reader, to clarify this one for me.
javascript algorithm: Check x/y-points to get if component is a circle or not
I have an matrix like this, so I hope you can get the circle outline: EDIT 1: What about the outline? The outline doesn't includes spaces (so every y-value gets minimum 2 x-values) EDIT 2: What is a circle? Searching for an more ore less "exact circles" like the example below! (nearly same radius at every point) 00000000000000000000000000000000 00000000000001111111100000000000 00000000000100000000010000000000 00000000010000000000000100000000 00000000100000000000000010000000 00000000100000000000000010000000 00000001000000000000000001000000 00000010000000000000000000100000 00000010000000000000000000100000 00000010000000000000000000100000 00000100000000000000000000100000 00000100000000000000000000100000 00000100000000000000000000100000 00000100000000000000000000100000 00000100000000000000000000100000 00000010000000000000000000100000 00000010000000000000000000100000 00000010000000000000000001000000 00000001000000000000000010000000 00000000100000000000000010000000 00000000100000000000000100000000 00000000010000000000001000000000 00000000000111111111100000000000 00000000000000000000000000000000 00000000000000000000000000000000 and I also have an array like this including all positions of the outline: var coordinates = [ [13,1],[14,1],[15,1],[16,1],[17,1],[18,1],[19,1],[20,1], [11,2],[21,2], [9,3],[23,3], [8,4],[24,4], [8,5],[24,5], [7,6],[25,6], [6,7],[26,7], [6,8],[26,8], [6,9],[26,9], [5,10],[26,10], [5,11],[26,11], [5,12],[26,12], [5,13],[26,13], [5,14],[26,14], [6,15],[26,15], [6,16],[26,16], [6,17],[25,17], [7,18],[24,18], [8,19],[24,19], [8,20],[23,20], [9,21],[22,21], [11,22],[12,22],[13,22],[14,22],[15,22],[16,22],[17,22],[18,22],[19,22],[20,22]] What is a good way to check if the coordinates are matching to be a circle? My first idea was using some code like this but tbh I'm sure there is a way more efficient & working way: var circle = [[13,1],[14,1],[15,1],[16,1],[17,1],[18,1],[19,1],[20,1],[11,2],[21,2],[9,3],[23,3],[8,4],[24,4],[8,5],[24,5],[7,6],[25,6],[6,7],[26,7],[6,8],[26,8],[6,9],[26,9],[5,10],[26,10],[5,11],[26,11],[5,12],[26,12],[5,13],[26,13],[5,14],[26,14],[6,15],[26,15],[6,16],[26,16],[6,17],[25,17],[7,18],[24,18],[8,19],[24,19],[8,20],[23,20],[9,21],[22,21],[11,22],[12,22],[13,22],[14,22],[15,22],[16,22],[17,22],[18,22],[19,22],[20,22]] var no_circle= [[13,1],[14,1],[25,4]] Array.prototype.is_circle = function() { var min = { 'x': Infinity, 'y': Infinity }; var max = { 'x': 0, 'y': 0 }; var center = { 'x': 0, 'y': 0 }; var radius; this.forEach(function(a) { a[0] = a[0] a[1] = a[1] if (a[0] > max.x) max.x = a[0]; if (a[0] < min.x) min.x = a[0]; if (a[1] > max.y) max.y = a[1]; if (a[1] < min.y) min.y = a[1]; }); center.x = (max.x + min.x) / 2; center.y = (max.y + min.y) / 2; radius = [] radius[0] = max.x - center.x radius[1] = center.x - min.x radius[2] = center.y - min.y radius[3] = max.y - center.y r = (radius[0] + radius[1] + radius[2] + radius[3]) / 4; if ((radius[0] > r-1 && radius[0] < r+1) && (radius[1] > r-1 && radius[1] < r+1) && (radius[2] > r-1 && radius[2] < r+1) && (radius[3] > r-1 && radius[3] < r+1)) return true; return false } var result1 = circle.is_circle(); console.log(result1) var result2 = no_circle.is_circle(); console.log(result2)
Your algorithm seems to check only four most distant points on X and Y axis. I guess that if you provide points that represent square shape it will also pass is_circle test. I propose that you do two-stage test with some extra roundness margin called e. Walk over whole set of points and remember x_min, y_min, x_max and y_max. Then make check if difference between delta of X and delta of Y is in the error margin i.e. abs((x_max-x_min) - (y_max-y_min)) <= e. That checks squarness of shape so that it can be circle, not oval. If that test passes then calculate center point c at (x_c, y_c) = (x_min+(x_max-x_min)/2, y_min+(y_max-y_min)/2) and for each point calculate if radius (distance from any point to center c) is within error margin e. To save on expensive calculations check if squared radius of each point is within error margin i.e. if abs((x-x_c)^2 + (y-y_c)^2 - r^2) <= e, where r^2 is computed for center c and first point on the list.
Get coords of the intersection of the line in the plane
I have a canvas with this params: width = 400, height = 400 and have a line passing through the point cursor[x1,y1] at an angle Q (in degree) I need get all coords of the intersection of the line in the plane and write it to array. Now i use this equation: y - y1 = k * (x - x1) to check all point I use this code: var rad = Q * Math.PI/180; for (ctrY = 0; ctrY < 400; ctrY += 1) { for (ctrX = 0; ctrX < 400; ctrX += 1) { if ( (ctrY - cursor.y) === ~~(Math.tan(rad) * (ctrX - cursor.x)) ) { z.push([ctrX, ctrY]); } } } For example when 0 < Q < 90 and cursor[x1,y1] = [200,200] z.length = 0 and it's not correct. Where i'm wrong? Maybe there is a more convenient algorithm? P.S. Sorry for my english
Seems you need line rastering algorithm. Consider Bresenham algorithm. You can also look at DDA algorithm
I imagine an algorithm like this. (I only consider the case when 0 < Q < 90). First I will want to calculate the points where the line will intersect the Ox and Oy axes, considering the origin (0,0) point the upper left corner and if we imagine that the negative x and y values are respectively to the left and to the top of this point. Let x2 and y2 be the values where the line will intersect Ox and Oy. We want to calculate these values. We now have a system with 2 unknown variables (x2 and y2): Math.tan(rad) = (y1 -y2)/x1 and Math.tan(rad) = y1/(x1-x2). We can deduct these equations by drawing the line on the coordinate system and analyzing a bit. If we solve the system of equations we find something like: x2 = (x1*y1 -x1 * x1 * Math.tan(rad)/(2 * y1-x1)) and y2= y1- x1 * Math.tan(rad) (These need to be verified, I haven't double checked my calculus). A linear equation can be defined by the formula y = a*x + b and in our case a = x2 and b = y2. We can then calculate the points like this: for (xIdx = 0; xIdx < 400; xIdx += 1) { var ctrX = xIdx; var ctrY = x2 * ctrX + y2 //todo: replace with the respective calculated variables x2 and y2(we could also define two functions in js) and proper rounding z.push([ctrX, ctrY]); } I'm not sure if I'm 100% accurate but I hope you understand my idea.
Generate set of points/coordinates to draw smooth parabola (semi-circle) given only a height, and a width
I'm building a patient monitor simulator in JavaScript. This involves drawing parabolas (semi-circles) on an HTML canvas. Normally this wouldn't be an issue with the bezierCurveTo() function however this solution is not applicable here as I need to animate the curve pixel by pixel as demonstrated here http://www.jet5.com/ecg/. From my understanding this will require an array of all points in the curve. My question is how can I generate this array of points from a provided width and height that I want the curve to be. Is there some sort of special command or algorithm that I can use to obtain these Cartesian coordinates. For a clearer picture of what I need please refer to the following image http://en.ecgpedia.org/wiki/File:Epi_endo_en.png. A lecturer helped me with the following equation: y = (x - t1) x (t2 - x). Here is my code (I ave created a point object and remember this is for an HTML canvas where 0, 0 is in the top left corner): var y = 0; var duration = 200; var t1 = 0; var t2 = duration; var x1 = 0; var x2 = 0; for (i = 0; i < duration; i++) { x1 = i; x2 = duration - i; var ctx = canvas.getContext("2d"); y = (x1 - t1) * (t2 - x2) if (i < duration / 2) { y = -y; } data.push(new point(y)); } While this partly worked from my understanding this equation wouldn't allow me to specify a height only the parabolas width. Any help is greatly appreciated
Best thing to do in this kind of mathematical situation is to normalize. Which means here, try to always go back to the case when x is between 0 and 1. so if x is in [ t1 ; t2 ] delta = (x-t1) / (t2 - t1). now delta moves in [0;1] !!! Magic In the same way, for your shape functions use normalized function that returns only in [ 0 ; 1]. The example you give in your code becomes : function shape(x) { return (x<0.5) ? x*(1-x) : -x*(1-x) } And the code becomes - just to be clear - for ( x = t1; x<t2; x++ ) { var delta = (x-t1) / (t2 - t1) ; y = amplitude * shape(delta); // do something with (x,y) } See a working jsbin here : http://jsbin.com/wodecowibexa/1/edit?js,output ![var cv = document.getElementById('cv'); var ctx = cv.getContext('2d'); ctx.fillStyle = '#C66'; // shift for all the drawings var plotShift = {x:0, y:200 } ; // draw between t1 and t2 with an initial y shift. // expects an easing function i.e. a function \[0;1\] -> \[0;1\] // yShift should be the last computed value. // returns the last computed value to allow chaining. function drawEasingFunction (yShift, t1, t2, amplitude, func) { var x=0, y=0; var duration = t2 - t1; for (x= t1; x < t2; x++) { // delta is a figure between 0 and 1 var delta = (x - t1) / duration; // y = yShift + amplitude*func(delta); ctx.fillRect(x+plotShift.x,y+plotShift.y,2,2); } return y; } var easingFunctions = \[ function (x) { return x} , /* line */ function (x) { return x*x} , /* line */ function (x) { return Math.sqrt(x)} , /* line */ function (x) { return Math.sin(Math.PI*x/2)} , /* sin */ function (x) { return x*(1-x)} , /* that's your function */ \]; var lastY = 0; var currentX = 0; var length = 50; // demo of all functions for (; currentX < cv.width; currentX+=length) { // take a random function var fnIndex = Math.floor(Math.random()*easingFunctions.length) ; var thisEasingFunction = easingFunctions\[fnIndex\]; // take some random amplitude var amplitude = (60 + Math.random()*10); // randomly switch the amplitude sign. if (Math.random() > 0.5) amplitude = -amplitude; // draw ! (and store last value) lastY = drawEasingFunction(lastY, currentX, currentX+length, amplitude, thisEasingFunction); }][2]
Apply gravity between two or more objects in HTML5 Canvas
I was creating something like a 2d gravity simulator, just for fun, and noticed that I'm a complete idiot in terms of math. I just can't get the gravity to work. I've tried following the instructions found here but it looks weird and when the distance reaches zero, it goes completely buggy. If I add 1 to the distance as recommended in the question, all objects go upper left. I've even tried not modifying gravity when distances reach zero, but this doesn't change the behavior. Here's the algorithm I'm using to apply gravity: var distX = obj1.x - obj2.x, distY = obj1.y - obj2.y; if (obj1 != obj2) { if (distY != 0) { obj1.vy += -(1 / (distY)); } if (distX != 0) { obj1.vx += -(1 / (distX)); } } I've tried using other algorithms too, but most of them don't care for the distance between objects. Note that I want the gravity to affect distant objects less than closer objects.
Instead of solving any equations we could use an approximation. dv/dt = G*M*m/r^2, but for small t we could use the approximation Δv = (G*M*m/r^2)*Δt. When the objects collide I have implemented perfectly inelastic collision (see Wikipedia). This prevents the distance between two objects from being to small and therefore the maximum force is limited. I also moved the part of the code where the object's position is changed to a separate loop, so the forces calculated for obj1 and obj2 are equal in size. Demo function tick() { allObjs.forEach(function (obj1) { allObjs.forEach(function (obj2) { var diffX = obj2.x - obj1.x, var diffY = obj2.y - obj1.y; var distSquare = diffX*diffX + diffY*diffY var dist = Math.sqrt(distSquare); if (obj1 != obj2) { if (dist > obj1.w/2 + obj2.w/2) { //If you add mass to the objects change to obj2.mass //instead of 50 var totalForce = 50/distSquare; obj1.vx += totalForce * diffX / dist; obj1.vy += totalForce * diffY / dist; } else { //Collision has occurred //If you add mass to the objects change to //tempX = (obj1.mass*obj1.vx + obj2.mass*obj2.vx)/(obj1.mass+ //obj2.mass); //tempY = (obj1.mass*obj1.vy + obj2.mass*obj2.vy)/(obj1.mass+ //obj2.mass); var tempX = (obj1.vx + obj2.vx)/2; var tempY = (obj1.vy + obj2.vy)/2; obj1.vx = tempX; obj2.vx = tempX; obj1.vy = tempY; obj2.vy = tempY; } } }); }); allObjs.forEach(function (obj1) { obj1.x += obj1.vx / 25; obj1.y += obj1.vy / 25; }); stage.update(); }
Try var distX = obj1.x - obj2.x, distY = obj1.y - obj2.y; var rsq = distX *distX + distY * distY; var r = Math.sqrt(rsq); var F = 50 / rsq; // constant chosen to be pleasing var rhat_x = distX / r; var rhat_y = distY / r; var Fx = F * rhat_x; var Fy = F * rhat_y; obj1.vx += -Fx; obj1.vy += -Fy; obj2.vx += Fx; obj2.vy += Fy; This is very basic, its not taking mass into account its using the simplest possible way of solving the equations you should really use something like 5th order Runga-Kutta w/ error correction. But it does use the formula for gravitational F = - G m1 m2 / r^2 where G is the universal gravitational constant, m1 m2 are the two masses (I've all of these to 1!) r^2 is the square of the distance between the objects. The force is in the direction to the other object, let this be a unit vector rhat so the vector version of the force, using 1 for the constants F = - ( 1 / r^2 ) rhat The above gives reasonable results it you start out with createPlanet(50, 200, 2, 0, 1); createPlanet(400, 200, 2, 0, -1); you have to take care that the two planets don't get too close or the acceleration goes off to infinity and the velocities get too big. While playing around I tried var distX = obj1.x - obj2.x, distY = obj1.y - obj2.y; var rsq = distX *distX + distY * distY; // square of the distance var r = Math.sqrt(rsq); var Fx = distX / r; var Fy = distY / r; obj1.vx += -Fx; obj1.vy += -Fy; obj2.vx += Fx; obj2.vy += Fy; which gives pleasing but physically incorrect results.
Newton's equations of motion F = ma need to be solved here. You are not doing anything like that in your code. No wonder it isn't matching your intuition. It would help to understand the physics. This is a vector equation. The force is gravity, which follows an inverse distance squared law. You also know how acceleration, velocity, and displacement are related. You have to know calculus. For your 2D world, that means six equations for each body in the problem. Two bodies means 12 coupled equations. Solving these equations means integrating all those coupled ordinary differential equations in time. You'll need to know something about numerical methods (e.g. Runga-Kutta 5th order integration w/ error correction). You'd have a lot to learn to write such a thing yourself. I'd recommend looking into a JavaScript physics library like Box2D or something else that Google might find.