Win2d UWP fluid animation canvas line - c#

I have to get a fluid animation of a line drawn with Win2d. Below is the code that simulates my problem.
MainPage.xaml:
<canvas:CanvasAnimatedControl x:Name="animatedControl"
Margin="0,30,0,0"
Height="500"
Draw="OnDraw"/>
MainPage.xaml.cs:
private void OnDraw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
CanvasDrawingSession ds = args.DrawingSession;
double height = sender.Size.Height;
double width = sender.Size.Width;
Random random = new Random();
double pointHeigth = random.Next(0, 300);
var point1 = new Vector2((float)(width / 2), (float)height);
var point2 = new Vector2((float)(width / 2), (float)height - (float)pointHeigth);
CanvasSolidColorBrush brush = new CanvasSolidColorBrush(sender, Colors.Green);
ds.DrawLine(point1, point2, brush, (float)10);
}
In this way I get a flounce animation how can I get a smooth animation of the line?
I state that I have to use the canvas with win2d.
Thanks in advance.
UPDATE (My code that doesn't work)
private Vector2 _point1;
private Vector2 _point2;
private Vector2 _targetPoint;
private TimeSpan _animationElapsedTime = TimeSpan.FromMilliseconds(100);
public MainPage()
{
this.InitializeComponent();
}
private void OnDraw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
CanvasDrawingSession ds = args.DrawingSession;
double height = sender.Size.Height;
double width = sender.Size.Width;
Random random = new Random();
double pointHeigth = random.Next(0, 300);
var _point1 = new Vector2((float)(width / 2), (float)height);
var _point2 = new Vector2((float)(width / 2), (float)height - (float)pointHeigth);
CanvasSolidColorBrush brush = new CanvasSolidColorBrush(sender, Colors.Green);
ds.DrawLine(_point1, _point2, brush, (float)10);
}
private Vector2 Tween(Vector2 sourcePoint, Vector2 targetPoint, float percentage)
{
return new Vector2(targetPoint.X + (targetPoint.X - sourcePoint.X) * percentage, targetPoint.Y + (targetPoint.Y - sourcePoint.Y) * percentage);
}
private void OnUpdate(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
{
_animationElapsedTime += args.Timing.ElapsedTime;
var percentage = Math.Min(_animationElapsedTime.TotalSeconds / 5.0f, 1f);
_point2 = Tween(_point1, _targetPoint, (float)percentage);
}
UPDATE 2 MyCode:
double previuosData = 0;
bool isDraw = false;
public MainPage()
{
this.InitializeComponent();
}
private void OnDraw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
if (isDraw)
return;
CanvasDrawingSession ds = args.DrawingSession;
double height = sender.Size.Height;
double width = sender.Size.Width;
CanvasSolidColorBrush brush = new CanvasSolidColorBrush(sender, Colors.Green);
Random random = new Random();
double pointHeigth = random.Next(0, 500);
var point1 = new Vector2((float)(width / 2), (float)height);
if (pointHeigth > previuosData)
{
for (double a = previuosData; a <= pointHeigth; a++)
{
isDraw = true;
var point2 = new Vector2((float)(width / 2), (float)height - (float)a);
ds.DrawLine(point1, point2, brush, (float)10);
}
isDraw = false;
}
else
{
for (double a = previuosData; a >= pointHeigth; a--)
{
isDraw = true;
var point2 = new Vector2((float)(width / 2), (float)height - (float)a);
ds.DrawLine(point1, point2, brush, (float)10);
}
isDraw = false;
}
previuosData = pointHeigth;
}
in this way I make the animation a little more fluid but with the for loop it's not enough how could I improve?

