Added a new project. Finished a while back.

This commit is contained in:
That-One-Nerd 2024-12-12 11:25:55 -05:00
parent 4147a29fdc
commit 93f4ba647f
28 changed files with 2283 additions and 1 deletions

View File

@ -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}") = "AirTrajectoryBuilder", "AirTrajectoryBuilder\AirTrajectoryBuilder.csproj", "{0EA33509-A4B2-4FB3-84E5-C9773FEDF0A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0EA33509-A4B2-4FB3-84E5-C9773FEDF0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EA33509-A4B2-4FB3-84E5-C9773FEDF0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EA33509-A4B2-4FB3-84E5-C9773FEDF0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EA33509-A4B2-4FB3-84E5-C9773FEDF0A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Binary file not shown.

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerd_STF" Version="3.0.0-beta1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\SweepCancelForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\SweepInfoViewer.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\SweepParametersForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,127 @@
using System.Drawing;
using System.Windows.Forms;
namespace AirTrajectoryBuilder
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
Menu = new MenuStrip();
MenuFile = new ToolStripMenuItem();
MenuFileNew = new ToolStripMenuItem();
MenuFileOpen = new ToolStripMenuItem();
MenuRun = new ToolStripMenuItem();
MenuRunSweep = new ToolStripMenuItem();
FileOpener = new OpenFileDialog();
MenuRunCancel = new ToolStripMenuItem();
Menu.SuspendLayout();
SuspendLayout();
//
// Menu
//
Menu.ImageScalingSize = new Size(32, 32);
Menu.Items.AddRange(new ToolStripItem[] { MenuFile, MenuRun });
Menu.Location = new Point(0, 0);
Menu.Name = "Menu";
Menu.Size = new Size(1263, 42);
Menu.TabIndex = 0;
Menu.Text = "menuStrip1";
//
// MenuFile
//
MenuFile.DropDownItems.AddRange(new ToolStripItem[] { MenuFileNew, MenuFileOpen });
MenuFile.Name = "MenuFile";
MenuFile.Size = new Size(71, 38);
MenuFile.Text = "File";
//
// MenuFileNew
//
MenuFileNew.Name = "MenuFileNew";
MenuFileNew.Size = new Size(221, 44);
MenuFileNew.Text = "New";
MenuFileNew.Click += MenuFileNew_Click;
//
// MenuFileOpen
//
MenuFileOpen.Name = "MenuFileOpen";
MenuFileOpen.Size = new Size(221, 44);
MenuFileOpen.Text = "Open...";
MenuFileOpen.Click += MenuFileOpen_Click;
//
// MenuRun
//
MenuRun.DropDownItems.AddRange(new ToolStripItem[] { MenuRunSweep, MenuRunCancel });
MenuRun.Name = "MenuRun";
MenuRun.Size = new Size(76, 38);
MenuRun.Text = "Run";
//
// MenuRunSweep
//
MenuRunSweep.Name = "MenuRunSweep";
MenuRunSweep.Size = new Size(359, 44);
MenuRunSweep.Text = "Sweep...";
MenuRunSweep.Click += MenuRunSweep_Click;
//
// FileOpener
//
FileOpener.Filter = "Scene files|*.sce|All files|*.*";
//
// MenuRunCancel
//
MenuRunCancel.Name = "MenuRunCancel";
MenuRunCancel.Size = new Size(359, 44);
MenuRunCancel.Text = "Cancel";
MenuRunCancel.Click += MenuRunCancel_Click;
//
// MainForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1263, 719);
Controls.Add(Menu);
MainMenuStrip = Menu;
Name = "MainForm";
Text = "MainForm";
Menu.ResumeLayout(false);
Menu.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
#endregion
private MenuStrip Menu;
private ToolStripMenuItem MenuFile;
private ToolStripMenuItem MenuFileOpen;
private ToolStripMenuItem MenuFileNew;
private OpenFileDialog FileOpener;
private ToolStripMenuItem MenuRun;
private ToolStripMenuItem MenuRunSweep;
private ToolStripMenuItem MenuRunCancel;
}
}

View File

