I have a project with a graphic object called GraphicsLine. What it does is simply draw line on every mousedown and stop on every mouseup, nothing complicated. It stores coordinates for the start and the end of the line (x,y). Now what I want to know is whenever a shape is created. For example, you create 4 lines that forms a square, I want to be able to run an algorithm that can tell me that there is a square in the drawing.
Note that the shape can be anything that is "closed". Not only square, rectangle or triangle.
The goal of this is to calculate the area of the created shapes.
Is there something that already exists for doing this? I've been struggling to find something that could fit my needs.
EDIT 1:
I added some additionnal information :
Lines are either "cliped" to another line start or end point or they are not. There is no close closure, it is on the same point or not closed at all. 1 line can be used in multiple shapes.
EDIT 2 :
So basically, I want something that can give me an array of "GraphicsLine" that forms a shape. So if we have 6 lines in the drawing but 4 of them forms a square, I want something that returns those 4 lines so I can create another object from it.
Thanks in advance.
Please check this question How do I calculate the area of a 2d polygon? it is probably what you need, you just have to port it to C# :)
Edit: from #chmike answer:
Where x and y are the arrays of coordinates
var x = [10,10,20,20];
var y = [10,20,20,10];
var n = x.Length;
x[n] = x[0];
x[n+1] = x[1];
y[n] = y[0];
y[n+1] = y[1];
// compute area
int area = 0;
for(var i = 1; i <= n; ++i ) {
area += x[i]*( y[i+1] - y[i-1] );
}
Console.Write(area /= 2);
Take a look at this tutorial of AForge.NET Shape Checker
If your GraphicsLine object is drawing on a PictureBox or if you can convert that object to a bitmap, you can then run the code below.
You can easily try detection of quadrilaterals, this code here will actually highlight the detected objects, you do change the loop and make it do whatever you want instead:
// if you are using a PictureBox to draw the shapes, then convert it to a bitmap
Bitmap bitmap = new Bitmap(pictureBox1.Image)
// locate objects using blob counter
BlobCounter blobCounter = new BlobCounter( );
blobCounter.ProcessImage( bitmap );
Blob[] blobs = blobCounter.GetObjectsInformation( );
// create Graphics object to draw on the image and a pen
Graphics g = Graphics.FromImage( bitmap );
Pen bluePen = new Pen( Color.Blue, 2 );
// check each object and draw circle around objects, which are recognized as circles
for ( int i = 0, n = blobs.Length; i < n; i++ )
{
List<IntPoint> edgePoints = blobCounter.GetBlobsEdgePoints( blobs[i] );
List<IntPoint> corners = PointsCloud.FindQuadrilateralCorners( edgePoints );
g.DrawPolygon( bluePen, ToPointsArray( corners ) );
}
bluePen.Dispose( );
g.Dispose( );
I know it is 2 years + later, but I found a way with a recursive function to know when the shapes are "closed". You start from any points of the last drawed line, then you check if the other point is connected to another line. You do this until you reach the starting point. I save all the lines into another class called Polygon. This way, they keep all the lines with the start and finish that forms the polygon. Then to calculate the area, I do as Eduardo Cobuci said in his answer.
Hope this helps.
Related
I have a set of geo coordinates as latitude/longitude pairs that I wish to project onto a 2D surface. Some of the coordinates are connected by lines forming a shape/outline/polygon.
I understand how to project individual points using one of the many available map projections like Mercator and then drawing them with Graphics.DrawArc but how do I go about projecting the connecting lines between them? I can't just project the two defining coordinates of a line and draw it Graphics.DrawLine because every single point on that line has to be projected as well, right? I don't know much about these things, so I hope you understand what I mean.
Is it even possible to do what I'm trying to do using just the methods provided by the System.Drawing.Graphics class? Can I do this with a projection matrix? If anyone could explain a bit how I would go about doing this, I would greatly appreciate it. Thanks.
If your are only drawing lines it is probably easiest just to subdivide the lines into short segments, project each segment point and draw straight lines between them. This might not be the most performant or exact way to do it. But it should be fairly easy to implement.
For example:
public static void DrawSubdivided(this Graphics g, Pen pen, Vector2 p1, Vector2 p2, float subdivisionLength)
{
var d = p2 - p1;
var length = d.Length();
// Add check for zero-length lines
var dn = d / length;
var points = new List<Vector2>();
for (float l = 0; l < length; l += subdivisionLength)
{
points.Add( p1 + dn * l);
}
points.Add(p2);
// apply transformation to the points
g.DrawLines(pen, points.Select(p => new PointF(p.X, p.Y)).ToArray());
}
This uses System.Numerics.Vector2 since it has reasonable arithmetic operations defined, in contrast to PointF.
I'm trying to get the corners of the following shape:
By corners I mean this (red dots):
The minimum quantity of points that can define this shape.
And I have implemented the following:
public Shape Optimize()
{
// If the vertices are null or empty this can't be executed
if (vertices.IsNullOrEmpty())
return this; // In this case, return the same instance.
if (!edges.IsNullOrEmpty())
edges = null; //Reset edges, because a recalculation was requested
// The corners available on each iteration
var corners = new Point[] { Point.upperLeft, Point.upperRight, Point.downLeft, Point.downRight };
//The idea is to know if any of the following or previous vertice is inside of the the array from upside, if it is true then we can add it.
Point[] vs = vertices.ToArray();
for (int i = 0; i < vertices.Count - 1; ++i)
{
Point backPos = i > 0 ? vs[i - 1] : vs[vertices.Count - 1],
curPos = vs[i], //Punto actual
nextPos = i < vertices.Count - 1 ? vs[i + 1] : vs[0];
// We get the difference point between the actual point and the back & next point
Point backDiff = backPos - curPos,
nextDiff = nextPos - curPos,
totalDiff = nextPos - backPos;
if (corners.Contains(backDiff) || corners.Contains(nextDiff) || corners.Contains(totalDiff))
AddEdge(curPos, center); // If any of the two points are defined in the corners of the point of before or after it means that the actual vertice is a edge/corner
}
return this;
}
This works rectangled shapes, but rotated shapes are very sharp, so, this code doesn't work well:
Blue pixels (in this photo and the following) are the vertices variable processed on Optimize method.
Green pixels are the detected corners/edges (on both photos).
But sharpness in a shape only defines the side inclination, so what can I do to improve this?
Also, I have tested Accord.NET BaseCornersDetector inherited classes, but the best result is obtained with HarrisCornersDetector, but:
Many edges/corners are innecesary, and they aren't in the needed place (see first photo).
Well, after hours of research I found a library called Simplify.NET that internally runs the Ramer–Douglas–Peucker algorithm.
Also, you maybe interested on the Bresenham algorithm, with this algorithm you can draw a line using two Points.
With this algorithm, you can check if your tolerance is too high, comparing the actual points and the points that this algorithm outputs and making some kind of percentage calculator of similarity.
Finally, is interesting to mention Concave Hull and Convex Hull algorithms.
All this is related to Unity3D.
My outputs:
And my implementation.
It's very important to say, that points needs to be sorted forcing them to be connected. If the shape is concave as you can see on the second photo maybe you need to iterate walls of the shape.
You can see an example of implementation here. Thanks to #Bunny83.
I'm working with a project that gets information out of a DXF file(a drawing file from autocad or any other drawing program). I get information from it and it shows the borders of a panel. In this panel I need to set lines, but when there are lines with coordinates OUTSIDE the panel I'm not allowed to draw them.
Below you can find an image, maybe it makes a bit more clear.
So the white lines are bricks and they need glue lines(yellow), The aquamarine line is the contour line. Outside this line it is not allowed to draw Glue lines(the yellow lines)
As you can see on brick with NR 4BA87 it draws glue lines outside the panel.
I need some sort of check if a point is inside my contour/panel I can draw it else don't draw it. How can I do this?
edit:
I draw these lines from a list so it would be great if I could remove(or not add them) the gluelines from my list that are outside my panel.
I've got a list with Points that are the contourpoints:
public List<PointF> ListContourPoints = new List<PointF>();
And I've got a list with glue lines:
List<GlueLine> glueLines = new List<GlueLine>();
my GlueLine class has 2 PointFin it, StartPosition and EndPosition.
It would be nice if I get something like:
if(GlueLines.StartPosition is INSIDE panel && GlueLines.EndPosition is INSIDE panel)
{
glueLines.Add(gl);
}
You have got the positional data of all the Points that make up the corners of the panel. So all you need to do is find out the rectangular shapes that make up the panel and then do positional checking on the lines.
An example function to create the rectangles would look something like the below, Note however that this function is created under the assumption that the panel is in the same shape as the example one provided, some more logic will be needed to create one which encompasses all of the variations:
private static Rectangle[] SplitPointsIntoRectangles(Point[] pa)
{
pa = pa.OrderBy(p => p.X).ToArray();
Point[] leftmost = pa.Select(p => p).Where(p => p.X == pa[0].X).ToArray();
Point[] rightmost = pa.Select(p => p).Where(p => p.X == pa[pa.Length - 1].X).ToArray();
pa = pa.OrderBy(p => p.Y).ToArray();
Point[] topmost = pa.Select(p => p).Where(p => p.Y == pa[0].Y).ToArray();
Point[] bottommost = pa.Select(p => p).Where(p => p.Y == pa[pa.Length - 1].Y).ToArray();
List<Point> edges = new List<Point>();
edges.AddRange(leftmost);
edges.AddRange(rightmost);
edges.AddRange(topmost);
edges.AddRange(bottommost);
Point middlePixel = pa.FirstOrDefault(p => !edges.Contains(p));
Rectangle[] ra = new Rectangle[2];
ra[0] = new Rectangle(leftmost[0].X, leftmost.Min(p => p.Y), rightmost[0].X - leftmost[0].X, bottommost[0].Y - leftmost[0].Y);
ra[1] = new Rectangle(topmost.Min(p => p.X), topmost[0].Y, rightmost[0].X - middlePixel.X, middlePixel.Y - topmost[0].Y);
return ra;
}
Once we have the rectangles we then need to make sure the lines are not outside of them, for this we can use the Rectangle.Intersects method. For this we use the points of each of the glue lines to create a rectangle, like so (assuming all these lines are horizontal):
Rectangle glueLineRect = new Rectangle(glueLineStartPoint.X, glueLineStartPoint.Y, glueLineEndPosition.X - glueLineStartPoint.X, 1);
If the following function returns an empty rectangle then the two rectangles do not intersect, else they do. Note: this function will return a none empty rectangle if the rectangles partially intersect.
Rectangle intersectionRect = Rectangle.Intersect(MainRect, glueLineRect);
This can then be extended to test on all of the rectangles contained within the panel and if it fails on all of them then the glue line resides outside of the panel.
I created this answer to get you started, the SplitPointsIntoRectangles method needs to be made generic. Also the looping needs to created to test whether the Glue Lines reside inside of the panel or not.
I've loaded a 3d model using Helix toolking like this
modelGroupScull = importer.Load("C:\\Users\\Robert\\Desktop\\a.obj");
GeometryModel3D modelScull = (GeometryModel3D)modelGroupScull.Children[0];
and I also have _3DTools that can draw lines from point-to-point in 3d space. now to draw a wireframe of my GeometryModel3D I guess I have to cycle to its vertexes and add them to ScreenSpaceLines3D.
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
// need to cycle through all vertexes of modelScull as Points, to add them to wireframe
wireframe.Points.Add(new Point3D(1, 2, 3));
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 3;
Viewport3D1.Children.Add(wireframe);
But... how do I actually get this vertex points?
EDIT:
Thanks for the answer. It did add the points
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
MeshGeometry3D mg3 = (MeshGeometry3D)modelScull.Geometry;
foreach (Point3D point3D in mg3.Positions)
{
wireframe.Points.Add(point3D);
}
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 1;
Viewport3D1.Children.Add(wireframe);
but the wireframe is messed up )
(source: gyazo.com)
maybe someone knows of other ways to draw wireframes? )
Normally the trigons are drawn with index buffers (to prevent extra rotations of vertices) Take a look at the TriangleIndices:
if you do something like this: (not tested it)
MeshGeometry3D mg3 = (MeshGeometry3D)modelScull.Geometry;
for(int index=0;index<mg3.TriangleIndices.Count; index+=3)
{
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index+1]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index+2]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index]]);
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 1;
Viewport3D1.Children.Add(wireframe);
}
But, this can create some overdraw (2 lines on the same coordinates) and probably very slow.
If you put each side into a list and use something like a Distinct on it, it will be better.
The problem with the ScreenSpaceLines3D is that will continue the line, instead of create 1 line (start/end).
If you can manage a algoritm that tries to draw you model with 1 line, it will go faster.
Wireframes are very slow in WPF. (because they are created with trigons)
You should find the vertex points in MeshGeometry3D.Positions Property
foreach (var point3D in modelScull.Geometry.Positions)
How to draw the spring like shape using c# drawing class
alt text http://img812.imageshack.us/img812/373/spring.jpg
First of all you'd need to think of a formula that would represent the spring. You could draw a circle and as you're going around it, let the X increase a bit. For instance:
for (double i = 0; i < 50; i += 0.01)
{
int x = (int)(Math.Sin(i) * 10 + i * 3);
int y =(int)(Math.Cos(i) * 10 + 50);
}
See the i variable there as time, and the result x and y the coordinates to draw; you'd traverse the path of the spring in small steps.
You could then create a new Bitmap and use the SetPixel method on those coordinates, and in the OnPaint method of your form, draw the bitmap on it.
If you're any good with math (I'm not :P) you might be able to only plot pixels inside the bitmap - the above example doesn't solve the problem of the minimum and maximum values for i.
This is more of a math problem than a C# one. What you want is to derive a Parametric equation for the curve you wish to draw.
With that go and fill an array of Point objects with values for the parametric equation on a certain interval with a certain step (the smaller the step the more the final drawing will look like the actual shape). Then you can use g.DrawLines (MSDN: DrawLines) to draw the actual curve on a surface.
You can edit the width, color and other properties of the line by modifying parameters of the Pen object.
Your actual code would look like this:
void DrawSpring (Graphics g)
{
List<Point> points = new List<Point>();
double step = 0.01;
for(double t = -2; t < 2; t += step)
{
Point p = new Point();
p.X = XPartOfTheEquation(t);
p.Y = YPartOfTheEquation(t);
points.Add(p);
}
g.DrawLines(new Pen(new SolidBrush(Color.Black), 2f), points.ToArray());
}