C# and SFML game intersection - c#

I have problem with my MoveEntity() method in a 2D platformer game (like PacMan). When player is on intersection path I want to make:
e.IsAtIntersection = true;
and then call UpdateDirection().
But he doesn't stop, instead he goes through paths and it doesn't matter if there are walls or empty paths. The program never reaches the above line. The member stays false. Maybe there is the possibility to write it in an easier way or that there is a simple mistake I made. Here is the complete method:
private void MoveEntity(Entity e)
{
int angle = 0;
if (e.currentDirection == Direction.RIGHT) angle = 0;
else if (e.currentDirection == Direction.DOWN) angle = 90;
else if (e.currentDirection == Direction.LEFT) angle = 180;
else if (e.currentDirection == Direction.UP) angle = 270;
var scalex = Math.Round(Math.Cos(angle * (Math.PI / 180.0)));
var scaley = Math.Round(Math.Sin(angle * (Math.PI / 180.0)));
var velocityx = (float)(e.Speed * scalex);
var velocityy = (float)(e.Speed * scaley);
Sprite sp = sprites[e.Name];
Vector2f v = new Vector2f(sp.Position.X + velocityx, sp.Position.Y + velocityy);
sp.Position = v;
var eCenterX = sp.Position.X + sp.TextureRect.Width / 2;
var eCenterY = sp.Position.Y + sp.TextureRect.Height / 2;
var tileY = Math.Floor((eCenterY) / TILEHEIGHT);
var tileX = Math.Floor((eCenterX) / TILEWIDTH);
var tileXpos = TILEWIDTH * Math.Floor(tileX + 1);
var tileYpos = TILEHEIGHT * Math.Floor(tileY + 1);
e.X = (int)tileY;
e.Y = (int)tileX;
if (eCenterX == tileXpos && eCenterY == tileYpos)
e.IsAtIntersection = true;
else
e.IsAtIntersection = false;
}

The == operator, with floating points, checks for exact equality. Often even floats that have gone through very similar operations will still not be equal. When you compare equality on floats the best practice is to do the following:
if(Math.Abs(floatA-floatB) <= someSmallFloat)
someSmallFloat here is often set to float.Epsilon but in your case you may want to use a higher number to allow a margin for error, more fun to allow the player to move even if they are slightly off, than punish the player for very small inaccuracies.

It is very unlikely that the floating point addition for e.X and e.Y hits exactly the target value. You should check if the entity moved across the according line:
E.g. something like this for the rightwards movement:
if(e.currentDirection == Direction.RIGHT
&& eCenterX - velocityx< tileXpos && eCenterX >= eCenterX)
e.IsAtIntersection = true;
You can also correct the position if you like.
...
e.IsAtIntersection = true;
e.X = tileXpos;
You could also move it along the y-coordinate to compensate for the back-movement along the x-coordinate.

Here is for you my edited working code:
private void MoveEntity(Entity e)
{
int angle = 0;
if (e.currentDirection == Direction.RIGHT) angle = 0;
else if (e.currentDirection == Direction.DOWN) angle = 90;
else if (e.currentDirection == Direction.LEFT) angle = 180;
else if (e.currentDirection == Direction.UP) angle = 270;
var scalex = Math.Round(Math.Cos(angle * (Math.PI / 180.0)));
var scaley = Math.Round(Math.Sin(angle * (Math.PI / 180.0)));
var velocityx = (float)(e.Speed * scalex);
var velocityy = (float)(e.Speed * scaley);
Sprite sp = sprites[e.Name];
Vector2f v = new Vector2f(sp.Position.X + velocityx, sp.Position.Y + velocityy);
sp.Position = v;
var tileY = (int)sp.Position.Y / 60;
var tileX = (int)sp.Position.X / 60;
var tileXpos = tileX * 60;
var tileYpos = tileY * 60;
e.X = (int)tileY;
e.Y = (int)tileX;
if (sp.Position.X == tileXpos && sp.Position.Y == tileYpos)
e.IsAtIntersection = true;
else
e.IsAtIntersection = false;
}

Related

Mouse movement based on head rotation

