How to do c# collision detection? - c#

Are there any predefined methods in c# which allow for collision detection?
I am new to c# and am trying to get collision detection of two ellipses are there any predefined ways collision detection can be implemented?
I already have code which draws the ellipses, what would be a good way to start the collision detection?
private void timer1_Tick(object sender, EventArgs e)
{
//Remove the previous ellipse from the paint canvas.
canvas1.Children.Remove(ellipse);
if (--loopCounter == 0)
timer.Stop();
//Add the ellipse to the canvas
ellipse = CreateAnEllipse(20, 20);
canvas1.Children.Add(ellipse);
Canvas.SetLeft(ellipse, rand.Next(0, 500));
Canvas.SetTop(ellipse, rand.Next(0, 310));
}
// Customize your ellipse in this method
public Ellipse CreateAnEllipse(int height, int width)
{
SolidColorBrush fillBrush = new SolidColorBrush() { Color = Colors.Yellow};
SolidColorBrush borderBrush = new SolidColorBrush() { Color = Colors.Black };
return new Ellipse()
{
Height = height,
Width = width,
StrokeThickness = 1,
Stroke = borderBrush,
Fill = fillBrush
};
}
this is the code to draw an ellipse which then gets removed and appears in another position.

I have tested this, it worked, at least for me
var x1 = Canvas.GetLeft(e1);
var y1 = Canvas.GetTop(e1);
Rect r1 = new Rect(x1, y1, e1.ActualWidth, e1.ActualHeight);
var x2 = Canvas.GetLeft(e2);
var y2 = Canvas.GetTop(e2);
Rect r2 = new Rect(x2, y2, e2.ActualWidth, e2.ActualHeight);
if (r1.IntersectsWith(r2))
MessageBox.Show("Intersected!");
else
MessageBox.Show("Non-Intersected!");

Would something like the following work?
var ellipse1Geom = ellipse1.RenderedGeometry;
var ellipse2Geom = ellipse2.RenderedGeometry;
var detail = ellipse1Geom.FillContainsWithDetail(ellipse2Geom);
if(detail != IntersectionDetail.Empty)
{
// We have an intersection or one contained inside the other
}
The Geometry.FillContainsWithDetail(Geometry) method is defined as
Returns a value that describes the intersection between the current geometry and the specified geometry.

I think you should definitely give a look at the XNA framework, it has loads of method to do collision detection.
Check out this other link on how to implement it manually in c# it might be helpful.

Provided that your Ellipses are always circles (i.e. their Width and Height properties are set to the same value) and they always have the Canvas.Left and Canvas.Top properties set, the following helper method checks for a collision:
public static bool CheckCollision(Ellipse e1, Ellipse e2)
{
var r1 = e1.ActualWidth / 2;
var x1 = Canvas.GetLeft(e1) + r1;
var y1 = Canvas.GetTop(e1) + r1;
var r2 = e2.ActualWidth / 2;
var x2 = Canvas.GetLeft(e2) + r2;
var y2 = Canvas.GetTop(e2) + r2;
var d = new Vector(x2 - x1, y2 - y1);
return d.Length <= r1 + r2;
}

Related

How can I fill the circular sector of an elliptic shape with a color gradient?

