New computer studies project.

This commit is contained in:
That-One-Nerd 2025-02-19 12:25:13 -05:00
parent 9c87723f89
commit 900e28c313
9 changed files with 545 additions and 3 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}") = "BasicProjectionRenderer", "BasicProjectionRenderer\BasicProjectionRenderer.csproj", "{C8F67C63-8D5D-4841-984B-7885A6268865}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C8F67C63-8D5D-4841-984B-7885A6268865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8F67C63-8D5D-4841-984B-7885A6268865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8F67C63-8D5D-4841-984B-7885A6268865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8F67C63-8D5D-4841-984B-7885A6268865}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

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

View File

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

View File

@ -0,0 +1,57 @@
using System.Drawing;
using System.Windows.Forms;
namespace BasicProjectionRenderer
{
partial class Form1
{
/// <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()
{
components = new System.ComponentModel.Container();
RefreshTimer = new Timer(components);
SuspendLayout();
//
// RefreshTimer
//
RefreshTimer.Enabled = true;
RefreshTimer.Interval = 10;
//
// Form1
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1300, 783);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
}
#endregion
private Timer RefreshTimer;
}
}

View File

@ -0,0 +1,137 @@
using Nerd_STF.Mathematics;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BasicProjectionRenderer;
public partial class Form1 : Form
{
public Mesh? Mesh { get; set; }
public Float2 ZoomLevel = (1, 1);
public Float3 ScreenCenter = (0, 0, -2.5);
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
RefreshTimer.Tick += OnTick;
}
public Int2 ToScreenSpace(Float3 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 Float3 FromScreenSpace(Int2 screenPoint)
{
Float3 result = new(screenPoint.x, screenPoint.y, ScreenCenter.z);
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;
}
private int paintIters;
private double paintTime;
protected override void OnPaint(PaintEventArgs e)
{
DateTime start = DateTime.Now;
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
if (Mesh is null) return;
SolidBrush colorBrush = new(Color.Black);
Pen colorPen = new(colorBrush);
/*// Draw edges.
for (int i = 0; i < Mesh.lines.Length; i++)
{
Line line = Mesh.lines[i];
//Color newColor = Color.FromArgb((int)(0xFF000000 | (0x00FFFFFF & line.GetHashCode())));
//colorPen.Color = newColor;
Float3 pointA = Mesh.GetPoint(line.IndA),
pointB = Mesh.GetPoint(line.IndB);
Int2 posA = ToScreenSpace(pointA), posB = ToScreenSpace(pointB);
g.DrawLine(colorPen, posA, posB);
}*/
// Draw faces.
for (int i = 0; i < Mesh.faces.Length; i++)
{
Face face = Mesh.faces[i];
Color newColor = Color.FromArgb((int)(0xFF000000 | (0x00FFFFFF & face.GetHashCode())));
colorBrush.Color = newColor;
Float3 pointA = Mesh.GetPoint(face.IndA),
pointB = Mesh.GetPoint(face.IndB),
pointC = Mesh.GetPoint(face.IndC);
Int2 posA = ToScreenSpace(pointA), posB = ToScreenSpace(pointB), posC = ToScreenSpace(pointC);
g.FillPolygon(colorBrush, [posA, posB, posC]);
}
DateTime end = DateTime.Now;
paintIters++;
paintTime += (end - start).TotalMilliseconds;
if (paintIters == 20)
{
double per = paintTime / 20;
Console.WriteLine($"{per:0.000} ms avg.");
paintIters = 0;
paintTime = 0;
}
}
private double elapsedTime, temp;
private void OnTick(object? sender, EventArgs e)
{
elapsedTime += RefreshTimer.Interval * 1e-3;
if (Mesh is not null)
{
DateTime start = DateTime.Now;
LookAtCursor();
DateTime end = DateTime.Now;
paintTime += (end - start).TotalMilliseconds;
void LookAtCursor()
{
Float3 mousePos = FromScreenSpace(PointToClient(Cursor.Position));
Float3 dist = Mesh!.Location - mousePos;
Angle leftRight = new(Math.Atan2(dist.x, dist.z), Angle.Units.Radians),
upDown = new(Math.Atan2(dist.y, dist.z), Angle.Units.Radians),
spin = new(temp, Angle.Units.Degrees);
Mesh.Rotation = (upDown, -leftRight, spin);
}
}
Invalidate();
}
}

View File

