using OpenTK.Mathematics; namespace TheLabs; public static class ShapeFactory { public static Mesh CreateTexturedCube() { List vertices = new List(); List indices = new List(); // --- FRONT FACE (Normal 0, 0, 1) --- // Sequence: Bottom-Left -> Bottom-Right -> Top-Right -> Top-Left Helpers.AddFace(vertices, indices, new Vertex(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // BL new Vertex(new Vector3( 0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // BR new Vertex(new Vector3( 0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1, 1), Vector3.Zero, Vector3.Zero), // TR new Vertex(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0, 1), Vector3.Zero, Vector3.Zero) // TL ); // --- BACK FACE (Normal 0, 0, -1) --- // Sequence: Bottom-Right -> Bottom-Left -> Top-Left -> Top-Right (Relative to Front view) Helpers.AddFace(vertices, indices, new Vertex(new Vector3( 0.5f, -0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // BR new Vertex(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // BL new Vertex(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(1, 1), Vector3.Zero, Vector3.Zero), // TL new Vertex(new Vector3( 0.5f, 0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(0, 1), Vector3.Zero, Vector3.Zero) // TR ); // --- LEFT FACE (Normal -1, 0, 0) --- // Sequence: Back-Left -> Front-Left -> Front-Top -> Back-Top Helpers.AddFace(vertices, indices, new Vertex(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(-1, 0, 0), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // BL new Vertex(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(-1, 0, 0), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // FL new Vertex(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(-1, 0, 0), new Vector2(1, 1), Vector3.Zero, Vector3.Zero), // FT new Vertex(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(-1, 0, 0), new Vector2(0, 1), Vector3.Zero, Vector3.Zero) // BT ); // --- RIGHT FACE (Normal 1, 0, 0) --- // Sequence: Front-Right -> Back-Right -> Back-Top -> Front-Top Helpers.AddFace(vertices, indices, new Vertex(new Vector3( 0.5f, -0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // FR new Vertex(new Vector3( 0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // BR new Vertex(new Vector3( 0.5f, 0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1, 1), Vector3.Zero, Vector3.Zero), // BT new Vertex(new Vector3( 0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0, 1), Vector3.Zero, Vector3.Zero) // FT ); // --- TOP FACE (Normal 0, 1, 0) --- // Sequence: Front-Left -> Front-Right -> Back-Right -> Back-Left Helpers.AddFace(vertices, indices, new Vertex(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 1, 0), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // FL new Vertex(new Vector3( 0.5f, 0.5f, 0.5f), new Vector3(0, 1, 0), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // FR new Vertex(new Vector3( 0.5f, 0.5f, -0.5f), new Vector3(0, 1, 0), new Vector2(1, 1), Vector3.Zero, Vector3.Zero), // BR new Vertex(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(0, 1, 0), new Vector2(0, 1), Vector3.Zero, Vector3.Zero) // BL ); // --- BOTTOM FACE (Normal 0, -1, 0) --- // Sequence: Front-Left -> Back-Left -> Back-Right -> Front-Right Helpers.AddFace(vertices, indices, new Vertex(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, -1, 0), new Vector2(0, 1), Vector3.Zero, Vector3.Zero), // FL new Vertex(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0, -1, 0), new Vector2(0, 0), Vector3.Zero, Vector3.Zero), // BL new Vertex(new Vector3( 0.5f, -0.5f, -0.5f), new Vector3(0, -1, 0), new Vector2(1, 0), Vector3.Zero, Vector3.Zero), // BR new Vertex(new Vector3( 0.5f, -0.5f, 0.5f), new Vector3(0, -1, 0), new Vector2(1, 1), Vector3.Zero, Vector3.Zero) // FR ); List finalFloats = new List(); foreach (var v in vertices) { finalFloats.Add(v.Position.X); finalFloats.Add(v.Position.Y); finalFloats.Add(v.Position.Z); finalFloats.Add(v.Normal.X); finalFloats.Add(v.Normal.Y); finalFloats.Add(v.Normal.Z); finalFloats.Add(v.TexCoords.X); finalFloats.Add(v.TexCoords.Y); // --- NEW: Add Tangent and Bitangent --- finalFloats.Add(v.Tangent.X); finalFloats.Add(v.Tangent.Y); finalFloats.Add(v.Tangent.Z); finalFloats.Add(v.Bitangent.X); finalFloats.Add(v.Bitangent.Y); finalFloats.Add(v.Bitangent.Z); } return new Mesh(finalFloats.ToArray(), indices.ToArray(), Mesh.VertexLayout.PosTexNormalTangent); } public static Mesh CreateColouredCircle() { float[] vertices = { }; int segments = 100; for (int i = 0; i <= segments; i++) { float angle = i * 2.0f * MathF.PI / segments; float x = 0.5f * MathF.Cos(angle); float y = 0.5f * MathF.Sin(angle); Array.Resize(ref vertices, vertices.Length + 7); vertices[^7] = x; vertices[^6] = y; vertices[^5] = 0f; } return new Mesh(vertices, Mesh.VertexLayout.PosColor); } public static Mesh CreateTexturedCircle() { var vertices = new List(); int segments = 100; vertices.Add(0f); // Center X vertices.Add(0f); // Center Y vertices.Add(0f); // Center Z vertices.Add(0.5f); // Center U vertices.Add(0.5f); // Center V for (int i = 0; i <= segments; i++) { float angle = i * 2.0f * MathF.PI / segments; float x = 0.5f * MathF.Cos(angle); float y = 0.5f * MathF.Sin(angle); vertices.Add(x); // X vertices.Add(y); // Y vertices.Add(0f); // Z vertices.Add((x + 0.5f)); // U vertices.Add((y + 0.5f)); // V } return new Mesh(vertices.ToArray(), Mesh.VertexLayout.PosTex); } public static Mesh CreateColorCube() { float[] vertices = { -0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, // Bottom-left 0.5f, -0.5f, 0.5f, 0f, 1f, 0f, 1f, 0f, 0f, 1f, // Bottom-right 0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 1f, 0f, 0f, 1f, // Top-right -0.5f, 0.5f, 0.5f, 1f, 1f, 0f, 1f, 0f, 0f, 1f, // Top-left // Back face (z = -0.5) -0.5f, -0.5f, -0.5f, 1f, 0f, 1f, 1f, 0f, 0f, -1f, // Bottom-left 0.5f, -0.5f, -0.5f, 0f, 1f, 1f, 1f, 0f, 0f, -1f, // Bottom-right 0.5f, 0.5f, -0.5f, 1f, 1f, 1f, 1f, 0f, 0f, -1f, // Top-right -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1f, 0f, 0f, -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); } public static Mesh CreateTexturedCylinder(int segments = 32, float height = 1.0f, float radius = 0.5f) { var vertices = new List(); var indices = new List(); uint vertexIndex = 0; float halfHeight = height / 2.0f; uint topCenterIndex = vertexIndex; vertices.AddRange(new[] { 0.0f, halfHeight, 0.0f, // Position 0.5f, 0.5f }); // Top center vertex vertexIndex++; uint topStartIndex = vertexIndex; for (int i = 0; i <= segments; i++) { float angle = i * 2.0f * MathF.PI / segments; float x = radius * MathF.Cos(angle); float z = radius * MathF.Sin(angle); // UV coordinates are mapped from model space (-0.5 to +0.5) // to texture space (0.0 to 1.0) float u = x / (radius * 2) + 0.5f; float v = z / (radius * 2) + 0.5f; vertices.AddRange(new[] { x, halfHeight, z, u, v }); vertexIndex++; } // Add Top Cap Indices (Triangle Fan) for (int i = 0; i < segments; i++) { indices.Add(topCenterIndex); indices.Add(topStartIndex + (uint)i); indices.Add(topStartIndex + (uint)((i + 1) % segments)); // Wrap around } uint sideStartIndex = vertexIndex; // Loop from 0 to segments (inclusive) to get (segments + 1) // vertices for wrapping the texture seamlessly. for (int i = 0; i <= segments; i++) { float angle = i * 2.0f * MathF.PI / segments; float x = radius * MathF.Cos(angle); float z = radius * MathF.Sin(angle); // The U coordinate goes from 0.0 to 1.0 around the cylinder float u = (float)i / segments; // Add Top Side Vertex vertices.AddRange(new[] { x, halfHeight, z, u, 1.0f }); // V = 1.0 (top) vertexIndex++; // Add Bottom Side Vertex vertices.AddRange(new[] { x, -halfHeight, z, u, 0.0f }); // V = 0.0 (bottom) vertexIndex++; } // Add Side Wall Indices (Quad Strip) for (int i = 0; i < segments; i++) { // Each segment has 2 vertices (top, bottom) // We reference the vertices we just created. uint topCurrent = sideStartIndex + (uint)(i * 2); uint bottomCurrent = topCurrent + 1; uint topNext = sideStartIndex + (uint)((i + 1) * 2); uint bottomNext = topNext + 1; // Triangle 1 (CCW) indices.Add(topCurrent); indices.Add(bottomCurrent); indices.Add(topNext); // Triangle 2 (CCW) indices.Add(bottomCurrent); indices.Add(bottomNext); indices.Add(topNext); } // --- 4. Create the Mesh --- return new Mesh(vertices.ToArray(), indices.ToArray(), Mesh.VertexLayout.PosTex); } public static Mesh CreateTexturedSphere(float radius = 0.5f, int stacks = 20, int slices = 20) { var vertices = new List(); var indices = new List(); // 1. Generate Vertices for (int i = 0; i <= stacks; i++) { // V ranges from 0.0 to 1.0 (bottom to top) float v = (float)i / stacks; // Phi ranges from -PI/2 (bottom) to +PI/2 (top) float phi = v * MathF.PI - MathF.PI / 2; for (int j = 0; j <= slices; j++) { // U ranges from 0.0 to 1.0 (around the sphere) float u = (float)j / slices; // Theta ranges from 0 to 2*PI float theta = u * 2 * MathF.PI; // Calculate Position (Spherical Coordinates) float x = radius * MathF.Cos(phi) * MathF.Cos(theta); float y = radius * MathF.Sin(phi); float z = radius * MathF.Cos(phi) * MathF.Sin(theta); // Calculate Normal (Normalized position) // Since center is (0,0,0), normal is just (x,y,z) normalized. // Or simpler: (x/r, y/r, z/r) float nx = x / radius; float ny = y / radius; float nz = z / radius; // Add Vertex Data: Pos (3) + Normal (3) + Tex (2) vertices.Add(x); vertices.Add(y); vertices.Add(z); // Position vertices.Add(nx); vertices.Add(ny); vertices.Add(nz); // Normal vertices.Add(u); vertices.Add(v); // Texture } } // 2. Generate Indices for (int i = 0; i < stacks; i++) { for (int j = 0; j < slices; j++) { // We are building a quad from 4 vertices: // TopLeft (TL), TopRight (TR), BottomLeft (BL), BottomRight (BR) uint bl = (uint)(i * (slices + 1) + j); uint br = (uint)(i * (slices + 1) + (j + 1)); uint tl = (uint)((i + 1) * (slices + 1) + j); uint tr = (uint)((i + 1) * (slices + 1) + (j + 1)); // Triangle 1 indices.Add(bl); indices.Add(br); indices.Add(tl); // Triangle 2 indices.Add(br); indices.Add(tr); indices.Add(tl); } } return new Mesh(vertices.ToArray(), indices.ToArray(), Mesh.VertexLayout.PosTexNormal); } public static Mesh createTexturedSquare() { float[] vertices = { // Format: X, Y, Z, U, V // --- Front Face --- -0.5f, -0.5f, 0.0f, 0f, 0f, // Bottom-left 0.5f, -0.5f, 0.0f, 1f, 0f, // Bottom-right 0.5f, 0.5f, 0.0f, 1f, 1f, // Top-right -0.5f, 0.5f, 0.0f, 0f, 1f, // Top-left }; uint[] indices = { // Front face 0, 2, 1, 0, 2, 3, }; return new Mesh(vertices, indices, Mesh.VertexLayout.PosTex); } }