What I want to do is to create this rotating cone visual effect.
I had previously used DirectX for that.
What i have tried so far:
Even if I'm changing the thickness to 50 or more, the Arc is still not filled.
public partial class Form1 : Form
{
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var center = new Point(pictureBox1.Width / 2, pictureBox1.Height / 2);
var innerR = 30;
var thickness = 20;
var startAngle = 0;
var arcLength = 360;
var outerR = innerR + thickness;
var outerRect = new Rectangle
(center.X - outerR, center.Y - outerR, 2 * outerR, 2 * outerR);
var innerRect = new Rectangle
(center.X - innerR, center.Y - innerR, 2 * innerR, 2 * innerR);
using (var p = new GraphicsPath())
{
p.AddArc(outerRect, startAngle, arcLength);
p.AddArc(innerRect, startAngle + arcLength, -arcLength);
p.CloseFigure();
e.Graphics.FillPath(Brushes.Green, p);
e.Graphics.DrawPath(Pens.Green, p);
}
}
}
I want to be able to fill the arc even when the thickness is 20 or less.
Or when the value of the innerR radius changes.
The goal is to be able to fill the arc in any case.
Here's one method of drawing that cone.
It looks like a Radar sweep, so you may want to define the sweep angle and the rotation speed (how much the current rotation angle is increased based on the Timer's interval).
Using a standard System.Windows.Forms.Timer to invalidate the Canvas that contains the Image you're showing here.
The Radar contour (the external perimeter) is centered on the canvas and drawn in relation to the thickness specified (so it's always sized as the canvas bounds). It doesn't necessarily be a perfect circle, it can be elliptical (as in the image here)
The Cone section is drawn adding an Arc to a GraphicsPath and is closed drawing two lines, from the center point of the outer GraphicsPath to the starting and ending points of the Arc (I think this is a simple method to generate a curved conic figure, it can be used in different situations and lets you generate different shapes almost without calculations, see the code about this)
It's filled with a LinearGradientBrush, the section near the center has less transparency than the section near the border; adjust as required
Each time the rotation angle reaches 360°, it's reset to 0.
This is delegated to the Timer's Tick event handler
=> Built with .Net 7, but if you need to adapt it to .Net Framework, the only things to change are the syntax of the using blocks, remove the null-forgiving operator from here: canvas!.ClientRectangle and nullable reference types (e.g., change object? to just object)
public partial class SomeForm : Form {
public SomeForm() {
InitializeComponent();
radarTimer.Interval = 100;
radarTimer.Tick += RadarTimer_Tick;
}
float coneSweepAngle = 36.0f;
float coneRotationAngle = .0f;
float radarSpeed = 1.8f;
float radarThickness = 5.0f;
System.Windows.Forms.Timer radarTimer = new System.Windows.Forms.Timer();
private void RadarTimer_Tick(object? sender, EventArgs e) {
coneRotationAngle += radarSpeed;
coneRotationAngle %= 360.0f;
canvas.Invalidate();
}
private void canvas_Paint(object sender, PaintEventArgs e) {
var center = new PointF(canvas.Width / 2.0f, canvas.Height / 2.0f);
RectangleF outerRect = canvas!.ClientRectangle;
outerRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using var pathOuter = new GraphicsPath();
using var pathInner = new GraphicsPath();
pathOuter.AddEllipse(outerRect);
pathInner.StartFigure();
pathInner.AddArc(outerRect, coneRotationAngle, coneSweepAngle);
var arcPoints = pathInner.PathPoints;
PointF first = arcPoints[0];
PointF last = arcPoints[arcPoints.Length - 1];
pathInner.AddLines(new[] { center, last, center, first });
pathInner.CloseFigure();
using var outerPen = new Pen(Color.FromArgb(100, Color.Red), radarThickness);
using var innerBrush = new LinearGradientBrush(
center, first, Color.FromArgb(200, Color.Orange), Color.FromArgb(20, Color.Orange));
e.Graphics.FillPath(innerBrush, pathInner);
e.Graphics.DrawPath(outerPen, pathOuter);
}
}
This is how it works:

Slow performance using dashed lines on Canvas

I need to create a grid using Canvas with horizontal and vertical lines. The problem is in very bad performance when I'm using dashed lines instead of solid. Is there any solution to solve this? I don't need the possibility to handle events of this dashed lines (maybe exists some 'lightweight' version of canvas object...).
If I add StrokeDashArray to the Line object the application is very slow...
private void DrawGrid()
{
var brush = new SolidColorBrush((Color) ColorConverter.ConvertFromString("#cccccc"));
for (int i = 100; i < _areaSize; i += 100)
{
var hLine = new Line
{
X1 = 0,
Y1 = i,
X2 = _areaSize,
Y2 = i,
Stroke = brush,
StrokeThickness = 1,
SnapsToDevicePixels = true,
};
var vLine = new Line
{
X1 = i,
Y1 = 0,
X2 = i,
Y2 = _areaSize,
Stroke = brush,
StrokeThickness = 1,
SnapsToDevicePixels = true
};
//hLine.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
//vLine.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
Container.Children.Add(hLine);
Container.Children.Add(vLine);
Panel.SetZIndex(hLine, -1000);
Panel.SetZIndex(vLine, -1000);
}
}

Drawing sets of coordinates to pixels so they mutually scale

So I have a List<object> of longitude and latitude coordinates of two points, and I need to connect the line between them. The trick is to display all of the lines within a panel so that they are scaled within the panel's dimensions (converting coordinate numbers to match the pixels) and I almost got it. However I'm confounded by some unknown problem. The code is:
int canvasWidth = panel1.Width,
canvasHeight = panel1.Height;
var minX1 = tockeKoordinate.Min(x => x.startX);
var minX2 = tockeKoordinate.Min(x => x.endX);
var minX = Math.Min(minX1, minX2);
var maxX1 = tockeKoordinate.Max(x => x.startX);
var maxX2 = tockeKoordinate.Max(x => x.endX);
var maxX = Math.Max(maxX1, maxX2);
var maxY1 = tockeKoordinate.Max(x => x.startY);
var maxY2 = tockeKoordinate.Max(x => x.endY);
var maxY = Math.Max(maxY1, maxY2);
var minY1 = tockeKoordinate.Min(x => x.startY);
var minY2 = tockeKoordinate.Min(x => x.endY);
var minY = Math.Min(minY1, minY2);
double coordinatesWidth = Math.Abs(maxX - minX),
coordinatesHeight = Math.Abs(maxY - minY);
float coefWidth = (float)coordinatesWidth / canvasWidth,
coefHeight = (float)coordinatesHeight / canvasHeight;
Basically I check the List for minimum and maximum XY coordinates, so I know what the extreme values are. Then I use a coeficient value to recalculate the coords in pixels so that are within the panel. When I use this:
drawLine(Math.Abs((float)(line.startX - minX) / coefWidth),
Math.Abs((float)(line.startY - minY) / coefHeight),
Math.Abs((float)(line.endX - maxX) / coefWidth),
Math.Abs((float)(line.endY - maxY) / coefHeight));
which is in foreach loop that iterates trough all the elements from the List . The drawline() method is as follows:
private void drawLine(float startX, float startY, float endX, float endY)
{
PointF[] points =
{
new PointF(startX, startY),
new PointF(endX, endY),
};
g.DrawLine(myPen, points[0], points[1]);
}
WHen all of this is put together, I get this picture:
I know for a fact that the "lines" should be connected and form shapes, in this case they represent roads in a suburban area.
I figured that it treats every coordinate set like it is the only one and then scales it to the panel dimensions. Actually it should scale it in reference to all of the other coordinates
It should "zoom" them out and connect with each other, because that is the way I defined the panel dimensions and everything else.
EDIT: ToW's solution did the trick, with this line of code changed to use my List:
foreach (var line in tockeKoordinate)
{
gp.AddLine((float)(line.startX), (float)(line.startY), (float)(line.endX), (float)(line.endY));
gp.CloseFigure();
}
End result when working properly:
As far as I can see your best bet would be to add all those lines to a GraphicsPath.
After it is complete you can look at its bounding rectangle and compare it to the size your Panel offers.
Then you can calculate a scale for the Graphics object to draw with and also a translation.
Finally you draw the lines with Graphics.DrawPath.
All with just 2 division on your side :-)
Here is an example:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics G = e.Graphics;
Random R = new Random(13);
GraphicsPath gp = new GraphicsPath();
for (int i = 0; i < 23; i++)
{
gp.AddLine(R.Next(1234), R.Next(1234), R.Next(1234), R.Next(1234));
gp.CloseFigure(); // disconnect lines
}
RectangleF rect = gp.GetBounds();
float scale = Math.Min(1f * panel1.Width / rect.Width,
1f * panel1.Height / rect.Height);
using (Pen penUnscaled = new Pen(Color.Blue, 4f))
using (Pen penScaled = new Pen(Color.Red, 4f))
{
G.Clear(Color.White);
G.DrawPath(penUnscaled, gp);
G.ScaleTransform(scale, scale);
G.TranslateTransform(-rect.X, -rect.Y);
G.DrawPath(penScaled, gp);
}
}
A few notes:
The blue lines do not fit onto the panel
The red lines are scaled down to fit
The Pen is scaled along with the rest of the Graphics but won't go under 1f.
To create connected lines do add a PointF[] or, more convenient a List<PointF>.ToArray().
I really should have used panel1.ClientSize.Width instead of panel1.Width etc..; now it is off a tiny bit at the bottom; bad boy me ;-)