It seems you are using Random and that generates a random number from the given range in each single frame. So it means 60 times per second the second point will jump randomly up and down.
Compare to something like the following:
private bool _adding = true;
private int _currentHeightOffset = 0;
private void OnDraw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
CanvasDrawingSession ds = args.DrawingSession;
double height = sender.Size.Height;
double width = sender.Size.Width;
var point1 = new Vector2((float)(width / 2), (float)height);
var point2 = new Vector2((float)(width / 2), (float)height - (float)_currentHeight);
CanvasSolidColorBrush brush = new CanvasSolidColorBrush(sender, Colors.Green);
ds.DrawLine(point1, point2, brush, (float)10);
if (_adding)
{
_currentHeightOffset++;
}
else
{
_currentHeightOffset--;
}
if (_currentHeightOffset == 0 || _currentHeightOffset == 300 )
{
_adding = !_adding;
}
}
Now in this example, I am moving the point pixel by pixel in each frame until it reaches 300 and then go back to 0. So the result should be a line which appears to be fixed in point1 and moving and stretching up and down.
The key is, that the values are no longer random, but predictable from frame to frame.
Update
As I discussed in comments, you could do "tweening" effect between two points.
We would use the following method:
private Vector2 Tween(Vector2 sourcePoint, Vector2 targetPoint, float percentage)
{
return new Vector2(
targetPoint.X + (targetPoint.X - sourcePoint.X)*percentage,
targetPoint.Y + (targetPoint.Y - sourcePoint.Y)*percentage)
}
Now if you want to animate between these two points for 5 seconds, you could do something like:
private Vector2 _point2;
private TimeSpan _animationElapsedTime = TimeSpan.Zero;
private void OnUpdate(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
{
_animationElapsedTime += args.Timing.ElapsedTime;
var percentage = Math.Min(_animationElapsedTime.TotalSeconds / 5.0f, 1f);
_point2 = Tween(_point1, _targetPoint, percentage);
}
This is just a quick sketch of how the code would look like, however, it the important points - once you select the target point (using random for example), you need to decide how long do you want the animation to take and then move the second point closer and closer to the target until the time is over.

Related

How to draw a circle around the other distance in the image ? or any distance?

public void DrawLine(PictureBox pb, Graphics g)
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.DrawEllipse(new Pen(Color.Red, 2f), 0, 0, pb.Size.Width, pb.Size.Height);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
DrawLine(pictureBox1, e.Graphics);
}
the result is the red circle at the range a bit before of the 300 kilometers.
but what if i want to make the circle to be drawn on the circle of the 200 kilometers or even the inner circle the smaller one(100 kilometers) ?
how to calculate where to draw the circle and what size ?
the image size is 512x512
I added another cone called it innerOuterRect but how do i make it shorter or longer depending on kilometers distance from the center ?
later i want to add a textBox and when i enter kilometers for example 100 it will move the cone size to 100 kilometers radius and if change it to 21.3 kilometers so the cone and so on.
so the first cone will stay the same and the second one to be changed in kilometers.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var center = new PointF(pictureBox1.Width / 2.0f, pictureBox1.Height / 2.0f);
RectangleF outerRect = pictureBox1.ClientRectangle;
RectangleF innerOuterRect = pictureBox1.ClientRectangle;
outerRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
innerOuterRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pathOuter = new GraphicsPath();
var pathInner = new GraphicsPath();
pathOuter.AddEllipse(outerRect);
pathOuter.AddEllipse(innerOuterRect);
pathInner.StartFigure();
pathInner.AddArc(outerRect, coneRotationAngle, coneSweepAngle);
//pathInner.AddArc(innerOuterRect, coneRotationAngle, coneSweepAngle);
var arcPoints = pathInner.PathPoints;
PointF first = arcPoints[0];
PointF last = arcPoints[arcPoints.Length - 1];
pathInner.AddLines(new[] { center, last, center, first });
pathInner.CloseFigure();
var outerPen = new Pen(Color.FromArgb(100, Color.Red), radarThickness);
var innerBrush = new LinearGradientBrush(
center, first, Color.FromArgb(200, Color.Orange), Color.FromArgb(20, Color.Orange));
e.Graphics.FillPath(innerBrush, pathInner);
e.Graphics.DrawPath(outerPen, pathOuter);
}
To determine the scale between the outer region and the region that defines the cone of the Radar, you can take the maximum size of the outer region. For example:
The outer region's maximum size is 300km:
float radarSizeMeters = 300.0f * 1000f;
The cone's region is a third of the outer region or a specific size:
float radarConeSizeMeters = radarSizeMeters / 3.0f;
// Or use a fixed size, depending on the use case
float radarConeSizeMeters = 10.873f * 1000;
Then the scale is a value between .0f and 1.0f:
float radarConeScale = radarConeSizeMeters / radarSizeMeters;
Having determined the relation between the outer and inner regions, you then use this scale factor to scale the graphic elements that define the two regions:
var outerRegion = [Canvas].ClientRectangle;
var innerRegion = new RectangleF(0, 0,
outerRegion.Width * radarConeScale, outerRegion.Height * radarConeScale
);
Then set the new location, based on the calculated size.
The center point is known in advance and doesn't change:
var center = new PointF(canvas.Width / 2.0f, canvas.Height / 2.0f);
// [...]
innerRegion.Location = new PointF(
center.X - innerRegion.Width / 2.0f,
center.Y - innerRegion.Height / 2.0f
);
In the sample code I'm doing the same thing, but using RectangleF.Inflate(), to deflate the inner region by half of the calculated measure.
RectangleF.Inflate() resizes the rectangle proportionally and also moves the Location
You can specify:
radarSizeMeters, which represents the size in meters of the whole region
radarConeSizeMeters, represents the size in meters of the cone's region
coneSweepAngle, which defines the amplitude of the visible cone
radarSpeed is the speed of the rotation. To set the rounds per minute (RPM):
radarSpeed = (360.0f * [Rounds per minute]) / 60 / (1000 / [Timer Interval]);
with [Timer Interval] = 100 (approximated)
Note that I'm targeting .NET 7 and the language version is C# 11, nullable enabled. For example, this:
PointF last = arcPoints[^1]; is the same as PointF last = arcPoints[arcPoints.Length - 1];
=> You must declare the GraphicsPaths, Pens and Brushes with using statements.
Must, not should
public partial class frmRadar : Form {
System.Windows.Forms.Timer radarTimer = new System.Windows.Forms.Timer();
float coneSweepAngle = 36.0f;
float coneRotationAngle = .0f;
float radarSpeed = 1.98f; // ~3 RPM, based on Timer.Interval
float radarThickness = 5.0f;
float radarSizeMeters = 32.620f * 1000;
float radarConeSizeMeters = 10.873f * 1000;
Color radarConeColor = Color.Orange;
public frmRadar()
{
InitializeComponent();
radarTimer.Interval = 100;
radarTimer.Tick += RadarTimer_Tick;
}
private void RadarTimer_Tick(object? sender, EventArgs e)
{
coneRotationAngle += radarSpeed;
coneRotationAngle %= 360.0f;
canvas.Invalidate();
}
private void canvas_Paint(object? sender, PaintEventArgs e)
{
var center = new PointF(canvas.Width / 2.0f, canvas.Height / 2.0f);
RectangleF outerRect = canvas!.ClientRectangle;
outerRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
using var pathRadarOuterRegion = new GraphicsPath();
using var pathConeRegion = new GraphicsPath();
pathRadarOuterRegion.AddEllipse(outerRect);
float radarConeScale = radarConeSizeMeters / radarSizeMeters;
SizeF radarConeScaleSize = new(
(outerRect.Width - (outerRect.Width * radarConeScale)) / -2.0f,
(outerRect.Height - (outerRect.Height * radarConeScale)) / -2.0f);
var coneRect = outerRect;
coneRect.Inflate(radarConeScaleSize);
pathConeRegion.AddArc(coneRect, coneRotationAngle, coneSweepAngle);
var arcPoints = pathConeRegion.PathPoints;
PointF first = arcPoints[0];
PointF last = arcPoints[^1];
pathConeRegion.AddLines(new[] { last, center, first, center });
pathConeRegion.CloseFigure();
using var outerPen = new Pen(Color.FromArgb(100, Color.Red), radarThickness);
using var innerBrush = new LinearGradientBrush(
center, first, Color.FromArgb(200, radarConeColor), Color.FromArgb(20, radarConeColor));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillPath(innerBrush, pathConeRegion);
e.Graphics.DrawPath(outerPen, pathRadarOuterRegion);
}
}

