diff --git a/.gitignore b/.gitignore index 710ed0d..fcc8853 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ convimg.out # Other stuff. .DS_Store */.vs/ +*.zip diff --git a/AirTrajectoryBuilder/AirTrajectoryBuilder.zip b/AirTrajectoryBuilder/AirTrajectoryBuilder.zip deleted file mode 100644 index 46350ed..0000000 Binary files a/AirTrajectoryBuilder/AirTrajectoryBuilder.zip and /dev/null differ diff --git a/FractalVisualizer/FractalVisualizer.sln b/FractalVisualizer/FractalVisualizer.sln new file mode 100644 index 0000000..e33061e --- /dev/null +++ b/FractalVisualizer/FractalVisualizer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FractalVisualizer", "FractalVisualizer\FractalVisualizer.csproj", "{FE00D3E5-97BE-4DD3-92AB-8E0CEF64BAC2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FE00D3E5-97BE-4DD3-92AB-8E0CEF64BAC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE00D3E5-97BE-4DD3-92AB-8E0CEF64BAC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE00D3E5-97BE-4DD3-92AB-8E0CEF64BAC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE00D3E5-97BE-4DD3-92AB-8E0CEF64BAC2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E98DD73D-4241-425D-B5CF-EC5B2B0D92EB} + EndGlobalSection +EndGlobal diff --git a/FractalVisualizer/FractalVisualizer/Complex.cs b/FractalVisualizer/FractalVisualizer/Complex.cs new file mode 100644 index 0000000..780db5f --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/Complex.cs @@ -0,0 +1,18 @@ +namespace FractalVisualizer; + +public readonly struct Complex +{ + public double MagSq => r * r + i * i; + + public readonly double r, i; + + public Complex(double r, double i) + { + this.r = r; + this.i = i; + } + + public static Complex operator +(Complex a, Complex b) => new(a.r + b.r, a.i + b.i); + public static Complex operator -(Complex a, Complex b) => new(a.r - b.r, a.i - b.i); + public static Complex operator *(Complex a, Complex b) => new(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r); +} diff --git a/FractalVisualizer/FractalVisualizer/Forms/MainForm.Designer.cs b/FractalVisualizer/FractalVisualizer/Forms/MainForm.Designer.cs new file mode 100644 index 0000000..3fbb371 --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/Forms/MainForm.Designer.cs @@ -0,0 +1,76 @@ +namespace FractalVisualizer.Forms +{ + partial class MainForm + { + /// + /// 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 Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + Viewer = new PictureBox(); + PositionLabel = new Label(); + ((System.ComponentModel.ISupportInitialize)Viewer).BeginInit(); + SuspendLayout(); + // + // Viewer + // + Viewer.Dock = DockStyle.Fill; + Viewer.Location = new Point(0, 0); + Viewer.Name = "Viewer"; + Viewer.Size = new Size(1366, 822); + Viewer.TabIndex = 0; + Viewer.TabStop = false; + // + // PositionLabel + // + PositionLabel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + PositionLabel.AutoSize = true; + PositionLabel.Font = new Font("Segoe UI", 16.125F, FontStyle.Bold, GraphicsUnit.Point, 0); + PositionLabel.Location = new Point(12, 754); + PositionLabel.Name = "PositionLabel"; + PositionLabel.Size = new Size(295, 59); + PositionLabel.TabIndex = 1; + PositionLabel.Text = "PositionLabel"; + PositionLabel.TextAlign = ContentAlignment.BottomLeft; + // + // MainForm + // + AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1366, 822); + Controls.Add(PositionLabel); + Controls.Add(Viewer); + Name = "MainForm"; + Text = "MainForm"; + ((System.ComponentModel.ISupportInitialize)Viewer).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private PictureBox Viewer; + private Label PositionLabel; + } +} \ No newline at end of file diff --git a/FractalVisualizer/FractalVisualizer/Forms/MainForm.cs b/FractalVisualizer/FractalVisualizer/Forms/MainForm.cs new file mode 100644 index 0000000..c97590d --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/Forms/MainForm.cs @@ -0,0 +1,205 @@ +using Nerd_STF.Mathematics; + +namespace FractalVisualizer.Forms +{ + public partial class MainForm : Form + { + public Float2 ScreenCenter { get; set; } + public Float2 Dpi { get; private set; } + public Float2 ZoomLevel { get; set; } + + public Action FuncSetup { get; set; } + public Func Func { get; set; } + public int MaxIterations { get; set; } + public double CutoffMagnitude { get; set; } + public bool OptimizeIterations { get; set; } + + public MainForm(Action setup, Func func) + { + InitializeComponent(); + + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + + Graphics tempG = CreateGraphics(); + Dpi = (tempG.DpiX, tempG.DpiY); + tempG.Dispose(); + + ZoomLevel = Float2.One; + FuncSetup = setup; + Func = func; + + Viewer.MouseDown += (o, e) => OnMouseDown(e); + Viewer.MouseMove += (o, e) => OnMouseMove(e); + Viewer.MouseUp += (o, e) => OnMouseUp(e); + + UpdatePositionText(); + BeginRender(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + BeginRender(); + } + + public void BeginRender() + { + if (renderTask is not null) + { + renderCancel!.Cancel(); + renderTask.Wait(); + + renderCancel.Dispose(); + renderTask.Dispose(); + } + renderCancel = new CancellationTokenSource(); + renderTask = Task.Run(async () => await Render(8, true, renderCancel.Token)); + } + + private static Color ColorFromIters(int i, int max) + { + if (i == 0 || i == max) return Color.Black; + + int brightestR = 33, + brightestG = 204, + brightestB = 38; + + double intensity = 1 - 1.0 / i; + int r = (int)(brightestR * intensity), + g = (int)(brightestG * intensity), + b = (int)(brightestB * intensity); + return Color.FromArgb(g, r, b); + } + + private CancellationTokenSource? renderCancel; + private Task? renderTask; + private Task Render(int depth, bool recurse, CancellationToken token) + { + DateTime start = DateTime.Now; + Graphics g = Viewer.CreateGraphics(); + SolidBrush pen = new(Color.Black); + double cutoff = CutoffMagnitude * CutoffMagnitude; + int maxIters = MaxIterations; + if (OptimizeIterations) maxIters /= depth + 1; + + int step = 1 << depth; + for (int x = 0; x < Viewer.Width; x += step) + { + for (int y = 0; y < Viewer.Height; y += step) + { + if (token.IsCancellationRequested) return Task.CompletedTask; + + Int2 point = (x, y); + Float2 coords = ScreenSpaceToGraphSpace(point); + Complex num = new(coords.x, coords.y); + FuncSetup(num); + int i; + for (i = 0; i < maxIters; i++) + { + if (token.IsCancellationRequested) return Task.CompletedTask; + num = Func(num); + if (num.MagSq >= CutoffMagnitude) break; + } + + if (token.IsCancellationRequested) return Task.CompletedTask; + pen.Color = ColorFromIters(i, maxIters); + g.FillRectangle(pen, new Rectangle(x, y, step, step)); + } + } + + g.Dispose(); + pen.Dispose(); + if (recurse && depth > 0) return Render(depth - 1, true, token); + else return Task.CompletedTask; + } + + public Int2 GraphSpaceToScreenSpace(Float2 graphPoint) + { + graphPoint.y = -graphPoint.y; + + graphPoint.x -= ScreenCenter.x; + graphPoint.y -= ScreenCenter.y; + + graphPoint.x *= Dpi.x / ZoomLevel.x; + graphPoint.y *= Dpi.y / ZoomLevel.y; + + graphPoint.x += Viewer.Width / 2.0; + graphPoint.y += Viewer.Height / 2.0; + + return new((int)graphPoint.x, (int)graphPoint.y); + } + public Float2 ScreenSpaceToGraphSpace(Int2 screenPoint) + { + Float2 result = new(screenPoint.x, screenPoint.y); + + result.x -= Viewer.Width / 2.0; + result.y -= Viewer.Height / 2.0; + + result.x /= Dpi.x / ZoomLevel.x; + result.y /= Dpi.y / ZoomLevel.y; + + result.x += ScreenCenter.x; + result.y += ScreenCenter.y; + + result.y = -result.y; + + return result; + } + + private void UpdatePositionText() + { + PositionLabel.Text = $"{ScreenCenter.ToString("0.000")} - {100.0 * ZoomLevel.x:0.000000}%"; + PositionLabel.Invalidate(); + } + + private bool dragging; + private Int2 initialDragPoint; + private Float2 initialCenter; + protected override void OnMouseDown(MouseEventArgs e) + { + if (!dragging) + { + initialCenter = ScreenCenter; + initialDragPoint = new Int2(Cursor.Position.X, Cursor.Position.Y); + dragging = true; + } + } + protected override void OnMouseUp(MouseEventArgs e) + { + if (dragging) + { + Int2 pixelDiff = new(initialDragPoint.x - Cursor.Position.X, + initialDragPoint.y - Cursor.Position.Y); + Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y); + ScreenCenter = new(initialCenter.x + graphDiff.x, + initialCenter.y + graphDiff.y); + BeginRender(); + dragging = false; + } + } + protected override void OnMouseMove(MouseEventArgs e) + { + if (dragging) + { + Int2 pixelDiff = new(initialDragPoint.x - Cursor.Position.X, + initialDragPoint.y - Cursor.Position.Y); + Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y); + ScreenCenter = new(initialCenter.x + graphDiff.x, + initialCenter.y + graphDiff.y); + UpdatePositionText(); + BeginRender(); + } + } + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + Float2 newZoom = ZoomLevel; + newZoom.x *= 1 - e.Delta * 0.00075; // Zoom factor. + newZoom.y *= 1 - e.Delta * 0.00075; + ZoomLevel = newZoom; + UpdatePositionText(); + BeginRender(); + } + } +} diff --git a/FractalVisualizer/FractalVisualizer/Forms/MainForm.resx b/FractalVisualizer/FractalVisualizer/Forms/MainForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/Forms/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj b/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj new file mode 100644 index 0000000..12b487d --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0-windows + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj.user b/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj.user new file mode 100644 index 0000000..f291941 --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/FractalVisualizer.csproj.user @@ -0,0 +1,8 @@ + + + + + Form + + + \ No newline at end of file diff --git a/FractalVisualizer/FractalVisualizer/Program.cs b/FractalVisualizer/FractalVisualizer/Program.cs new file mode 100644 index 0000000..f123069 --- /dev/null +++ b/FractalVisualizer/FractalVisualizer/Program.cs @@ -0,0 +1,31 @@ +/**********722871********** + * Date: 11/13/2024 + * Programmer: Kyle Gilbert + * Program Name: Fractal Visualizer + * Program Description: A visualizer for complex-based iterative fractals. + **************************/ + +using FractalVisualizer.Forms; + +namespace FractalVisualizer; + +public static class Program +{ + [STAThread] + public static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.SystemAware); + + Application.Run(new MainForm(MandlebrotSetup, MandlebrotIter) + { + MaxIterations = 512, + CutoffMagnitude = 4, + }); + } + + private static Complex start; + private static void MandlebrotSetup(Complex num) => start = num; + private static Complex MandlebrotIter(Complex num) => num * num + start; +} \ No newline at end of file diff --git a/README.md b/README.md index 2adc427..bc8112f 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,8 @@ I have about 1-2 weeks for each project. Check the Git commits for specific date - Create a `.sce` file (a somewhat easy to use plain text format) - Nice colors for each object. Scales seamlessly with a higher DPI. - Sweeps possible angles and speeds to try and find the path that brings the ball closest to the end point. +- Fractal Visualizer + - A program that can be used to visualize fractals. + - Allows you to zoom in and drag the screen around in real time. + - Renders in multiple resolution scales so as to be as responsive as possible. Upscales over time. + - Currently does the mandlebrot set. It has support for any complex iterative fractal, but you have to code it yourself.