@ -0,0 +1,447 @@
using AirTrajectoryBuilder.ObjectModels;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System;
using Nerd_STF.Mathematics;
using System.IO;
using System.Reflection;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
using System.Threading;
using AirTrajectoryBuilder.Forms;
namespace AirTrajectoryBuilder;
public partial class MainForm : Form
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Scene Scene { get; set; }
private readonly float scalingFactor;
private readonly string baseFolder, sceneFolder;
public SweepParameters? SweepParameters;
private CancellationTokenSource? simCancel;
private SimulationResult? simResult;
private SweepStatus simStatus;
private SweepCancelForm? simCancelForm;
internal SweepInfoViewer? simViewer;
private readonly Font statusFont;
public MainForm(Scene? initialScene)
{
InitializeComponent();
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Graphics tempG = CreateGraphics();
scalingFactor = tempG.DpiX / 96;
tempG.Dispose();
baseFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
sceneFolder = Path.Combine(baseFolder, "./scenes/");
Directory.CreateDirectory(sceneFolder);
FileOpener.InitialDirectory = sceneFolder;
Scene = initialScene ?? Scene.Default;
statusFont = new("Segoe UI", 10);
MenuRunCancel.Enabled = false;
}
public Int2 PlotToScreen(Float2 plot)
{
int menuHeight = Menu.Height;
int buffer = (int)(20 * scalingFactor);
double bufferX, bufferY, maxPix, pixPerUnit;
double clientAspect = (double)(ClientRectangle.Height - menuHeight - buffer * 2) / (ClientRectangle.Width - buffer * 2),
sceneAspect = Scene.Height / Scene.Width;
if (clientAspect > sceneAspect)
{
// Client is taller than scene, use width as max.
maxPix = ClientRectangle.Width - 2 * buffer;
pixPerUnit = maxPix / Scene.Width;
bufferX = buffer;
bufferY = (ClientRectangle.Height + menuHeight - Scene.Height * pixPerUnit) * 0.5f;
}
else
{
// Client is wider than scene, use height as max.
maxPix = ClientRectangle.Height - menuHeight - 2 * buffer;
pixPerUnit = maxPix / Scene.Height;
bufferX = (ClientRectangle.Width - Scene.Width * pixPerUnit) * 0.5f;
bufferY = buffer + menuHeight;
}
return ((int)(plot.x * pixPerUnit + bufferX),
(int)((Scene.Height - plot.y) * pixPerUnit + bufferY));
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
Pen pen = new(Color.Black, scalingFactor);
SolidBrush fill = new(Color.Black);
const int fillAlpha = 64;
// Draw components.
foreach (ISceneObject obj in Scene.Objects)
{
if (obj is SceneRect objRect)
{
pen.Color = Color.Green;
fill.Color = Color.FromArgb(fillAlpha, pen.Color);
Int2 rectFrom = PlotToScreen(objRect.From), rectTo = PlotToScreen(objRect.To);
int minX = int.Min(rectFrom.x, rectTo.x), sizeX = MathE.Absolute(rectFrom.x - rectTo.x),
minY = int.Min(rectFrom.y, rectTo.y), sizeY = MathE.Absolute(rectFrom.y - rectTo.y);
Rectangle rect = new(new Point(minX, minY), new Size(sizeX, sizeY));
g.FillRectangle(fill, rect);
g.DrawRectangle(pen, rect);
}
else if (obj is SceneTri objTri)
{
pen.Color = Color.Orange;
fill.Color = Color.FromArgb(fillAlpha, pen.Color);
Int2 triA = PlotToScreen(objTri.A),
triB = PlotToScreen(objTri.B),
triC = PlotToScreen(objTri.C);
g.FillPolygon(fill, [triA, triB, triC]);
g.DrawPolygon(pen, [triA, triB, triC]);
}
else if (obj is SceneEllipse objEllipse)
{
pen.Color = Color.Purple;
fill.Color = Color.FromArgb(fillAlpha, pen.Color);
Int2 min = PlotToScreen(objEllipse.Position - objEllipse.Size * 0.5),
max = PlotToScreen(objEllipse.Position + objEllipse.Size * 0.5);
Rectangle ellipseRect = new(min, max - min);
g.FillEllipse(fill, ellipseRect);
g.DrawEllipse(pen, ellipseRect);
}
}
// Draw scene border.
pen.Color = Color.Blue;
pen.Width = scalingFactor * 2;
Int2 sceneMin = PlotToScreen((0, Scene.Height)), sceneMax = PlotToScreen((Scene.Width, 0));
g.DrawRectangle(pen, new Rectangle(sceneMin, sceneMax - sceneMin));
// Draw starting position.
int startPosSize = (int)(6 * scalingFactor);
pen.Color = Color.Red;
pen.Width = scalingFactor;
fill.Color = Color.FromArgb(fillAlpha, pen.Color);
Int2 startPos = PlotToScreen(Scene.StartAt);
startPos.x -= startPosSize;
startPos.y -= startPosSize;
Rectangle startRect = new(startPos, Int2.One * startPosSize * 2);
g.FillEllipse(fill, startRect);
g.DrawEllipse(pen, startRect);
// Draw ending position.
pen.Color = Color.Lime;
pen.Width = scalingFactor;
fill.Color = Color.FromArgb(fillAlpha, pen.Color);
Int2 endPos = PlotToScreen(Scene.EndAt);
endPos.x -= startPosSize;
endPos.y -= startPosSize;
Rectangle endRect = new(endPos, Int2.One * startPosSize * 2);
g.FillEllipse(fill, endRect);
g.DrawEllipse(pen, endRect);
// If there's a trail, draw it.
pen.Color = Color.Red;
if (simResult is not null)
{
Point[] points = new Point[simResult.Trail.Count];
for (int i = 0; i < points.Length; i++)
{
points[i] = PlotToScreen(simResult.Trail[i]);
}
g.DrawLines(pen, points);
// Draw X at end point (if it's a crash and not a finish).
if (simResult.EndDistanceSquared >= SweepParameters!.Tolerance * SweepParameters.Tolerance)
{
Int2 end = points[^1];
PointF[] xShape = [
new Float2(-5, -5) * scalingFactor + end,
new Float2(5, 5) * scalingFactor + end,
new Float2(0, 0) * scalingFactor + end,
new Float2(-5, 5) * scalingFactor + end,
new Float2(5, -5) * scalingFactor + end,
];
g.DrawLines(pen, xShape);
}
}
string message = simStatus switch
{
SweepStatus.NoSweep => "No Sweep",
SweepStatus.Sweeping => $"Sweeping... Best {simResult?.StartingConditions.StartAngle:0.000} deg, {simResult?.StartingConditions.StartVelocity:0.0} m/s",
SweepStatus.FinishedSweep => $"Done Sweeping. Best {simResult?.StartingConditions.StartAngle:0.000} deg, {simResult?.StartingConditions.StartVelocity:0.0} m/s",
SweepStatus.CancelledSweep => $"Cancelled Sweep. Best {simResult?.StartingConditions.StartAngle:0.000} deg, {simResult?.StartingConditions.StartVelocity:0.0} m/s",
_ => "???",
};
fill.Color = Color.Blue;
SizeF size = g.MeasureString(message, statusFont);
const float spacing = 1;
g.DrawString(message, statusFont, fill, new PointF(spacing * scalingFactor, ClientRectangle.Height - size.Height - spacing * scalingFactor));
e.Dispose();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate(true);
}
private void ResetSceneData()
{
simCancel?.Cancel();
simResult = null;
simCancel = null;
SweepParameters = null;
simStatus = SweepStatus.NoSweep;
simViewer?.Close();
Invalidate(true);
}
private void MenuFileNew_Click(object? sender, EventArgs e)
{
if (!TryCancelSweep()) return;
if (!Scene.HasBeenSaved)
{
DialogResult result = MessageBox.Show(
"Are you sure you want to discard your changes?", "Lose changes?",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.No) return;
}
Scene = Scene.Default;
ResetSceneData();
}
private void MenuFileOpen_Click(object? sender, EventArgs e)
{
if (!TryCancelSweep()) return;
DialogResult result;
if (!Scene.HasBeenSaved)
{
result = MessageBox.Show(
"Are you sure you want to discard your changes?", "Lose changes?",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.No) return;
}
result = FileOpener.ShowDialog();
if (result == DialogResult.Cancel) return;
try
{
Scene = Scene.Read(FileOpener.FileName);
}
catch (Exception ex)
{
MessageBox.Show($"Error opening scene file: {ex.GetType().Name}");
}
ResetSceneData();
}
private void MenuRunSweep_Click(object? sender, EventArgs e)
{
if (!TryCancelSweep()) return;
SweepParametersForm param = new(this);
DialogResult result = param.ShowDialog();
if (result == DialogResult.Cancel) return;
SweepParameters = param.Result;
simCancel = new();
simStatus = SweepStatus.Sweeping;
MenuRunCancel.Enabled = true;
simViewer ??= new SweepInfoViewer(this);
simViewer.UncompleteSweep();
simViewer.Show();
Task.Run(() => SweepSimulation(SweepParameters, simCancel.Token));
}
public bool TryCancelSweep()
{
if (simStatus == SweepStatus.Sweeping && simCancel is not null)
{
SweepCancelForm form = new();
simCancelForm = form;
DialogResult result = form.ShowDialog();
if (result == DialogResult.No) return false;
simCancel?.Cancel();
return true;
}
return true;
}
private void SweepSimulation(SweepParameters param, CancellationToken token)
{
Float2 diff = param.Scene.EndAt - param.Scene.StartAt;
double minAngle = Math.Atan2(diff.y, diff.x) * Constants.Pi / 180,
maxAngle = Constants.Pi / 2,
angleStep = param.AngleDelta * Constants.Pi / 180;
double closest = double.MaxValue, tolSquared = param.Tolerance * param.Tolerance;
bool end = false;
int angleSteps = (int)((maxAngle - minAngle) / angleStep);
int steps = 0;
simViewer?.SetMaxIters(angleSteps);
for (double ang = minAngle; ang <= maxAngle; ang += angleStep)
{
for (double vel = param.SpeedMin; vel <= param.SpeedMax; vel += param.SpeedDelta)
{
if (token.IsCancellationRequested)
{
end = true;
break;
}
SimulationParameters simParams = new()
{
DeltaTime = param.TimeDelta,
Gravity = param.Gravity,
StartAngle = ang,
StartVelocity = vel,
Scene = Scene,
ToleranceSquared = tolSquared,
ObjectRadius = param.ObjectRadius,
DragCoefficient = param.DragCoefficient,
Mass = param.Mass,
AirDensity = param.AirDensity,
GenerateTable = param.FileMode != ResultsFileMode.None
};
SimulationResult result = SimulateTrajectory(simParams, token);
if (result.EndDistanceSquared < closest)
{
Invoke(() =>
{
simResult = result;
});
Invalidate(true);
closest = result.EndDistanceSquared;
}
}
if (end) break;
simViewer?.SetCurrentIters(steps);
steps++;
}
simCancel = null;
simStatus = token.IsCancellationRequested ? SweepStatus.CancelledSweep : SweepStatus.FinishedSweep;
if (simCancelForm is not null)
{
simCancelForm.DialogResult = DialogResult.No;
simCancelForm.Close();
}
MenuRunCancel.Enabled = false;
simViewer?.Invoke(() => simViewer?.CompleteSweep(simResult!));
Invalidate(true);
}
private static SimulationResult SimulateTrajectory(SimulationParameters param, CancellationToken token)
{
Float2 pos = param.Scene.StartAt;
Float2 vel = (Math.Cos(param.StartAngle) * param.StartVelocity,
Math.Sin(param.StartAngle) * param.StartVelocity);
Float2 gravity = (0, param.Gravity);
double halfArea = 0.5 * param.ObjectRadius * param.ObjectRadius * Constants.Pi;
SimulationResult result = new(param);
double trailPointsPerSecond = 5;
int ticksPerTrailPoint = (int)(1 / (trailPointsPerSecond * param.DeltaTime));
if (param.GenerateTable) result.Table = [];
int ticks = 0;
double time = 0;
while (true)
{
Float2 air = (halfArea * param.DragCoefficient * param.AirDensity * vel.x * vel.x,
halfArea * param.DragCoefficient * param.AirDensity * vel.y * vel.y);
Float2 acc = gravity;
if (param.Mass > 0)
{
air /= param.Mass;
acc.x -= air.x * Math.Sign(vel.x);
acc.y -= air.y * Math.Sign(vel.y);
}
if (param.GenerateTable) result.Table!.Add(new(pos, vel, acc));
if (pos.x < 0 || pos.x >= param.Scene.Width ||
pos.y < 0 || pos.y >= param.Scene.Height)
{
result.Trail.Add(pos);
break;
}
if (token.IsCancellationRequested) break;
bool collide = false;
for (int i = 0; i < param.Scene.Objects.Count; i++)
{
if (param.Scene.Objects[i].Contains(pos))
{
collide = true;
break;
}
}
if (collide)
{
result.Trail.Add(pos);
break;
}
Float2 diff = param.Scene.EndAt - pos;
result.EndDistanceSquared = diff.x * diff.x + diff.y * diff.y;
if (result.EndDistanceSquared <= param.ToleranceSquared)
{
result.Trail.Add(pos);
break;
}
if (ticks % ticksPerTrailPoint == 0) result.Trail.Add(pos);
pos += vel * param.DeltaTime;
vel += acc * param.DeltaTime;
time += param.DeltaTime;
}
result.StartingConditions.StartAngle *= 180 / Constants.Pi;
result.EndSpeed = vel.Magnitude;
result.Duration = time;
return result;
}
private void MenuRunCancel_Click(object sender, EventArgs e)
{
simCancel?.Cancel();
}
public enum SweepStatus
{
NoSweep,
Sweeping,
FinishedSweep,
CancelledSweep
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="Menu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="FileOpener.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>157, 17</value>
</metadata>
</root>

View File

@ -0,0 +1,87 @@
namespace AirTrajectoryBuilder.Forms
{
partial class SweepCancelForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
MessageLabel = new System.Windows.Forms.Label();
YesButton = new System.Windows.Forms.Button();
button2 = new System.Windows.Forms.Button();
SuspendLayout();
//
// MessageLabel
//
MessageLabel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
MessageLabel.Location = new System.Drawing.Point(12, 12);
MessageLabel.Name = "MessageLabel";
MessageLabel.Size = new System.Drawing.Size(637, 135);
MessageLabel.TabIndex = 0;
MessageLabel.Text = "Are you sure you want to cancel the sweep?";
MessageLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// YesButton
//
YesButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
YesButton.DialogResult = System.Windows.Forms.DialogResult.Yes;
YesButton.Location = new System.Drawing.Point(178, 150);
YesButton.Name = "YesButton";
YesButton.Size = new System.Drawing.Size(150, 46);
YesButton.TabIndex = 1;
YesButton.Text = "Yes";
YesButton.UseVisualStyleBackColor = true;
//
// button2
//
button2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
button2.DialogResult = System.Windows.Forms.DialogResult.No;
button2.Location = new System.Drawing.Point(334, 150);
button2.Name = "button2";
button2.Size = new System.Drawing.Size(150, 46);
button2.TabIndex = 2;
button2.Text = "No";
button2.UseVisualStyleBackColor = true;
//
// SweepCancelForm
//
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(661, 208);
Controls.Add(button2);
Controls.Add(YesButton);
Controls.Add(MessageLabel);
Name = "SweepCancelForm";
Text = "Cancel the Sweep?";
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label MessageLabel;
private System.Windows.Forms.Button YesButton;
private System.Windows.Forms.Button button2;
}
}

