From 8d46d85df03193afd7a85132924e3e7899746505 Mon Sep 17 00:00:00 2001 From: zyb3rwolfi Date: Tue, 11 Nov 2025 15:35:10 +0000 Subject: [PATCH] refractor --- TheRepo/TheLabs/Mesh.cs | 132 +++++++++++++++++++++++++++++ TheRepo/TheLabs/MyExampleWindow.cs | 61 ++++++------- TheRepo/TheLabs/RenderObject.cs | 42 +++++++++ TheRepo/TheLabs/ShapeFactory.cs | 118 ++++++++++++++++++++++++++ TheRepo/TheLabs/Texture.cs | 61 +++++++++++++ 5 files changed, 384 insertions(+), 30 deletions(-) create mode 100644 TheRepo/TheLabs/Mesh.cs create mode 100644 TheRepo/TheLabs/RenderObject.cs create mode 100644 TheRepo/TheLabs/ShapeFactory.cs create mode 100644 TheRepo/TheLabs/Texture.cs diff --git a/TheRepo/TheLabs/Mesh.cs b/TheRepo/TheLabs/Mesh.cs new file mode 100644 index 0000000..d2c868b --- /dev/null +++ b/TheRepo/TheLabs/Mesh.cs @@ -0,0 +1,132 @@ +namespace TheLabs; +using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using OpenTK.Windowing.Desktop; +using StbImageSharp; + +public class Mesh : IDisposable +{ + private readonly int _vao; + private readonly int _vbo; + private readonly int _ebo; + private readonly int _vertexCount; + private readonly bool _useIndices; + + // A flexible way to define vertex layout + // (Could be an enum or a struct) + public enum VertexLayout { PosColor, PosTex } + + public Mesh(float[] vertices, uint[] indices, VertexLayout layout) + { + _vertexCount = indices.Length; + _useIndices = true; + + _vao = GL.GenVertexArray(); + GL.BindVertexArray(_vao); + + _vbo = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); + GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); + + _ebo = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo); + GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw); + + // --- Set up Vertex Attributes based on layout --- + if (layout == VertexLayout.PosColor) + { + // layout(location = 0) in shader: vec3 position + var stride = 7 * sizeof(float); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // layout(location = 1) in shader: vec4 color + GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + } + else if (layout == VertexLayout.PosTex) + { + // layout(location = 0) in shader: vec3 position + var stride = 5 * sizeof(float); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // layout(location = 1) in shader: vec2 texCoord + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + } + // ... (you could add more layouts) + + GL.BindVertexArray(0); + } + + // Constructor for non-indexed drawing (like your Circle) + public Mesh(float[] vertices, VertexLayout layout) + { + // Calculate vertex count based on stride + if (layout == VertexLayout.PosColor) + _vertexCount = vertices.Length / 7; + else // PosTex + _vertexCount = vertices.Length / 5; + + _useIndices = false; + _ebo = 0; // No EBO + + _vao = GL.GenVertexArray(); + GL.BindVertexArray(_vao); + + _vbo = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); + GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); + + // --- Set up Vertex Attributes based on layout --- + if (layout == VertexLayout.PosColor) + { + // layout(location = 0) in shader: vec3 position + var stride = 7 * sizeof(float); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // layout(location = 1) in shader: vec4 color + GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + } + else if (layout == VertexLayout.PosTex) + { + // layout(location = 0) in shader: vec3 position + var stride = 5 * sizeof(float); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // layout(location = 1) in shader: vec2 texCoord + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + } + + GL.BindVertexArray(0); + } + + // Draw the mesh. Notice it doesn't know about shaders or matrices! + public void Draw() + { + GL.BindVertexArray(_vao); + + if (_useIndices) + { + GL.DrawElements(PrimitiveType.Triangles, _vertexCount, DrawElementsType.UnsignedInt, 0); + } + else + { + // For your Circle (which used TriangleFan) + GL.DrawArrays(PrimitiveType.TriangleFan, 0, _vertexCount); + } + } + + public void Dispose() + { + GL.DeleteBuffer(_vbo); + GL.DeleteBuffer(_ebo); + GL.DeleteVertexArray(_vao); + } +} \ No newline at end of file diff --git a/TheRepo/TheLabs/MyExampleWindow.cs b/TheRepo/TheLabs/MyExampleWindow.cs index 59a87be..28855bb 100644 --- a/TheRepo/TheLabs/MyExampleWindow.cs +++ b/TheRepo/TheLabs/MyExampleWindow.cs @@ -16,12 +16,15 @@ namespace TheLabs // 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 Cube _cube; - private TexturedCube _texturedCube; - private Circle _circle; - private Cylinder _cylinder; private Shader _shader; + + // - Resources that only need to be created once + private Mesh _cubeMesh; + private Texture _cubeTexture; private float _rotation; + + // -- Scene Objects + private RenderObject _cubeObject; public MyExampleWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -33,20 +36,29 @@ namespace TheLabs { // This is called when the window is created and is where we can set up OpenGL resources. base.OnLoad(); + string shaderVertPath = "Shaders/shader.vert"; + string texturePath = "Textures/placeholder.png"; + + if (!File.Exists(shaderVertPath)) + { + throw new FileNotFoundException($"The shader file was not found at: {Path.GetFullPath(shaderVertPath)}"); + } + if (!File.Exists(texturePath)) + { + throw new FileNotFoundException($"The texture file was not found at: {Path.GetFullPath(texturePath)}"); + } //GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); // Set The background color to a nice blue. GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); - // Enable depth buffering. GL.Enable(EnableCap.DepthTest); - GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - // Create and compile our shader program from the shader source files _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - // Create a cube - // Use the shader program. This is similar to "activating" the shader program. - _shader.Use(); - GL.Uniform1(GL.GetUniformLocation(_shader.Handle, "uTexture"), 0); + _cubeMesh = ShapeFactory.CreateTexturedCube(); + _cubeTexture = new Texture("Textures/placeholder.png"); + _cubeObject = new RenderObject(_cubeMesh, _shader, _cubeTexture); + _cubeObject.Position = new Vector3(-0.75f, 0.0f, 0.0f); + } // Now that initialization is done, let's create our render loop. @@ -56,44 +68,33 @@ namespace TheLabs base.OnRenderFrame(e); // Clear the color buffer and the depth buffer GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit); - // Create the model, view, and projection matrices - Matrix4 model = Matrix4.CreateRotationY(_rotation) * Matrix4.CreateRotationX(_rotation * 0.5f); + _cubeObject.Rotation = Quaternion.FromEulerAngles(_rotation * 0.5f, _rotation, 0); // + + // --- Set up Camera --- Matrix4 view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f); Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( MathHelper.DegreesToRadians(45f), Size.X / (float)Size.Y, 0.1f, 100.0f - ); + ); + + _cubeObject.Draw(view, projection); - // Draw the cube - //_cube = new Cube(); - //_cube.Draw(_shader, view, projection, _rotation); - //_circle.Draw(_shader, view, projection, model); - //_cylinder.Draw(_shader, model, view, projection); - _texturedCube = new TexturedCube(); - _texturedCube.Draw(_shader, view, projection, _rotation); SwapBuffers(); - GL.GetError(); } protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); - + _rotation += (float)e.Time; var input = KeyboardState; if (input.IsKeyDown(Keys.Escape)) { Close(); } - if (input.IsKeyDown(Keys.R)) - { - _rotation += 0.8f * (float)e.Time; // Update rotation only when the condition is met - } else if (input.IsKeyDown(Keys.T)) - { - _rotation -= 0.8f * (float)e.Time; // Update rotation only when the condition is met - } + } protected override void OnResize(ResizeEventArgs e) diff --git a/TheRepo/TheLabs/RenderObject.cs b/TheRepo/TheLabs/RenderObject.cs new file mode 100644 index 0000000..74d8eb3 --- /dev/null +++ b/TheRepo/TheLabs/RenderObject.cs @@ -0,0 +1,42 @@ +using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; + +namespace TheLabs; + +public class RenderObject +{ + public Mesh Mesh; + public Shader Shader; + public Texture Texture; + + public Vector3 Position = Vector3.Zero; + public Quaternion Rotation = Quaternion.Identity; + public Vector3 Scale = Vector3.One; + + public RenderObject(Mesh mesh, Shader shader, Texture texture) + { + Mesh = mesh; + Shader = shader; + Texture = texture; + } + + public void Draw(Matrix4 view, Matrix4 projection) + { + // 1. Activate Shader + Shader.Use(); + Shader.SetInt("uTexture", 0); + // 2. Bind Texture + Texture.Use(); // Assumes you have a Texture class + + // 3. Calculate Model Matrix + Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position); + // 4. Set Uniforms + Shader.SetMatrix4("model", model); + Shader.SetMatrix4("view", view); + Shader.SetMatrix4("projection", projection); + + // 5. Tell the Mesh to draw itself + Mesh.Draw(); + } +} \ No newline at end of file diff --git a/TheRepo/TheLabs/ShapeFactory.cs b/TheRepo/TheLabs/ShapeFactory.cs new file mode 100644 index 0000000..9093274 --- /dev/null +++ b/TheRepo/TheLabs/ShapeFactory.cs @@ -0,0 +1,118 @@ +namespace TheLabs; + +public static class ShapeFactory +{ + public static Mesh CreateTexturedCube() + { + float[] vertices = { + // Front face (z = +0.5) + -0.5f, -0.5f, 0.5f, 0f, 0f, // Bottom-left + 0.5f, -0.5f, 0.5f, 1f, 0f, // Bottom-right + 0.5f, 0.5f, 0.5f, 1f, 1f, // Top-right + -0.5f, 0.5f, 0.5f, 0f, 1f, // Top-left + + // Back face (z = -0.5) + -0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left + 0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-right + 0.5f, 0.5f, -0.5f, 0f, 1f, // Top-right + -0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left + + // Left face (x = -0.5) + -0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-left + -0.5f, -0.5f, 0.5f, 1f, 0f, // Bottom-right + -0.5f, 0.5f, 0.5f, 1f, 1f, // Top-right + -0.5f, 0.5f, -0.5f, 0f, 1f, // Top-left + // Right face (x = +0.5) + 0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left + 0.5f, -0.5f, 0.5f, 0f, 0f, // Bottom-right + 0.5f, 0.5f, 0.5f, 0f, 1f, // Top-right + 0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left + + // Top face (y = +0.5) + -0.5f, 0.5f, -0.5f, 0f, 1f, // Bottom-left + 0.5f, 0.5f, -0.5f, 1f, 1f, // Bottom-right + 0.5f, 0.5f, 0.5f, 1f, 0f, // Top-right + -0.5f, 0.5f, 0.5f, 0f, 0f, // Top-left + + // Bottom face (y = -0.5) + -0.5f, -0.5f, -0.5f, 1f, 1f, // Bottom-left + 0.5f, -0.5f, -0.5f, 0f, 1f, // Bottom-right + 0.5f, -0.5f, 0.5f, 0f, 0f, // Top-right + -0.5f, -0.5f, 0.5f, 1f, 0f, // Top-left + + }; + + uint[] indices = { + // Front face + 0, 1, 2, + 0, 2, 3, + // Back face + 4, 5, 6, + 4, 6, 7, + // Left face + 8, 9, 10, + 8, 10, 11, + // Right face + 12, 13, 14, + 12, 14, 15, + // Top face + 16, 17, 18, + 16, 18, 19, + // Bottom face + 20, 21, 22, + 20, 22, 23 + }; + + return new Mesh(vertices, indices, Mesh.VertexLayout.PosTex); + } + + public static Mesh CreateColorCube() + { + float[] vertices = { + // Front face (z = +0.5) + -0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, // Bottom-left + 0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, // Bottom-right + 0.5f, 0.5f, 0.5f, 0f, 0f, 0f, 1f, // Top-right + -0.5f, 0.5f, 0.5f, 1f, 0f, 0f, 1f, // Top-left + + // Back face (z = -0.5) + -0.5f, -0.5f, -0.5f, 1f, 0f, 1f, 1f, // Bottom-left + 0.5f, -0.5f, -0.5f, 0f, 1f, 1f, 1f, // Bottom-right + 0.5f, 0.5f, -0.5f, 1f, 1f, 1f, 1f, // Top-right + -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1f // Top-left + }; + + uint[] indices = { + // Front face + 0, 1, 2, + 2, 3, 0, + + // Back face + 4, 5, 6, + 6, 7, 4, + + // Left face + 4, 0, 3, + 3, 7, 4, + + // Right face + 1, 5, 6, + 6, 2, 1, + + // Top face + 3, 2, 6, + 6, 7, 3, + + // Bottom face + 4, 5, 1, + 1, 0, 4 + }; + + return new Mesh(vertices, indices, Mesh.VertexLayout.PosColor); + } + + // You'd also add CreateCircle, CreateCylinder, etc. + // Your Cylinder should be ONE mesh, not 3 draw calls. + // Generate the top, bottom, and side vertices into one + // big array and use one EBO for all of it. +} \ No newline at end of file diff --git a/TheRepo/TheLabs/Texture.cs b/TheRepo/TheLabs/Texture.cs new file mode 100644 index 0000000..bd171ff --- /dev/null +++ b/TheRepo/TheLabs/Texture.cs @@ -0,0 +1,61 @@ +namespace TheLabs; + +using OpenTK.Graphics.OpenGL4; +using StbImageSharp; +using System.IO; + +public class Texture : IDisposable +{ + public readonly int Handle; + + public Texture(string path) + { + // Generate the handle + Handle = GL.GenTexture(); + + // Bind the texture so we can configure it + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, Handle); + + // --- Set texture parameters --- + // Repeat the texture if UVs go outside [0, 1] + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + // Set filter for shrinking (mipmap) and stretching (linear) + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + // --- Load and upload the image data --- + StbImage.stbi_set_flip_vertically_on_load(1); + + // Load the image from disk + using (var stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + + // Upload data to the GPU + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, + image.Width, image.Height, 0, + PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + // Generate mipmaps for better quality shrinking + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + // Unbind the texture + GL.BindTexture(TextureTarget.Texture2D, 0); + } + + // A simple method to bind the texture to a specific unit + public void Use(TextureUnit unit = TextureUnit.Texture0) + { + GL.ActiveTexture(unit); + GL.BindTexture(TextureTarget.Texture2D, Handle); + } + + public void Dispose() + { + GL.DeleteTexture(Handle); + } +} \ No newline at end of file