Cannot Draw Line from Within Class - Why won't it draw? - c#

I am new to C# and I am trying to use OOP and classes. I am trying to draw a simple sinewave and an axis line (X axis). I have gotten similar code to in the "main - Form1" but I cannot get it to draw in the form from within a class. It draws nothing! The code does compile.
What am I missing? What can I do better?
I call the class from a button click -
Drawclick(object sender, EventArgs e)
{ DrawSine Sine1 = new DrawSine(950);
}
Here is the class
class DrawSine:Form1
{
private float sinex=0;//set up variables for sine
private float countx = 0;
private float siney = 0;
private float sinex1=0;
private float siney1=0;
public float offset;
public float scalex;
public float scaley;
public DrawSine(int widthG)
{
Graphics Graphsine = this.CreateGraphics();//declare sine
Graphics Graphline = this.CreateGraphics();//declare axis
Pen Graphpen = new Pen(Brushes.Black, 1.0F);
Graphline.DrawLine(Graphpen, 0, 200, widthG, 200);//draw line
0,200 to end of form,200
int WidthG = widthG;
do //draw sine wave left to right
{
sinex += scalex * 6.28f / widthG;
siney = (float)Math.Sin(sinex);
Graphsine.DrawLine(Graphpen, sinex1, siney1 + offset,
countx, siney * 100 + offset);
sinex1 = sinex;
sinex1 = siney * 100;
countx += 1;
}
while (countx <= widthG);
}
}

This may be drawing something on the form but you cannot see because soon after the Load event, there will be a PaintBackground event and a PaintEvent as the form is rendered.
These events will effectively erase everything on the form.
2 Remedies:
Encapsulate the above draw code in a method (Let's say DrawSine())
One, Override OnPaint event and write your code there. Your painting will remain intact on form resize or form redraw.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawSine(e, 950);
}
Two, use form Shown event
private void Form1_Shown(object sender, EventArgs e)
{
DrawSine(null, 950);
}
Your original method:
public DrawSine(Graphics g, int widthG)
{
Graphics Graphsine = g??this.CreateGraphics();//declare sine
Graphics Graphline = g??this.CreateGraphics();//declare axis
Pen Graphpen = new Pen(Brushes.Black, 1.0F);
Graphline.DrawLine(Graphpen, 0, 200, widthG, 200);//draw line
//0,200 to end of form,200
int WidthG = widthG;
do //draw sine wave left to right
{
sinex += scalex * 6.28f / widthG;
siney = (float)Math.Sin(sinex);
Graphsine.DrawLine(Graphpen, sinex1, siney1 + offset,
countx, siney * 100 + offset);
sinex1 = sinex;
sinex1 = siney * 100;
countx += 1;
}
while (countx <= widthG);
}

Related

Rotate image in C# Windows Form from original starting position on Button click [duplicate]