Refresh the canvas only for certain brushes

I'm trying to graph some circles and lines etc but I only want some lines to refresh on the canvas and the others not to, is there any way around this?
For example the mypen, mypen2 and mypen3, I want them to refresh on canvas but the graphics "g" a little further down I don't want to refresh, I want all the instances to show. How do I do this? Here is my code
private void drawlines()
{
canvas.Refresh();
int j = Int32.Parse(ivalue.Text);
float position1 = canvas.Width / 2;
float position2 = canvas.Height / 2;
float XX = (float)(Math.Round(position1 + Math.Sin(DegreeToRadian(j)) * 100));
float XY = (float)(Math.Round(position2 - Math.Cos(DegreeToRadian(j)) * 100));
float X2 = (position1 + XX);
float XY2 = XY;
System.Drawing.Pen myPen;
System.Drawing.Pen myPen2;
System.Drawing.Pen myPen3;
System.Drawing.Pen myPen4;
myPen = new System.Drawing.Pen(System.Drawing.Color.Red);
myPen2 = new System.Drawing.Pen(System.Drawing.Color.Blue);
myPen3 = new System.Drawing.Pen(System.Drawing.Color.Black);
myPen4 = new System.Drawing.Pen(System.Drawing.Color.Green);
System.Drawing.Graphics formGraphics = canvas.CreateGraphics();
formGraphics.DrawRectangle(myPen,XX, XY,3,3);
formGraphics.DrawRectangle(myPen2, canvas.Width / 2, XY, 3, 3);
formGraphics.DrawRectangle(myPen3, position1, position2, 3, 3);
formGraphics.DrawRectangle(myPen4, position1, XY2, 3, 3);
label1.Text = Convert.ToString(XY);
label1.Refresh();
listBox1.Items.Clear();
listBox1.Items.Add("XX=[" + XX + "] XY=[" + XY + "]");
}
private void Go_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 360; i = i + 1)
{
drawlines();
int linearm = (canvas.Width / 2) - i;
ivalue.Text = Convert.ToString(i);
ivalue.Refresh();
int testx = Int32.Parse(label1.Text);
Graphics g;
g = canvas.CreateGraphics();
Pen p;
Rectangle r;
p = new Pen(Brushes.Green);
r = new Rectangle(linearm,testx, 1, 1);
g.DrawRectangle(p, r);
System.Threading.Thread.Sleep(15);
}
}
I assume you are using winforms? If so you need to change your code to work like this:
To be persistant everything need to be drawin in the Paint event and using its e.Graphics object. (This is the Golden Rule! Corollary: Never use System.Drawing.Graphics formGraphics = canvas.CreateGraphics();)
Everything you want to be drawn must be stored in Lists of classes, sufficient to hold all info you need.
If you were to draw only Rectangles in only one pen a List<Rectangle> would be enough, but for other shapes and pens you will want to create a class to hold those data.
Now you can:
Draw them all in the Paint event, iterating the List<your DrawItemClass>
Remove or set inactive those items in the List you don't want to be drawn any longer..

