C# Raycasting / Find closest rectangle from direction - c#

So, I'm trying to find a way to find the first Rectangle (From a list of rectangles, all 2D) that my Point will hit, going in a specific direction (In C#), however I cannot seem to correctly describe the terminology (If there is such a thing for this, the closest thing I found is raycasting).
My goal is to start from a specific "Point" (In this case the asteterik- * seen from the example below), and from there choose a specific direction (Left, Right, Down, Up) (No Angles). So let's say we choose Down, then the first rect that it would hit is "I'M A RECT 2", and therefore should return this.
I know all the positions and sizes of the Rectangles already so I got that information.
How do I go about doing this?
* [I'M A RECT 1]
[I'M A RECT 2]
[I'M A RECT 3]
[I'M A RECT 4]

You can check if the rectangle can intersect with the ray originating from the point, and then compute the distance from the point:
var point = new PointF(1.2f, 2.5f);
var rectangles = new RectangleF[]
{
new RectangleF(1, 1, 1, 1),
new RectangleF(3, 1, 1, 1),
new RectangleF(5, 2, 1, 1),
};
var hit = rectangles
.Select(x =>
{
if (IsBetween(point.X, x.Left, x.Left + x.Width))
return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Top - x.Height, x.Top) as float? };
else if (IsBetween(point.X, x.Top - x.Height, x.Top))
return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Left, x.Left + x.Width) as float? };
else return new { Rectangle = x, Distance = default(float?) };
})
.Where(x => x.Distance != null)
.OrderBy(x => x.Distance)
.FirstOrDefault()?.Rectangle;
bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
Edit:
var hit = RayTest(point, rectangles, RayDirections.Right | RayDirections.Down) // or, try just `Down`
.Where(x => x.Success)
.OrderBy(x => x.Distance)
.FirstOrDefault()?.Target;
[Flags]
public enum RayDirections { None = 0, Left = 1 << 0, Up = 1 << 1, Right = 1 << 2, Down = 1 << 3, All = (1 << 4) - 1 }
public class RayHit<T>
{
public T Target { get; }
public float? Distance { get; }
public bool Success => Distance.HasValue;
public RayHit(T target, float? distance)
{
this.Target = target;
this.Distance = distance;
}
}
public IEnumerable<RayHit<RectangleF>> RayTest(PointF point, IEnumerable<RectangleF> rectangles, RayDirections directions = RayDirections.All)
{
if (directions == RayDirections.None)
return Enumerable.Empty<RayHit<RectangleF>>();
var ray = new
{
Horizontal = new
{
LBound = directions.HasFlag(RayDirections.Left) ? float.MinValue : point.X,
UBound = directions.HasFlag(RayDirections.Right) ? float.MaxValue : point.X,
},
Vertical = new
{
LBound = directions.HasFlag(RayDirections.Down) ? float.MinValue : point.Y,
UBound = directions.HasFlag(RayDirections.Up) ? float.MaxValue : point.Y,
},
};
return rectangles
.Select(x =>
{
float left = x.Left, right = x.Left + x.Width;
float top = x.Top, bottom = x.Top - x.Height;
if (IsBetween(point.X, left, right) && (IsBetween(top, ray.Vertical.LBound, ray.Vertical.UBound) || IsBetween(bottom, ray.Vertical.LBound, ray.Vertical.UBound)))
return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, bottom, top) as float?);
else if (IsBetween(point.X, bottom, top) && (IsBetween(left, ray.Horizontal.LBound, ray.Horizontal.UBound) || IsBetween(right, ray.Horizontal.LBound, ray.Horizontal.UBound)))
return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, left, right) as float?);
else return new RayHit<RectangleF>(x, default);
});
bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
}
note: In both version, there is a bug when the point is inside the rectangle. The distance computed will be the distance to the closest edge, instead of 0 or negative value.

Related

How to find coordinate of contour centroid in C#