View File

@ -0,0 +1,11 @@
using System.Windows.Forms;
namespace AirTrajectoryBuilder.Forms;
public partial class SweepCancelForm : Form
{
public SweepCancelForm()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,99 @@
namespace AirTrajectoryBuilder.Forms
{
partial class SweepInfoViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
SweepProgress = new System.Windows.Forms.ProgressBar();
ProgressLabel = new System.Windows.Forms.Label();
ResultsPanel = new System.Windows.Forms.Panel();
ResultsLabel = new System.Windows.Forms.Label();
ResultsPanel.SuspendLayout();
SuspendLayout();
//
// SweepProgress
//
SweepProgress.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
SweepProgress.Location = new System.Drawing.Point(12, 229);
SweepProgress.Name = "SweepProgress";
SweepProgress.Size = new System.Drawing.Size(673, 20);
SweepProgress.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
SweepProgress.TabIndex = 0;
SweepProgress.Value = 50;
//
// ProgressLabel
//
ProgressLabel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
ProgressLabel.Location = new System.Drawing.Point(12, 194);
ProgressLabel.Name = "ProgressLabel";
ProgressLabel.Size = new System.Drawing.Size(673, 32);
ProgressLabel.TabIndex = 1;
ProgressLabel.Text = "Conducting Sweep...";
ProgressLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// ResultsPanel
//
ResultsPanel.AutoScroll = true;
ResultsPanel.Controls.Add(ResultsLabel);
ResultsPanel.Dock = System.Windows.Forms.DockStyle.Fill;
ResultsPanel.Location = new System.Drawing.Point(0, 0);
ResultsPanel.Name = "ResultsPanel";
ResultsPanel.Size = new System.Drawing.Size(697, 405);
ResultsPanel.TabIndex = 2;
ResultsPanel.Visible = false;
//
// ResultsLabel
//
ResultsLabel.Location = new System.Drawing.Point(12, 9);
ResultsLabel.Name = "ResultsLabel";
ResultsLabel.Size = new System.Drawing.Size(682, 387);
ResultsLabel.TabIndex = 0;
ResultsLabel.Text = "label1";
//
// SweepInfoViewer
//
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(697, 405);
Controls.Add(ResultsPanel);
Controls.Add(ProgressLabel);
Controls.Add(SweepProgress);
Name = "SweepInfoViewer";
StartPosition = System.Windows.Forms.FormStartPosition.Manual;
Text = "Simulation Results";
ResultsPanel.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ProgressBar SweepProgress;
private System.Windows.Forms.Label ProgressLabel;
private System.Windows.Forms.Panel ResultsPanel;
private System.Windows.Forms.Label ResultsLabel;
}
}