I've a device which gives a quaternion data about the direction the device is facing and I want to use this data to move the mouse on-screen.
I've written the following code until now, but even when the device is idle (and I'm not getting major change in angle), I'm noticing mouse movement towards top-left
Setting the next position:
public void OnDataReceived(Quaternion quat)
{
var angle = GetAngle(quat.X, quat.Y, quat.Z, quat.W);
angle.Z = 0f;
var diff = angle - lastAngle;
lastAngle = angle;
var dtX = (int)(Math.Tan(diff.Y) * MoveMultiplier);
var dtY = (int)(Math.Sin(diff.X) * MoveMultiplier);
User32Wrapper.GetCursorPos(ref current);
next.x = Math.Clamp(current.x + dtX, 0, Width);
next.y = Math.Clamp(current.y + dtY, 0, Height);
isDirty = true;
}
Moving mouse (which is being called continuously):
private void MoveMouse(float deltaTime)
{
if (isDirty)
{
var dt = Speed * deltaTime;
var x = (int)Lerp(current.x, next.x, dt);
var y = (int)Lerp(current.y, next.y, dt);
if (x >= 0 && x < Width && y >= 0 && y < Height)
{
current.x = x;
current.y = y;
User32Wrapper.Move(x, y);
if (Math.Abs(current.x - next.x) < precision && Math.Abs(current.y - next.y) < precision)
{
isDirty = false;
User32Wrapper.Move(next.x, next.y);
User32Wrapper.GetCursorPos(ref current);
}
}
}
}
User32Wrapper.Move() is call to win32's mouse_event()
DLL_EXPORT void __cdecl Move(int x, int y) {
int _x = x * 65535 / GetSystemMetrics(0);
int _y = y * 65535 / GetSystemMetrics(1);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, _x, _y, 0, 0);
}
Am I missing something with mouse movement, any help would be appreciated.
Thanks

Get List<Segment> from a PathGeometry

