#include "Asset.hpp" #include #include #include namespace asset { Mesh make_unit_quad(float size) { Mesh m; const float h = size * 0.5f; m.positions = {-size, -size, 0.0f, size, -size, 0.0f, size, size, 0.0f, -size, size, 0.0f}; m.uvs = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; m.indices = {0, 1, 2, 2, 3, 0}; return m; } Mesh make_unit_cube(float size) { Mesh m; const float h = size * 0.5f; // 6 faces × 4 verts (CCW when viewed from outside) const float P[] = { // +X (right) h,-h,-h, h, h,-h, h, h, h, h,-h, h, // -X (left) -h,-h, h, -h, h, h, -h, h,-h, -h,-h,-h, // +Y (top) *** fixed winding *** -h, h,-h, -h, h, h, h, h, h, h, h,-h, // -Y (bottom) *** fixed winding *** -h,-h,-h, h,-h,-h, h,-h, h, -h,-h, h, // +Z (front) -h,-h, h, h,-h, h, h, h, h, -h, h, h, // -Z (back) h,-h,-h, -h,-h,-h, -h, h,-h, h, h,-h }; const float N[] = { // +X 1,0,0, 1,0,0, 1,0,0, 1,0,0, // -X -1,0,0, -1,0,0, -1,0,0, -1,0,0, // +Y 0,1,0, 0,1,0, 0,1,0, 0,1,0, // -Y 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // +Z 0,0,1, 0,0,1, 0,0,1, 0,0,1, // -Z 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 }; // 0..1 quad per face; orientation is fine with the corrected positions const float UV[] = { 0,0, 1,0, 1,1, 0,1, 0,0, 1,0, 1,1, 0,1, 0,0, 1,0, 1,1, 0,1, 0,0, 1,0, 1,1, 0,1, 0,0, 1,0, 1,1, 0,1, 0,0, 1,0, 1,1, 0,1 }; // 2 triangles per face const uint32_t I[] = { 0, 1, 2, 0, 2, 3, // +X 4, 5, 6, 4, 6, 7, // -X 8, 9, 10, 8, 10, 11, // +Y (fixed) 12, 13, 14, 12, 14, 15, // -Y (fixed) 16, 17, 18, 16, 18, 19, // +Z 20, 21, 22, 20, 22, 23 // -Z }; m.positions.assign(std::begin(P), std::end(P)); m.normals.assign (std::begin(N), std::end(N)); m.uvs.assign (std::begin(UV), std::end(UV)); m.indices.assign (std::begin(I), std::end(I)); return m; } Mesh make_unit_ico(float size){ Mesh m; // Golden ratio const float phi = (1.0f + std::sqrt(5.0f)) * 0.5f; // Canonical 12 vertices (right-handed) const std::array V = { glm::vec3(-1, phi, 0), glm::vec3( 1, phi, 0), glm::vec3(-1, -phi, 0), glm::vec3( 1, -phi, 0), glm::vec3( 0, -1, phi), glm::vec3( 0, 1, phi), glm::vec3( 0, -1, -phi), glm::vec3( 0, 1, -phi), glm::vec3( phi, 0, -1), glm::vec3( phi, 0, 1), glm::vec3(-phi, 0, -1), glm::vec3(-phi, 0, 1) }; // Normalize to unit sphere and scale to radius = size * 0.5 const float r = size * 0.5f; m.positions.reserve(12 * 3); m.normals .reserve(12 * 3); for (auto p : V) { glm::vec3 n = glm::normalize(p); glm::vec3 s = r * n; m.positions.insert(m.positions.end(), {s.x, s.y, s.z}); m.normals .insert(m.normals .end(), {n.x, n.y, n.z}); } // 20 faces (CCW) static const uint32_t I[] = { 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 }; m.indices.assign(std::begin(I), std::end(I)); // UVs omitted (non-trivial on icosahedra) m.uvs.clear(); return m; } Mesh make_unit_ico_flat(float size) { Mesh m; m.positions.clear(); m.normals.clear(); m.uvs.clear(); m.indices.clear(); // draw non-indexed (or fill 0..N-1 if you prefer) // Golden ratio const float phi = (1.0f + std::sqrt(5.0f)) * 0.5f; const float r = size * 0.5f; // radius // 12 canonical vertices (right-handed) const std::array V = { glm::vec3(-1, phi, 0), glm::vec3( 1, phi, 0), glm::vec3(-1, -phi, 0), glm::vec3( 1, -phi, 0), glm::vec3( 0, -1, phi), glm::vec3( 0, 1, phi), glm::vec3( 0, -1, -phi), glm::vec3( 0, 1, -phi), glm::vec3( phi, 0, -1), glm::vec3( phi, 0, 1), glm::vec3(-phi, 0, -1), glm::vec3(-phi, 0, 1) }; // 20 faces (CCW) static const uint32_t F[20][3] = { { 0,11, 5}, { 0, 5, 1}, { 0, 1, 7}, { 0, 7,10}, { 0,10,11}, { 1, 5, 9}, { 5,11, 4}, {11,10, 2}, {10, 7, 6}, { 7, 1, 8}, { 3, 9, 4}, { 3, 4, 2}, { 3, 2, 6}, { 3, 6, 8}, { 3, 8, 9}, { 4, 9, 5}, { 2, 4,11}, { 6, 2,10}, { 8, 6, 7}, { 9, 8, 1} }; m.positions.reserve(20 * 3 * 3); m.normals.reserve (20 * 3 * 3); for (const auto& tri : F) { // Sphere positions (normalized then scaled to radius r) glm::vec3 p0 = glm::normalize(V[tri[0]]) * r; glm::vec3 p1 = glm::normalize(V[tri[1]]) * r; glm::vec3 p2 = glm::normalize(V[tri[2]]) * r; // Flat face normal (one per triangle) glm::vec3 n = glm::normalize(glm::cross(p1 - p0, p2 - p0)); // Duplicate vertices per face so normals don't interpolate across edges const glm::vec3 P[3] = { p0, p1, p2 }; for (int i = 0; i < 3; ++i) { m.positions.push_back(P[i].x); m.positions.push_back(P[i].y); m.positions.push_back(P[i].z); m.normals.push_back(n.x); m.normals.push_back(n.y); m.normals.push_back(n.z); } } // If you prefer indexed draw, uncomment to fill 0..N-1 // m.indices.resize(m.positions.size() / 3); // std::iota(m.indices.begin(), m.indices.end(), 0u); return m; } Mesh make_grid(int N, float extent) { Mesh m; const int V = (N+1); m.positions.reserve(V*V*3); m.uvs .reserve(V*V*2); const float half = extent*0.5f; for (int y=0; y& v, size_t i, const glm::vec3& a) { v[i+0] += a.x; v[i+1] += a.y; v[i+2] += a.z; } Mesh make_terrain(int W, int H, float dx, float dz, HeightFn heightFn, bool genNormals, bool genUVs, float x0, float z0) { assert(W > 0 && H > 0 && dx > 0.0f && dz > 0.0f); Mesh m; const int VX = (W + 1); const int VZ = (H + 1); const int N = VX * VZ; m.positions.resize(3 * N); if (genNormals) m.normals.assign(3 * N, 0.0f); if (genUVs) m.uvs.resize(2 * N); // --- Vertices (positions + uvs) --- for (int j = 0; j < VZ; ++j) { for (int i = 0; i < VX; ++i) { const int v = j * VX + i; const size_t p3 = 3 * v; const size_t t2 = 2 * v; // Grid spans the XY plane; height goes into Z const float X = x0 + i * dx; const float Y = z0 + j * dz; // reuse dz as grid step in Y const float Z = heightFn ? heightFn(X, Y) : 0.0f; m.positions[p3 + 0] = X; // x m.positions[p3 + 1] = Y; // y (planar) m.positions[p3 + 2] = Z; // z (height) if (genUVs) { m.uvs[t2 + 0] = (VX > 1) ? (float)i / (float)(VX - 1) : 0.0f; m.uvs[t2 + 1] = (VZ > 1) ? (float)j / (float)(VZ - 1) : 0.0f; } } } // --- Indices (two triangles per quad), CCW in XY so normals point +Z --- m.indices.reserve(W * H * 6); for (int j = 0; j < H; ++j) { for (int i = 0; i < W; ++i) { const uint32_t i0 = (uint32_t)( j * VX + i ); const uint32_t i1 = (uint32_t)( j * VX + i + 1); const uint32_t i2 = (uint32_t)((j+1) * VX + i ); const uint32_t i3 = (uint32_t)((j+1) * VX + i + 1); m.indices.push_back(i0); m.indices.push_back(i1); m.indices.push_back(i2); m.indices.push_back(i1); m.indices.push_back(i3); m.indices.push_back(i2); } } // --- Smoothed per-vertex normals (area-weighted) --- if (genNormals) { // Accumulate face normals for (size_t k = 0; k < m.indices.size(); k += 3) { const uint32_t ia = m.indices[k+0]; const uint32_t ib = m.indices[k+1]; const uint32_t ic = m.indices[k+2]; const glm::vec3 A{ m.positions[3*ia+0], m.positions[3*ia+1], m.positions[3*ia+2] }; const glm::vec3 B{ m.positions[3*ib+0], m.positions[3*ib+1], m.positions[3*ib+2] }; const glm::vec3 C{ m.positions[3*ic+0], m.positions[3*ic+1], m.positions[3*ic+2] }; const glm::vec3 N = glm::cross(B - A, C - A); // CCW -> +Z on flat areas add3(m.normals, 3*ia, N); add3(m.normals, 3*ib, N); add3(m.normals, 3*ic, N); } // Normalize for (int v = 0; v < N; ++v) { glm::vec3 n{ m.normals[3*v+0], m.normals[3*v+1], m.normals[3*v+2] }; const float len = glm::length(n); if (len > 1e-20f) { n /= len; } else { n = glm::vec3(0, 0, 1); // fallback for degenerate spots } m.normals[3*v+0] = n.x; m.normals[3*v+1] = n.y; m.normals[3*v+2] = n.z; } } return m; } } // namespace asset