diff --git a/Demo/AngleBar.Designer.cs b/Demo/AngleBar.Designer.cs new file mode 100644 index 0000000..e99426a --- /dev/null +++ b/Demo/AngleBar.Designer.cs @@ -0,0 +1,95 @@ + +namespace Demo +{ + partial class AngleBar + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.trackBar1 = new System.Windows.Forms.TrackBar(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); + this.checkBox1 = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + this.SuspendLayout(); + // + // trackBar1 + // + this.trackBar1.Dock = System.Windows.Forms.DockStyle.Fill; + this.trackBar1.Location = new System.Drawing.Point(0, 0); + this.trackBar1.Maximum = 360; + this.trackBar1.Minimum = 0; + this.trackBar1.Name = "trackBar1"; + this.trackBar1.Size = new System.Drawing.Size(485, 34); + this.trackBar1.TabIndex = 0; + this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll); + // + // numericUpDown1 + // + this.numericUpDown1.AutoSize = true; + this.numericUpDown1.Dock = System.Windows.Forms.DockStyle.Right; + this.numericUpDown1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.numericUpDown1.Location = new System.Drawing.Point(485, 0); + this.numericUpDown1.Maximum = 360; + this.numericUpDown1.Minimum = 0; + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(53, 29); + this.numericUpDown1.TabIndex = 1; + this.numericUpDown1.ValueChanged += new System.EventHandler(this.numericUpDown1_ValueChanged); + // + // checkBox1 + // + this.checkBox1.AutoSize = true; + this.checkBox1.Dock = System.Windows.Forms.DockStyle.Left; + this.checkBox1.Location = new System.Drawing.Point(0, 0); + this.checkBox1.Name = "checkBox1"; + this.checkBox1.Size = new System.Drawing.Size(15, 34); + this.checkBox1.TabIndex = 2; + this.checkBox1.UseVisualStyleBackColor = true; + // + // AngleBar + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.trackBar1); + this.Controls.Add(this.numericUpDown1); + this.Controls.Add(this.checkBox1); + this.Name = "AngleBar"; + this.Size = new System.Drawing.Size(538, 34); + ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TrackBar trackBar1; + private System.Windows.Forms.NumericUpDown numericUpDown1; + private System.Windows.Forms.CheckBox checkBox1; + } +} diff --git a/Demo/AngleBar.cs b/Demo/AngleBar.cs new file mode 100644 index 0000000..7b24381 --- /dev/null +++ b/Demo/AngleBar.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Demo +{ + public partial class AngleBar : UserControl + { + public AngleBar() + { + InitializeComponent(); + } + + public double Angle + { + get => trackBar1.Value / 180.0 * Math.PI; + set => numericUpDown1.Value = (decimal)((value % (Math.PI*2)) / Math.PI * 180); + } + + public bool Checked => checkBox1.Checked; + + + private void trackBar1_Scroll(object sender, EventArgs e) + { + numericUpDown1.Value = trackBar1.Value; + } + + private void numericUpDown1_ValueChanged(object sender, EventArgs e) + { + trackBar1.Value = (int)numericUpDown1.Value; + } + } +} diff --git a/Demo/AngleBar.resx b/Demo/AngleBar.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/Demo/AngleBar.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Demo/Form1.Designer.cs b/Demo/Form1.Designer.cs index 9cfbb3d..24f5e00 100644 --- a/Demo/Form1.Designer.cs +++ b/Demo/Form1.Designer.cs @@ -29,7 +29,10 @@ namespace Demo /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.timer1 = new System.Windows.Forms.Timer(this.components); + this.panel1 = new System.Windows.Forms.Panel(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.SuspendLayout(); // @@ -38,27 +41,46 @@ namespace Demo this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; this.pictureBox1.Location = new System.Drawing.Point(0, 0); this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(800, 450); + this.pictureBox1.Size = new System.Drawing.Size(788, 766); this.pictureBox1.TabIndex = 0; this.pictureBox1.TabStop = false; - this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click); + this.pictureBox1.SizeChanged += new System.EventHandler(this.pictureBox1_SizeChanged); + // + // timer1 + // + this.timer1.Enabled = true; + this.timer1.Interval = 50; + this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + // + // panel1 + // + this.panel1.AutoSize = true; + this.panel1.Dock = System.Windows.Forms.DockStyle.Top; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(788, 0); + this.panel1.TabIndex = 1; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(800, 450); + this.ClientSize = new System.Drawing.Size(788, 766); this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.panel1); this.Name = "Form1"; this.Text = "Form1"; ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); this.ResumeLayout(false); + this.PerformLayout(); } #endregion private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Timer timer1; + private System.Windows.Forms.Panel panel1; } } diff --git a/Demo/Form1.cs b/Demo/Form1.cs index f343f4d..4bb4049 100644 --- a/Demo/Form1.cs +++ b/Demo/Form1.cs @@ -9,6 +9,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using YMath.Geometry; +using System.Diagnostics.CodeAnalysis; namespace Demo { @@ -17,74 +19,156 @@ namespace Demo public Form1() { InitializeComponent(); + CreateTrackBars(); + InitializeImage(); + } + + + double angle = 0; + + private void Render(Bitmap bmp) + { + g.Clear(Color.White); + + Projector projector3d2d = new Projector( + new Camera(1), + new CoordSystem(new Vector(0, 0, 2), YMath.Matrix.Ident(3))); + + Projector projector4d3d = new Projector( + new Camera(1), + new CoordSystem(new Vector(0, 0, 0, 3), YMath.Matrix.Ident(4))); + + Geom geom = Geometries.NewCube(4); + + double[] angles = angleSelectBars.Select(b => b.Angle).ToArray(); + projector4d3d.CoordSystem.RotationMatrix + = YMath.Matrix.Rotation(4, angles); + angle += 0.01; + + geom = projector4d3d.Project(geom); + geom = projector3d2d.Project(geom); + + foreach (var v in geom.Verticies) + DrawPoint(v); + + foreach (var line in geom.Edges) + DrawLine(line); } - private void pictureBox1_Click(object sender, EventArgs e) + private void DrawPoint(Vector v) + { + var pos = Denormalize(v); + var diameter = 2; + var rect = new RectangleF( + pos.X - diameter, pos.Y - diameter, diameter * 2, diameter * 2); + g.FillEllipse(Brushes.Black, rect); + } + + private void DrawLine(LineSegment line) { - /*var g = pictureBox1.CreateGraphics(); - g.SmoothingMode = SmoothingMode.HighQuality; var pen = new Pen(Brushes.Black, 2); - var rect = CubePoints(100.0, 2); - DrawLines(g, pen, rect);*/ - }/* - - // [-1; 1] -> [0; width] - private static Vector[] RelativeToAbsolute(Vector[] points, int width, int height) - { - return points.Select(p => new Vector( - (p[0] + 1) / 2 * width, - (p[1] + 1) / 2 * height)) - .ToArray(); + g.DrawLine(pen, Denormalize(line.Start), Denormalize(line.End)); } - private static void DrawLines(Graphics g, Pen pen, Line[] lines) + private PointF Denormalize(Vector p) { - foreach (var line in lines) - g.DrawLine(pen, - new PointF((float)line.Start[0], (float)line.End[1]), - new PointF((float)line.Start[0], (float)line.End[1])); + p = (p + new Vector(1, 1)) / 2; + p[0] *= bmp.Width; + p[1] = 1 - p[1]; + p[1] *= bmp.Height; + return new PointF((float)p[0], (float)p[1]); } - public struct Line + private void timer1_Tick(object sender, EventArgs e) { - public Line(Vector start, Vector end) + Render(bmp); + pictureBox1.Refresh(); + foreach (var bar in angleSelectBars) + if (bar.Checked) + bar.Angle += 0.03; + } + + Bitmap bmp; + Graphics g; + + private void pictureBox1_SizeChanged(object sender, EventArgs e) + { + InitializeImage(); + } + + [MemberNotNull(nameof(bmp), nameof(g))] + private void InitializeImage() + { + bmp = new Bitmap(pictureBox1.Width, pictureBox1.Width); + g = Graphics.FromImage(bmp); + g.SmoothingMode = SmoothingMode.HighQuality; + pictureBox1.Image = bmp; + } + + + AngleBar[] angleSelectBars; + private void CreateTrackBars() + { + angleSelectBars = new AngleBar[6]; + for (int i = 0; i < angleSelectBars.Length; i++) { - Start = start; - End = end; + var bar = new AngleBar(); + bar.Dock = DockStyle.Top; + panel1.Controls.Add(bar); + angleSelectBars[i] = bar; + bar.BringToFront(); } - - public Vector Start { get; set; } - public Vector End { get; set; } } - public static Line[] Cube(int nDimensions) - { - if (nDimensions == 0) - return new Vector[] { new Vector(0) }; + /* - var result = new List(); - var offset = new Vector(nDimensions); - offset[nDimensions - 1] = 1; - var face /*боже*//* = Cube(nDimensions - 1); - result.AddRange(face.Select(p => p + offset)); - result.AddRange(face.Select(p => p - offset)); +// [-1; 1] -> [0; width] +private static Vector[] RelativeToAbsolute(Vector[] points, int width, int height) +{ +return points.Select(p => new Vector( +(p[0] + 1) / 2 * width, +(p[1] + 1) / 2 * height)) +.ToArray(); +} - } +private static void DrawLines(Graphics g, Pen pen, Line[] lines) +{ +foreach (var line in lines) +g.DrawLine(pen, +new PointF((float)line.Start[0], (float)line.End[1]), +new PointF((float)line.Start[0], (float)line.End[1])); +} - public static Vector[] CubePoints(double size, int nDimensions) - { - int n = 1 << nDimensions; - if (nDimensions >= 32) - throw new ArgumentException("Fuck yourself"); - var result = new Vector[n]; - for (int i = 0; i < n; i++) - { - var vec = new Vector(nDimensions); - for (int j = 0; j < nDimensions; j++) - vec[j] = ((i >> j) & 1) - 0.5; - result[i] = vec; - } - return result; - }*/ + + +public static Line[] Cube(int nDimensions) +{ +if (nDimensions == 0) +return new Vector[] { new Vector(0) }; + +var result = new List(); +var offset = new Vector(nDimensions); +offset[nDimensions - 1] = 1; +var face /*боже*//* = Cube(nDimensions - 1); +result.AddRange(face.Select(p => p + offset)); +result.AddRange(face.Select(p => p - offset)); + +} + +public static Vector[] CubePoints(double size, int nDimensions) +{ +int n = 1 << nDimensions; +if (nDimensions >= 32) + throw new ArgumentException("Fuck yourself"); +var result = new Vector[n]; +for (int i = 0; i < n; i++) +{ + var vec = new Vector(nDimensions); + for (int j = 0; j < nDimensions; j++) + vec[j] = ((i >> j) & 1) - 0.5; + result[i] = vec; +} +return result; +}*/ } } diff --git a/Demo/Program.cs b/Demo/Program.cs index 4007da9..16fbfad 100644 --- a/Demo/Program.cs +++ b/Demo/Program.cs @@ -18,17 +18,6 @@ namespace Demo Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - - - Projector p = new Projector( - new Camera(1), - new CoordSystem(new Vector(), Matrix.Ident(4))); - var point = p.Project(new Vector(1, 1, 1, 2)); - - - - - Application.Run(new Form1()); } } diff --git a/YMath.sln b/YMath.sln index a505dae..0a00941 100644 --- a/YMath.sln +++ b/YMath.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30711.63 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YMath", "YMath\YMath.csproj", "{33638CEC-2BD2-4B91-A1CB-0383F7E429BE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YMath", "YMath\YMath.csproj", "{33638CEC-2BD2-4B91-A1CB-0383F7E429BE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{CE175AF3-5242-4178-BB97-54956FE958D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{CE175AF3-5242-4178-BB97-54956FE958D8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/YMath/Combinatorics.cs b/YMath/Combinatorics.cs new file mode 100644 index 0000000..0213986 --- /dev/null +++ b/YMath/Combinatorics.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YMath +{ + /// + /// Basic combinatorial functions + /// + static class Combinatorics + { + /// + /// Returns number of k-combinations from set of n elements + /// (aka binomial coefficient) + /// + public static int C(int n, int k) + { + // https://stackoverflow.com/questions/9330915/number-of-combinations-n-choose-r-in-c + if (k > n) return 0; + if (k * 2 > n) k = n - k; + if (k == 0) return 1; + + int result = n; + for (int i = 2; i <= k; ++i) + { + result *= (n - i + 1); + result /= i; + } + return result; + } + + public static int Factorial(int n) + { + int res = 1; + for (int i = 1; i < n; n++) + res *= i; + return res; + } + } +} diff --git a/YMath/Geometry/Geom.cs b/YMath/Geometry/Geom.cs new file mode 100644 index 0000000..a7ca452 --- /dev/null +++ b/YMath/Geometry/Geom.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YMath.Geometry +{ + /// + /// Represents geomretry object + /// + public class Geom : ICloneable + { + /// + /// Creates new geometry containing given points and lines + /// + public Geom(Vector[] verticies, LineSegment[] edges) + { + IEnumerable dimensions = verticies + .Select(v => v.Dimensions) + .Concat(edges.SelectMany( + e => new[] { e.Start.Dimensions, e.End.Dimensions })); + if (!dimensions.All(d => d == dimensions.First())) + throw new ArgumentException("Dimensions inconsistent"); + + Verticies = verticies; + Edges = edges; + } + + /// + /// Creates a new geomretry merging the given set of geometries + /// + /// + public Geom(params Geom[] geometries) + : this(geometries.SelectMany( g=> g.Verticies).ToArray(), + geometries.SelectMany(g => g.Edges).ToArray()) + { + } + + /// + /// Verticies of the object + /// + public Vector[] Verticies { get; set; } + + /// + /// Edges of the object + /// + public LineSegment[] Edges { get; set; } + + public object Clone() + { + return new Geom( + (Vector[])Verticies.Clone(), (LineSegment[])Edges.Clone()); + } + + /// + /// Shifts the object by a given vector + /// + public static Geom operator +(Geom left, Vector shift) + { + return new Geom( + left.Verticies.Select(v => v + shift).ToArray(), + left.Edges.Select(l => new LineSegment(l.Start + shift, l.End + shift)).ToArray()); + } + + public override string ToString() + { + var b = new StringBuilder(); + b.AppendLine("Verticies:"); + foreach (var v in Verticies) + b.AppendLine(" " + v.ToString()); + b.AppendLine("Edges:"); + foreach (var v in Edges) + b.AppendLine(" " + v.ToString()); + return b.ToString(); + } + } +} diff --git a/YMath/Geometry/Geometries.cs b/YMath/Geometry/Geometries.cs new file mode 100644 index 0000000..56e9085 --- /dev/null +++ b/YMath/Geometry/Geometries.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YMath.Geometry +{ + /// + /// A helper class to build arbitrary objects + /// + public static class Geometries + { + /// + /// Returns a new n-dimensional cube + /// + /// Number of dimensions + /// A new n-dimensional cubes. All coordinates of all verticies are either +1 or -1. + public static Geom NewCube(uint nDimesions) + { + if (nDimesions == 0) + return new Geom(new Vector[] { new Vector(0) }, + new LineSegment[0]); + + // Creating two n-1 dimensional faces + var offset = new Vector((int)nDimesions); + offset[offset.Dimensions - 1] = 1; + var face /*боже*/ = NewCube(nDimesions-1); + var face1 = face + offset; + var face2 /*боже упаси*/ = face + -offset; + + // Connecting these faces + var lines = face1.Verticies + .Zip(face2.Verticies) + .Select(p => new LineSegment(p.First, p.Second)) + .ToArray(); + return new Geom(face1, face2, new Geom(new Vector[0], lines)); + + } + } +} diff --git a/YMath/Geometry/LineSegment.cs b/YMath/Geometry/LineSegment.cs new file mode 100644 index 0000000..c9fad26 --- /dev/null +++ b/YMath/Geometry/LineSegment.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YMath.Geometry +{ + /// + /// Represents a line segment defined by 2 points + /// + public struct LineSegment + { + public LineSegment(Vector start, Vector end) + { + Start = start; + End = end; + } + + public Vector Start { get; set; } + public Vector End { get; set; } + + public override string ToString() + { + return $"{Start} - {End}"; + } + } +} diff --git a/YMath/Matrix.cs b/YMath/Matrix.cs index 03ba9f5..846a92b 100644 --- a/YMath/Matrix.cs +++ b/YMath/Matrix.cs @@ -137,5 +137,67 @@ namespace YMath result[i, i] = 1; return result; } + + /// + /// Returns an n-dimensional rotation matrix. + /// Rotates the object by (in rad) in the plane formed by and . + /// + /// Order of the output matrix + /// The first axis index starting from 0 + /// The second axis index starting from 0 + /// The angle in radians + /// n-dimensional rotation matrix + public static Matrix Rotation(int nDimensions, int axis1, int axis2, double angle) + { + if (axis1 == axis2) + throw new ArgumentException("axis1 == axis2"); + if (axis1 >= nDimensions || axis2 >= nDimensions) + throw new ArgumentOutOfRangeException("axes"); + if (axis1 < 0 || axis2 < 0 || nDimensions < 0) + throw new ArgumentOutOfRangeException("Dimension number and axis indexes can't be less then 0"); + + var res = Matrix.Ident(nDimensions); + res[axis1, axis1] = Math.Cos(angle); + res[axis2, axis2] = Math.Cos(angle); + res[axis1, axis2] = -Math.Sin(angle); + res[axis2, axis1] = Math.Sin(angle); + + return res; + } + + /// + /// Creates n-dimensional rotation matrixes, which rotates by a given Euiler angles + /// + /// Matrix order + /// Euiler angles in rad + /// + public static Matrix Rotation(int nDimensions, double[] angles) + { + int n = Combinatorics.C(nDimensions, 2); + if (angles.Length != n) + throw new ArgumentException($"Expected {n} Euiler angles for {nDimensions}-dimensional space, got {angles.Length} angles"); + var result = Matrix.Ident(nDimensions); + + // Iterate over axis combinations + int axis1 = 0; + int axis2 = 1; + int iAngle = 0; + while (axis2 < nDimensions) + { + double angle = angles[iAngle]; + result *= Matrix.Rotation(nDimensions, axis1, axis2, angle); + + iAngle++; + if (axis1 + 1 == axis2) + { + axis2++; + axis1 = 0; + } + else + axis1++; + } + + return result; + } } } diff --git a/YMath/Projector.cs b/YMath/Projector.cs index c27ec31..f354c67 100644 --- a/YMath/Projector.cs +++ b/YMath/Projector.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using YMath.Geometry; namespace YMath { @@ -35,5 +36,17 @@ namespace YMath point = Camera.WorldToImage(point); return point; } + + /// + /// Projects the given geometry + /// + /// The N-1 dimensional geometry + public Geom Project(Geom geom) + { + return new Geom( + geom.Verticies.Select(v => Project(v)).ToArray(), + geom.Edges.Select(e => new LineSegment( + Project(e.Start), Project(e.End))).ToArray()); + } } }