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.