How to display only 2 lines when a mousemove event occurs on a canvas

I'm trying to make two lines that follows the mouse every time the MouseMove event occurs. I can create the two lines for the event, but the problem is that they will create two lines for every MouseMove. Here's my code. I'm using WPF and C#.
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
double xPos = e.GetPosition(m_Grid).X;
double yPos = e.GetPosition(m_Grid).Y;
Line vertLine = new Line();
Line horzLine = new Line();
vertLine.X1 = xPos;
vertLine.Y1 = 0;
vertLine.X2 = xPos;
vertLine.Y2 = m_Grid.Height;
horzLine.X1 = 0;
horzLine.Y1 = yPos;
horzLine.X2 = m_Grid.Width;
horzLine.Y2 = yPos;
vertLine.StrokeThickness = 1;
horzLine.StrokeThickness = 1;
vertLine.Stroke = Brushes.Black;
horzLine.Stroke = Brushes.Black;
m_Grid.Children.Add(vertLine); //m_Grid is my Canvas
m_Grid.Children.Add(horzLine);
m_Grid.UpdateLayout();
}
Just Clear the Grid.Children every time (also don't call UpdateLayout, it's none of your business).
It would be best to find the existing lines and then remove them, this way if you have anything else in your canvas you won't be removing it, only these specific lines.
You can do this by giving them a name and then searching for them with that name.
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
double xPos = e.GetPosition(m_Grid).X;
double yPos = e.GetPosition(m_Grid).Y;
Line vertLine = new Line
{
Name = "vertLine",
X1 = xPos,
Y1 = 0,
X2 = xPos,
Y2 = m_Grid.Height,
Stroke = Brushes.Black,
StrokeThickness = 1
};
Line horzLine = new Line
{
Name = "horzLine",
X1 = 0,
Y1 = yPos,
X2 = m_Grid.Width,
Y2 = yPos,
StrokeThickness = 1,
Stroke = Brushes.Black
};
m_Grid.Children.Remove((Line) m_Grid.Children.OfType<Line>().FirstOrDefault(x => x.Name == "vertLine"));
m_Grid.Children.Remove((Line) m_Grid.Children.OfType<Line>().FirstOrDefault(x => x.Name == "horzLine"));
m_Grid.Children.Add(vertLine); //m_Grid is my Canvas
m_Grid.Children.Add(horzLine);
m_Grid.UpdateLayout();
}

Categories