I've been trying to recode a C++ DirectX code to C# that would help me with Drawing a perfect circle. Currently I have this code that i translated by myself:
private void Circle(int X, int Y, int radius, int numSides, Color color)
{
Vector2[] Line = new Vector2[128];
float Step = (float)(Math.PI * 2.0 / numSides);
int Count = 0;
for (float a = 0; a < Math.PI * 2.0; a += Step)
{
float X1 = (float)(radius * Math.Cos(a) + X);
float Y1 = (float)(radius * Math.Sin(a) + Y);
float X2 = (float)(radius * Math.Cos(a + Step) + X);
float Y2 = (float)(radius * Math.Sin(a + Step) + Y);
Line[Count].X = X1;
Line[Count].Y = Y1;
Line[Count + 1].X = X2;
Line[Count + 1].Y = Y2;
Count += 2;
}
line.Begin();
line.Draw(Line, color);
line.End();
}
The problem is that the circle is drawn but also a Line from a point in the circle to the left top corner, like this.
Don't iterate with a floating point variable. They might get imprecise during the iteration. In your case, the last step is probably very close behind the upper bound (instead of hitting it exactly). So it won't get calculated and left as the default (0, 0).
So use an integer iteration variable:
for (int i = 0; i < numSides; ++i)
{
float a = i * Step;
...
}
Then, you can also get rid of Count.
Furthermore, you should make your coordinate buffer dynamic:
Vector2[] Line = new Vector2[2 * numSides];
Related
Is there any possibility to plot a circle in a WindowsForm Chart?
A method-call as follows would be really nice!
Graph.Series["circle"].Circle.Add(centerX, centerY, radius);
Well, I created myself a work around.
Maybe it helps someone
public void DrawCircle(Chart Graph, double centerX, double centerY, double radius, int amountOfEdges)
{
string name = "circle_" + centerX + centerY + radius + amountOfEdges;
// Create new data series
if (Graph.Series.IndexOf(name) == -1)
Graph.Series.Add(name);
// preferences of the line
Graph.Series[name].ChartType = SeriesChartType.Spline;
Graph.Series[name].Color = Color.FromArgb(0, 0, 0);
Graph.Series[name].BorderWidth = 1;
Graph.Series[name].IsVisibleInLegend = false;
// add line segments (first one also as last one)
for (int k = 0; k <= amountOfEdges; k++)
{
double x = centerX + radius * Math.Cos(k * 2 * Math.PI / amountOfEdges);
double y = centerY + radius * Math.Sin(k * 2 * Math.PI / amountOfEdges);
Graph.Series[name].Points.AddXY(x, y);
}
}
You can call it for example via
DrawCircle(Graph, 5, 4, 3, 30);
Around 30 points should be enough to get a nice circle instead of a polygon, but depends on the size of your chart.
Good evening, I know on the web there are similar questions and a few tutorials about it, but I'd like you to check my code and correct it. I mean, I'd like to know what's wrong with my project.
I have to draw a parabola graph given its equation on my main panel.
I also must include two buttons, zoom in and zoom out, which are used to reduce and enlarge the "view" panel's view (and so the parabola).
I was recommended to use a scale var.
This is my code:
note: x0, y0 are panel_main x center, y center.
I have x, y that are used to determine x,y from the equation.
xpc, ypc are converted for the window scale (so are pixels).
xmin, xmax are the extreme values that, with a certain scale, stay on the panel
I hope you can give me a hint, thanks a lot!
public void DisegnaParabola()
{
Graphics gs = panel_main.CreateGraphics();
pen.Color = Color.Black;
scale = (x0*2) / zoom; //Pixels equivalent to 1x or 1y
n_punti = (x0*2) / scale; //Number of x math points that are visible in window
xmin = -(n_punti / 2);
xmax = n_punti / 2;
precision = 1 / scale; //Increment of x to have 1px
if (asse_parabola.SelectedIndex == 0) //if Y axis
{
for (double i = xmin + precision; i < xmax; i += precision)
{
rifx = i - precision; //Old points
rifxpc = rifx * scale;
rify = (a * Math.Pow(rifx, 2)) + b * rifx + c;
rifypc = y0 - (rify * scale);
x = i; //New points
y = (a * Math.Pow(x, 2)) + b * x + c;
ypc = y0 - (y * scale);
gs.DrawLine(pen, (float)rifxpc, (float)rifypc, (float)xpc, (float)ypc);
}
}
else
{
scale = (y0*2) / zoom; //Pixels for 1y
n_punti = (y0*2) / scale; //Numbers of y in the window
ymin = -(n_punti / 2);
ymax = n_punti / 2;
for(double i=ymin+precision; i<ymax; i+=precision)
{
rify = y - precision;
rifypc = (y0*2) - rify * scale;
rifx = (a * Math.Pow(rify, 2)) + b * rify + c;
rifxpc = x0 + (rifx * scale);
y = i;
x = (a * Math.Pow(y, 2)) + b * y + c;
xpc = x0 + (x * scale);
gs.DrawLine(pen, (float)rifypc, (float)rifxpc, (float)ypc, (float)xpc);
}
}
lbl_canc.Visible = true;
}
Your question actually consists of several tasks and as usual the key is to take and break those apart..
One issue is getting the data, I will leave the details to you but show how to sparate it from the rest.
The next issue is to scale the data. I'll show you how to avoid this one altogether and scale the drawing tool instead.
And the third one is to draw them to a display surface. As you'll see this is really simple once the other issues are taken care of.
Let's start with the most important step: Collecting the data. You try to create and scale and draw them all in the same piece of code. This has many disadvantages..
Let's first collect the data in a suitable structure:
List<PointF> points = new List<PointF>();
List<T> is the collection of choice most of the time; certainly much nicer than arrays! In some method you should fill that list with your data, calculated from some formula.
Here is an example:
List<PointF> getPoints(float start, float end, int count, float ymax)
{
List<PointF> points = new List<PointF>();
float deltaX = (end - start) / count;
for (int i = 0; i < count; i++)
{
float x = i * deltaX;
// insert your own formula(s) here!
float y = ymax + (float)Math.Sin(x * somefactor) * ymax;
points.Add(new PointF(x, y));
}
return points;
}
Now for the second important part: How to scale the data? This can be done either when creating them; but again, separating the two taks makes them both a lot simpler.
So here is a function that, instead of scaling the data scales the Graphics object we will use to plot them:
void ScaleGraphics(Graphics g, List<PointF> data)
{
float xmax = data.Select(x => x.X).Max();
float ymax = data.Select(x => x.Y).Max();
float xmin = data.Select(x => x.X).Min();
float ymin = data.Select(x => x.Y).Min();
float width = Math.Abs(xmax - xmin);
float height = Math.Abs(ymax - ymin);
var vr = g.VisibleClipBounds;
g.ScaleTransform(vr.Width / width, vr.Height / height);
}
This method makes sure that all the data in our list will fit into the drawing surface. If you want to restrict them to a different size you can pass it in and change the code accordingly..
Finally we need to do the actual drawing. We do that where we should, that is in the Paint event of our drawing surface control..:
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (points.Count < 2) return; // no lines to draw, yet
ScaleGraphics(e.Graphics, points);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using ( Pen pen = new Pen(Color.Blue )
{ Width = 1.5f , LineJoin = LineJoin.Round, MiterLimit = 1f} )
e.Graphics.DrawLines(pen, points.ToArray());
}
I'm able to point zoom on the Mandelbrot set, as long as the mouse doesn't move after zooming has begun. I've tried calculating a normalized delta (new coordinate - old coordinate)*(oldzoom), but what happens is the image appears to jump around to a new location. I've seen this issue before. I'm struggling more here because I have to somehow convert this mouse position delta back to the -2,2 coordinate space of the Mandelbrot set.
Here's my code. What's important is the GetZoomPoint method, and then the lines of code that define x0 and y0. Also, I use the Range class to scale values from one range to another. I WAS using deltaTrans (thats the thing I was talking about earlier where I normalize the mouse delta with the old scale).
using OpenTK.Graphics.OpenGL;
using SpriteSheetMaker;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Fractal.Fractal
{
public class Mandelbrot : BaseTexture
{
private static Transform GlobalTransform = SpriteSheetMaker.Global.Transform;
private static Vector3 GlobalScale = GlobalTransform.Scale;
private static Vector3 GlobalTrans = GlobalTransform.Translation;
private static Vector3 LastWindowPoint = null;
private static Vector3 ZoomFactor = Vector3.ONE * 1.2f;
private static Vector3 Displacement = Vector3.ZERO;
private static int WindowSize = 100;
public static Vector3 GetZoomPoint()
{
var zP = OpenGLHelpers.LastZoomPoint.Clone();
if (LastWindowPoint == null)
{
LastWindowPoint = zP.Clone();
}
var delta = zP - LastWindowPoint;
var oldZoom = GlobalScale / ZoomFactor;
var deltaTrans = delta.XY * oldZoom.XY;
var factor = ZoomFactor.Clone();
Range xR = new Range(0, WindowSize);
Range yR = new Range(0, WindowSize);
Range complexRange = new Range(-2, 2);
// Calculate displacement of zooming position.
var dx = (zP.X - Displacement.X) * (factor.X - 1f);
var dy = (zP.Y - Displacement.Y) * (factor.Y - 1f);
// Compensate for displacement.
Displacement.X -= dx;
Displacement.Y -= dy;
zP -= Displacement;
var x = complexRange.ScaleValue(zP.X, xR);
var y = complexRange.ScaleValue(zP.Y, yR);
var rtn = new Vector3(x, y);
LastWindowPoint = zP.Clone();
return rtn;
}
public static Mandelbrot Generate()
{
var size = new Size(WindowSize, WindowSize);
var radius = new Size(size.Width / 2, size.Height / 2);
Bitmap bmp = new Bitmap(size.Width, size.Height);
LockBitmap.LockBitmapUnsafe lbm = new LockBitmap.LockBitmapUnsafe(bmp);
lbm.LockBits();
var pt = Mandelbrot.GetZoomPoint();
Parallel.For(0, size.Width, i =>
{
// float x0 = complexRangeX.ScaleValue(i, xRange);
float x0 = ((i - radius.Width) / GlobalScale.X) + pt.X;
Parallel.For(0, size.Height, j =>
{
// float y0 = complexRangeY.ScaleValue(j, yRange);
float y0 = ((j - radius.Height) / GlobalScale.Y) + pt.Y;
float value = 0f;
float x = 0.0f;
float y = 0.0f;
int iteration = 0;
int max_iteration = 100;
while (x * x + y * y <= 4.0 && iteration < max_iteration)
{
float xtemp = x * x - y * y + x0;
y = 2.0f * x * y + y0;
x = xtemp;
iteration += 1;
if (iteration == max_iteration)
{
value = 255;
break;
}
else
{
value = iteration * 50f % 255f;
}
}
int v = (int)value;
lbm.SetPixel(i, j, new ColorLibrary.HSL(v / 255f, 1.0, 0.5).ToDotNetColor());
});
});
lbm.UnlockBits();
var tex = new BaseTextureImage(bmp);
var rtn = new Mandelbrot(tex);
return rtn;
}
public override void Draw()
{
base._draw();
}
private Mandelbrot(BaseTextureImage graphic)
{
var topLeft = new Vector3(0, 1);
var bottomLeft = new Vector3(0, 0);
var bottomRight = new Vector3(1, 0);
var topRight = new Vector3(1, 1);
this.Vertices = new List<Vector3>()
{
topLeft,bottomLeft,bottomRight,topRight
};
this.Size.X = WindowSize;
this.Size.Y = WindowSize;
this.Texture2D = graphic;
}
}
}
I refactored my code, and also figured out a solution to this problem. 2 big wins in one. Ok, so I found a solution on CodeProject written in C# which I was readily able to adapt to my project. I'm not sure why I didn't realize this when I posted the question, but what I needed to solve this issue was to create a 'window' of zoom and not think in terms of a 'point zoom'. Yes, even if I am trying to zoom directly into a point, that point is just the center of some sort of a window.
Here is the method I have, which expects start and end mousedown coordinates (screen space), and converts the mandelbrot set window size accordingly.
public void ApplyZoom(double x0, double y0, double x1, double y1)
{
if (x1 == x0 && y0 == y1)
{
//This was just a click, no movement occurred
return;
}
/*
* XMin, YMin and XMax, YMax are the current extent of the set
* mx0,my0 and mx1,my1 are the part we selected
* do the math to draw the selected rectangle
* */
double scaleX, scaleY;
scaleX = (XMax - XMin) / (float)BitmapSize;
scaleY = (YMax - YMin) / (float)BitmapSize;
XMax = (float)x1 * scaleX + XMin;
YMax = (float)y1 * scaleY + YMin;
XMin = (float)x0 * scaleX + XMin;
YMin = (float)y0 * scaleY + YMin;
this.Refresh(); // force mandelbrot to redraw
}
Basically, whats happening is we calculate the ratio between the mandelbrot window size versus the screen size we are drawing to. Then, using that scale, we basically convert our mousedown coordinates to mandelbrot set coordinates (x1*scaleX, etc) and manipulate the current Min and Max coordinates with them, using the Min values as the pivot point.
Here's the link to the CodeProject I used as a reference: CodeProject link
I'm trying to get a character to throw something in an arc at a target.
I know the vertex(x,y) and the target(x,y) and I want to get an arc from the origin(x,y) to the target with a max height of vertex.y
What I have is based off the vertex form of y = a(x-h)^2 + k
public static Vector3 parabola(Vector2 origin, Vector2 target, float height)
{
float dist = target.x - origin.x;
Vector2 vertex = new Vector2(origin.x + (dist / 2), origin.y + height);
//a = (y-k) / (x-h)^2
float a = (target.y - vertex.y) / ((target.x - vertex.x) * (target.x - vertex.x));
//b = (-h + -h) * a
float b = (-vertex.x + -vertex.x) * a;
//c = (h * h) * a + k
float c = (vertex.x * vertex.x) * a + vertex.y;
return new Vector3(a, b, c);
}
x += Time.DeltaTime;
float yPos = a * ((x - h) * (x - h)) + k;
This doesn't produce the correct arc. It's usually much too steep or much too shallow. Is my algebra wrong, or am I using the wrong approach?
Thanks
Here is a good solution: Wiki:Trajectory of a projectile.
I'd like to copy a roughly rectangular area to a rectangular area. Example:
Both areas are defined by their corner points. The general direction is kept (no flipping etc).
Simply rotating the source image does not work since opposing sides may be of different length.
So far I found no way to do this in pure C# (except manual pixel copying), so I guess I have to resort to the Windows API or some 3rd party library?
Since I could not find an answer, I wrote a naive implementation myself. It works reasonably well.
Examples
I drew all examples manually in Paint, so they are not very exact - it was just enough to test some basics.
a) Slight rotation.
Source:
Result:
b) Various sides
Source:
Result:
c) Perspective
Source:
Result:
Code
(it's specialized to my use case, but it should be easy to adapt):
// _Corners are, well, the 4 corners in the source image
// _Px is an array of pixels extracted from the source image
public void Rescale ()
{
RescaleImage (
_Corners[0],
_Corners[1],
_Corners[3],
_Corners[2],
100,
100);
}
private void RescaleImage (PointF TL, PointF TR, PointF LL, PointF LR, int sx, int sy)
{
var bmpOut = new Bitmap (sx, sy);
for (int x = 0; x < sx; x++) {
for (int y = 0; y < sy; y++) {
/*
* relative position
*/
double rx = (double) x / sx;
double ry = (double) y / sy;
/*
* get top and bottom position
*/
double topX = TL.X + rx * (TR.X - TL.X);
double topY = TL.Y + rx * (TR.Y - TL.Y);
double bottomX = LL.X + rx * (LR.X - LL.X);
double bottomY = LL.Y + rx * (LR.Y - LL.Y);
/*
* select center between top and bottom point
*/
double centerX = topX + ry * (bottomX - topX);
double centerY = topY + ry * (bottomY - topY);
/*
* store result
*/
var c = PolyColor (centerX, centerY);
bmpOut.SetPixel (x, y, c);
}
}
bmpOut.Save (_Path + "out5 rescale out.bmp");
}
private Color PolyColor (double x, double y)
{
// get fractions
double xf = x - (int) x;
double yf = y - (int) y;
// 4 colors - we're flipping sides so we can use the distance instead of inverting it later
Color cTL = _Px[(int) y + 1, (int) x + 1];
Color cTR = _Px[(int) y + 1, (int) x + 0];
Color cLL = _Px[(int) y + 0, (int) x + 1];
Color cLR = _Px[(int) y + 0, (int) x + 0];
// 4 distances
double dTL = Math.Sqrt (xf * xf + yf * yf);
double dTR = Math.Sqrt ((1 - xf) * (1 - xf) + yf * yf);
double dLL = Math.Sqrt (xf * xf + (1 - yf) * (1 - yf));
double dLR = Math.Sqrt ((1 - xf) * (1 - xf) + (1 - yf) * (1 - yf));
// 4 parts
double factor = 1.0 / (dTL + dTR + dLL + dLR);
dTL *= factor;
dTR *= factor;
dLL *= factor;
dLR *= factor;
// accumulate parts
double r = dTL * cTL.R + dTR * cTR.R + dLL * cLL.R + dLR * cLR.R;
double g = dTL * cTL.G + dTR * cTR.G + dLL * cLL.G + dLR * cLR.G;
double b = dTL * cTL.B + dTR * cTR.B + dLL * cLL.B + dLR * cLR.B;
Color c = Color.FromArgb ((int) (r + 0.5), (int) (g + 0.5), (int) (b + 0.5));
return c;
}
Generally speaking, what you want to do is map the destination coordinates to the source coordinates through a transform function:
for (int y = 0; y < destHeight; y++) {
for (x=0; x < destWidth; x++) {
Color c = Transform(x, y, sourceImage, sourceTransform);
SetPixel(destImage, x, y, c);
}
}
Let's assume that sourceTransform is an object that encapsulates a transformation from source to dest coordinates (and vice versa).
Working in dest coordinates will make it easier to avoid that curve in your retransformed source image and will allow you to better antialias, as you can map the corners of the dest pixel to the source image and sample within it and interpolate/extrapolate.
In your case you're going to have a set of linear equations that do the mapping - in this case this is known as quadrilateral warping - see this previous question.