Arc Segment between two points and a radius - c#

I am trying to paint an arc segment using WPF, but I somehow cannot figure out how to do this using the ArcSegment-Element.
I have two points of the arc given (P1 and P2) and I also have the center of the circle and the radius.

I know this is a little old, but here goes a code version for a center and two angles - can be adapted to start and end points easily:
Outline:
Make a path and set the "top left" to 0,0 (You can still have center be negative if you want - this is just for path reference)
Set up the boiler plate PathGeo and PathFigure (these are building blocks of the multi-segment path in Word etc.)
Do angle checking
If it is > 180 deg (pi radians), call it a "large angle"
Find begining and end points and set them
Based upon the math, it is a clockwise rotation (remember that positive Y is down, positive X is right)
Set to canvas
public void DrawArc(ref Path arc_path, Vector center, double radius, double start_angle, double end_angle, Canvas canvas)
{
arc_path = new Path();
arc_path.Stroke = Brushes.Black;
arc_path.StrokeThickness = 2;
Canvas.SetLeft(arc_path, 0);
Canvas.SetTop(arc_path, 0);
start_angle = ((start_angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
end_angle = ((end_angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
if(end_angle < start_angle){
double temp_angle = end_angle;
end_angle = start_angle;
start_angle = temp_angle;
}
double angle_diff = end_angle - start_angle;
PathGeometry pathGeometry = new PathGeometry();
PathFigure pathFigure = new PathFigure();
ArcSegment arcSegment = new ArcSegment();
arcSegment.IsLargeArc = angle_diff >= Math.PI;
//Set start of arc
pathFigure.StartPoint = new Point(center.X + radius * Math.Cos(start_angle), center.Y + radius * Math.Sin(start_angle));
//set end point of arc.
arcSegment.Point = new Point(center.X + radius * Math.Cos(end_angle), center.Y + radius * Math.Sin(end_angle));
arcSegment.Size = new Size(radius, radius);
arcSegment.SweepDirection = SweepDirection.Clockwise;
pathFigure.Segments.Add(arcSegment);
pathGeometry.Figures.Add(pathFigure);
arc_path.Data = pathGeometry;
canvas.Children.Add(arc_path);
}

Create a PathFigure with P1 as StartPoint and an ArcSegment with P2 as Point and a quadratic Size that contains the radius.
Example: P1 = (150,100), P2 = (50,50), Radius = 100, i.e. Size=(100,100):
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="150,100">
<ArcSegment Size="100,100" Point="50,50"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
or shorter:
<Path Stroke="Black" Data="M150,100 A100,100 0 0 0 50,50"/>

Related

How can I draw a circle behind another circle on the same line slope?

I have a line and a circle at the end of the line, like in this image:
I draw the first circle and center this circle is end of first line.
How can I draw another circle behind the first one and on the same slope of line? I tried this code:
float y2 = m * x - m * x1 + y1; //new
p1 = new PointF(x1, y1); //x and y for first line line
PointF p2 = new PointF();
p2 = new PointF(x2, y2); //make the new x,y as point
g.DrawLine(penTow, p1, p2); //this should draw line from center first circle to center of second circle
g.DrawEllipse(penOrginal,
(float)(p2.X - radius), (float)(p2.Y - radius),
(float)(radius * 2), (float)(radius * 2)); //draw the second circle and center is the end of second line
When I tried it the circle was too far from the first circle
var slope = Math.Atan2(p2.X - p1.X, p2.Y - p1.Y);
var nextX = p2.X + radus * 2 * Math.Sin(slope);
var nextY = p2.Y + radus * 2 * Math.Cos(slope);
e.Graphics.DrawEllipse(Pens.Black,
(float)(nextX - radus), (float)(nextY - radus),
(float)(radus * 2), (float)(radus * 2));

Drawing a triangle with rounded corners

So here's my code to draw a triangle:
Graphics y = CreateGraphics();
Pen yy = new Pen(Color.Red);
pictureBox1.Refresh();
Point x = new Point(Convert.ToInt32(textBox1.Text)*10, Convert.ToInt32(textBox2.Text)*10);
Point xx = new Point(Convert.ToInt32(textBox3.Text)*10, Convert.ToInt32(textBox4.Text)*10);
Point xxx = new Point(Convert.ToInt32(textBox5.Text)*10, Convert.ToInt32(textBox6.Text)*10);
Point[] u = { x, xx, xxx };
y = pictureBox1.CreateGraphics();
y.DrawPolygon(yy, u);
Is there any way to round the corners on this? I read about it on google and it seems there's only a way to round rectangles, but not triangles.
Is there any command to do that for me or I have to do it manually?
Thanks for replies :)
It is not possible out of the box.
You can create rounded polygons like this:
Change each line by making it shorter (on both ends) by your chosen number of pixels, now line AB is line A1B1..
Create a GraphicsPath which consists of these lines and in between each pair of lines a curve that connects those new endpoints
To better control the curves it would help to add a point in the middle between the original corners and the connection of the new endpoints
For a Triangle ABC this would mean to
Create A1, A2, B1, B2, C1, C2
Then calculate the middle points A3, B3, C3
and finally adding to a GraphicsPath:
Line A1B1, Curve B1B3B2, Line B2C1, Curve C1C3C2, Line C2A2 and Curve A2A3A1.
Here is a piece of code that uses this method:
A GraphicsPaths to be used in the Paint event:
GraphicsPath GP = null;
A test method with a list of points, making up a triangle - (*) we overdraw by two points to make things easier; one could add the final lines and curves by ading a few lines of code instead..
private void TestButton_Click(object sender, EventArgs e)
{
Point A = new Point(5, 50);
Point B = new Point(250, 100);
Point C = new Point(50, 250);
List<Point> points = new List<Point>();
points.Add(A);
points.Add(B);
points.Add(C);
points.Add(A); // *
points.Add(B); // *
GP = roundedPolygon(points.ToArray(), 20);
panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (GP == null) return;
using (Pen pen = new Pen(Brushes.BlueViolet, 3f))
e.Graphics.DrawPath(pen, GP);
}
GraphicsPath roundedPolygon(Point[] points, int rounding)
{
GraphicsPath GP = new GraphicsPath();
List<Line> lines = new List<Line>();
for (int p = 0; p < points.Length - 1; p++)
lines.Add( shortenLine(new Line(points[p], points[p+1]), rounding) );
for (int l = 0; l < lines.Count - 1; l++)
{
GP.AddLine(lines[l].P1, lines[l].P2);
GP.AddCurve(new Point[] { lines[l].P2,
supPoint(lines[l].P2,points[l+1], lines[l+1].P1),
lines[l+1].P1 });
}
return GP;
}
// a simple structure
struct Line
{
public Point P1; public Point P2;
public Line(Point p1, Point p2){P1 = p1; P2 = p2;}
}
// routine to make a line shorter on both ends
Line shortenLine(Line line, int byPixels)
{
float len = (float)Math.Sqrt(Math.Pow(line.P1.X - line.P2.X, 2)
+ Math.Pow(line.P1.Y - line.P2.Y, 2));
float prop = (len - byPixels) / len;
Point p2 = pointBetween(line.P1, line.P2, prop);
Point p1 = pointBetween(line.P1, line.P2, 1 - prop);
return new Line(p1,p2);
}
// with a proportion of 0.5 the point sits in the middle
Point pointBetween(Point p1, Point p2, float prop)
{
return new Point((int)(p1.X + (p2.X - p1.X) * prop),
(int)(p1.Y + (p2.Y - p1.Y) * prop));
}
// a supporting point, change the second prop (**) to change the rounding shape!
Point supPoint(Point p1, Point p2, Point p0)
{
Point p12 = pointBetween(p1, p2, 0.5f);
return pointBetween(p12, p0, 0.5f); // **
}
Example:
I think if you adjust your coordinates appropriately and specify a Width for your Pen greater than 1 and set the LineJoin property to Rounded, you should obtain a triangle with rounded corners.
I fought with the math for a bit, then I had an idea...overlaid shapes.
<Canvas >
<Polyline Margin="-1 0 0 0"
Fill="{StaticResource Yellow}"
Points="3,14.5 17,14.5 10,2 3,14.5"
StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Stroke="Black"
StrokeThickness="4"/>
<Polyline Margin="-1 0 0 0"
Fill="{StaticResource Yellow}"
Stroke="{StaticResource Yellow}"
Points="3,14.5 17,14.5 10,2 3,14.5"
StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
StrokeThickness="3"/>
</Canvas>
public GraphicsPath DrawRoundedTriangle(RectangleF Rect, PointF point_A, PointF point_B, PointF point_C, float Radius)
{
Radius = (Rect.Height / 9f) * (Radius * 0.2f);
float Diameter = Radius * 2f;
float arcSize = (float)((Diameter / 2f) * Math.Sqrt(3.0));
GraphicsPath path = new GraphicsPath();
float sweep_angle = 120f; //Angle of Arc in the corners
if (Radius == 0f)
{
//Drawing of triangle without a rounded corner
path.AddLine(point_A, point_B);
path.AddLine(point_B, point_C);
path.AddLine(point_C, point_A);
}
else
{
PointF[] array = new PointF[6];
array[0] = new PointF(point_A.X + Radius, point_A.Y - arcSize); //Left Vertex point
array[1] = new PointF(point_B.X - Radius, point_B.Y + arcSize); //Top Vertex point
array[2] = new PointF(point_B.X + Radius, point_B.Y + arcSize); //Top Vertex point
array[3] = new PointF(point_C.X - Radius, point_C.Y - arcSize); //Right Vertex point
array[4] = new PointF(point_C.X - arcSize, point_C.Y); //Right Vertex point
array[5] = new PointF(point_A.X + arcSize, point_C.Y); //Left Vertex point
//Path Creation for Drawing
path.AddArc( (array[5].X - Radius), (array[5].Y - Diameter), Diameter, Diameter, 90f, sweep_angle); //Left Vertex
path.AddLine(array[0], array[1]); //Left Side Line
path.AddArc(array[1].X, ((array[1].Y - arcSize) + Radius), Diameter, Diameter, 210f, sweep_angle); //Top Vertex
path.AddLine(array[2], array[3]); //Right Side Line
path.AddArc((array[3].X - arcSize), (array[3].Y - (Diameter - arcSize)), Diameter, Diameter, 330f, sweep_angle); //Right Vertex
path.AddLine(array[4], array[5]); //Bottom Line
}
path.CloseFigure();
return path;
}

Draw a circle on a map by radius and angle

Im using Microsoft visual studio 2010, with the refernce dynamic data display.
I would like to draw a circle on a map, i have 2 points, one of them is the center of the circle and the other is the point on the circle, the distance between them is the radius of the circle.
the result should look like this :
http://sizmedia.com/my.php?i=mjmynzim2nhy.png
my result when I draw a circle with one point and const distance is like this (distance = radius = 15):
http://sizmedia.com/my.php?i=hm2zuv5yyenj.png
***** I don't care if the circle will look like my result(the ellipse)
because as I understood the earth is circle and its type of reasonable. ****
but when I draw circle with distance between 2 point (distance = 3400 +) I can't see the circle that I draw.
I would love to get some help, there is my code to find distance between 2 points.
// Calculating the distance between the two points
double dLat = (ps.X - centerPoint.X) / 180 * Math.PI;
double dLong = (
double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(ps).Y.ToString()) -
double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(centerPoint).Y.ToString())) / 180 * Math.PI;
double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2)
+ Math.Cos(ps.X / 180 * Math.PI) * Math.Cos(pointLine1.X / 180 * Math.PI)
* Math.Sin(dLong / 2) * Math.Sin(dLong / 2);
double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
//Calculate radius of earth
double radiusE = 6378135; // Equatorial radius, in metres
double radiusP = 6356750; // Polar Radius
//Numerator part of function
double nr = Math.Pow(radiusE * radiusP * Math.Cos(ps.X / 180 * Math.PI), 2);
//Denominator part of the function
double dr = Math.Pow(radiusE * Math.Cos(ps.X / 180 * Math.PI), 2)
+ Math.Pow(radiusP * Math.Sin(ps.X / 180 * Math.PI), 2);
double radius = Math.Sqrt(nr / dr);
//Calculate distance in meters.
distance = (radius * c); // resualt in meters
distance /= 1000; // resualt in KM
And there is my code to add the circle :
while (a < 360) // Doing one round around the point (The angels)
{
// Get the X position of the pointClicked
cx = (double)prePs.X;
// Get the Y position of the pointClicked
cy = double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(prePs).Y.ToString());
// Get the new X position of the pointClicked by the angel with math calculation
xEndP = (float)(distance * Math.Cos(a * Math.PI / 180F)) + cx;
// Get the new Y position of the pointClicked by the angel with math calculation
yEndP = (float)(distance * Math.Sin(a * Math.PI / 180F)) + cy;
// Creating the new point
globalPoint = new DraggablePoint(new Point(xEndP, yEndP));
globalPoint.Position = new Point(xEndP, yEndP);
globalPoint.Visibility = Visibility.Visible;
// Increas the angel
a++;
//Creat new point on the circle with new angel
xEndPNext = (float)(distance * Math.Cos(a * Math.PI / 180F)) + cx;
yEndPNext = (float)(distance * Math.Sin(a * Math.PI / 180F)) + cy;
// Creat line between the two new points that we creat now
segmentHelper = new Segment(new Point(xEndP, yEndP), new Point(xEndPNext, yEndPNext));
// Brush between the points by line
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(255, 47, 79, 49);
segmentHelper.Stroke = mySolidColorBrush;
// Add the line to the chartplotter
plotter.Children.Add(segmentHelper);
// Add the angel
a++;
}
My algorithm is take one point, and the next point and to draw line between them ( when the points are visiblty false) and then i get a nice circle.
Thank you very much :)