I draw in a canvas several shapes. I have two kinds of shapes : Ellipse, and Path.
Now when I make a click on my Canvas, I want to get the nearest Shape.
I could manage to do something for Ellipse, but for Path I don't manage to find how to get its coordinates.
Here is the code I use to generate a List, in case something is not optimum in that method :
Concretely, "percage" are drillings, if Type=12, it means I draw a slot(Path). else I draw a circle(Ellipse)
if (percage.Type == 12)
{
double r = percage.Diametre / 2;
LineSegment ligne1 = new LineSegment();
LineSegment ligne2 = new LineSegment();
Point ptCentre = new Point(dx + percage.Coor_X, this.MyScrollViewer.ActualHeight * echelle - dy - percage.Coor_Y);
double angle = percage.AnglePer;
double xLeft = ptCentre.X - r;
double xRight = ptCentre.X + r;
double yUp = ptCentre.Y - ((percage.Longueur / 2) - r);
double yDown = ptCentre.Y + ((percage.Longueur / 2) - r);
Point pt1 = new Point(xLeft, yUp);
Point pt2 = new Point(xRight, yUp);
Point pt3 = new Point(xRight, yDown);
Point pt4 = new Point(xLeft, yDown);
pt1 = Global.RotatePoint(pt1, ptCentre, angle - 90);
pt2 = Global.RotatePoint(pt2, ptCentre, angle - 90);
pt3 = Global.RotatePoint(pt3, ptCentre, angle - 90);
pt4 = Global.RotatePoint(pt4, ptCentre, angle - 90);
Path arc_path1 = new Path();
arc_path1.Stroke = Brushes.Red;
arc_path1.StrokeThickness = 2;
PathGeometry pathGeometry = new PathGeometry();
ArcSegment arc1 = new ArcSegment();
ArcSegment arc2 = new ArcSegment();
PathFigure pathfigure1 = new PathFigure();
PathFigure pathfigure2 = new PathFigure();
arc1.Point = new Point(pt2.X, pt2.Y);
arc1.Point = new Point(pt4.X, pt4.Y);
pathfigure1.StartPoint = new Point(pt1.X, pt1.Y);
pathfigure1.StartPoint = new Point(pt3.X, pt3.Y);
SweepDirection sd = SweepDirection.Counterclockwise;
if (yUp < yDown)
{
sd = SweepDirection.Clockwise;
}
arc1.Size = new Size(r, r);
arc1.SweepDirection = sd;
arc2.Size = new Size(r, r);
arc2.SweepDirection = sd;
arc1.Point = pt2;
arc2.Point = pt4;
ligne1.Point = new Point(pt3.X, pt3.Y);
ligne2.Point = new Point(pt1.X, pt1.Y);
pathfigure1.StartPoint = new Point(pt1.X, pt1.Y);
pathfigure1.Segments.Add(arc1);
pathfigure1.Segments.Add(ligne1);
pathfigure1.Segments.Add(arc2);
pathfigure1.Segments.Add(ligne2);
pathGeometry.Figures.Add(pathfigure1);
arc_path1.Data = pathGeometry;
arc_path1.Tag = percage;
percage.ListShapes.Add(arc_path1);
}
else
{
Ellipse ellipse = new Ellipse();
ellipse.Stroke = System.Windows.Media.Brushes.Red;
ellipse.StrokeThickness = 1;
ellipse.Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
ellipse.Width = percage.Diametre;
ellipse.Height = percage.Diametre;
percage.Coor_X_Graph = X1 + dx - (percage.Diametre / 2);
percage.Coor_Y_Graph = this.MyScrollViewer.ActualHeight * echelle - (Y1 + dy) - (percage.Diametre / 2);
ellipse.Margin = new System.Windows.Thickness(percage.Coor_X_Graph, percage.Coor_Y_Graph, 0, 0);
ellipse.Tag = percage;
percage.ListShapes.Add(ellipse);
}
Then, to get the nearest shape, I began that code :
For ellipse I can retrieve its coordinates, but for Path, couldn't find the List of Segments inside.
StartPoint = e.GetPosition(monDessin);
double distance=-1;
Shape selectedShape = null;
for (int i = monDessin.Children.Count - 1; i > -1; i--)
{
if (monDessin.Children[i] is Ellipse)
{
Ellipse ell = (Ellipse)monDessin.Children[i];
double x = ell.Margin.Left + Width / 2;
double y = ell.Margin.Top - ell.Height / 2;
double dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y -y) * (StartPoint.Y - y));
if(distance==-1 || dist<distance)
{
distance = dist;
}
}
else if(monDessin.Children[i] is Path)
{
Path path=(Path)monDessin.Children[i];
Geometry geometry = path.Data;
foreach(PathFigure pf in ?????)
}
}
Finally, I could do it myself looking a bit more on internet (not sure it is the best way as I began on that theme,so any other suggestion is welcome)
I found some "solution" here
But the code didn't work, got an error on the following line (without understand why, neither what it does)
string value = seralizer.ConvertToString(geomerty, null);
Finally I adapted it looking on msdn website, I found geometry.GetOutlinedPathGeometry() and pathGeometry.Figures that allowed to get list of figures. I just don't understand why all my ArcSegment became BezierSegment.
Anyway, it works fine, so I put here the code :
StartPoint = e.GetPosition(monDessin);
double distance=-1;
double dist;
Shape selectedShape = null;
for (int i = monDessin.Children.Count - 1; i > -1; i--)
{
string type = monDessin.Children[i].GetType().ToString();
if (monDessin.Children[i] is Ellipse)
{
Ellipse ell = (Ellipse)monDessin.Children[i];
double x = ell.Margin.Left + ell.Width / 2;
double y = ell.Margin.Top - ell.Height / 2;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y -y) * (StartPoint.Y - y));
if(distance==-1 || dist<distance)
{
distance = dist;
}
}
else if(monDessin.Children[i] is Path)
{
Path path=(Path)monDessin.Children[i];
string titi = path.Tag.GetType().ToString();
Geometry geometry = path.Data;
PathGeometry pathGeometry = geometry.GetOutlinedPathGeometry();
PathFigureCollection figures = pathGeometry.Figures;
if (figures != null)
{
foreach (PathFigure figure in figures)
{
foreach (PathSegment segment in figure.Segments)
{
//first syntax : if(segment is LineSegment)
if(segment is LineSegment)
{
LineSegment lineSegment = (LineSegment)segment;
double x = lineSegment.Point.X;
ouble y = lineSegment.Point.Y;
}
//2nd syntax :
//ArcSegment arcSegment = segment as ArcSegment;
//Then check if not null
ArcSegment arcSegment = segment as ArcSegment;
if (arcSegment != null)
{
double x = arcSegment.Point.X;
double y = arcSegment.Point.Y;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y - y) * (StartPoint.Y - y));
if (distance == -1 || dist < distance)
{
distance = dist;
}
}
BezierSegment bezierSegment = segment as BezierSegment;
if (bezierSegment != null)
{
double x = bezierSegment.Point3.X;
double y = bezierSegment.Point3.Y;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y - y) * (StartPoint.Y - y));
if (distance == -1 || dist < distance)
{
distance = dist;
}
}
}
}
}
}
}
Nota : I found two different syntaxes to check the type of segment(i.e LineSegment), I don't know which is the best approach, the first syntax seems better, but the 2nd one has the advantage to check if it is null(even if in theory it may never happen???)