@ -0,0 +1,123 @@
<?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="RefreshTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -0,0 +1,142 @@
using Nerd_STF.Mathematics;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections.Generic;
using System.IO;
namespace BasicProjectionRenderer;
public record class Line(int IndA, int IndB) : IEquatable<Line>
{
public virtual bool Equals(Line? other)
{
if (other is null) return false;
return (IndA == other.IndA && IndB == other.IndB) ||
(IndA == other.IndB && IndB == other.IndA);
}
public override int GetHashCode() => base.GetHashCode();
}
public record class Face(int IndA, int IndB, int IndC);
public class Mesh
{
public required Float3[] points;
public required Line[] lines;
public required Face[] faces;
public Float3 Location { get; set; } = (0, 0, 0);
public (Angle, Angle, Angle) Rotation
{
get => _rotation;
set
{
if (_rotation != value)
{
_rotation = value;
CalculateRotationMatrix();
}
}
}
public Float3 Scale { get; set; } = (1, 1, 1);
private readonly Dictionary<int, Float3> _pointCache = [];
private (Angle, Angle, Angle) _rotation = (Angle.Zero, Angle.Zero, Angle.Zero);
private Matrix3x3 _rotMatrix = Matrix3x3.Identity;
private void CalculateRotationMatrix()
{
double radX = _rotation.Item1.Radians,
radY = _rotation.Item2.Radians,
radZ = _rotation.Item3.Radians;
(double cosX, double sinX) = (MathE.Cos(radX), MathE.Sin(radX));
(double cosY, double sinY) = (MathE.Cos(radY), MathE.Sin(radY));
(double cosZ, double sinZ) = (MathE.Cos(radZ), MathE.Sin(radZ));
Matrix3x3 rotX = new([
[ 1, 0, 0 ],
[ 0, cosX, -sinX ],
[ 0, sinX, cosX ]
]);
Matrix3x3 rotY = new([
[ cosY, 0, sinY ],
[ 0, 1, 0 ],
[ -sinY, 0, cosY ]
]);
Matrix3x3 rotZ = new([
[ cosZ, -sinZ, 0 ],
[ sinZ, cosZ, 0 ],
[ 0, 0, 1 ]
]);
_rotMatrix = rotX * rotY * rotZ;
_pointCache.Clear();
Array.Sort(faces, SortFace);
}
public Float3 GetPoint(int index)
{
if (_pointCache.TryGetValue(index, out Float3 cached)) return cached;
Float3 p = points[index];
p = _rotMatrix * p;
p *= Scale;
p += Location;
_pointCache.Add(index, p);
return p;
}
private int SortFace(Face f1, Face f2)
{
Float3 f1a = GetPoint(f1.IndA), f2a = GetPoint(f2.IndA),
f1b = GetPoint(f1.IndB), f2b = GetPoint(f2.IndB),
f1c = GetPoint(f1.IndC), f2c = GetPoint(f2.IndC);
Float3 avg1 = (f1a + f1b + f1c) / 3,
avg2 = (f2a + f2b + f2c) / 3;
return avg1.z.CompareTo(avg2.z);
}
public static Mesh FromObj(Stream data)
{
StreamReader reader = new(data, leaveOpen: true);
string? line;
List<Float3> points = [];
List<Line> lines = [];
List<Face> faces = [];
while ((line = reader.ReadLine()) is not null)
{
line = line.Trim();
if (line.StartsWith('#')) continue;
string[] parts = line.Split(' ');
switch (parts[0])
{
case "v":
points.Add((double.Parse(parts[1]), double.Parse(parts[2]), double.Parse(parts[3])));
break;
case "f":
int indA = int.Parse(parts[1]) - 1,
indB = int.Parse(parts[2]) - 1,
indC = int.Parse(parts[3]) - 1;
/*Line l1 = new(indA, indB), l2 = new(indB, indC), l3 = new(indC, indA);
if (!lines.Contains(l1)) lines.Add(l1);
if (!lines.Contains(l2)) lines.Add(l2);
if (!lines.Contains(l3)) lines.Add(l3);*/
faces.Add(new(indA, indB, indC));
break;
}
}
reader.Close();
Console.WriteLine($"{points.Count} verts, {lines.Count} edges, {faces.Count} faces");
return new()
{
points = [.. points],
lines = [.. lines],
faces = [.. faces]
};
}
public override int GetHashCode() => points.GetHashCode();
}

View File

@ -0,0 +1,33 @@
/**********722871**********
* Date: 2/19/2025
* Programmer: Kyle Gilbert
* Program Name: BasicProjectionRenderer
* Program Description: Renders an OBJ file in orthographic view. All code
* (even the matrix multiplication) is my own.
**************************/
using System;
using System.IO;
using System.Windows.Forms;
namespace BasicProjectionRenderer;
public static class Program
{
[STAThread]
public static void Main()
{
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
string path = "cube.obj";
FileStream fs = new(path, FileMode.Open);
Mesh obj = Mesh.FromObj(fs);
fs.Close();
Form1 form = new()
{
Mesh = obj
};
Application.Run(form);
}
}

View File

@ -31,17 +31,22 @@ I have about 1-2 weeks for each project. Check the Git commits for specific date
- 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. - 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. - 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. - Shows results as characters per minute, words per minute, and accuracy percentage at the end.
- AirTrajectoryBuilder - AirTrajectoryBuilder/
- A program I wrote that simulates the air trajectory of a projectile. Finished a while ago. - 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) - Create a `.sce` file (a somewhat easy to use plain text format)
- Nice colors for each object. Scales seamlessly with a higher DPI. - 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. - Sweeps possible angles and speeds to try and find the path that brings the ball closest to the end point.
- Fractal Visualizer - Fractal Visualizer/
- A program that can be used to visualize fractals. - A program that can be used to visualize fractals.
- Allows you to zoom in and drag the screen around in real time. - Allows you to zoom in and drag the screen around in real time.
- Renders in multiple resolution scales so as to be as responsive as possible. Upscales over time. - Renders in multiple resolution scales so as to be as responsive as possible. Upscales over time.
- Currently does the mandlebrot set. It has support for any complex iterative fractal, but you have to code it yourself. - Currently does the mandlebrot set. It has support for any complex iterative fractal, but you have to code it yourself.
- Ciphers - Ciphers/
- Command-line tool that enciphers and deciphers text. - Command-line tool that enciphers and deciphers text.
- Small thing. Not super good. I used it to complete homework for a different class. - Small thing. Not super good. I used it to complete homework for a different class.
- Uses an argument parsing library I also wrote. - Uses an argument parsing library I also wrote.
- BasicProjectionRenderer/
- A program that parses and renders an OBJ file.
- 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.