I am searching for a way to calculate the surface under a polygon.
The thing I want to accomplish is that a user that uses my program, can create a polygon to mark out his property. Now I want to know what the surface area is so I can tell the user how big his property is.
Unit m² or km² or hectare.
The points of the polygon have a latitude and longitude.
I am using C# with WPF and GMap.NET. The map is in a WindowsFormHost so I can use the Winforms thing from GMap.Net because this provoides overlays etc.
I hope that someone can help me or show me a post where this is explained that I didn't found.
Using a 2D vector space approximation (local tangent space)
In this section, I can detail how I come to these formulas.
Let's note Points the points of the polygon (where Points[0] == Points[Points.Count - 1] to close the polygon).
The idea behind the next methods is to split the polygon into triangles (the area is the sum of all triangle areas). But, to support all polygon types with a simple decomposition (not only star-shaped polygon), some triangle contributions are negative (we have a "negative" area). The triangles decomposition I use is : {(O, Points[i], Points[i + 1]} where O is the origin of the affine space.
The area of a non-self-intersecting polygon (in euclidian geometry) is given by:
In 2D:
float GetArea(List<Vector2> points)
{
float area2 = 0;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
area2 += point.x * nextPoint.y - point.y * nextPoint.x;
}
return area2 / 2f;
}
In 3D, given normal, the unitary normal of the polygon (which is planar):
float GetArea(List<Vector3> points, Vector3 normal)
{
Vector3 vector = Vector3.Zero;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
vector += Vector3.CrossProduct(point, nextPoint);
}
return (1f / 2f) * Math.Abs(Vector3.DotProduct(vector, normal));
}
In the previous code I assumed you have a Vector3 struct with Add, Subtract, Multiply, CrossProduct and DotProduct operations.
In your case, you have a lattitude and longitude. Then, you are not in an 2D euclidean space. It is a spheric space where computing the area of any polygon is much more complex.
However, it is locally homeomorphic to a 2D vector space (using the tangent space).
Then, if the area you try to measure is not too wide (few kilometers), the above formula should work.
Now, you just have to find the normal of the polygon. To do so, and to reduce the error (because we are approximating the area), we use the normal at the centroid of the polygon. The centroid is given by:
Vector3 GetCentroid(List<Vector3> points)
{
Vector3 vector = Vector3.Zero;
Vector3 normal = Vector3.CrossProduct(points[0], points[1]); // Gets the normal of the first triangle (it is used to know if the contribution of the triangle is positive or negative)
normal = (1f / normal.Length) * normal; // Makes the vector unitary
float sumProjectedAreas = 0;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
float triangleProjectedArea = Vector3.DotProduct(Vector3.CrossProduct(point, nextPoint), normal);
sumProjectedAreas += triangleProjectedArea;
vector += triangleProjectedArea * (point + nextPoint);
}
return (1f / (6f * sumProjectedAreas)) * vector;
}
I've added a new property to Vector3 : Vector3.Length
Finally, to convert latitude and longitude into a Vector3:
Vector3 GeographicCoordinatesToPoint(float latitude, float longitude)
{
return EarthRadius * new Vector3(Math.Cos(latitude) * Math.Cos(longitude), Math.Cos(latitude) * Math.Sin(longitude), Math.Sin(latitude));
}
To sum up:
// Converts the latitude/longitude coordinates to 3D coordinates
List<Vector3> pointsIn3D = (from point in points
select GeographicCoordinatesToPoint(point.Latitude, point.Longitude))
.ToList();
// Gets the centroid (to have the normal of the vector space)
Vector3 centroid = GetCentroid(pointsIn3D );
// As we are on a sphere, the normal at a given point is the colinear to the vector going from the center of the sphere to the point.
Vector3 normal = (1f / centroid.Length) * centroid; // We want a unitary normal.
// Finally the area is computed using:
float area = GetArea(pointsIn3D, normal);
The Vector3 struct
public struct Vector3
{
public static readonly Vector3 Zero = new Vector3(0, 0, 0);
public readonly float X;
public readonly float Y;
public readonly float Z;
public float Length { return Math.Sqrt(X * X + Y * Y + Z * Z); }
public Vector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public static Vector3 operator +(Vector3 vector1, Vector3 vector2)
{
return new Vector3(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
}
public static Vector3 operator -(Vector3 vector1, Vector3 vector2)
{
return new Vector3(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
}
public static Vector3 operator *(float scalar, Vector3 vector)
{
return new Vector3(scalar * vector.X, scalar * vector.Y, scalar * vector.Z);
}
public static float DotProduct(Vector3 vector1, Vector3 vector2)
{
return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
}
public static Vector3 CrossProduct(Vector3 vector1, Vector3 vector2)
{
return return new Vector3(vector1.Y * vector2.Z - vector1.Z * vector2.Y,
vector1.Z * vector2.X - vector1.X * vector2.Z,
vector1.X * vector2.Y - vector1.Y * vector2.X);
}
}
I fixed it with part of the code from Cédric and code from the internet.
I fixed it by using poly.Points and poly.LocalPoints.
The poly.Points are the latitude and longitude while the LocalPoints are points see to the center of the map on the screen.
the C# library has a function to calculate the distance (km) so I calculted the distance and then I calculated the distance in LocalPoints. Dived the localPoints throug the length in km and then you know how long 1 Localpoint is in km.
Code:
List<PointLatLng> firstTwoPoints = new List<PointLatLng>();
firstTwoPoints.Add(poly.Points[0]);
firstTwoPoints.Add(poly.Points[1]);
GMapPolygon oneLine = new GMapPolygon(firstTwoPoints,"testesddfsdsd"); //Create new polygone from messuring the distance.
double lengteLocalLine =
Math.Sqrt(((poly.LocalPoints[1].X - poly.LocalPoints[0].X)*(poly.LocalPoints[1].X - poly.LocalPoints[0].X)) +
((poly.LocalPoints[1].Y - poly.LocalPoints[0].Y)*(poly.LocalPoints[1].Y - poly.LocalPoints[0].Y))); //This calculates the length of the line in LocalPoints.
double pointInKm = oneLine.Distance / lengteLocalLine; //This gives me the length of 1 LocalPoint in km.
List<Carthesian> waarden = new List<Carthesian>();
//Here we fill the list "waarden" with the points.
//NOTE: the last value is NOT copied because this is handled in calculation method.
foreach (GPoint localPoint in poly.LocalPoints)
{
waarden.Add(new Carthesian(){X = (localPoint.X * pointInKm), Y = (localPoint.Y * pointInKm)});
}
MessageBox.Show("" + GetArea(waarden)*1000000);
}
//Method for calculating area
private double GetArea(IList<Carthesian> points)
{
if (points.Count < 3)
{
return 0;
}
double area = GetDeterminant(points[points.Count - 1].X , points[points.Count - 1].Y, points[0].X, points[0].Y);
for (int i = 1; i < points.Count; i++)
{
//Debug.WriteLine("Lng: " + points[i].Lng + " Lat:" + points[i].Lat);
area += GetDeterminant(points[i - 1].X, points[i - 1].Y , points[i].X, points[i].Y);
}
return Math.Abs(area / 2);
}
//Methode for getting the Determinant
private double GetDeterminant(double x1, double y1, double x2, double y2)
{
return x1 * y2 - x2 * y1;
}
//This class is just to make it nicer to show in code and it also was from previous tries
class Carthesian
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
Because we calculate the surface using 2D there is a small error, but for my application this is acceptable.
And thanks to Cédric for answering my question and helping me to fix the problem I had.
Its much easier to just use a backend database like SQL Server 2008 or MySql, sending the points of the polygon to the server in a query and return area, length, parimeter, intersection...etc.
If this is viable, search STArea() or STIntersect on Sql Server geography/geometry datatypes.
here is an example of something I have been working on.
using Microsoft.SqlServer.Types;
using System.Data.SqlClient;
GMap.NET.WindowsForms.GMapOverlay o = new GMapOverlay();
GMap.NET.WindowsForms.GMapOverlay o1 = new GMapOverlay();
List<PointLatLng> p = new List<PointLatLng>();
List<string> p1 = new List<string>();
//below adds a marker to the map upon each users click. At the same time adding that Lat/Long to a <PointLatLng> list
private void gMapControl1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right )
{
p.Add(new PointLatLng(Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat), Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng)));
p1.Add(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat);
GMarkerGoogle marker = new GMarkerGoogle(gMapControl2.FromLocalToLatLng(e.X, e.Y), GMarkerGoogleType.red_small);
// marker.Tag =(gMapControl1.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl1.FromLocalToLatLng(e.X, e.Y).Lat);
o.Markers.Add(marker);
gMapControl2.Overlays.Add(o);
}
}
//Then the below code will add that <PointLatLng> List to a SQL query and return Area and Centoid of polygon. Area is returned in Acres
private void gMapControl1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
try
{
o.Clear();
n = new GMapPolygon(p, "polygon");
n.Fill = new SolidBrush(Color.Transparent);
n.Stroke = new Pen(Color.Red, 1);
o.Polygons.Add(n);
gMapControl2.Overlays.Add(o);
StringBuilder a = new StringBuilder();
StringBuilder b = new StringBuilder();
p1.ToArray();
for (int i = 0; i != p1.Count; i++)
{
a.Append(p1[i].ToString() + ",");
}
a.Append(p1[0].ToString());
cs.Open();
SqlCommand cmd = new SqlCommand("Declare #g geography; set #g = 'Polygon((" + a + "))'; Select Round((#g.STArea() *0.00024711),3) As Area", cs);
SqlCommand cmd1 = new SqlCommand("Declare #c geometry; set #c =geometry::STGeomFromText('Polygon((" + a + "))',0); Select Replace(Replace(#c.STCentroid().ToString(),'POINT (',''),')','')AS Center", cs);
poly = "Polygon((" + a + "))";
SqlDataReader sdr = cmd.ExecuteReader();
while (sdr.Read())
{
txtArea.Text = sdr["Area"].ToString();
}
sdr.Close();
SqlDataReader sdr1 = cmd1.ExecuteReader();
while (sdr1.Read())
{
center = sdr1["Center"].ToString();
lat = center.Substring(center.IndexOf(" ") + 1);
lat = lat.Remove(9);
lon = center.Substring(0, (center.IndexOf(" ")));
lon = lon.Remove(10);
txtCenter.Text = lat + ", " + lon;
}
sdr1.Close();
}
catch (Exception ex)
{
MessageBox.Show("Please start the polygon over, you must create polygon in a counter-clockwise fasion","Counter-Clockwise Only!",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
finally
{
p.Clear();
p1.Clear();
cs.Close();
o.Markers.Clear();
}
}
}
Related
I'm able to point zoom on the Mandelbrot set, as long as the mouse doesn't move after zooming has begun. I've tried calculating a normalized delta (new coordinate - old coordinate)*(oldzoom), but what happens is the image appears to jump around to a new location. I've seen this issue before. I'm struggling more here because I have to somehow convert this mouse position delta back to the -2,2 coordinate space of the Mandelbrot set.
Here's my code. What's important is the GetZoomPoint method, and then the lines of code that define x0 and y0. Also, I use the Range class to scale values from one range to another. I WAS using deltaTrans (thats the thing I was talking about earlier where I normalize the mouse delta with the old scale).
using OpenTK.Graphics.OpenGL;
using SpriteSheetMaker;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Fractal.Fractal
{
public class Mandelbrot : BaseTexture
{
private static Transform GlobalTransform = SpriteSheetMaker.Global.Transform;
private static Vector3 GlobalScale = GlobalTransform.Scale;
private static Vector3 GlobalTrans = GlobalTransform.Translation;
private static Vector3 LastWindowPoint = null;
private static Vector3 ZoomFactor = Vector3.ONE * 1.2f;
private static Vector3 Displacement = Vector3.ZERO;
private static int WindowSize = 100;
public static Vector3 GetZoomPoint()
{
var zP = OpenGLHelpers.LastZoomPoint.Clone();
if (LastWindowPoint == null)
{
LastWindowPoint = zP.Clone();
}
var delta = zP - LastWindowPoint;
var oldZoom = GlobalScale / ZoomFactor;
var deltaTrans = delta.XY * oldZoom.XY;
var factor = ZoomFactor.Clone();
Range xR = new Range(0, WindowSize);
Range yR = new Range(0, WindowSize);
Range complexRange = new Range(-2, 2);
// Calculate displacement of zooming position.
var dx = (zP.X - Displacement.X) * (factor.X - 1f);
var dy = (zP.Y - Displacement.Y) * (factor.Y - 1f);
// Compensate for displacement.
Displacement.X -= dx;
Displacement.Y -= dy;
zP -= Displacement;
var x = complexRange.ScaleValue(zP.X, xR);
var y = complexRange.ScaleValue(zP.Y, yR);
var rtn = new Vector3(x, y);
LastWindowPoint = zP.Clone();
return rtn;
}
public static Mandelbrot Generate()
{
var size = new Size(WindowSize, WindowSize);
var radius = new Size(size.Width / 2, size.Height / 2);
Bitmap bmp = new Bitmap(size.Width, size.Height);
LockBitmap.LockBitmapUnsafe lbm = new LockBitmap.LockBitmapUnsafe(bmp);
lbm.LockBits();
var pt = Mandelbrot.GetZoomPoint();
Parallel.For(0, size.Width, i =>
{
// float x0 = complexRangeX.ScaleValue(i, xRange);
float x0 = ((i - radius.Width) / GlobalScale.X) + pt.X;
Parallel.For(0, size.Height, j =>
{
// float y0 = complexRangeY.ScaleValue(j, yRange);
float y0 = ((j - radius.Height) / GlobalScale.Y) + pt.Y;
float value = 0f;
float x = 0.0f;
float y = 0.0f;
int iteration = 0;
int max_iteration = 100;
while (x * x + y * y <= 4.0 && iteration < max_iteration)
{
float xtemp = x * x - y * y + x0;
y = 2.0f * x * y + y0;
x = xtemp;
iteration += 1;
if (iteration == max_iteration)
{
value = 255;
break;
}
else
{
value = iteration * 50f % 255f;
}
}
int v = (int)value;
lbm.SetPixel(i, j, new ColorLibrary.HSL(v / 255f, 1.0, 0.5).ToDotNetColor());
});
});
lbm.UnlockBits();
var tex = new BaseTextureImage(bmp);
var rtn = new Mandelbrot(tex);
return rtn;
}
public override void Draw()
{
base._draw();
}
private Mandelbrot(BaseTextureImage graphic)
{
var topLeft = new Vector3(0, 1);
var bottomLeft = new Vector3(0, 0);
var bottomRight = new Vector3(1, 0);
var topRight = new Vector3(1, 1);
this.Vertices = new List<Vector3>()
{
topLeft,bottomLeft,bottomRight,topRight
};
this.Size.X = WindowSize;
this.Size.Y = WindowSize;
this.Texture2D = graphic;
}
}
}
I refactored my code, and also figured out a solution to this problem. 2 big wins in one. Ok, so I found a solution on CodeProject written in C# which I was readily able to adapt to my project. I'm not sure why I didn't realize this when I posted the question, but what I needed to solve this issue was to create a 'window' of zoom and not think in terms of a 'point zoom'. Yes, even if I am trying to zoom directly into a point, that point is just the center of some sort of a window.
Here is the method I have, which expects start and end mousedown coordinates (screen space), and converts the mandelbrot set window size accordingly.
public void ApplyZoom(double x0, double y0, double x1, double y1)
{
if (x1 == x0 && y0 == y1)
{
//This was just a click, no movement occurred
return;
}
/*
* XMin, YMin and XMax, YMax are the current extent of the set
* mx0,my0 and mx1,my1 are the part we selected
* do the math to draw the selected rectangle
* */
double scaleX, scaleY;
scaleX = (XMax - XMin) / (float)BitmapSize;
scaleY = (YMax - YMin) / (float)BitmapSize;
XMax = (float)x1 * scaleX + XMin;
YMax = (float)y1 * scaleY + YMin;
XMin = (float)x0 * scaleX + XMin;
YMin = (float)y0 * scaleY + YMin;
this.Refresh(); // force mandelbrot to redraw
}
Basically, whats happening is we calculate the ratio between the mandelbrot window size versus the screen size we are drawing to. Then, using that scale, we basically convert our mousedown coordinates to mandelbrot set coordinates (x1*scaleX, etc) and manipulate the current Min and Max coordinates with them, using the Min values as the pivot point.
Here's the link to the CodeProject I used as a reference: CodeProject link
I've been trying to recode a C++ DirectX code to C# that would help me with Drawing a perfect circle. Currently I have this code that i translated by myself:
private void Circle(int X, int Y, int radius, int numSides, Color color)
{
Vector2[] Line = new Vector2[128];
float Step = (float)(Math.PI * 2.0 / numSides);
int Count = 0;
for (float a = 0; a < Math.PI * 2.0; a += Step)
{
float X1 = (float)(radius * Math.Cos(a) + X);
float Y1 = (float)(radius * Math.Sin(a) + Y);
float X2 = (float)(radius * Math.Cos(a + Step) + X);
float Y2 = (float)(radius * Math.Sin(a + Step) + Y);
Line[Count].X = X1;
Line[Count].Y = Y1;
Line[Count + 1].X = X2;
Line[Count + 1].Y = Y2;
Count += 2;
}
line.Begin();
line.Draw(Line, color);
line.End();
}
The problem is that the circle is drawn but also a Line from a point in the circle to the left top corner, like this.
Don't iterate with a floating point variable. They might get imprecise during the iteration. In your case, the last step is probably very close behind the upper bound (instead of hitting it exactly). So it won't get calculated and left as the default (0, 0).
So use an integer iteration variable:
for (int i = 0; i < numSides; ++i)
{
float a = i * Step;
...
}
Then, you can also get rid of Count.
Furthermore, you should make your coordinate buffer dynamic:
Vector2[] Line = new Vector2[2 * numSides];
I have a problem with coding a function that will do the following:
Function will take 2 sets of coordinates (x,y,z). First set is starting point and second one is end point.
Now first i will have to determine distance between those two points. If distance will be greater than lets say 100 then i will have to calculate a temporary point. I will then calculate distance between first set of coordinates and this temporary point. Please check illustration below i am sure things will be much more understandable.
Function that calculates new point between points A,B:
x = (x1+x2/2)
y = (y1+y2/2)
z = (z1+z2/2)
Function that calculates distance is:
public float DistanceTo(float x, float y, float z, float x2, float y2, float z2)
{
float a = x - x2;
float b = y - y2;
float c = z - z2;
return Math.Sqrt(a * a + b * b + c * c);
}
Link to illustration:
Perhaps my approach will be CPU heavy and perhaps slow (?) but currently i am out of ideas how to approach the problem.
Basically i need a function that will go from A to B in increments if (distance < 100).
Thank you for reading and thanks for any solution posted!
PS please dont make fun of my paint skills :)
Compute the distance d from (xb,yb,zb) (begin) to (xe,ye,ze) (end). Compute the number of parts N=(int)((d-1)/100)+1. Compute
xk = xb + (k*(xe-xb))/N
yk = yb + (k*(ye-yb))/N
zk = zb + (k*(ze-zb))/N
for k=1,...,N-1 to get equally spaced points on the segment with a distance not surpassing 100.
You can do it recursively
create a Point struct that represent a point in R3, with 2 utility methods to calculate the distance and midpoint.
struct Point
{
public double x;
public double y;
public double z;
public double Distance(Point b)
{
return Math.Sqrt(Math.Pow(b.x - this.x, 2) +
Math.Pow(b.y - this.y, 2) +
Math.Pow(b.z - this.z, 2));
}
public Point MidPoint(Point b)
{
return new Point()
{
x = (this.x + b.x) / 2,
y = (this.y + b.y) / 2,
z = (this.z + b.z) / 2
};
}
}
and write a simple recursive function that will calculate the distance and call itself recursively until the distance between point a and the temp point is less than maxSegmentLength which is 100 in your case:
public static Point GetPoint(Point a, Point b, double maxSegmentLength)
{
var distance = a.Distance(b);
if (distance < maxSegmentLength)
return b;
else
return GetPoint(a, a.MidPoint(b),maxSegmentLength);
}
Or more efficiently with vectors:
struct Vector
{
public double Vx;
public double Vy;
public double Vz;
public double R;
public Vector(Point a,Point b)
{
R = a.Distance(b);
Vx = (b.x - a.x)/R;
Vy = (b.y - a.y)/R;
Vz = (b.z - a.z)/R;
}
}
public static Point GetPoint(Point a, Point b,double maxSegmentLength)
{
var Vab = new Vector(a, b);
var dAC = Vab.R;
while (dAC > maxSegmentLength) { dAC /= 2; } //or replace this line and the one above it with var dAC=Math.Pow(0.5,(int)(-(Math.Log(maxSegmentLength / Vab.R) / Math.Log(2))) + 1)*Vab.R;
return new Point() {
x = a.x + Vab.Vx * dAC ,
y = a.y + Vab.Vy * dAC ,
z = a.z + Vab.Vz * dAC
};
}
If the point is always at a midpoint then its distance is always half of the original
public double Calc(Point A, Point B)
{
double d = Distance(A,B);
while(d>100)
{
d/=2;
}
return d;
}
I have a simulation with multiple circles moving in 2D space, with elastic collisions between them.
I'd like to add an attraction force between particles, so that particles move towards other particles depending on mass, etc. How would I go about this?
My collision management function looks like this:
void manageCollision(Particle particleA, Particle particleB)
{
float distanceX = particleA.Position.X - particleB.Position.X;
float distanceY = particleA.Position.Y - particleB.Position.Y;
double collisionAngle = Math.Atan2(distanceY, distanceX);
double pA_magnitude = Math.Sqrt(particleA.Velocity.X * particleA.Velocity.X + particleA.Velocity.Y * particleA.Velocity.Y);
double pB_magnitude = Math.Sqrt(particleB.Velocity.X * particleB.Velocity.X + particleB.Velocity.Y * particleB.Velocity.Y);
double pA_direction = Math.Atan2(particleA.Velocity.Y, particleA.Velocity.X);
double pB_direction = Math.Atan2(particleB.Velocity.Y, particleB.Velocity.X);
double pA_newVelocityX = pA_magnitude * Math.Cos(pA_direction - collisionAngle);
double pA_newVelocityY = pA_magnitude * Math.Sin(pA_direction - collisionAngle);
double pB_newVelocityX = pB_magnitude * Math.Cos(pB_direction - collisionAngle);
double pB_newVelocityY = pB_magnitude * Math.Sin(pB_direction - collisionAngle);
double pA_finalVelocityX = ((particleA.Mass - particleB.Mass) * pA_newVelocityX + (particleB.Mass + particleB.Mass) * pB_newVelocityX) / (particleA.Mass + particleB.Mass);
double pB_finalVelocityX = ((particleA.Mass + particleA.Mass) * pA_newVelocityX + (particleB.Mass - particleA.Mass) * pB_newVelocityX) / (particleA.Mass + particleB.Mass);
double pA_finalVelocityY = pA_newVelocityY;
double pB_finalVelocityY = pB_newVelocityY;
particleA.Velocity = new Vector2((float)(Math.Cos(collisionAngle) * pA_finalVelocityX + Math.Cos(collisionAngle + Math.PI / 2) * pA_finalVelocityY), (float)(Math.Sin(collisionAngle) * pA_finalVelocityX + Math.Sin(collisionAngle + Math.PI / 2) * pA_finalVelocityY));
particleB.Velocity = new Vector2((float)(Math.Cos(collisionAngle) * pB_finalVelocityX + Math.Cos(collisionAngle + Math.PI / 2) * pB_finalVelocityY), (float)(Math.Sin(collisionAngle) * pB_finalVelocityX + Math.Sin(collisionAngle + Math.PI / 2) * pB_finalVelocityY));
}
Each ball or particle spawns with a random mass and radius.
The function is called within an update type of method, like this:
Vector2 globalGravity = new Vector2(0f, gravityScale / 6000);
for (int i = 0; i < particles.Count(); i++)
{
particles[i].Update((float)updateTimer.Interval, globalGravity);
Vector2 position = particles[i].Position;
Vector2 velocity = particles[i].Velocity;
collisionWallCheck(ref position, ref velocity, particles[i].Radius);
particles[i].Position = position;
particles[i].Velocity = velocity;
Particle pA = particles[i];
for (int k = i + 1; k < particles.Count(); k++)
{
Particle pB = particles[k];
Vector2 delta = pA.Position - pB.Position;
float dist = delta.Length();
if (dist < particles[i].Radius + particles[k].Radius && !particles[i].Colliding && !particles[k].Colliding)
{
particles[i].Colliding = true;
particles[k].Colliding = true;
manageCollision(particles[i], particles[k]);
particles[i].initColorTable(); // Upon collision, change the color
particles[k].initColorTable();
totalCollisions++;
}
else
{
particles[i].Colliding = false;
particles[k].Colliding = false;
}
}
}
I'm storing the initial position, velocity and masses of each ball.
What I apparently need to do, and don't know how to implement, is:
Calculate the magnitude and direction of the gravitational force.
Knowing the force, you can calculate the acceleration of each body.
Knowing the acceleration you can calculate the new velocity.
Knowing the velocity you can calculate the new position.
I'm shaky with the equations for it essentially, and I'd like to start off by making an attraction force between just two balls.
Using Steven's suggestion, this is the new integrated code.
void updateTimer_Tick(object sender, EventArgs e)
{
const double G = 6.67398 * 0.00000000001;
for (int i = 0; i < particles.Count(); i++)
{
double sumX = 0;
double sumY = 0;
Particle pA = particles[i];
for (int k = i + 1; k < particles.Count(); k++)
{
Particle pB = particles[k];
Vector2 delta = pA.Position - pB.Position;
float dist = delta.Length();
if (dist < particles[i].Radius + particles[k].Radius && !particles[i].Colliding && !particles[k].Colliding)
{
particles[i].Colliding = true;
particles[k].Colliding = true;
manageCollision(particles[i], particles[k]);
particles[i].initColorTable();
particles[k].initColorTable();
totalCollisions++;
particles[i].Colliding = false;
particles[k].Colliding = false;
}
else
{
double distanceX = particles[i].Position.X - particles[k].Position.X;
double distanceY = particles[i].Position.Y - particles[k].Position.Y;
double r = Math.Sqrt(Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2));
double force = G * particles[i].Mass * particles[k].Mass / (r * r);
double theta = Math.Tan(distanceY / distanceX);
sumX += force * Math.Cos(theta);
sumY += force * Math.Sin(theta);
particles[i].Colliding = false;
particles[k].Colliding = false;
}
}
double netForce = Math.Sqrt(Math.Pow(sumX, 2) + Math.Pow(sumY, 2));
double a = netForce / particles[i].Mass;
double aTheta = Math.Tan(sumY / sumX);
// Here we get accelerations for X and Y. You can probably figure out velocities from here.
double aX = a * Math.Cos(aTheta);
double aY = a * Math.Sin(aTheta);
Vector2 accel = new Vector2((float)aX, (float)aY);
particles[i].Update((float)updateTimer.Interval, accel);
//particles[i].Update((float)updateTimer.Interval, globalGravity);
Vector2 position = particles[i].Position;
Vector2 velocity = particles[i].Velocity;
collisionWallCheck(ref position, ref velocity, particles[i].Radius);
particles[i].Position = position;
particles[i].Velocity = velocity + accel;
}
Draw();
}
The Update function for the particles is simple, and before it used a global gravity Vector which was 0,0.
public void Update(float timeStep, Vector2 gravity)
{
velocity = velocity + timeStep * gravity;
position = position + timeStep * velocity;
}
I'm now unsure how to deal with the cases of 0.
Start by calculating the force of gravity acting on each object. This is given by
F = Gm1m2/r*r
where m1 and m2 are the masses of two objects, G is the gravitational constant, and r is the distance between the two objects.
Now, r is a vector, so you may want to split this up into separate components - Fx and Fy. You can do this as follows:
Fx = F * cos(theta)
Fy = F * sin(theta)
For each mass, calculate the force of gravity acting on it and every other object. Sum the vectors to get the net force of gravity. (Note - that link is available for your interest, but takes a long time to get to the point). At this point you will have a net force on each object, from which you can calculate acceleration. Here's the code to get to this point:
const double G = 6.67398 * 0.00000000001;
for (int i = 0; i < particles.Count(); i++)
{
double sumX = 0;
double sumY = 0;
for (int j = 0; j < particles.Count(); j++)
{
// Don't add attraction to self
if (i == j)
continue;
double distanceX = particles[i].Position.X - particles[j].Position.X;
double distanceY = particles[i].Position.Y - particles[j].Position.Y;
double r = Math.Sqrt(Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2));
double force = G * particles[i].Mass * particles[j].Mass / (r * r);
double theta = Math.Tan(distanceY / distanceX);
sumX += force * Math.Cos(theta);
sumY += force * Math.Sin(theta);
}
double netForce = Math.Sqrt(Math.Pow(sumX, 2) + Math.Pow(sumY, 2));
double a = netForce / particles[i].Mass;
double aTheta = Math.Tan(sumY / sumX);
// Here we get accelerations for X and Y. You can probably figure out velocities from here.
double aX = a * Math.Cos(aTheta);
double aY = a * Math.Sin(aTheta);
}
NOTES
This doesn't take stuff like 0-values into account - you'll have to clean up this code to deal with special cases before it will run without crashing.
Don't update any positions until you've calculated all the forces, or else you'll be off for later elements in the list.
Another thing worth noting: This algorithm is O(n^2), so if you have more than a few bodies it's going to take a lot of crunching. That's unfortunately just the way it is; if you find a fast way to calculate gravitational attraction for a large number of bodies, you should probably call NASA.
Depending on your coordinate system, you may find the y-vectors getting reversed. This is because Euclidean geometry thinks of positive values of y as "going up" whereas programmers tend to measure y in positive units "going down" from the top of the screen. This can play havoc with your angles and things.
Knowing the position all the balls and their masses, you can calculate the vector of the force felt between any two bodies. Find the vector from ball 'A' to all other balls - 'A' to ball 'B', 'A' to 'C', 'A' to 'D', etc. Then, simple add all of A's vectors up to get a final vector of force acting on A. Repeat for B -> A, B -> C, etc to find B's vector. Do this for all, calculate the new velocity, and adjust the positions for the amount of time between steps.
I am developing a small program, and to generate the coordenates I am using this code:
public double[] GenerateNewCircle(int maxSize, int numberOfBalls, double centerx, double centery)
{
double[] position = null;
double angle = 360 / 6;
double angleRad = DegreeToRadian(incrementAngle);
position = CalculatePosition(radiusX, radiusY, centerx, centery, angleRad);
return position;
}
public double[] CalculatePosition(double radiusX, double radiusY, double centerX, double centerY, double angle)
{
double[] position = new double[2];
position[0] = Math.Cos(angle) * (radiusX + centerX)*1.5;
position[1] = Math.Sin(angle) * (radiusY + centerY)*1.5;
return position;
}
To check if not overlapping or if is in the area I used this
bool circleIsAllowed(List<Circle> circles, Circle newCircle)
{
if (newCircle.x < 10 || newCircle.x > pictureBox1.Width)
return false;
if (newCircle.y < 9 || newCircle.y > pictureBox1.Height)
return false;
foreach (Circle it in circles)
{
double aX = Math.Pow(it.x - newCircle.x, 2);
double aY = Math.Pow(it.y - newCircle.y, 2);
double Dif = Math.Abs(aX - aY);
double ra1 = it.r / 2;
double ra2 = it.r / 2;
double raDif = Math.Pow(ra1 + ra2, 2);
if ((raDif + 1) > Dif) return false;
}
return true; // no existing circle overlaps
}
I have two problems right now
It blocks because is also trying to generate a circle around it, when is a space near.
How can I increase the distance between the circles? They are too close.
This circles is to draw around a circle (around it)
This is the image:
There is mistake in second code block. Try this:
double aX = Math.Pow(it.x - newCircle.x, 2);
double aY = Math.Pow(it.y - newCircle.y, 2);
double distance = Math.Pow(aX + aY, 0.5); // distance between circles centers
double ra1 = it.r / 2;
double ra2 = it.r / 2;
// is it.r diameter? if so, then ok, else div by 2 is useless
double circlesDistance = 10; // distance between circles (not centers)
if (distance < ra1 + ra2 + circlesDistance)
{
return false;
}
To compute the distance between two circle centers, you should apply the Pythagorean Theorem:
a2 + b2 = c2.
This works perfectly if we have circles (with ellipses you'd have to compute their radius along the connection between their centres which would involve some sinus and cosinus computations).
double aX = Math.Pow(it.x - newCircle.x, 2);
double aY = Math.Pow(it.y - newCircle.y, 2);
if ((aX + aY) <= Math.Pow(it.r + newCircle.r, 2)) {
return false;
}