I am trying to implement a zooming-in option on a graph by allowing the user to select the area he wants (shown in pic). The graph doesn't take up the whole screen. It ranges from -0.8 to around +1.4 on Y-axis.
After Zooming-in:
I do the zooming in by scaling and translating the projection matrix in the OnRenderFrame event.
Xscale= 1, Yscale=1, Xtrans =0, Ytrans =0;
protected override void OnRenderFrame(FrameEventArgs e)
{
GL.ClearColor(Color4.Black);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
for (int j = 0; j < lines.Count(); j++)
{
projection = Matrix4.CreateOrthographicOffCenter(-1.0f, 1.0f, -1.0f, 1.0f, -2f, 2.0f);
Matrix4 Translate = Matrix4.CreateTranslation(Xtrans, Ytrans, 0);
Matrix4 Scale = Matrix4.CreateScale(1/Xscale, 1/Yscale, 1);
projection = Translate* Scale* projection;
view = Matrix4.LookAt(CameraPos,
CameraPos + CameraFront, CameraUp);
shader.Use();
shader.SetMatrix4("model", Matrix4.Identity);
shader.SetMatrix4("view", view);
shader.SetMatrix4("projection", projection);
shader.SetFloat("color", new Vector4(colors.names[i]));
GL.BindVertexArray(VAO[i]);
GL.DrawArrays(PrimitiveType.LineStrip, 0, Y1Vertices[0].Count());
shader.Unbind();
GL.BindVertexArray(0);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}
SwapBuffers();
base.OnRenderFrame(e);
}
I get the scaling factors by finding the ratio between the rectangle dimensions and screen dimensions. For translating, I am trying to find the difference between the rectangle origin and the screen origin then translate the object by this amount. The scaling is working correctly but not the translation. I think maybe the issue is that my object doesn't fill the screen initially, as in doesn't go from -1 to 1 on Y-axis? I am not sure how to account for this offset especially that it changes depending on the graph data. I would also prefer if the zooming in doesn't take up the whole screen and just stays within the initial graph dimensions. Could you please help me out?
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if(e.Button == MouseButton.Right)
{
initialPos = new Vector2(e.X, e.Y);
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseMoveEventArgs e)
{
MouseState mstate = Mouse.GetCursorState();
if (e.Mouse[MouseButton.Right])
{
mouseDown = true;
newPos = new Vector2(e.X, e.Y);
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
if (e.Button == MouseButton.Right)
{
Xscale *= (Math.Abs(newPos.X - initialPos.X)) / this.Width;
Yscale *= (Math.Abs(newPos.Y - initialPos.Y) / this.Height);
Xtrans = (((newPos.X + initialPos.X) / 2)-(this.Width/2))/this.Width;
Ytrans = (((newPos.Y + initialPos.Y) / 2) - (this.Height / 2)) / this.Height;
}
base.OnMouseUp(e);
}
Edit:
After changing to projection = Scale*Translate* projection;
Related
At the moment I develope a ChartControl and it works just pretty well in my opinion,
but now I'm at a point where it would be nice to have the ability to zoom the drawed signal for better analyzing.
At the moment I calculate the needed points like this:
for (int i = 0; i < PointsCount; i++){
xAxisPoint = xAxisOP.X + i * (xAxisWidth / PointsCount);
yAxisPoint = yAxisHeight * data[i].Point / Divisor;
if(yAxisPoint > yAxisHeight){
yAxisPoint = yAxisHeight;
}
if(yAxisPoint < -yAxisHeight){
yAxisPoint = -yAxisHeight;
}
Points[i] = new PointF(xAxisPoint, yAxisOP.Y + yAxisPoint);
}
if(zoom){
graphics.ScaleTransform(0.2f*ZoomFactor, 0.2f*ZoomFactor);
}
using (Pen plotPen = new Pen(plotColor, 1)){
graphics.DrawLines(plotPen, Points);
}
But the problem is: When it zooms in, the zoom is way too big and is drawn outside the bounds of my control.
Is there a way to specify an area in which it should be Scaled (zoomed)?
For the final question: Is there a way to specify an area in which it should be scaled/zoomed? you need a combination of SetClip, TranslateTransform and ScaleTransform.
Here is an example.
It uses a
target rectangle zoomTgtArea where the zoomed graphics are displayed,
a mouse location zoomOrigin where the zoom origin is,
a float zoomFactor, a positive float.
Initial values:
Rectangle zoomTgtArea = new Rectangle(300, 500, 200, 200);
Point zoomOrigin = Point.Empty; // updated in MouseMove when button is pressed
float zoomFactor = 2f;
The trick to zoom in on only a part of the graphics is to display the graphics twice, once normally and once with the transformations of the Graphics object.
Let's try:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
// normal drawing
DrawStuff(e.Graphics);
// for the movable zoom we want a small correction
Rectangle cr = pictureBox.ClientRectangle;
float pcw = cr.Width / (cr.Width - ZoomTgtArea.Width / 2f) ;
float pch = cr.Height / (cr.Height - ZoomTgtArea.Height / 2f) ;
// now we prepare the graphics object; note: order matters!
e.Graphics.SetClip(zoomTgtArea );
// we can either follow the mouse or keep the output area fixed:
if (cbx_fixed.Checked)
e.Graphics.TranslateTransform( ZoomTgtArea.X - zoomCenter.X * zoomFactor,
ZoomTgtArea.Y - zoomCenter.Y * zoomFactor);
else
e.Graphics.TranslateTransform( - zoomCenter.X * zoomFactor * pcw,
- zoomCenter.Y * zoomFactor * pch);
// finally zoom
e.Graphics.ScaleTransform(zoomFactor, zoomFactor);
// and display zoomed
DrawStuff(e.Graphics);
}
The DrawStuff I used is simple:
void DrawStuff(Graphics g)
{
bool isZoomed = g.Transform.Elements[0]!= 1
|| g.Transform.OffsetX != 0 | g.Transform.OffsetY != 0;
if (isZoomed) g.Clear(Color.Gainsboro); // pick your back color
// all your drawing here!
Rectangle r = new Rectangle(10, 10, 500, 800); // some size
using (Font f = new Font("Tahoma", 11f))
g.DrawString(text, f, Brushes.DarkSlateBlue, r);
}
Its only extra is clearing the background so the normal drawing won't shine through the zoomed version..
Let's see:
I am writing a Solar System simulation program; I am but a beginner using C#.
I am using OnPaint on a custom control to draw my graphics on the form. I am having problems with the animation as, instead of having the planet rotate around the sun (a fixed point in the centre of the control), it is rotating around the point in which the planet should be. However, this point is still rotating around the centre of the control.
I have declared these variables at the top of the custom control:
private color col1;
private float angle;
private double r1, r2, ex, why;
Below is the code in OnPaint:
protected override void OnPaint(PaintEventArgs pe)
{
this.DoubleBuffered = true;
base.OnPaint(pe);
Graphics g = pe.Graphics;
AnimationControl anim = new AnimationControl();
Planet sun = new Planet(50, 60);
sun.drawSun(pe);
angle += 0.01f;
if (angle > 359)
{
angle = 0;
}
Matrix matrix = new Matrix();
matrix.Rotate(angle, MatrixOrder.Append);
matrix.Translate(SandboxForm.ActiveForm.Width / 2,
SandboxForm.ActiveForm.Height / 2, MatrixOrder.Append);
g.Transform = matrix;
r1 = 200;
r2 = 100;
double diameter = 40;
col1 = Color.Red;
SolidBrush bru2 = new SolidBrush(col1);
ex = ((SandboxForm.ActiveForm.Width / 2) - diameter - (sun.getSunRadius())) + (r1 * (Math.Cos(angle))); /
why = ((SandboxForm.ActiveForm.Height / 2) - diameter - (sun.getSunRadius())) + (r2 * (Math.Sin(angle)));
g.FillEllipse(bru2, (float)ex, (float)why, (float)diameter, (float)diameter);
Invalidate();
}
I had to simplify your code as there are missing classes etc. However in this you can see that it now does what you want.
If you try this code, you'll see that the order of Rotate and Translate do matter, but it wasn't having any effect when you tried my suggestion because you're painting before you apply the transform.
Notice also that I have using around my matrix, that's because you should dispose of it when you're finished with it.
protected override void OnPaint(PaintEventArgs pe)
{
this.DoubleBuffered = true;
base.OnPaint(pe);
Graphics g = pe.Graphics;
angle += 0.2f;
if (angle > 359)
{
angle = 0;
}
using (Matrix matrix = new Matrix())
{
matrix.Rotate(angle, MatrixOrder.Append);
matrix.Translate(300, 200, MatrixOrder.Append);
g.Transform = matrix;
pe.Graphics.DrawEllipse(Pens.Red, new Rectangle(50, 60, 50, 50));
}
Invalidate();
}
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);
I am creating a screenshot maker/uploader and everything works great but now i wanna intergrate something like paint. So first i am creating the pencil function. But there starts the problems i can draw :D but not on the position of my mouse. He takes other position then my cursor?
So the question is:
Hoe to get the mouse positions on a zoomed picturebox?
My code:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (draw && pencil)
{
Graphics panel = Graphics.FromImage(ScreenShot);
Pen pen = new Pen(Color.Black, 14);
pen.EndCap = LineCap.Round;
pen.StartCap = LineCap.Round;
panel.DrawLine(pen, pX, pY, e.X, e.Y);
pictureBox1.CreateGraphics().DrawImage(ScreenShot, pictureBox1.Width, pictureBox1.Height);
pictureBox1.Invalidate();
}
Point p = pictureBox1.PointToClient(Cursor.Position);
pX = p.X;
pY = p.Y;
}
When using the mouse on a zoomed image the reported pixels are the zoomed coordinates. So we need to get at the real pixels..
After setting up things somehow..
float pbZoom = 3f; // the factor by which the PictureBox is zoomed in or out
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox.ClientSize = new Size((int)(pictureBox.Image.Width * pbZoom),
(int)(pictureBox.Image.Height * pbZoom));
pictureBox.Paint += pictureBox_Paint;
pictureBox.MouseDown += pictureBox_MouseDown;
pictureBox.MouseMove += pictureBox_MouseMove;
pictureBox.MouseUp += pictureBox_MouseUp;
.. you can write the events to draw onto or into the zoomed image.
First a helper function to undo the zoom for a point:
PointF unZoomed(PointF pt) { return new PointF(pt.X / pbZoom, pt.Y / pbZoom );}
For simplicity lets keep a few things at class level:
PointF mDown = PointF.Empty; // mouse down point
PointF mCurrent = PointF.Empty; // current mouse location
We start each mouse event by calculating the unzoomed e.Location.
Our test draws a straight, moving red line on the surface until you release the mouse. Then that line is drawn into the Image in green.
The MouseDown simply stores the starting point, unzoomed:
void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
mDown = unZoomed(e.Location);
}
In the MouseMove we store the current point and trigger the Paint event:
void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.None) return;
PointF e_ = unZoomed(e.Location);
mCurrent = e_;
pictureBox.Invalidate();
}
In the MouseUp we draw the line into the Image of the PictureBox, changing its pixels; then we set the Image and reset the points:
void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
Bitmap bmp = (Bitmap) pictureBox.Image;
using (Graphics G = Graphics.FromImage(bmp))
{
G.SmoothingMode = SmoothingMode.HighQuality;
G.DrawLine(Pens.Green, mDown, mCurrent);
}
mDown = PointF.Empty;
mCurrent = PointF.Empty;
pictureBox.Image = bmp;
}
To draw onto the Control surface we use the Paint event. To draw scaled we use a Matrix..:
void pictureBox_Paint(object sender, PaintEventArgs e)
{
Matrix scaleMatrix = new Matrix();
scaleMatrix.Scale(pbZoom, pbZoom);
e.Graphics.MultiplyTransform(scaleMatrix);
e.Graphics.DrawLine(Pens.Red, mDown, mCurrent);
}
I also working with zoom image. I simply use this code, it's working. So you can just compare with bitmap (original image) before zoomed in/out.
private void pbInput_MouseMove(object sender, MouseEventArgs e) {
if (pbInput.Image == null) {
mouseY.Text = "";
mouseX.Text = "";
}
else {
Bitmap b = new Bitmap(pbInput.Image);
int x = b.Width * e.X / pbInput.Width;
int y = b.Height * e.Y / pbInput.Height;
mouseX.Text = x.ToString();
mouseY.Text = y.ToString();
}
}
this.Cursor = new Cursor(Cursor.Current.Handle);
Size size1 = pictureBox1.Image.Size;
Size size2 = pictureBox1.Size;
float x1 = (float)size1.Width / ((float)size2.Width + 1);
float y1 = (float)size1.Height / ((float)size2.Height + 1);
float divisor = x1 >= y1 ? x1 : y1;
divisor = divisor > 0 ? divisor : 1;
Size imagesize = new Size((int)(size1.Width / divisor), (int)(size1.Height / divisor));
int ex = imagesize.Width - size2.Width >= 0 ? imagesize.Width - size2.Width : size2.Width - imagesize.Width;
int ey = imagesize.Height - size2.Height >= 0 ? imagesize.Height - size2.Height : size2.Height - imagesize.Height;
float ftx = ((float)size1.Width / (float)imagesize.Width) * ((float)(Cursor.Position.X - this.Location.X - pictureBox1.Location.X - (ex / 2) - 8));
float fty = ((float)size1.Height / (float)imagesize.Height) * ((float)(Cursor.Position.Y - this.Location.Y - pictureBox1.Location.Y - (ey / 2) - 31));
Point LOP = new Point(Convert.ToInt32(ftx), Convert.ToInt32(fty)); //point of cursos on picturebox image pixel ...
I have a image of a Map and a smaller PictureBox control.
I'm getting input from my joysyick. My the Y takes the image up and left
Adn the X actually rotates the image..
My problem is when i rotate the Image, the Y axis rotates with it, so when i move up again it wont really go up.. it will go the the new direction the Y axis points too..
Here is my code if you could understand my problam..
public void UpdateTurret()
{
while (js != null)
{
js.GetData();
Thread.Sleep(80);
MapY += js.State.Y;
MapRotation += js.State.X;
{
Image map = Properties.Resources.Map;
Bitmap bmp = (Bitmap)map.Clone();
Graphics g = Graphics.FromImage((Image)bmp);
g.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2 - (MapY / 2));
g.RotateTransform(MapRotation);
g.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2 + (MapY / 2));
g.DrawImage(bmp, 0, 0);
Graphics gfx = Graphics.FromImage((Image)bmp);
gfx.DrawPie(new Pen(Color.Blue, 5), bmp.Width/2 - 5, bmp.Height/2 - 5, 5, 5, 0, 360);
gfx.DrawImage(bmp, 0, MapY);
picBoxMap.Image = (Image)bmp;
float rot = MapRotation;
rot = (float)Math.Abs((rot - 360*Math.Ceiling(rot / 360)));
DrawString = (rot).ToString() + "° Y:" + MapY.ToString();
}
}
}
My problem now is that the rotation point is always centered, i want my rotation point to be the new position i reached.
So i figured out it should be like this:
g.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2 - MapY);
g.RotateTransform(MapRotation);
g.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2 + MapY);
But this cause another bug. Now when i rotate the Image, the Y axis rotates with it, so when i move up again it wont really go up.. it will go the the new direction the Y axis points too..
Anyone has an idea on solving this issue?
EDIT:
Here is my new code:
public void UpdateTurret()
{
while (js != null)
{
js.GetData();
Thread.Sleep(80);
MapY += js.State.Y;
MapRotation += js.State.X;
{
Image map = Properties.Resources.Map;
Size mapSize = map.Size;
Bitmap bmp = (Bitmap)map.Clone();
Graphics g = Graphics.FromImage((Image)bmp);
Matrix transformMatrix = new Matrix();
transformMatrix.Translate(-mapSize.Width / 2, -mapSize.Height / 2, MatrixOrder.Append);
transformMatrix.Rotate(MapRotation, MatrixOrder.Append);
transformMatrix.Translate(mapSize.Width / 2, mapSize.Height / 2, MatrixOrder.Append);
transformMatrix.Translate(0, MapY, MatrixOrder.Append);
g.Transform = transformMatrix;
g.DrawImage(bmp, 0,0);
picBoxMap.Image = (Image)bmp;
float rot = MapRotation;
rot = (float)Math.Abs((rot - 360*Math.Ceiling(rot / 360)));
DrawString = (rot).ToString() + "° Y:" + MapY.ToString();
}
}
//Draw Cross
Graphics gfx = picBoxMap.CreateGraphics();
Rectangle rc = picBoxMap.ClientRectangle;
gfx.DrawLine(Pens.Red, rc.Width / 2, rc.Height / 2 + 10, rc.Width / 2, rc.Height / 2 - 10);
gfx.DrawLine(Pens.Red, rc.Width / 2 + 10, rc.Height / 2, rc.Width / 2 - 10, rc.Height / 2);
}
My problem now is that after i move the map on the Y axis, the rotation point stays on the center point.
And look after i only rotated the map:
You can see i didn't move the Y axis but it did changed.. because the Rotation point is at the center of the image and not where to red cross is.
I need the rotation point to be at the same position of the red cross.
Possibly a better approach is to use the Matrix.Rotate() and Matrix.Translate() methods to get a matrix to set the Graphics.Transform to.
Then you can simply draw your map at the origin (ignoring moving and rotating it), and the graphics object will do the rest.
See the examples in the Matrix method links for more info. In their example they draw a rectangle, but you could easily draw your image instead.
In my unedited answer i was wrong. I've corrected my code below.
The key things to note are:
You want the point the map rotates at to vary according to where the player is, so you should translate the map first (since this will then effect where the rotation happens).
I've changed the code to use the RotateAt so it's easier to understand. This way we don't need to worry about the extra translations to get the rotation point at the origin then back again.
As you want the arrow keys to mean up with respect to the rotated image we can't do it as simply as normal. I've added Cos and Sin terms in, deduced using basic trigonometry.
I've now got 2 pictureboxes, the first shows only the translation, and the direction of the player, the second is a radar like view (which is what you're after). So, this answer has the 2 main map display types, fixed north in picturebox1, rotating north in picturebox2.
Arrow keys move the image, Q and E rotate it.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private PictureBox pictureBox1;
private PictureBox pictureBox2;
private Image imageToDraw = null;
private float imageRotation = 0.0f;
private PointF imageTranslation = new PointF();
public Form1()
{
InitializeComponent();
pictureBox1 = new PictureBox() { Top = 20, Left = 10, Width = 280, Height = 310, BorderStyle = BorderStyle.FixedSingle };
pictureBox2 = new PictureBox() { Top = 20, Left = pictureBox1.Right + 10, Width = 280, Height = 310, BorderStyle = BorderStyle.FixedSingle };
pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
pictureBox2.Paint += new PaintEventHandler(pictureBox2_Paint);
this.Controls.Add(pictureBox1);
this.Controls.Add(pictureBox2);
this.Controls.Add(new Label() { Text = "Left = translation only, Right = translation and rotation", Width = Width / 2 });
this.ClientSize = new Size(pictureBox2.Right + 10, pictureBox2.Bottom + 10);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
}
private void Form1_Activated(object sender, EventArgs e)
{
try
{
imageToDraw = Image.FromFile("C:\\Map.jpg");
}
catch (Exception)
{
MessageBox.Show("Ensure C:\\Map.jpg exists!");
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (imageToDraw != null)
imageToDraw.Dispose();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
const float MoveSpeed = 5.0f;
switch (e.KeyCode)
{
case Keys.Q:
imageRotation -= 1.0f;
break;
case Keys.E:
imageRotation += 1.0f;
break;
case Keys.Up:
imageTranslation = new PointF(imageTranslation.X - (float)Math.Sin(imageRotation / 180 * Math.PI) * MoveSpeed, imageTranslation.Y - (float)Math.Cos(imageRotation / 180 * Math.PI) * MoveSpeed);
break;
case Keys.Down:
imageTranslation = new PointF(imageTranslation.X + (float)Math.Sin(imageRotation / 180 * Math.PI) * MoveSpeed, imageTranslation.Y + (float)Math.Cos(imageRotation / 180 * Math.PI) * MoveSpeed);
break;
case Keys.Left:
imageTranslation = new PointF(imageTranslation.X - (float)Math.Cos(imageRotation / 180 * Math.PI) * MoveSpeed, imageTranslation.Y + (float)Math.Sin(imageRotation / 180 * Math.PI) * MoveSpeed);
break;
case Keys.Right:
imageTranslation = new PointF(imageTranslation.X + (float)Math.Cos(imageRotation / 180 * Math.PI) * MoveSpeed, imageTranslation.Y - (float)Math.Sin(imageRotation / 180 * Math.PI) * MoveSpeed);
break;
}
pictureBox1.Invalidate();
pictureBox2.Invalidate();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (imageToDraw != null)
{
e.Graphics.ResetTransform();
Matrix transformMatrix = new Matrix();
transformMatrix.Translate(-imageTranslation.X, -imageTranslation.Y);
e.Graphics.Transform = transformMatrix;
e.Graphics.DrawImage(imageToDraw, Point.Empty);
transformMatrix = new Matrix();
transformMatrix.Translate(50, 50);
transformMatrix.RotateAt(-imageRotation, new PointF(20, 20));
e.Graphics.Transform = transformMatrix;
e.Graphics.DrawString("^", new Font(DefaultFont.FontFamily, 40), Brushes.Black, 0, 0);
}
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
if (imageToDraw != null)
{
e.Graphics.ResetTransform();
Matrix transformMatrix = new Matrix();
transformMatrix.Translate(-imageTranslation.X, -imageTranslation.Y);
transformMatrix.RotateAt(imageRotation, new PointF(pictureBox1.Width / 2 + imageTranslation.X, pictureBox1.Height / 2 + imageTranslation.Y));
e.Graphics.Transform = transformMatrix;
e.Graphics.DrawImage(imageToDraw, Point.Empty);
}
}
}
}