View File

@ -0,0 +1,97 @@
using AirTrajectoryBuilder.ObjectModels;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace AirTrajectoryBuilder.Forms;
public partial class SweepInfoViewer : Form
{
private readonly MainForm mainForm;
private SimulationResult? results;
public SweepInfoViewer(MainForm mainForm)
{
InitializeComponent();
ResultsPanel.Visible = false;
this.mainForm = mainForm;
Location =
new Point(mainForm.Location.X + mainForm.Size.Width,
mainForm.Location.Y + (mainForm.Size.Height - Size.Height) / 2);
}
protected override void OnClosing(CancelEventArgs e)
{
if (!mainForm.TryCancelSweep())
{
e.Cancel = true;
return;
}
base.OnClosing(e);
mainForm.simViewer = null;
}
public void SetMaxIters(int max)
{
SweepProgress.Maximum = max;
}
public void SetCurrentIters(int val)
{
SweepProgress.Value = val;
}
public void CompleteSweep(SimulationResult best)
{
results = best;
SweepProgress.Visible = false;
ProgressLabel.Visible = false;
DisplayResults();
}
public void UncompleteSweep()
{
SweepProgress.Visible = true;
ProgressLabel.Visible = true;
ResultsPanel.Visible = false;
Invalidate(true);
}
private void SetLabelSize()
{
ResultsLabel.Location = new Point(0, 0);
ResultsLabel.Size = new(ResultsPanel.ClientRectangle.Width,
ResultsLabel.PreferredHeight);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetLabelSize();
}
private void DisplayResults()
{
ResultsPanel.Visible = true;
if (results is null) return;
ResultsLabel.Text = $"""
Initial Angle: {results.StartingConditions.StartAngle:0.00} degrees
Initial Velocity: {results.StartingConditions.StartVelocity:0.0} m/s
Gravity: {results.StartingConditions.Gravity:0.000} m/s^2
Delta Time: {results.StartingConditions.DeltaTime:0.000} seconds
Final Velocity: {results.EndSpeed:0.0} m/s
Duration: {results.Duration:0.00} seconds
Error: off by {results.EndDistanceSquared:0.000} meters
""";
SetLabelSize();
SetLabelSize(); // ...nice. But required!
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,361 @@
namespace AirTrajectoryBuilder
{
partial class SweepParametersForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
AngleSweepValue = new System.Windows.Forms.TextBox();
AngleSweepLabel = new System.Windows.Forms.Label();
SpeedMinLabel = new System.Windows.Forms.Label();
SpeedMinValue = new System.Windows.Forms.TextBox();
SpeedMaxLabel = new System.Windows.Forms.Label();
SpeedDeltaLabel = new System.Windows.Forms.Label();
SpeedMaxValue = new System.Windows.Forms.TextBox();
SpeedDeltaValue = new System.Windows.Forms.TextBox();
RunButton = new System.Windows.Forms.Button();
GravityLabel = new System.Windows.Forms.Label();
GravityValue = new System.Windows.Forms.TextBox();
TimeDeltaLabel = new System.Windows.Forms.Label();
TimeDeltaValue = new System.Windows.Forms.TextBox();
CancelButton = new System.Windows.Forms.Button();
ProjectileMotionLabel = new System.Windows.Forms.Label();
AirTrajectoryLabel = new System.Windows.Forms.Label();
ObjectRadiusValue = new System.Windows.Forms.TextBox();
ObjectRadiusLabel = new System.Windows.Forms.Label();
MassValue = new System.Windows.Forms.TextBox();
MassLabel = new System.Windows.Forms.Label();
AirDensityValue = new System.Windows.Forms.TextBox();
AirDensityLabel = new System.Windows.Forms.Label();
DragCoefficientValue = new System.Windows.Forms.TextBox();
DragCoefficientLabel = new System.Windows.Forms.Label();
ResultsLabel = new System.Windows.Forms.Label();
FileOutputLabel = new System.Windows.Forms.Label();
FileOutputValue = new System.Windows.Forms.ComboBox();
SuspendLayout();
//
// AngleSweepValue
//
AngleSweepValue.Location = new System.Drawing.Point(312, 122);
AngleSweepValue.Name = "AngleSweepValue";
AngleSweepValue.Size = new System.Drawing.Size(101, 39);
AngleSweepValue.TabIndex = 0;
//
// AngleSweepLabel
//
AngleSweepLabel.Location = new System.Drawing.Point(82, 122);
AngleSweepLabel.Name = "AngleSweepLabel";
AngleSweepLabel.Size = new System.Drawing.Size(224, 39);
AngleSweepLabel.TabIndex = 1;
AngleSweepLabel.Text = "Angle Sweep Delta";
AngleSweepLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// SpeedMinLabel
//
SpeedMinLabel.Location = new System.Drawing.Point(12, 168);
SpeedMinLabel.Name = "SpeedMinLabel";
SpeedMinLabel.Size = new System.Drawing.Size(159, 39);
SpeedMinLabel.TabIndex = 2;
SpeedMinLabel.Text = "Speed Min";
SpeedMinLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SpeedMinValue
//
SpeedMinValue.Location = new System.Drawing.Point(36, 210);
SpeedMinValue.Name = "SpeedMinValue";
SpeedMinValue.Size = new System.Drawing.Size(101, 39);
SpeedMinValue.TabIndex = 3;
//
// SpeedMaxLabel
//
SpeedMaxLabel.Location = new System.Drawing.Point(177, 168);
SpeedMaxLabel.Name = "SpeedMaxLabel";
SpeedMaxLabel.Size = new System.Drawing.Size(159, 39);
SpeedMaxLabel.TabIndex = 4;
SpeedMaxLabel.Text = "Speed Max";
SpeedMaxLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SpeedDeltaLabel
//
SpeedDeltaLabel.Location = new System.Drawing.Point(342, 168);
SpeedDeltaLabel.Name = "SpeedDeltaLabel";
SpeedDeltaLabel.Size = new System.Drawing.Size(159, 39);
SpeedDeltaLabel.TabIndex = 5;
SpeedDeltaLabel.Text = "Sweep Delta";
SpeedDeltaLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SpeedMaxValue
//
SpeedMaxValue.Location = new System.Drawing.Point(204, 210);
SpeedMaxValue.Name = "SpeedMaxValue";
SpeedMaxValue.Size = new System.Drawing.Size(101, 39);
SpeedMaxValue.TabIndex = 6;
//
// SpeedDeltaValue
//
SpeedDeltaValue.Location = new System.Drawing.Point(371, 210);
SpeedDeltaValue.Name = "SpeedDeltaValue";
SpeedDeltaValue.Size = new System.Drawing.Size(101, 39);
SpeedDeltaValue.TabIndex = 7;
//
// RunButton
//
RunButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
RunButton.DialogResult = System.Windows.Forms.DialogResult.OK;
RunButton.Location = new System.Drawing.Point(198, 707);
RunButton.Name = "RunButton";
RunButton.Size = new System.Drawing.Size(150, 46);
RunButton.TabIndex = 8;
RunButton.Text = "Sweep";
RunButton.UseVisualStyleBackColor = true;
//
// GravityLabel
//
GravityLabel.Location = new System.Drawing.Point(138, 258);
GravityLabel.Name = "GravityLabel";
GravityLabel.Size = new System.Drawing.Size(113, 39);
GravityLabel.TabIndex = 10;
GravityLabel.Text = "Gravity";
GravityLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// GravityValue
//
GravityValue.Location = new System.Drawing.Point(257, 258);
GravityValue.Name = "GravityValue";
GravityValue.Size = new System.Drawing.Size(101, 39);
GravityValue.TabIndex = 9;
//
// TimeDeltaLabel
//
TimeDeltaLabel.Location = new System.Drawing.Point(131, 75);
TimeDeltaLabel.Name = "TimeDeltaLabel";
TimeDeltaLabel.Size = new System.Drawing.Size(137, 39);
TimeDeltaLabel.TabIndex = 12;
TimeDeltaLabel.Text = "Time Delta";
TimeDeltaLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// TimeDeltaValue
//
TimeDeltaValue.Location = new System.Drawing.Point(274, 75);
TimeDeltaValue.Name = "TimeDeltaValue";
TimeDeltaValue.Size = new System.Drawing.Size(101, 39);
TimeDeltaValue.TabIndex = 11;
//
// CancelButton
//
CancelButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
CancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
CancelButton.Location = new System.Drawing.Point(354, 707);
CancelButton.Name = "CancelButton";
CancelButton.Size = new System.Drawing.Size(150, 46);
CancelButton.TabIndex = 13;
CancelButton.Text = "Cancel";
CancelButton.UseVisualStyleBackColor = true;
//
// ProjectileMotionLabel
//
ProjectileMotionLabel.Font = new System.Drawing.Font("Segoe UI", 10.125F, System.Drawing.FontStyle.Bold);
ProjectileMotionLabel.Location = new System.Drawing.Point(12, 9);
ProjectileMotionLabel.Name = "ProjectileMotionLabel";
ProjectileMotionLabel.Size = new System.Drawing.Size(489, 39);
ProjectileMotionLabel.TabIndex = 14;
ProjectileMotionLabel.Text = "General Parameters:";
ProjectileMotionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// AirTrajectoryLabel
//
AirTrajectoryLabel.Font = new System.Drawing.Font("Segoe UI", 10.125F, System.Drawing.FontStyle.Bold);
AirTrajectoryLabel.Location = new System.Drawing.Point(14, 327);
AirTrajectoryLabel.Name = "AirTrajectoryLabel";
AirTrajectoryLabel.Size = new System.Drawing.Size(489, 39);
AirTrajectoryLabel.TabIndex = 15;
AirTrajectoryLabel.Text = "Aerodynamics (Optional):";
AirTrajectoryLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// ObjectRadiusValue
//
ObjectRadiusValue.Location = new System.Drawing.Point(82, 419);
ObjectRadiusValue.Name = "ObjectRadiusValue";
ObjectRadiusValue.Size = new System.Drawing.Size(101, 39);
ObjectRadiusValue.TabIndex = 17;
//
// ObjectRadiusLabel
//
ObjectRadiusLabel.Location = new System.Drawing.Point(12, 377);
ObjectRadiusLabel.Name = "ObjectRadiusLabel";
ObjectRadiusLabel.Size = new System.Drawing.Size(239, 39);
ObjectRadiusLabel.TabIndex = 16;
ObjectRadiusLabel.Text = "Object Radius";
ObjectRadiusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// MassValue
//
MassValue.Location = new System.Drawing.Point(335, 419);
MassValue.Name = "MassValue";
MassValue.Size = new System.Drawing.Size(101, 39);
MassValue.TabIndex = 19;
//
// MassLabel
//
MassLabel.Location = new System.Drawing.Point(265, 377);
MassLabel.Name = "MassLabel";
MassLabel.Size = new System.Drawing.Size(239, 39);
MassLabel.TabIndex = 18;
MassLabel.Text = "Object Mass";
MassLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// AirDensityValue
//
AirDensityValue.Location = new System.Drawing.Point(335, 512);
AirDensityValue.Name = "AirDensityValue";
AirDensityValue.Size = new System.Drawing.Size(101, 39);
AirDensityValue.TabIndex = 23;
//
// AirDensityLabel
//
AirDensityLabel.Location = new System.Drawing.Point(265, 470);
AirDensityLabel.Name = "AirDensityLabel";
AirDensityLabel.Size = new System.Drawing.Size(239, 39);
AirDensityLabel.TabIndex = 22;
AirDensityLabel.Text = "Air Density";
AirDensityLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// DragCoefficientValue
//
DragCoefficientValue.Location = new System.Drawing.Point(82, 512);
DragCoefficientValue.Name = "DragCoefficientValue";
DragCoefficientValue.Size = new System.Drawing.Size(101, 39);
DragCoefficientValue.TabIndex = 21;
//
// DragCoefficientLabel
//
DragCoefficientLabel.Location = new System.Drawing.Point(12, 470);
DragCoefficientLabel.Name = "DragCoefficientLabel";
DragCoefficientLabel.Size = new System.Drawing.Size(239, 39);
DragCoefficientLabel.TabIndex = 20;
DragCoefficientLabel.Text = "Drag Coefficient";
DragCoefficientLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// ResultsLabel
//
ResultsLabel.Font = new System.Drawing.Font("Segoe UI", 10.125F, System.Drawing.FontStyle.Bold);
ResultsLabel.Location = new System.Drawing.Point(14, 583);
ResultsLabel.Name = "ResultsLabel";
ResultsLabel.Size = new System.Drawing.Size(489, 39);
ResultsLabel.TabIndex = 24;
ResultsLabel.Text = "Results:";
ResultsLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// FileOutputLabel
//
FileOutputLabel.Location = new System.Drawing.Point(37, 622);
FileOutputLabel.Name = "FileOutputLabel";
FileOutputLabel.Size = new System.Drawing.Size(156, 64);
FileOutputLabel.TabIndex = 25;
FileOutputLabel.Text = "File Output";
FileOutputLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// FileOutputValue
//
FileOutputValue.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
FileOutputValue.FormattingEnabled = true;
FileOutputValue.Items.AddRange(new object[] { "None", "Text - Best", "Text - All", "Json - Best", "Json - All", "Binary - Best", "Binary - All" });
FileOutputValue.Location = new System.Drawing.Point(199, 635);
FileOutputValue.MaxLength = 1;
FileOutputValue.Name = "FileOutputValue";
FileOutputValue.Size = new System.Drawing.Size(242, 40);
FileOutputValue.TabIndex = 26;
//
// SweepParametersForm
//
AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(516, 765);
Controls.Add(FileOutputValue);
Controls.Add(FileOutputLabel);
Controls.Add(ResultsLabel);
Controls.Add(AirDensityValue);
Controls.Add(AirDensityLabel);
Controls.Add(DragCoefficientValue);
Controls.Add(DragCoefficientLabel);
Controls.Add(MassValue);
Controls.Add(MassLabel);
Controls.Add(ObjectRadiusValue);
Controls.Add(ObjectRadiusLabel);
Controls.Add(AirTrajectoryLabel);
Controls.Add(ProjectileMotionLabel);
Controls.Add(CancelButton);
Controls.Add(TimeDeltaLabel);
Controls.Add(TimeDeltaValue);
Controls.Add(GravityLabel);
Controls.Add(GravityValue);
Controls.Add(RunButton);
Controls.Add(SpeedDeltaValue);
Controls.Add(SpeedMaxValue);
Controls.Add(SpeedDeltaLabel);
Controls.Add(SpeedMaxLabel);
Controls.Add(SpeedMinValue);
Controls.Add(SpeedMinLabel);
Controls.Add(AngleSweepLabel);
Controls.Add(AngleSweepValue);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
Name = "SweepParametersForm";
Text = "Set Sweep Parameters";
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox AngleSweepValue;
private System.Windows.Forms.Label AngleSweepLabel;
private System.Windows.Forms.Label SpeedMinLabel;
private System.Windows.Forms.TextBox SpeedMinValue;
private System.Windows.Forms.Label SpeedMaxLabel;
private System.Windows.Forms.Label SpeedDeltaLabel;
private System.Windows.Forms.TextBox SpeedMaxValue;
private System.Windows.Forms.TextBox SpeedDeltaValue;
private System.Windows.Forms.Button RunButton;
private System.Windows.Forms.Label GravityLabel;
private System.Windows.Forms.TextBox GravityValue;
private System.Windows.Forms.Label TimeDeltaLabel;
private System.Windows.Forms.TextBox TimeDeltaValue;
private System.Windows.Forms.Button CancelButton;
private System.Windows.Forms.Label ProjectileMotionLabel;
private System.Windows.Forms.Label AirTrajectoryLabel;
private System.Windows.Forms.TextBox ObjectRadiusValue;
private System.Windows.Forms.Label ObjectRadiusLabel;
private System.Windows.Forms.TextBox MassValue;
private System.Windows.Forms.Label MassLabel;
private System.Windows.Forms.TextBox AirDensityValue;
private System.Windows.Forms.Label AirDensityLabel;
private System.Windows.Forms.TextBox DragCoefficientValue;
private System.Windows.Forms.Label DragCoefficientLabel;
private System.Windows.Forms.Label ResultsLabel;
private System.Windows.Forms.Label FileOutputLabel;
private System.Windows.Forms.ComboBox FileOutputValue;
}
}

View File

@ -0,0 +1,156 @@
using AirTrajectoryBuilder.ObjectModels;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System;
namespace AirTrajectoryBuilder;
public partial class SweepParametersForm : Form
{
private static readonly FileMode[] fileModeValues = Enum.GetValues<FileMode>();
public double AngleDelta = 0.1,
SpeedDelta = 0.5,
SpeedMin = 10,
SpeedMax = 100,
Gravity = -9.81,
TimeDelta = 0.01;
public double ObjectRadius = 0,
DragCoefficient = 0,
Mass = 0,
AirDensity = 1.225;
public ResultsFileMode FileMode = ResultsFileMode.None;
private static SweepParameters? previous = null;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public SweepParameters Result
{
get
{
SweepParameters result = new()
{
AngleDelta = AngleDelta,
SpeedDelta = SpeedDelta,
SpeedMin = SpeedMin,
SpeedMax = SpeedMax,
Gravity = Gravity,
TimeDelta = TimeDelta,
ObjectRadius = ObjectRadius,
DragCoefficient = DragCoefficient,
Mass = Mass,
AirDensity = AirDensity,
Tolerance = 1e-1, // TODO
Scene = form.Scene,
FileMode = FileMode
};
previous = result;
return result;
}
set
{
AngleDelta = value.AngleDelta;
SpeedDelta = value.SpeedDelta;
SpeedMin = value.SpeedMin;
SpeedMax = value.SpeedMax;
Gravity = value.Gravity;
TimeDelta = value.TimeDelta;
ObjectRadius = value.ObjectRadius;
DragCoefficient = value.DragCoefficient;
Mass = value.Mass;
AirDensity = value.AirDensity;
FileMode = value.FileMode;
previous = value;
SetValues();
}
}
private readonly MainForm form;
public SweepParametersForm(MainForm form)
{
InitializeComponent();
this.form = form;
AngleSweepValue.Leave += (o, e) =>
{
if (!double.TryParse(AngleSweepValue.Text, out AngleDelta))
AngleSweepValue.Text = AngleDelta.ToString();
};
SpeedDeltaValue.Leave += (o, e) =>
{
if (!double.TryParse(SpeedDeltaValue.Text, out SpeedDelta))
SpeedDeltaValue.Text = SpeedDelta.ToString();
};
SpeedMinValue.Leave += (o, e) =>
{
if (!double.TryParse(SpeedMinValue.Text, out SpeedMin))
SpeedMinValue.Text = SpeedMin.ToString();
};
SpeedMaxValue.Leave += (o, e) =>
{
if (!double.TryParse(SpeedMaxValue.Text, out SpeedMax))
SpeedMaxValue.Text = SpeedMax.ToString();
};
GravityValue.Leave += (o, e) =>
{
if (!double.TryParse(GravityValue.Text, out Gravity))
GravityValue.Text = Gravity.ToString();
};
TimeDeltaValue.Leave += (o, e) =>
{
if (!double.TryParse(TimeDeltaValue.Text, out TimeDelta))
TimeDeltaValue.Text = TimeDelta.ToString();
};
ObjectRadiusValue.Leave += (o, e) =>
{
if (!double.TryParse(ObjectRadiusValue.Text, out ObjectRadius))
ObjectRadiusValue.Text = ObjectRadius.ToString();
};
MassValue.Leave += (o, e) =>
{
if (!double.TryParse(MassValue.Text, out Mass))
MassValue.Text = Mass.ToString();
};
DragCoefficientValue.Leave += (o, e) =>
{
if (!double.TryParse(DragCoefficientValue.Text, out DragCoefficient))
DragCoefficientValue.Text = DragCoefficient.ToString();
};
AirDensityValue.Leave += (o, e) =>
{
if (!double.TryParse(AirDensityValue.Text, out AirDensity))
AirDensityValue.Text = AirDensity.ToString();
};
FileOutputValue.Leave += (o, e) =>
{
int index = FileOutputValue.SelectedIndex;
if (index < 0 || index >= FileOutputValue.Items.Count)
{
FileOutputValue.SelectedIndex = (int)FileMode;
}
else FileMode = (ResultsFileMode)FileOutputValue.SelectedIndex;
};
if (previous is null) SetValues();
else Result = previous;
}
private void SetValues()
{
AngleSweepValue.Text = AngleDelta.ToString();
SpeedDeltaValue.Text = SpeedDelta.ToString();
SpeedMinValue.Text = SpeedMin.ToString();
SpeedMaxValue.Text = SpeedMax.ToString();
GravityValue.Text = Gravity.ToString();
TimeDeltaValue.Text = TimeDelta.ToString();
ObjectRadiusValue.Text = ObjectRadius.ToString();
MassValue.Text = Mass.ToString();
DragCoefficientValue.Text = DragCoefficient.ToString();
AirDensityValue.Text = AirDensity.ToString();
FileOutputValue.SelectedIndex = (int)FileMode;
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,9 @@
using Nerd_STF.Mathematics;
namespace AirTrajectoryBuilder.ObjectModels;
public interface ISceneObject
{
public bool Contains(Float2 point);
public ISceneObject DeepCopy();
}

View File

@ -0,0 +1,12 @@
namespace AirTrajectoryBuilder.ObjectModels;
public enum ResultsFileMode
{
None,
TextBest,
TextAll,
JsonBest,
JsonAll,
BinaryBest,
BinaryAll
}

View File

@ -0,0 +1,146 @@
using Nerd_STF.Mathematics;
using System.Collections.Generic;
using System.IO;
namespace AirTrajectoryBuilder.ObjectModels
{
public class Scene
{
public static Scene Default => new()
{
Width = 100,
Height = 60,
Objects = [],
HasBeenSaved = true,
FilePath = null,
EndAt = (100, 0)
};
public double Width { get; set; }
public double Height { get; set; }
public Float2 StartAt { get; set; }
public Float2 EndAt { get; set; }
public bool HasBeenSaved { get; set; }
public string? FilePath { get; set; }
public List<ISceneObject> Objects { get; private set; } = [];
public Scene DeepCopy()
{
Scene copy = new()
{
Width = Width,
Height = Height,
StartAt = StartAt,
EndAt = EndAt,
HasBeenSaved = HasBeenSaved,
FilePath = FilePath,
Objects = []
};
foreach (ISceneObject obj in Objects) copy.Objects.Add(obj.DeepCopy());
return copy;
}
public static Scene Read(string path)
{
if (!File.Exists(path)) throw new IOException();
StreamReader reader = new(path);
string? line;
Scene? scene = null;
while ((line = reader.ReadLine()) is not null)
{
if (string.IsNullOrWhiteSpace(line)) continue;
string[] parts = line.Split(' ');
switch (parts[0])
{
case "Scene":
if (parts.Length != 4) throw new IOException();
else if (parts[2] != "by") throw new IOException();
scene = new()
{
Width = double.Parse(parts[1]),
Height = double.Parse(parts[3]),
Objects = [],
HasBeenSaved = true,
FilePath = path,
};
break;
case "Rect":
if (scene is null) throw new IOException();
else if (parts.Length != 6) throw new IOException();
else if (parts[3] != "to") throw new IOException();
SceneRect rect = new()
{
From = (double.Parse(parts[1]), double.Parse(parts[2])),
To = (double.Parse(parts[4]), double.Parse(parts[5]))
};
scene.Objects.Add(rect);
break;
case "Tri":
if (scene is null) throw new IOException();
else if (parts.Length != 9) throw new IOException();
else if (parts[3] != "and" || parts[6] != "and") throw new IOException();
SceneTri tri = new()
{
A = (double.Parse(parts[1]), double.Parse(parts[2])),
B = (double.Parse(parts[4]), double.Parse(parts[5])),
C = (double.Parse(parts[7]), double.Parse(parts[8]))
};
scene.Objects.Add(tri);
break;
case "Ellipse":
if (scene is null) throw new IOException();
else if (parts.Length != 7) throw new IOException();
else if (parts[3] != "and" || parts[5] != "by") throw new IOException();
SceneEllipse ellipse = new()
{
Position = (double.Parse(parts[1]), double.Parse(parts[2])),
Size = (double.Parse(parts[4]), double.Parse(parts[6]))
};
scene.Objects.Add(ellipse);
break;
case "Start":
if (scene is null) throw new IOException();
else if (parts.Length != 3) throw new IOException();
scene.StartAt = (double.Parse(parts[1]), double.Parse(parts[2]));
break;
case "End":
if (scene is null) throw new IOException();
else if (parts.Length != 3) throw new IOException();
scene.EndAt = (double.Parse(parts[1]), double.Parse(parts[2]));
break;
default: throw new IOException();
}
}
reader.Close();
return scene ?? Default;
}
public void Write()
{
StreamWriter writer = new(FilePath ?? throw new IOException());
writer.WriteLine($"Scene {Width} by {Height}\n");
foreach (ISceneObject obj in Objects)
{
if (obj is SceneRect objRect)
{
writer.WriteLine($"Rect {objRect.From.x} {objRect.From.y} to {objRect.To.x} {objRect.To.y}");
}
else if (obj is SceneTri objTri)
{
writer.WriteLine($"Tri {objTri.A.x} {objTri.A.y} and {objTri.B.x} {objTri.B.y} and {objTri.C.x} {objTri.C.y}");
}
else if (obj is SceneEllipse objEllipse)
{
writer.WriteLine($"Ellipse {objEllipse.Position.x} {objEllipse.Position.y} and {objEllipse.Size.x} by {objEllipse.Size.y}");
}
}
writer.Close();
}
}
}

View File

@ -0,0 +1,24 @@
using Nerd_STF.Mathematics;
namespace AirTrajectoryBuilder.ObjectModels;
public class SceneEllipse : ISceneObject
{
public Float2 Position { get; set; }
public Float2 Size { get; set; }
public bool Contains(Float2 point)
{
Float2 p = point - Position;
Float2 delta = p * 2 / Size;
delta.x *= delta.x;
delta.y *= delta.y;
return delta.x + delta.y <= 1;
}
public ISceneObject DeepCopy() => new SceneEllipse()
{
Position = Position,
Size = Size
};
}

View File

@ -0,0 +1,24 @@
using Nerd_STF.Mathematics;
namespace AirTrajectoryBuilder.ObjectModels;
public class SceneRect : ISceneObject
{
public Float2 From { get; set; }
public Float2 To { get; set; }
public bool Contains(Float2 p)
{
double minX = double.Min(From.x, To.x), maxX = double.Max(From.x, To.x),
minY = double.Min(From.y, To.y), maxY = double.Max(From.y, To.y);
return p.x >= minX && p.x <= maxX &&
p.y >= minY && p.y <= maxY;
}
public ISceneObject DeepCopy() => new SceneRect()
{
From = From,
To = To
};
}

View File

@ -0,0 +1,33 @@
using Nerd_STF.Mathematics;
using System;
namespace AirTrajectoryBuilder.ObjectModels;
public class SceneTri : ISceneObject
{
public Float2 A { get; set; }
public Float2 B { get; set; }
public Float2 C { get; set; }
private static double Area(Float2 a, Float2 b, Float2 c)
{
return Math.Abs((a.x * (b.y - c.y) +
b.x * (c.y - a.y) +
c.x * (a.y - b.y)) * 0.5);
}
public bool Contains(Float2 p)
{
double area = Area(A, B, C),
a1 = Area(p, B, C),
a2 = Area(A, p, C),
a3 = Area(A, B, p);
return area == a1 + a2 + a3;
}
public ISceneObject DeepCopy() => new SceneTri()
{
A = A,
B = B,
C = C
};
}

View File

@ -0,0 +1,16 @@
namespace AirTrajectoryBuilder.ObjectModels;
public class SimulationParameters
{
public required double StartAngle;
public required double StartVelocity;
public required double DeltaTime;
public required double Gravity;
public required double ToleranceSquared;
public required double ObjectRadius;
public required double DragCoefficient;
public required double Mass;
public required double AirDensity;
public required Scene Scene;
public required bool GenerateTable;
}

View File

@ -0,0 +1,24 @@
using Nerd_STF.Mathematics;
using System.Collections.Generic;
using System.IO;
namespace AirTrajectoryBuilder.ObjectModels;
public class SimulationResult
{
public double Duration;
public double EndDistanceSquared;
public double EndSpeed;
public List<Float2> Trail = [];
public List<TableEntry>? Table = null;
public SimulationParameters StartingConditions;
public SimulationResult(SimulationParameters starting)
{
StartingConditions = starting;
}
}

View File

@ -0,0 +1,18 @@
namespace AirTrajectoryBuilder.ObjectModels;
public class SweepParameters
{
public required double AngleDelta, TimeDelta;
public required double SpeedMin, SpeedMax, SpeedDelta;
public required double Gravity;
public required double Tolerance;
public required double ObjectRadius;
public required double DragCoefficient;
public required double Mass;
public required double AirDensity;
public required ResultsFileMode FileMode;
public required Scene Scene;
}

View File

@ -0,0 +1,5 @@
using Nerd_STF.Mathematics;
namespace AirTrajectoryBuilder.ObjectModels;
public record class TableEntry(Float2 Position, Float2 Velocity, Float2 Acceleration);

View File

@ -0,0 +1,41 @@
/**********722871**********
* Date: 12/2/2024
* Programmer: Kyle Gilbert
* Program Name: AirTrajectoryBuilder
* Program Description: Sweeps possible launch angles and speeds in a given scene
* and applies aerodynamic physics.
**************************/
using AirTrajectoryBuilder.ObjectModels;
using System;
using System.Windows.Forms;
namespace AirTrajectoryBuilder;
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Scene? scene;
if (args.Length > 0)
{
try
{
scene = Scene.Read(args[0]);
}
catch (Exception ex)
{
MessageBox.Show($"Error opening scene file: {ex.GetType().Name}");
scene = null;
}
}
else scene = null;
Application.Run(new MainForm(scene));
}
}

View File

@ -27,7 +27,12 @@ I have about 1-2 weeks for each project. Check the Git commits for specific date
- Data transfer is automatically encrypted behind the scenes (though the server decrypts it when it gets it).
- Allows for as many people to connect as need be.
- The client is somewhat janky, but the server has zero issues from my testing.
- TypingTest
- TypingTest/
- A small game I made in like 2 hours. Using a list of words, it picks one at random and the user has to type it out.
- It has a one-minute timer, and highlights the letters you got right.
- Shows results as characters per minute, words per minute, and accuracy percentage at the end.
- AirTrajectoryBuilder
- A program I wrote that simulates the air trajectory of a projectile. Finished a while ago.
- 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.