I am working on a project for school, we need to make a basic top down race game in C# without using XNA.
First of all let me tell you that the stuff we have learned about programming so far has little to do with making something that even remotely looks like a racegame. It didn't get any more difficult than array's, loops etc.
So we didn't learn about graphics or anything like that.
Having said all that I am having the following problem.
We have created a Graphics object, and then use DrawImage and use a bitmap from a car.jpg.
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, xPos, yPos, car.Width, car.Height);
Then we wait for a key press e.g Right
case Keys.Right:
if (angle != 360)
{
angle += 10;
}
else
{
angle = 0;
}
this.Refresh();
break;
The problem we have is that the pivot point for the rotation is in the top left corner. So as soon as we move the car to something like (20,25) and start to rotate it, it will use (0,0) as the center of rotation. What we want to achieve is to have the center point of rotation at the center of our car.
We have tried looking for ways to change the centerX and centerY of the RotateTransform but have come to the conclusion that this isn't possible with the bitmap.
We have been struggling with this problem for over 2 days and can't seem to find any solution for achieving the thing we want.
Is there something we are doing wrong creating the Graphics object, or is there a totally different way to change centerX and centerY for the car?
To draw a rotated Bitmap you need to do a few steps to prepare the Graphics object:
first you move its origin onto the midpoint of the rotation
then you rotate by the desired angle
next you move it back
now you can draw the Bitmap
finally you reset the Graphics
This needs to be done for each bitmap.
Here are the steps in code to draw a Bitmap bmp at position (xPos, yPos):
float moveX = bmp.Width / 2f + xPos;
float moveY = bmp.Height / 2f+ xPosf;
e.Graphics.TranslateTransform(moveX , moveY );
e.Graphics.RotateTransform(angle);
e.Graphics.TranslateTransform(-moveX , -moveY );
e.Graphics.DrawImage(bmp, xPos, yPos);
e.Graphics.ResetTransform();
There is one possible complication: If your Bitmap has different dpi resolution than the screen i.e. than the Graphics you must first adapt the Bitmap's dpi setting!
To adapt the Bitmapto the usual 96dpi you can simply do a
bmp.SetResolution(96,96);
To be prepared for future retina-like displays you can create a class variable you set at startup:
int ScreenDpi = 96;
private void Form1_Load(object sender, EventArgs e)
{
using (Graphics G = this.CreateGraphics()) ScreenDpi = (int)G.DpiX;
}
and use it after loading the Bitmap:
bmp.SetResolution(ScreenDpi , ScreenDpi );
As usual the DrawImage method uses the top left corner of the Bitmap. You may need to use different Points for the rotation point and possibly also for the virtual position of your car, maybe in the middle of its front..
Here is static class which will paint the image in desired location within desired area. Change the rotationangle value to rotate the image. And you can also pan and zoom the image.
Add this class in your Project and call the static functions from Win Form.
public static class FullImage
{
public static Image image;
public static RectangleF DisplayRect, SourceRect;
public static Size ParentBoundry;
public static float rotationangle=0;
internal static void Paint(Graphics graphics)
{
if (image == null)
return;
float hw = DisplayRect.X + DisplayRect.Width / 2f;
float hh = DisplayRect.Y + DisplayRect.Height / 2f;
System.Drawing.Drawing2D.Matrix m = graphics.Transform;
m.RotateAt(rotationangle, new PointF(hw, hh), System.Drawing.Drawing2D.MatrixOrder.Append);
graphics.Transform = m;
graphics.DrawImage(image, new RectangleF(DisplayRect.X, DisplayRect.Y, DisplayRect.Width, DisplayRect.Height), SourceRect, GraphicsUnit.Pixel);
graphics.ResetTransform();
}
public static void LoadImage(Image img)
{
image = img;
SizeF s = GetResizedSize(image, ParentBoundry);
SourceRect = new RectangleF(0, 0, image.Width, image.Height);
DisplayRect = new RectangleF(ParentBoundry.Width / 2 - s.Width / 2, ParentBoundry.Height / 2 - s.Height / 2, s.Width, s.Height);
}
public static Size GetResizedSize(Image ImageToResize, Size size)
{
int sourceWidth = ImageToResize.Width;
int sourceHeight = ImageToResize.Height;
float nPercent = 0;
float nPercentW = 0;
float nPercentH = 0;
nPercentW = ((float)size.Width / (float)sourceWidth);
nPercentH = ((float)size.Height / (float)sourceHeight);
if (nPercentH < nPercentW)
nPercent = nPercentH;
else
nPercent = nPercentW;
int destWidth = (int)(sourceWidth * nPercent);
int destHeight = (int)(sourceHeight * nPercent);
return new Size(destWidth, destHeight);
}
internal static void MouseWheel(int delta)
{
if (delta > 0)
DisplayRect = ZoomImage(DisplayRect,CurrentMouse, .1f);
else
DisplayRect = ZoomImage(DisplayRect, CurrentMouse, -.1f);
}
private RectangleF ZoomImage(RectangleF ImageRectangle, PointF MouseLocation, float ScaleFactor)
{
/// Original Size and Location
SizeF OriginalSize = ImageRectangle.Size;
PointF OriginalPoint = ImageRectangle.Location;
///Mouse cursor location -located in width% and height% of totaloriginal image
float mouse_widthpercent = System.Math.Abs(OriginalPoint.X - MouseLocation.X) / OriginalSize.Width * 100;
float mouse_heightpercent = System.Math.Abs(OriginalPoint.Y - MouseLocation.Y) / OriginalSize.Height * 100;
///Zoomed Image by scalefactor
SizeF FinalSize = new SizeF(OriginalSize.Width + OriginalSize.Width * ScaleFactor, OriginalSize.Height + OriginalSize.Height * ScaleFactor);
if (FinalSize.Width < 15 || FinalSize.Height < 15)
return ImageRectangle;
if (FinalSize.Width > 60000 || FinalSize.Height > 60000)
return ImageRectangle;
/// How much width increases and height increases
float widhtincrease = FinalSize.Width - OriginalSize.Width;
float heightincrease = FinalSize.Height - OriginalSize.Height;
/// Adjusting Image location after zooming the image
PointF FinalLocation = new System.Drawing.PointF(OriginalPoint.X - widhtincrease * mouse_widthpercent / 100,
OriginalPoint.Y - heightincrease * mouse_heightpercent / 100);
ImageRectangle = new RectangleF(FinalLocation.X, FinalLocation.Y, FinalSize.Width, FinalSize.Height);
return ImageRectangle;
}
static bool drag = false;
static Point Initial, CurrentMouse;
internal static void MouseMove(Point location)
{
CurrentMouse = location;
if (drag)
{
DisplayRect = new RectangleF(DisplayRect.X + location.X - Initial.X, DisplayRect.Y + location.Y - Initial.Y, DisplayRect.Width, DisplayRect.Height);
Initial = location;
}
}
internal static void MouseDown(Point location)
{
Initial = location;
drag = true;
}
internal static void MouseUp(Point location)
{
drag = false;
}
}
After Adding this code in your project (Better add in separate cs file), Call the functions from Win Form class (Form1.cs).
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
FullImage.ParentBoundry = new Size(this.Width, this.Height);
// Enter the image path
FullImage.LoadImage(Image.FromFile(#"D:\a.jpg"));
}
//Create a paint event
private void Form1_Paint(object sender, PaintEventArgs e)
{
FullImage.Paint(e.Graphics);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseDown(e.Location);
this.Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseMove(e.Location);
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseUp(e.Location);
this.Invalidate();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
Vault.FullImage.MouseWheel(e.Delta);
this.Invalidate();
}
Now, if you want to rotate the image, just set the value however you want (with slider, button or add some more functions to detect the mouse movement and then rotate)
Example: add a button and each time the button clicked increase the value by 1.
private void button1_clicked(object sender, EventArgs e)
{
FullImage.rotationangle++;
this.invalidate();
}
To rotate the top left from the center you first need to know the angle of it then adjust it by the angle you want and re-calculate the new top left by the new angle:
var newXPos = (int)(xPos + car.Width / 2.0 + Math.Cos(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Width / 2.0);
var newYPos = (int)(yPos + car.Height / 2.0 + Math.Sin(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Height / 2.0);
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, newXPos, newYPos, car.Width, car.Height);

winforms c#. I draw the line in code, how can I add event hover to it? [duplicate]

My program can draw lines using canvas.Drawline(). How to click line and change this color (select line)?
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas=panel1.CreateGraphics();
}
Coordinate line stored in coordFirs & coodLast.
Here is a suitable Line class:
class Line
{
public Color LineColor { get; set; }
public float Linewidth { get; set; }
public bool Selected { get; set; }
public Point Start { get; set; }
public Point End { get; set; }
public Line(Color c, float w, Point s, Point e)
{ LineColor = c; Linewidth = w; Start = s; End = e; }
public void Draw(Graphics G)
{ using (Pen pen = new Pen(LineColor, Linewidth)) G.DrawLine(pen, Start, End); }
public bool HitTest(Point Pt)
{
// test if we fall outside of the bounding box:
if ((Pt.X < Start.X && Pt.X < End.X) || (Pt.X > Start.X && Pt.X > End.X) ||
(Pt.Y < Start.Y && Pt.Y < End.Y) || (Pt.Y > Start.Y && Pt.Y > End.Y))
return false;
// now we calculate the distance:
float dy = End.Y - Start.Y;
float dx = End.X - Start.X;
float Z = dy * Pt.X - dx * Pt.Y + Start.Y * End.X - Start.X * End.Y;
float N = dy * dy + dx * dx;
float dist = (float)( Math.Abs(Z) / Math.Sqrt(N));
// done:
return dist < Linewidth / 2f;
}
}
Define a List for the lines, probably at class level:
List<Line> lines = new List<Line>();
Here is how you can initialize it with a few lines:
for (int i = 0; i < 20; i++) lines.Add(new Line(Color.Black, 4f,
new Point(R.Next(panel1.Width), R.Next(panel1.Height)),
new Point(R.Next(panel1.Width), R.Next(panel1.Height))));
Here is the result of clicking on a crossing:
Whenever you add, change or remove a line you need to make the Panel reflect the news by triggering the Paint event:
panel1.Invalidate();
Here is the Paint event of the Panel:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Line L in lines) L.Draw(e.Graphics);
}
In the MouseClick event you do the test:
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
foreach(Line L in lines)
L.LineColor = L.HitTest(e.Location) ? Color.Red : Color.Black;
panel1.Invalidate();
}
To avoid flicker don't use the basic Panel class as it isn't doublebuffered. Instead use either a PictureBox or a Label (with AutoSize=false) or a doublebuffered Panel subclass:
class DrawPanel : Panel
{ public DrawPanel () { DoubleBuffered = true; } }
Notes:
There is no such thing as a 'Line' in WinForms, only pixels of various colors. So to select a line you need to store it's two endpoints' coordinates and then find out if you have hit it when clicking.
The above example shows how to do it in math.
Instead one could test each line by drawing it onto a bitmap and test the pixel the mouse has clicked. But drawing those bitmaps would have to do math behind the scenes as well and also allocate space for the bitmaps, so the math will be more efficient..
Yes the Line class looks a little long for such a simple thing a s a line but look how short all the event codes now are! That's because the responsiblities are where they belong!
Also note the the first rule of doing any drawing in WinForms is: Never cache or store a Grahics object. In fact you shouldn't ever use CreateGraphics in the first place, as the Graphics object will never stay in scope and the graphics it produces will not persist (i.e. survive a Minimize-maximize sequence)..
Also note how I pass out the e.Graphics object of the Paint event's parameters to the Line instances so they can draw themselves with a current Graphics object!
To select thinner lines it may help to modify the distance check a little..
The Math was taken directly form Wikipedia.
You can change the color of everything on click. By using click event of particular object.
I give you an example for button. If you click on button then panal’s color will be change. You can modify the code as per your requirement.
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas = panel1.CreateGraphics();
}
private void panel1_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.Blue;
}
private void nonSelectableButton3_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.BurlyWood;
}

