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;
Related
For a while now I've been using the following function to rotate a series of Points around a pivot point in various programs of mine.
private Point RotatePoint(Point point, Point pivot, double radians)
{
var cosTheta = Math.Cos(radians);
var sinTheta = Math.Sin(radians);
var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);
return new Point((int)x, (int)y);
}
This has always worked great, until I tried to rotate a shape repeatedly by small amounts. For example, this is what I get from calling it for 45° on a rectangular polygon made up of 4 points:
foreach (var point in points)
Rotate(point, center, Math.PI / 180f * 45);
But this is what I get by calling rotate 45 times for 1°:
for (var i = 0; i < 45; ++i)
foreach (var point in points)
Rotate(point, center, Math.PI / 180f * 1)
As long as I call it only once it's fine, and it also seems like it gets gradually worse the lower the rotation degree is. Is there some flaw in the function, or am I misunderstanding something fundamental about what this function does?
How could I rotate repeatedly by small amounts? I could save the base points and use them to update the current points whenever the rotation changes, but is that the only way?
Your Point position measure is off because of the integer rounding generated by the RotatePoint() method.
A simple correction in the method returned value, using float coordinates, will produce the correct measure:
To test it, create a Timer and register its Tick event as RotateTimerTick():
Added a rotation spin increment (see the rotationSpin Field) to emphasize the motion effect.
PointF pivotPoint = new PointF(100F, 100F);
PointF rotatingPoint = new PointF(50F, 100F);
double rotationSpin = 0D;
private PointF RotatePoint(PointF point, PointF pivot, double radians)
{
var cosTheta = Math.Cos(radians);
var sinTheta = Math.Sin(radians);
var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);
return new PointF((float)x, (float)y);
}
private void RotateTimerTick(object sender, EventArgs e)
{
rotationSpin += .5;
if (rotationSpin > 90) rotationSpin = 0;
rotatingPoint = RotatePoint(rotatingPoint, pivotPoint, (Math.PI / 180f) * rotationSpin);
Panel1.Invalidate(new Rectangle(new Point(50,50), new Size(110, 110)));
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillEllipse(Brushes.White, new RectangleF(100, 100, 8, 8));
e.Graphics.FillEllipse(Brushes.Yellow, new RectangleF(rotatingPoint, new SizeF(8, 8)));
}
This is the result using float values:
And this is what happens using integer values:
If you want you can use the Media3D to only deal with matrix and simplify the coding. Something as simple as this will work.
public Point3D Rotate(Point3D point, Point3D rotationCenter, Vector3D rotation, double degree)
{
// create empty matrix
var matrix = new Matrix3D();
// translate matrix to rotation point
matrix.Translate(rotationCenter - new Point3D());
// rotate it the way we need
matrix.Rotate(new Quaternion(rotation, degree));
// apply the matrix to our point
point = matrix.Transform(point);
return point;
}
Then you simply call the method and specify the rotation. Lets say you work with 2D (like in your example) and lets assume we work with XY plane so the rotation is in Z. You can do something like :
var rotationPoint = new Point3D(0, 0, 0);
var currentPoint = new Point3D(10, 0, 0);
// rotate the current point around the rotation point in Z by 45 degree
var newPoint = Rotate(currentPoint, rotation, new Vector3D(0, 0, 1), 45d);
I am writing an application that has a panel in which I display:
Picture background
Lots of drawn objects (using GDI+ lines, paths, etc)
Said objects have some hit detection in mouse move event.
The panel pans and zooms. The panel is double buffered.
So everything is working pretty well - things look good, no flicker, but I am at the point where I am trying to focus on performance. I take less than 1 ms to draw my objects (per object) BUT when zoomed out I can have upwards of 500 objects to draw, which starts to make everything from the drawing to the hit detection sluggish.
I have already done a few things to try to improve performance like making a list of only the on screen (drawable) objects - but as previously mentioned, when zoomed out, it can still be LOTS. I got the grand idea that maybe instead of updating EVERY object EVERY time, I could implement a simple Invalidate() which would tell the object its made a visual change worthy of drawing. In the object paint code (which is passed e.graphics), I attempted to set the clipping region to the size of the object and only update that portion of the panel BG (also when done, I reset the e.Grpahics.clip). The result was only the invalidated object paints - everything else is blank.
I am confident this is the C#/GDI noob coming out in me and you all will tell me what stupid mistake I've probably overlooked.
At the time of writing this, I realize that I can save some time by make the path of my object constant instead of making it fresh every time in the draw function (since all objects paths are identical). I'll update with time I shave off shortly.
Here is the code that paints the BG image (and determines which OBJS to paint) and the OBJ paint code.
private void PaintImage(PaintEventArgs e)
{
int scale = 10; //TARGET ICON BASE SCALE....
var watch2 = System.Diagnostics.Stopwatch.StartNew();
if (bitmap != null && redrawBG)
{
float widthZoomed = TgtPanel.Width / Zoom;
float heigthZoomed = TgtPanel.Height / Zoom;
//Do checks the reason 30,000 is used is because
//much over this will cause DrawImage to crash
if (widthZoomed > 30000.0f)
{
Zoom = TgtPanel.Width / 30000.0f;
widthZoomed = 30000.0f;
}
if (heigthZoomed > 30000.0f)
{
Zoom = TgtPanel.Height / 30000.0f;
heigthZoomed = 30000.0f;
}
//we stop at 2 because at this point you have almost zoomed into a single pixel
if (widthZoomed < 2.0f)
{
Zoom = TgtPanel.Width / 2.0f;
widthZoomed = 2.0f;
}
if (heigthZoomed < 2.0f)
{
Zoom = TgtPanel.Height / 2.0f;
heigthZoomed = 2.0f;
}
float wz2 = widthZoomed / 2.0f;
float hz2 = heigthZoomed / 2.0f;
Rectangle drawRect = new Rectangle(
(int)(viewPortCenter.X - wz2),
(int)(viewPortCenter.Y - hz2),
(int)(widthZoomed),
(int)(heigthZoomed));
e.Graphics.Clear(Color.White); //Clear the Back buffer
//Draw the image, Write image to back buffer, and [render back buffer - no longer using my own backbuffer]
e.Graphics.DrawImage(bitmap,
this.TgtPanel.DisplayRectangle, drawRect, GraphicsUnit.Pixel);
// e.Graphics.ScaleTransform(Zoom, Zoom);
// e.Graphics.DrawImage(bitmap,
// 0,0);
if (draging)
{ //line to visualize the drag
e.Graphics.DrawLine(new Pen(Color.Yellow, 10), StartDrag.X, StartDrag.Y, lastMouse.X, lastMouse.Y);
}
//this checks for offscreen - works
if (drawRect.X > iconbitmap.Width || drawRect.X < -(drawRect.Width) ||
drawRect.Y > 0 + iconbitmap.Height || drawRect.Y < -(drawRect.Height))
{
label1.Text = "OFF";
}
} //if bitmap != null & redraw
// Font and Brush for the text graphics
Point mypoint = WorldToScreen(0.75f * scale, 7.0f * scale);
RectangleF bgrect = new RectangleF();
bgrect.X = mypoint.X;
bgrect.Y = mypoint.Y;
bgrect.Width = (3.5f * scale * Zoom);
bgrect.Height = (2.0f * scale * Zoom);
int aFontSizeDefault = 40;
int aFontSizeMinimum = 2;
String adrawString = "AAA"; //test this length
Font aDefaultFont = new Font("MS Sans Serif", aFontSizeDefault, FontStyle.Regular);
FontAdjustment afa = new FontAdjustment();
Font AAdjustedFont = afa.GetAdjustedFont(e.Graphics, adrawString,
aDefaultFont, Convert.ToInt32(bgrect.Width), aFontSizeDefault, aFontSizeMinimum, true);
//DRAW TGT BG
var Point1 = ScreenToWorld(0, 0);
var Point2 = ScreenToWorld(TgtPanel.Width, TgtPanel.Height);
//getVisible Screen == on;y draw visible targets
if (redrawBG)
{
VISIBLETARGETS.Clear(); //erase visible tgts array - we're going to update it
foreach (Target TGT in THETARGETS)
if (TGT.PosX >= Point1.X - 40 && TGT.PosX <= Point2.X - 9)
if (TGT.PosY >= Point1.Y - 83 && TGT.PosY <= Point2.Y - 5)
{
TGT.OnScreen = true;
//drawTarget(TGT, AAdjustedFont, e);
VISIBLETARGETS.Add(TGT); //update as visible
}
else TGT.OnScreen = false;
//redrawBG = false;
}
var watch = System.Diagnostics.Stopwatch.StartNew();
foreach(Target TGT in VISIBLETARGETS)
{
if(TGT.Invalidated || redrawBG) // THIS IS DRAWING ONLY TGT -- NOT OTHERS, OR BG - FIX THIS WITH CLIPPING?
drawTarget(TGT, AAdjustedFont, e);
}
watch.Stop();
watch2.Stop();
var elapsedMs = watch.ElapsedMilliseconds;//ElapsedTicks;
label6.Text = "TotalDrawTime = " + watch2.ElapsedMilliseconds.ToString();
label4.Text = "AvgDrawTime = " + elapsedMs.ToString();
label5.Text = "VisibleTgts = " + VISIBLETARGETS.Count.ToString();
AAdjustedFont.Dispose();
aDefaultFont.Dispose();
//------------- DRAWING TGT WITH GDI - WITH ORIGINAL BACKBUFFER
/// myBuffer.Render(this.TgtPanel.CreateGraphics());
redrawBG = false;
}
public void drawTarget(Target Tgt, Font AAdjustedFont, PaintEventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
const float scale = 10; //10 is at 1 zoom
var bgrect = new RectangleF();
Point mypoint = WorldToScreen(Tgt.PosX + 0.75f * scale, Tgt.PosY + 1.0f * scale);
bgrect.X = mypoint.X;
bgrect.Y = mypoint.Y;
bgrect.Width = 3.5f * scale * Zoom;
bgrect.Height = 7.5f * scale * Zoom;
//PLAY WITH CLIP
e.Graphics.Clip = new Region(bgrect);
//var hbrush = new HatchBrush(HatchStyle.DarkDownwardDiagonal, Color.White);
var hbrush = new SolidBrush(Color.White);
//if(WantToDrawIconBG() ....
//e.Graphics.FillRectangle(hbrush, bgrect); //ICON BACKGROUND
//ADDR RECT
// mypoint = WorldToScreen(0, Tgt.PosY + 7.0f * scale);
mypoint = WorldToScreen(0, Tgt.PosY + 6.90f * scale); //moved Y up a bit from above
bgrect.Y = mypoint.Y;
bgrect.Height = 1.5f * scale * Zoom;
/////brush.Color = (Color.GhostWhite);
e.Graphics.FillRectangle(hbrush, bgrect);
hbrush.Dispose();
string adrawString = Tgt.Address;
System.Drawing.Font adrawFont = new System.Drawing.Font("Arial", 16);
System.Drawing.SolidBrush adrawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Red);
System.Drawing.StringFormat adrawFormat = new System.Drawing.StringFormat();
adrawFormat.Alignment = StringAlignment.Center;
e.Graphics.DrawString(adrawString, AAdjustedFont, adrawBrush, bgrect, adrawFormat); //draw addr
//======= LETS MAKE THE TGT ICON SHAPE =======
GraphicsPath path = new GraphicsPath(FillMode.Alternate);
//TARGET POINTS (w = 3, h = 6)
var h1 = WorldToScreen(Tgt.PosX + 2 * scale, Tgt.PosY + 1.1f * scale);
var h2 = WorldToScreen(Tgt.PosX + 3 * scale, Tgt.PosY + 1.1f * scale);
var n1 = WorldToScreen(Tgt.PosX + 2 * scale, Tgt.PosY + 2 * scale);
var n2 = WorldToScreen(Tgt.PosX + 3 * scale, Tgt.PosY + 2 * scale);
var s1 = WorldToScreen(Tgt.PosX + 1 * scale, Tgt.PosY + 3 * scale);
var s2 = WorldToScreen(Tgt.PosX + 4 * scale, Tgt.PosY + 3 * scale);
var b1 = WorldToScreen(Tgt.PosX + 1 * scale, Tgt.PosY + 7 * scale);
var b2 = WorldToScreen(Tgt.PosX + 4 * scale, Tgt.PosY + 7 * scale);
var controln2 = WorldToScreen(Tgt.PosX + 1 * scale, (Convert.ToInt32(Tgt.PosY + 0.5 * scale)));
var controls2 = WorldToScreen(Tgt.PosX + 1 * scale, Tgt.PosY + 1 * scale);
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 255));
PointF[] npoints = { n2, s2, b2 };
PointF[] npoints2 = { n1, s1, b1 };
e.Graphics.DrawCurve(pen, npoints, 0.5f);
path.AddCurve(npoints, 0.5f); /////
e.Graphics.DrawLine(pen, b2, b1);
path.AddLine(b2, b1); /////
e.Graphics.DrawCurve(pen, npoints2, 0.5f);
path.AddCurve(npoints2, 0.5f); /////
PointF[] hpoints = { n1, h1, h2, n2 };
e.Graphics.DrawCurve(pen, hpoints, 0.1f);
path.AddCurve(hpoints, 0.1f); /////
path.CloseAllFigures();
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
if (Zoom > 0.9) //only draw stroke if big enough to see (and there arent a million targets to draw
{
pen.Color = Tgt.Selected ? Color.Chartreuse : Color.FromName(comboBox1.Text); //black default
pen.Width = Tgt.Selected ? 2 * Zoom : 1 * Zoom; //draw thicker border if selected
e.Graphics.DrawPath(pen, path); ///------------------------------------------STROKE PATH.....
//e.Graphics.FillPath(new SolidBrush(Color.ForestGreen), path);
}
// how much time is wasted making 2 gradient brushes? One we wont even use.
LinearGradientBrush linGrBrush = new LinearGradientBrush(
WorldToScreen(Tgt.PosX + 0 * scale, Tgt.PosY + 5 * scale),
WorldToScreen(Tgt.PosX + 5.5f * scale, Tgt.PosY + 5 * scale),
Color.ForestGreen, // Opaque red
Color.FromArgb(255, 0, 255, 0)); // Opaque blue
LinearGradientBrush linRedBrush = new LinearGradientBrush(
WorldToScreen(Tgt.PosX + 0 * scale, Tgt.PosY + 5 * scale),
WorldToScreen(Tgt.PosX + 5.5f * scale, Tgt.PosY + 5 * scale),
Color.FromArgb(255, 255, 0, 0), // Opaque red
Color.Firebrick); // Opaque blue
//FILL TARGET ICON WITH COLOR - UP or DOWN
if (Tgt.IsUp) e.Graphics.FillPath(linGrBrush, path);
else e.Graphics.FillPath(linRedBrush, path);
//------------
//tgt lines (cosmetic only)
if (Zoom > 0.9) //only draw if big enough to see (and there arent a million targets to draw
{
var transPen = new Pen(Color.FromArgb(150, 200, 200, 200));
var l1a = WorldToScreen(Tgt.PosX + 2.5f * scale, Tgt.PosY + 1.5f * scale);
var l1b = WorldToScreen(Tgt.PosX + 2.5f * scale, Tgt.PosY + 6 * scale);
e.Graphics.DrawLine(transPen, l1a, l1b);
var l2a = WorldToScreen(Tgt.PosX + 1.5f * scale, Tgt.PosY + 2.5f * scale);
var l2b = WorldToScreen(Tgt.PosX + 1.5f * scale, Tgt.PosY + 6.5f * scale);
e.Graphics.DrawLine(transPen, l2a, l2b);
var l3a = WorldToScreen(Tgt.PosX + 3.5f * scale, Tgt.PosY + 2.5f * scale);
var l3b = WorldToScreen(Tgt.PosX + 3.5f * scale, Tgt.PosY + 6.5f * scale);
e.Graphics.DrawLine(transPen, l3a, l3b);
}
//Draw Hits....
mypoint = WorldToScreen(Tgt.PosX + 1.0f * scale, Tgt.PosY + 3.0f * scale);
bgrect.X = mypoint.X;
bgrect.Y = mypoint.Y;
bgrect.Width = 3.0f * scale * Zoom;
bgrect.Height = 1.5f * scale * Zoom;
adrawString = Tgt.Hits.ToString();
adrawFormat.Alignment = StringAlignment.Center;
if (Zoom > 0.9) //only draw if big enough to see (and there arent a million targets to draw
{
adrawBrush.Color = Color.FromArgb(100, 100, 100, 100);
e.Graphics.FillRectangle(adrawBrush, bgrect);
}
adrawBrush.Color = Color.White;
e.Graphics.DrawString(adrawString, AAdjustedFont, adrawBrush, bgrect, adrawFormat); //draw hits
//Draw Score....
mypoint = WorldToScreen(Tgt.PosX + 1.0f * scale, Tgt.PosY + 5.0f * scale);
bgrect.X = mypoint.X;
bgrect.Y = mypoint.Y;
bgrect.Width = 3.0f * scale * Zoom;
bgrect.Height = 1.5f * scale * Zoom;
adrawString = Tgt.Score.ToString();
adrawFormat.Alignment = StringAlignment.Center;
if (Zoom > 0.9) //only draw if big enough to see (and there arent a million targets to draw
{
adrawBrush.Color = Color.FromArgb(100, 100, 100, 100);
e.Graphics.FillRectangle(adrawBrush, bgrect);
}
adrawBrush.Color = Color.White;
e.Graphics.DrawString(adrawString, AAdjustedFont, adrawBrush, bgrect, adrawFormat); //draw hits
adrawFont.Dispose();
adrawBrush.Dispose();
adrawFormat.Dispose();
path.Dispose();
watch.Stop();
var elapsedMs = watch.ElapsedTicks;
//14279 original ticks
//12764 removing label and reducing font size calcs...
// 1695 ! removed font size to external calc so it happens only once
e.Graphics.ResetClip();
e.Graphics.Clip.Dispose();
Tgt.Invalidated = false; //dont draw again until change
}
I'm working on a method to programatically draw equilateral polygon shapes in C#, WPF. But I have been stuck and I can't solve calculating the angles. Where is the problem? How should I correct this? I have given the public int, R(radius) a value of 100.
private Path EquilateralPolygon(int sides)
{
//Centering
Point center = new Point(canvasSize.Width / 2, canvasSize.Height / 2);
PathFigure myPathFigure = new PathFigure();
int alfa = 360 / sides;
int[] angles = new int[6];
for (int i = 0; i < sides; i++)
angles[i] = 360 - alfa * i;
MessageBox.Show(angles.Sum().ToString());
Point A = new Point(center.X, center.Y - R);
myPathFigure.StartPoint = A;
PolyLineSegment myLineSegment = new PolyLineSegment();
for (int i = 1; i < sides; i++)
{
myLineSegment.Points.Add(new Point(center.X + Math.Cos(angles[i]) * R, center.Y + Math.Sin(angles[i]) * R));
}
myLineSegment.Points.Add(A);
PathSegmentCollection myPathSegmentCollection = new PathSegmentCollection();
myPathSegmentCollection.Add(myLineSegment);
myPathFigure.Segments = myPathSegmentCollection;
PathFigureCollection myPathFigureCollection = new PathFigureCollection();
myPathFigureCollection.Add(myPathFigure);
PathGeometry myPathGeometry = new PathGeometry();
myPathGeometry.Figures = myPathFigureCollection;
Path myPath = new Path();
myPath.Stroke = Brushes.Red;
myPath.StrokeThickness = 1;
myPath.Data = myPathGeometry;
return myPath;
}
You've posted a lot of code and were not specific about how it's not working, so there may be more than one issue with your code. However, one big issue is that the Math.Cos (and related trig methods) take the angle in the form of radians, not degrees as you have them.
Parameters
d
Type: System.Double An angle, measured in radians.
You will need to convert them to radians. To convert, multiply by π (available via Math.PI) then divide by 180 degrees.
myLineSegment.Points.Add(
new Point(center.X + Math.Cos(angles[i] * Math.PI / 180.0) * R,
center.Y + Math.Sin(angles[i] * Math.PI / 180) * R));
EDIT: In addition to the radians/degrees issue, I can see you may be experiencing integer truncation, both in the use of your angles array and your calculation of alfa. I would suggest you try changing your use of integers to double so that your code works fine with fractions of a degree.
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"/>
i am currently try to inscribe diagonals of a decagon inside a circle
like this
in c# my approach would be creating a circle
e.Graphics.DrawEllipse(myPen, 0, 0, 100, 100);
and draw lines inside using
e.Graphics.DrawLine(myPen, 20, 5, 50, 50);
after that i would draw a decagon polygon.
currently im stuck at how to divide the circle into 10 parts/ finding the correct coordiantes of the points on the circumference of the circles because im not good in math,
i want to know how would i know the next point in a circumference of the circle the size of my circle is indicated above.
and also i want also to ask a better approach for my problem.
Thank you :)
Just for grits and shins, here's a generic implementation that will inscribe an X-sided polygon into the Rectangle you pass it. Note that in this approach I'm not actually calculating any absolute points. Instead, I am translating the origin, rotating the surface, and drawing the lines only with respect to the origin using a fixed length and an angle. This is repeated in a loop to achieve the end result below, and is very similar to commanding the Turtle in Logo:
public partial class Form1 : Form
{
PictureBox pb = new PictureBox();
NumericUpDown nud = new NumericUpDown();
public Form1()
{
InitializeComponent();
this.Text = "Inscribed Polygon Demo";
TableLayoutPanel tlp = new TableLayoutPanel();
tlp.RowCount = 2;
tlp.RowStyles.Clear();
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlp.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
tlp.ColumnCount = 2;
tlp.ColumnStyles.Clear();
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
tlp.Dock = DockStyle.Fill;
this.Controls.Add(tlp);
Label lbl = new Label();
lbl.Text = "Number of Sides:";
lbl.TextAlign = ContentAlignment.MiddleRight;
tlp.Controls.Add(lbl, 0, 0);
nud.Minimum = 3;
nud.Maximum = 20;
nud.AutoSize = true;
nud.ValueChanged += new EventHandler(nud_ValueChanged);
tlp.Controls.Add(nud, 1, 0);
pb.Dock = DockStyle.Fill;
pb.Paint += new PaintEventHandler(pb_Paint);
pb.SizeChanged += new EventHandler(pb_SizeChanged);
tlp.SetColumnSpan(pb, 2);
tlp.Controls.Add(pb, 0, 1);
}
void nud_ValueChanged(object sender, EventArgs e)
{
pb.Refresh();
}
void pb_SizeChanged(object sender, EventArgs e)
{
pb.Refresh();
}
void pb_Paint(object sender, PaintEventArgs e)
{
// make circle centered and 90% of PictureBox size:
int Radius = (int)((double)Math.Min(pb.ClientRectangle.Width, pb.ClientRectangle.Height) / (double)2.0 * (double).9);
Point Center = new Point((int)((double)pb.ClientRectangle.Width / (double)2.0), (int)((double)pb.ClientRectangle.Height / (double)2.0));
Rectangle rc = new Rectangle(Center, new Size(1, 1));
rc.Inflate(Radius, Radius);
InscribePolygon(e.Graphics, rc, (int)nud.Value);
}
private void InscribePolygon(Graphics G, Rectangle rc, int numSides)
{
if (numSides < 3)
throw new Exception("Number of sides must be greater than or equal to 3!");
float Radius = (float)((double)Math.Min(rc.Width, rc.Height) / 2.0);
PointF Center = new PointF((float)(rc.Location.X + rc.Width / 2.0), (float)(rc.Location.Y + rc.Height / 2.0));
RectangleF rcF = new RectangleF(Center, new SizeF(1, 1));
rcF.Inflate(Radius, Radius);
G.DrawEllipse(Pens.Black, rcF);
float Sides = (float)numSides;
float ExteriorAngle = (float)360 / Sides;
float InteriorAngle = (Sides - (float)2) / Sides * (float)180;
float SideLength = (float)2 * Radius * (float)Math.Sin(Math.PI / (double)Sides);
for (int i = 1; i <= Sides; i++)
{
G.ResetTransform();
G.TranslateTransform(Center.X, Center.Y);
G.RotateTransform((i - 1) * ExteriorAngle);
G.DrawLine(Pens.Black, new PointF(0, 0), new PointF(0, -Radius));
G.TranslateTransform(0, -Radius);
G.RotateTransform(180 - InteriorAngle / 2);
G.DrawLine(Pens.Black, new PointF(0, 0), new PointF(0, -SideLength));
}
}
}
I got the formula for the length of the side here at Regular Polygon Calculator.
One way of dealing with this is using trigonometric functions sin and cos. Pass them the desired angle, in radians, in a loop (you need a multiple of 2*π/10, i.e. a = i*π/5 for i between 0 and 9, inclusive). R*sin(a) will give you the vertical offset from the origin; R*cos(a) will give you the horizontal offset.
Note that sin and cos are in the range from -1 to 1, so you will see both positive and negative results. You will need to add an offset for the center of your circle to make the points appear at the right spots.
Once you've generated a list of points, connect point i to point i+1. When you reach the ninth point, connect it to the initial point to complete the polygon.
I don't test it, but i think it is ok.
#define DegreeToRadian(d) d * (Pi / 180)
float r = 1; // radius
float cX = 0; // centerX
float cY = 0; // centerY
int numSegment = 10;
float angleOffset = 360.0 / numSegment;
float currentAngle = 0;
for (int i = 0; i < numSegment; i++)
{
float startAngle = DegreeToRadian(currentAngle);
float endAngle = DegreeToRadian(fmod(currentAngle + angleOffset, 360));
float x1 = r * cos(startAngle) + cX;
float y1 = r * sin(startAngle) + cY;
float x2 = r * cos(endAngle) + cX;
float y2 = r * sin(endAngle) + cY;
currentAngle += angleOffset;
// [cX, cY][x1, y1][x2, y2]
}
(fmod is c++ function equals to floatNumber % floatNumber)