I have tried to adapt some code I came across to draw an equilateral triangle in c#
public void drawTriangle(PaintEventArgs e, int x, int y, int distance)
{
float angle = 0;
SolidBrush brs = new SolidBrush(Color.Green);
PointF[] p = new PointF[3];
p[0].X = x;
p[0].Y = y;
p[1].Y = (float)( x + distance * Math.Cos(angle + Math.PI / 3));
p[1].X = (float)( y + distance * Math.Sin(angle + Math.PI / 3));
p[2].Y = (float)( x + distance * Math.Cos(angle - Math.PI / 3));
p[2].X = (float)( y + distance * Math.Sin(angle - Math.PI / 3));
e.Graphics.FillPolygon(brs, p);
}
Unfortunately, this doesn't even come close. I have drawn equilateral triangles, but the points were always based on the centers of congruent circles. I am trying to find a simpler way. I am sure there must be an obvious problem with this code, but I am trying to learn the math needed as I go, so I don't know what it is. Thanks for your time.
Try this approach. I assume that for zero angle p[0] is left bottom vertex, p[1] is right bottom (the same horizontal).
(BTW, you have got strange mangling of Y/X)
p[0].X = x;
p[0].Y = y;
p[1].X = (float)( x + distance * Math.Cos(angle));
p[1].Y = (float)( y + distance * Math.Sin(angle));
p[2].X = (float)( x + distance * Math.Cos(angle + Math.PI / 3));
p[2].Y = (float)( y + distance * Math.Sin(angle + Math.PI / 3));
Related
When i call my funtion with a startingAngle=0 it produce a good shape with the correct size.
Example:
var points = GetPolygonVertices(sides:4, radius:5, center:(5, 5), startingAngle:0), produces:
points[0] = {X = 10 Y = 5}
points[1] = {X = 5 Y = 0}
points[2] = {X = 0 Y = 5}
points[3] = {X = 5 Y = 10}
As observed the side length is 10px, which is correct, but produce a rotated square at 45º from human eye prespective.
To fix this i added a switch/case to offset the startAngle so it will put the square at correct angle for human eye, by rotating 45º. The rotation works, but the shape is no longer a square of 10x10px, instead i lose 1 to 2px from sides:
[0] = {X = 9 Y = 1}
[1] = {X = 1 Y = 1}
[2] = {X = 1 Y = 9}
[3] = {X = 9 Y = 9}
and become worse as radius grow, for example with radius=10:
[0] = {X = 17 Y = 3}
[1] = {X = 3 Y = 3}
[2] = {X = 3 Y = 17}
[3] = {X = 17 Y = 17}
I tried with both floor and ceil instead of round, but it always end in lose 1 or 2px...
Is there a way to improve the function to keep the shape size equal no matter the number of sides and rotation angle?
My function:
public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
{
if (sides < 3)
throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
// Fix rotation
switch (sides)
{
case 3:
startingAngle += 90;
break;
case 4:
startingAngle += 45;
break;
case 5:
startingAngle += 22.5;
break;
}
var points = new Point[sides];
var step = 360.0 / sides;
int i = 0;
for (var angle = startingAngle; angle < startingAngle + 360.0; angle += step) //go in a circle
{
if (i == sides) break; // Fix floating problem
double radians = angle * Math.PI / 180.0;
points[i++] = new(
(int) Math.Round(Math.Cos(radians) * radius + center.X),
(int) Math.Round(Math.Sin(-radians) * radius + center.Y)
);
}
return points;
}
EDIT: I updated the function to get rid of the switch condition and product shapes in correct orientation for human eye when angle is not given. Still it suffer from same "problem"
public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
{
if (sides < 3)
throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
var vertices = new Point[sides];
double deg = 360.0 / sides;//calculate the rotation angle
var rad = Math.PI / 180.0;
var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);
var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);
vertices[0] = new(
(int) Math.Round(x0),
(int) Math.Round(y0)
);
vertices[1] = new(
(int) Math.Round(x1),
(int) Math.Round(y1)
);
for (int i = 0; i < sides - 2; i++)
{
double dsinrot = Math.Sin((deg * (i + 1)) * rad);
double dcosrot = Math.Cos((deg * (i + 1)) * rad);
vertices[i + 2] = new(
(int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
(int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
);
}
if (flipHorizontally)
{
var startX = center.X - radius;
var endX = center.X + radius;
for (int i = 0; i < sides; i++)
{
vertices[i].X = endX - (vertices[i].X - startX);
}
}
if (flipVertically)
{
var startY = center.Y - radius;
var endY = center.Y + radius;
for (int i = 0; i < sides; i++)
{
vertices[i].Y = endY - (vertices[i].Y - startY);
}
}
return vertices;
}
EDIT 2: From Tim Roberts anwser here the functions to calculate side length from radius and radius from side length, this solve my problem. Thanks!
public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
{
return 2 * radius * Math.Sin(Math.PI / sides);
}
public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
{
return radius * Math.Cos(Math.PI / sides);
}
public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
{
var theta = 360.0 / sides;
return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
}
Your problem is one of mathematics. You said "As observed, the side length is 10px". It very definitely is not 10px. The distance from (10,5) to (5,0) is sqrt(5*5 + 5*5), which is 7.07. That's exactly what we expect for a square that is inscribed in a circle of radius 5: 5 x sqrt(2).
And that's what the other squares are as well.
FOLLOWUP
As an added bonus, here is a function that returns the radius of the circle that circumscribes a regular polygon with N sides of length L:
import math
def rad(length,nsides):
theta = 360/nsides
r = length / (2 * math.cos( (90-theta/2) * math.pi / 180))
return r
for s in range(3,9):
print(s, rad(10,s))
Im using Microsoft visual studio 2010, with the refernce dynamic data display.
I would like to draw a circle on a map, i have 2 points, one of them is the center of the circle and the other is the point on the circle, the distance between them is the radius of the circle.
the result should look like this :
http://sizmedia.com/my.php?i=mjmynzim2nhy.png
my result when I draw a circle with one point and const distance is like this (distance = radius = 15):
http://sizmedia.com/my.php?i=hm2zuv5yyenj.png
***** I don't care if the circle will look like my result(the ellipse)
because as I understood the earth is circle and its type of reasonable. ****
but when I draw circle with distance between 2 point (distance = 3400 +) I can't see the circle that I draw.
I would love to get some help, there is my code to find distance between 2 points.
// Calculating the distance between the two points
double dLat = (ps.X - centerPoint.X) / 180 * Math.PI;
double dLong = (
double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(ps).Y.ToString()) -
double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(centerPoint).Y.ToString())) / 180 * Math.PI;
double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2)
+ Math.Cos(ps.X / 180 * Math.PI) * Math.Cos(pointLine1.X / 180 * Math.PI)
* Math.Sin(dLong / 2) * Math.Sin(dLong / 2);
double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
//Calculate radius of earth
double radiusE = 6378135; // Equatorial radius, in metres
double radiusP = 6356750; // Polar Radius
//Numerator part of function
double nr = Math.Pow(radiusE * radiusP * Math.Cos(ps.X / 180 * Math.PI), 2);
//Denominator part of the function
double dr = Math.Pow(radiusE * Math.Cos(ps.X / 180 * Math.PI), 2)
+ Math.Pow(radiusP * Math.Sin(ps.X / 180 * Math.PI), 2);
double radius = Math.Sqrt(nr / dr);
//Calculate distance in meters.
distance = (radius * c); // resualt in meters
distance /= 1000; // resualt in KM
And there is my code to add the circle :
while (a < 360) // Doing one round around the point (The angels)
{
// Get the X position of the pointClicked
cx = (double)prePs.X;
// Get the Y position of the pointClicked
cy = double.Parse(this.plotter.Viewport.Transform.DataTransform.ViewportToData(prePs).Y.ToString());
// Get the new X position of the pointClicked by the angel with math calculation
xEndP = (float)(distance * Math.Cos(a * Math.PI / 180F)) + cx;
// Get the new Y position of the pointClicked by the angel with math calculation
yEndP = (float)(distance * Math.Sin(a * Math.PI / 180F)) + cy;
// Creating the new point
globalPoint = new DraggablePoint(new Point(xEndP, yEndP));
globalPoint.Position = new Point(xEndP, yEndP);
globalPoint.Visibility = Visibility.Visible;
// Increas the angel
a++;
//Creat new point on the circle with new angel
xEndPNext = (float)(distance * Math.Cos(a * Math.PI / 180F)) + cx;
yEndPNext = (float)(distance * Math.Sin(a * Math.PI / 180F)) + cy;
// Creat line between the two new points that we creat now
segmentHelper = new Segment(new Point(xEndP, yEndP), new Point(xEndPNext, yEndPNext));
// Brush between the points by line
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(255, 47, 79, 49);
segmentHelper.Stroke = mySolidColorBrush;
// Add the line to the chartplotter
plotter.Children.Add(segmentHelper);
// Add the angel
a++;
}
My algorithm is take one point, and the next point and to draw line between them ( when the points are visiblty false) and then i get a nice circle.
Thank you very much :)
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 have 2 lines that i draw like this:
float Alpha = RotDegrees;
PointF PitCenter = new Point(picBoxZoomMap.Width / 2, picBoxZoomMap.Height / 2);
PointF p = new PointF(PitCenter.X - 20, PitCenter.Y - 250);
PointF p2 = new PointF(PitCenter.X + 20, PitCenter.Y - 250);
zoomgfx.DrawLine(Pens.Red, PitCenter, new PointF(
(float)((p.Y - PitCenter.Y) * Math.Sin(Alpha * Math.PI / 180) + p.X),
(float)(PitCenter.Y + (p.Y - PitCenter.Y) * Math.Cos(Alpha * Math.PI / 180))));
zoomgfx.DrawLine(Pens.Red, PitCenter, new PointF(
(float)((p2.Y - PitCenter.Y) * Math.Sin(Alpha * Math.PI / 180) + p2.X),
(float)(PitCenter.Y + (p2.Y - PitCenter.Y) * Math.Cos(Alpha * Math.PI / 180))));
Here are the lines when Alpha = 0;
And here are the lines after 90 degrees rotation..
As you see the lines somehow meets.. i really cant understand why..
Any ideas?
Your formula for the rotation is incorrect, have a look here --> Rotate a point by another point in 2D
Change your code to this, and you will get the right effect:
PointF PitCenter = new Point(picBoxZoomMap.Width / 2, picBoxZoomMap.Height / 2);
PointF p = new PointF(PitCenter.X - 20, PitCenter.Y - 250);
PointF p2 = new PointF(PitCenter.X + 20, PitCenter.Y - 250);
var AlphaRad = RotDegrees * Math.PI / 180;
zoomgfx.DrawLine(Pens.Red, PitCenter, new PointF(
(float)(Math.Cos(AlphaRad) * (p.X - PitCenter.X) - Math.Sin(AlphaRad) * (p.Y - PitCenter.Y) + PitCenter.X),
(float)(Math.Sin(AlphaRad) * (p.X - PitCenter.X) + Math.Cos(AlphaRad) * (p.Y - PitCenter.Y) + PitCenter.Y)));
zoomgfx.DrawLine(Pens.Red, PitCenter, new PointF(
(float)(Math.Cos(AlphaRad) * (p2.X - PitCenter.X) - Math.Sin(AlphaRad) * (p2.Y - PitCenter.Y) + PitCenter.X),
(float)(Math.Sin(AlphaRad) * (p2.X - PitCenter.X) + Math.Cos(AlphaRad) * (p2.Y - PitCenter.Y) + PitCenter.Y)));
You take the difference between the Y-coordinates and multiply with Sin to get the X-coordinates. This is correct.
However, you also take the difference between the Y-coordinates when you construct the new Y-coordinates. For this, you should take the difference between the X-coordinates and multiply by Cos.
E.g. your line 8 and line 12 produces the same Y-coordinates for the new point because p.Y - PitCenter.Y is the same as p2.Y - PitCenter.Y since p.Y = p2.Y.
Makes sense?
I find it easier to understand things if I break it down a bit more. It looks to me like you're trying to do too many things all at once. I think this will do what you're looking for (I'm not sure if the radius definition is exactly what you want) but it's hopefully clear enough that you understand what I'm suggesting.
float RotDegrees = 90.0; // Centerline angle for wedge
float width = 10.0; // Assume a 10 degree wedge
// Center of view
PointF PitCenter = new PointF(picBoxZoomMap.Width / 2,
picBoxZoomMap.Height / 2);
// Determine the angle for the wedges in radians
float theta0 = (RotDegrees - width / 2.0) * Math.PI / 180.0;
float theta1 = (RotDegrees + width / 2.0) * Math.PI / 180.0;
// May need to adjust this to satisfy your needs
float radius = 100.0;
// Determine the endpoints of the new wedge ... Assumes (0,0) is in the upper
// left corner rather than the lower left (where it belongs ;). If it's in the
// lower left after all, change the subtraction in the Y components to an
// addition
PointF p0 = new PointF( PitCenter.X + radius * Math.Cos(theta0),
PitCenter.Y - radius * Math.Sin(theta0) );
PointF p1 = new PointF( PitCenter.X + radius * Math.Cos(theta1),
PitCenter.Y - radius * Math.Sin(theta1));
// Draw the lines
zoomgfx.DrawLine(Pens.Red, PitCenter, p0);
zoomgfx.DrawLine(Pens.Red, PitCenter, p1);
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.