Drawing DXF Arc using WPF ArcSegment

I'm trying to draw an imported DXF file using WPF over a canvas.
I've been using DXFLib (available here) to read and parse various files and it seems to work really well.
I'm now in the process to draw all the entities in the DXF but I'm stuck with ARC. My problem is similar to the one in this post (some arcs are drawn in wrong direction):
DXF Parser : Ellipses angle direction
In the DXF file the ARC is stored as: Center, Radius, StartAngle, EndAngle; in WPF the ArcSegment is instead described as: StartPoint, EndPoint, Size, IsLargeArc and SweepDirection.
The latter is the one that I think cause the problem, as I'm not able to determine direction by the DXF File. In the above question is stated to use the "Extrude Direction" which it happen NOT to be included in my file.
Following the relevant code:
private Load(string filename)
{
DXFLib.DXFDocument doc = new DXFLib.DXFDocument();
doc.Load(filename);
if (doc.Entities.Count > 0)
{
foreach (DXFLib.DXFEntity entity in doc.Entities)
{
if (entity is DXFLib.DXFLine)
{
DXFLib.DXFLine line = (DXFLib.DXFLine)entity;
PointF start = new PointF((float)line.Start.X, (float)line.Start.Y);
PointF end = new PointF((float)line.End.X, (float)line.End.Y);
Line drawLine = new Line();
drawLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
drawLine.X1 = end.X * scaleX;
drawLine.X2 = start.X * scaleX;
drawLine.Y1 = end.Y * scaleY;
drawLine.Y2 = start.Y * scaleY;
drawLine.StrokeThickness = 1;
canvas.Children.Add(drawLine);
}
else if (entity is DXFLib.DXFCircle)
{
DXFLib.DXFCircle circle = (DXFLib.DXFCircle)entity;
Ellipse drawCircle = new Ellipse();
SolidColorBrush solidBrush = new SolidColorBrush();
solidBrush.Color = System.Windows.Media.Color.FromArgb(255, 255, 255, 0);
drawCircle.Fill = solidBrush;
drawCircle.StrokeThickness = 1;
drawCircle.Stroke = System.Windows.Media.Brushes.RoyalBlue;
drawCircle.Width = circle.Radius * 2 * scaleX;
drawCircle.Height = circle.Radius * 2 * scaleY;
drawCircle.Margin = new Thickness((circle.Center.X.Value - circle.Radius) * scaleX, (circle.Center.Y.Value - circle.Radius) * scaleY, 0, 0);
canvas.Children.Add(drawCircle);
}
else if (entity is DXFLib.DXFArc)
{
DXFLib.DXFArc arc = (DXFLib.DXFArc)entity;
Path path = new Path();
path.Stroke = System.Windows.Media.Brushes.Black;
path.StrokeThickness = 1;
System.Windows.Point endPoint = new System.Windows.Point(
(arc.Center.X.Value + Math.Cos(arc.EndAngle * Math.PI / 180) * arc.Radius) * scaleX,
(arc.Center.Y.Value + Math.Sin(arc.EndAngle * Math.PI / 180) * arc.Radius) * scaleY);
System.Windows.Point startPoint = new System.Windows.Point(
(arc.Center.X.Value + Math.Cos(arc.StartAngle * Math.PI / 180) * arc.Radius) * scaleX,
(arc.Center.Y.Value + Math.Sin(arc.StartAngle * Math.PI / 180) * arc.Radius) * scaleY);
ArcSegment arcSegment = new ArcSegment();
double sweep = 0.0;
if (arc.EndAngle < arc.StartAngle)
sweep = (360 + arc.EndAngle) - arc.StartAngle;
else sweep = Math.Abs(arc.EndAngle - arc.StartAngle);
arcSegment.IsLargeArc = sweep >= 180;
arcSegment.Point = endPoint;
arcSegment.Size = new System.Windows.Size(arc.Radius * scaleX, arc.Radius * scaleY);
arcSegment.SweepDirection = arc.ExtrusionDirection.Z >= 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
PathGeometry geometry = new PathGeometry();
PathFigure pathFigure = new PathFigure();
pathFigure.StartPoint = startPoint;
pathFigure.Segments.Add(arcSegment);
geometry.Figures.Add(pathFigure);
path.Data = geometry;
canvas.Children.Add(path);
}
}
}
}
The XAML file is sample as:
<Window x:Class="DxfViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="canvas">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5" />
</Canvas.LayoutTransform>
</Canvas>
</Window>
** UPDATE **
The issue has been resolved in the following way:
ArcSegment arcSegment = new ArcSegment();
double sweep = 0.0;
if (arc.EndAngle < arc.StartAngle)
sweep = (360 + arc.EndAngle) - arc.StartAngle;
else sweep = Math.Abs(arc.EndAngle - arc.StartAngle);
arcSegment.IsLargeArc = sweep >= 180;
arcSegment.Point = endPoint;
arcSegment.Size = new System.Windows.Size(arc.Radius * scaleX, arc.Radius * scaleY);
arcSegment.SweepDirection = arc.ExtrusionDirection.Z >= 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
Here is the DXF reference for arcs according to AutoDesk:
Group codes Description
100
Subclass marker (AcDbCircle)
39
Thickness (optional; default = 0)
10
Center point (in OCS)
DXF: X value; APP: 3D point
20, 30
DXF: Y and Z values of center point (in OCS)
40
Radius
100
Subclass marker (AcDbArc)
50
Start angle
51
End angle
210
Extrusion direction. (optional; default = 0, 0, 1)
DXF: X value; APP: 3D vector
220, 230
DXF: Y and Z values of extrusion direction (optional)
The author of DXFLib accounted for these in his DxfArc class, so are these values not set at all at runtime? I don't see anything in his code for setting a default value, which probably needs to be added since AutoCAD is making the assumption.
The extrusion direction has to be stored as values 220, 230, otherwise the default should ALWAYS work. If that's not the case, I'd take a close look at how these DFX files are being run. Are they an earlier release that doesn't support this operation?
Update:
I think the DxfArc class really should be modified to set a null ExtrusionDirection to {0, 0, 1} based on the code and your project. I modified your main routine with the following changes and it appears to work correctly:
// Changing the class will make this less ugly
var arc = (DXFLib.DXFArc)entity;
if (arc.ExtrusionDirection.X == null ||
arc.ExtrusionDirection.Y == null ||
arc.ExtrusionDirection.Z == null)
{
arc.ExtrusionDirection.X = 0;
arc.ExtrusionDirection.Y = 0;
arc.ExtrusionDirection.Z = 1;
}
arcSegment.SweepDirection = arc.ExtrusionDirection.Z > 0
? SweepDirection.Clockwise
: SweepDirection.Counterclockwise;
When you look at the Arc in AutoCAD, the ExtrusionDirection is listed as 'Normal' under the entity's properties.
I would like to modify your solution since the situation was exactly same in my case and this solved it.
ARC goes always counter clockwise around the extrusion vector, which is (0, 0, 1) by default and the usual case for 2D arcs.
arcSegment.SweepDirection = arc.ExtrusionDirection.Z > 0
? SweepDirection.Counterclockwise
: SweepDirection.Clockwise;

