Trilateration with approximated distances using latitude and longitude - algorithm

I can't quite figure this one out.
I am trying to approximate the location (latitude / longitude) of a beacon based on 3 distance measurements from 3 fixed locations. However the distance readings available may have an error of up to 1 km.
Similar questions regarding trilateration have been asked here (with precise measurements), here, here (distance measurement errors in Java, but not in lat/lon coordinates and no answers) as well as others. I also managed to dig up this paper dealing with imperfect measurement data, however it for one assumes a cartesian coordinate system and is also rather mathematical than close to a usable implementation.
So none of above links and answers are really applicable to the following problem:
All available distance measurements are approximated in km (where data most frequently contains readings in-between 1 km and 100 km, in case this matters)
Measurement errors of up to 1 km are possible.
3 distance measurements are performed based on 3 fixed (latitude / longitude known) positions.
target approximation should also be a latitude / longitude combination.
So far I have adapted this Answer to C#, however I noticed that due to the measurement inaccuracies this algorithm does not work (as the algorithm assumes the 3 distance-circles to perfectly intersect with each other):
public static class Trilateration
{
public static GeoLocation Compute(DistanceReading point1, DistanceReading point2, DistanceReading point3)
{
// not my code :P
// assuming elevation = 0
const double earthR = 6371d;
//using authalic sphere
//if using an ellipsoid this step is slightly different
//Convert geodetic Lat/Long to ECEF xyz
// 1. Convert Lat/Long to radians
// 2d. Convert Lat/Long(radians) to ECEF
double xA = earthR * (Math.Cos(Radians(point1.GeoLocation.Latitude)) * Math.Cos(Radians(point1.GeoLocation.Longitude)));
double yA = earthR * (Math.Cos(Radians(point1.GeoLocation.Latitude)) * Math.Sin(Radians(point1.GeoLocation.Longitude)));
double zA = earthR * Math.Sin(Radians(point1.GeoLocation.Latitude));
double xB = earthR * (Math.Cos(Radians(point2.GeoLocation.Latitude)) * Math.Cos(Radians(point2.GeoLocation.Longitude)));
double yB = earthR * (Math.Cos(Radians(point2.GeoLocation.Latitude)) * Math.Sin(Radians(point2.GeoLocation.Longitude)));
double zB = earthR * (Math.Sin(Radians(point2.GeoLocation.Latitude)));
double xC = earthR * (Math.Cos(Radians(point3.GeoLocation.Latitude)) * Math.Cos(Radians(point3.GeoLocation.Longitude)));
double yC = earthR * (Math.Cos(Radians(point3.GeoLocation.Latitude)) * Math.Sin(Radians(point3.GeoLocation.Longitude)));
double zC = earthR * Math.Sin(Radians(point3.GeoLocation.Latitude));
// a 64 bit Vector3 implementation :)
Vector3_64 P1 = new(xA, yA, zA);
Vector3_64 P2 = new(xB, yB, zB);
Vector3_64 P3 = new(xC, yC, zC);
//from wikipedia
//transform to get circle 1 at origin
//ransform to get circle 2d on x axis
Vector3_64 ex = (P2 - P1).Normalize();
double i = Vector3_64.Dot(ex, P3 - P1);
Vector3_64 ey = (P3 - P1 - i * ex).Normalize();
Vector3_64 ez = Vector3_64.Cross(ex, ey);
double d = (P2 - P1).Length;
double j = Vector3_64.Dot(ey, P3 - P1);
//from wikipedia
//plug and chug using above values
double x = (Math.Pow(point1.DistanceKm, 2d) - Math.Pow(point2.DistanceKm, 2d) + Math.Pow(d, 2d)) / (2d * d);
double y = ((Math.Pow(point1.DistanceKm, 2d) - Math.Pow(point3.DistanceKm, 2d) + Math.Pow(i, 2d) + Math.Pow(j, 2d)) / (2d * j)) - ((i / j) * x);
// only one case shown here
double z = Math.Sqrt(Math.Pow(point1.DistanceKm, 2d) - Math.Pow(x, 2d) - Math.Pow(y, 2d));
//triPt is a vector with ECEF x,y,z of trilateration point
Vector3_64 triPt = P1 + x * ex + y * ey + z * ez;
//convert back to lat/long from ECEF
//convert to degrees
double lat = Degrees(Math.Asin(triPt.Z / earthR));
double lon = Degrees(Math.Atan2(triPt.Y, triPt.X));
return new GeoLocation(lat, lon);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double Radians(double degrees) =>
degrees * Math.Tau / 360d;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double Degrees(double radians) =>
radians * 360d / Math.Tau;
}
Above code most often than not does not work in my case, and instead only returns "Not a number" as it tries to take the square root of a negative number when calculating the final z value (due to measurement inaccuracies).
In my case measurements may return data like this (visualized with some random online tool):
where only 2 or even none of the distance circles intersect:
What I am looking for is the an algorithm returning the best possible approximation of the target location based on three distance measurements with a known maximum error of 1 km or further approaches I could take.
I have also thought of iterating over points on the circles to then determining the minimum average distance to all the points on the other circles but the 3-dimensional sphere geometry of the earth is giving me a headache. Also there's probably a way better and simpler approach to this which I just can't figure out right now.
As this is more of an algorithmic problem, rather than any language-specific thing, I appreciate any help in whatever programming language, pseudo code or natural language.

If you have access to a scientific computing library which provides non-linear optimization utilities, then you could try finding the point which minimizes the following:
(||x - p_1|| - r_1)^2 + (||x - p_2|| - r_2)^2 + (||x - p_3|| - r_3)^2 + (||x - p_earth|| - r_earth)^2
where p_i is the location (in Cartesian coordinates) of the ith location you measure from, r_i is the corresponding distance reading, p_earth is the location of the Earth, r_earth is the radius of the earth, and ||a|| denotes the norm/length of the vector a.
Each term in the expression is trying to minimize the residual radius error.
This can of course be modified to suit your needs - e.g. if constrained optimization is available, you could encode the requirement that the point be on the surface on the earth as a constraint rather than a term to optimize for. If spherical earth model isn't accurate enough, you could define an error from the Earth's surface, or just project your result onto the Earth if that is accurate enough.

Related

Flocking/ Boids Algorithm : Field Of view specified by angle in 3D

I am trying to make boids algorithm in Unity 3D.
I got into one problem: How to implement field of view in specific angle?
360 degrees is easy - U check only distance between two boids. But I dont want boids able to look behind themself. I also want to be able to change angle of view in Inspector, so it must be based on calculations.
I would be gratefull for any ideas:(
I already tried with mesh collider which is cone but it didnt go well. - not working for 180 and higher. So I am looking best way to calculate this.
Assume that the boid is at point p = (p.x, p.y, p.z) heading toward some point h = (h.x, h.y, h.z), and we want to know whether an object at point q = (q.x, q.y, q.z) is in the boid's field of vision.
The Law of Cosines gives us a formula for the cosine of the angle φ between the boid's heading and the boid's path to the object:
(h−p) · (q−p)
cos(φ) = ---------------
||h−p|| ||q−p||
= (dx1*dx2 + dy1*dy2 + dz1*dz2) /
(sqrt(dx1*dx1 + dy1*dy1 + dz1*dz1) * sqrt(dx2*dx2 + dy2*dy2 + dz2*dz2))
where
dx1 = h.x - p.x
dy1 = h.y - p.y
dz1 = h.z - p.z
dx2 = q.x - p.x
dy2 = q.y - p.y
dz2 = q.z - p.z
Given some angle ρ (in whatever units your cosine function accepts, usually radians) past which the boid cannot see (putting the field of vision at 2ρ), we have
φ > ρ if and only if cos(φ) < cos(ρ),
so we can precompute cos(ρ) and then use the above formula for repeated tests.
To avoid division by zero and other numerical problems, you might want to check whether the denominator of the division is very small and if so declare that the boid can feel whatever the object is even outside its field of vision.

Calculate Geolocation on line between two GeoLocations

I have two known Google Geolocation points A and B. I need to return GeoLocation point C which is on AB line and on distance x from point A:
Geolocation returnGeolocationC(Geolocation A, Geolocation B, double x) {
...
return C;
}
I know that I can use Haversine formula and I can calculate AB distance and therefore I also have AC and CB distance. Any idea or hint how to implement this?
Edit: Line is straight, no need to consider roads.
Well, this is a good problem which solution will depend on the area of interest, for instance:
Consider the situation faced by a botanist studying a stand of oak trees on a small plot of land. One component of the data analysis involves determining the location of these trees and calculating the distance betwee
n them. In this situation, straight line or Euclidean distance is the most logical choice. This only requires the use of the Pythagorean Theorem to calculate the shortest distance between two points:
straight_line_distance = sqrt ( ( x2 - x1 )**2 + ( y2 - y1 )**2 );
The variables x and y refer to co-ordinates in a two-dimensional plane and can reflect any unit of measurement, such as feet or miles.
Consider a different situation, an urban area, where the objective is to calculate the distance between customers’ homes and various retail outlets. In this situation, distance takes on a more specific meaning, usually road distance, making straight line distance less suitable. Since streets in many cities are based on a grid system, the typical trip may be approximated by what is known as the Manhattan, city block or taxi cab distance (Fothering-
ham, 2002):
block_distance = ( abs( x2 - x1 ) + abs( y2 - y1 ) ) ;
Instead of the hypotenuse of the right-angled triangle that was calculated for the straight line distance, the above formula simply adds the two sides that form the right angle. The straight line and city block formulae are closely related, and can be generalized by what are referred to as the Minkowski metrics, which in this case are restricted to two dimensions:
minkowski_metric = ( abs(x2 - x1)**k + abs(y2 - y1)**k )**(1/k);
The advantage of this formula is that you only need to vary the exponent to get a range of distance measures. When k = 1, it is equivalent to the city block distance; when k=2, it is the Euclidean distance. Less commonly,
other values of k may be used if desired, usually between 1 and 2. In some situations, it may have been determined that actual distances were greater than the straight line, but less than the city block, in which case a value such as "1.4" may be more appropriate. One of the interesting features of the Minkowski metric is that for values considerably larger than 2 (approaching infinity), the distance is the larger of two sides used in the city block calculation, although this is typically not applicable in a geographic context.
So pseudocode would be something like the following:
distance2d (x1, y1, x2, y2, k)
(max( abs(x2 - x1), abs(y2 - y1) ) * (k > 2))
+
((abs(x2 - x1)**k + abs(y2 - y1)** k )**(1/ k)) * (1 <=k<=2)
end
If 1 <= k <=2, the basic Minkowski metric is applied, since (1 <= k <=2) resolves to 1 and (k > 2) resolves to 0. If k > 2, an alternate formula is applied, since computations become increasingly intensive for large values of k. This second formula is not really necessary, but is useful in demonstrating how modifications can be easily incorporated in distance measures.
The previous distance measures are based on the concept of distance in two dimensions. For small areas like cities or counties, this is a reasonable implification. For longer distances such as those that span larger countries
or continents, measures based on two dimensions are no longer appropriate, since they fail to account for the curvature of the earth. Consequently, global distance measures need to use the graticule, the co-ordinate system
comprised of latitude and longitude along with special formulae to calculate the distances. Lines of latitude run in an east to west direction either above or below the equator. Lines of longitude run north and south through the poles, often with the Prime Meridian (running through Greenwich, England) measured at 0°. Further details of latitude and longitude are available (Slocum et al., 2005). One issue with using latitude and longitude is that the co-ordinates may require some transformation and preparation before they are suitable to use in distance calculations. Coordinates are often expressed in the sexagesimal system (similar to time) of degrees, minutes, and seconds, in which each degree consists of 60 minutes and each
minute is 60 seconds. Furthermore, it is also necessary to provide and indication of the position relative to the equator (North or South) and the Prime Meridian (East or West). The full co-ordinates may take on a variety of formats; below is a typical example that corresponds approximately to the city of Philadelphia:
39° 55' 48" N 75° 12' 12" W
As you mentioned Harvesine, and also I am extending a lot, we can compare results using law of cosines and Harvesine, so pseudocode:
begin
ct = constant('pi')/180 ;
radius = 3959 ; /* 6371 km */
#Both latitude and longitude are in decimal degrees ;
lat1 = 36.12;
long1 = -86.67;
lat2 = 33.94;
long2 = -118.40 ;
#Law of Cosines ;
a = sin(lat1*ct) * sin(lat2*ct) ;
b = cos(lat1*ct) * cos(lat2*ct) * cos((long2-long1) *ct);
c = arcos(a + b) ;
d = radius * c ;
put 'Distance using Law of Cosines ' d
# Haversine ** ;
a2 = sin( ((lat2 - lat1)*ct)/2)**2 +
cos(lat1*ct) * cos(lat2*ct) * sin(((long2 - long1)*ct)/2)**2
c2 = 2 * arsin(min(1,sqrt(a2))) ;
d2 = radius * c2 ;
put 'Distance using Haversine formula =' d2
end
In addition to the constant that will be used to convert degrees to radians, the radius of the earth is required, which on average is equal to 6371 kilometres or 3959 miles. The Law of Cosines uses spherical geometry to
calculate the great circle distance for two points on the globe. The formula is analogous to the Law of Cosines for plane geometry, in which three connected great arcs correspond to the three sides of the triangle. The Haversine formula is mathematically equivalent to the Law of Cosines, but is often preferred since it is less sensitive to round-off error that can occur when measuring distances between points that are located very close tog
ether (Sinnott, 1984). With the Haversine, the error can occur for points that are on opposite sides of the earth, but this is usually less of a problem.
You can find a really easy formula at this link.
Since you have the distance from one of the points and not the fraction of the distance on the segment you can slightly modify the formula:
A=sin(d-x)/sin(d)
B=sin(x)/sin(d)
x = A*cos(lat1)*cos(lon1) + B*cos(lat2)*cos(lon2)
y = A*cos(lat1)*sin(lon1) + B*cos(lat2)*sin(lon2)
z = A*sin(lat1) + B*sin(lat2)
lat=atan2(z,sqrt(x^2+y^2))
lon=atan2(y,x)
where x is the required distance and d is the distance between A and B (that you can evaluate with Haversine), both divided by the Earth radius.
You can also use another formula for sin(d):
nx = cos(lat1)*sin(lon1)*sin(lat2) - sin(lat1)* cos(lat2)*sin(lon2)
ny = -cos(lat1)*cos(lon1)*sin(lat2) + sin(lat1)* cos(lat2)*cos(lon2)
nz = cos(lat1)*cos(lon1)*cos(lat2)*sin(lon2) - cos(lat1)*sin(lon1)*cos(lat2)*cos(lon2)
sind = sqrt(nx^2+ny^2+nz^2)
It's more complex than the Haversine formula, but you can memoize some of the factors in the two steps.
As the OP posted a non working Java implementation, this is my corrections to make it work.
private static GpsLocation CalcGeolocationWithDistance(GpsLocation pointA, GpsLocation pointB, double distanceFromA)
{ //distanceFromA = 2.0 km, PointA and PointB are in Europe on 4.0km distance.
double earthRadius = 6371000.0;
double distanceAB = CalcDistance(pointA.Latitude, pointA.Longitude, pointB.Latitude, pointB.Longitude);
//distance AB is calculated right according to Google Maps (4.0 km)
double a = Math.Sin((distanceAB - distanceFromA) / earthRadius) / Math.Sin(distanceAB / earthRadius);
double b = Math.Sin(distanceFromA / earthRadius) / Math.Sin(distanceAB / earthRadius);
double x = a * Math.Cos(pointA.Latitude * Math.PI / 180) * Math.Cos(pointA.Longitude * Math.PI / 180) + b * Math.Cos(pointB.Latitude * Math.PI / 180) * Math.Cos(pointB.Longitude * Math.PI / 180);
double y = a * Math.Cos(pointA.Latitude * Math.PI / 180) * Math.Sin(pointA.Longitude * Math.PI / 180) + b * Math.Cos(pointB.Latitude * Math.PI / 180) * Math.Sin(pointB.Longitude * Math.PI / 180);
double z = a * Math.Sin(pointA.Latitude * Math.PI / 180) + b * Math.Sin(pointB.Latitude * Math.PI / 180);
double lat = Math.Atan2(z, Math.Sqrt(x * x + y * y)) * 180 / Math.PI;
double lon = Math.Atan2(y, x) * 180 / Math.PI;
//lat and lon are mo more placed somewhere in Africa ;)
return new GpsLocation(lat, lon);
}

Find coordinates inside a rectangular area constructed by lat/long GPS pairs

I've never deal much with location-based data, so very much new to the whole GPS coding related questions. I have a problem that I don't seem to find a very efficient way in solving it or maybe there's an algorithm that I'm not too sure.
Let said you have given 4 lat/long coordinates which construct some kind of a rectangular area: (X0, Y0), (X1, Y0), (X0, Y1), (X1, Y1)
-----------------------------------
| b |
| a |
| | d
| |
| c |
-----------------------------------
e
Is there a way to find all the point that are inside the given rectangular area : a, b, c
And all the points outside of the area? e, d
I can easily to construct a 2D matrix to do this, but that's only if the coordinates are in integer, but with lat/long pairs, the coordinates are usually in float numbers which we cannot use it to construct a 2D table.
Any cool ideas?
Edited 1:
What about this Ray-casting algorithm? Is this a good algorithm to be used for GPS coordinates which is a float number?
If your rectangle is axis-aligned, #Eyal's answer is the right one (and you actually don't need 8 values but 4 are enough).
If you deal with a rotated rectangle (will work for any quadrilateral), the ray-casting method is appropriate: consider the horizontal line Y=Yt through your test point and find the edges that cross it (one endpoint above, one endpoint below). There will be 0 or 2 such edges. In case 0, you are outside. Otherwise, compute the abscissas of the intersections of these edges with the line. If 0 or 2 intersection are on the left of the test point, you are outside.
Xi= Xt + (Yt - Y0) (X1 - X0) / (Y1 - Y0)
An alternative solution to #YvesDaoust's and #EyalSchneider's is to find the winding number or the crossing number of each point (http://geomalgorithms.com/a03-_inclusion.html). This solution scales to a polygon of any number of vertices (regardless of axis-alignment).
The Crossing Number (cn) method
- which counts the number of times a ray starting from the point P crosses the polygon boundary edges. The point is outside when this "crossing number" is even; otherwise, when it is odd, the point is inside. This method is sometimes referred to as the "even-odd" test.
The Winding Number (wn) method
- which counts the number of times the polygon winds around the point P. The point is outside only when this "winding number" wn = 0; otherwise, the point is inside.
Incidentally, #YvesDaoust's solution effectively calculates the crossing number of the point.
There is an unlimited number of points inside a rectangle, so you have to define a
step with (distane between two points).
You could just iterate with two nested loops,
lat, lon coordinates can be converted to integer using a multiplication factor of:
multiply with 1E7 (10000000) to get maximum acuracy of 1cm, or
10000000: 1cm
1000000: 10cm
100000: 1m
10000: 10m
1000: 100m
100: 1km
10: 11km
1: 111km
Now iterate
// convert to spherical integer rectangle
double toIntFact = 1E7;
int x = (int) (x0 * toIntFact);
int y = (int) (y0 * toIntFact);
int tx1 = x1 * toIntFact;
int ty1 = y1 * toIntFact;
int yStep = 100000; // about 1.11 m latitudinal span. choose desired step above in list
int xStep = (int) (yStep / cos(Math.toRadians(y0))); // longitude adaption factor depending of cos(latitude); more acurate (symetric) is to use cos of centerLatitude: (y0 + y1) / 2;
for (int px = x; px < tx1; px+= xStep) {
for (int py = y; py < ty1; py+= yStep) {
drawPoint(px, py); // or whatever
}
}
This should give an point set with same distances inbetween point for about some kilometer wide rectangles.
The code does not work when overlapping the Datum limit (-180 to 180 jump) or
when overlapping the poles. Delivers useable results up to latitude 80° N or S.
This code uses some kind of implicit equidistant (equirectangular) projection (see the division by cos(centerLat) to correct the fact that 1 degree of latitude is another distance measured in meters than one degree of longitude.
If the size of the rectangle exceeds some ten or hundred kilomters, then depending on your requirements have to use an advanced projection: e.g convert lat, lon with an WGS84 to UTM conversion. The result are coordinates in meters, which then you iterate analog.
But are you sure that this is what you want?
Nobody wants to find all atoms inside a rectangle.
May all screen pixels, or a method isInsideRectangle(lat,lon, Rectangle);
So think again for what you need that.

Algorithm for deriving control points of a bezier curve from points along that curve?

I've been looking for, but obviously not finding, an algorithm that will allow me to plug in a list of x,y coordinates that are known to be along a curve so as to get the 4 control points for a cubic bezier curve spit out.
To be more precise, I'm looking for an algorithm that will give me the two control points required to shape the curve while inputting a series of discrete points including the two control points which determine the start and end of the curve.
Thanks!
Edit: Okay, due to math, an old foe, I need to ask for the bezier curve of best fit to a polynomial function.
So I assume that the endpoints are fixed, and then you have a number of (x,y) sample points that you want to fit with a cubic Bezier.
The number of sample points that you have will determine what approach to take. Let's look through a few cases:
2 points
2 sample points is the simplest case. That gives you a total of 4 points, if you count the end points. This is the number of CVs in a cubic Bezier. To solve this, you need a parameter (t) value for both of the sample points. Then you have a system of 2 equations and 2 points that you need to solve, where the equation is the parametric equation of a Bezier curve at the t values you've chosen.
The t values can be whatever you like, but you will get better results by using either 1/3 and 2/3, or looking at relative distances, or relative distances along a baseline, depending on your data.
1 point
This is similar to 2 points, except that you have insufficient information to uniquely determine all your degrees of freedom. What I would suggest is to fit a quadratic Bezier, and then degree elevate. I wrote up a detailed example of quadratic fitting in this question.
More than 2 points
In this case, there isn't a unique solution. I have used least-squares approximation with good results. The steps are:
Pick t values for each sample
Build your system of equations as a matrix
Optionally add fairing or some other smoothing function
Solve the matrix with a least-squares solver
There is a good description of these steps in this free cagd textbook, chapter 11. It talks about fitting b-splines, but a cubic bezier is a type of b-spline (knot vector is 0,0,0,1,1,1 and has 4 points).
Let's say you have a curve y = f(x)
To define a bezier curve you need 4 points, like:
P1x, P1y, P2x, P2y, P3x, P3y and P4x and P4y
P1 and P4 you are the begin/end points of the curve. P2 and P3 are control points.
You already know where the beginning and end of the curve is. You have to calculate P2 and P3. The x coordinate P2x and P3x are easy, because you just pick them by selecting the curve's t to be eg 1/3 and 2/3. So you have P2x and P3x
Then, you end up with a system of two equations and two unknowns (the P2y and P3y).
After crunching some math you end up with something like this:
(My f(x) was a cubic polynomial, which also guaranteed that I would be able to fit one cubic Bezier curve to it exactly.)
/**
#params {Object} firstPoint = {x:...,y...}
#params {Object} lastPoint = {x:...,y...}
#params {Object} cubicPoly Definition of a cubic polynomial in the form y=ax^3+bx^2+c.
Has a method EvaluateAt, which calculates y for a particular x
*/
var CalcBezierControlPoints = function(firstPoint, lastPoint, cubicPoly) {
var xDiff = lastPoint.X - firstPoint.X;
var x1 = firstPoint.X + xDiff / 3.0;
var x2 = firstPoint.X + 2.0 * xDiff / 3.0;
var y1 = cubicPoly.EvaluateAt(x1);
var y2 = cubicPoly.EvaluateAt(x2);
var f1 = 0.296296296296296296296; // (1-1/3)^3
var f2 = 0.037037037037037037037; // (1-2/3)^3
var f3 = 0.296296296296296296296; // (2/3)^3
var b1 = y1 - firstPoint.Y * f1 - lastPoint.Y / 27.0;
var b2 = y2 - firstPoint.Y * f2 - f3 * lastPoint.Y;
var c1 = (-2 * b1 + b2) / -0.666666666666666666;
var c2 = (b2 - 0.2222222222222 * c1) / 0.44444444444444444;
var p2 = {};
var p3 = {};
p2.X = x1;
p2.Y = c1;
p3.X = x2;
p3.Y = c2;
return ([p2, p3]);
}

circle-circle collision

I am going to develop a 2-d ball game where two balls (circles) collide. Now I have the problem with determining the colliding point (in fact, determining whether they are colliding in x-axis/y-axis). I have an idea that when the difference between the y coordinate of 2 balls is greater than the x coordinate difference then they collide in their y axis, otherwise, they collide in their x axis. Is my idea correct? I implemented this thing in my games. Normally it works well, but sometimes, it fails. Can anyone tell me whether my idea is right? If not, then why, and is any better way?
By collision in the x axis, I mean the circle's 1st, 4th, 5th, or 8th octant, y axis means the circle's 2nd, 3rd, 6th, or 7th octant.
Thanks in advance!
Collision between circles is easy. Imagine there are two circles:
C1 with center (x1,y1) and radius r1;
C2 with center (x2,y2) and radius r2.
Imagine there is a line running between those two center points. The distance from the center points to the edge of either circle is, by definition, equal to their respective radii. So:
if the edges of the circles touch, the distance between the centers is r1+r2;
any greater distance and the circles don't touch or collide; and
any less and then do collide.
So you can detect collision if:
(x2-x1)^2 + (y2-y1)^2 <= (r1+r2)^2
meaning the distance between the center points is less than the sum of the radii.
The same principle can be applied to detecting collisions between spheres in three dimensions.
Edit: if you want to calculate the point of collision, some basic trigonometry can do that. You have a triangle:
(x1,y1)
|\
| \
| \ sqrt((x2-x1)^2 + (y2-y1)^2) = r1+r2
|y2-y1| | \
| \
| X \
(x1,y2) +------+ (x2,y2)
|x2-x1|
The expressions |x2-x1| and |y2-y1| are absolute values. So for the angle X:
|y2 - y1|
sin X = -------
r1 + r2
|x2 - x1|
cos X = -------
r1 + r2
|y2 - y1|
tan X = -------
|x2 - x1|
Once you have the angle you can calculate the point of intersection by applying them to a new triangle:
+
|\
| \
b | \ r2
| \
| X \
+-----+
a
where:
a
cos X = --
r2
so
a = r2 cos X
From the previous formulae:
|x2 - x1|
a = r2 -------
r1 + r2
Once you have a and b you can calculate the collision point in terms of (x2,y2) offset by (a,b) as appropriate. You don't even need to calculate any sines, cosines or inverse sines or cosines for this. Or any square roots for that matter. So it's fast.
But if you don't need an exact angle or point of collision and just want the octant you can optimize this further by understanding something about tangents, which is:
0 <= tan X <= 1 for 0 <= X <= 45 degrees;
tan X >= 1 for 45 <= X <= 90
0 >= tan X >= -1 for 0 >= X => -45;
tan X <= -1 for -45 >= X => -90; and
tan X = tan (X+180) = tan (X-180).
Those four degree ranges correspond to four octants of the cirlce. The other four are offset by 180 degrees. As demonstrated above, the tangent can be calculated simply as:
|y2 - y1|
tan X = -------
|x2 - x1|
Lose the absolute values and this ratio will tell you which of the four octants the collision is in (by the above tangent ranges). To work out the exact octant just compare x1 and x2 to determine which is leftmost.
The octant of the collision on the other single is offset (octant 1 on C1 means octant 5 on C2, 2 and 6, 3 and 7, 4 and 8, etc).
As cletus says, you want to use the sum of the radii of the two balls. You want to compute the total distance between the centers of the balls, as follows:
Ball 1: center: p1=(x1,y1) radius: r1
Ball 2: center: p2=(x2,y2) radius: r2
collision distance: R= r1 + r2
actual distance: r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )
A collision will happen whenever (r12 < R). As Artelius says, they shouldn't actually collide on the x/y axes, they collide at a particular angle. Except, you don't actually want that angle; you want the collision vector. This is the difference between the centers of the two circles when they collide:
collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
actual distance: r12= sqrt( dx*dx + dy*dy )
Note that you have already computed dx and dy above when figuring the actual distance, so you might as well keep track of them for purposes like this. You can use this collision vector for determining the new velocity of the balls -- you're going to end up scaling the collision vector by some factors, and adding that to the old velocities... but, to get back to the actual collision point:
collision point: pcollision= ( (x1*r2+x2*r1)/(r1+r2), (y1*r2+y2*r1)/(r1+r2) )
To figure out how to find the new velocity of the balls (and in general to make more sense out of the whole situation), you should probably find a high school physics book, or the equivalent. Unfortunately, I don't know of a good web tutorial -- suggestions, anyone?
Oh, and if still want to stick with the x/y axis thing, I think you've got it right with:
if( abs(dx) > abs(dy) ) then { x-axis } else { y-axis }
As for why it might fail, it's hard to tell without more information, but you might have a problem with your balls moving too fast, and passing right by each other in a single timestep. There are ways to fix this problem, but the simplest way is to make sure they don't move too fast...
This site explains the physics, derives the algorithm, and provides code for collisions of 2D balls.
Calculate the octant after this function calculates the following: position of collision point relative to centre of mass of body a; position of collision point relative to centre of mass of body a
/**
This function calulates the velocities after a 2D collision vaf, vbf, waf and wbf from information about the colliding bodies
#param double e coefficient of restitution which depends on the nature of the two colliding materials
#param double ma total mass of body a
#param double mb total mass of body b
#param double Ia inertia for body a.
#param double Ib inertia for body b.
#param vector ra position of collision point relative to centre of mass of body a in absolute coordinates (if this is
known in local body coordinates it must be converted before this is called).
#param vector rb position of collision point relative to centre of mass of body b in absolute coordinates (if this is
known in local body coordinates it must be converted before this is called).
#param vector n normal to collision point, the line along which the impulse acts.
#param vector vai initial velocity of centre of mass on object a
#param vector vbi initial velocity of centre of mass on object b
#param vector wai initial angular velocity of object a
#param vector wbi initial angular velocity of object b
#param vector vaf final velocity of centre of mass on object a
#param vector vbf final velocity of centre of mass on object a
#param vector waf final angular velocity of object a
#param vector wbf final angular velocity of object b
*/
CollisionResponce(double e,double ma,double mb,matrix Ia,matrix Ib,vector ra,vector rb,vector n,
vector vai, vector vbi, vector wai, vector wbi, vector vaf, vector vbf, vector waf, vector wbf) {
double k=1/(ma*ma)+ 2/(ma*mb) +1/(mb*mb) - ra.x*ra.x/(ma*Ia) - rb.x*rb.x/(ma*Ib) - ra.y*ra.y/(ma*Ia)
- ra.y*ra.y/(mb*Ia) - ra.x*ra.x/(mb*Ia) - rb.x*rb.x/(mb*Ib) - rb.y*rb.y/(ma*Ib)
- rb.y*rb.y/(mb*Ib) + ra.y*ra.y*rb.x*rb.x/(Ia*Ib) + ra.x*ra.x*rb.y*rb.y/(Ia*Ib) - 2*ra.x*ra.y*rb.x*rb.y/(Ia*Ib);
double Jx = (e+1)/k * (Vai.x - Vbi.x)( 1/ma - ra.x*ra.x/Ia + 1/mb - rb.x*rb.x/Ib)
- (e+1)/k * (Vai.y - Vbi.y) (ra.x*ra.y / Ia + rb.x*rb.y / Ib);
double Jy = - (e+1)/k * (Vai.x - Vbi.x) (ra.x*ra.y / Ia + rb.x*rb.y / Ib)
+ (e+1)/k * (Vai.y - Vbi.y) ( 1/ma - ra.y*ra.y/Ia + 1/mb - rb.y*rb.y/Ib);
Vaf.x = Vai.x - Jx/Ma;
Vaf.y = Vai.y - Jy/Ma;
Vbf.x = Vbi.x - Jx/Mb;
Vbf.y = Vbi.y - Jy/Mb;
waf.x = wai.x - (Jx*ra.y - Jy*ra.x) /Ia;
waf.y = wai.y - (Jx*ra.y - Jy*ra.x) /Ia;
wbf.x = wbi.x - (Jx*rb.y - Jy*rb.x) /Ib;
wbf.y = wbi.y - (Jx*rb.y - Jy*rb.x) /Ib;
}
I agree with provided answers, they are very good.
I just want to point you a small pitfall: if the speed of balls is high, you can just miss the collision, because circles never intersect for given steps.
The solution is to solve the equation on the movement and to find the correct moment of the collision.
Anyway, if you would implement your solution (comparisons on X and Y axes) you'd get the good old ping pong! http://en.wikipedia.org/wiki/Pong
:)
The point at which they collide is on the line between the midpoints of the two circles, and its distance from either midpoint is the radius of that respective circle.

Resources