Initial commit

This commit is contained in:
github-classroom[bot] 2025-09-11 09:05:42 +00:00 committed by GitHub
commit e77901d4f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 2443 additions and 0 deletions

398
.gitignore vendored Normal file
View File

@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# Examples of Code Snippets and Figures to support your explanations
```c#
public static void Main()
{
var nativeWindowSettings = new NativeWindowSettings
{
Size = new Vector2i(800, 600),
Title = "My OpenTK Example Program"
};
using (var window = new MyExampleWindow(GameWindowSettings.Default,
nativeWindowSettings))
{
window.Run();
}
}
```
*Snippet 1 - from Program.cs Line 12*
![Screenshot of three busses made using primitive cube and cylinder objects.](images/busses.png)
*Figure 1 - The Busses*
<figure>
<img
src="images/busses.png"
alt="The beautiful busses.">
<figcaption>*Figure 2 - The Busses*</figcaption>
</figure>
# Your reflection
As you develop your scene, you should reflect on your progress by editing this document in each of the sections below. You should include information that makes it clear what you have implemented and demonstrates your understanding of the concepts and techniques that you have applied.
# Implement a MyVector object that passes all the provided tests.
Your reflection
# Implement a MyMatrix object that passes all the provided tests.
Your reflection
# Create a Primitive class that is capable of generating Triangles, Quads, Cubes, Cylinders, and Spheres.
Your reflection
# Use the OpenTK Matrix4 and Vector3/4 structs to implement transformations (translate, rotate, scale) in your scene.
Your reflection
# Implement a Scene Graph to store and execute your transformations and drawings.
Your reflection
# Animation
Your reflection
# Lighting
Your reflection
# Textures
Your reflection
# Cameras
Your reflection

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Shaders\" />
</ItemGroup>
<ItemGroup>
<None Update="Shaders\shader.frag">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Shaders\shader.vert">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Link="README.md" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,189 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace LearnOpenTK.Common
{
// A simple class meant to help create shaders.
public class Shader
{
public readonly int Handle;
private readonly Dictionary<string, int> _uniformLocations;
// This is how you create a simple shader.
// Shaders are written in GLSL, which is a language very similar to C in its semantics.
// The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on.
// A commented example of GLSL can be found in shader.vert.
public Shader(string vertPath, string fragPath)
{
// There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
// The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
// The vertex shader won't be too important here, but they'll be more important later.
// The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel.
// The fragment shader is what we'll be using the most here.
// Load vertex shader and compile
var shaderSource = File.ReadAllText(vertPath);
// GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
// Now, bind the GLSL source code
GL.ShaderSource(vertexShader, shaderSource);
// And then compile
CompileShader(vertexShader);
// We do the same for the fragment shader.
shaderSource = File.ReadAllText(fragPath);
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader, shaderSource);
CompileShader(fragmentShader);
// These two shaders must then be merged into a shader program, which can then be used by OpenGL.
// To do this, create a program...
Handle = GL.CreateProgram();
// Attach both shaders...
GL.AttachShader(Handle, vertexShader);
GL.AttachShader(Handle, fragmentShader);
// And then link them together.
LinkProgram(Handle);
// When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
// Detach them, and then delete them.
GL.DetachShader(Handle, vertexShader);
GL.DetachShader(Handle, fragmentShader);
GL.DeleteShader(fragmentShader);
GL.DeleteShader(vertexShader);
// The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
// Querying this from the shader is very slow, so we do it once on initialization and reuse those values
// later.
// First, we have to get the number of active uniforms in the shader.
GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);
// Next, allocate the dictionary to hold the locations.
_uniformLocations = new Dictionary<string, int>();
// Loop over all the uniforms,
for (var i = 0; i < numberOfUniforms; i++)
{
// get the name of this uniform,
var key = GL.GetActiveUniform(Handle, i, out _, out _);
// get the location,
var location = GL.GetUniformLocation(Handle, key);
// and then add it to the dictionary.
_uniformLocations.Add(key, location);
}
}
private static void CompileShader(int shader)
{
// Try to compile the shader
GL.CompileShader(shader);
// Check for compilation errors
GL.GetShader(shader, ShaderParameter.CompileStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
var infoLog = GL.GetShaderInfoLog(shader);
throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
}
}
private static void LinkProgram(int program)
{
// We link the program
GL.LinkProgram(program);
// Check for linking errors
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetProgramInfoLog(program)` to get information about the error.
throw new Exception($"Error occurred whilst linking Program({program})");
}
}
// A wrapper function that enables the shader program.
public void Use()
{
GL.UseProgram(Handle);
}
// The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically,
// you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values.
public int GetAttribLocation(string attribName)
{
return GL.GetAttribLocation(Handle, attribName);
}
// Uniform setters
// Uniforms are variables that can be set by user code, instead of reading them from the VBO.
// You use VBOs for vertex-related data, and uniforms for almost everything else.
// Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
// 1. Bind the program you want to set the uniform on
// 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
// 3. Use the appropriate GL.Uniform* function to set the uniform.
/// <summary>
/// Set a uniform int on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetInt(string name, int data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
/// <summary>
/// Set a uniform float on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetFloat(string name, float data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
/// <summary>
/// Set a uniform Matrix4 on this shader
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
/// <remarks>
/// <para>
/// The matrix is transposed before being sent to the shader.
/// </para>
/// </remarks>
public void SetMatrix4(string name, Matrix4 data)
{
GL.UseProgram(Handle);
GL.UniformMatrix4(_uniformLocations[name], true, ref data);
}
/// <summary>
/// Set a uniform Vector3 on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetVector3(string name, Vector3 data)
{
GL.UseProgram(Handle);
GL.Uniform3(_uniformLocations[name], data);
}
}
}

View File

@ -0,0 +1,215 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
using LearnOpenTK.Common;
namespace TheLabs
{
public class MyExampleWindow : GameWindow
{
// Create the vertices for our triangle. These are listed in normalized device coordinates (NDC)
// In NDC, (0, 0) is the center of the screen.
// Negative X coordinates move to the left, positive X move to the right.
// Negative Y coordinates move to the bottom, positive Y move to the top.
// OpenGL only supports rendering in 3D, so to create a flat triangle, the Z coordinate will be kept as 0.
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, // Bottom-left vertex
0.5f, -0.5f, 0.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f // Top vertex
};
// These are the handles to OpenGL objects. A handle is an integer representing where the object lives on the
// graphics card. Consider them sort of like a pointer; we can't do anything with them directly, but we can
// send them to OpenGL functions that need them.
// What these objects are will be explained in OnLoad.
private int _vertexBufferObject;
private int _vertexArrayObject;
// This class is a wrapper around a shader, which helps us manage it.
// The shader class's code is in the Common project.
// What shaders are and what they're used for will be explained later in this tutorial.
private Shader _shader;
public MyExampleWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
// Now, we start initializing OpenGL.
protected override void OnLoad()
{
base.OnLoad();
// This will be the color of the background after we clear it, in normalized colors.
// Normalized colors are mapped on a range of 0.0 to 1.0, with 0.0 representing black, and 1.0 representing
// the largest possible value for that channel.
// This is a deep green.
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// We need to send our vertices over to the graphics card so OpenGL can use them.
// To do this, we need to create what's called a Vertex Buffer Object (VBO).
// These allow you to upload a bunch of data to a buffer, and send the buffer to the graphics card.
// This effectively sends all the vertices at the same time.
// First, we need to create a buffer. This function returns a handle to it, but as of right now, it's empty.
_vertexBufferObject = GL.GenBuffer();
// Now, bind the buffer. OpenGL uses one global state, so after calling this,
// all future calls that modify the VBO will be applied to this buffer until another buffer is bound instead.
// The first argument is an enum, specifying what type of buffer we're binding. A VBO is an ArrayBuffer.
// There are multiple types of buffers, but for now, only the VBO is necessary.
// The second argument is the handle to our buffer.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
// Finally, upload the vertices to the buffer.
// Arguments:
// Which buffer the data should be sent to.
// How much data is being sent, in bytes. You can generally set this to the length of your array, multiplied by sizeof(array type).
// The vertices themselves.
// How the buffer will be used, so that OpenGL can write the data to the proper memory space on the GPU.
// There are three different BufferUsageHints for drawing:
// StaticDraw: This buffer will rarely, if ever, update after being initially uploaded.
// DynamicDraw: This buffer will change frequently after being initially uploaded.
// StreamDraw: This buffer will change on every frame.
// Writing to the proper memory space is important! Generally, you'll only want StaticDraw,
// but be sure to use the right one for your use case.
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
// One notable thing about the buffer we just loaded data into is that it doesn't have any structure to it. It's just a bunch of floats (which are actaully just bytes).
// The opengl driver doesn't know how this data should be interpreted or how it should be divided up into vertices. To do this opengl introduces the idea of a
// Vertex Array Obejct (VAO) which has the job of keeping track of what parts or what buffers correspond to what data. In this example we want to set our VAO up so that
// it tells opengl that we want to interpret 12 bytes as 3 floats and divide the buffer into vertices using that.
// To do this we generate and bind a VAO (which looks deceptivly similar to creating and binding a VBO, but they are different!).
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
// Now, we need to setup how the vertex shader will interpret the VBO data; you can send almost any C datatype (and a few non-C ones too) to it.
// While this makes them incredibly flexible, it means we have to specify how that data will be mapped to the shader's input variables.
// To do this, we use the GL.VertexAttribPointer function
// This function has two jobs, to tell opengl about the format of the data, but also to associate the current array buffer with the VAO.
// This means that after this call, we have setup this attribute to source data from the current array buffer and interpret it in the way we specified.
// Arguments:
// Location of the input variable in the shader. the layout(location = 0) line in the vertex shader explicitly sets it to 0.
// How many elements will be sent to the variable. In this case, 3 floats for every vertex.
// The data type of the elements set, in this case float.
// Whether or not the data should be converted to normalized device coordinates. In this case, false, because that's already done.
// The stride; this is how many bytes are between the last element of one vertex and the first element of the next. 3 * sizeof(float) in this case.
// The offset; this is how many bytes it should skip to find the first element of the first vertex. 0 as of right now.
// Stride and Offset are just sort of glossed over for now, but when we get into texture coordinates they'll be shown in better detail.
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
// Enable variable 0 in the shader.
GL.EnableVertexAttribArray(0);
// We've got the vertices done, but how exactly should this be converted to pixels for the final image?
// Modern OpenGL makes this pipeline very free, giving us a lot of freedom on how vertices are turned to pixels.
// The drawback is that we actually need two more programs for this! These are called "shaders".
// Shaders are tiny programs that live on the GPU. OpenGL uses them to handle the vertex-to-pixel pipeline.
// Check out the Shader class in Common to see how we create our shaders, as well as a more in-depth explanation of how shaders work.
// shader.vert and shader.frag contain the actual shader code.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
// Now, enable the shader.
// Just like the VBO, this is global, so every function that uses a shader will modify this one until a new one is bound instead.
_shader.Use();
// Setup is now complete! Now we move to the OnRenderFrame function to finally draw the triangle.
}
// Now that initialization is done, let's create our render loop.
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
// This clears the image, using what you set as GL.ClearColor earlier.
// OpenGL provides several different types of data that can be rendered.
// You can clear multiple buffers by using multiple bit flags.
// However, we only modify the color, so ColorBufferBit is all we need to clear.
GL.Clear(ClearBufferMask.ColorBufferBit);
// To draw an object in OpenGL, it's typically as simple as binding your shader,
// setting shader uniforms (not done here, will be shown in a future tutorial)
// binding the VAO,
// and then calling an OpenGL function to render.
// Bind the shader
_shader.Use();
// Bind the VAO
GL.BindVertexArray(_vertexArrayObject);
// And then call our drawing function.
// For this tutorial, we'll use GL.DrawArrays, which is a very simple rendering function.
// Arguments:
// Primitive type; What sort of geometric primitive the vertices represent.
// OpenGL used to support many different primitive types, but almost all of the ones still supported
// is some variant of a triangle. Since we just want a single triangle, we use Triangles.
// Starting index; this is just the start of the data you want to draw. 0 here.
// How many vertices you want to draw. 3 for a triangle.
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
// OpenTK windows are what's known as "double-buffered". In essence, the window manages two buffers.
// One is rendered to while the other is currently displayed by the window.
// This avoids screen tearing, a visual artifact that can happen if the buffer is modified while being displayed.
// After drawing, call this function to swap the buffers. If you don't, it won't display what you've rendered.
SwapBuffers();
// And that's all you have to do for rendering! You should now see a yellow triangle on a black screen.
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
// When the window gets resized, we have to call GL.Viewport to resize OpenGL's viewport to match the new size.
// If we don't, the NDC will no longer be correct.
GL.Viewport(0, 0, Size.X, Size.Y);
}
// Now, for cleanup.
// You should generally not do cleanup of opengl resources when exiting an application,
// as that is handled by the driver and operating system when the application exits.
//
// There are reasons to delete opengl resources, but exiting the application is not one of them.
// This is provided here as a reference on how resource cleanup is done in opengl, but
// should not be done when exiting the application.
//
// Places where cleanup is appropriate would be: to delete textures that are no
// longer used for whatever reason (e.g. a new scene is loaded that doesn't use a texture).
// This would free up video ram (VRAM) that can be used for new textures.
//
// The coming chapters will not have this code.
protected override void OnUnload()
{
// Unbind all the resources by binding the targets to 0/null.
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all the resources.
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shader.Handle);
base.OnUnload();
}
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using OpenTK.Mathematics;
public class MyMatrix
{
public MyMatrix(float pRow0Column0,
float pRow0Column1,
float pRow0Column2,
float pRow0Column3,
float pRow1Column0,
float pRow1Column1,
float pRow1Column2,
float pRow1Column3,
float pRow2Column0,
float pRow2Column1,
float pRow2Column2,
float pRow2Column3,
float pRow3Column0,
float pRow3Column1,
float pRow3Column2,
float pRow3Column3)
{
}
public float GetElement(int pRow, int pColumn)
{
return -1;
}
public void SetElement(int pRow, int pColumn, float pValue)
{
}
public static MyMatrix CreateIdentity()
{
return null;
}
public static MyMatrix CreateTranslation(MyVector pTranslation)
{
return null;
}
public static MyMatrix CreateScale(MyVector pScale)
{
return null;
}
public static MyMatrix CreateRotationX(float pAngle)
{
return null;
}
public static MyMatrix CreateRotationY(float pAngle)
{
return null;
}
public static MyMatrix CreateRotationZ(float pAngle)
{
return null;
}
public MyVector Multiply(MyVector pVector)
{
return null;
}
public MyMatrix Multiply(MyMatrix pMatrix)
{
return null;
}
public MyMatrix Inverse()
{
return null;
}
// this method takes our MyMatrix and converts it to an OpenTK Matrix4
// this looks a little odd as the OpenTK Matrix4 constructor takes the elements in row major order
public Matrix4 ToMatrix4()
{
return Matrix4.Identity;
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
public class MyVector
{
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
// this W component has only been included to make it easier to do Matrix multiplications
public float W { get; private set; }
public MyVector(float pX, float pY, float pZ, float pW = 1)
{
}
public MyVector Add(MyVector pVector)
{
return null;
}
public MyVector Subtract(MyVector pVector)
{
return null;
}
public MyVector Multiply(float pScalar)
{
return null;
}
public MyVector Divide(float pScalar)
{
return null;
}
public float Magnitude()
{
return -1;
}
public MyVector Normalise()
{
return null;
}
public float DotProduct(MyVector pVector)
{
return -1;
}
public MyVector Interpolate(MyVector pVector, float pInterpolation)
{
return null;
}
public float AngleBetween(MyVector pVector)
{
return -1;
}
public MyVector CrossProduct(MyVector pVector)
{
return null;
}
}

View File

@ -0,0 +1,24 @@
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using System;
namespace TheLabs
{
public static class Program
{
public static void Main()
{
var nativeWindowSettings = new NativeWindowSettings
{
Size = new Vector2i(800, 600),
Title = "My OpenTK Example Program"
};
using (var window = new MyExampleWindow(GameWindowSettings.Default,
nativeWindowSettings))
{
window.Run();
}
}
}
}

View File

@ -0,0 +1,8 @@
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0, 1.0, 0.0, 1.0);
}

View File

@ -0,0 +1,41 @@
// For more information on how shaders work, check out the web version of this tutorial.
// I'll include a simpler summary here.
// First non-comment line should always be a #version statement; this just tells the GLSL compiler what version it should use.
#version 330 core
// GLSL's syntax is somewhat like C, but it has a few differences.
// There are four different types of variables in GLSL: input, output, uniform, and internal.
// - Input variables are sent from the buffer, in a way defined by GL.VertexAttribPointer.
// - Output variables are sent from this shader to the next one in the chain (which will be the fragment shader most of the time).
// - Uniforms will be touched on in the next tutorial.
// - Internal variables are defined in the shader file and only used there.
// The vertex shader is run once for every vertex. In C# pseudocode, it might look something like:
// foreach(var vertex in vertices)
// shader(vertex)
// This defines our input variable, aPosition.
// It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer.
// However, you can omit it, and replace this with just "in vec3 aPosition". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with
// a call to GL.GetAttribLocation(shaderHandle, attributeName)
// Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial.
// Then, the keyword "vec3" means this is a vector with 3 floats inside.
layout(location = 0) in vec3 aPosition;
// Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main.
// You can do all sorts of calculations here to modify your vertices, but right now, we don't need to do any of that.
// gl_Position is the final vertex position; pass a vec4 to it and you're done.
// Keep in mind that we only pass a vec3 to this shader; the fourth component of a vertex is known as "w".
// It's only used in some more advanced OpenGL functions; it's not needed here.
// So with a call to the vec4 function, we just give it a constant value of 1.0.
void main(void)
{
gl_Position = vec4(aPosition, 1.0);
}

View File

@ -0,0 +1,189 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace LearnOpenTK.Common
{
// A simple class meant to help create shaders.
public class Shader
{
public readonly int Handle;
private readonly Dictionary<string, int> _uniformLocations;
// This is how you create a simple shader.
// Shaders are written in GLSL, which is a language very similar to C in its semantics.
// The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on.
// A commented example of GLSL can be found in shader.vert.
public Shader(string vertPath, string fragPath)
{
// There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
// The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
// The vertex shader won't be too important here, but they'll be more important later.
// The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel.
// The fragment shader is what we'll be using the most here.
// Load vertex shader and compile
var shaderSource = File.ReadAllText(vertPath);
// GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
// Now, bind the GLSL source code
GL.ShaderSource(vertexShader, shaderSource);
// And then compile
CompileShader(vertexShader);
// We do the same for the fragment shader.
shaderSource = File.ReadAllText(fragPath);
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader, shaderSource);
CompileShader(fragmentShader);
// These two shaders must then be merged into a shader program, which can then be used by OpenGL.
// To do this, create a program...
Handle = GL.CreateProgram();
// Attach both shaders...
GL.AttachShader(Handle, vertexShader);
GL.AttachShader(Handle, fragmentShader);
// And then link them together.
LinkProgram(Handle);
// When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
// Detach them, and then delete them.
GL.DetachShader(Handle, vertexShader);
GL.DetachShader(Handle, fragmentShader);
GL.DeleteShader(fragmentShader);
GL.DeleteShader(vertexShader);
// The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
// Querying this from the shader is very slow, so we do it once on initialization and reuse those values
// later.
// First, we have to get the number of active uniforms in the shader.
GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);
// Next, allocate the dictionary to hold the locations.
_uniformLocations = new Dictionary<string, int>();
// Loop over all the uniforms,
for (var i = 0; i < numberOfUniforms; i++)
{
// get the name of this uniform,
var key = GL.GetActiveUniform(Handle, i, out _, out _);
// get the location,
var location = GL.GetUniformLocation(Handle, key);
// and then add it to the dictionary.
_uniformLocations.Add(key, location);
}
}
private static void CompileShader(int shader)
{
// Try to compile the shader
GL.CompileShader(shader);
// Check for compilation errors
GL.GetShader(shader, ShaderParameter.CompileStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
var infoLog = GL.GetShaderInfoLog(shader);
throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
}
}
private static void LinkProgram(int program)
{
// We link the program
GL.LinkProgram(program);
// Check for linking errors
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetProgramInfoLog(program)` to get information about the error.
throw new Exception($"Error occurred whilst linking Program({program})");
}
}
// A wrapper function that enables the shader program.
public void Use()
{
GL.UseProgram(Handle);
}
// The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically,
// you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values.
public int GetAttribLocation(string attribName)
{
return GL.GetAttribLocation(Handle, attribName);
}
// Uniform setters
// Uniforms are variables that can be set by user code, instead of reading them from the VBO.
// You use VBOs for vertex-related data, and uniforms for almost everything else.
// Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
// 1. Bind the program you want to set the uniform on
// 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
// 3. Use the appropriate GL.Uniform* function to set the uniform.
/// <summary>
/// Set a uniform int on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetInt(string name, int data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
/// <summary>
/// Set a uniform float on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetFloat(string name, float data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
/// <summary>
/// Set a uniform Matrix4 on this shader
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
/// <remarks>
/// <para>
/// The matrix is transposed before being sent to the shader.
/// </para>
/// </remarks>
public void SetMatrix4(string name, Matrix4 data)
{
GL.UseProgram(Handle);
GL.UniformMatrix4(_uniformLocations[name], true, ref data);
}
/// <summary>
/// Set a uniform Vector3 on this shader.
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <param name="data">The data to set</param>
public void SetVector3(string name, Vector3 data)
{
GL.UseProgram(Handle);
GL.Uniform3(_uniformLocations[name], data);
}
}
}

