How to check rectangle collision on a line - c#

I have a line drawn from two rectangles, spanning from xpos, ypos to xpos2, ypos2. I'm trying to detect if a rectangle (stored in 4 arrays of X/Y pos and random speed in those two directions) collides with the line.
I've tried (Vector2.Distance(new Vector2(xpos + 13, ypos + 13), new Vector2(EnX[index], EnY[index])) + Vector2.Distance(new Vector2(xpos2 + 13, ypos2 + 13), new Vector2(EnX[index], EnY[index])) == Vector2.Distance(new Vector2(xpos + 13, ypos + 13), new Vector2(xpos2 + 13, ypos2 + 13))) in a if statement, but that doesn't work.
EDIT: I've now tried
m = (ypos2 + 13 - ypos + 13) / (xpos2 + 13 - xpos + 13);
b = ((m * xpos2 + 13) - ypos2 + 13);
if (EnY[index] == m * EnX[index] + b)
Where Xpos/2 and ypos/2 are the line starting points. EnX[] and EnY[] are the "enemy" X and Y positions, where I'm trying to check if they're hitting a line.

I assume that you have the rectangle's corner positions as 2d vectors:
Vector2[] corners;
The rectangle intersects with the line if any two corner points lie on the opposite side of the line. To evaluate the side, we need the line's normal (the slope approach you tried may fail for vertical lines):
Vector2 normal = new Vector2D(ypos2 - ypos, xpos - xpos2);
We can then use the sign of the dot product to evaluate the side:
Vector2 lineStart = new Vector2(xpos, ypos);
//we don't know yet on which side of the line the rectangle lies
float rectangleSide = 0;
foreach(Vector2 corner in corners)
{
//cornerSide will be positive if the corner is on the side the normal points to,
//zero if the corner is exactly on the line, and negative otherwise
float cornerSide = Vector2.Dot(corner - lineStart, normal);
if(rectangleSide == 0)
//first evaluated corner or all previous corners lie exactly on the line
rectangleSide = cornerSide;
else
if(cornerSide != 0 && //ignore corners on the line
(cornerSide > 0) != (rectangleSide > 0)) //different sides
return true; //rectangle intersects with line
}
return false; //rectangle does not intersect with line

Related

c# draw the z axis

