using Assimp; using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; using LearnOpenTK.Common; using OpenTK.Mathematics; using TheLabs.Shapes; 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 SceneNode _rootNode; private SceneNode _characterNode; private SceneNode _buildingNode; private Shader _shader; private Camera _camera; // - Resources that only need to be created once private Mesh _exampleObject; private Mesh _buildingObject; private Texture _texture; private RenderObject _render; private RenderObject _buildingRender; float _rotation = 0.0f; // -- Scene Objects private Vector3 _cameraPosition = new Vector3(0.0f, 0.0f, -3.0f); private Vector3 _cameraFront = new Vector3(0.0f, 0.0f, -1.0f); private Vector3 _cameraUp = Vector3.UnitY; private float _yaw = -90.0f; private float _pitch = 0.0f; private float _cameraSpeed = 2.5f; private float _rotationSpeed = 50.0f; private float _mouseSensitivity = 0.1f; // This prevents a large camera jump when the window first gets focus private bool _firstMove = true; public MyExampleWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) { } // Now, we start initializing OpenGL. protected override void OnLoad() { // This is called when the window is created and is where we can set up OpenGL resources. base.OnLoad(); //GL.Disable(EnableCap.CullFace); //GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); GL.Enable(EnableCap.DepthTest); _camera = new Camera(_cameraPosition, _cameraFront, _cameraUp); _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); _texture = new Texture("Textures/placeholder.png"); _rootNode = new SceneNode(); _buildingObject = ShapeFactory.CreateTexturedCube(); _buildingRender = new RenderObject(_buildingObject, _shader, _texture); _buildingNode = new SceneNode(_buildingRender); _buildingNode.Position = new Vector3(2.0f, -1.0f, 0.0f); _buildingNode.Scale = new Vector3(0.1f, 0.1f, 0.1f); _rootNode.AddChild(_buildingNode); CursorState = CursorState.Grabbed; } // Now that initialization is done, let's create our render loop. protected override void OnRenderFrame(FrameEventArgs e) { // This is called once per frame and is where all the rendering code goes. base.OnRenderFrame(e); // Clear the color buffer and the depth buffer GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit); //_cubeObject.Rotation = Quaternion.FromEulerAngles(_rotation * 0.5f, _rotation, 0); // //_cubeObject2.Rotation = Quaternion.FromEulerAngles(-_rotation * 0.5f, -_rotation, 0); // --- Set up Camera --- Matrix4 view = _camera.LookAt(); float aspectRatio = (float)Size.X / Size.Y; // 2. Add a safety check for divide-by-zero if (Size.Y == 0 || Size.X == 0) { aspectRatio = 1.0f; // Default to 1:1 if window is minimized } Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( MathHelper.DegreesToRadians(45f), aspectRatio, 0.1f, 100.0f ); // --- Draw Scene Objects --- _rootNode.Draw(view, projection, Matrix4.Identity); SwapBuffers(); } protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); _rotation += (float)e.Time; var input = KeyboardState; if (input.IsKeyDown(Keys.Escape)) { Close(); } if (!IsFocused) { _firstMove = true; return; } var mouuse = MouseState; if (_firstMove) { mouuse = MouseState; _firstMove = false; } else { // Get the mouse's movement delta float deltaX = mouuse.Delta.X; float deltaY = mouuse.Delta.Y; // Apply sensitivity _yaw += deltaX * _mouseSensitivity; _pitch -= deltaY * _mouseSensitivity; // Y-axis is inverted } // --- Rotation (Arrow Keys) --- if (input.IsKeyDown(Keys.Left)) { _yaw -= _rotationSpeed * (float)e.Time; } if (input.IsKeyDown(Keys.Right)) { _yaw += _rotationSpeed * (float)e.Time; } if (input.IsKeyDown(Keys.Up)) { _pitch += _rotationSpeed * (float)e.Time; } if (input.IsKeyDown(Keys.Down)) { _pitch -= _rotationSpeed * (float)e.Time; } _pitch = MathHelper.Clamp(_pitch, -89.0f, 89.0f); Vector3 front; _camera._target.X = MathF.Cos(MathHelper.DegreesToRadians(_yaw)) * MathF.Cos(MathHelper.DegreesToRadians(_pitch)); _camera._target.Y = MathF.Sin(MathHelper.DegreesToRadians(_pitch)); _camera._target.Z = MathF.Sin(MathHelper.DegreesToRadians(_yaw)) * MathF.Cos(MathHelper.DegreesToRadians(_pitch)); _cameraFront = Vector3.Normalize(_camera._target); var _cameraRight = Vector3.Normalize(Vector3.Cross(_cameraFront, _camera._up)); if (input.IsKeyDown(Keys.W)) _camera._position += _cameraFront * _cameraSpeed * (float)e.Time; if (input.IsKeyDown(Keys.S)) _camera._position -= _cameraFront * _cameraSpeed * (float)e.Time; if (input.IsKeyDown(Keys.A)) _camera._position -= _cameraRight * _cameraSpeed * (float)e.Time; if (input.IsKeyDown(Keys.D)) _camera._position += _cameraRight * _cameraSpeed * (float)e.Time; } 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); GL.DeleteProgram(_shader.Handle); base.OnUnload(); } } }