Rotating point by angle

I know that the theory of rotating a point by an angle is on the internet a million times, but I don't get my code to work properly.
I have a line with 2 points, when you click on 1 of the 2 points, you will rotate the point relative to the other point. In my testcase I have a upper left point and a bottom right point, so a diagonal line.
I want to make sure the line snaps to a 90 degrees rotation so It will always be a straight line (either vertically or horizontally). So I first get the current angle, then get the angle that It should be, and calculate the difference.
Point startPoint = obj2.Location;
Point currentEndPoint = new Point(obj2.Location.X + obj2.Size.Width, obj2.Location.Y + obj2.Size.Height);
Point newEndPoint = e.Location;
double angle = MathHelper.GetAngleOfVerticalLine(startPoint, newEndPoint);
double angleToBe = MathHelper.GetClosestNumber(angle, new double[] { 0, 90, 180, 270, 360 });
double angleToDo = 0.0; // -5
if (angle < angleToBe)
{
angleToDo = Math.Abs(angle - angleToBe);
}
else
{
angleToDo = angleToBe - angle;
}
angleToDo %= 360;
Point newSize = MathHelper.RotatePoint(newEndPoint, startPoint, angleToDo);
obj.Size = (Size)newSize;
public static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
(cosTheta * (pointToRotate.X - centerPoint.X) -
sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
Y =
(int)
(sinTheta * (pointToRotate.X - centerPoint.X) +
cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
};
}
But the results that I get, are not straight lines but they are kind of random. The angle, angleToBe and angleToDo work properly. The RotatePoint method should be the problem then, but I'm not 100% sure about that.
Can't you use the Matrix.Rotate class to do the heavy lifting? Source: http://msdn.microsoft.com/en-us/library/s0s56wcf.aspx (Of course the math is half the fun).

Categories