From 65826edc9fffabdd816bc7e59141e25d2c38aee5 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Tue, 1 Aug 2017 22:42:47 -0400 Subject: [PATCH] Initial commit --- Harmonograph.sln | 22 +++ Harmonograph/GraphWindow.cs | 174 ++++++++++++++++++++++++ Harmonograph/Harmonograph.csproj | 93 +++++++++++++ Harmonograph/HarmonographSolver.cs | 32 +++++ Harmonograph/OpenTK.dll.config | 25 ++++ Harmonograph/PendulumSolver.cs | 31 +++++ Harmonograph/Program.cs | 23 ++++ Harmonograph/Properties/AssemblyInfo.cs | 36 +++++ Harmonograph/blackonwhite.json | 28 ++++ Harmonograph/bow2.json | 29 ++++ Harmonograph/packages.config | 5 + Harmonograph/rainbowroad.json | 29 ++++ Harmonograph/sparse.json | 28 ++++ Harmonograph/spring.json | 29 ++++ Harmonograph/test.json | 24 ++++ Harmonograph/whiteonblack.json | 28 ++++ 16 files changed, 636 insertions(+) create mode 100644 Harmonograph.sln create mode 100644 Harmonograph/GraphWindow.cs create mode 100644 Harmonograph/Harmonograph.csproj create mode 100644 Harmonograph/HarmonographSolver.cs create mode 100644 Harmonograph/OpenTK.dll.config create mode 100644 Harmonograph/PendulumSolver.cs create mode 100644 Harmonograph/Program.cs create mode 100644 Harmonograph/Properties/AssemblyInfo.cs create mode 100644 Harmonograph/blackonwhite.json create mode 100644 Harmonograph/bow2.json create mode 100644 Harmonograph/packages.config create mode 100644 Harmonograph/rainbowroad.json create mode 100644 Harmonograph/sparse.json create mode 100644 Harmonograph/spring.json create mode 100644 Harmonograph/test.json create mode 100644 Harmonograph/whiteonblack.json diff --git a/Harmonograph.sln b/Harmonograph.sln new file mode 100644 index 0000000..098bebc --- /dev/null +++ b/Harmonograph.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.0.0 +MinimumVisualStudioVersion = 10.0.0.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonograph", "Harmonograph\Harmonograph.csproj", "{3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Harmonograph/GraphWindow.cs b/Harmonograph/GraphWindow.cs new file mode 100644 index 0000000..95b7b06 --- /dev/null +++ b/Harmonograph/GraphWindow.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Xml; +using Newtonsoft.Json.Linq; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using OpenTK.Input; + +namespace Harmonograph +{ + public class GraphWindow : GameWindow + { + private const double Tau = Math.PI * 2; + private const double Pi = Math.PI; + + public HarmonographSolver Solver { get; } = new HarmonographSolver() + { + new PendulumSolver(Vector2d.UnitX * 1.5, 4.01, 0, 0.005), + new PendulumSolver(Vector2d.UnitY, 5.01, 0, 0.005), + new PendulumSolver(Vector2d.UnitX, 1.01, 0, 0.005), + new PendulumSolver(Vector2d.UnitY / 2, -1.01, 1, 0.005), + }; + + public double ViewHeight { get; set; } + public double DisplayRate { get; set; } + public int Cycles { get; set; } + + public Color Back { get; } + public Color Front { get; } + + private Vector2[] _path = new Vector2[0]; + private double _time = 0; + + private double _phaseRate = Math.PI / 4; + private bool _phaseMode = false; + + public GraphWindow(string settingsFile) + : base(2560, 1440, new GraphicsMode(32, 0, 0, 4)) + { + if (settingsFile != null && File.Exists(settingsFile)) + { + var jo = JObject.Parse(File.ReadAllText(settingsFile)); + Back = jo["back"]?.ToObject() ?? Color.Black; + Front = jo["front"]?.ToObject() ?? Color.FromArgb(0x33, 0xff, 0xff, 0xff); + + ViewHeight = jo["viewHeight"]?.ToObject() ?? 4; + DisplayRate = jo["displayRate"]?.ToObject() ?? 1; + + Cycles = jo["cycles"]?.ToObject() ?? 100; + + if (jo["pendulums"] != null) + { + Solver.Pendulums.Clear(); + foreach (var t in jo["pendulums"]) + { + var amp = t["amplitude"]?.ToObject() ?? Vector2d.UnitX; + var period = t["period"]?.ToObject() ?? 1; + var phase = t["phase"]?.ToObject() ?? 0; + var friction = t["friction"]?.ToObject() ?? 0.005; + + Solver.Add(new PendulumSolver(amp, period, phase, friction)); + } + } + } + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Width = 2560; + Height = 1440; + X = (DisplayDevice.Default.Width - Width) / 2; + Y = (DisplayDevice.Default.Height - Height) / 2; + + RecalculatePath(); + } + + public void RecalculatePath() + { + var m = (int) (Cycles * 200 * Tau); + var path = new Vector2[m]; + + Parallel.For(0, m, i => path[i] = (Vector2) Solver.At(i / 20.0)); + + _path = path; + } + + protected override void OnRenderFrame(FrameEventArgs e) + { + base.OnRenderFrame(e); + + var proj = Matrix4d.CreateOrthographic(ViewHeight * Width / Height, ViewHeight, -1, 1); + + GL.MatrixMode(MatrixMode.Projection); + GL.LoadMatrix(ref proj); + + GL.Viewport(ClientRectangle); + GL.ClearColor(Back); + GL.Clear(ClearBufferMask.ColorBufferBit); + + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + GL.LineWidth(1.5f); + GL.Begin(PrimitiveType.LineStrip); + GL.Color4(Front); + for (var i = 0; i < _path.Length && i < _time; i++) + { + var p = _path[i]; + GL.Vertex2(p); + } + GL.End(); + + SwapBuffers(); + } + + public void SaveScreenshot() => SaveScreenshot(DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".png"); + + public void SaveScreenshot(string file) + { + if (GraphicsContext.CurrentContext == null) + throw new GraphicsContextMissingException(); + var w = ClientSize.Width; + var h = ClientSize.Height; + var bmp = new Bitmap(w, h); + var data = bmp.LockBits(ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, + System.Drawing.Imaging.PixelFormat.Format24bppRgb); + GL.ReadPixels(0, 0, w, h, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); + bmp.UnlockBits(data); + + bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); + + bmp.Save(file); + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + + if (e.Key == Key.Space) + SaveScreenshot(); + + if (e.Key == Key.A) + _phaseMode = !_phaseMode; + + if (e.Key == Key.R) + _time = 0; + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + base.OnUpdateFrame(e); + + Title = $"{1 / e.Time:00.00}"; + + if (DisplayRate <= 0) + _time = _path.Length; + + if (Focused) + _time += e.Time * DisplayRate * 100; + + if (_phaseMode) + { + foreach (var p in Solver.Pendulums) + p.Phase += e.Time * _phaseRate; + RecalculatePath(); + } + } + } +} \ No newline at end of file diff --git a/Harmonograph/Harmonograph.csproj b/Harmonograph/Harmonograph.csproj new file mode 100644 index 0000000..d50d4eb --- /dev/null +++ b/Harmonograph/Harmonograph.csproj @@ -0,0 +1,93 @@ + + + + + Debug + AnyCPU + {3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Properties + Harmonograph + Harmonograph + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\packages\OpenTK.3.0.0-pre\lib\net20\OpenTK.dll + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + \ No newline at end of file diff --git a/Harmonograph/HarmonographSolver.cs b/Harmonograph/HarmonographSolver.cs new file mode 100644 index 0000000..082c19d --- /dev/null +++ b/Harmonograph/HarmonographSolver.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using OpenTK; + +namespace Harmonograph +{ + public class HarmonographSolver : IEnumerable + { + public const double Tau = Math.PI / 2; + + public List Pendulums { get; } = new List(); + + public void Add(PendulumSolver pendulum) => Pendulums.Add(pendulum); + + public Vector2d At(double time) + { + return Pendulums.Select(p => p.At(time)).Aggregate(Vector2d.Zero, Vector2d.Add); + } + + public IEnumerator GetEnumerator() + { + return Pendulums.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) Pendulums).GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Harmonograph/OpenTK.dll.config b/Harmonograph/OpenTK.dll.config new file mode 100644 index 0000000..7098d39 --- /dev/null +++ b/Harmonograph/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Harmonograph/PendulumSolver.cs b/Harmonograph/PendulumSolver.cs new file mode 100644 index 0000000..4d8d8aa --- /dev/null +++ b/Harmonograph/PendulumSolver.cs @@ -0,0 +1,31 @@ +using System; +using OpenTK; + +namespace Harmonograph +{ + public class PendulumSolver + { + public const double Tau = Math.PI * 2; + + public Vector2d Amplitude { get; set; } + public double Period { get; set; } + public double Phase { get; set; } + public double Friction { get; set; } + + public PendulumSolver(Vector2d amplitude, double period, double phase, + double friction = 0.01) + { + Amplitude = amplitude; + Period = period; + Phase = phase; + Friction = friction; + } + + public Vector2d At(double time) + { + var phase = (Phase + Period * time) / Tau; + + return Amplitude * Math.Sin(phase) * Math.Exp(-time * Friction / Tau); + } + } +} \ No newline at end of file diff --git a/Harmonograph/Program.cs b/Harmonograph/Program.cs new file mode 100644 index 0000000..9573f88 --- /dev/null +++ b/Harmonograph/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading; + +namespace Harmonograph +{ + internal class Program + { + public static void Main(string[] args) + { + string f = null; + if (args.Length > 0) f = args[0]; + if (f == "-") + { + Console.Write("input file.\n>>> "); + f = Console.ReadLine() + ".json"; + } + using (var w = new GraphWindow(f)) + { + w.Run(); + } + } + } +} \ No newline at end of file diff --git a/Harmonograph/Properties/AssemblyInfo.cs b/Harmonograph/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3a7f7d5 --- /dev/null +++ b/Harmonograph/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Harmonograph")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Harmonograph")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3DEBA9F3-AD0D-43A6-BF4E-0220BDAFB099")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Harmonograph/blackonwhite.json b/Harmonograph/blackonwhite.json new file mode 100644 index 0000000..10573b7 --- /dev/null +++ b/Harmonograph/blackonwhite.json @@ -0,0 +1,28 @@ +{ + "back": "0x00000000", + "front": "0x10ffffff", + "displayRate": 1000, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 1.5, "y": 0 }, + "period": 3.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": 0, "y": 1 }, + "period": 4.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 2.999, + "friction": 0.0005 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/bow2.json b/Harmonograph/bow2.json new file mode 100644 index 0000000..8683cbe --- /dev/null +++ b/Harmonograph/bow2.json @@ -0,0 +1,29 @@ +{ + "back": "0x10ffffff", + "front": "0x10000000", + "displayRate": 1000, + "cycles": 500, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 2, "y": 0 }, + "period": 3.001, + "friction": 0.001 + }, + { + "amplitude": { "x": 0, "y": 1 }, + "period": 4, + "friction": 0.001 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1, + "friction": 0.001 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 3, + "friction": 0.001 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/packages.config b/Harmonograph/packages.config new file mode 100644 index 0000000..d8c7d4f --- /dev/null +++ b/Harmonograph/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Harmonograph/rainbowroad.json b/Harmonograph/rainbowroad.json new file mode 100644 index 0000000..894f346 --- /dev/null +++ b/Harmonograph/rainbowroad.json @@ -0,0 +1,29 @@ +{ + "back": "0x10ffffff", + "front": "0x70000000", + "displayRate": 10, + "cycles": 10, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 2, "y": -.5 }, + "period": 2.01, + "friction": 0.01 + }, + { + "amplitude": { "x": 0, "y": .5 }, + "period": 5.01, + "friction": 0.01 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1, + "friction": 0.01 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 3, + "friction": 0.01 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/sparse.json b/Harmonograph/sparse.json new file mode 100644 index 0000000..d37bb94 --- /dev/null +++ b/Harmonograph/sparse.json @@ -0,0 +1,28 @@ +{ + "back": "0xffffffff", + "front": "0xff000000", + "displayRate": 500, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 1.5, "y": 0 }, + "period": 3.01, + "friction": 0.02 + }, + { + "amplitude": { "x": 0, "y": 1 }, + "period": 4.01, + "friction": 0.02 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1.01, + "friction": 0.02 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 2.98, + "friction": 0.01 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/spring.json b/Harmonograph/spring.json new file mode 100644 index 0000000..4726d76 --- /dev/null +++ b/Harmonograph/spring.json @@ -0,0 +1,29 @@ +{ + "back": "0x10ffffff", + "front": "0x70000000", + "displayRate": 1, + "cycles": 10, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 2, "y": -.5 }, + "period": 5.01, + "friction": 0.01 + }, + { + "amplitude": { "x": 0, "y": .5 }, + "period": 4.01, + "friction": 0.01 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1, + "friction": 0.01 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 3, + "friction": 0.01 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/test.json b/Harmonograph/test.json new file mode 100644 index 0000000..24c2f10 --- /dev/null +++ b/Harmonograph/test.json @@ -0,0 +1,24 @@ +{ + "back": "0x10ffffff", + "front": "0x20000000", + "displayRate": 01, + "cycles": 10, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 1, "y": -.5 }, + "period": 5.005, + "friction": 0.001 + }, + { + "amplitude": { "x": .5, "y": 1 }, + "period": 4.01, + "friction": 0.001 + }, + { + "amplitude": { "x": .5, "y": 0 }, + "period": 3.01, + "friction": 0.001 + } + ] +} \ No newline at end of file diff --git a/Harmonograph/whiteonblack.json b/Harmonograph/whiteonblack.json new file mode 100644 index 0000000..fa931f2 --- /dev/null +++ b/Harmonograph/whiteonblack.json @@ -0,0 +1,28 @@ +{ + "back": "0xffffffff", + "front": "0x10000000", + "displayRate": 1000, + "viewHeight": 3, + "pendulums": [ + { + "amplitude": { "x": 1.5, "y": 0 }, + "period": 3.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": 0, "y": 1 }, + "period": 4.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": .5, "y": .5 }, + "period": 1.001, + "friction": 0.0005 + }, + { + "amplitude": { "x": -.5, "y": .5 }, + "period": 2.999, + "friction": 0.0005 + } + ] +} \ No newline at end of file