diff --git a/Nebulix/Rendering/Mesh.cs b/Nebulix/Rendering/Mesh.cs index 4af35d7..4b24255 100644 --- a/Nebulix/Rendering/Mesh.cs +++ b/Nebulix/Rendering/Mesh.cs @@ -1,20 +1,20 @@ -using Silk.NET.Maths; +using System.Numerics; using Silk.NET.OpenGL; namespace Nebulix.Rendering { public sealed class Mesh { - public Vector3D[] Vertices { get => vertices; - set { vertices = value; regenerate = true; normals = new Vector3D[vertices.Length];} } + public Vector3[] Vertices { get => vertices; + set { vertices = value; regenerate = true; normals = new Vector3[vertices.Length];} } public uint[] Indices { get => indices; set { indices = value; regenerate = true; } } - private uint vao = 0, vbo = 0, ebo = 0; + private uint vao, vbo, ebo; private bool regenerate = true; - private Vector3D[] vertices = []; + private Vector3[] vertices = []; private uint[] indices = []; - private Vector3D[] normals = []; + private Vector3[] normals = []; public void Clear() { @@ -29,13 +29,13 @@ namespace Nebulix.Rendering uint i0 = indices[i]; uint i1 = indices[i+1]; uint i2 = indices[i+2]; - Vector3D v0 = vertices[i0]; - Vector3D v1 = vertices[i1]; - Vector3D v2 = vertices[i2]; + Vector3 v0 = vertices[i0]; + Vector3 v1 = vertices[i1]; + Vector3 v2 = vertices[i2]; - Vector3D normal = Vector3D.Cross(v1-v0, v2-v0); + Vector3 normal = Vector3.Cross(v1-v0, v2-v0); // Commenting this out, will result in the normals being weighted based on the triangle area - normal = Vector3D.Normalize(normal); + normal = Vector3.Normalize(normal); normals[i0] += normal; normals[i1] += normal; normals[i2] += normal; @@ -43,7 +43,7 @@ namespace Nebulix.Rendering for (int i = 0; i < normals.Length; i++) { - normals[i] = Vector3D.Normalize(normals[i]); // smoothing for shared vertices + normals[i] = Vector3.Normalize(normals[i]); // smoothing for shared vertices } } diff --git a/Nebulix/Rendering/Shaders/Shader.cs b/Nebulix/Rendering/Shaders/Shader.cs index dbe9caf..90e48ab 100644 --- a/Nebulix/Rendering/Shaders/Shader.cs +++ b/Nebulix/Rendering/Shaders/Shader.cs @@ -1,6 +1,6 @@ -using Silk.NET.Maths; -using Silk.NET.OpenGL; +using Silk.NET.OpenGL; using System.Numerics; +using Silk.NET.Maths; namespace Nebulix.Rendering; @@ -35,6 +35,10 @@ public class Shader _glContext.Uniform1(_glContext.GetUniformLocation(_shaderProgramId, name), value); } + public void SetVector(string name, Vector3 value) + { + _glContext.Uniform3(_glContext.GetUniformLocation(_shaderProgramId, name), value.X, value.Y, value.Z); + } public void SetVector(string name, Vector3D value) { _glContext.Uniform3(_glContext.GetUniformLocation(_shaderProgramId, name), value.X, value.Y, value.Z); diff --git a/Nebulix/Vector.cs b/Nebulix/Vector.cs new file mode 100644 index 0000000..a96f292 --- /dev/null +++ b/Nebulix/Vector.cs @@ -0,0 +1,35 @@ +using System.Numerics; + +namespace Nebulix; + + +public static class VectorExtensions +{ + public static Vector3 Up(this Vector3 _) => Vector3.UnitY; + public static Vector3 Down(this Vector3 _) => -Vector3.UnitY; + public static Vector3 Left(this Vector3 _) => Vector3.UnitX; + public static Vector3 Right(this Vector3 _) => -Vector3.UnitX; + public static Vector3 Front(this Vector3 _) => -Vector3.UnitZ; + public static Vector3 Back(this Vector3 _) => -Vector3.UnitZ; + + // Thanks to: https://stackoverflow.com/a/67920029 + public static Vector3 Slerp(this Vector3 start, Vector3 end, float percent) + { + // the cosine of the angle between 2 vectors. + float dot = Vector3.Dot(start, end); + + // Clamp it to be in the range of Acos() + // This may be unnecessary, but floating point precision can be a fickle mistress. + Math.Clamp(dot, -1.0f, 1.0f); + + // Acos(dot) returns the angle between start and end, + // And multiplying that by percent returns the angle between start and the final result. + float theta = (float)Math.Acos(dot) * percent; + Vector3 relativeVec = end - start * dot; + relativeVec = Vector3.Normalize(relativeVec); + + // Orthonormal basis + // The final result. + return ((start * (float)Math.Cos(theta)) + (relativeVec * (float)Math.Sin(theta))); + } +} \ No newline at end of file diff --git a/src/Camera/Camera.cs b/src/Camera/Camera.cs index 82b2a05..7d3d909 100644 --- a/src/Camera/Camera.cs +++ b/src/Camera/Camera.cs @@ -1,4 +1,4 @@ -using Silk.NET.Maths; +using System.Numerics; namespace Engine_silk.NET; @@ -14,19 +14,19 @@ public enum MovementDirection public class Camera { - public Vector3D Position => _position; - private readonly Vector3D _worldUp; - private Vector3D _position; - private Vector3D _front; - private Vector3D _right; - private Vector3D _up; + public Vector3 Position => _position; + private readonly Vector3 _worldUp; + private Vector3 _position; + private Vector3 _front; + private Vector3 _right; + private Vector3 _up; private float _yaw; private float _pitch; /// /// The view matrix according to the current camera position and rotation /// - public Matrix4X4 ViewMatrix { get => Matrix4X4.CreateLookAt(_position, _position + _front, _worldUp); } + public Matrix4x4 ViewMatrix { get => Matrix4x4.CreateLookAt(_position, _position + _front, _worldUp); } public float MouseSensitivity { get; set; } = 0.1f; /// /// Controlls how fast the camera moves if a key is pressed @@ -34,14 +34,14 @@ public class Camera public float MovementSpeed { get; set; } public float Fov { get; set; } - public Camera(Vector3D position, float yaw = -90.0f, float pitch = 0, float movementSpeed = 2.5f, float fov = 45.0f) + public Camera(Vector3 position, float yaw = -90.0f, float pitch = 0, float movementSpeed = 2.5f, float fov = 45.0f) { _position = position; _yaw = yaw; _pitch = pitch; MovementSpeed = movementSpeed; Fov = fov; - _worldUp = Vector3D.UnitY; + _worldUp = Vector3.UnitY; UpdateCameraVectors(); } @@ -99,15 +99,15 @@ public class Camera private void UpdateCameraVectors() { - Vector3D front = new( + Vector3 front = new( MathF.Cos(Maths.Convert.ToRadians(_yaw)) * MathF.Cos(Maths.Convert.ToRadians(_pitch)), MathF.Sin(Maths.Convert.ToRadians(_pitch)), MathF.Sin(Maths.Convert.ToRadians(_yaw)) * MathF.Cos(Maths.Convert.ToRadians(_pitch)) ); - _front = Vector3D.Normalize(front); + _front = Vector3.Normalize(front); - _right = Vector3D.Normalize(Vector3D.Cross(_front, _worldUp)); - _up = Vector3D.Normalize(Vector3D.Cross(_right, _front)); + _right = Vector3.Normalize(Vector3.Cross(_front, _worldUp)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); } } diff --git a/src/IcoSphere.cs b/src/IcoSphere.cs new file mode 100644 index 0000000..3d634aa --- /dev/null +++ b/src/IcoSphere.cs @@ -0,0 +1,183 @@ +using System.Numerics; +using Nebulix; +using Nebulix.Rendering; +using Silk.NET.OpenGL; + +namespace Engine_silk.NET; + +public class IcoSphere +{ + private readonly Mesh mesh = new(); + private readonly int resolution; + private readonly IcoSphereGenerator generator = new(); + + public IcoSphere(int resolution) + { + if (resolution < 2) + throw new ArgumentOutOfRangeException(nameof(resolution), resolution, + "Resolution must be greater than 1"); + this.resolution = resolution; + } + + public void CreateSphere() + { + generator.Generate(resolution); + + mesh.Clear(); + mesh.Vertices = generator.Vertices; + mesh.Indices = Array.ConvertAll(generator.Triangles, x => (uint)x); + mesh.CalculateNormals(); + } + + public void RenderSphere(GL gl) // Will not be needed + { + mesh.Render(gl); + } +} + +// Thank you Sebastian Lague +class IcoSphereGenerator +{ + + // Output: + public Vector3[] Vertices => vertices?.Items ?? Array.Empty(); + public int[] Triangles => triangles?.Items ?? Array.Empty(); + + // Internal: + FixedSizeList? vertices; + FixedSizeList? triangles; + int numDivisions; + int numVertsPerFace; + + // Indices of the vertex pairs that make up each of the initial 12 edges + static readonly int[] VertexPairs = { 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 2, 3, 3, 4, 4, 1, 5, 1, 5, 2, 5, 3, 5, 4 }; + // Indices of the edge triplets that make up the initial 8 faces + static readonly int[] EdgeTriplets = { 0, 1, 4, 1, 2, 5, 2, 3, 6, 3, 0, 7, 8, 9, 4, 9, 10, 5, 10, 11, 6, 11, 8, 7 }; + // The six initial vertices up left back right forward down + static readonly Vector3[] BaseVertices = { Vector3.UnitY, -Vector3.UnitX, -Vector3.UnitZ, Vector3.UnitX, Vector3.UnitZ, -Vector3.UnitY }; + + public void Generate (int resolution) { + numDivisions = Math.Max (0, resolution); + numVertsPerFace = ((numDivisions + 3) * (numDivisions + 3) - (numDivisions + 3)) / 2; + int numVerts = numVertsPerFace * 8 - (numDivisions + 2) * 12 + 6; + int numTrisPerFace = (numDivisions + 1) * (numDivisions + 1); + + vertices = new FixedSizeList (numVerts); + triangles = new FixedSizeList (numTrisPerFace * 8 * 3); + + vertices.AddRange (BaseVertices); + + // Create 12 edges, with n vertices added along them (n = numDivisions) + Edge[] edges = new Edge[12]; + for (int i = 0; i < VertexPairs.Length; i += 2) { + Vector3 startVertex = vertices.Items[VertexPairs[i]]; + Vector3 endVertex = vertices.Items[VertexPairs[i + 1]]; + + int[] edgeVertexIndices = new int[numDivisions + 2]; + edgeVertexIndices[0] = VertexPairs[i]; + + // Add vertices along edge + for (int divisionIndex = 0; divisionIndex < numDivisions; divisionIndex++) { + float t = (divisionIndex + 1f) / (numDivisions + 1f); + edgeVertexIndices[divisionIndex + 1] = vertices.NextIndex; + vertices.Add (startVertex.Slerp(endVertex, t)); + } + edgeVertexIndices[numDivisions + 1] = VertexPairs[i + 1]; + int edgeIndex = i / 2; + edges[edgeIndex] = new Edge (edgeVertexIndices); + } + + // Create faces + for (int i = 0; i < EdgeTriplets.Length; i += 3) { + int faceIndex = i / 3; + bool reverse = faceIndex >= 4; + CreateFace (edges[EdgeTriplets[i]], edges[EdgeTriplets[i + 1]], edges[EdgeTriplets[i + 2]], reverse); + } + } + + void CreateFace (Edge sideA, Edge sideB, Edge bottom, bool reverse) + { + if (vertices is null) throw new ArgumentException("Vertices cannot be null", nameof(vertices)); + if (triangles is null) throw new ArgumentException("triangles cannot be null", nameof(triangles)); + + int numPointsInEdge = sideA.VertexIndices.Length; + var vertexMap = new FixedSizeList (numVertsPerFace); + vertexMap.Add (sideA.VertexIndices[0]); // top of triangle + + for (int i = 1; i < numPointsInEdge - 1; i++) { + // Side A vertex + vertexMap.Add (sideA.VertexIndices[i]); + + // Add vertices between sideA and sideB + Vector3 sideAVertex = vertices.Items[sideA.VertexIndices[i]]; + Vector3 sideBVertex = vertices.Items[sideB.VertexIndices[i]]; + int numInnerPoints = i - 1; + for (int j = 0; j < numInnerPoints; j++) { + float t = (j + 1f) / (numInnerPoints + 1f); + vertexMap.Add (vertices.NextIndex); + vertices.Add (sideAVertex.Slerp(sideBVertex, t)); + } + + // Side B vertex + vertexMap.Add (sideB.VertexIndices[i]); + } + + // Add bottom edge vertices + for (int i = 0; i < numPointsInEdge; i++) { + vertexMap.Add (bottom.VertexIndices[i]); + } + + // Triangulate + int numRows = numDivisions + 1; + for (int row = 0; row < numRows; row++) { + // vertices down left edge follow quadratic sequence: 0, 1, 3, 6, 10, 15... + // the nth term can be calculated with: (n^2 - n)/2 + int topVertex = ((row + 1) * (row + 1) - row - 1) / 2; + int bottomVertex = ((row + 2) * (row + 2) - row - 2) / 2; + + int numTrianglesInRow = 1 + 2 * row; + for (int column = 0; column < numTrianglesInRow; column++) { + int v0, v1, v2; + + if (column % 2 == 0) { + v0 = topVertex; + v1 = bottomVertex + 1; + v2 = bottomVertex; + topVertex++; + bottomVertex++; + } else { + v0 = topVertex; + v1 = bottomVertex; + v2 = topVertex - 1; + } + + triangles.Add (vertexMap.Items[v0]); + triangles.Add (vertexMap.Items[(reverse) ? v2 : v1]); + triangles.Add (vertexMap.Items[(reverse) ? v1 : v2]); + } + } + + } + + // Convenience classes: + + private record Edge(int[] VertexIndices); + + private class FixedSizeList(int size) + { + public readonly T[] Items = new T[size]; + public int NextIndex; + + public void Add (T item) { + Items[NextIndex] = item; + NextIndex++; + } + + public void AddRange (IEnumerable items) { + foreach (var item in items) { + Add (item); + } + } + } + +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 767e749..76375f4 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,5 @@ using Nebulix; +using Silk.NET.Maths; namespace Engine_silk.NET; @@ -6,7 +7,6 @@ using Engine_silk.NET.Textures; using Nebulix.InputSystem; using Nebulix.Rendering; using Silk.NET.Input; -using Silk.NET.Maths; using Silk.NET.OpenGL; using Silk.NET.Windowing; using System; @@ -19,7 +19,6 @@ using System.Numerics; // Next to do will be cleaning this up by creating a shader class and maybe even an engine class // IEngine -> IEngine.Create(WindowOptions) -> return OpenGLEngine/VulkanEngine maybe? -using Vector3 = Silk.NET.Maths.Vector3D; public static class Program { @@ -33,7 +32,7 @@ public static class Program private static Camera _cam; private static Vector2 _lastMousePosition; private static uint _vao, _vbo; - private static Sphere sphere; + private static IcoSphere sphere; public static void Main(string[] args) { @@ -141,7 +140,7 @@ public static class Program // Sphere - sphere = new Sphere(10); + sphere = new IcoSphere(10); sphere.CreateSphere(); _sphereShader = new Nebulix.Rendering.Shader(_gl, "./Shaders/Sphere/sphere.vert", "./Shaders/Sphere/sphere.frag"); @@ -180,7 +179,7 @@ public static class Program var difference = (float)(_window.Time * 10); var modelMatrix = Matrix4x4.CreateRotationY(Maths.Convert.ToRadians(difference)) * Matrix4x4.CreateRotationX(Maths.Convert.ToRadians(difference)); var viewMatrix = _cam.ViewMatrix; - var projectionMatrix = Matrix4X4.CreatePerspectiveFieldOfView(Maths.Convert.ToRadians(_cam.Fov), Width / (float)Height, 0.1f, 100.0f); + var projectionMatrix = Matrix4x4.CreatePerspectiveFieldOfView(Maths.Convert.ToRadians(_cam.Fov), Width / (float)Height, 0.1f, 100.0f); _shader.Use(); _shader.SetMatrix("modelMatrix", modelMatrix); diff --git a/src/Sphere.cs b/src/Sphere.cs index d03b055..23c59db 100644 --- a/src/Sphere.cs +++ b/src/Sphere.cs @@ -1,6 +1,6 @@ -using Nebulix; +using System.Numerics; +using Nebulix; using Nebulix.Rendering; -using Silk.NET.Maths; using Silk.NET.OpenGL; namespace Engine_silk.NET @@ -22,14 +22,14 @@ namespace Engine_silk.NET public void CreateSphere() { // TODO: merge the individual meshes to one mesh - Vector3D[] directions = + Vector3[] directions = [ - Vector3D.UnitZ, -Vector3D.UnitZ, - Vector3D.UnitY, -Vector3D.UnitY, - Vector3D.UnitX, -Vector3D.UnitX + Vector3.UnitZ, -Vector3.UnitZ, + Vector3.UnitY, -Vector3.UnitY, + Vector3.UnitX, -Vector3.UnitX ]; - List> vertices = new(); + List vertices = new(); List indices = new(); for (int i = 0; i < directions.Length; i++) { @@ -64,24 +64,24 @@ namespace Engine_silk.NET public Mesh Mesh => _mesh; private readonly Mesh _mesh; - private readonly Vector3D _localX; - private readonly Vector3D _localY; + private readonly Vector3 _localX; + private readonly Vector3 _localY; - private readonly Vector3D _localUp; + private readonly Vector3 _localUp; private readonly uint _resolution; - public Face(Vector3D localUp, Mesh mesh, uint resolution) + public Face(Vector3 localUp, Mesh mesh, uint resolution) { _mesh = mesh; _localX = new(localUp.Y, localUp.Z, localUp.X); - _localY = Vector3D.Cross(localUp, _localX); + _localY = Vector3.Cross(localUp, _localX); _localUp = localUp; _resolution = resolution; } internal void GenerateMesh() { - var vertices = new Vector3D[_resolution * _resolution]; + var vertices = new Vector3[_resolution * _resolution]; // _resolution - 1 because the vertices index starts at 0 // * 6 because each triangle needs 3 points and each small quad has 2 triangles 3*2 = 6 var indices = new uint[(_resolution - 1) * (_resolution - 1) * 6]; @@ -93,9 +93,9 @@ namespace Engine_silk.NET for (uint x = 0; x < _resolution; x++) { i = x + y * _resolution; - Vector2D percent = new Vector2D(x, y) / (_resolution - 1); + Vector2 percent = new Vector2(x, y) / (_resolution - 1); // place vertex on correct position of the plane to easily calculate indices - Vector3D vertexPosition = _localUp + (percent.X - 0.5f) * 2 * _localX + (percent.Y - 0.5f) * 2 * _localY; + Vector3 vertexPosition = _localUp + (percent.X - 0.5f) * 2 * _localX + (percent.Y - 0.5f) * 2 * _localY; vertices[i] = PointOnCubeToPointOnSphere(vertexPosition); if (x != _resolution - 1 && y != _resolution - 1) // we didn't reach the bottom right point yet @@ -121,7 +121,7 @@ namespace Engine_silk.NET // Smooth mapping so that the points are not clumped on the former corners of the cube // http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html - private Vector3D PointOnCubeToPointOnSphere(Vector3D point) + private Vector3 PointOnCubeToPointOnSphere(Vector3 point) { float x2 = point.X * point.X; float y2 = point.Y * point.Y; @@ -129,7 +129,7 @@ namespace Engine_silk.NET float newX = point.X * MathF.Sqrt(1 - y2 / 2 - z2 / 2 + (y2 * z2) / 3); float newY = point.Y * MathF.Sqrt(1 - z2 / 2 - x2 / 2 + (z2 * x2) / 3); float newZ = point.Z * MathF.Sqrt(1 - x2 / 2 - y2 / 2 + (x2 * y2) / 3); - return new Vector3D(newX, newY, newZ); + return new Vector3(newX, newY, newZ); } } }