// // MainWindow.cs // // Author: // Jarl Gullberg // // Copyright (c) 2016 Jarl Gullberg // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using Gdk; using GLWidgetTestGTK3.Data; using GLWidgetTestGTK3.Debug; using GLWidgetTestGTK3.World; using Gtk; using UI = Gtk.Builder.ObjectAttribute; using OpenTK; using OpenTK.Mathematics; using OpenTK.Graphics.OpenGL; using KeyPressEventArgs = Gtk.KeyPressEventArgs; namespace GLWidgetTestGTK3 { public partial class MainWindow : Gtk.Window { [UI] readonly Box MainBox; [UI] readonly MenuBar MainMenuBar; [UI] readonly Alignment GLWidgetAlignment; [UI] readonly GLWidget MainGLWidget; private bool GLInit; private Scene Scene; private uint VertexArrayID; private uint ColourBufferID; private int ShaderProgramID; /* Default camera positions */ private Vector3 cameraPosition; private Vector3 cameraLookDirection; private Vector3 cameraRightVector; private Vector3 cameraUpVector; private float horizontalViewAngle; private float verticalViewAngle; private const float defaultFOV = 45.0f; private const float defaultMovementSpeed = 10.0f; private const float defaultCameraSpeed = 0.5f; // Other variables private bool wantsToMove = false; private float deltaTime; private int mouseXLastFrame; private int mouseYLastFrame; private float rightAxis; private float forwardAxis; private static readonly float[] cubeColourBufferData = { 0.583f, 0.771f, 0.014f, 0.609f, 0.115f, 0.436f, 0.327f, 0.483f, 0.844f, 0.822f, 0.569f, 0.201f, 0.435f, 0.602f, 0.223f, 0.310f, 0.747f, 0.185f, 0.597f, 0.770f, 0.761f, 0.559f, 0.436f, 0.730f, 0.359f, 0.583f, 0.152f, 0.483f, 0.596f, 0.789f, 0.559f, 0.861f, 0.639f, 0.195f, 0.548f, 0.859f, 0.014f, 0.184f, 0.576f, 0.771f, 0.328f, 0.970f, 0.406f, 0.615f, 0.116f, 0.676f, 0.977f, 0.133f, 0.971f, 0.572f, 0.833f, 0.140f, 0.616f, 0.489f, 0.997f, 0.513f, 0.064f, 0.945f, 0.719f, 0.592f, 0.543f, 0.021f, 0.978f, 0.279f, 0.317f, 0.505f, 0.167f, 0.620f, 0.077f, 0.347f, 0.857f, 0.137f, 0.055f, 0.953f, 0.042f, 0.714f, 0.505f, 0.345f, 0.783f, 0.290f, 0.734f, 0.722f, 0.645f, 0.174f, 0.302f, 0.455f, 0.848f, 0.225f, 0.587f, 0.040f, 0.517f, 0.713f, 0.338f, 0.053f, 0.959f, 0.120f, 0.393f, 0.621f, 0.362f, 0.673f, 0.211f, 0.457f, 0.820f, 0.883f, 0.371f, 0.982f, 0.099f, 0.879f }; public static MainWindow Create() { Builder builder = new Builder(null, "GLWidgetTestGTK3.interfaces.MainWindow.glade", null); return new MainWindow(builder, builder.GetObject("MainWindow").Handle); } protected MainWindow(Builder builder, IntPtr handle) : base(handle) { builder.Autoconnect(this); DeleteEvent += OnDeleteEvent; this.GLInit = false; ResetCamera(); this.MainGLWidget = new GLWidget() { GLVersionMajor = 3, GLVersionMinor = 3, }; this.MainGLWidget.Events |= EventMask.ButtonPressMask | EventMask.ButtonReleaseMask | EventMask.KeyPressMask | EventMask.KeyReleaseMask; this.MainGLWidget.Initialized += OnViewportInitialized; this.MainGLWidget.ButtonPressEvent += OnViewportButtonPressed; this.MainGLWidget.ButtonReleaseEvent += OnViewportButtonReleased; this.MainGLWidget.KeyPressEvent += OnViewportKeyPressed; this.MainGLWidget.KeyReleaseEvent += OnViewportKeyReleased; // Add the GL widget to the UI this.GLWidgetAlignment.Add(this.MainGLWidget); this.GLWidgetAlignment.ShowAll(); } private List FloatArrayToVertexList(float[] vertexPositions) { if ((vertexPositions.Length % 3) != 0) { throw new ArgumentException("The input array must be of a 3-multiple length. Incomplete entries are not allowed.", nameof(vertexPositions)); } List convertedVertices = new List(); for (int i = 0; i < vertexPositions.Length; i += 3) { Vector3 Position = new Vector3(vertexPositions[i], vertexPositions[i + 1], vertexPositions[i + 2]); Vertex vertex = new Vertex(Position); convertedVertices.Add(vertex); } return convertedVertices; } private void OnViewportKeyReleased(object o, KeyReleaseEventArgs args) { if (args.Event.Type == EventType.KeyRelease) { if( args.Event.Key == Gdk.Key.w || args.Event.Key == Gdk.Key.W) { forwardAxis = 0.0f; } else if( args.Event.Key == Gdk.Key.s || args.Event.Key == Gdk.Key.S) { forwardAxis = 0.0f; } if( args.Event.Key == Gdk.Key.d || args.Event.Key == Gdk.Key.D) { rightAxis = 0.0f; } else if( args.Event.Key == Gdk.Key.a || args.Event.Key == Gdk.Key.A) { rightAxis = 0.0f; } } } private void OnViewportKeyPressed(object o, KeyPressEventArgs args) { if (args.Event.Type == EventType.KeyPress) { if( args.Event.Key == Gdk.Key.w || args.Event.Key == Gdk.Key.W) { forwardAxis = 1.0f; } else if( args.Event.Key == Gdk.Key.s || args.Event.Key == Gdk.Key.S) { forwardAxis = -1.0f; } if( args.Event.Key == Gdk.Key.d || args.Event.Key == Gdk.Key.D) { rightAxis = 1.0f; } else if( args.Event.Key == Gdk.Key.a || args.Event.Key == Gdk.Key.A) { rightAxis = -1.0f; } if( args.Event.Key == Gdk.Key.r || args.Event.Key == Gdk.Key.R) { if (wantsToMove) { ResetCamera(); } } if (args.Event.Key == Gdk.Key.Escape) { Application.Quit(); } } } private void ResetCamera() { this.cameraPosition = new Vector3(0.0f, 0.0f, 5.0f); this.horizontalViewAngle = MathHelper.DegreesToRadians(180.0f); this.verticalViewAngle = MathHelper.DegreesToRadians(0.0f); this.cameraLookDirection = new Vector3( (float)(Math.Cos(this.verticalViewAngle) * Math.Sin(this.horizontalViewAngle)), (float)Math.Sin(this.verticalViewAngle), (float)(Math.Cos(this.verticalViewAngle) * Math.Cos(this.horizontalViewAngle))); this.cameraRightVector = new Vector3( (float)Math.Sin(horizontalViewAngle - MathHelper.PiOver2), 0, (float)Math.Cos(horizontalViewAngle - MathHelper.PiOver2)); this.cameraUpVector = Vector3.Cross(cameraRightVector, cameraLookDirection); } [GLib.ConnectBefore] private void OnViewportButtonReleased(object o, ButtonReleaseEventArgs args) { // Right click is released if (args.Event.Type == EventType.ButtonRelease && args.Event.Button == 3) { // Return the mouse pointer this.Window.Cursor = new Cursor(CursorType.Arrow); this.GrabFocus(); this.wantsToMove = false; } } [GLib.ConnectBefore] private void OnViewportButtonPressed(object o, ButtonPressEventArgs args) { // Right click is pressed if (args.Event.Type == EventType.ButtonPress && args.Event.Button == 3) { // Hide the mouse pointer this.Window.Cursor = new Cursor(CursorType.BlankCursor); this.MainGLWidget.GrabFocus(); this.wantsToMove = true; this.MainGLWidget.GetPointer(out this.mouseXLastFrame, out this.mouseYLastFrame); } } protected virtual void OnViewportInitialized(object sender, EventArgs e) { var version = GL.GetString(StringName.Version); this.Scene = new Scene(); // Create the cube actor Actor cubeActor = new Actor(new Mesh(FloatArrayToVertexList(Shapes.UnindexedCube))); Actor cubeActor1 = new Actor(new Mesh(FloatArrayToVertexList(Shapes.UnindexedCube))); Actor cubeActor2 = new Actor(new Mesh(FloatArrayToVertexList(Shapes.UnindexedCube))); Actor cubeActor3 = new Actor(new Mesh(FloatArrayToVertexList(Shapes.UnindexedCube))); // Translate the cube actor cubeActor.Transform.Translation = new Vector3(4.0f, 0.0f, 0.0f); cubeActor1.Transform.Translation = new Vector3(0.0f, 4.0f, 0.0f); cubeActor2.Transform.Translation = new Vector3(0.0f, -4.0f, 0.0f); cubeActor3.Transform.Translation = new Vector3(-4.0f, 0.0f, 0.0f); Actor triangleActor = new Actor(new Mesh(FloatArrayToVertexList(Shapes.UnindexedTriangle))); this.Scene.Actors.Add(cubeActor); this.Scene.Actors.Add(cubeActor1); this.Scene.Actors.Add(cubeActor2); this.Scene.Actors.Add(cubeActor3); this.Scene.Actors.Add(triangleActor); // Generate the colour buffer GL.GenBuffers(1, out ColourBufferID); // Upload the colour data GL.BindBuffer(BufferTarget.ArrayBuffer, ColourBufferID); GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(cubeColourBufferData.Length * sizeof(float)), cubeColourBufferData, BufferUsageHint.StaticDraw); // Make sure we use the depth buffer when drawing GL.Enable(EnableCap.DepthTest); //GL.DepthFunc(DepthFunction.Less); // Enable backface culling for performance reasons //GL.Enable(EnableCap.CullFace); // Render wireframe //GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); // Initialize the viewport int widgetWidth = this.GLWidgetAlignment.AllocatedWidth; int widgetHeight = this.GLWidgetAlignment.AllocatedHeight; GL.Viewport(0, 0, widgetWidth, widgetHeight); GL.ClearColor(0.522f, 0.573f, 0.678f, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // Load the shaders ShaderProgramID = LoadShaders(); // Add idle event handler to process rendering whenever and as long as time is available. GLInit = true; //GLib.Idle.Add(OnIdleProcessMain); System.Threading.Thread thread = new System.Threading.Thread(Start); thread.Start(); } protected void RenderFrame() { MainGLWidget.MakeCurrent(); // Make sure the viewport is accurate for the current widget size on screen int widgetWidth = this.GLWidgetAlignment.AllocatedWidth; int widgetHeight = this.GLWidgetAlignment.AllocatedHeight; var error = GL.GetError(); GL.Viewport(0, 0, widgetWidth, widgetHeight); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // Activate the shaders GL.UseProgram(ShaderProgramID); // See if there's any movement to compute if (wantsToMove) { int mouseX; int mouseY; this.MainGLWidget.GetPointer(out mouseX, out mouseY); this.horizontalViewAngle += defaultCameraSpeed * this.deltaTime * (float)(mouseXLastFrame - mouseX); this.verticalViewAngle += defaultCameraSpeed * this.deltaTime * (float)(mouseYLastFrame - mouseY); if (verticalViewAngle > MathHelper.DegreesToRadians(90.0f)) { verticalViewAngle = MathHelper.DegreesToRadians(90.0f); } else if (verticalViewAngle < MathHelper.DegreesToRadians(-90.0f)) { verticalViewAngle = MathHelper.DegreesToRadians(-90.0f); } mouseXLastFrame = mouseX; mouseYLastFrame = mouseY; // Compute the look direction this.cameraLookDirection = new Vector3( (float)(Math.Cos(this.verticalViewAngle) * Math.Sin(this.horizontalViewAngle)), (float)Math.Sin(this.verticalViewAngle), (float)(Math.Cos(this.verticalViewAngle) * Math.Cos(this.horizontalViewAngle))); this.cameraRightVector = new Vector3( (float)Math.Sin(this.horizontalViewAngle - MathHelper.PiOver2), 0, (float)Math.Cos(this.horizontalViewAngle - MathHelper.PiOver2)); this.cameraUpVector = Vector3.Cross(this.cameraRightVector, this.cameraLookDirection); // Perform any movement if (forwardAxis > 0) { this.cameraPosition += this.cameraLookDirection * deltaTime * defaultMovementSpeed; } if (forwardAxis < 0) { this.cameraPosition -= this.cameraLookDirection * deltaTime * defaultMovementSpeed; } if (rightAxis > 0) { this.cameraPosition += this.cameraRightVector * deltaTime * defaultMovementSpeed; } if (rightAxis < 0) { this.cameraPosition -= this.cameraRightVector * deltaTime * defaultMovementSpeed; } } // Calculate the relative viewpoint float aspectRatio = (float)widgetWidth / (float)widgetHeight; Matrix4 Projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(defaultFOV), aspectRatio, 0.1f, 1000.0f); Matrix4 View = Matrix4.LookAt( cameraPosition, cameraPosition + cameraLookDirection, cameraUpVector ); // Enable the colour array GL.EnableVertexAttribArray(1); GL.BindBuffer(BufferTarget.ArrayBuffer, ColourBufferID); GL.VertexAttribPointer( 1, 3, VertexAttribPointerType.Float, false, 0, 0); // Tick the actors before any rendering is done this.Scene.Tick(deltaTime); foreach (Actor actor in this.Scene.Actors) { actor.Render(ShaderProgramID, View, Projection); } // Release the arrays GL.DisableVertexAttribArray(1); //swap MainGLWidget.Swapbuffers(); MainGLWidget.ClearCurrent(); } public void Start() { System.Threading.Thread.Sleep(1000); while (true) { RenderFrame(); System.Threading.Thread.Sleep(5); } } protected bool OnIdleProcessMain() { if (!GLInit) return false; else { // Start deltaTime calculation Stopwatch deltaTimeWatcher = new Stopwatch(); deltaTimeWatcher.Start(); //System.Threading.Tasks.Task.Run(RenderFrame).Wait(); RenderFrame(); // End delta time calculation deltaTimeWatcher.Stop(); this.deltaTime = (float)(deltaTimeWatcher.ElapsedMilliseconds * 0.001f); return true; } } private int LoadShaders() { int VertexShaderID = GL.CreateShader(ShaderType.VertexShader); int FragmentShaderID = GL.CreateShader(ShaderType.FragmentShader); string vertexShaderSourceCode; using (Stream shaderStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("GLWidgetTestGTK3.Shaders.VertexShader.glsl")) { using (StreamReader sr = new StreamReader(shaderStream)) { vertexShaderSourceCode = sr.ReadToEnd(); } } string fragmentShaderSourceCode; using (Stream shaderStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("GLWidgetTestGTK3.Shaders.FragmentShader.glsl")) { using (StreamReader sr = new StreamReader(shaderStream)) { fragmentShaderSourceCode = sr.ReadToEnd(); } } int result = 0; int compilationLogLength; Console.WriteLine("Compiling vertex shader..."); GL.ShaderSource(VertexShaderID, vertexShaderSourceCode); GL.CompileShader(VertexShaderID); GL.GetShader(VertexShaderID, ShaderParameter.CompileStatus, out result); GL.GetShader(VertexShaderID, ShaderParameter.InfoLogLength, out compilationLogLength); if (compilationLogLength > 0) { string compilationLog; GL.GetShaderInfoLog(VertexShaderID, out compilationLog); Console.WriteLine(compilationLog); } Console.WriteLine("Compiling fragment shader..."); GL.ShaderSource(FragmentShaderID, fragmentShaderSourceCode); GL.CompileShader(FragmentShaderID); GL.GetShader(FragmentShaderID, ShaderParameter.CompileStatus, out result); GL.GetShader(FragmentShaderID, ShaderParameter.InfoLogLength, out compilationLogLength); if (compilationLogLength > 0) { string compilationLog; GL.GetShaderInfoLog(FragmentShaderID, out compilationLog); Console.WriteLine(compilationLog); } Console.WriteLine("Linking shader program..."); int shaderProgramID = GL.CreateProgram(); GL.AttachShader(shaderProgramID, VertexShaderID); GL.AttachShader(shaderProgramID, FragmentShaderID); GL.LinkProgram(shaderProgramID); GL.GetProgram(shaderProgramID, ProgramParameter.LinkStatus, out result); GL.GetProgram(shaderProgramID, ProgramParameter.InfoLogLength, out compilationLogLength); if (compilationLogLength > 0) { string compilationLog; GL.GetProgramInfoLog(shaderProgramID, out compilationLog); Console.WriteLine(compilationLog); } // Clean up the shader source code and unlinked object files from graphics memory GL.DetachShader(shaderProgramID, VertexShaderID); GL.DetachShader(shaderProgramID, FragmentShaderID); GL.DeleteShader(VertexShaderID); GL.DeleteShader(FragmentShaderID); return shaderProgramID; } /// /// Handles application shutdown procedures - terminating render threads, cleaning /// up the UI, etc. /// /// Sender. /// The deletion arguments. protected void OnDeleteEvent(object sender, DeleteEventArgs a) { Application.Quit(); a.RetVal = true; } } }