├── README.md ├── infogr-raytracer ├── assets │ ├── font.png │ └── container.png ├── Light.cs ├── packages.config ├── shaders │ ├── shader.frag │ ├── raytrace-shader.vert │ ├── shader.vert │ └── raytrace-shader.frag ├── Scene.cs ├── Program.cs ├── IGameObject.cs ├── Ray.cs ├── Properties │ └── AssemblyInfo.cs ├── OpenTK.dll.config ├── Camera.cs ├── Circle.cs ├── Texture.cs ├── ShaderGame.cs ├── Shader.cs ├── Window.cs ├── Rectangle.cs ├── infogr-raytracer.csproj ├── Game.cs └── Surface.cs ├── instructions └── INFOGR_P1_2020_Raytracing.pdf ├── Tests ├── Tests.csproj ├── RectangleTest.cs └── CircleTest.cs ├── infogr-raytracer.sln └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # infogr-raytracer 2 | INFOGR ray tracing practicum 3 | -------------------------------------------------------------------------------- /infogr-raytracer/assets/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-raytracer/master/infogr-raytracer/assets/font.png -------------------------------------------------------------------------------- /infogr-raytracer/assets/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-raytracer/master/infogr-raytracer/assets/container.png -------------------------------------------------------------------------------- /instructions/INFOGR_P1_2020_Raytracing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-raytracer/master/instructions/INFOGR_P1_2020_Raytracing.pdf -------------------------------------------------------------------------------- /infogr-raytracer/Light.cs: -------------------------------------------------------------------------------- 1 | using OpenTK; 2 | using OpenTK.Graphics; 3 | 4 | namespace infogr_raytracer 5 | { 6 | public struct Light 7 | { 8 | public Vector2 Position; 9 | public Vector3 Color; 10 | } 11 | } -------------------------------------------------------------------------------- /infogr-raytracer/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /infogr-raytracer/shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture0; 8 | 9 | void main() 10 | { 11 | outputColor = texture(texture0, texCoord); 12 | } -------------------------------------------------------------------------------- /infogr-raytracer/Scene.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenTK; 3 | 4 | namespace infogr_raytracer 5 | { 6 | public class Scene 7 | { 8 | public List Lights; 9 | public List GameObjects; 10 | } 11 | } -------------------------------------------------------------------------------- /infogr-raytracer/shaders/raytrace-shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 vertexPosition; 3 | 4 | out vec2 screenPosition; 5 | 6 | void main() 7 | { 8 | gl_Position = vec4(vertexPosition, 1.0); 9 | 10 | screenPosition = vertexPosition.xy; 11 | } -------------------------------------------------------------------------------- /infogr-raytracer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace infogr_raytracer 2 | { 3 | internal class Program 4 | { 5 | public static void Main(string[] args) 6 | { 7 | using (Window window = new Window(800, 800, "INFOGR Raytracer")) 8 | { 9 | window.Run(60.0); 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /infogr-raytracer/shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec3 aPosition; 4 | layout (location = 1) in vec2 aTexCoord; 5 | 6 | out vec2 texCoord; 7 | 8 | void main(void) 9 | { 10 | // pass the input texture coordinate to the fragment shader 11 | texCoord = aTexCoord; 12 | 13 | gl_Position = vec4(aPosition, 1.0); 14 | } -------------------------------------------------------------------------------- /infogr-raytracer/IGameObject.cs: -------------------------------------------------------------------------------- 1 | using OpenTK; 2 | 3 | namespace infogr_raytracer 4 | { 5 | public interface IGameObject 6 | { 7 | Vector2 Position { get; } 8 | void Move(Vector2 position); 9 | 10 | void MoveBy(Vector2 translation); 11 | 12 | bool Intersects(Ray ray); 13 | 14 | // TODO: 15 | // Vector3 Trace(Ray ray); 16 | } 17 | } -------------------------------------------------------------------------------- /infogr-raytracer/Ray.cs: -------------------------------------------------------------------------------- 1 | using OpenTK; 2 | 3 | namespace infogr_raytracer 4 | { 5 | public struct Ray 6 | { 7 | private Vector2 _direction; 8 | 9 | /// 10 | /// The origin of our ray. 11 | /// 12 | public Vector2 Origin; 13 | 14 | /// 15 | /// The direction component of our ray. Is normalized when set. 16 | /// 17 | public Vector2 Direction 18 | { 19 | get => _direction; 20 | set => _direction = value.Normalized(); 21 | } 22 | 23 | /// 24 | /// The magnitude of this ray 25 | /// 26 | public float Magnitude; 27 | } 28 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /infogr-raytracer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "infogr-raytracer", "infogr-raytracer\infogr-raytracer.csproj", "{077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{366D4B29-2926-462C-B78C-129501E5519E}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {366D4B29-2926-462C-B78C-129501E5519E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {366D4B29-2926-462C-B78C-129501E5519E}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {366D4B29-2926-462C-B78C-129501E5519E}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {366D4B29-2926-462C-B78C-129501E5519E}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /infogr-raytracer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("infogr_raytracer")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("infogr_raytracer")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /infogr-raytracer/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /infogr-raytracer/Camera.cs: -------------------------------------------------------------------------------- 1 | using OpenTK; 2 | 3 | namespace infogr_raytracer 4 | { 5 | public struct Camera 6 | { 7 | /// 8 | /// The Camera's position (in world space) 9 | /// 10 | public Vector2 Position; 11 | 12 | private Vector2 _viewPort; 13 | private Vector2 _screenResolution; 14 | private float _screenSizeWorldSizeRatio; 15 | 16 | /// 17 | /// Convert a screen space coordinate to a world space coordinate 18 | /// Takes the current position into account 19 | /// 20 | /// The X component of the screen space coordinate 21 | /// The Y component of the screen space coordinate 22 | /// The screen space coordinate 23 | public Vector2 WorldSpace(int screenSpaceX, int screenSpaceY) 24 | { 25 | // float ratio = ((float) ViewPort.X / (float) ScreenResolution.X); 26 | return new Vector2( 27 | (float) screenSpaceX * _screenSizeWorldSizeRatio, 28 | (float) _viewPort.Y - screenSpaceY * _screenSizeWorldSizeRatio 29 | ) + Position; 30 | } 31 | 32 | /// 33 | /// Handle screen resizing by adjusting the screen resolution, viewport and world size ratio 34 | /// 35 | /// The new screen width 36 | /// The new screen height 37 | public void Resize(int screenWidth, int screenHeight) 38 | { 39 | float pixelsPerWorldSpaceUnit = 100; 40 | _screenResolution = new Vector2(screenWidth, screenHeight); 41 | _viewPort = new Vector2((float) screenWidth / pixelsPerWorldSpaceUnit, 42 | (float) screenHeight / pixelsPerWorldSpaceUnit); 43 | 44 | _screenSizeWorldSizeRatio = ((float) _viewPort.X / (float) _screenResolution.X); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /infogr-raytracer/Circle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK; 3 | 4 | namespace infogr_raytracer 5 | { 6 | public struct Circle: IGameObject 7 | { 8 | public Vector2 Position { get; set; } 9 | public float Radius; 10 | 11 | /// 12 | /// Move a circle to a new position. 13 | /// 14 | /// The circle's new position. 15 | public void Move(Vector2 position) 16 | { 17 | Position = position; 18 | } 19 | 20 | /// 21 | /// Translate a circle. 22 | /// 23 | /// The translation by which the circle's position should be altered 24 | public void MoveBy(Vector2 translation) 25 | { 26 | Move(translation + Position); 27 | } 28 | 29 | /// 30 | /// Return if this circle intersects a ray. 31 | /// Uses the ABC formula. 32 | /// 33 | /// The ray for which the intersection should be checked. 34 | /// If the circle intersects the ray. 35 | public bool Intersects(Ray ray) 36 | { 37 | Vector2 positionToRayOrigin = ray.Origin - Position; 38 | 39 | // Apply ABC formula 40 | float a = Vector2.Dot(ray.Direction, ray.Direction); 41 | float b = Vector2.Dot(ray.Direction, positionToRayOrigin); 42 | float c = Vector2.Dot(positionToRayOrigin, positionToRayOrigin) - Radius * Radius; 43 | float d = b * b - a * c; 44 | 45 | // Early exit if there are no intersections 46 | if (d < 0) return false; 47 | 48 | // Calculate intersections based on d 49 | float sqrtD = (float) Math.Sqrt(d); 50 | float distance = (-b - sqrtD) / a; 51 | if (distance < 0) distance = (-b + sqrtD) / a; 52 | 53 | // Check if our result is within the ray's length 54 | return (distance > 0 && distance < ray.Magnitude); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /infogr-raytracer/Texture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using OpenTK.Graphics.OpenGL; 5 | using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat; 6 | 7 | namespace infogr_raytracer 8 | { 9 | public class Texture: IDisposable 10 | { 11 | public readonly int Handle; 12 | 13 | public Texture(string path) 14 | { 15 | Handle = GL.GenTexture(); 16 | 17 | Use(); 18 | 19 | using (var image = new Bitmap(path)) 20 | { 21 | // Get pixels from the loaded bitmap 22 | var data = image.LockBits( 23 | new System.Drawing.Rectangle(0, 0, image.Width, image.Height), 24 | ImageLockMode.ReadOnly, 25 | System.Drawing.Imaging.PixelFormat.Format32bppArgb); 26 | 27 | // Generate a texture based on the Bitmap data 28 | GL.TexImage2D(TextureTarget.Texture2D, 29 | 0, 30 | PixelInternalFormat.Rgba, 31 | image.Width, 32 | image.Height, 33 | 0, 34 | PixelFormat.Bgra, 35 | PixelType.UnsignedByte, 36 | data.Scan0); 37 | 38 | // Set the min and mag filter 39 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 40 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 41 | 42 | // Set the wrapping mode 43 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); 44 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); 45 | 46 | // Generate Mipmap 47 | GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); 48 | } 49 | 50 | } 51 | 52 | /// 53 | /// Activate texture 54 | /// 55 | /// 56 | public void Use(TextureUnit unit = TextureUnit.Texture0) 57 | { 58 | GL.ActiveTexture(unit); 59 | GL.BindTexture(TextureTarget.Texture2D, Handle); 60 | } 61 | 62 | 63 | // Make our texture disposable 64 | private bool _disposedValue = false; 65 | 66 | protected virtual void Dispose(bool disposing) 67 | { 68 | if (!_disposedValue) 69 | { 70 | GL.DeleteTexture(Handle); 71 | _disposedValue = true; 72 | } 73 | } 74 | 75 | ~Texture() 76 | { 77 | GL.DeleteProgram(Handle); 78 | } 79 | 80 | 81 | public void Dispose() 82 | { 83 | Dispose(true); 84 | GC.SuppressFinalize(this); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /infogr-raytracer/ShaderGame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using OpenTK.Graphics.OpenGL; 4 | 5 | namespace infogr_raytracer 6 | { 7 | public class ShaderGame 8 | { 9 | private readonly float[] _vertices = 10 | { 11 | 1.0f, 1.0f, 0.0f, 12 | 1.0f, -1.0f, 0.0f, 13 | -1.0f, -1.0f, 0.0f, 14 | -1.0f, 1.0f, 0.0f 15 | }; 16 | 17 | private readonly uint[] _indices = 18 | { 19 | 0, 1, 3, 20 | 1, 2, 3 21 | }; 22 | 23 | private int _vertexBufferObject; 24 | private int _vertexArrayObject; 25 | private int _elementBufferObject; 26 | 27 | private Shader _shader; 28 | 29 | /// 30 | /// Called when the Game is loaded. 31 | /// Should be used for one-time setup. 32 | /// 33 | public void OnLoad() 34 | { 35 | VaoSetup(); 36 | 37 | } 38 | 39 | private void VaoSetup() 40 | { 41 | _vertexBufferObject = GL.GenBuffer(); 42 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 43 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 44 | 45 | _elementBufferObject = GL.GenBuffer(); 46 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 47 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 48 | 49 | _vertexArrayObject = GL.GenVertexArray(); 50 | GL.BindVertexArray(_vertexArrayObject); 51 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 52 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 53 | 54 | 55 | _shader = new Shader("shaders/raytrace-shader.vert", "shaders/raytrace-shader.frag"); 56 | _shader.Use(); 57 | 58 | int vertexLocation = _shader.GetAttribLocation("vertexPosition"); 59 | GL.EnableVertexAttribArray(vertexLocation); 60 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 61 | } 62 | 63 | /// 64 | /// Called when the frame is rendered. 65 | /// 66 | public void OnRenderFrame() 67 | { 68 | GL.BindVertexArray(_vertexArrayObject); 69 | 70 | var stopwatch = new Stopwatch(); 71 | stopwatch.Start(); 72 | 73 | _shader.SetFloat("time", (float) Math.Sin(DateTime.Now.Ticks / 1_000_000.0) / 2.0f); 74 | 75 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 76 | 77 | stopwatch.Stop(); 78 | 79 | var fps = stopwatch.ElapsedMilliseconds == 0 ? 80 | "Infinite" : 81 | $"{1_000 / stopwatch.ElapsedMilliseconds}"; 82 | // Console.WriteLine($"FPS: {fps}"); 83 | } 84 | 85 | public void OnUnload() 86 | { 87 | _shader.Dispose(); 88 | } 89 | 90 | public void OnResize(int width, int height) 91 | { 92 | _shader.SetInt("SCREEN_WIDTH", width); 93 | _shader.SetInt("SCREEN_HEIGHT", height); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /Tests/RectangleTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using infogr_raytracer; 5 | using OpenTK; 6 | 7 | namespace Tests 8 | { 9 | public class RectangleTest 10 | { 11 | public struct RectanglePositionTestCase 12 | { 13 | public Vector2 Position; 14 | public float Width; 15 | public float Height; 16 | public float Angle; 17 | public string Description; 18 | 19 | public override string ToString() 20 | { 21 | return Description; 22 | } 23 | } 24 | 25 | private static List rectanglePositionTestCases = 26 | new List() 27 | { 28 | new RectanglePositionTestCase() 29 | { 30 | Position = new Vector2(0, 0), 31 | Width = 4, 32 | Height = 4, 33 | Angle = 0, 34 | Description = "Square rectangle at origin of size 4, 4" 35 | }, 36 | new RectanglePositionTestCase() 37 | { 38 | Position = new Vector2(0, 0), 39 | Width = 2, 40 | Height = 4, 41 | Angle = 0, 42 | Description = "Rectangle at origin of size 2, 4" 43 | }, 44 | new RectanglePositionTestCase() 45 | { 46 | Position = new Vector2(2, 3), 47 | Width = 2, 48 | Height = 4, 49 | Angle = 0, 50 | Description = "Rectangle at (2, 3) of size 2, 4" 51 | }, 52 | new RectanglePositionTestCase() 53 | { 54 | Position = new Vector2(2.042f, 3.036f), 55 | Width = 3.2f, 56 | Height = 4.2f, 57 | Angle = 0, 58 | Description = "Rectangle at (2.042, 3.036) of size 3.2, 4.2" 59 | }, 60 | new RectanglePositionTestCase() 61 | { 62 | Position = new Vector2(5.43421f, -4.321f), 63 | Width = 3.21f, 64 | Height = 4.21f, 65 | Angle = 0, 66 | Description = "Rectangle at (5.43421, -4.321) of size 3.21, 4.321" 67 | }, 68 | new RectanglePositionTestCase() 69 | { 70 | Position = new Vector2(5.43421f, -4.321f), 71 | Width = -5.21f, 72 | Height = 4.21f, 73 | Angle = 0, 74 | Description = "Rectangle with negative Width" 75 | }, 76 | new RectanglePositionTestCase() 77 | { 78 | Position = new Vector2(5.43421f, -4.321f), 79 | Width = 5.21f, 80 | Height = -7.21f, 81 | Angle = 0, 82 | Description = "Rectangle with negative Height" 83 | } 84 | }; 85 | 86 | [Test] 87 | [TestCaseSource("rectanglePositionTestCases")] 88 | public void CalculatesPosition(RectanglePositionTestCase testCase) 89 | { 90 | Rectangle rectangle = new Rectangle(testCase.Position, testCase.Width, testCase.Height); 91 | Assert.That(testCase.Position.X, Is.EqualTo(rectangle.Position.X).Within(0.001f)); 92 | Assert.That(testCase.Position.Y, Is.EqualTo(rectangle.Position.Y).Within(0.001f)); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /infogr-raytracer/Shader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using OpenTK.Graphics.OpenGL; 5 | 6 | namespace infogr_raytracer 7 | { 8 | public class Shader: IDisposable 9 | { 10 | public int Handle; 11 | 12 | public Shader(string vertexPath, string fragmentPath) 13 | { 14 | // Get the Vertex Shader Source from the file located at vertexPath 15 | // and the Fragment Shader Source from the file located at fragmentPath 16 | string vertexShaderSource; 17 | using (StreamReader reader = new StreamReader(vertexPath, Encoding.UTF8)) 18 | vertexShaderSource = reader.ReadToEnd(); 19 | 20 | string fragmentShaderSource; 21 | using (StreamReader reader = new StreamReader(fragmentPath, Encoding.UTF8)) 22 | fragmentShaderSource = reader.ReadToEnd(); 23 | 24 | 25 | // Create OpenGL shaders and bind the source code to the shaders 26 | int vertexShader = GL.CreateShader(ShaderType.VertexShader); 27 | GL.ShaderSource(vertexShader, vertexShaderSource); 28 | 29 | int fragmentShader = GL.CreateShader(ShaderType.FragmentShader); 30 | GL.ShaderSource(fragmentShader, fragmentShaderSource); 31 | 32 | 33 | // Compile the shaders and check for errors 34 | GL.CompileShader(vertexShader); 35 | string infoLogVert = GL.GetShaderInfoLog(vertexShader); 36 | if (infoLogVert != String.Empty) 37 | Console.WriteLine(infoLogVert); 38 | 39 | GL.CompileShader(fragmentShader); 40 | string infoLogFrag = GL.GetShaderInfoLog(fragmentShader); 41 | if (infoLogFrag != String.Empty) 42 | Console.WriteLine(infoLogFrag); 43 | 44 | 45 | // Link our compiled shaders together into a program that can run on the GPU. 46 | Handle = GL.CreateProgram(); 47 | GL.AttachShader(Handle, vertexShader); 48 | GL.AttachShader(Handle, fragmentShader); 49 | 50 | GL.LinkProgram(Handle); // Handle is now a usable shader program 51 | 52 | 53 | // Cleanup. Vertex and fragment shaders are useless now that they've been linked. We detach and delete them. 54 | GL.DetachShader(Handle, vertexShader); 55 | GL.DetachShader(Handle, fragmentShader); 56 | GL.DeleteShader(fragmentShader); 57 | GL.DeleteShader(vertexShader); 58 | } 59 | 60 | public void Use() 61 | { 62 | GL.UseProgram(Handle); 63 | } 64 | 65 | public void SetUniform4(string name, float v0, float v1, float v2, float v3) 66 | { 67 | Use(); 68 | GL.Uniform4(GL.GetUniformLocation(Handle, name), v0, v1, v2, v3); 69 | } 70 | 71 | public void SetFloat(string name, float value) 72 | { 73 | Use(); 74 | GL.Uniform1(GL.GetUniformLocation(Handle, name), value); 75 | } 76 | 77 | public void SetInt(string name, int value) 78 | { 79 | Use(); 80 | GL.Uniform1(GL.GetUniformLocation(Handle, name), value); 81 | } 82 | 83 | public int GetAttribLocation(string attribName) 84 | { 85 | return GL.GetAttribLocation(Handle, attribName); 86 | } 87 | 88 | 89 | // Make our shader disposable 90 | private bool _disposedValue = false; 91 | 92 | protected virtual void Dispose(bool disposing) 93 | { 94 | if (!_disposedValue) 95 | { 96 | GL.DeleteProgram(Handle); 97 | 98 | _disposedValue = true; 99 | } 100 | } 101 | 102 | ~Shader() 103 | { 104 | GL.DeleteProgram(Handle); 105 | } 106 | 107 | 108 | public void Dispose() 109 | { 110 | Dispose(true); 111 | GC.SuppressFinalize(this); 112 | } 113 | 114 | } 115 | } -------------------------------------------------------------------------------- /infogr-raytracer/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK; 3 | using OpenTK.Graphics; 4 | using OpenTK.Graphics.OpenGL; 5 | 6 | namespace infogr_raytracer 7 | { 8 | public class Window: GameWindow 9 | { 10 | // TODO: Figure out a proper way to set feature flag 11 | const bool SHADER_VERSION = true; 12 | 13 | private Game _game; 14 | private ShaderGame _shaderGame; 15 | 16 | /// 17 | /// Constructs a new Window with the specified attributes. 18 | /// 19 | /// The width of the Window's window 20 | /// The height of the Window's window 21 | /// The title of the Window's window 22 | public Window(int width, int height, string title) : base(width, height, GraphicsMode.Default, title, 23 | GameWindowFlags.Default, DisplayDevice.Default, 24 | 3, 3, GraphicsContextFlags.ForwardCompatible) 25 | { 26 | } 27 | 28 | /// 29 | /// Print OpenGL and Open GL Shading Language versions 30 | /// 31 | private static void PrintVersionInfo() 32 | { 33 | Console.WriteLine($"OpenGL Version: {GL.GetString(StringName.Version)}"); 34 | Console.WriteLine($"OpenGL Shading Language Version: {GL.GetString(StringName.ShadingLanguageVersion)}"); 35 | Console.WriteLine(); 36 | } 37 | 38 | /// 39 | /// Called after an OpenGL context has been established, but before entering the main loop. 40 | /// 41 | /// Not used. 42 | protected override void OnLoad(EventArgs e) 43 | { 44 | PrintVersionInfo(); 45 | 46 | if (SHADER_VERSION) 47 | { 48 | _shaderGame = new ShaderGame(); 49 | _shaderGame.OnLoad(); 50 | } 51 | else 52 | { 53 | _game = new Game { Screen = new Surface(Width, Height) }; 54 | _game.OnLoad(); 55 | } 56 | 57 | base.OnLoad(e); 58 | } 59 | 60 | /// 61 | /// Called when the frame is rendered. 62 | /// 63 | protected override void OnRenderFrame(FrameEventArgs e) 64 | { 65 | // Clear the screen 66 | GL.Clear(ClearBufferMask.ColorBufferBit); 67 | 68 | if (SHADER_VERSION) 69 | { 70 | _shaderGame.OnRenderFrame(); 71 | } 72 | else 73 | { 74 | // Let our Game draw stuff to it's Screen 75 | _game.OnRenderFrame(); 76 | _game.Screen.Draw(); 77 | } 78 | 79 | // Show the new frame 80 | Context.SwapBuffers(); 81 | base.OnRenderFrame(e); 82 | } 83 | 84 | /// 85 | /// Called when this window is resized. 86 | /// Updates viewport to match window size. 87 | /// 88 | /// Not used. 89 | protected override void OnResize(EventArgs e) 90 | { 91 | GL.Viewport(0, 0, Width, Height); 92 | 93 | // TODO: Implement for SHADER_VERSION 94 | if (SHADER_VERSION) 95 | { 96 | _shaderGame.OnResize(Width, Height); 97 | } 98 | if (!SHADER_VERSION) 99 | { 100 | _game.Screen.Unload(); 101 | _game.Screen = new Surface(Width, Height); 102 | _game.OnResize(); 103 | } 104 | 105 | base.OnResize(e); 106 | } 107 | 108 | /// 109 | /// Called after GameWindow.Exit was called, but before destroying the OpenGL context. 110 | /// Unloads our screen. 111 | /// 112 | /// Not used. 113 | protected override void OnUnload(EventArgs e) 114 | { 115 | // TODO: Implement for SHADER_VERSION 116 | if (SHADER_VERSION) 117 | { 118 | _shaderGame.OnUnload(); 119 | } 120 | else 121 | { 122 | _game.Screen.Unload(); 123 | } 124 | base.OnUnload(e); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /infogr-raytracer/Rectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK; 3 | 4 | namespace infogr_raytracer 5 | { 6 | public struct Rectangle: IGameObject 7 | { 8 | // Clockwise order, starting with top-left corner, with respect to the angle 9 | private Vector2[] _corners; 10 | 11 | public Rectangle(Vector2 position, float width, float height, float angle = 0) 12 | { 13 | // Determine x, y coordinates as if the rectangle were at the origin 14 | _corners = new[] 15 | { 16 | new Vector2(- width * 0.5f, + height * 0.5f), 17 | new Vector2(+ width * 0.5f, + height * 0.5f), 18 | new Vector2(+ width * 0.5f, - height * 0.5f), 19 | new Vector2(- width * 0.5f, - height * 0.5f), 20 | }; 21 | 22 | // Rotate the points based on angle 23 | if (angle != 0) 24 | { 25 | for (int i = 0; i < _corners.Length; i++) 26 | _corners[i] = new Vector2( 27 | _corners[i].X * (float) Math.Cos(angle) - _corners[i].Y * (float) Math.Sin(angle), 28 | _corners[i].X * (float) Math.Sin(angle) + _corners[i].Y * (float) Math.Cos(angle)); 29 | } 30 | 31 | // Translate the points to their position 32 | for (int i = 0; i < _corners.Length; i++) 33 | _corners[i] += position; 34 | 35 | } 36 | 37 | public Vector2 Position 38 | { 39 | get => (_corners[0] + _corners[2]) / 2; 40 | } 41 | 42 | public void Move(Vector2 position) 43 | { 44 | Vector2 translation = position - Position; 45 | for (int i = 0; i < _corners.Length; i++) 46 | _corners[i] += translation; 47 | } 48 | 49 | /// 50 | /// Translate a rectangle. 51 | /// 52 | /// The translation by which the rectangle's position should be altered 53 | public void MoveBy(Vector2 translation) 54 | { 55 | Move(translation + Position); 56 | } 57 | 58 | /// 59 | /// Return if this rectangle intersects a ray. 60 | /// 61 | /// The ray for which the intersection should be checked 62 | /// If the rectangle intersects the ray. 63 | public bool Intersects(Ray ray) 64 | { 65 | return _lineIntersects(ray, _corners[0], _corners[1]) || 66 | _lineIntersects(ray, _corners[1], _corners[2]) || 67 | _lineIntersects(ray, _corners[2], _corners[3]) || 68 | _lineIntersects(ray, _corners[3], _corners[0]); 69 | } 70 | 71 | 72 | /// 73 | /// Determine if our ray crosses the line from line start to line end. 74 | /// 75 | /// The ray that needs to be checked. 76 | /// The start of the line. 77 | /// The end of the line. 78 | /// 79 | private bool _lineIntersects(Ray ray, Vector2 lineStart, Vector2 lineEnd) 80 | { 81 | // Uses this https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect 82 | // algorithm of finding out if two vectors intersect. 83 | 84 | Vector2 rayStart = ray.Origin; 85 | Vector2 rayEnd = ray.Origin + ray.Direction; 86 | 87 | Vector2 rayStoLineS = lineStart - rayStart; 88 | Vector2 r = ray.Direction * ray.Magnitude; 89 | Vector2 s = lineEnd - lineStart; 90 | 91 | float crossR = rayStoLineS.X * r.Y - rayStoLineS.Y * r.X; 92 | float crossS = rayStoLineS.X * s.Y - rayStoLineS.Y * s.X; 93 | float rCrossS = r.X * s.Y - r.Y * s.X; 94 | 95 | if (crossR == 0f) // Lines are collinear 96 | return ((lineStart.X - rayStart.X < 0f) != (lineStart.X - rayEnd.X < 0f)) || 97 | ((lineStart.Y - rayStart.Y < 0f) != (lineStart.Y - rayEnd.Y < 0f)); 98 | 99 | if (rCrossS == 0f) // Lines are parallel 100 | return false; 101 | 102 | float t = crossS / rCrossS; 103 | float u = crossR / rCrossS; 104 | 105 | return (t >= 0f) && (t <= 1f) && (u >= 0f) && (u <= 1f); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /infogr-raytracer/infogr-raytracer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {077A96BD-0CB5-4244-9CE2-A4A9A3E4AA19} 9 | Exe 10 | Properties 11 | infogr_raytracer 12 | infogr_raytracer 13 | v4.7.2 14 | 512 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll 38 | True 39 | 40 | 41 | ..\packages\OpenTK.3.2\lib\net20\OpenTK.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Always 75 | 76 | 77 | Always 78 | 79 | 80 | 81 | Always 82 | 83 | 84 | Always 85 | 86 | 87 | 88 | 89 | 90 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 91 | 92 | 93 | 94 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /infogr-raytracer/shaders/raytrace-shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec2 screenPosition; 4 | out vec4 FragColor; 5 | 6 | uniform int SCREEN_WIDTH; 7 | uniform int SCREEN_HEIGHT; 8 | 9 | #define M_PI 3.1415926535897932384626433832795 10 | 11 | uniform float time; 12 | 13 | 14 | 15 | // DATA MODEL 16 | struct Ray 17 | { 18 | vec2 origin; 19 | vec2 direction; 20 | float magnitude; 21 | }; 22 | 23 | struct Light 24 | { 25 | vec2 position; 26 | vec3 color; 27 | }; 28 | 29 | struct Circle 30 | { 31 | vec2 position; 32 | float radius; 33 | }; 34 | 35 | struct Camera 36 | { 37 | vec2 position; 38 | vec2 viewport; 39 | int screenSizeWorldSizeRatio; 40 | } camera; 41 | 42 | struct Rectangle { 43 | vec2[4] corners; 44 | }; 45 | 46 | 47 | Rectangle newRectangle(in vec2 position, float width, float height, float angle) { 48 | vec2[4] corners = vec2[4]( 49 | vec2(-width * 0.5, height * 0.5), 50 | vec2(width * 0.5, height * 0.5), 51 | vec2(width * 0.5, -height * 0.5), 52 | vec2(-width * 0.5, -height * 0.5) 53 | ); 54 | 55 | if (angle != 0) { 56 | for (int i = 0; i < corners.length(); i++) { 57 | vec2 corner = corners[i]; 58 | corners[i] = vec2( 59 | corner.x * cos(angle) - corner.y * sin(angle), 60 | corner.xy * sin(angle) + corner.y * cos(angle) 61 | ); 62 | } 63 | } 64 | 65 | for (int i = 0; i < corners.length(); i++) { 66 | corners[i] += position; 67 | } 68 | 69 | return Rectangle(corners); 70 | } 71 | 72 | 73 | 74 | 75 | // SCENE DEFINITION 76 | Light[] lights = Light[] ( 77 | Light(vec2(0, 0), 4 * vec3(0, 0.75, 0.75)), 78 | Light(vec2(4, 0), 4 * vec3(0, 1, 0)), 79 | Light(vec2(8, 0), 4 * vec3(0.75, 1, 0)), 80 | Light(vec2(0, 4), 4 * vec3(0, 0, 1)), 81 | Light(vec2(4, 4), 4 * vec3(1, 1, 1)), 82 | Light(vec2(8, 4), 4 * vec3(1, 1, 0)), 83 | Light(vec2(0, 8), 4 * vec3(1, 0, 1)), 84 | Light(vec2(4, 8), 4 * vec3(1, 0, 0)), 85 | Light(vec2(8, 8), 4 * vec3(1, 0.75, 0)) 86 | ); 87 | 88 | Circle[] circles = Circle[] ( 89 | Circle(vec2(2, 2), 0.1), 90 | Circle(vec2(3, 2), 0.2), 91 | Circle(vec2(4, 2), 0.3), 92 | Circle(vec2(5, 2), 0.2), 93 | Circle(vec2(6, 2), 0.1) 94 | ); 95 | 96 | Rectangle[] rectangles = Rectangle[]( 97 | newRectangle(vec2(3,3), 1, 1, 0.4) 98 | ); 99 | 100 | 101 | 102 | 103 | // INTERSECTION CHECKS 104 | bool circleIntersects(Circle circle, Ray ray) 105 | { 106 | vec2 posToOrigin = ray.origin - circle.position; 107 | float a = dot(ray.direction, ray.direction); 108 | float b = dot(ray.direction, posToOrigin); 109 | float c = dot(posToOrigin, posToOrigin) - (circle.radius * circle.radius); 110 | float d = (b * b) - (a * c); 111 | 112 | if (d < 0) return false; 113 | 114 | float sqrtD = sqrt(d); 115 | float distance = (-b - sqrtD) / a; 116 | if (distance < 0) distance = (-b + sqrtD) / a; 117 | 118 | return distance > 0 && distance < ray.magnitude; 119 | } 120 | 121 | bool lineIntersects(Ray ray, vec2 lineStart, vec2 lineEnd) { 122 | vec2 rayStart = ray.origin; 123 | vec2 rayEnd = ray.origin + ray.direction; 124 | 125 | vec2 rayStoLineS = lineStart - rayStart; 126 | vec2 r = ray.direction * ray.magnitude; 127 | vec2 s = lineEnd - lineStart; 128 | 129 | float crossR = (rayStoLineS.x * r.y) - (rayStoLineS.y * r.x); 130 | float crossS = (rayStoLineS.x * s.y) - (rayStoLineS.y * s.x); 131 | float rCrossS = r.x * s.y - r.y * s.x; 132 | 133 | if (crossR == 0) { 134 | return ((lineStart.x - rayStart.x < 0) != (lineStart.x - rayEnd.x < 0)) || 135 | ((lineStart.y - rayStart.y < 0) != (lineStart.y - rayEnd.y < 0)); 136 | } 137 | 138 | if (rCrossS == 0) return false; 139 | 140 | float t = crossS / rCrossS; 141 | float u = crossR / rCrossS; 142 | 143 | return (t >= 0) && (t <= 1) && (u >= 0) && (u <= 1); 144 | } 145 | 146 | bool rectangleIntersect(Ray ray, Rectangle rect) { 147 | vec2[4] corners = rect.corners; 148 | 149 | return lineIntersects(ray, corners[0], corners[1]) || 150 | lineIntersects(ray, corners[1], corners[2]) || 151 | lineIntersects(ray, corners[2], corners[3]) || 152 | lineIntersects(ray, corners[3], corners[0]); 153 | } 154 | 155 | 156 | 157 | 158 | // Convert point from screen space to world space 159 | vec2 ToWorldSpace(vec2 screenSpacePoint) 160 | { 161 | float ratio = 100; // screen pixels per world space unit 162 | 163 | return screenSpacePoint * vec2(SCREEN_WIDTH, SCREEN_HEIGHT) / ratio; 164 | } 165 | 166 | vec3 Trace(vec2 worldPoint) 167 | { 168 | vec3 colorAtPixel = vec3(0, 0, 0); 169 | 170 | for (int i = 0; i < lights.length(); i++) 171 | { 172 | vec2 vector2Light = lights[i].position - worldPoint; 173 | 174 | // Don't forget to normalize the ray's direction 175 | Ray ray = Ray(worldPoint, vector2Light, length(vector2Light)); 176 | ray.direction = normalize(ray.direction); 177 | 178 | // Check for occlusions between ray 179 | bool occluded = false; 180 | for (int c = 0; c < circles.length(); c++) { 181 | Circle circle = circles[c]; 182 | if (circleIntersects(circle, ray)) { 183 | occluded = true; 184 | break; 185 | } 186 | } 187 | if (occluded) continue; 188 | 189 | for (int r = 0; r < rectangles.length(); r++) { 190 | Rectangle rect = rectangles[r]; 191 | if (rectangleIntersect(ray, rect)) { 192 | occluded = true; 193 | break; 194 | } 195 | } 196 | if (occluded) continue; 197 | 198 | 199 | float distanceToLight = length(vector2Light); 200 | float intensity = 1.0 / (4 * M_PI * distanceToLight); 201 | 202 | colorAtPixel += lights[i].color * intensity; 203 | } 204 | 205 | return colorAtPixel; 206 | } 207 | 208 | 209 | 210 | 211 | void main() 212 | { 213 | 214 | for (int i = 0; i < circles.length(); i++) 215 | { 216 | circles[i].position += vec2(time, 0); 217 | } 218 | 219 | camera.position = vec2 (4, 4 + time); 220 | vec3 colorAtPixel = Trace(ToWorldSpace(screenPosition) + camera.position); 221 | FragColor = vec4(colorAtPixel, 1); 222 | } -------------------------------------------------------------------------------- /infogr-raytracer/Game.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OpenTK; 4 | using OpenTK.Input; 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace infogr_raytracer 10 | { 11 | public class Game 12 | { 13 | public Surface Screen; 14 | 15 | // private Scene _scene = new Scene() 16 | // { 17 | // Lights = new List() 18 | // { 19 | // new Light() { Color = new Vector3(1, 1, 1), Position = new Vector2(2.0f, 2.0f) }, 20 | // new Light() { Color = new Vector3(3, 2, 1), Position = new Vector2(3.0f, 4.0f) }, 21 | // new Light() { Color = new Vector3(3, 4, 5), Position = new Vector2(3.0f, 8.0f) }, 22 | // new Light() { Color = new Vector3(1, 0, 0), Position = new Vector2(7.0f, 8.0f) }, 23 | // new Light() { Color = new Vector3(0, 0, 1), Position = new Vector2(7.5f, 8.0f) } 24 | // }, 25 | // GameObjects = new List() 26 | // { 27 | // new Circle() { Position = new Vector2(4.0f,4.5f), Radius = 0.1f }, 28 | // new Circle() { Position = new Vector2(6.5f,6.5f), Radius = 1.0f }, 29 | // new Circle() { Position = new Vector2(4.0f,4.0f), Radius = 0.3f }, 30 | // new Circle() { Position = new Vector2(3.0f,3.0f), Radius = 0.1f } 31 | // } 32 | // }; 33 | 34 | private Scene _scene = new Scene() 35 | { 36 | Lights = new List() 37 | { 38 | new Light() { Position = new Vector2(4.0f, 2.0f), Color = 3 * new Vector3(6, 2, 1) }, 39 | new Light() { Position = new Vector2(6.0f, 4.0f), Color = 0.5f * new Vector3(1, 2, 3) }, 40 | new Light() { Position = new Vector2(4.5f, 5.0f), Color = 2f * new Vector3(1, 2, 3) }, 41 | }, 42 | GameObjects = new List() 43 | { 44 | new Circle() { Position = new Vector2(4.0f, 0.9999f), Radius = 1.0f }, 45 | new Circle() { Position = new Vector2(4.0f, 3.65f), Radius = 0.1f }, 46 | new Circle() { Position = new Vector2(3.5f, 3.5f), Radius = 0.2f }, 47 | new Rectangle(new Vector2(4.5f, 3.9f), 0.4f, 0.8f, angle: 0.7f) 48 | } 49 | }; 50 | 51 | 52 | private Camera _camera = new Camera() 53 | { 54 | Position = new Vector2(0, 0) 55 | }; 56 | 57 | /// 58 | /// Called when the Game is loaded. 59 | /// Should be used for one-time setup. 60 | /// 61 | public void OnLoad() 62 | { 63 | } 64 | 65 | 66 | /// 67 | /// Called when the frame is rendered. 68 | /// 69 | public void OnRenderFrame() 70 | { 71 | MoveCamera(); 72 | RenderScene(); 73 | 74 | _scene.GameObjects[3].MoveBy(new Vector2(-0.1f, 0)); 75 | } 76 | 77 | /// 78 | /// Render our scene. 79 | /// 80 | private void RenderScene() 81 | { 82 | Screen.Clear(255); 83 | 84 | var timer = new System.Diagnostics.Stopwatch(); 85 | 86 | timer.Start(); 87 | Parallel.For(0, Screen.Width * Screen.Height, (i) => 88 | { 89 | var x = i % Screen.Width; 90 | var y = i / Screen.Width; 91 | Vector3 colorForPixel = Trace(_camera.WorldSpace(x, y)); 92 | Screen.Plot(x, y, ToScreenColor(colorForPixel)); 93 | }); 94 | timer.Stop(); 95 | 96 | var fps = 1000f / timer.ElapsedMilliseconds; 97 | Screen.Print("Look on my Works, ye Mighty, and despair!", 10, 10, 0xFFFFFF); 98 | Screen.Print($"FPS: {fps}", 10, 30, 0xFFFFFF); 99 | } 100 | 101 | /// 102 | /// Moves the Camera using the arrow keys. 103 | /// 104 | private void MoveCamera() 105 | { 106 | KeyboardState keyboardState = Keyboard.GetState(); 107 | 108 | float speed = 0.2f; 109 | 110 | if (keyboardState[Key.Right]) _camera.Position.X += speed; 111 | if (keyboardState[Key.Left]) _camera.Position.X -= speed; 112 | if (keyboardState[Key.Up]) _camera.Position.Y += speed; 113 | if (keyboardState[Key.Down]) _camera.Position.Y -= speed; 114 | } 115 | 116 | 117 | /// 118 | /// On Window resize call the camera's resize method. 119 | /// 120 | public void OnResize() 121 | { 122 | _camera.Resize(Screen.Width, Screen.Height); 123 | } 124 | 125 | /// 126 | /// Convert a world color (float, [0-1], and [1, ->) for HDR), to a screen color (int, [0-255]). 127 | /// 128 | /// The world color of type float, [0-1], and [1, ->). 129 | /// A screen color of type int, [0-255]. 130 | private int ToScreenColor(Vector3 worldColor) 131 | { 132 | return ((int) (Math.Min(worldColor.X * 255, 255)) << 16) + 133 | ((int) (Math.Min(worldColor.Y * 255, 255)) << 8) + 134 | (int) (Math.Min(worldColor.Z * 255, 255)); 135 | } 136 | 137 | /// 138 | /// Calculate which color a world space point should have. 139 | /// 140 | /// A world space point for which the color should be determined. 141 | /// The determined color 142 | private Vector3 Trace(Vector2 point) 143 | { 144 | Vector3 colorAtPoint = new Vector3(0, 0, 0); 145 | 146 | foreach (Light light in _scene.Lights) 147 | { 148 | // Construct ray from point to the light source 149 | Vector2 vectorToLight = light.Position - point; 150 | Ray rayToLight = new Ray() { Origin = point, Direction = vectorToLight, Magnitude = vectorToLight.Length}; 151 | 152 | // Check if the light is occluded (intersected) by a Game Object, continue if it is occluded 153 | if (_scene.GameObjects.Exists(gameObject => gameObject.Intersects(rayToLight))) continue; 154 | 155 | // If the light from the light source is not occluded by any game object, 156 | // Calculate the light intensity form that non-occluded light source to our point 157 | float distanceToLight = vectorToLight.Length; 158 | float intensity = 1f / (4f * (float) Math.PI * distanceToLight * distanceToLight); 159 | 160 | // Add the color from the light to our final color 161 | colorAtPoint += light.Color * intensity; 162 | } 163 | 164 | return colorAtPoint; 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /Tests/CircleTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using infogr_raytracer; 5 | using OpenTK; 6 | 7 | namespace Tests 8 | { 9 | public class CircleTest 10 | { 11 | public struct CircleTestCase 12 | { 13 | public Ray Ray; 14 | public Circle Circle; 15 | public bool ExpectedResult; 16 | public string Description; 17 | 18 | public override string ToString() 19 | { 20 | return Description; 21 | } 22 | } 23 | 24 | private static List testCases = new List() 25 | { 26 | new CircleTestCase() 27 | { 28 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 29 | Ray = new Ray() 30 | { 31 | Origin = new Vector2(4, 0), 32 | Direction = new Vector2(-1, 0), 33 | Magnitude = 5f, 34 | }, 35 | ExpectedResult = true, 36 | Description = "Intersection near origin" 37 | }, 38 | new CircleTestCase() 39 | { 40 | Circle = new Circle() { Position = new Vector2(2, 2), Radius = 1f}, 41 | Ray = new Ray() { Origin = new Vector2(0, 2), Direction = new Vector2(1, 0), Magnitude = 50f }, 42 | ExpectedResult = true, 43 | Description = "Orthogonal from left" 44 | }, 45 | new CircleTestCase() 46 | { 47 | Circle = new Circle() { Position = new Vector2(2, 2), Radius = 1f}, 48 | Ray = new Ray() { Origin = new Vector2(2, 4), Direction = new Vector2(0, -1), Magnitude = 50f }, 49 | ExpectedResult = true, 50 | Description = "Orthogonal from top" 51 | }, 52 | new CircleTestCase() 53 | { 54 | Circle = new Circle() { Position = new Vector2(2, 2), Radius = 1f}, 55 | Ray = new Ray() { Origin = new Vector2(4, 2), Direction = new Vector2(-1, 0), Magnitude = 50f }, 56 | ExpectedResult = true, 57 | Description = "Orthogonal from right" 58 | }, 59 | new CircleTestCase() 60 | { 61 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 62 | Ray = new Ray() { Origin = new Vector2(0, -2), Direction = new Vector2(0, 1), Magnitude = 50f }, 63 | ExpectedResult = true, 64 | Description = "Orthogonal from bottom" 65 | }, 66 | new CircleTestCase() 67 | { 68 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 69 | Ray = new Ray() { Origin = new Vector2(-4, 4), Direction = new Vector2(1, -1), Magnitude = 50f }, 70 | ExpectedResult = true, 71 | Description = "Orthogonal from top left" 72 | }, 73 | new CircleTestCase() 74 | { 75 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 76 | Ray = new Ray() { Origin = new Vector2(-4, -4), Direction = new Vector2(1, 1), Magnitude = 50f }, 77 | ExpectedResult = true, 78 | Description = "Orthogonal from bottom left" 79 | }, 80 | new CircleTestCase() 81 | { 82 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 83 | Ray = new Ray() { Origin = new Vector2(4, 4), Direction = new Vector2(-1, -1), Magnitude = 50f }, 84 | ExpectedResult = true, 85 | Description = "Orthogonal from top right" 86 | }, 87 | new CircleTestCase() 88 | { 89 | Circle = new Circle() { Position = new Vector2(0, 0), Radius = 1f}, 90 | Ray = new Ray() { Origin = new Vector2(4, -4), Direction = new Vector2(-1, 1), Magnitude = 50f }, 91 | ExpectedResult = true, 92 | Description = "Orthogonal from bottom right" 93 | }, 94 | new CircleTestCase() 95 | { 96 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 97 | Ray = new Ray() { Origin = new Vector2(0, 3), Direction = new Vector2(1, 0), Magnitude = 50f }, 98 | ExpectedResult = true, 99 | Description = "Touch the circle at the bottom" 100 | }, 101 | new CircleTestCase() 102 | { 103 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 104 | Ray = new Ray() { Origin = new Vector2(0, 10), Direction = new Vector2(1, 0), Magnitude = 50f }, 105 | ExpectedResult = false, 106 | Description = "Ray that flies above the circle" 107 | }, 108 | new CircleTestCase() 109 | { 110 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 111 | Ray = new Ray() { Origin = new Vector2(0, -10), Direction = new Vector2(1, 0), Magnitude = 50f }, 112 | ExpectedResult = false, 113 | Description = "Ray that flies beneath the circle" 114 | }, 115 | new CircleTestCase() 116 | { 117 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 118 | Ray = new Ray() { Origin = new Vector2(0, 0), Direction = new Vector2(0, 1), Magnitude = 50f }, 119 | ExpectedResult = false, 120 | Description = "Ray that flies to the left of the circle" 121 | }, 122 | new CircleTestCase() 123 | { 124 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 125 | Ray = new Ray() { Origin = new Vector2(6, 0), Direction = new Vector2(0, 1), Magnitude = 50f }, 126 | ExpectedResult = false, 127 | Description = "Ray that flies to the right of the circle" 128 | }, 129 | new CircleTestCase() 130 | { 131 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 132 | Ray = new Ray() { Origin = new Vector2(4, 4), Direction = new Vector2(0, 1), Magnitude = 50f }, 133 | ExpectedResult = true, 134 | Description = "Ray from the inside the circle" 135 | }, 136 | new CircleTestCase() 137 | { 138 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 0.1f}, 139 | Ray = new Ray() { Origin = new Vector2(4, 4), Direction = new Vector2(0, 1), Magnitude = 50f }, 140 | ExpectedResult = true, 141 | Description = "Ray from the inside of a smaller the circle" 142 | }, 143 | new CircleTestCase() 144 | { 145 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 0.001f}, 146 | Ray = new Ray() { Origin = new Vector2(4, 4), Direction = new Vector2(0, 1), Magnitude = 50f }, 147 | ExpectedResult = true, 148 | Description = "Ray from the inside of a tiny circle" 149 | }, 150 | new CircleTestCase() 151 | { 152 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 0.001f}, 153 | Ray = new Ray() { Origin = new Vector2(0, 0), Direction = new Vector2(1, 1), Magnitude = 1f }, 154 | ExpectedResult = false, 155 | Description = "Ray end before reaching intersection" 156 | }, 157 | new CircleTestCase() 158 | { 159 | Circle = new Circle() { Position = new Vector2(4, 4), Radius = 1f}, 160 | Ray = new Ray() { Origin = new Vector2(0, 0), Direction = new Vector2(1, 1), Magnitude = 1f }, 161 | ExpectedResult = false, 162 | Description = "Ray end before reaching intersection" 163 | } 164 | }; 165 | 166 | [Test] 167 | [TestCaseSource("testCases")] 168 | public void TestIntersect(CircleTestCase testCase) 169 | { 170 | Console.WriteLine(testCase.Description); 171 | Assert.AreEqual(testCase.Circle.Intersects(testCase.Ray), testCase.ExpectedResult, testCase.Description); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea 7 | 8 | # ============================================= 9 | # Visual Studio & Associated technologies 10 | # ============================================= 11 | 12 | ## Ignore Visual Studio temporary files, build results, and 13 | ## files generated by popular Visual Studio add-ons. 14 | ## 15 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 16 | 17 | # User-specific files 18 | *.suo 19 | *.user 20 | *.userosscache 21 | *.sln.docstates 22 | 23 | # User-specific files (MonoDevelop/Xamarin Studio) 24 | *.userprefs 25 | 26 | # Build results 27 | [Dd]ebug/ 28 | [Dd]ebugPublic/ 29 | [Rr]elease/ 30 | [Rr]eleases/ 31 | x64/ 32 | x86/ 33 | bld/ 34 | [Bb]in/ 35 | [Oo]bj/ 36 | [Ll]og/ 37 | 38 | # Visual Studio 2015 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUNIT 48 | *.VisualState.xml 49 | TestResult.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | **/Properties/launchSettings.json 64 | 65 | *_i.c 66 | *_p.c 67 | *_i.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.pch 72 | *.pdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # TFS 2012 Local Workspace 111 | $tf/ 112 | 113 | # Guidance Automation Toolkit 114 | *.gpState 115 | 116 | # ReSharper is a .NET coding add-in 117 | _ReSharper*/ 118 | *.[Rr]e[Ss]harper 119 | *.DotSettings.user 120 | 121 | # JustCode is a .NET coding add-in 122 | .JustCode 123 | 124 | # TeamCity is a build add-in 125 | _TeamCity* 126 | 127 | # DotCover is a Code Coverage Tool 128 | *.dotCover 129 | 130 | # Visual Studio code coverage results 131 | *.coverage 132 | *.coveragexml 133 | 134 | # NCrunch 135 | _NCrunch_* 136 | .*crunch*.local.xml 137 | nCrunchTemp_* 138 | 139 | # MightyMoose 140 | *.mm.* 141 | AutoTest.Net/ 142 | 143 | # Web workbench (sass) 144 | .sass-cache/ 145 | 146 | # Installshield output folder 147 | [Ee]xpress/ 148 | 149 | # DocProject is a documentation generator add-in 150 | DocProject/buildhelp/ 151 | DocProject/Help/*.HxT 152 | DocProject/Help/*.HxC 153 | DocProject/Help/*.hhc 154 | DocProject/Help/*.hhk 155 | DocProject/Help/*.hhp 156 | DocProject/Help/Html2 157 | DocProject/Help/html 158 | 159 | # Click-Once directory 160 | publish/ 161 | 162 | # Publish Web Output 163 | *.[Pp]ublish.xml 164 | *.azurePubxml 165 | # but database connection strings (with potential passwords) will be unencrypted 166 | *.pubxml 167 | *.publishproj 168 | 169 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 170 | # checkin your Azure Web App publish settings, but sensitive information contained 171 | # in these scripts will be unencrypted 172 | PublishScripts/ 173 | 174 | # NuGet Packages 175 | *.nupkg 176 | # The packages folder can be ignored because of Package Restore 177 | **/packages/* 178 | # except build/, which is used as an MSBuild target. 179 | !**/packages/build/ 180 | # Uncomment if necessary however generally it will be regenerated when needed 181 | #!**/packages/repositories.config 182 | # NuGet v3's project.json files produces more ignorable files 183 | *.nuget.props 184 | *.nuget.targets 185 | 186 | # Microsoft Azure Build Output 187 | csx/ 188 | *.build.csdef 189 | 190 | # Microsoft Azure Emulator 191 | ecf/ 192 | rcf/ 193 | 194 | # Windows Store app package directories and files 195 | AppPackages/ 196 | BundleArtifacts/ 197 | Package.StoreAssociation.xml 198 | _pkginfo.txt 199 | *.appx 200 | 201 | # Visual Studio cache files 202 | # files ending in .cache can be ignored 203 | *.[Cc]ache 204 | # but keep track of directories ending in .cache 205 | !*.[Cc]ache/ 206 | 207 | # Others 208 | ClientBin/ 209 | ~$* 210 | *~ 211 | *.dbmdl 212 | *.dbproj.schemaview 213 | *.jfm 214 | *.pfx 215 | *.publishsettings 216 | orleans.codegen.cs 217 | 218 | # Since there are multiple workflows, uncomment next line to ignore bower_components 219 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 220 | #bower_components/ 221 | 222 | # RIA/Silverlight projects 223 | Generated_Code/ 224 | 225 | # Backup & report files from converting an old project file 226 | # to a newer Visual Studio version. Backup files are not needed, 227 | # because we have git ;-) 228 | _UpgradeReport_Files/ 229 | Backup*/ 230 | UpgradeLog*.XML 231 | UpgradeLog*.htm 232 | 233 | # SQL Server files 234 | *.mdf 235 | *.ldf 236 | *.ndf 237 | 238 | # Business Intelligence projects 239 | *.rdl.data 240 | *.bim.layout 241 | *.bim_*.settings 242 | 243 | # Microsoft Fakes 244 | FakesAssemblies/ 245 | 246 | # GhostDoc plugin setting file 247 | *.GhostDoc.xml 248 | 249 | # Node.js Tools for Visual Studio 250 | .ntvs_analysis.dat 251 | node_modules/ 252 | 253 | # Typescript v1 declaration files 254 | typings/ 255 | 256 | # Visual Studio 6 build log 257 | *.plg 258 | 259 | # Visual Studio 6 workspace options file 260 | *.opt 261 | 262 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 263 | *.vbw 264 | 265 | # Visual Studio LightSwitch build output 266 | **/*.HTMLClient/GeneratedArtifacts 267 | **/*.DesktopClient/GeneratedArtifacts 268 | **/*.DesktopClient/ModelManifest.xml 269 | **/*.Server/GeneratedArtifacts 270 | **/*.Server/ModelManifest.xml 271 | _Pvt_Extensions 272 | 273 | # Paket dependency manager 274 | .paket/paket.exe 275 | paket-files/ 276 | 277 | # FAKE - F# Make 278 | .fake/ 279 | 280 | # CodeRush 281 | .cr/ 282 | 283 | # Python Tools for Visual Studio (PTVS) 284 | __pycache__/ 285 | *.pyc 286 | 287 | # Cake - Uncomment if you are using it 288 | # tools/** 289 | # !tools/packages.config 290 | 291 | # Tabs Studio 292 | *.tss 293 | 294 | # Telerik's JustMock configuration file 295 | *.jmconfig 296 | 297 | # BizTalk build output 298 | *.btp.cs 299 | *.btm.cs 300 | *.odx.cs 301 | *.xsd.cs 302 | 303 | # ========================= 304 | # Windows detritus 305 | # ========================= 306 | 307 | # Windows thumbnail cache files 308 | Thumbs.db 309 | ehthumbs.db 310 | ehthumbs_vista.db 311 | 312 | # Dump file 313 | *.stackdump 314 | 315 | # Folder config file 316 | Desktop.ini 317 | 318 | # Recycle Bin used on file shares 319 | $RECYCLE.BIN/ 320 | 321 | # Windows Installer files 322 | *.cab 323 | *.msi 324 | *.msm 325 | *.msp 326 | 327 | # Windows shortcuts 328 | *.lnk 329 | 330 | # =========== 331 | # Macintosh 332 | # =========== 333 | 334 | # General 335 | *.DS_Store 336 | .AppleDouble 337 | .LSOverride 338 | 339 | # Icon must end with two \r 340 | Icon 341 | 342 | 343 | # Thumbnails 344 | ._* 345 | 346 | # Files that might appear in the root of a volume 347 | .DocumentRevisions-V100 348 | .fseventsd 349 | .Spotlight-V100 350 | .TemporaryItems 351 | .Trashes 352 | .VolumeIcon.icns 353 | .com.apple.timemachine.donotpresent 354 | 355 | # Directories potentially created on remote AFP share 356 | .AppleDB 357 | .AppleDesktop 358 | Network Trash Folder 359 | Temporary Items 360 | .apdisk 361 | 362 | # =================================================== 363 | # Exclude F# project specific directories and files 364 | # =================================================== 365 | 366 | # NuGet Packages Directory 367 | packages/ 368 | 369 | # Generated documentation folder 370 | docs/output/ 371 | 372 | # Temp folder used for publishing docs 373 | temp/ 374 | 375 | # Test results produced by build 376 | TestResults.xml 377 | output.mlpd 378 | coverage.xml 379 | 380 | # Nuget outputs 381 | nuget/*.nupkg 382 | release.cmd 383 | release.sh 384 | localpackages/ 385 | paket-files 386 | *.orig 387 | .paket/paket.exe 388 | docs/content/license.md 389 | docs/content/release-notes.md 390 | .fake 391 | docs/tools/FSharp.Formatting.svclog 392 | 393 | # =========== 394 | # Covers JetBrains IDEs: Rider, IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 395 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 396 | # =========== 397 | 398 | # User-specific stuff: 399 | .idea/**/workspace.xml 400 | .idea/**/tasks.xml 401 | .idea/dictionaries 402 | 403 | # Sensitive or high-churn files: 404 | .idea/**/dataSources/ 405 | .idea/**/dataSources.ids 406 | .idea/**/dataSources.xml 407 | .idea/**/dataSources.local.xml 408 | .idea/**/sqlDataSources.xml 409 | .idea/**/dynamic.xml 410 | .idea/**/uiDesigner.xml 411 | 412 | # Gradle: 413 | .idea/**/gradle.xml 414 | .idea/**/libraries 415 | 416 | # CMake 417 | cmake-build-debug/ 418 | 419 | # Mongo Explorer plugin: 420 | .idea/**/mongoSettings.xml 421 | 422 | ## File-based project format: 423 | *.iws 424 | 425 | ## Plugin-specific files: 426 | 427 | # IntelliJ 428 | /out/ 429 | 430 | # mpeltonen/sbt-idea plugin 431 | .idea_modules/ 432 | 433 | # JIRA plugin 434 | atlassian-ide-plugin.xml 435 | 436 | # Cursive Clojure plugin 437 | .idea/replstate.xml 438 | 439 | # Crashlytics plugin (for Android Studio and IntelliJ) 440 | com_crashlytics_export_strings.xml 441 | crashlytics.properties 442 | crashlytics-build.properties 443 | fabric.properties 444 | 445 | # ============ 446 | # MonoDevelop 447 | # ============ 448 | 449 | #User Specific 450 | *.userprefs 451 | *.usertasks 452 | 453 | #Mono Project Files 454 | *.pidb 455 | *.resources 456 | test-results/ 457 | 458 | # ================ 459 | # Linux-specific 460 | # ================ 461 | 462 | *~ 463 | 464 | # temporary files which can be created if a process still has a handle open of a deleted file 465 | .fuse_hidden* 466 | 467 | # KDE directory preferences 468 | .directory 469 | 470 | # Linux trash folder which might appear on any partition or disk 471 | .Trash-* 472 | 473 | # .nfs files are created when an open file is removed but is still being accessed 474 | .nfs* 475 | 476 | # ============ 477 | # VS Code 478 | # ============ 479 | 480 | .vscode/* 481 | !.vscode/settings.json 482 | !.vscode/tasks.json 483 | !.vscode/launch.json 484 | !.vscode/extensions.json 485 | 486 | # ============ 487 | # Emacs 488 | # ============ 489 | 490 | # -*- mode: gitignore; -*- 491 | *~ 492 | \#*\# 493 | /.emacs.desktop 494 | /.emacs.desktop.lock 495 | *.elc 496 | auto-save-list 497 | tramp 498 | .\#* 499 | 500 | # Org-mode 501 | .org-id-locations 502 | *_archive 503 | 504 | # flymake-mode 505 | *_flymake.* 506 | 507 | # eshell files 508 | /eshell/history 509 | /eshell/lastdir 510 | 511 | # elpa packages 512 | /elpa/ 513 | 514 | # reftex files 515 | *.rel 516 | 517 | # AUCTeX auto folder 518 | /auto/ 519 | 520 | # cask packages 521 | .cask/ 522 | dist/ 523 | 524 | # Flycheck 525 | flycheck_*.el 526 | 527 | # server auth directory 528 | /server/ 529 | 530 | # projectiles files 531 | .projectile 532 | 533 | # directory configuration 534 | .dir-locals.el 535 | 536 | # =========== 537 | # Vim 538 | # =========== 539 | 540 | # Swap 541 | [._]*.s[a-v][a-z] 542 | [._]*.sw[a-p] 543 | [._]s[a-v][a-z] 544 | [._]sw[a-p] 545 | 546 | # Session 547 | Session.vim 548 | 549 | # Temporary 550 | .netrwhist 551 | *~ 552 | # Auto-generated tag files 553 | tags -------------------------------------------------------------------------------- /infogr-raytracer/Surface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using OpenTK.Graphics.OpenGL; 5 | using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat; 6 | 7 | namespace infogr_raytracer 8 | { 9 | public class Surface 10 | { 11 | public readonly int Width; 12 | public readonly int Height; 13 | public int[] Pixels; 14 | 15 | // Font 16 | private static bool _fontReady = false; 17 | private static Surface _font; 18 | private static int[] _fontRedir; 19 | 20 | // OpenGL values for rendering the screen's texture 21 | private readonly float[] _vertices = 22 | { 23 | // Position Texture coordinates 24 | 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // top right 25 | 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // bottom right 26 | -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // bottom left 27 | -1.0f, 1.0f, 0.0f, 0.0f, 0.0f // top left 28 | }; 29 | 30 | private readonly uint[] _indices = 31 | { 32 | 0, 1, 3, 33 | 1, 2, 3 34 | }; 35 | 36 | private int _vertexBufferObject; 37 | private int _vertexArrayObject; 38 | private int _elementBufferObject; 39 | 40 | private Shader _shader; 41 | private int _textureId; 42 | 43 | 44 | /// 45 | /// Constructs a Surface with a specified width and height 46 | /// 47 | /// The width of the Surface 48 | /// The height of the Surface 49 | public Surface( int w, int h ) 50 | { 51 | Width = w; 52 | Height = h; 53 | Pixels = new int[w * h]; 54 | 55 | CreateTexture(); 56 | } 57 | 58 | 59 | /// 60 | /// Creates the OpenGL VBO, EBO, VAO, Shader, and Texture needed for rendering the Surface. 61 | /// 62 | private void CreateTexture() 63 | { 64 | 65 | _vertexBufferObject = GL.GenBuffer(); 66 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 67 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 68 | 69 | _elementBufferObject = GL.GenBuffer(); 70 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 71 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 72 | 73 | _vertexArrayObject = GL.GenVertexArray(); 74 | GL.BindVertexArray(_vertexArrayObject); 75 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 76 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 77 | 78 | _textureId = GenTexture(); 79 | GL.BindTexture(TextureTarget.Texture2D, _textureId); 80 | 81 | _shader = new Shader("shaders/shader.vert", "shaders/shader.frag"); 82 | _shader.Use(); 83 | 84 | int vertexLocation = _shader.GetAttribLocation("aPosition"); 85 | GL.EnableVertexAttribArray(vertexLocation); 86 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 87 | 88 | int texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 89 | GL.EnableVertexAttribArray(texCoordLocation); 90 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 91 | } 92 | 93 | /// 94 | /// Constructs a Surface using a file 95 | /// 96 | /// The filename of the surface 97 | public Surface( string fileName ) 98 | { 99 | Bitmap bmp = new Bitmap( fileName ); 100 | Width = bmp.Width; 101 | Height = bmp.Height; 102 | Pixels = new int[Width * Height]; 103 | BitmapData data = bmp.LockBits( new System.Drawing.Rectangle( 0, 0, Width, Height ), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb ); 104 | IntPtr ptr = data.Scan0; 105 | System.Runtime.InteropServices.Marshal.Copy( data.Scan0, Pixels, 0, Width * Height ); 106 | bmp.UnlockBits( data ); 107 | } 108 | 109 | /// 110 | /// Creates an OpenGL texture 111 | /// 112 | /// An ID of the texture provided by OpenGL 113 | public int GenTexture() 114 | { 115 | int id = GL.GenTexture(); 116 | GL.BindTexture( TextureTarget.Texture2D, id ); 117 | GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear ); 118 | GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear ); 119 | GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, Pixels ); 120 | return id; 121 | } 122 | 123 | /// 124 | /// Clears the surface to a specified color 125 | /// 126 | /// The color that the surface should be cleared to 127 | public void Clear( int c ) 128 | { 129 | for( int s = Width * Height, p = 0; p < s; p++ ) Pixels[p] = c; 130 | } 131 | 132 | // copy the surface to another surface 133 | /// 134 | /// Copy the surface to another surface 135 | /// 136 | /// The surface that should be copied to 137 | /// 138 | /// 139 | public void CopyTo( Surface target, int x = 0, int y = 0 ) 140 | { 141 | int src = 0; 142 | int dst = 0; 143 | int srcwidth = Width; 144 | int srcheight = Height; 145 | int dstwidth = target.Width; 146 | int dstheight = target.Height; 147 | if( (srcwidth + x) > dstwidth ) srcwidth = dstwidth - x; 148 | if( (srcheight + y) > dstheight ) srcheight = dstheight - y; 149 | if( x < 0 ) 150 | { 151 | src -= x; 152 | srcwidth += x; 153 | x = 0; 154 | } 155 | if( y < 0 ) 156 | { 157 | src -= y * Width; 158 | srcheight += y; 159 | y = 0; 160 | } 161 | if( (srcwidth > 0) && (srcheight > 0) ) 162 | { 163 | dst += x + dstwidth * y; 164 | for( int v = 0; v < srcheight; v++ ) 165 | { 166 | for( int u = 0; u < srcwidth; u++ ) target.Pixels[dst + u] = Pixels[src + u]; 167 | dst += dstwidth; 168 | src += Width; 169 | } 170 | } 171 | } 172 | 173 | /// 174 | /// Draw a rectangle 175 | /// 176 | /// The X component of the top-left coordinate 177 | /// The Y component of the top-left coordinate 178 | /// The X component of the bottom-right coordinate 179 | /// The Y component of the bottom-right coordinate 180 | /// The color that the bar should be drawn in 181 | public void Box( int x1, int y1, int x2, int y2, int c ) 182 | { 183 | int dest = y1 * Width; 184 | for( int y = y1; y <= y2; y++, dest += Width ) 185 | { 186 | Pixels[dest + x1] = c; 187 | Pixels[dest + x2] = c; 188 | } 189 | int dest1 = y1 * Width; 190 | int dest2 = y2 * Width; 191 | for( int x = x1; x <= x2; x++ ) 192 | { 193 | Pixels[dest1 + x] = c; 194 | Pixels[dest2 + x] = c; 195 | } 196 | } 197 | 198 | /// 199 | /// Draw a solid bar 200 | /// 201 | /// The X component of the top-left coordinate 202 | /// The Y component of the top-left coordinate 203 | /// The X component of the bottom-right coordinate 204 | /// The Y component of the bottom-right coordinate 205 | /// The color that the bar should be drawn in 206 | public void Bar( int x1, int y1, int x2, int y2, int c ) 207 | { 208 | int dest = y1 * Width; 209 | for( int y = y1; y <= y2; y++, dest += Width ) for( int x = x1; x <= x2; x++ ) 210 | { 211 | Pixels[dest + x] = c; 212 | } 213 | } 214 | /// 215 | /// helper function for line clipping 216 | /// 217 | int OUTCODE( int x, int y ) 218 | { 219 | int xmin = 0, ymin = 0, xmax = Width - 1, ymax = Height - 1; 220 | return (((x) < xmin) ? 1 : (((x) > xmax) ? 2 : 0)) + (((y) < ymin) ? 4 : (((y) > ymax) ? 8 : 0)); 221 | } 222 | 223 | /// 224 | /// Draw a line, clipped to the window 225 | /// 226 | /// The X component of the top-left coordinate 227 | /// The Y component of the top-left coordinate 228 | /// The X component of the bottom-right coordinate 229 | /// The Y component of the bottom-right coordinate 230 | /// The color that the bar should be drawn in 231 | public void Line( int x1, int y1, int x2, int y2, int c ) 232 | { 233 | int xmin = 0, ymin = 0, xmax = Width - 1, ymax = Height - 1; 234 | int c0 = OUTCODE( x1, y1 ), c1 = OUTCODE( x2, y2 ); 235 | bool accept = false; 236 | while( true ) 237 | { 238 | if( c0 == 0 && c1 == 0 ) { accept = true; break; } 239 | else if( (c0 & c1) > 0 ) break; 240 | else 241 | { 242 | int x = 0, y = 0; 243 | int co = (c0 > 0) ? c0 : c1; 244 | if( (co & 8) > 0 ) { x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1); y = ymax; } 245 | else if( (co & 4) > 0 ) { x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1); y = ymin; } 246 | else if( (co & 2) > 0 ) { y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1); x = xmax; } 247 | else if( (co & 1) > 0 ) { y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1); x = xmin; } 248 | if( co == c0 ) { x1 = x; y1 = y; c0 = OUTCODE( x1, y1 ); } 249 | else { x2 = x; y2 = y; c1 = OUTCODE( x2, y2 ); } 250 | } 251 | } 252 | if( !accept ) return; 253 | if( Math.Abs( x2 - x1 ) >= Math.Abs( y2 - y1 ) ) 254 | { 255 | if( x2 < x1 ) { int h = x1; x1 = x2; x2 = h; h = y2; y2 = y1; y1 = h; } 256 | int l = x2 - x1; 257 | if( l == 0 ) return; 258 | int dy = ((y2 - y1) * 8192) / l; 259 | y1 *= 8192; 260 | for( int i = 0; i < l; i++ ) 261 | { 262 | Pixels[x1++ + (y1 / 8192) * Width] = c; 263 | y1 += dy; 264 | } 265 | } 266 | else 267 | { 268 | if( y2 < y1 ) { int h = x1; x1 = x2; x2 = h; h = y2; y2 = y1; y1 = h; } 269 | int l = y2 - y1; 270 | if( l == 0 ) return; 271 | int dx = ((x2 - x1) * 8192) / l; 272 | x1 *= 8192; 273 | for( int i = 0; i < l; i++ ) 274 | { 275 | Pixels[x1 / 8192 + y1++ * Width] = c; 276 | x1 += dx; 277 | } 278 | } 279 | } 280 | // plot a single pixel 281 | 282 | /// 283 | /// Plot a single pixel 284 | /// 285 | /// The X position of the pixel 286 | /// The Y position of the pixel 287 | /// The Color of the pixel 288 | public void Plot( int x, int y, int c ) 289 | { 290 | if( (x >= 0) && (y >= 0) && (x < Width) && (y < Height) ) 291 | { 292 | Pixels[x + y * Width] = c; 293 | } 294 | } 295 | 296 | /// 297 | /// Print a string with it's top-left coroner at a specified position 298 | /// 299 | /// The text of the string 300 | /// The X component of the top-left corner 301 | /// The Y component of the top-left corner 302 | /// The color of the string 303 | public void Print( string t, int x, int y, int c ) 304 | { 305 | if( !_fontReady ) 306 | { 307 | _font = new Surface( "./assets/font.png" ); 308 | string ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+={}[];:<>,.?/\\ "; 309 | _fontRedir = new int[256]; 310 | for( int i = 0; i < 256; i++ ) _fontRedir[i] = 0; 311 | for( int i = 0; i < ch.Length; i++ ) 312 | { 313 | int l = (int)ch[i]; 314 | _fontRedir[l & 255] = i; 315 | } 316 | _fontReady = true; 317 | } 318 | for( int i = 0; i < t.Length; i++ ) 319 | { 320 | int f = _fontRedir[(int)t[i] & 255]; 321 | int dest = x + i * 12 + y * Width; 322 | int src = f * 12; 323 | for( int v = 0; v < _font.Height; v++, src += _font.Width, dest += Width ) for( int u = 0; u < 12; u++ ) 324 | { 325 | if( (_font.Pixels[src + u] & 0xffffff) != 0 ) Pixels[dest + u] = c; 326 | } 327 | } 328 | } 329 | 330 | public void Draw() 331 | { 332 | // Draw the screen 333 | GL.BindVertexArray(_vertexArrayObject); 334 | GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, 335 | PixelFormat.Bgra, PixelType.UnsignedByte, Pixels); 336 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 337 | } 338 | 339 | /// 340 | /// Unloads all OpenGL buffers, VAOs, and shader programs. 341 | /// 342 | public void Unload() 343 | { 344 | GL.BindBuffer(BufferTarget.ArrayBuffer, 0); 345 | GL.BindVertexArray(0); 346 | GL.UseProgram(0); 347 | 348 | GL.DeleteBuffer(_vertexBufferObject); 349 | GL.DeleteBuffer(_elementBufferObject); 350 | GL.DeleteVertexArray(_vertexArrayObject); 351 | 352 | _shader.Dispose(); 353 | } 354 | } 355 | 356 | public class Sprite 357 | { 358 | Surface bitmap; 359 | static public Surface target; 360 | int textureID; 361 | 362 | /// 363 | /// Constructs a sprite form a spcified filename 364 | /// 365 | /// The sprite's filename 366 | public Sprite( string fileName ) 367 | { 368 | bitmap = new Surface( fileName ); 369 | textureID = bitmap.GenTexture(); 370 | } 371 | 372 | 373 | /// 374 | /// Draw a sprite with scaling 375 | /// 376 | /// The X position where the sprite should be drawn 377 | /// The Y position where the sprite should be drawn 378 | /// The scale at which the sprite should be drawn 379 | public void Draw( float x, float y, float scale = 1.0f ) 380 | { 381 | GL.BindTexture( TextureTarget.Texture2D, textureID ); 382 | GL.Enable( EnableCap.Blend ); 383 | GL.BlendFunc( BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha ); 384 | GL.Begin( PrimitiveType.Quads ); 385 | float u1 = (x * 2 - 0.5f * scale * bitmap.Width) / target.Width - 1; 386 | float v1 = 1 - (y * 2 - 0.5f * scale * bitmap.Height) / target.Height; 387 | float u2 = ((x + 0.5f * scale * bitmap.Width) * 2) / target.Width - 1; 388 | float v2 = 1 - ((y + 0.5f * scale * bitmap.Height) * 2) / target.Height; 389 | GL.TexCoord2( 0.0f, 1.0f ); GL.Vertex2( u1, v2 ); 390 | GL.TexCoord2( 1.0f, 1.0f ); GL.Vertex2( u2, v2 ); 391 | GL.TexCoord2( 1.0f, 0.0f ); GL.Vertex2( u2, v1 ); 392 | GL.TexCoord2( 0.0f, 0.0f ); GL.Vertex2( u1, v1 ); 393 | GL.End(); 394 | GL.Disable( EnableCap.Blend ); 395 | } 396 | } 397 | } --------------------------------------------------------------------------------