Rotate a Graphics bitmap at its center

I am working on a project for school, we need to make a basic top down race game in C# without using XNA.
First of all let me tell you that the stuff we have learned about programming so far has little to do with making something that even remotely looks like a racegame. It didn't get any more difficult than array's, loops etc.
So we didn't learn about graphics or anything like that.
Having said all that I am having the following problem.
We have created a Graphics object, and then use DrawImage and use a bitmap from a car.jpg.
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, xPos, yPos, car.Width, car.Height);
Then we wait for a key press e.g Right
case Keys.Right:
if (angle != 360)
{
angle += 10;
}
else
{
angle = 0;
}
this.Refresh();
break;
The problem we have is that the pivot point for the rotation is in the top left corner. So as soon as we move the car to something like (20,25) and start to rotate it, it will use (0,0) as the center of rotation. What we want to achieve is to have the center point of rotation at the center of our car.
We have tried looking for ways to change the centerX and centerY of the RotateTransform but have come to the conclusion that this isn't possible with the bitmap.
We have been struggling with this problem for over 2 days and can't seem to find any solution for achieving the thing we want.
Is there something we are doing wrong creating the Graphics object, or is there a totally different way to change centerX and centerY for the car?
To draw a rotated Bitmap you need to do a few steps to prepare the Graphics object:
first you move its origin onto the midpoint of the rotation
then you rotate by the desired angle
next you move it back
now you can draw the Bitmap
finally you reset the Graphics
This needs to be done for each bitmap.
Here are the steps in code to draw a Bitmap bmp at position (xPos, yPos):
float moveX = bmp.Width / 2f + xPos;
float moveY = bmp.Height / 2f+ xPosf;
e.Graphics.TranslateTransform(moveX , moveY );
e.Graphics.RotateTransform(angle);
e.Graphics.TranslateTransform(-moveX , -moveY );
e.Graphics.DrawImage(bmp, xPos, yPos);
e.Graphics.ResetTransform();
There is one possible complication: If your Bitmap has different dpi resolution than the screen i.e. than the Graphics you must first adapt the Bitmap's dpi setting!
To adapt the Bitmapto the usual 96dpi you can simply do a
bmp.SetResolution(96,96);
To be prepared for future retina-like displays you can create a class variable you set at startup:
int ScreenDpi = 96;
private void Form1_Load(object sender, EventArgs e)
{
using (Graphics G = this.CreateGraphics()) ScreenDpi = (int)G.DpiX;
}
and use it after loading the Bitmap:
bmp.SetResolution(ScreenDpi , ScreenDpi );
As usual the DrawImage method uses the top left corner of the Bitmap. You may need to use different Points for the rotation point and possibly also for the virtual position of your car, maybe in the middle of its front..
Here is static class which will paint the image in desired location within desired area. Change the rotationangle value to rotate the image. And you can also pan and zoom the image.
Add this class in your Project and call the static functions from Win Form.
public static class FullImage
{
public static Image image;
public static RectangleF DisplayRect, SourceRect;
public static Size ParentBoundry;
public static float rotationangle=0;
internal static void Paint(Graphics graphics)
{
if (image == null)
return;
float hw = DisplayRect.X + DisplayRect.Width / 2f;
float hh = DisplayRect.Y + DisplayRect.Height / 2f;
System.Drawing.Drawing2D.Matrix m = graphics.Transform;
m.RotateAt(rotationangle, new PointF(hw, hh), System.Drawing.Drawing2D.MatrixOrder.Append);
graphics.Transform = m;
graphics.DrawImage(image, new RectangleF(DisplayRect.X, DisplayRect.Y, DisplayRect.Width, DisplayRect.Height), SourceRect, GraphicsUnit.Pixel);
graphics.ResetTransform();
}
public static void LoadImage(Image img)
{
image = img;
SizeF s = GetResizedSize(image, ParentBoundry);
SourceRect = new RectangleF(0, 0, image.Width, image.Height);
DisplayRect = new RectangleF(ParentBoundry.Width / 2 - s.Width / 2, ParentBoundry.Height / 2 - s.Height / 2, s.Width, s.Height);
}
public static Size GetResizedSize(Image ImageToResize, Size size)
{
int sourceWidth = ImageToResize.Width;
int sourceHeight = ImageToResize.Height;
float nPercent = 0;
float nPercentW = 0;
float nPercentH = 0;
nPercentW = ((float)size.Width / (float)sourceWidth);
nPercentH = ((float)size.Height / (float)sourceHeight);
if (nPercentH < nPercentW)
nPercent = nPercentH;
else
nPercent = nPercentW;
int destWidth = (int)(sourceWidth * nPercent);
int destHeight = (int)(sourceHeight * nPercent);
return new Size(destWidth, destHeight);
}
internal static void MouseWheel(int delta)
{
if (delta > 0)
DisplayRect = ZoomImage(DisplayRect,CurrentMouse, .1f);
else
DisplayRect = ZoomImage(DisplayRect, CurrentMouse, -.1f);
}
private RectangleF ZoomImage(RectangleF ImageRectangle, PointF MouseLocation, float ScaleFactor)
{
/// Original Size and Location
SizeF OriginalSize = ImageRectangle.Size;
PointF OriginalPoint = ImageRectangle.Location;
///Mouse cursor location -located in width% and height% of totaloriginal image
float mouse_widthpercent = System.Math.Abs(OriginalPoint.X - MouseLocation.X) / OriginalSize.Width * 100;
float mouse_heightpercent = System.Math.Abs(OriginalPoint.Y - MouseLocation.Y) / OriginalSize.Height * 100;
///Zoomed Image by scalefactor
SizeF FinalSize = new SizeF(OriginalSize.Width + OriginalSize.Width * ScaleFactor, OriginalSize.Height + OriginalSize.Height * ScaleFactor);
if (FinalSize.Width < 15 || FinalSize.Height < 15)
return ImageRectangle;
if (FinalSize.Width > 60000 || FinalSize.Height > 60000)
return ImageRectangle;
/// How much width increases and height increases
float widhtincrease = FinalSize.Width - OriginalSize.Width;
float heightincrease = FinalSize.Height - OriginalSize.Height;
/// Adjusting Image location after zooming the image
PointF FinalLocation = new System.Drawing.PointF(OriginalPoint.X - widhtincrease * mouse_widthpercent / 100,
OriginalPoint.Y - heightincrease * mouse_heightpercent / 100);
ImageRectangle = new RectangleF(FinalLocation.X, FinalLocation.Y, FinalSize.Width, FinalSize.Height);
return ImageRectangle;
}
static bool drag = false;
static Point Initial, CurrentMouse;
internal static void MouseMove(Point location)
{
CurrentMouse = location;
if (drag)
{
DisplayRect = new RectangleF(DisplayRect.X + location.X - Initial.X, DisplayRect.Y + location.Y - Initial.Y, DisplayRect.Width, DisplayRect.Height);
Initial = location;
}
}
internal static void MouseDown(Point location)
{
Initial = location;
drag = true;
}
internal static void MouseUp(Point location)
{
drag = false;
}
}
After Adding this code in your project (Better add in separate cs file), Call the functions from Win Form class (Form1.cs).
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
FullImage.ParentBoundry = new Size(this.Width, this.Height);
// Enter the image path
FullImage.LoadImage(Image.FromFile(#"D:\a.jpg"));
}
//Create a paint event
private void Form1_Paint(object sender, PaintEventArgs e)
{
FullImage.Paint(e.Graphics);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseDown(e.Location);
this.Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseMove(e.Location);
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseUp(e.Location);
this.Invalidate();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
Vault.FullImage.MouseWheel(e.Delta);
this.Invalidate();
}
Now, if you want to rotate the image, just set the value however you want (with slider, button or add some more functions to detect the mouse movement and then rotate)
Example: add a button and each time the button clicked increase the value by 1.
private void button1_clicked(object sender, EventArgs e)
{
FullImage.rotationangle++;
this.invalidate();
}
To rotate the top left from the center you first need to know the angle of it then adjust it by the angle you want and re-calculate the new top left by the new angle:
var newXPos = (int)(xPos + car.Width / 2.0 + Math.Cos(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Width / 2.0);
var newYPos = (int)(yPos + car.Height / 2.0 + Math.Sin(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Height / 2.0);
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, newXPos, newYPos, car.Width, car.Height);

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();
}

How to scale the cube by pressing the button in C #?

I have a program that prints the cube and which can be rotated. Here's the code.
public partial class ProjectorForm : Form
{
Projector projector;
Cube cube;
float deltaRot;
public ProjectorForm()
{
InitializeComponent();
}
private void ProjectorForm_Load(object sender, EventArgs e)
{
deltaRot = 0.01f;
projector = new Projector();
cube = new Cube(Vector3.UnitZ * 20*10, 10*10, 10*10, 15*10);
updateTimer.Start();
}
private void updateTimer_Tick(object sender, EventArgs e)
{
if (rotXBox.Checked)
cube.RotateX(deltaRot);
if (rotYBox.Checked)
cube.RotateY(deltaRot);
if (rotZBox.Checked)
cube.RotateZ(deltaRot);
doubleBufferedPanel1.Invalidate();
}
private void doubleBufferedPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
cube.Draw(projector, Color.Black, doubleBufferedPanel1.ClientSize.Width, doubleBufferedPanel1.ClientSize.Height, e.Graphics);
}
private void button1_Click(object sender, EventArgs e)
{
deltaRot = float.Parse(deltaRotBox.Text);
}
}
class Projector
{
public Vector3 cameraPosition;
public float planeDistance;
ProjectorForm n = new ProjectorForm();
public Projector()
{
cameraPosition = Vector3.Zero;
planeDistance = 256; //Here, multiply by 2 and the scaled cube, how to make that scale when you press the button.
}
public PointF Project(Vector3 point, float width, float height)
{
float x = cameraPosition.X + ((cameraPosition.Z + planeDistance) / (point.Z - cameraPosition.Z)) * (point.X - cameraPosition.X) + width / 2;
float y = cameraPosition.Y + ((cameraPosition.Z + planeDistance) / (point.Z - cameraPosition.Z)) * (point.Y - cameraPosition.Y) + height / 2;
return new PointF(x, y);
}
public void DrawLine(Color color, Vector3 p1, Vector3 p2, float width, float height, Graphics g)
{
g.DrawLine(new Pen(color), Project(p1, width, height), Project(p2, width, height));
}
public void FillPolygon(Color color, Vector3[] vertices, float width, float height, Graphics g)
{
PointF[] points = new PointF[vertices.Length];
for (int i = 0; i < points.Length; i++)
points[i] = Project(vertices[i], width, height);
g.FillPolygon(new SolidBrush(color), points);
}
}
How to make a cube can be scaled by pressing a button. I found the variable planeDistance in the class Projector, when it increased by 2 times the cube is scaled, but I do not know how it can be increased by means of a button.
The field planeDistance is public, so you can change it from outside the class. i.e. just add something like the following to the event handler of a button:
projector.planeDistance += 10; // Change 10 as appropriate
It's worth noting that this doesn't change the size of the cube, it changes how far away the camera is from it. So, while the cube appears to be changing in size, that's just because the camera is moving closer / further away.
To actually change the size of the cube you would have to change fields in the cube class.
Since the size is defined by the vectors created in the constructor you don't really have an easy way of changing them once the cube is created.
You could create a new cube whenever you want to change the size (keep track of the size in another variable on the form).
You could add a method to the cube class that creates new vectors that define the new size (it would look a bit like the constructor, only populating the arrays, not creating them).
You could add a size field to your cube, always create a unit cube (1, 1, 1) then when rendering multiply each vector by your size.

Categories