View File

@ -0,0 +1,215 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
using LearnOpenTK.Common;
namespace TheLabs
{
public class MyExampleWindow : GameWindow
{
// Create the vertices for our triangle. These are listed in normalized device coordinates (NDC)
// In NDC, (0, 0) is the center of the screen.
// Negative X coordinates move to the left, positive X move to the right.
// Negative Y coordinates move to the bottom, positive Y move to the top.
// OpenGL only supports rendering in 3D, so to create a flat triangle, the Z coordinate will be kept as 0.
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, // Bottom-left vertex
0.5f, -0.5f, 0.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f // Top vertex
};
// These are the handles to OpenGL objects. A handle is an integer representing where the object lives on the
// graphics card. Consider them sort of like a pointer; we can't do anything with them directly, but we can
// send them to OpenGL functions that need them.
// What these objects are will be explained in OnLoad.
private int _vertexBufferObject;
private int _vertexArrayObject;
// This class is a wrapper around a shader, which helps us manage it.
// The shader class's code is in the Common project.
// What shaders are and what they're used for will be explained later in this tutorial.
private Shader _shader;
public MyExampleWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
// Now, we start initializing OpenGL.
protected override void OnLoad()
{
base.OnLoad();
// This will be the color of the background after we clear it, in normalized colors.
// Normalized colors are mapped on a range of 0.0 to 1.0, with 0.0 representing black, and 1.0 representing
// the largest possible value for that channel.
// This is a deep green.
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// We need to send our vertices over to the graphics card so OpenGL can use them.
// To do this, we need to create what's called a Vertex Buffer Object (VBO).
// These allow you to upload a bunch of data to a buffer, and send the buffer to the graphics card.
// This effectively sends all the vertices at the same time.
// First, we need to create a buffer. This function returns a handle to it, but as of right now, it's empty.
_vertexBufferObject = GL.GenBuffer();
// Now, bind the buffer. OpenGL uses one global state, so after calling this,
// all future calls that modify the VBO will be applied to this buffer until another buffer is bound instead.
// The first argument is an enum, specifying what type of buffer we're binding. A VBO is an ArrayBuffer.
// There are multiple types of buffers, but for now, only the VBO is necessary.
// The second argument is the handle to our buffer.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
// Finally, upload the vertices to the buffer.
// Arguments:
// Which buffer the data should be sent to.
// How much data is being sent, in bytes. You can generally set this to the length of your array, multiplied by sizeof(array type).
// The vertices themselves.
// How the buffer will be used, so that OpenGL can write the data to the proper memory space on the GPU.
// There are three different BufferUsageHints for drawing:
// StaticDraw: This buffer will rarely, if ever, update after being initially uploaded.
// DynamicDraw: This buffer will change frequently after being initially uploaded.
// StreamDraw: This buffer will change on every frame.
// Writing to the proper memory space is important! Generally, you'll only want StaticDraw,
// but be sure to use the right one for your use case.
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
// One notable thing about the buffer we just loaded data into is that it doesn't have any structure to it. It's just a bunch of floats (which are actaully just bytes).
// The opengl driver doesn't know how this data should be interpreted or how it should be divided up into vertices. To do this opengl introduces the idea of a
// Vertex Array Obejct (VAO) which has the job of keeping track of what parts or what buffers correspond to what data. In this example we want to set our VAO up so that
// it tells opengl that we want to interpret 12 bytes as 3 floats and divide the buffer into vertices using that.
// To do this we generate and bind a VAO (which looks deceptivly similar to creating and binding a VBO, but they are different!).
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
// Now, we need to setup how the vertex shader will interpret the VBO data; you can send almost any C datatype (and a few non-C ones too) to it.
// While this makes them incredibly flexible, it means we have to specify how that data will be mapped to the shader's input variables.
// To do this, we use the GL.VertexAttribPointer function
// This function has two jobs, to tell opengl about the format of the data, but also to associate the current array buffer with the VAO.
// This means that after this call, we have setup this attribute to source data from the current array buffer and interpret it in the way we specified.
// Arguments:
// Location of the input variable in the shader. the layout(location = 0) line in the vertex shader explicitly sets it to 0.
// How many elements will be sent to the variable. In this case, 3 floats for every vertex.
// The data type of the elements set, in this case float.
// Whether or not the data should be converted to normalized device coordinates. In this case, false, because that's already done.
// The stride; this is how many bytes are between the last element of one vertex and the first element of the next. 3 * sizeof(float) in this case.
// The offset; this is how many bytes it should skip to find the first element of the first vertex. 0 as of right now.
// Stride and Offset are just sort of glossed over for now, but when we get into texture coordinates they'll be shown in better detail.
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
// Enable variable 0 in the shader.
GL.EnableVertexAttribArray(0);
// We've got the vertices done, but how exactly should this be converted to pixels for the final image?
// Modern OpenGL makes this pipeline very free, giving us a lot of freedom on how vertices are turned to pixels.
// The drawback is that we actually need two more programs for this! These are called "shaders".
// Shaders are tiny programs that live on the GPU. OpenGL uses them to handle the vertex-to-pixel pipeline.
// Check out the Shader class in Common to see how we create our shaders, as well as a more in-depth explanation of how shaders work.
// shader.vert and shader.frag contain the actual shader code.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
// Now, enable the shader.
// Just like the VBO, this is global, so every function that uses a shader will modify this one until a new one is bound instead.
_shader.Use();
// Setup is now complete! Now we move to the OnRenderFrame function to finally draw the triangle.
}
// Now that initialization is done, let's create our render loop.
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
// This clears the image, using what you set as GL.ClearColor earlier.
// OpenGL provides several different types of data that can be rendered.
// You can clear multiple buffers by using multiple bit flags.
// However, we only modify the color, so ColorBufferBit is all we need to clear.
GL.Clear(ClearBufferMask.ColorBufferBit);
// To draw an object in OpenGL, it's typically as simple as binding your shader,
// setting shader uniforms (not done here, will be shown in a future tutorial)
// binding the VAO,
// and then calling an OpenGL function to render.
// Bind the shader
_shader.Use();
// Bind the VAO
GL.BindVertexArray(_vertexArrayObject);
// And then call our drawing function.
// For this tutorial, we'll use GL.DrawArrays, which is a very simple rendering function.
// Arguments:
// Primitive type; What sort of geometric primitive the vertices represent.
// OpenGL used to support many different primitive types, but almost all of the ones still supported
// is some variant of a triangle. Since we just want a single triangle, we use Triangles.
// Starting index; this is just the start of the data you want to draw. 0 here.
// How many vertices you want to draw. 3 for a triangle.
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
// OpenTK windows are what's known as "double-buffered". In essence, the window manages two buffers.
// One is rendered to while the other is currently displayed by the window.
// This avoids screen tearing, a visual artifact that can happen if the buffer is modified while being displayed.
// After drawing, call this function to swap the buffers. If you don't, it won't display what you've rendered.
SwapBuffers();
// And that's all you have to do for rendering! You should now see a yellow triangle on a black screen.
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
// When the window gets resized, we have to call GL.Viewport to resize OpenGL's viewport to match the new size.
// If we don't, the NDC will no longer be correct.
GL.Viewport(0, 0, Size.X, Size.Y);
}
// Now, for cleanup.
// You should generally not do cleanup of opengl resources when exiting an application,
// as that is handled by the driver and operating system when the application exits.
//
// There are reasons to delete opengl resources, but exiting the application is not one of them.
// This is provided here as a reference on how resource cleanup is done in opengl, but
// should not be done when exiting the application.
//
// Places where cleanup is appropriate would be: to delete textures that are no
// longer used for whatever reason (e.g. a new scene is loaded that doesn't use a texture).
// This would free up video ram (VRAM) that can be used for new textures.
//
// The coming chapters will not have this code.
protected override void OnUnload()
{
// Unbind all the resources by binding the targets to 0/null.
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all the resources.
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shader.Handle);
base.OnUnload();
}
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using OpenTK.Mathematics;
public class MyMatrix
{
public MyMatrix(float pRow0Column0,
float pRow0Column1,
float pRow0Column2,
float pRow0Column3,
float pRow1Column0,
float pRow1Column1,
float pRow1Column2,
float pRow1Column3,
float pRow2Column0,
float pRow2Column1,
float pRow2Column2,
float pRow2Column3,
float pRow3Column0,
float pRow3Column1,
float pRow3Column2,
float pRow3Column3)
{
}
public float GetElement(int pRow, int pColumn)
{
return -1;
}
public void SetElement(int pRow, int pColumn, float pValue)
{
}
public static MyMatrix CreateIdentity()
{
return null;
}
public static MyMatrix CreateTranslation(MyVector pTranslation)
{
return null;
}
public static MyMatrix CreateScale(MyVector pScale)
{
return null;
}
public static MyMatrix CreateRotationX(float pAngle)
{
return null;
}
public static MyMatrix CreateRotationY(float pAngle)
{
return null;
}
public static MyMatrix CreateRotationZ(float pAngle)
{
return null;
}
public MyVector Multiply(MyVector pVector)
{
return null;
}
public MyMatrix Multiply(MyMatrix pMatrix)
{
return null;
}
public MyMatrix Inverse()
{
return null;
}
// this method takes our MyMatrix and converts it to an OpenTK Matrix4
// this looks a little odd as the OpenTK Matrix4 constructor takes the elements in row major order
public Matrix4 ToMatrix4()
{
return Matrix4.Identity;
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
public class MyVector
{
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
// this W component has only been included to make it easier to do Matrix multiplications
public float W { get; private set; }
public MyVector(float pX, float pY, float pZ, float pW = 1)
{
}
public MyVector Add(MyVector pVector)
{
return null;
}
public MyVector Subtract(MyVector pVector)
{
return null;
}
public MyVector Multiply(float pScalar)
{
return null;
}
public MyVector Divide(float pScalar)
{
return null;
}
public float Magnitude()
{
return -1;
}
public MyVector Normalise()
{
return null;
}
public float DotProduct(MyVector pVector)
{
return -1;
}
public MyVector Interpolate(MyVector pVector, float pInterpolation)
{
return null;
}
public float AngleBetween(MyVector pVector)
{
return -1;
}
public MyVector CrossProduct(MyVector pVector)
{
return null;
}
}

View File

@ -0,0 +1,24 @@
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using System;
namespace TheLabs
{
public static class Program
{
public static void Main()
{
var nativeWindowSettings = new NativeWindowSettings
{
Size = new Vector2i(800, 600),
Title = "My OpenTK Example Program"
};
using (var window = new MyExampleWindow(GameWindowSettings.Default,
nativeWindowSettings))
{
window.Run();
}
}
}
}

View File

@ -0,0 +1,8 @@
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0, 1.0, 0.0, 1.0);
}