Position controls on a circle

I am trying to spread panels (9 in my example) on a circle that I have drawn.
I am using c# winforms.
I have tried many variations of my code but I'm not getting what I want and started to get confused.
Eventually I want something like that:
I am not really sure how to put the center of my panels on the corresponding points on the circle using the angles.
Here's my code:
public partial class Form1 : Form
{
List<Panel> plist = new List<Panel>();
Rectangle circ_rect = new Rectangle();
const int Num_Screens = 9;
const int margin = 15;
public Form1()
{
InitializeComponent();
WindowState = FormWindowState.Maximized;
}
private void Generate_Panels()
{
for (int i = 0; i < 9; i++)
{
Panel p = new Panel();
p.BackColor = Color.LightSkyBlue;
p.Size = new Size(250, 150);
p.BorderStyle = BorderStyle.FixedSingle;
p.Name = "panel_" + ((i + 1).ToString());
plist.Add(p);
}
}
private void Generate_Circle()
{
//Create panels
Generate_Panels();
//Set circle coord
Point circ_center = new Point(Width / 2, Height / 2);
Size circ_Size = new Size(Height - margin, Height - margin);
circ_center = new Point((circ_center.X - (circ_Size.Width / 2)),
(circ_center.Y - (circ_Size.Height / 2)));
circ_rect = new Rectangle(circ_center, circ_Size);
float radius = circ_Size.Width / 2;
float angle = 0.0f;
Point loc = Point.Empty;
Point rect_center = Point.Empty;
for (int i = 0; i < plist.Count; i++)
{
rect_center = new Point((plist[i].Width / 2), (plist[i].Height / 2));
angle = 360 * ((i + 1f) / 9);
loc.X = (int)(radius * Math.Cos(angle * Math.PI / 180)) + circ_center.X;
loc.Y = (int)(radius * Math.Sin(angle * Math.PI / 180)) + circ_center.Y;
plist[i].Location = new Point(loc.X - (plist[i].Width / 2) + circ_rect.X,
loc.Y - (plist[i].Height / 2) + circ_rect.Y);
this.Controls.Add(plist[i]);
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.DrawEllipse(Pens.Red, circ_rect);
}
private void Form1_Load(object sender, EventArgs e)
{
Generate_Circle();
}
}
Having r as radius of a circle with center of (0,0) in a Cartesian coordinate system, we can calculate coordinate of a on the circle based on the angle:
x = r * cos(degree) and y = r * sin(degree)
In C# Sin and Cos methods, accept radians, so we should convert degree to radians using the following formula.
radians = Math.PI * degree / 180.0
The next step is converting the Cartesian coordinate system values to the form coordinate values:
panel.X = x + center.X - panel.Width/2
panel.Y = center.Y - y - panel.Height/2
The next step is calculating the angles. You can set angles manually or you can calculate them by setting an angle as start angle (like 90) and adding a value (like 40, 360/count) as step to the angles.
Example
public partial class Form1 : Form {
Rectangle circle;
List<Panel> panels;
List<int> angles;
public Form1() {
InitializeComponent();
ResizeRedraw = true;
angles = Enumerable.Range(0, 9).Select(x => 90 + x * 40).ToList();
panels = Enumerable.Range(0, 9).Select(x => new Panel() {
Size = new Size(100, 40),
BackColor = Color.LightSkyBlue
}).ToList();
this.Controls.AddRange(panels.ToArray());
}
protected override void OnLayout(LayoutEventArgs levent) {
base.OnLayout(levent);
int padding = 50;
int radius = Math.Min(ClientSize.Width, ClientSize.Height) / 2 - padding;
Point center = new Point(ClientSize.Width / 2, ClientSize.Height / 2);
circle = new Rectangle(center.X - radius, center.Y - radius,
2 * radius, 2 * radius);
for (int i = 0; i < 9; i++) {
var x = (int)(radius * Math.Cos(Math.PI * angles[i] / 180.0)) + center.X;
var y = center.Y - (int)(radius * Math.Sin(Math.PI * angles[i] / 180.0));
panels[i].Left = x - (panels[i].Width / 2);
panels[i].Top = y - (panels[i].Height / 2);
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawEllipse(Pens.Red, circle);
}
}

Uniform circular motion

I need to implement a simple animation of a ball moving in uniform circular motion. I've tried several formulas and the following version seems the best so far.However, there are still 2 issues and I really can't figure out what's wrong.
First, a couple of seconds right after the program starts, the ball moves erratically. I think that the values for theta (the angle in radians) are not computed correctly, but I don't know why.
Secondly, the movement becomes more uniform after a while, but it seems to decrease over time.
The value for 'speed' indicates the number of seconds it takes to do a full revolution.
What I want is an uniform, correct circular movement (according to the value of speed) and without the jerkiness in the beginning.
My code so far:
public partial class ServerForm : Form
{
Stopwatch watch;
//Angular velocity
float angularVelocity;
//Angle
float theta = 20;
//Speed - time to complete a full revolution, in seconds
private float speed = 3;
//Circle center
private int centerX = 250;
private int centerY = 200;
//Circle radius
private float R = 120;
//Current position
private LocationData currentLocation;
public ServerForm()
{
InitializeComponent();
}
public void UpdateUI()
{
currentLocation.CoordX = (float)(centerX + Math.Cos(theta) * R);
currentLocation.CoordY = (float)(centerY + Math.Sin(theta) * R);
currentLocation.Speed = speed;
try
{
this.Invoke(new Action(() => { this.Invalidate(); }));
}
catch (Exception ex)
{
watch.Stop();
Application.Exit();
}
theta += (float)((angularVelocity * 1000 / watch.ElapsedMilliseconds));
//Console.Out.WriteLine("elapsed miliseconds: " + watch.ElapsedMilliseconds + " theta = " + theta);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush color = new SolidBrush(Color.BlueViolet);
g.FillEllipse(color, currentLocation.CoordX, currentLocation.CoordY, 30, 30);
//Draw circle & center
g.DrawEllipse(new Pen(color), centerX, centerY, 5, 5);
float x = centerX - R;
float y = centerY - R;
float width = 2 * R;
float height = 2 * R;
g.DrawEllipse(new Pen(color), x, y, width, height);
base.OnPaint(e);
}
private void button1_Click(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(textSpeed.Text))
{
ResetValues(float.Parse(textSpeed.Text));
}
}
private void ResetValues(float newSpeed)
{
speed = newSpeed;
angularVelocity = (float)(2 * Math.PI / speed); // radians / sec
//Start at the top
currentLocation.CoordX = centerX;
currentLocation.CoordY = centerY - R;
theta = 90;
watch.Restart();
}
private void ServerForm_Load(object sender, EventArgs e)
{
watch = new Stopwatch();
timer1.Enabled = true;
timer1.Interval = 100;
timer1.Tick += timer1_Tick;
currentLocation = new LocationData();
ResetValues(speed);
}
void timer1_Tick(object sender, EventArgs e)
{
UpdateUI();
}
}
LocationData is just a class holding the coordinates & current speed.
Are the units for time & angular velocity (and the transformations to use miliseconds) correct?
I changed BackgroundWorker to Timer, but I still get that erratic motion and the movement slows down after a while.
Try using a System.Windows.Forms.Timer instead of a BackgroundWorker. I believe you'll get more consistent results. This is definitely not a good case for using a BackgroundWorker.
Here's a more-or-less complete solution. Note that I'm scaling the swing radius and the ball radius by the size of the Form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.UserPaint, true);
}
private void Form1_Load(object sender, System.EventArgs e)
{
_stopwatch.Start();
}
private void Timer1_Tick(object sender, System.EventArgs e)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.Clear(BackColor);
const float rotationTime = 2000f;
var elapsedTime = (float) _stopwatch.ElapsedMilliseconds;
var swingRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 4f;
var theta = Math.PI * 2f * elapsedTime / rotationTime;
var ballRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 10f;
var ballCenterX = (float) ((ClientSize.Width / 2f) + (swingRadius * Math.Cos(theta)));
var ballCenterY = (float) ((ClientSize.Height / 2f) + (swingRadius * Math.Sin(theta)));
var ballLeft = ballCenterX - ballRadius;
var ballTop = ballCenterY - ballRadius;
var ballWidth = ballRadius * 2f;
var ballHeight = ballRadius * 2f;
e.Graphics.FillEllipse(Brushes.Red, ballLeft, ballTop, ballWidth, ballHeight);
e.Graphics.DrawEllipse(Pens.Black, ballLeft, ballTop, ballWidth, ballHeight);
}
private readonly Stopwatch _stopwatch = new Stopwatch();
}

Transcribing a polygon on a circle

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)

Move a control in a circle at runtime?

I know you can change a control's x/y location at runtime and I can use a timer to move it up/down/left/right/diagonally but how can you programatically move it in a circle?
For example, if I had a PictureBox control at the 12 o'clock position on my main form, can I move that picture box in a circle, finishing at its start position, on a button click?
Use sinus and cosinus functions.
Look at that for example.
A concrete C# example exists here.
In case that the link will not exist some day, here is the source code for drawing 25 increasing radius circles on a form:
void PutPixel(Graphics g, int x, int y, Color c)
{
Bitmap bm = new Bitmap(1, 1);
bm.SetPixel(0, 0, Color.Red);
g.DrawImageUnscaled(bm, x, y);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics myGraphics = e.Graphics;
myGraphics.Clear(Color.White);
double radius = 5;
for (int j = 1; j <= 25; j++)
{
radius = (j + 1) * 5;
for (double i = 0.0; i < 360.0; i += 0.1)
{
double angle = i * System.Math.PI / 180;
int x = (int)(150 + radius * System.Math.Cos(angle));
int y = (int)(150 + radius * System.Math.Sin(angle));
PutPixel(myGraphics, x, y, Color.Red);
}
}
myGraphics.Dispose();
}
Result:
I've written a small class deriving from PictureBox which should let you achieve your result easily enough. Everytime you call RotateStep its location will change accordingly. Angle and speed are expressed in radians, distance in pixels.
class RotatingPictureBox : PictureBox
{
public double Angle { get; set; }
public double Speed { get; set; }
public double Distance { get; set; }
public void RotateStep()
{
var oldX = Math.Cos(Angle)*Distance;
var oldY = Math.Sin(Angle)*Distance;
Angle += Speed;
var x = Math.Cos(Angle)*Distance - oldX;
var y = Math.Sin(Angle)*Distance - oldY;
Location += new Size((int) x, (int) y);
}
}
Sample usage:
public Form1()
{
InitializeComponent();
var pictureBox = new RotatingPictureBox
{
Angle = Math.PI,
Speed = Math.PI/20,
Distance = 50,
BackColor = Color.Black,
Width = 10,
Height = 10,
Location = new Point(100, 50)
};
Controls.Add(pictureBox);
var timer = new Timer {Interval = 10};
timer.Tick += (sender, args) => pictureBox.RotateStep();
timer.Start();
}

Categories