I'm drawing coordinate axes in picturebox
void draw_cor()
{
int w = pictureBox1.ClientSize.Width / 2;
int h = pictureBox1.ClientSize.Height / 2;
Refresh();
Graphics e = pictureBox1.CreateGraphics();
e.TranslateTransform(w, h);
DrawXAxis(new Point(-w, 0), new Point(w, 0), e);
DrawYAxis(new Point(0, h), new Point(0, -h), e);
DrawZAxis(new Point(-pictureBox1.ClientSize.Width , pictureBox1.ClientSize.Height), new Point(pictureBox1.ClientSize.Width, -pictureBox1.ClientSize.Height ), e);
}
markup and text for the x axis as an example
private void DrawXAxis(Point start, Point end, Graphics g)
{
for (int i = Step; i < end.X; i += Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
for (int i = -Step; i > start.X; i -= Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
g.DrawLine(Pens.Black, start, end);
g.DrawString("X", new Font(Font.FontFamily, 10, FontStyle.Bold), Brushes.Black, new Point(end.X - 15, end.Y));
}
private void DrawText(Point point, string text, Graphics g, bool isYAxis)
{
var f = new Font(Font.FontFamily, 6);
var size = g.MeasureString(text, f);
var pt = isYAxis
? new PointF(point.X + 1, point.Y - size.Height / 2)
: new PointF(point.X - size.Width / 2, point.Y + 1);
var rect = new RectangleF(pt, size);
g.DrawString(text, f, Brushes.Black, rect);
}
can someone explain how to make a method for marking the z axis?
I understand that the shift should be diagonal in both x and y, but nothing worked out for me and no markup appears on the screen.(so far I have managed to draw only a straight line diagonally )
upd:
private void DrawZAxis(Point start, Point end, Graphics g)
{
for (int i = -Step, j=Step ; i > start.X; i -= Step,j += Step)
{
g.DrawLine(Pens.Black, new Point(i-5, j), new Point(i+5, j));
DrawText(new Point(i, j), (i / -Step).ToString(), g, false);
}
...
}
I seem to have succeeded, but I ran into such a problem:
that is, the markup is not always on the coordinate axis. How to avoid this? It is necessary that the numbers are always on the axis (I suppose I should calculate the coefficient when the window is scaled, but only where to add it or by what to multiply?)
You are dealing with 3D data, so you should use 3D tools to transform your axes, and your data for that matter.
So you need to define a projection from 3D space to 2D space. This is usually done by defining a projection matrix. There are multiple projections to chose from, it looks like your projection is Oblique, but orthographic and perspective projections are also common. The System.Numerics.Vectors library has classes for Matrix4x4, vector2/3/4, with methods to create your projection and transform your vectors.
After transforming a vector you can simply keep the x/y values and discard the z-value to get your image coordinates. Note that if using a perspective transform you need a vector4 and divide the x/y/z elements by W.
Armed with these tools it should be a fairly simple thing to generate start/end points for each axis, and create tick-marks in 3D, before projecting everything to 2D for drawing.
Another option would be to just do everything in Wpf3D to start with, this will likely make some functionality like rotating the camera simpler.

GDI+ curve "overflowing"

I'm currently using GDI+ to draw a line graph, and using Graphics.DrawCurve to smooth out the line. The problem is that the curve doesn't always match the points I feed it, and that makes the curve grow out of the graph frame in some points, as seen below(red is Graphics.DrawLines, green is Graphics.DrawCurve).
How would I go about solving this?
The simplest solution is to set a tension:
The green curve is drawn with the default tension, the blue one set a tension of 0.1f:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawLines(Pens.Red, points.ToArray());
e.Graphics.DrawCurve(Pens.Green, points.ToArray());
e.Graphics.DrawCurve(Pens.Blue, points.ToArray(), 0.1f);
}
You will need to test what is the best compromise, 0.2f is still ok, 0.3f is already overdrawing quite a bit..
For a really good solution you will need to use DrawBeziers. This will let you draw curves that can go through the points without any overdrawing and with full control of the radius of the curves; but to to so you will need to 'find', i.e. calculate good control points, which is anything but trivial..:
This result is by no means perfect but already complicated enough.. I have displayed the curve points and their respective control points in the same color. For each point there is an incoming and an outgoing control point. For a smooth curve they need to have the same tangents/gradients in their curve points.
I use a few helper functions to calculate a few things about the segments:
A list of gradients
A list of signs of the gradients
A list of segment lengths
Lists of horizontal and of vertical gaps between points
The main function calculates the array of bezier points, that is the curve points and between each pair the previous left and the next right control points.
In the Paint event it is used like this:
List<PointF> bezz = getBezz(points);
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawBeziers(pen, bezz.ToArray());
Here are the functions I used:
List<float> getGradients(List<PointF> p)
{
List<float> grads = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
if (dx == 0) grads.Add(dy == 0 ? 0 : dy > 0 ?
float.PositiveInfinity : float.NegativeInfinity);
else grads.Add(dy / dx);
}
return grads;
}
List<float> getLengths(List<PointF> p)
{
List<float> lengs = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
lengs.Add((float)Math.Sqrt(dy * dy + dx * dx));
}
return lengs;
}
List<float> getGaps(List<PointF> p, bool horizontal)
{
List<float> gaps = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
gaps.Add(horizontal ? dx : dy);
}
return gaps;
}
List<int> getSigns(List<float> g)
{
return g.Select(x => x > 0 ? 1 : x == 0 ? 0 : -1).ToList();
}
And finally the main function; here I make a distinction: Extreme points ( minima & maxima) should have their control points on the same height as the points themselves. This will prevent vertical overflowing. They are easy to find: The signs of their gradients will always altenate.
Other points need to have the same gradient for incoming and outcoming control points. I use the average between the segments' gradients. (Maybe a weighed average would be better..) And I weigh their distance according to the segment lengths..
List<PointF> getBezz(List<PointF> points)
{
List<PointF> bezz = new List<PointF>();
int pMax = points.Count;
List<float> hGaps = getGaps(points, true);
List<float> vGaps = getGaps(points, false);
List<float> grads = getGradients(points);
List<float> lengs = getLengths(points);
List<int> signs = getSigns(grads);
PointF[] bezzA = new PointF[pMax * 3 - 2];
// curve points
for (int i = 0; i < pMax; i++) bezzA[i * 3] = points[i];
// left control points
for (int i = 1; i < pMax; i++)
{
float x = points[i].X - hGaps[i - 1] / 2f;
float y = points[i].Y;
if (i < pMax - 1 && signs[i - 1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y - hGaps[i-1] / 2f * m * vGaps[i-1] / lengs[i-1];
}
bezzA[i * 3 - 1] = new PointF(x, y);
}
// right control points
for (int i = 0; i < pMax - 1; i++)
{
float x = points[i].X + hGaps[i] / 2f;
float y = points[i].Y;
if (i > 0 && signs[i-1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y + hGaps[i] / 2f * m * vGaps[i] / lengs[i];
}
bezzA[i * 3 + 1] = new PointF(x, y);
}
return bezzA.ToList();
}
Note that I didn't code for the case of points with the same x-coordinate. So this is ok for 'functional graphs' but not for, say figures, like e.g. stars..
Maybe you just want to look at the "overshooting the bounds" problem as not a problem with the overshoot, but with the bounds. In which case, you can determine the actual bounds of a curve using the System.Drawing.Drawing2D.GraphicsPath object:
GraphicsPath gp = new GraphicsPath();
gp.AddCurve(listOfPoints);
RectangleF bounds = gp.GetBounds();
You can draw that GraphicsPath directly:
graphics.DrawPath(Pens.Black, gp);
As far as solving the bounds problem, the line necessarily overshoots the vertex on some axis. It's easier to see this fact when the lines are aligned to the bounds.
Given these points:
In order for them to be curved, they must exceed their bounds in some way:
If you never want to exceed their vertical bounds, you could simply ensure that the bezier handles have the same Y value as the vertex, but they will overshoot on the X:
Or vice-versa:
You could deliberately undershoot just enough to avoid the way curves can overshoot. This can be done by swapping the bezier handles, which would maybe be at the line-centers, with the vertices:

Tooltip in PictureBox FillPie Coordinates C#

I'm drawing a Circle made of 360 FillPie. Each FillPie color is taken from a List. I want to return a string that says at which degree is the mouse and how much is the value of the list to put it on a tooltip.
List<int> datiDisco = new List<int>();
public void Paint (Graphics grafica)
{
try
{
for (int i = 0; i < datiDisco.Count; i++)
{
Brush penna = new SolidBrush(Color.FromArgb(255, ScalaGrigi(valori[i]), ScalaGrigi(valori[i]), ScalaGrigi(valori[i])));
grafica.FillPie(penna, 0, 0, 400, 400, i, 1.0f);
}
}
catch
{
}
}
until here the code is working and i managed to draw the circle with the correct color.Now i can't figure out how i can take the coordinate of each fillpie that i have drawn. Can someone help me?
Figuring out which pie segment the mouse cursor lies in is a simple application of trigonometry, specifically an application of the inverse tangent (aka arctangent or atan).
As a quick reminder for those who've encountered this before, or as a lesson for those who haven't, let's look quickly at the tangent function. Trigonometry deals with the geometry of right triangles, and by definition a right triangle has two sides and a hypotenuse. The hypotenuse is a special name for the side of the triangle opposite the right (90° or π/2) angle. The other two sides are helpfully just called sides.
The tangent function's value is the ratio of the side opposite an angle to the side adjacent to that angle. The arctangent is the angle whose tangent is equal to that ratio. Because of the symmetry of the function we need to calculate the angle, and then add or subtract an offset depending on the quadrant to extract the 'real' angle. In diagram form this looks like:
The tangent function has discontinuities at several points, namely when the adjacent side's length is 0 (90° and 270°), so we'll have to treat those points specially.
OK, enough math, now on to the practical application.
For this demo, create a new C# WinForms project, and on the default Form1 add a PictureBox.
First, since I don't have your color generation function, I use the following list of values and helper function:
List<int> values = Enumerable.Range(0, 360).ToList();
int Rescale(int x) => (int)(((double)x / 360.0) * 255.0);
In the constructor hook up a couple events, and set some properties:
public Form1()
{
InitializeComponent();
this.pictureBox1.BorderStyle = BorderStyle.Fixed3D;
this.pictureBox1.Size = new Size(50, 50);
this.Size = new Size(450, 450);
this.DoubleBuffered = true;
this.Paint += Form1_Paint;
this.MouseMove += Form1_MouseMove;
}
To paint the circle I use a slightly modified version of your OnPaint handler:
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
for (int i = 0; i < values.Count; i++)
{
Brush b = new SolidBrush(Color.FromArgb(255, Rescale(values[i]), 0, 0));
e.Graphics.FillPie(b, 0, 0, 400, 400, (float)i, 1.0f);
}
}
In the MouseMove event is where we do most of the heavy lifting:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.pictureBox1.Location = new Point(e.X + 5, e.Y - 5);
int segment = (int)GetAngle(new Rectangle(0, 0, 400, 400), e.Location);
this.pictureBox1.BackColor = Color.FromArgb(255, Rescale(segment), 0, 0);
}
You may notice that since there are 360 wedges are in increments of a degree, I just truncated the angle. If you need more precision, or you decide to use segments greater than 1 degree, then you could use various rounding algorithms to round the angle to the nearest section of the pie.
At last, we're ready to implement the GetAngle function. First we calculate the center of the circle, because everything is relative to that.
int cx = (rect.Width + rect.X) / 2;
int cy = (rect.Height + rect.Y) / 2;
Next calculate the difference between the mouse's position and the center of the rectangle. (I've inverted the y coordinate to line up with 'standard' Cartesian coordinates, to make things easier, and match the coordinates you'd see in a math textbook.)
float x = pTo.X - cx;
float y = (cy - pTo.Y);
Next check for the arctangent's undefined points (and a couple of shortcuts we can take):
if ((int)x == 0)
{
if (y > 0) return 270;
else return 90;
}
else if ((int)y == 0)
{
if (x > 0) return 0;
else return 180;
}
Calculate the internal angle:
float ccwAngle = (float)Math.Atan(Math.Abs(y) / Math.Abs(x));
And map that angle to the appropriate quadrant:
if (x > 0 && y > 0)
{
}
else if (x < 0 && y > 0)
{
ccwAngle = (float)Math.PI - ccwAngle;
}
else if (x < 0 && y < 0)
{
ccwAngle = ccwAngle + (float)Math.PI;
}
else if (x > 0 && y < 0)
{
ccwAngle *= -1f;
}
Convert the angle from degrees to radians and normalize (make sure it's between 0° and 360°)
ccwAngle *= (float)(180 / Math.PI);
while (ccwAngle > 360) ccwAngle -= 360;
while (ccwAngle < 0) ccwAngle += 360;
Finally convert the counter-clockwise angle we needed to do the math into the clockwise angle that GDI uses, and return the value:
return 360f - ccwAngle;
All that together produces the final result:
(The code above is also available as a complete example in this gist)

I have a voxel engine that I am trying to optimize with Don't Repeat Yourself. Is there any way to fix this long-running mess?

For every single block type, I have to repeat a section of code that generates each face multiple times. I know there has to be a better way, but I did try to place the sections into a function per face and it errored out hard, even using a ref.
if (block.blockType == 1 && top == 0)
{
vertexIndex = vertices.Count;
vertices.Add(new Vector3(x, y + 1, z));
vertices.Add(new Vector3(x, y + 1, z + 1));
vertices.Add(new Vector3(x + 1, y + 1, z + 1));
vertices.Add(new Vector3(x + 1, y + 1, z));
// first triangle for the block top
triangles.Add(vertexIndex);
triangles.Add(vertexIndex + 1);
triangles.Add(vertexIndex + 2);
// second triangle for the block top
triangles.Add(vertexIndex + 2);
triangles.Add(vertexIndex + 3);
triangles.Add(vertexIndex);
// add UV
uvs.Add(new Vector2 (0.125f, 0.0f));
uvs.Add(new Vector2 (0.25f, 0.0f));
uvs.Add(new Vector2 (0.25f, 0.125f));
uvs.Add(new Vector2 (0.125f, 0.125f));
}
This is a typical section (sans StackOverflow formatting) - what could I do with it?
You should handle your 3 arrays, (Vertex / vertex indexes / uv coords) within a single flat array, that you pre-fill with 0 before filling it with actual values.
Performances would skyrocket.
I wonder if your vertex index won't contain each time the same sequences. You could only store ONE vertexIndex instead of the 6 numbers (+0 +1 +2 +2 +3 +0). But maybe you want to have the indexes at hand for webgl.
.
To factorize, i would make 6 of functions like this one :
Vertices.prototype.AddVerticesUP = function(x, y, z) {
var currIndex = this.lastIndex;
var vertArr = this.verticeArray;
vertArr[currIndex]=vertArr[currIndex+3]=
vertArr[currIndex+6]=vertArr[currIndex+9] = x;
vertArr[currIndex+1]=vertArr[currIndex+4]=
vertArr[currIndex+7]=vertArr[currIndex+10] = y;
vertArr[currIndex+2]=vertArr[currIndex+5]=
vertArr[currIndex+8]=vertArr[currIndex+11] =z;
// first point
vertArr[currIndex+1]++;
// second
vertArr[currIndex+4]++; vertArr[currIndex+5]++;
// third
vertArr[currIndex+6]++;vertArr[currIndex+7]++;vertArr[currIndex+8]++;
// fourth
vertArr[currIndex+9]++;vertArr[currIndex+10]++;
this.lastIndex +=12;
}
then build an array out of them :
Vertices.prototype.AddVertices= [ Vertices.prototype.AddVerticesUP,
Vertices.prototype.AddVerticesDOWN,
Vertices.prototype.AddVerticesLEFT,
Vertices.prototype.AddVerticesRIGHT,
Vertices.prototype.AddVerticesFRONT,
Vertices.prototype.AddVerticesBACK] ;
To use that array, if dir is the direction, just do :
Vertices.AddVertices [dir] ( x,y,z );
For your uv coordinates, you can simplify also, since all coordinates are axis aligned and square. Signature could be : uvs.addSquareUVs( baseU, baseV, size ). But if guess right, the coordinates depends only on side + block type. So you could make 6 functions with the same mechanism as above, and the signature would be uvs.addUV [dir] (block type) or uvs.addUV [dir] [block type] () .
now the 6 if/else for each side, multiplied by the number of block type can be greatly simplified to just this code :
vertexIndex = vertices.Count;
vertices.AddVertices [direction] ( x, y, z);
triangles.AddSquareStartingAt(vertexIndex);
uvs.addSquareUVs [direction] [block.blockType] ();
For vertex adding and uv coordinates you could define static collections somewhere:
public static readonly Vector3[] Corners = new Vector3[]{new Vector3(0, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 0)};
public static readonly Vector2[] UVCorners = new Vector2[]{new Vector2(0, 0), new Vector2(0.125f, 0), new Vector2(0.125f, 0.125f), new Vector2(0, 0.125f)};
And then you can make the section more compact:
if (block.blockType == 1 && top == 0)
{
vertexIndex = vertices.Count;
Vector3 origin = new Vector3(x, y, z);
foreach (Vector3 corner in Corners) vertices.Add(origin + corner);
// first triangle for the block top
triangles.Add(vertexIndex);
triangles.Add(vertexIndex + 1);
triangles.Add(vertexIndex + 2);
// second triangle for the block top
triangles.Add(vertexIndex + 2);
triangles.Add(vertexIndex + 3);
triangles.Add(vertexIndex);
// add UV
Vector2 uvOrigin = new Vector2(0.125f, 0);
foreach (Vector2 uvCorner in UVCorners) uvs.Add(uvOrigin + uvCorner);
}

Checking if a point is inside a rotated rectangle

I know this question has been asked a few times before, and I have read various posts about this. However I am struggling to get this to work.
bool isClicked()
{
Vector2 origLoc = Location;
Matrix rotationMatrix = Matrix.CreateRotationZ(-Rotation);
Location = new Vector2(0 -(Texture.Width/2), 0 - (Texture.Height/2));
Vector2 rotatedPoint = new Vector2(Game1.mouseState.X, Game1.mouseState.Y);
rotatedPoint = Vector2.Transform(rotatedPoint, rotationMatrix);
if (Game1.mouseState.LeftButton == ButtonState.Pressed &&
rotatedPoint.X > Location.X &&
rotatedPoint.X < Location.X + Texture.Width &&
rotatedPoint.Y > Location.Y &&
rotatedPoint.Y < Location.Y + Texture.Height)
{
Location = origLoc;
return true;
}
Location = origLoc;
return false;
}
Let point P(x,y), and rectangle A(x1,y1), B(x2,y2), C(x3,y3), D(x4,y4).
Calculate the sum of areas of △APD, △DPC, △CPB, △PBA.
If this sum is greater than the area of the rectangle:
Then point P(x,y) is outside the rectangle.
Else it is in or on the rectangle.
The area of each triangle can be calculated using only coordinates with this formula:
Assuming the three points are: A(x,y), B(x,y), C(x,y)...
Area = abs( (Bx * Ay - Ax * By) + (Cx * By - Bx * Cy) + (Ax * Cy - Cx * Ay) ) / 2
I assume that Location is the rectangle's rotation center. If not, please update your answer with an appropriate figure.
What you want to do is expressing the mouse location in the local system of the rectangle. Therfore, you could do the following:
bool isClicked()
{
Matrix rotationMatrix = Matrix.CreateRotationZ(-Rotation);
//difference vector from rotation center to mouse
var localMouse = new Vector2(Game1.mouseState.X, Game1.mouseState.Y) - Location;
//now rotate the mouse
localMouse = Vector2.Transform(localMouse, rotationMatrix);
if (Game1.mouseState.LeftButton == ButtonState.Pressed &&
rotatedPoint.X > -Texture.Width / 2 &&
rotatedPoint.X < Texture.Width / 2 &&
rotatedPoint.Y > -Texture.Height / 2 &&
rotatedPoint.Y < Texture.Height / 2)
{
return true;
}
return false;
}
Additionally, you may want to move the check if the mouse is pressed to the beginning of the method. If it is not pressed, you don't have to calculate the transformation etc.

Categories