I am doing image processing so that I am finding contours in the image. What I need is the centroid pixel number of the found contour in the image. To find the pixel number I am using the code given below.
After finding the pixel number I want to show it in the text boxes as x and y coordinates. But the code is not working.
Please help me. What is wrong?
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(cannyImage, contours, null, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
var cannyOut = cannyImage.ToImage<Bgr, byte>();
//CvInvoke.DrawContours(cannyOut, contours, 2, new MCvScalar(255, 0, 0),2);
VectorOfPoint approx = new VectorOfPoint();
Dictionary<int, double> shapes = new Dictionary<int, double>();
for (int i = 0; i < contours.Size; i++)
{
approx.Clear();
double perimeter = CvInvoke.ArcLength(contours[i], true);
CvInvoke.ApproxPolyDP(contours[i], approx, 0.04 * perimeter, true);
double area = CvInvoke.ContourArea(contours[i]);
if (approx.Size > 4)
{
shapes.Add(i, area);
}
}
if (shapes.Count > 0)
{
var sortedShapes = (from item in shapes
orderby item.Value ascending
select item).ToList();
for (int i = 0; i < sortedShapes.Count; i++)
{
CvInvoke.DrawContours(cannyOut, contours, sortedShapes[i].Key, new MCvScalar(255, 0, 0), 2);
var moments = CvInvoke.Moments(contours[sortedShapes[i].Key]);
int x = (int)(moments.M10 / moments.M00);
int y = (int)(moments.M01 / moments.M00);
CvInvoke.PutText(cannyOut, (i + 1).ToString(), new Point(x, y), Emgu.CV.CvEnum.FontFace.HersheyTriplex, 1.0,
new MCvScalar(255, 0, 0), 2);
//CvInvoke.PutText(cannyOut, sortedShapes[i].Value.ToString(), new Point(x, y - 30), Emgu.CV.CvEnum.FontFace.HersheyTriplex, 1.0,
// new MCvScalar(255, 0, 0), 2);
textBox1.Text = x.ToString();
textBox2.Text = y.ToString();
}
}
To find the centroid of a shape you need to split it into many triangles first.
Then for each triangle with vertices A, B, C you do the summation weighted by the area of the triangle just as so
static void Main(string[] args)
{
var shape = new List<Triangle>();
// fill shape with triangles
float area = 0f;
Vector2 centroid = Vector2.Zero;
foreach (var triangle in shape)
{
float trig_area = triangle.Area;
Vector2 trig_cen = triangle.Centroid;
area += trig_area;
centroid += trig_area * trig_cen;
}
centroid /= area;
}
For reference, a 2D triangle has the following properties
public readonly struct Triangle
{
public Triangle(Vector2 a, Vector2 b, Vector2 c) : this()
{
A = a;
B = b;
C = c;
}
public Vector2 A { get; }
public Vector2 B { get; }
public Vector2 C { get; }
public float Area { get => (Cross(A, B) + Cross(B, C) + Cross(C, A)) / 2; }
public Vector2 Centroid { get => (A + B + C) / 3; }
// helper function
static float Cross(Vector2 a, Vector2 b) => a.X * b.Y - a.Y * b.X;
}

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

Draw polygon from unordered points

So, I know that there are similar questions and I searched a lot before typing my code and asking this question.
In my case, the user clicks on a place on the screen to add a point. When the user finishes adding points, makes a right click to say that the points are ok and draw the polygon.
A the poins are irregularly placed, I must calculate the center point and the angle of each point to order the point list.
And then, when I move a point, I recalculate the angles with new positions and redraw the polygon.
It works but, when I move a point beyond two others beyond them, sometimes it doesn't draw the plygon. I couldn't find what is wrong.
here is my code and two images to explain the problem :
public class CustomPoint3D
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public int Angle { get; set; }
public CustomPoint3D()
{
}
public CustomPoint3D(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
}
private void AddZoneSurface(List<CustomPoint3D> customPoints, string guid)
{
//Calculates angles and orders / sorts the list of points
List<Point2D> points = From3DTo2D(customPoints);
//Draws a polygon in Eyeshot but it can be any tool to create a polygon.
var polygon = devDept.Eyeshot.Entities.Region.CreatePolygon(points.ToArray());
polygon.ColorMethod = colorMethodType.byEntity;
polygon.EntityData = "tool-surface-" + guid;
polygon.Color = System.Drawing.Color.FromArgb(80, 0, 0, 0);
sceneLeft.Entities.Add(polygon);
sceneLeft.Invalidate();
}
private List<Point2D> From3DTo2D(List<CustomPoint3D> points)
{
List<Point2D> retVal = new List<Point2D>();
var minX = points.Min(ro => ro.X);
var maxX = points.Max(ro => ro.X);
var minY = points.Min(ro => ro.Y);
var maxY = points.Max(ro => ro.Y);
var center = new CustomPoint3D()
{
X = minX + (maxX - minX) / 2,
Y = minY + (maxY - minY) / 2
};
// precalculate the angles of each point to avoid multiple calculations on sort
for (var i = 0; i < points.Count; i++)
{
points[i].Angle = (int)(Math.Acos((points[i].X - center.X) / lineDistance(center, points[i])));
if (points[i].Y > center.Y)
{
points[i].Angle = (int)(Math.PI + Math.PI - points[i].Angle);
}
}
//points.Sort((a, b) => a.Angle - b.Angle);
points = points.OrderBy(ro => ro.Angle).ToList();
foreach (var item in points)
{
retVal.Add(new Point2D() { X = item.X, Y = item.Y });
}
return retVal;
}
double lineDistance(CustomPoint3D point1, CustomPoint3D point2)
{
double xs = 0;
double ys = 0;
xs = point2.X - point1.X;
xs = xs * xs;
ys = point2.Y - point1.Y;
ys = ys * ys;
return Math.Sqrt(xs + ys);
}
On the first images, I move the point from its initial position to the indicated position, it doesn't draw the polygon.
You should read the Wikipedia page on convex hull algorithms and pick an algorithm that you feel comfortable implementing that also meets your O(n) complexity requirements.
If convex hull isn't what you're after then you'll need to be a bit more specific as to how you want the points to define the shape. One (probably sub-optimal) solution would be to calculate the convex hull, find the center, pick a point as your "start" point and then order the remaining points by angle from the start point.
So if someone needs a sample which works, I found the problem.
I should have declared the angle property of th CustomPoint3D object like this
As the property was integer, an angle 0,3 or 0,99 was giving 0 as angle.
public class CustomPoint3D
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public double Angle { get; set; }
public CustomPoint3D()
{
}
public CustomPoint3D(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
}
and calculate this values as double
private List<Point2D> From3DTo2D(List<CustomPoint3D> points)
{
List<Point2D> retVal = new List<Point2D>();
var minX = points.Min(ro => ro.X);
var maxX = points.Max(ro => ro.X);
var minY = points.Min(ro => ro.Y);
var maxY = points.Max(ro => ro.Y);
var center = new CustomPoint3D()
{
X = minX + (maxX - minX) / 2,
Y = minY + (maxY - minY) / 2
};
// precalculate the angles of each point to avoid multiple calculations on sort
for (var i = 0; i < points.Count; i++)
{
points[i].Angle = Math.Acos((points[i].X - center.X) / lineDistance(center, points[i]));
if (points[i].Y > center.Y)
{
points[i].Angle = Math.PI + Math.PI - points[i].Angle;
}
}
//points.Sort((a, b) => a.Angle - b.Angle);
points = points.OrderBy(ro => ro.Angle).ToList();
foreach (var item in points)
{
retVal.Add(new Point2D() { X = item.X, Y = item.Y });
}
return retVal;
}
And

Drawing points along path spirally

Well, I'm trying to optimize what I did here (Smoothing noises with different amplitudes (Part 2)).
By this reason, I did a new implementation from scratch (https://youtu.be/o7pVEXhh3TI) to draw the path:
private void Start()
{
Polygon pol = File.ReadAllText(PolyPath).Deserialize<Polygon>();
// Create tex object
var list = pol.Vertices.AsEnumerable();
tex = list.CreateTextureObject(pol.Position, offset);
exampleTexture = new Texture2D(tex.Width, tex.Height);
exampleTexture.SetPixels32(new Color32[tex.Width * tex.Height]);
exampleTexture.Apply();
vertices = pol.Vertices.Select(v => (v - pol.Position) + offset).Clone().ToList();
_ss = new List<Segment>(pol.Segments.Select(s => new Segment((s.start + pol.Center - pol.Position) + offset, (s.end + pol.Center - pol.Position) + offset)));
foreach (Segment curSeg in _ss)
for (int i = -effectDistance; i < effectDistance; ++i)
{
Vector2 perp = Vector2.Perpendicular(((Vector2)curSeg.start - (Vector2)curSeg.end)).normalized;
segments.Add((Vector2)curSeg.start + perp * i);
F.DrawLine((Vector2)curSeg.start + perp * i, (Vector2)curSeg.end + perp * i, (x, y) => layers.Add(new Point(x, y)));
}
Debug.Log("Layer Count: " + layers.Count);
drawPath = true;
}
private void OnGUI()
{
if (exampleTexture == null)
return;
GUI.DrawTexture(new Rect((Screen.width - tex.Width) / 2, (Screen.height - tex.Height) / 2, tex.Width, tex.Height), exampleTexture);
if (drawPath)
{
{
Point? cur = layers.Count > 0 ? (Point?)layers.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(170, 0, 0, 255));
exampleTexture.Apply();
layers.Remove(cur.Value);
}
}
{
Point? cur = segments.Count > 0 ? (Point?)segments.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(0, 170, 0, 255));
exampleTexture.Apply();
segments.Remove(cur.Value);
}
}
{
Point? cur = vertices.Count > 0 ? (Point?)vertices.First() : null;
//Debug.Log(cur);
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(255, 128, 0, 255));
exampleTexture.Apply();
vertices.Remove(cur.Value);
}
}
if (vertices.Count == 0 && segments.Count == 0 && layers.Count == 0)
drawPath = false;
}
}
This is what DrawLines actually do:
public static class F
{
public static void DrawLine(Point p1, Point p2, Action<int, int> action)
{
DrawLine(p1.x, p1.y, p2.x, p2.y, action);
}
public static void DrawLine(int x0, int y0, int x1, int y1, Action<int, int> action)
{
int sx = 0,
sy = 0;
int dx = Mathf.Abs(x1 - x0),
dy = Mathf.Abs(y1 - y0);
if (x0 < x1) { sx = 1; } else { sx = -1; }
if (y0 < y1) { sy = 1; } else { sy = -1; }
int err = dx - dy,
e2 = 0;
while (true)
{
action?.Invoke(x0, y0);
if ((x0 == x1) && (y0 == y1))
break;
e2 = 2 * err;
if (e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx)
{
err = err + dx;
y0 = y0 + sy;
}
}
}
}
This is an implemenentation of Bresenham algorithm.
This implementation is better because I have lowered iterations from 280k to 6k, but there is an issue as you can see this is innacurate...
The way this works first is getting the perpendicular of each segment on the shape (green pixels) and then drawing lines between the start and the end point of that segment. Segmenents are obtained using Ramer-Douglas-Peucker algorithm.
So I was thinking on draw the "orange" path spirally. I don't know how to explain this, basically, obtaining the same path but, with an scale (Translating/transforming? list of points from its center with an offset/distance) but I think I will have the same innacuracy.
Any guide will be appreciated. What algorithm could I use to draw the path with "layers"?
Following some of the information here, you might be able to use "inward/outward polygon offsetting" (aka "polygon buffering") to get the result you are interested in.
A tool such as Clipper can help.
Once you have a way to outwardly offset your shape, do the following:
First, draw the outer shape (black region below), then offset the inner shape outwards as far as you need it to go, and draw it on top of the outer shape (brown region below) using an appropriate noise/color scheme:
Then, apply a smaller offset, then draw that shape on top using a different noise/colorscheme (orange region below).
Repeat until you have as many gradients as you need:
Finally, draw the inner shape without any offsetting with its noise/color scheme:

