Going from a cube to a pyramid in visual studio using C#? - c#
Hey everyone im trying to figure out how to make a 3D pyramid out of existing code that is already a 3D cube in visual studio by changing some of the code. I've been messing around with the verts as my friend suggested but I am still unsuccessful. Here is the code..
// ================================================
// | Downloaded From |
// | Visual C# Kicks - http://www.vcskicks.com/ |
// ================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
namespace EulerRotation
{
public partial class Form1 : Form
{
Cube cube;
public Form1()
{
InitializeComponent();
}
private void btnReset_Click(object sender, EventArgs e)
{
tX.Value = 0;
tY.Value = 0;
tZ.Value = 0;
render();
}
private void Form1_Load(object sender, EventArgs e)
{
cube = new Cube(100);
render();
}
private void render()
{
//Set the rotation values
cube.RotateX = tX.Value;
cube.RotateY = tY.Value;
cube.RotateZ = tZ.Value;
//Cube is positioned based on center
Point origin = new Point(picCube.Width / 2, picCube.Height / 2);
picCube.Image = cube.drawCube(origin);
}
private void tX_Scroll(object sender, EventArgs e)
{
render();
}
private void tY_Scroll(object sender, EventArgs e)
{
render();
}
private void tZ_Scroll(object sender, EventArgs e)
{
render();
}
}
internal class Math3D
{
public class Point3D
{
//The Point3D class is rather simple, just keeps track of X Y and Z values,
//and being a class it can be adjusted to be comparable
public double X;
public double Y;
public double Z;
public Point3D(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
public Point3D(float x, float y, float z)
{
X = (double)x;
Y = (double)y;
Z = (double)z;
}
public Point3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public Point3D()
{
}
public override string ToString()
{
return "(" + X.ToString() + ", " + Y.ToString() + ", " + Z.ToString() + ")";
}
}
public class Camera
{
//For 3D drawing we need a point of perspective, thus the Camera
public Point3D Position = new Point3D();
}
public static Point3D RotateX(Point3D point3D, double degrees)
{
//Here we use Euler's matrix formula for rotating a 3D point x degrees around the x-axis
//[ a b c ] [ x ] [ x*a + y*b + z*c ]
//[ d e f ] [ y ] = [ x*d + y*e + z*f ]
//[ g h i ] [ z ] [ x*g + y*h + z*i ]
//[ 1 0 0 ]
//[ 0 cos(x) sin(x)]
//[ 0 -sin(x) cos(x)]
double cDegrees = (Math.PI * degrees) / 180.0f; //Convert degrees to radian for .Net Cos/Sin functions
double cosDegrees = Math.Cos(cDegrees);
double sinDegrees = Math.Sin(cDegrees);
double y = (point3D.Y * cosDegrees) + (point3D.Z * sinDegrees);
double z = (point3D.Y * -sinDegrees) + (point3D.Z * cosDegrees);
return new Point3D(point3D.X, y, z);
}
public static Point3D RotateY(Point3D point3D, double degrees)
{
//Y-axis
//[ cos(x) 0 sin(x)]
//[ 0 1 0 ]
//[-sin(x) 0 cos(x)]
double cDegrees = (Math.PI * degrees) / 180.0; //Radians
double cosDegrees = Math.Cos(cDegrees);
double sinDegrees = Math.Sin(cDegrees);
double x = (point3D.X * cosDegrees) + (point3D.Z * sinDegrees);
double z = (point3D.X * -sinDegrees) + (point3D.Z * cosDegrees);
return new Point3D(x, point3D.Y, z);
}
public static Point3D RotateZ(Point3D point3D, double degrees)
{
//Z-axis
//[ cos(x) sin(x) 0]
//[ -sin(x) cos(x) 0]
//[ 0 0 1]
double cDegrees = (Math.PI * degrees) / 180.0; //Radians
double cosDegrees = Math.Cos(cDegrees);
double sinDegrees = Math.Sin(cDegrees);
double x = (point3D.X * cosDegrees) + (point3D.Y * sinDegrees);
double y = (point3D.X * -sinDegrees) + (point3D.Y * cosDegrees);
return new Point3D(x, y, point3D.Z);
}
public static Point3D Translate(Point3D points3D, Point3D oldOrigin, Point3D newOrigin)
{
//Moves a 3D point based on a moved reference point
Point3D difference = new Point3D(newOrigin.X - oldOrigin.X, newOrigin.Y - oldOrigin.Y, newOrigin.Z - oldOrigin.Z);
points3D.X += difference.X;
points3D.Y += difference.Y;
points3D.Z += difference.Z;
return points3D;
}
//These are to make the above functions workable with arrays of 3D points
public static Point3D[] RotateX(Point3D[] points3D, double degrees)
{
for (int i = 0; i < points3D.Length; i++)
{
points3D[i] = RotateX(points3D[i], degrees);
}
return points3D;
}
public static Point3D[] RotateY(Point3D[] points3D, double degrees)
{
for (int i = 0; i < points3D.Length; i++)
{
points3D[i] = RotateY(points3D[i], degrees);
}
return points3D;
}
public static Point3D[] RotateZ(Point3D[] points3D, double degrees)
{
for (int i = 0; i < points3D.Length; i++)
{
points3D[i] = RotateZ(points3D[i], degrees);
}
return points3D;
}
public static Point3D[] Translate(Point3D[] points3D, Point3D oldOrigin, Point3D newOrigin)
{
for (int i = 0; i < points3D.Length; i++)
{
points3D[i] = Translate(points3D[i], oldOrigin, newOrigin);
}
return points3D;
}
}
internal class Cube
{
//Example cube class to demonstrate the use of 3D points and 3D rotation
public int width = 0;
public int height = 0;
public int depth = 0;
double xRotation = 0.0;
double yRotation = 0.0;
double zRotation = 0.0;
Math3D.Camera camera1 = new Math3D.Camera();
Math3D.Point3D cubeOrigin;
public double RotateX
{
get { return xRotation; }
set { xRotation = value; }
}
public double RotateY
{
get { return yRotation; }
set { yRotation = value; }
}
public double RotateZ
{
get { return zRotation; }
set { zRotation = value; }
}
public Cube(int side)
{
width = side;
height = side;
depth = side;
cubeOrigin = new Math3D.Point3D(width / 2, height / 2, depth / 2);
}
public Cube(int side, Math3D.Point3D origin)
{
width = side;
height = side;
depth = side;
cubeOrigin = origin;
}
public Cube(int Width, int Height, int Depth)
{
width = Width;
height = Height;
depth = Depth;
cubeOrigin = new Math3D.Point3D(width / 2, height / 2, depth / 2);
}
public Cube(int Width, int Height, int Depth, Math3D.Point3D origin)
{
width = Width;
height = Height;
depth = Depth;
cubeOrigin = origin;
}
//Finds the othermost points. Used so when the cube is drawn on a bitmap,
//the bitmap will be the correct size
public static Rectangle getBounds(PointF[] points)
{
double left = points[0].X;
double right = points[0].X;
double top = points[0].Y;
double bottom = points[0].Y;
for (int i = 1; i < points.Length; i++)
{
if (points[i].X < left)
left = points[i].X;
if (points[i].X > right)
right = points[i].X;
if (points[i].Y < top)
top = points[i].Y;
if (points[i].Y > bottom)
bottom = points[i].Y;
}
return new Rectangle(0, 0, (int)Math.Round(right - left), (int)Math.Round(bottom - top));
}
public Bitmap drawCube(Point drawOrigin)
{
//FRONT FACE
//Top Left - 7
//Top Right - 4
//Bottom Left - 6
//Bottom Right - 5
//Vars
PointF[] point3D = new PointF[24]; //Will be actual 2D drawing points
Point tmpOrigin = new Point(0, 0);
Math3D.Point3D point0 = new Math3D.Point3D(0, 0, 0); //Used for reference
//Zoom factor is set with the monitor width to keep the cube from being distorted
double zoom = (double)Screen.PrimaryScreen.Bounds.Width / 1.5;
//Set up the cube
Math3D.Point3D[] cubePoints = fillCubeVertices(width, height, depth);
//Calculate the camera Z position to stay constant despite rotation
Math3D.Point3D anchorPoint = (Math3D.Point3D)cubePoints[4]; //anchor point
double cameraZ = -(((anchorPoint.X - cubeOrigin.X) * zoom) / cubeOrigin.X) + anchorPoint.Z;
camera1.Position = new Math3D.Point3D(cubeOrigin.X, cubeOrigin.Y, cameraZ);
//Apply Rotations, moving the cube to a corner then back to middle
cubePoints = Math3D.Translate(cubePoints, cubeOrigin, point0);
cubePoints = Math3D.RotateX(cubePoints, xRotation); //The order of these
cubePoints = Math3D.RotateY(cubePoints, yRotation); //rotations is the source
cubePoints = Math3D.RotateZ(cubePoints, zRotation); //of Gimbal Lock
cubePoints = Math3D.Translate(cubePoints, point0, cubeOrigin);
//Convert 3D Points to 2D
Math3D.Point3D vec;
for (int i = 0; i < point3D.Length; i++)
{
vec = cubePoints[i];
if (vec.Z - camera1.Position.Z >= 0)
{
point3D[i].X = (int)((double)-(vec.X - camera1.Position.X) / (-0.1f) * zoom) + drawOrigin.X;
point3D[i].Y = (int)((double)(vec.Y - camera1.Position.Y) / (-0.1f) * zoom) + drawOrigin.Y;
}
else
{
tmpOrigin.X = (int)((double)(cubeOrigin.X - camera1.Position.X) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.X;
tmpOrigin.Y = (int)((double)-(cubeOrigin.Y - camera1.Position.Y) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.Y;
point3D[i].X = (float)((vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.X);
point3D[i].Y = (float)(-(vec.Y - camera1.Position.Y) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.Y);
point3D[i].X = (int)point3D[i].X;
point3D[i].Y = (int)point3D[i].Y;
}
}
//Now to plot out the points
Rectangle bounds = getBounds(point3D);
bounds.Width += drawOrigin.X;
bounds.Height += drawOrigin.Y;
Bitmap tmpBmp = new Bitmap(bounds.Width, bounds.Height);
Graphics g = Graphics.FromImage(tmpBmp);
//Back Face
g.DrawLine(Pens.Black, point3D[0], point3D[1]);
g.DrawLine(Pens.Black, point3D[1], point3D[2]);
g.DrawLine(Pens.Black, point3D[2], point3D[3]);
g.DrawLine(Pens.Black, point3D[3], point3D[0]);
//Front Face
g.DrawLine(Pens.Black, point3D[4], point3D[5]);
g.DrawLine(Pens.Black, point3D[5], point3D[6]);
g.DrawLine(Pens.Black, point3D[6], point3D[7]);
g.DrawLine(Pens.Black, point3D[7], point3D[4]);
//Right Face
g.DrawLine(Pens.Black, point3D[8], point3D[9]);
g.DrawLine(Pens.Black, point3D[9], point3D[10]);
g.DrawLine(Pens.Black, point3D[10], point3D[11]);
g.DrawLine(Pens.Black, point3D[11], point3D[8]);
//Left Face
g.DrawLine(Pens.Black, point3D[12], point3D[13]);
g.DrawLine(Pens.Black, point3D[13], point3D[14]);
g.DrawLine(Pens.Black, point3D[14], point3D[15]);
g.DrawLine(Pens.Black, point3D[15], point3D[12]);
//Bottom Face
g.DrawLine(Pens.Black, point3D[16], point3D[17]);
g.DrawLine(Pens.Black, point3D[17], point3D[18]);
g.DrawLine(Pens.Black, point3D[18], point3D[19]);
g.DrawLine(Pens.Black, point3D[19], point3D[16]);
//Top Face
g.DrawLine(Pens.Black, point3D[20], point3D[21]);
g.DrawLine(Pens.Black, point3D[21], point3D[22]);
g.DrawLine(Pens.Black, point3D[22], point3D[23]);
g.DrawLine(Pens.Black, point3D[23], point3D[20]);
g.Dispose(); //Clean-up
return tmpBmp;
}
public static Math3D.Point3D[] fillCubeVertices(int width, int height, int depth)
{
Math3D.Point3D[] verts = new Math3D.Point3D[24];
//front face
verts[0] = new Math3D.Point3D(0, 0, 0);
verts[1] = new Math3D.Point3D(0, height, 0);
verts[2] = new Math3D.Point3D(width, height, 0);
verts[3] = new Math3D.Point3D(width, 0, 0);
//back face
verts[4] = new Math3D.Point3D(0, 0, depth);
verts[5] = new Math3D.Point3D(0, height, depth);
verts[6] = new Math3D.Point3D(width, height, depth);
verts[7] = new Math3D.Point3D(width, 0, depth);
//left face
verts[8] = new Math3D.Point3D(0, 0, 0);
verts[9] = new Math3D.Point3D(0, 0, depth);
verts[10] = new Math3D.Point3D(0, height, depth);
verts[11] = new Math3D.Point3D(0, height, 0);
//right face
verts[12] = new Math3D.Point3D(width, 0, 0);
verts[13] = new Math3D.Point3D(width, 0, depth);
verts[14] = new Math3D.Point3D(width, height, depth);
verts[15] = new Math3D.Point3D(width, height, 0);
//top face
verts[16] = new Math3D.Point3D(0, height, 0);
verts[17] = new Math3D.Point3D(0, height, depth);
verts[18] = new Math3D.Point3D(width, height, depth);
verts[19] = new Math3D.Point3D(width, height, 0);
//bottom face
verts[20] = new Math3D.Point3D(0, 0, 0);
verts[21] = new Math3D.Point3D(0, 0, depth);
verts[22] = new Math3D.Point3D(width, 0, depth);
verts[23] = new Math3D.Point3D(width, 0, 0);
return verts;
}
}
}
Right now when I run this code in visual studio 2015 it creates a 3D cube but how do i modify it to where I can get a pyramid?
Related
How to find coordinate of contour centroid in C#
I am doing image processing so that I am finding contours in the image. What I need is the centroid pixel number of the found contour in the image. To find the pixel number I am using the code given below. After finding the pixel number I want to show it in the text boxes as x and y coordinates. But the code is not working. Please help me. What is wrong? VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); CvInvoke.FindContours(cannyImage, contours, null, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple); var cannyOut = cannyImage.ToImage<Bgr, byte>(); //CvInvoke.DrawContours(cannyOut, contours, 2, new MCvScalar(255, 0, 0),2); VectorOfPoint approx = new VectorOfPoint(); Dictionary<int, double> shapes = new Dictionary<int, double>(); for (int i = 0; i < contours.Size; i++) { approx.Clear(); double perimeter = CvInvoke.ArcLength(contours[i], true); CvInvoke.ApproxPolyDP(contours[i], approx, 0.04 * perimeter, true); double area = CvInvoke.ContourArea(contours[i]); if (approx.Size > 4) { shapes.Add(i, area); } } if (shapes.Count > 0) { var sortedShapes = (from item in shapes orderby item.Value ascending select item).ToList(); for (int i = 0; i < sortedShapes.Count; i++) { CvInvoke.DrawContours(cannyOut, contours, sortedShapes[i].Key, new MCvScalar(255, 0, 0), 2); var moments = CvInvoke.Moments(contours[sortedShapes[i].Key]); int x = (int)(moments.M10 / moments.M00); int y = (int)(moments.M01 / moments.M00); CvInvoke.PutText(cannyOut, (i + 1).ToString(), new Point(x, y), Emgu.CV.CvEnum.FontFace.HersheyTriplex, 1.0, new MCvScalar(255, 0, 0), 2); //CvInvoke.PutText(cannyOut, sortedShapes[i].Value.ToString(), new Point(x, y - 30), Emgu.CV.CvEnum.FontFace.HersheyTriplex, 1.0, // new MCvScalar(255, 0, 0), 2); textBox1.Text = x.ToString(); textBox2.Text = y.ToString(); } }
To find the centroid of a shape you need to split it into many triangles first. Then for each triangle with vertices A, B, C you do the summation weighted by the area of the triangle just as so static void Main(string[] args) { var shape = new List<Triangle>(); // fill shape with triangles float area = 0f; Vector2 centroid = Vector2.Zero; foreach (var triangle in shape) { float trig_area = triangle.Area; Vector2 trig_cen = triangle.Centroid; area += trig_area; centroid += trig_area * trig_cen; } centroid /= area; } For reference, a 2D triangle has the following properties public readonly struct Triangle { public Triangle(Vector2 a, Vector2 b, Vector2 c) : this() { A = a; B = b; C = c; } public Vector2 A { get; } public Vector2 B { get; } public Vector2 C { get; } public float Area { get => (Cross(A, B) + Cross(B, C) + Cross(C, A)) / 2; } public Vector2 Centroid { get => (A + B + C) / 3; } // helper function static float Cross(Vector2 a, Vector2 b) => a.X * b.Y - a.Y * b.X; }
Drawing points along path spirally
Well, I'm trying to optimize what I did here (Smoothing noises with different amplitudes (Part 2)). By this reason, I did a new implementation from scratch (https://youtu.be/o7pVEXhh3TI) to draw the path: private void Start() { Polygon pol = File.ReadAllText(PolyPath).Deserialize<Polygon>(); // Create tex object var list = pol.Vertices.AsEnumerable(); tex = list.CreateTextureObject(pol.Position, offset); exampleTexture = new Texture2D(tex.Width, tex.Height); exampleTexture.SetPixels32(new Color32[tex.Width * tex.Height]); exampleTexture.Apply(); vertices = pol.Vertices.Select(v => (v - pol.Position) + offset).Clone().ToList(); _ss = new List<Segment>(pol.Segments.Select(s => new Segment((s.start + pol.Center - pol.Position) + offset, (s.end + pol.Center - pol.Position) + offset))); foreach (Segment curSeg in _ss) for (int i = -effectDistance; i < effectDistance; ++i) { Vector2 perp = Vector2.Perpendicular(((Vector2)curSeg.start - (Vector2)curSeg.end)).normalized; segments.Add((Vector2)curSeg.start + perp * i); F.DrawLine((Vector2)curSeg.start + perp * i, (Vector2)curSeg.end + perp * i, (x, y) => layers.Add(new Point(x, y))); } Debug.Log("Layer Count: " + layers.Count); drawPath = true; } private void OnGUI() { if (exampleTexture == null) return; GUI.DrawTexture(new Rect((Screen.width - tex.Width) / 2, (Screen.height - tex.Height) / 2, tex.Width, tex.Height), exampleTexture); if (drawPath) { { Point? cur = layers.Count > 0 ? (Point?)layers.First() : null; if (cur.HasValue) { exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(170, 0, 0, 255)); exampleTexture.Apply(); layers.Remove(cur.Value); } } { Point? cur = segments.Count > 0 ? (Point?)segments.First() : null; if (cur.HasValue) { exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(0, 170, 0, 255)); exampleTexture.Apply(); segments.Remove(cur.Value); } } { Point? cur = vertices.Count > 0 ? (Point?)vertices.First() : null; //Debug.Log(cur); if (cur.HasValue) { exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(255, 128, 0, 255)); exampleTexture.Apply(); vertices.Remove(cur.Value); } } if (vertices.Count == 0 && segments.Count == 0 && layers.Count == 0) drawPath = false; } } This is what DrawLines actually do: public static class F { public static void DrawLine(Point p1, Point p2, Action<int, int> action) { DrawLine(p1.x, p1.y, p2.x, p2.y, action); } public static void DrawLine(int x0, int y0, int x1, int y1, Action<int, int> action) { int sx = 0, sy = 0; int dx = Mathf.Abs(x1 - x0), dy = Mathf.Abs(y1 - y0); if (x0 < x1) { sx = 1; } else { sx = -1; } if (y0 < y1) { sy = 1; } else { sy = -1; } int err = dx - dy, e2 = 0; while (true) { action?.Invoke(x0, y0); if ((x0 == x1) && (y0 == y1)) break; e2 = 2 * err; if (e2 > -dy) { err = err - dy; x0 = x0 + sx; } if (e2 < dx) { err = err + dx; y0 = y0 + sy; } } } } This is an implemenentation of Bresenham algorithm. This implementation is better because I have lowered iterations from 280k to 6k, but there is an issue as you can see this is innacurate... The way this works first is getting the perpendicular of each segment on the shape (green pixels) and then drawing lines between the start and the end point of that segment. Segmenents are obtained using Ramer-Douglas-Peucker algorithm. So I was thinking on draw the "orange" path spirally. I don't know how to explain this, basically, obtaining the same path but, with an scale (Translating/transforming? list of points from its center with an offset/distance) but I think I will have the same innacuracy. Any guide will be appreciated. What algorithm could I use to draw the path with "layers"?
Following some of the information here, you might be able to use "inward/outward polygon offsetting" (aka "polygon buffering") to get the result you are interested in. A tool such as Clipper can help. Once you have a way to outwardly offset your shape, do the following: First, draw the outer shape (black region below), then offset the inner shape outwards as far as you need it to go, and draw it on top of the outer shape (brown region below) using an appropriate noise/color scheme: Then, apply a smaller offset, then draw that shape on top using a different noise/colorscheme (orange region below). Repeat until you have as many gradients as you need: Finally, draw the inner shape without any offsetting with its noise/color scheme:
Rotating a PictureBox is cropping the Image
I have problem that image is getting cropped when I execute a function there are images which rotation do and my code: I copied this code from StackOverflow and searched for this quite a long time, so some help would be good I don't know why is cutting the image at every rotation. public static Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle, bool bNoClip) { int W, H, X, Y; if (bNoClip) { double dW = (double)image.Width; double dH = (double)image.Height; double degrees = Math.Abs(angle); if (degrees <= 90) { double radians = 0.0174532925 * degrees; double dSin = Math.Sin(radians); double dCos = Math.Cos(radians); W = (int)(dH * dSin + dW * dCos); H = (int)(dW * dSin + dH * dCos); X = (W - image.Width) / 2; Y = (H - image.Height) / 2; } else { degrees -= 90; double radians = 0.0174532925 * degrees; double dSin = Math.Sin(radians); double dCos = Math.Cos(radians); W = (int)(dW * dSin + dH * dCos); H = (int)(dH * dSin + dW * dCos); X = (W - image.Width) / 2; Y = (H - image.Height) / 2; } } else { W = image.Width; H = image.Height; X = 0; Y = 0; } //create a new empty bitmap to hold rotated image Bitmap bmpRet = new Bitmap(W, H); bmpRet.SetResolution(image.HorizontalResolution, image.VerticalResolution); //make a graphics object from the empty bitmap Graphics g = Graphics.FromImage(bmpRet); //Put the rotation point in the "center" of the image g.TranslateTransform(rotateAtX + X, rotateAtY + Y); //rotate the image g.RotateTransform(angle); //move the image back g.TranslateTransform(-rotateAtX - X, -rotateAtY - Y); //draw passed in image onto graphics object g.DrawImage(image, new PointF(0 + X, 0 + Y)); return bmpRet; } public static Bitmap RotateImage(Image image, float angle) { // center of the image float rotateAtX = image.Width / 2; float rotateAtY = image.Height / 2; bool bNoClip = false; return RotateImage(image, rotateAtX, rotateAtY, angle, bNoClip); } public static Bitmap RotateImage(Image image, float angle, bool bNoClip) { // center of the image float rotateAtX = image.Width / 2; float rotateAtY = image.Height / 2; return RotateImage(image, rotateAtX, rotateAtY, angle, bNoClip); } private void DOWN_Click(object sender, EventArgs e) { locomotive.Image = RotateImage(locomotive.Image, 35); }
Drawing triangles on each edge of regular polygon
I've been tried to draw triangles on each edge of regular polygons. So far I got to make polygons like this: What I'm trying to make is that small triangle on the each edge of the polygon: How do I do this? Code how to draw polygons is below: int sides = 5; Graphics g = e.Graphics; nPoints = CalculateVertices(sides, radius, angle, center); g.DrawPolygon(navypen, nPoints); g.FillPolygon(BlueBrush, nPoints); Point center = new Point(ClientSize.Width / 2, ClientSize.Height / 2); for(int i = 0; i < sides; i++) { g.DrawLine(new Pen(Color.Navy), center.X, center.Y, nPoints[i].X, nPoints[i].Y); } private PointF[] CalculateVertices(int sides, int radius, float startingAngle, Point center) { if (sides < 3) { sides = 3; } //throw new ArgumentException("Polygon must have 3 sides or more."); List<PointF> points = new List<PointF>(); float step = 360.0f / sides; float angle = startingAngle; //starting angle for (double i = startingAngle; i < startingAngle + 360.0; i += step) //go in a circle { points.Add(DegreesToXY(angle, radius, center)); angle += step; } return points.ToArray(); } private PointF DegreesToXY(float degrees, float radius, Point origin) { PointF xy = new PointF(); double radians = degrees * Math.PI / 180.0; xy.X = (int)(Math.Cos(radians) * radius + origin.X); xy.Y = (int)(Math.Sin(-radians) * radius + origin.Y); return xy; }
Try this out...instead of calculating absolute points for the triangle, I've instead computed points for a "unit triangle" at the origin (using your function!). Then I simply rotate and move the Graphics surface and draw the unit triangle where I want it: public partial class Form1 : Form { public Form1() { InitializeComponent(); } private PointF[] nPoints; private PointF[] triangle; private int sides = 5; private int angle = 0; private int radius = 100; private int triangleLength = 10; private void Form1_Load(object sender, EventArgs e) { triangle = this.CalculateVertices(3, triangleLength, 0, new Point(0, 0)); // this "unit triangle" will get reused! } private void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; Point center = new Point(ClientSize.Width / 2, ClientSize.Height / 2); nPoints = CalculateVertices(sides, radius, angle, center); // draw the polygon g.FillPolygon(Brushes.Blue, nPoints); g.DrawPolygon(Pens.Black, nPoints); for (int i = 0; i < sides; i++) { g.DrawLine(Pens.Black, center.X, center.Y, nPoints[i].X, nPoints[i].Y); } // draw small triangles on each edge: float step = 360.0f / sides; float curAngle = angle + step / 2; // start in-between the original angles for (double i = curAngle; i < angle + (step / 2) + 360.0; i += step) //go in a circle { // move to the center and rotate: g.ResetTransform(); g.TranslateTransform(center.X, center.Y); g.RotateTransform((float)i); // move out to where the triangle will be drawn and render it g.TranslateTransform(radius, 0); g.FillPolygon(Brushes.LightGreen, triangle); g.DrawPolygon(Pens.Black, triangle); } } // this is your code unchanged private PointF[] CalculateVertices(int sides, int radius, float startingAngle, Point center) { if (sides < 3) { sides = 3; } //throw new ArgumentException("Polygon must have 3 sides or more."); List<PointF> points = new List<PointF>(); float step = 360.0f / sides; float angle = startingAngle; //starting angle for (double i = startingAngle; i < startingAngle + 360.0; i += step) //go in a circle { points.Add(DegreesToXY(angle, radius, center)); angle += step; } return points.ToArray(); } // this is your code unchanged private PointF DegreesToXY(float degrees, float radius, Point origin) { PointF xy = new PointF(); double radians = degrees * Math.PI / 180.0; xy.X = (int)(Math.Cos(radians) * radius + origin.X); xy.Y = (int)(Math.Sin(-radians) * radius + origin.Y); return xy; } }
Point-Zoom on Mandelbrot Set in C# - It works, except when the mouse has moved
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