Point-Zoom on Mandelbrot Set in C# - It works, except when the mouse has moved

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

Visual C# method calls using too much memory?

I'm writing a game using the XNA 4.0 framework. I've written a set of methods that translates the 2D mouse coordinates to a line in the 3d world, then checks to see if that line intersects a plane, and if the intersection point is within the bounds of a face in that plane.
The math works, but for some reason when I do these calculations over 500 times a frame it brings the program to a halt. I can watch the memory usage climb from starting at 15 MB to about 130 MB before garbage collection decides to clean things up. I know specifically it is in this code because when I comment it out, everything else runs smoothly.
I'll paste my code below, any insight would be helpful and thank you!
The Loop:
GraphicObject me = new GraphicObject();
Intersection intersect;
double? dist = null;
foreach (GraphicObject obj in GraphicObjects)
{
intersect = obj.intersectMe(line);
if (intersect.Distance != null)
{
if (intersect.Distance < dist || dist == null)
{
dist = intersect.Distance;
me = obj;
}
else
{
obj.Highlight(false);
}
}
else
{
obj.Highlight(false);
}
}
if (dist != null)
{
me.Highlight(true);
}
intersectMe:
public override Intersection intersectMe(Ray _line)
{
GraphicHelper.Intersects(_line, rect.Vertices[0].Normal, rect.Vertices[0].Position, intersect);
if (intersect.Distance != null)
{
if (!rect.PointOnMe(intersect.X - position.X, intersect.Y - position.Y, intersect.Z - position.Z))
{
intersect.Distance = null;
}
}
return intersect;
}
GraphicsHelper.Intersects:
// _l = line, _n = normal to plane, _p = point on the plane
public static void Intersects(Ray _l, Vector3 _n, Vector3 _p, Intersection _i)
{
_i.Distance = null;
float num = (_n.X * (_p.X - _l.Position.X) + _n.Y * (_p.Y - _l.Position.Y) + _n.Z * (_p.Z - _l.Position.Z));
float denom = (_n.X * _l.Direction.X + _n.Y * _l.Direction.Y + _n.Z * _l.Direction.Z);
if (denom != 0 && num != 0)
{
float t = num / denom;
if (t > 0)
{
_i.X = _l.Position.X + _l.Direction.X * t;
_i.Y = _l.Position.Y + _l.Direction.Y * t;
_i.Z = _l.Position.Z + _l.Direction.Z * t;
_i.Distance = _i.X * _i.X + _i.Y * _i.Y + _i.Z * _i.Z;
}
}
}
PointOnMe:
public bool PointOnMe(float _x, float _y, float _z)
{
float ex = _x - Vertices[3].Position.X;
float ey = _y - Vertices[3].Position.Y;
float ez = _z - Vertices[3].Position.Z;
float ae = a.X * ex + a.Y * ey + a.Z * ez;
float be = b.X * ex + b.Y * ey + b.Z * ez;
ex = _x - Vertices[1].Position.X;
ey = _y - Vertices[1].Position.Y;
ez = _z - Vertices[1].Position.Z;
float ce = c.X * ex + c.Y * ex + c.Z * ez;
float de = d.X * ex + d.Y * ey + d.Z * ez;
if (ae > 0 && be > 0 && ce > 0 && de > 0)
{
return true;
}
else
{
return false;
}
}
Thank you all for taking some time to look at this for me. The error was actually in how I handle obj.Highlight(), TaW's kick in the butt to get a profiler setup helped me to figure that out.
public override void Highlight(bool toggle)
{
if(toggle)
{
rect.Texture = new Texture2D(GraphicsManager.Graphics.GraphicsDevice, 1, 1);
rect.Texture.SetData<Color>(new Color[] { Color.Yellow });
}
else
{
rect.Texture = new Texture2D(GraphicsManager.Graphics.GraphicsDevice, 1, 1);
rect.Texture.SetData<Color>(new Color[] { squareColor });
}
}
Every frame all the obj's were having new textures generated. A terrible way to do things.

Attraction Force between 2D balls

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.

Categories