Gift wrapping algorithm

I saved group of points on my panel to List<MyVector> savedPoints, then I calculated the the point with lowest coordinate y :
public void searchLowest()
{
MyVector temp;
double ylon = savedPoints[0].getY();
for (int i = 0; i < savedPoints.Count; i++)
{
if (savedPoints[i].getY() > ylon)
{
ylon = savedPoints[i].getY();
lowest = i;
}
}
temp = savedPoints[lowest];
}
after this I made a method to calculate polar angles :
public static double angle(MyVector vec1, MyVector vec2)
{
double angle = Math.Atan2(vec1.getY() - vec2.getY(), vec1.getX() - vec2.getX());
return angle;
}
now don't know how to use Gift wrapping algorithm in my case. The pseudocode on WikiPedia link is not really understandable for me, so I'm asking for help here.
I'm using C# and win forms (net.framework 4.0)
Thanks for any help.
Using this as a reference, here is teh code:
namespace GiftWrapping
{
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class Program
{
static void Main(string[] args)
{
List<Point> test = new List<Point>(
new Point[]
{
new Point(200,200), new Point(300,100), new Point(200,50), new Point(100,100),
new Point(200, 100), new Point(300, 200), new Point(250, 100),
});
foreach (Point point in ConvexHull(test))
{
Console.WriteLine(point);
}
Console.ReadKey();
}
public static List<Point> ConvexHull(List<Point> points)
{
if (points.Count < 3)
{
throw new ArgumentException("At least 3 points reqired", "points");
}
List<Point> hull = new List<Point>();
// get leftmost point
Point vPointOnHull = points.Where(p => p.X == points.Min(min => min.X)).First();
Point vEndpoint;
do
{
hull.Add(vPointOnHull);
vEndpoint = points[0];
for (int i = 1; i < points.Count; i++)
{
if ((vPointOnHull == vEndpoint)
|| (Orientation(vPointOnHull, vEndpoint, points[i]) == -1))
{
vEndpoint = points[i];
}
}
vPointOnHull = vEndpoint;
}
while (vEndpoint != hull[0]);
return hull;
}
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
}
}
adaptation to your private classes, would be your homework.
Here is an implementation using the System.Windows.Point class in WindowsBase:
public struct PolarVector {
public double Radius { get; set; }
public double Angle { get; set; }
public override string ToString() {
return "{" + Radius + "," + Angle + "}";
}
}
private static void Main(string[] args) {
var points = new[] {
new Point {X = 0, Y = 0},
//new Point {X = 2, Y = 0},
new Point {X = 0, Y = 2},
new Point {X = 1.5, Y = 0.5},
new Point {X = 2, Y = 2},
};
foreach(var point in ConvexHull(points)) {
Console.WriteLine(point);
}
Console.WriteLine();
if(Debugger.IsAttached) {
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
public static IList<Point> ConvexHull(IList<Point> points) {
var pointOnHull = LeftMost(points);
var pointsOnHull = new List<Point>();
Point currentPoint;
do {
pointsOnHull.Add(pointOnHull);
currentPoint = points[0];
foreach(var nextPoint in points.Skip(1)) {
if (currentPoint == pointOnHull || IsLeft(nextPoint, pointOnHull, currentPoint)) {
currentPoint = nextPoint;
}
}
pointOnHull = currentPoint;
}
while (currentPoint != pointsOnHull[0]);
return pointsOnHull;
}
private static Point LeftMost(IEnumerable<Point> points) {
return points.Aggregate((v1, v2) => v2.X < v1.X ? v2 : v1);
}
private static bool IsLeft(Point nextPoint, Point lastPoint, Point currentPoint) {
var nextVector = ToPolar(nextPoint, lastPoint);
var currentVector = ToPolar(currentPoint, lastPoint);
return nextVector.Radius != 0 && Normalize(nextVector.Angle - currentVector.Angle) > 0;
}
private static PolarVector ToPolar(Point target, Point start) {
var vector = target - start;
return new PolarVector { Radius = Math.Sqrt((vector.Y * vector.Y) + (vector.X * vector.X)), Angle = Math.Atan2(vector.Y, vector.X)};
}
private static double Normalize(double radians) {
while(radians > Math.PI) {
radians -= 2*Math.PI;
}
while (radians < -Math.PI) {
radians += 2*Math.PI;
}
return radians;
}
For Gift Wrapping algorithm implementation, it is advisable that one uses Left test technique
// Left test implementation given by Petr
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
Using this(Left test) comparison technique helps us in wrapping the gift faster, so to speak.
Never ever use arc tangent calculations, it will impact the run times in a big way.
Reference: Left test technique mentioned here - https://stackoverflow.com/a/1560510/1019673

Categories