diff --git a/BasicProjectionRenderer/BasicProjectionRenderer/Form1.cs b/BasicProjectionRenderer/BasicProjectionRenderer/Form1.cs
index f4aeb52..e371adf 100644
--- a/BasicProjectionRenderer/BasicProjectionRenderer/Form1.cs
+++ b/BasicProjectionRenderer/BasicProjectionRenderer/Form1.cs
@@ -114,6 +114,7 @@ public partial class Form1 : Form
private void OnTick(object? sender, EventArgs e)
{
elapsedTime += RefreshTimer.Interval * 1e-3;
+ //temp += MathE.Sin(elapsedTime * 8) * 0.9;
if (Mesh is not null)
{
DateTime start = DateTime.Now;
diff --git a/BasicProjectionRenderer/BasicProjectionRenderer/Program.cs b/BasicProjectionRenderer/BasicProjectionRenderer/Program.cs
index bd10bb8..c00bc4e 100644
--- a/BasicProjectionRenderer/BasicProjectionRenderer/Program.cs
+++ b/BasicProjectionRenderer/BasicProjectionRenderer/Program.cs
@@ -19,8 +19,8 @@ public static class Program
{
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
-
- string path = "cube.obj";
+
+ string path = "monkey.obj";
FileStream fs = new(path, FileMode.Open);
Mesh obj = Mesh.FromObj(fs);
fs.Close();
diff --git a/InverseKinematics/InverseKinematics.sln b/InverseKinematics/InverseKinematics.sln
new file mode 100644
index 0000000..4678a4b
--- /dev/null
+++ b/InverseKinematics/InverseKinematics.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35506.116 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InverseKinematics", "InverseKinematics\InverseKinematics.csproj", "{74875265-97D7-42EA-A2F2-23D2A890FD85}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {74875265-97D7-42EA-A2F2-23D2A890FD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {74875265-97D7-42EA-A2F2-23D2A890FD85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {74875265-97D7-42EA-A2F2-23D2A890FD85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {74875265-97D7-42EA-A2F2-23D2A890FD85}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/InverseKinematics/InverseKinematics/Form1.Designer.cs b/InverseKinematics/InverseKinematics/Form1.Designer.cs
new file mode 100644
index 0000000..0547934
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/Form1.Designer.cs
@@ -0,0 +1,45 @@
+namespace InverseKinematics
+{
+ partial class Form1
+ {
+ ///
+ /// 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()
+ {
+ SuspendLayout();
+ //
+ // Form1
+ //
+ AutoScaleDimensions = new SizeF(13F, 32F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(1134, 720);
+ Name = "Form1";
+ Text = "Form1";
+ ResumeLayout(false);
+ }
+
+ #endregion
+ }
+}
diff --git a/InverseKinematics/InverseKinematics/Form1.cs b/InverseKinematics/InverseKinematics/Form1.cs
new file mode 100644
index 0000000..36a6bd4
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/Form1.cs
@@ -0,0 +1,155 @@
+using Nerd_STF.Mathematics;
+using System.Drawing.Drawing2D;
+
+namespace InverseKinematics;
+
+public partial class Form1 : Form
+{
+ public Float2 DesiredPoint { get; set; }
+
+ public Float2[] Points { get; set; }
+
+ public Float2 ZoomLevel { get; set; } = (3, 3);
+ public Float2 ScreenCenter { get; set; } = (0, 0);
+
+ public Form1()
+ {
+ InitializeComponent();
+ Points = new Float2[5];
+ for (int i = 0; i < Points.Length; i++)
+ {
+ Points[i] = (0, i);
+ }
+
+ SetStyle(ControlStyles.UserPaint, true);
+ SetStyle(ControlStyles.AllPaintingInWmPaint, true);
+ SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
+ }
+
+ public Int2 ToScreenSpace(Float2 worldPoint)
+ {
+ float dpi = DeviceDpi;
+ worldPoint.y = -worldPoint.y;
+
+ worldPoint.x -= ScreenCenter.x;
+ worldPoint.y -= ScreenCenter.y;
+
+ worldPoint.x *= dpi / ZoomLevel.x;
+ worldPoint.y *= dpi / ZoomLevel.y;
+
+ worldPoint.x += ClientRectangle.Width * 0.5;
+ worldPoint.y += ClientRectangle.Height * 0.5;
+
+ return new((int)worldPoint.x, (int)worldPoint.y);
+ }
+ public Float2 FromScreenSpace(Int2 screenPoint)
+ {
+ Float2 result = (screenPoint.x, screenPoint.y);
+
+ result.x -= ClientRectangle.Width / 2.0;
+ result.y -= ClientRectangle.Height / 2.0;
+
+ float dpi = DeviceDpi;
+ result.x /= dpi / ZoomLevel.x;
+ result.y /= dpi / ZoomLevel.y;
+
+ result.x += ScreenCenter.x;
+ result.y += ScreenCenter.y;
+
+ result.y = -result.y;
+
+ return result;
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ DesiredPoint = FromScreenSpace(e.Location);
+ Invalidate();
+ }
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ RunFabrik();
+
+ Graphics g = e.Graphics;
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ float scaleFactor = g.DpiX / 96.0f;
+
+ SolidBrush brush = new(Color.Black);
+ Pen pen = new(Color.Black, scaleFactor);
+
+ Float2 radius = (2, 2);
+ Int2 prevPointPix = (0, 0);
+ for (int i = 0; i < Points.Length; i++)
+ {
+ Int2 pointPix = ToScreenSpace(Points[i]);
+ g.FillEllipse(brush, new RectangleF(pointPix - radius * scaleFactor, radius * scaleFactor * 2));
+
+ if (i > 0)
+ {
+ g.DrawLine(pen, prevPointPix, pointPix);
+ }
+ prevPointPix = pointPix;
+ }
+
+ Int2 desiredPix = ToScreenSpace(DesiredPoint);
+ radius = (5, 5);
+
+ brush.Color = Color.Red;
+ g.FillEllipse(brush, new RectangleF(desiredPix - radius * scaleFactor, radius * scaleFactor * 2));
+ }
+
+ private void RunFabrik()
+ {
+ // Initial setup.
+ for (int i = 0; i < Points.Length; i++)
+ {
+ // Reset points to 90 degrees away from goal.
+ // This produces a "nice" rotation no matter where
+ // you are.
+ Float2 dir = (-DesiredPoint.y, DesiredPoint.x);
+ dir.Normalize();
+
+ Points[i] = dir * i;
+ }
+
+ RunFabrik(true, 16);
+ }
+
+ // True = forwards, false = backwards.
+ private void RunFabrik(bool direction, int maxIters)
+ {
+ if (maxIters <= 0) return;
+
+ const double maxLength = 1;
+ if (direction)
+ {
+ Points[^1] = DesiredPoint;
+ for (int i = Points.Length - 2; i >= 0; i--)
+ {
+ Float2 thisPoint = Points[i],
+ connected = Points[i + 1],
+ diff = connected - thisPoint;
+
+ double step = maxLength - diff.Magnitude;
+ Float2 dir = diff.Normalized;
+ Points[i] = thisPoint - dir * step;
+ }
+ }
+ else
+ {
+ Points[0] = (0, 0);
+ for (int i = 1; i < Points.Length; i++)
+ {
+ Float2 thisPoint = Points[i],
+ connected = Points[i - 1],
+ diff = connected - thisPoint;
+
+ double step = maxLength - diff.Magnitude;
+ Float2 dir = diff.Normalized;
+ Points[i] = thisPoint - dir * step;
+ }
+ }
+
+ RunFabrik(!direction, maxIters - 1);
+ }
+}
diff --git a/InverseKinematics/InverseKinematics/Form1.resx b/InverseKinematics/InverseKinematics/Form1.resx
new file mode 100644
index 0000000..8b2ff64
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/Form1.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/InverseKinematics/InverseKinematics/InverseKinematics.csproj b/InverseKinematics/InverseKinematics/InverseKinematics.csproj
new file mode 100644
index 0000000..caba88c
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/InverseKinematics.csproj
@@ -0,0 +1,15 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ true
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/InverseKinematics/InverseKinematics/InverseKinematics.csproj.user b/InverseKinematics/InverseKinematics/InverseKinematics.csproj.user
new file mode 100644
index 0000000..7814ea2
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/InverseKinematics.csproj.user
@@ -0,0 +1,8 @@
+
+
+
+
+ Form
+
+
+
diff --git a/InverseKinematics/InverseKinematics/Program.cs b/InverseKinematics/InverseKinematics/Program.cs
new file mode 100644
index 0000000..10d8a50
--- /dev/null
+++ b/InverseKinematics/InverseKinematics/Program.cs
@@ -0,0 +1,20 @@
+/**********722871**********
+ * Date: 2/25/2025
+ * Programmer: Kyle Gilbert
+ * Program Name: InverseKinematics
+ * Program Description: Calculates a series of joints with inverse kinematics.
+ **************************/
+
+namespace InverseKinematics;
+
+internal static class Program
+{
+ [STAThread]
+ public static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.SetHighDpiMode(HighDpiMode.SystemAware);
+ Application.Run(new Form1());
+ }
+}
diff --git a/README.md b/README.md
index 5a71783..b1a514f 100644
--- a/README.md
+++ b/README.md
@@ -50,3 +50,8 @@ I have about 1-2 weeks for each project. Check the Git commits for specific date
- Not super optimized, but I've made a few tweaks to speed it up.
- All calculations are my own, from sin and cosine to matrix multiplication.
- Math code comes from a library I wrote, Nerd_STF.
+- InverseKinematics/
+ - A program that calculates inverse kinematics for a series of joints.
+ - Took me less than an hour to write. Faster than I expected.
+ - Handles hundreds of nodes with very little optimization.
+ - Might improve in the future to work around obstacles.