View File

@ -0,0 +1,41 @@
// For more information on how shaders work, check out the web version of this tutorial.
// I'll include a simpler summary here.
// First non-comment line should always be a #version statement; this just tells the GLSL compiler what version it should use.
#version 330 core
// GLSL's syntax is somewhat like C, but it has a few differences.
// There are four different types of variables in GLSL: input, output, uniform, and internal.
// - Input variables are sent from the buffer, in a way defined by GL.VertexAttribPointer.
// - Output variables are sent from this shader to the next one in the chain (which will be the fragment shader most of the time).
// - Uniforms will be touched on in the next tutorial.
// - Internal variables are defined in the shader file and only used there.
// The vertex shader is run once for every vertex. In C# pseudocode, it might look something like:
// foreach(var vertex in vertices)
// shader(vertex)
// This defines our input variable, aPosition.
// It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer.
// However, you can omit it, and replace this with just "in vec3 aPosition". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with
// a call to GL.GetAttribLocation(shaderHandle, attributeName)
// Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial.
// Then, the keyword "vec3" means this is a vector with 3 floats inside.
layout(location = 0) in vec3 aPosition;
// Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main.
// You can do all sorts of calculations here to modify your vertices, but right now, we don't need to do any of that.
// gl_Position is the final vertex position; pass a vec4 to it and you're done.
// Keep in mind that we only pass a vec3 to this shader; the fourth component of a vertex is known as "w".
// It's only used in some more advanced OpenGL functions; it's not needed here.
// So with a call to the vec4 function, we just give it a constant value of 1.0.
void main(void)
{
gl_Position = vec4(aPosition, 1.0);
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Shaders\" />
</ItemGroup>
<ItemGroup>
<None Update="Shaders\shader.frag">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Shaders\shader.vert">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Link="README.md" />
</ItemGroup>
</Project>

37
TheRepo/TheRepo.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34302.85
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheLabs", "TheLabs\TheLabs.csproj", "{4761B222-F037-4EE0-815E-0264545C5393}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorAndMatrixTests", "VectorAndMatrixTests\VectorAndMatrixTests.csproj", "{D138F1AF-D5B0-442A-BECB-93DB7D6BD4C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoWhateverYouWant", "DoWhateverYouWant\DoWhateverYouWant.csproj", "{E59FCA07-9BE4-096B-6CD4-DAC39A9BC7B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4761B222-F037-4EE0-815E-0264545C5393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4761B222-F037-4EE0-815E-0264545C5393}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4761B222-F037-4EE0-815E-0264545C5393}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4761B222-F037-4EE0-815E-0264545C5393}.Release|Any CPU.Build.0 = Release|Any CPU
{D138F1AF-D5B0-442A-BECB-93DB7D6BD4C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D138F1AF-D5B0-442A-BECB-93DB7D6BD4C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D138F1AF-D5B0-442A-BECB-93DB7D6BD4C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D138F1AF-D5B0-442A-BECB-93DB7D6BD4C9}.Release|Any CPU.Build.0 = Release|Any CPU
{E59FCA07-9BE4-096B-6CD4-DAC39A9BC7B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E59FCA07-9BE4-096B-6CD4-DAC39A9BC7B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E59FCA07-9BE4-096B-6CD4-DAC39A9BC7B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E59FCA07-9BE4-096B-6CD4-DAC39A9BC7B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AC8A53CE-EB86-4B6B-B38B-00D843404282}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,400 @@
using OpenTK.Mathematics;
namespace VectorAndMatrixTests
{
[TestClass]
public class MatrixTests
{
[TestMethod]
public void Constructor()
{
// Use the Assert class to test conditions
MyMatrix myMatrix = new MyMatrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Matrix4 matrix4 = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateIdentity()
{
// Use the Assert class to test conditions
MyMatrix myMatrix = MyMatrix.CreateIdentity();
Matrix4 matrix4 = Matrix4.Identity;
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateTranslation()
{
// Use the Assert class to test conditions
MyVector translationVector = new MyVector(30, 40, 50);
MyMatrix myMatrix = MyMatrix.CreateTranslation(translationVector);
Matrix4 matrix4 = Matrix4.CreateTranslation(translationVector.X, translationVector.Y, translationVector.Z);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateScale()
{
// Use the Assert class to test conditions
MyVector scaleVector = new MyVector(30, 40, 50);
MyMatrix myMatrix = MyMatrix.CreateScale(scaleVector);
Matrix4 matrix4 = Matrix4.CreateScale(scaleVector.X, scaleVector.Y, scaleVector.Z);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateRotationX()
{
// Use the Assert class to test conditions
float angle = MathF.PI/4;
float cosAngle = MathF.Cos(angle);
float sinAngle = MathF.Sin(angle);
MyMatrix myMatrix = MyMatrix.CreateRotationX(angle);
Matrix4 matrix4 = Matrix4.CreateRotationX(angle);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateRotationY()
{
// Use the Assert class to test conditions
float angle = MathF.PI/4;
float cosAngle = MathF.Cos(angle);
float sinAngle = MathF.Sin(angle);
MyMatrix myMatrix = MyMatrix.CreateRotationY(angle);
Matrix4 matrix4 = Matrix4.CreateRotationY(angle);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void CreateRotationZ()
{
// Use the Assert class to test conditions
float angle = MathF.PI / 4;
float cosAngle = MathF.Cos(angle);
float sinAngle = MathF.Sin(angle);
MyMatrix myMatrix = MyMatrix.CreateRotationZ(angle);
Matrix4 matrix4 = Matrix4.CreateRotationZ(angle);
Assert.AreEqual(matrix4.M11, myMatrix.GetElement(0, 0));
Assert.AreEqual(matrix4.M12, myMatrix.GetElement(0, 1));
Assert.AreEqual(matrix4.M13, myMatrix.GetElement(0, 2));
Assert.AreEqual(matrix4.M14, myMatrix.GetElement(0, 3));
Assert.AreEqual(matrix4.M21, myMatrix.GetElement(1, 0));
Assert.AreEqual(matrix4.M22, myMatrix.GetElement(1, 1));
Assert.AreEqual(matrix4.M23, myMatrix.GetElement(1, 2));
Assert.AreEqual(matrix4.M24, myMatrix.GetElement(1, 3));
Assert.AreEqual(matrix4.M31, myMatrix.GetElement(2, 0));
Assert.AreEqual(matrix4.M32, myMatrix.GetElement(2, 1));
Assert.AreEqual(matrix4.M33, myMatrix.GetElement(2, 2));
Assert.AreEqual(matrix4.M34, myMatrix.GetElement(2, 3));
Assert.AreEqual(matrix4.M41, myMatrix.GetElement(3, 0));
Assert.AreEqual(matrix4.M42, myMatrix.GetElement(3, 1));
Assert.AreEqual(matrix4.M43, myMatrix.GetElement(3, 2));
Assert.AreEqual(matrix4.M44, myMatrix.GetElement(3, 3));
}
[TestMethod]
public void MultiplyVector()
{
// Use the Assert class to test conditions
MyVector myVector = new MyVector(30, 40, 0);
MyVector translationVector = new MyVector(10, 20, 30);
MyMatrix translationMatrix = MyMatrix.CreateTranslation(translationVector);
MyVector translatedVector = translationMatrix.Multiply(myVector);
Matrix4 translationMatrix4 = Matrix4.CreateTranslation(translationVector.X, translationVector.Y, translationVector.Z);
Vector4 vector4 = new Vector4(myVector.X, myVector.Y, myVector.Z, myVector.W);
Vector4 translatedVector4 = translationMatrix4 * vector4;
Assert.AreEqual(translatedVector4.X, translatedVector.X);
Assert.AreEqual(translatedVector4.Y, translatedVector.Y);
Assert.AreEqual(translatedVector4.Z, translatedVector.Z);
Assert.AreEqual(translatedVector4.W, translatedVector.W);
MyVector scaleVector = new MyVector(2, 2, 2);
MyMatrix scaleMatrix = MyMatrix.CreateScale(scaleVector);
MyVector scaledVector = scaleMatrix.Multiply(myVector);
Matrix4 scaleMatrix4 = Matrix4.CreateScale(scaleVector.X, scaleVector.Y, scaleVector.Z);
vector4 = new Vector4(myVector.X, myVector.Y, myVector.Z, myVector.W);
Vector4 scaledVector4 = scaleMatrix4 * vector4;
Assert.AreEqual(scaledVector4.X, scaledVector.X);
Assert.AreEqual(scaledVector4.Y, scaledVector.Y);
Assert.AreEqual(scaledVector4.Z, scaledVector.Z);
Assert.AreEqual(scaledVector4.W, scaledVector.W);
float angle = MathF.PI / 4;
MyMatrix rotationMatrix = MyMatrix.CreateRotationZ(angle);
MyVector rotatedVector = rotationMatrix.Multiply(myVector);
Matrix4 rotationMatrix4 = Matrix4.CreateRotationZ(angle);
vector4 = new Vector4(myVector.X, myVector.Y, myVector.Z, myVector.W);
Vector4 rotatedVector4 = rotationMatrix4 * vector4;
Assert.AreEqual(rotatedVector4.X, rotatedVector.X);
Assert.AreEqual(rotatedVector4.Y, rotatedVector.Y);
Assert.AreEqual(rotatedVector4.Z, rotatedVector.Z);
Assert.AreEqual(rotatedVector4.W, rotatedVector.W);
}
[TestMethod]
public void Multiply()
{
MyVector myVector = new MyVector(30, 40, 0);
MyVector translationVector = new MyVector(10, 20, 30);
MyMatrix translationMatrix = MyMatrix.CreateTranslation(translationVector);
float angle = MathF.PI / 4;
MyMatrix rotationMatrix = MyMatrix.CreateRotationZ(angle);
MyVector scaleVector = new MyVector(2, 2, 2);
MyMatrix scaleMatrix = MyMatrix.CreateScale(scaleVector);
Matrix4 translationMatrix4 = Matrix4.CreateTranslation(translationVector.X, translationVector.Y, translationVector.Z);
Matrix4 rotationMatrix4 = Matrix4.CreateRotationZ(angle);
Matrix4 scaleMatrix4 = Matrix4.CreateScale(scaleVector.X, scaleVector.Y, scaleVector.Z);
MyMatrix scaleXTranslationMatrix = scaleMatrix.Multiply(translationMatrix);
Matrix4 scaleXTranslationMatrix4 = scaleMatrix4 * translationMatrix4;
Assert.AreEqual(scaleXTranslationMatrix4.M11, scaleXTranslationMatrix.GetElement(0, 0));
Assert.AreEqual(scaleXTranslationMatrix4.M12, scaleXTranslationMatrix.GetElement(0, 1));
Assert.AreEqual(scaleXTranslationMatrix4.M13, scaleXTranslationMatrix.GetElement(0, 2));
Assert.AreEqual(scaleXTranslationMatrix4.M14, scaleXTranslationMatrix.GetElement(0, 3));
Assert.AreEqual(scaleXTranslationMatrix4.M21, scaleXTranslationMatrix.GetElement(1, 0));
Assert.AreEqual(scaleXTranslationMatrix4.M22, scaleXTranslationMatrix.GetElement(1, 1));
Assert.AreEqual(scaleXTranslationMatrix4.M23, scaleXTranslationMatrix.GetElement(1, 2));
Assert.AreEqual(scaleXTranslationMatrix4.M24, scaleXTranslationMatrix.GetElement(1, 3));
Assert.AreEqual(scaleXTranslationMatrix4.M31, scaleXTranslationMatrix.GetElement(2, 0));
Assert.AreEqual(scaleXTranslationMatrix4.M32, scaleXTranslationMatrix.GetElement(2, 1));
Assert.AreEqual(scaleXTranslationMatrix4.M33, scaleXTranslationMatrix.GetElement(2, 2));
Assert.AreEqual(scaleXTranslationMatrix4.M34, scaleXTranslationMatrix.GetElement(2, 3));
Assert.AreEqual(scaleXTranslationMatrix4.M41, scaleXTranslationMatrix.GetElement(3, 0));
Assert.AreEqual(scaleXTranslationMatrix4.M42, scaleXTranslationMatrix.GetElement(3, 1));
Assert.AreEqual(scaleXTranslationMatrix4.M43, scaleXTranslationMatrix.GetElement(3, 2));
Assert.AreEqual(scaleXTranslationMatrix4.M44, scaleXTranslationMatrix.GetElement(3, 3));
MyMatrix translationXScaleMatrix = translationMatrix.Multiply(scaleMatrix);
Matrix4 translationXScaleMatrix4 = translationMatrix4 * scaleMatrix4;
Assert.AreEqual(translationXScaleMatrix4.M11, translationXScaleMatrix.GetElement(0, 0));
Assert.AreEqual(translationXScaleMatrix4.M12, translationXScaleMatrix.GetElement(0, 1));
Assert.AreEqual(translationXScaleMatrix4.M13, translationXScaleMatrix.GetElement(0, 2));
Assert.AreEqual(translationXScaleMatrix4.M14, translationXScaleMatrix.GetElement(0, 3));
Assert.AreEqual(translationXScaleMatrix4.M21, translationXScaleMatrix.GetElement(1, 0));
Assert.AreEqual(translationXScaleMatrix4.M22, translationXScaleMatrix.GetElement(1, 1));
Assert.AreEqual(translationXScaleMatrix4.M23, translationXScaleMatrix.GetElement(1, 2));
Assert.AreEqual(translationXScaleMatrix4.M24, translationXScaleMatrix.GetElement(1, 3));
Assert.AreEqual(translationXScaleMatrix4.M31, translationXScaleMatrix.GetElement(2, 0));
Assert.AreEqual(translationXScaleMatrix4.M32, translationXScaleMatrix.GetElement(2, 1));
Assert.AreEqual(translationXScaleMatrix4.M33, translationXScaleMatrix.GetElement(2, 2));
Assert.AreEqual(translationXScaleMatrix4.M34, translationXScaleMatrix.GetElement(2, 3));
Assert.AreEqual(translationXScaleMatrix4.M41, translationXScaleMatrix.GetElement(3, 0));
Assert.AreEqual(translationXScaleMatrix4.M42, translationXScaleMatrix.GetElement(3, 1));
Assert.AreEqual(translationXScaleMatrix4.M43, translationXScaleMatrix.GetElement(3, 2));
Assert.AreEqual(translationXScaleMatrix4.M44, translationXScaleMatrix.GetElement(3, 3));
MyMatrix chainedMatrix = translationMatrix.Multiply(scaleMatrix).Multiply(rotationMatrix);
Matrix4 chainedMatrix4 = translationMatrix4 * scaleMatrix4 * rotationMatrix4;
float cosAngle = MathF.Cos(angle);
float sinAngle = MathF.Sin(angle);
Assert.AreEqual(chainedMatrix4.M11, chainedMatrix.GetElement(0, 0));
Assert.AreEqual(chainedMatrix4.M12, chainedMatrix.GetElement(0, 1));
Assert.AreEqual(chainedMatrix4.M13, chainedMatrix.GetElement(0, 2));
Assert.AreEqual(chainedMatrix4.M14, chainedMatrix.GetElement(0, 3));
Assert.AreEqual(chainedMatrix4.M21, chainedMatrix.GetElement(1, 0));
Assert.AreEqual(chainedMatrix4.M22, chainedMatrix.GetElement(1, 1));
Assert.AreEqual(chainedMatrix4.M23, chainedMatrix.GetElement(1, 2));
Assert.AreEqual(chainedMatrix4.M24, chainedMatrix.GetElement(1, 3));
Assert.AreEqual(chainedMatrix4.M31, chainedMatrix.GetElement(2, 0));
Assert.AreEqual(chainedMatrix4.M32, chainedMatrix.GetElement(2, 1));
Assert.AreEqual(chainedMatrix4.M33, chainedMatrix.GetElement(2, 2));
Assert.AreEqual(chainedMatrix4.M34, chainedMatrix.GetElement(2, 3));
Assert.AreEqual(chainedMatrix4.M41, chainedMatrix.GetElement(3, 0));
Assert.AreEqual(chainedMatrix4.M42, chainedMatrix.GetElement(3, 1));
Assert.AreEqual(chainedMatrix4.M43, chainedMatrix.GetElement(3, 2));
Assert.AreEqual(chainedMatrix4.M44, chainedMatrix.GetElement(3, 3));
}
[TestMethod]
public void Inverse()
{
MyVector translationVector = new MyVector(20, 10, 5);
MyMatrix translationMatrix = MyMatrix.CreateTranslation(translationVector);
MyVector scaleVector = new MyVector(2, 2, 2);
MyMatrix scaleMatrix = MyMatrix.CreateScale(scaleVector);
float angle = MathF.PI / 2;
MyMatrix rotationZMatrix = MyMatrix.CreateRotationZ(angle);
MyMatrix multipliedMatrix = translationMatrix.Multiply(scaleMatrix).Multiply(rotationZMatrix);
MyMatrix inverseMatrix = multipliedMatrix.Inverse();
MyMatrix identityMatrix = multipliedMatrix.Multiply(inverseMatrix);
Assert.AreEqual(1, identityMatrix.GetElement(0, 0), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(0, 1), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(0, 2), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(0, 3), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(1, 0), 0.001);
Assert.AreEqual(1, identityMatrix.GetElement(1, 1), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(1, 2), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(1, 3), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(2, 0), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(2, 1), 0.001);
Assert.AreEqual(1, identityMatrix.GetElement(2, 2), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(2, 3), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(3, 0), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(3, 1), 0.001);
Assert.AreEqual(0, identityMatrix.GetElement(3, 2), 0.001);
Assert.AreEqual(1, identityMatrix.GetElement(3, 3), 0.001);
Matrix4 translationMatrix4 = Matrix4.CreateTranslation(translationVector.X, translationVector.Y, translationVector.Z);
Matrix4 scaleMatrix4 = Matrix4.CreateScale(scaleVector.X, scaleVector.Y, scaleVector.Z);
Matrix4 rotationZMatrix4 = Matrix4.CreateRotationZ(angle);
Matrix4 multipliedMatrix4 = translationMatrix4 * scaleMatrix4 * rotationZMatrix4;
Matrix4 inverseMatrix4 = Matrix4.Invert(multipliedMatrix4);
Assert.AreEqual(inverseMatrix4.M11, inverseMatrix.GetElement(0, 0));
Assert.AreEqual(inverseMatrix4.M12, inverseMatrix.GetElement(0, 1));
Assert.AreEqual(inverseMatrix4.M13, inverseMatrix.GetElement(0, 2));
Assert.AreEqual(inverseMatrix4.M14, inverseMatrix.GetElement(0, 3));
Assert.AreEqual(inverseMatrix4.M21, inverseMatrix.GetElement(1, 0));
Assert.AreEqual(inverseMatrix4.M22, inverseMatrix.GetElement(1, 1));
Assert.AreEqual(inverseMatrix4.M23, inverseMatrix.GetElement(1, 2));
Assert.AreEqual(inverseMatrix4.M24, inverseMatrix.GetElement(1, 3));
Assert.AreEqual(inverseMatrix4.M31, inverseMatrix.GetElement(2, 0));
Assert.AreEqual(inverseMatrix4.M32, inverseMatrix.GetElement(2, 1));
Assert.AreEqual(inverseMatrix4.M33, inverseMatrix.GetElement(2, 2));
Assert.AreEqual(inverseMatrix4.M34, inverseMatrix.GetElement(2, 3));
Assert.AreEqual(inverseMatrix4.M41, inverseMatrix.GetElement(3, 0));
Assert.AreEqual(inverseMatrix4.M42, inverseMatrix.GetElement(3, 1));
Assert.AreEqual(inverseMatrix4.M43, inverseMatrix.GetElement(3, 2));
Assert.AreEqual(inverseMatrix4.M44, inverseMatrix.GetElement(3, 3));
}
[TestMethod]
public void ToMatrix4()
{
MyMatrix myMatrix = new MyMatrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Matrix4 matrix4 = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Matrix4 matrix4Convert = myMatrix.ToMatrix4();
Assert.AreEqual(matrix4.M11, matrix4Convert.M11);
Assert.AreEqual(matrix4.M12, matrix4Convert.M12);
Assert.AreEqual(matrix4.M13, matrix4Convert.M13);
Assert.AreEqual(matrix4.M14, matrix4Convert.M14);
Assert.AreEqual(matrix4.M21, matrix4Convert.M21);
Assert.AreEqual(matrix4.M22, matrix4Convert.M22);
Assert.AreEqual(matrix4.M23, matrix4Convert.M23);
Assert.AreEqual(matrix4.M24, matrix4Convert.M24);
Assert.AreEqual(matrix4.M31, matrix4Convert.M31);
Assert.AreEqual(matrix4.M32, matrix4Convert.M32);
Assert.AreEqual(matrix4.M33, matrix4Convert.M33);
Assert.AreEqual(matrix4.M34, matrix4Convert.M34);
Assert.AreEqual(matrix4.M41, matrix4Convert.M41);
Assert.AreEqual(matrix4.M42, matrix4Convert.M42);
Assert.AreEqual(matrix4.M43, matrix4Convert.M43);
Assert.AreEqual(matrix4.M44, matrix4Convert.M44);
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TheLabs\TheLabs.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,218 @@
using OpenTK.Mathematics;
namespace VectorAndMatrixTests
{
[TestClass]
public class VectorTests
{
[TestMethod]
public void Constructor()
{
// Use the Assert class to test conditions
MyVector myVector = new MyVector(30, 40, 0);
Vector3 Vector3 = new Vector3(30, 40, 0);
Assert.AreEqual(Vector3.X, myVector.X);
Assert.AreEqual(Vector3.Y, myVector.Y);
Assert.AreEqual(Vector3.Z, myVector.Z);
}
[TestMethod]
public void Add()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector secondVector = new MyVector(20, 30, 0);
MyVector thirdVector = firstVector.Add(secondVector);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = new Vector3(20, 30, 0);
Vector3 thirdVector3 = firstVector3 + secondVector3;
Assert.AreEqual(thirdVector3.X, thirdVector.X);
Assert.AreEqual(thirdVector3.Y, thirdVector.Y);
Assert.AreEqual(thirdVector3.Z, thirdVector.Z);
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
[TestMethod]
public void Subtract()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector secondVector = new MyVector(5, 10, 0);
MyVector thirdVector = firstVector.Subtract(secondVector);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = new Vector3(5, 10, 0);
Vector3 thirdVector3 = firstVector3 - secondVector3;
Assert.AreEqual(thirdVector3.X, thirdVector.X);
Assert.AreEqual(thirdVector3.Y, thirdVector.Y);
Assert.AreEqual(thirdVector3.Z, thirdVector.Z);
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
[TestMethod]
public void Multiply()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
float scalar = 10;
MyVector secondVector = firstVector.Multiply(scalar);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = firstVector3 * scalar;
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
[TestMethod]
public void Divide()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
float scalar = 10;
MyVector secondVector = firstVector.Divide(scalar);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = firstVector3 / scalar;
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
[TestMethod]
public void Magnitude()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
float magnitude = firstVector.Magnitude();
Vector3 firstVector3 = new Vector3(30, 40, 0);
float magnitude4 = firstVector3.Length;
Assert.AreEqual(magnitude4, magnitude);
}
[TestMethod]
public void Normalise()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector normalisedVector = firstVector.Normalise();
float magnitude = normalisedVector.Magnitude();
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 normalisedVector3 = Vector3.Normalize(firstVector3);
float magnitude4 = normalisedVector3.Length;
Assert.AreEqual(normalisedVector3.X, normalisedVector.X, 0.001);
Assert.AreEqual(normalisedVector3.Y, normalisedVector.Y, 0.001);
Assert.AreEqual(normalisedVector3.Z, normalisedVector.Z, 0.001);
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
}
[TestMethod]
public void DotProduct()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, -10);
MyVector secondVector = new MyVector(40, -20, 40);
float dotProduct = firstVector.DotProduct(secondVector);
Vector3 firstVector3 = new Vector3(30, 40, -10);
Vector3 secondVector3 = new Vector3(40, -20, 40);
float dotProduct4 = Vector3.Dot(firstVector3, secondVector3);
Assert.AreEqual(dotProduct4, dotProduct);
}
[TestMethod]
public void Interpolate()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector secondVector = new MyVector(60, 80, 0);
float interpolation = 0.25f;
MyVector interpolatedVector = firstVector.Interpolate(secondVector, interpolation);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = new Vector3(60, 80, 0);
Vector3 interpolatedVector3 = Vector3.Lerp(firstVector3, secondVector3, interpolation);
Assert.AreEqual(interpolatedVector3.X, interpolatedVector.X);
Assert.AreEqual(interpolatedVector3.Y, interpolatedVector.Y);
Assert.AreEqual(interpolatedVector3.Z, interpolatedVector.Z);
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
[TestMethod]
public void AngleBetween()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector secondVector = new MyVector(-40, 30, 0);
float angleBetween = firstVector.AngleBetween(secondVector);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = new Vector3(-40, 30, 0);
float angleBetween3 = Vector3.CalculateAngle(firstVector3, secondVector3);
Assert.AreEqual(angleBetween3, angleBetween, 0.001);
}
[TestMethod]
public void CrossProduct()
{
// Use the Assert class to test conditions
MyVector firstVector = new MyVector(30, 40, 0);
MyVector secondVector = new MyVector(-40, 30, 0);
MyVector crossProduct = firstVector.CrossProduct(secondVector);
Vector3 firstVector3 = new Vector3(30, 40, 0);
Vector3 secondVector3 = new Vector3(-40, 30, 0);
Vector3 crossProduct3 = Vector3.Cross(firstVector3, secondVector3);
Assert.AreEqual(crossProduct3.X, crossProduct.X);
Assert.AreEqual(crossProduct3.Y, crossProduct.Y);
Assert.AreEqual(crossProduct3.Z, crossProduct.Z);
Assert.AreEqual(firstVector3.X, firstVector.X);
Assert.AreEqual(firstVector3.Y, firstVector.Y);
Assert.AreEqual(firstVector3.Z, firstVector.Z);
Assert.AreEqual(secondVector3.X, secondVector.X);
Assert.AreEqual(secondVector3.Y, secondVector.Y);
Assert.AreEqual(secondVector3.Z, secondVector.Z);
}
}
}

BIN
images/busses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB