|
| 1 | +#include "icosphere.hpp" |
| 2 | + |
| 3 | +#include <array> |
| 4 | +#include <cmath> |
| 5 | +#include <unordered_map> |
| 6 | + |
| 7 | +#include "constants.hpp" |
| 8 | + |
| 9 | +namespace { |
| 10 | + // Hash function for glm::vec3 to use in unordered_map |
| 11 | + struct Vec3Hash { |
| 12 | + std::size_t operator()(const glm::vec3& v) const { |
| 13 | + return std::hash<float>()(v.x) ^ (std::hash<float>()(v.y) << 1) ^ (std::hash<float>()(v.z) << 2); |
| 14 | + } |
| 15 | + }; |
| 16 | + |
| 17 | + // Equality function for glm::vec3 |
| 18 | + struct Vec3Equal { |
| 19 | + bool operator()(const glm::vec3& a, const glm::vec3& b) const { |
| 20 | + const float epsilon = 1e-6f; |
| 21 | + return std::abs(a.x - b.x) < epsilon && std::abs(a.y - b.y) < epsilon && std::abs(a.z - b.z) < epsilon; |
| 22 | + } |
| 23 | + }; |
| 24 | +} |
| 25 | + |
| 26 | +model::Icosphere::Icosphere(float radius, int subdivisions) : radius(radius), subdivisions(subdivisions) { |
| 27 | + glCreateVertexArrays(1, &glAttributesIdx); |
| 28 | + glCreateBuffers(1, &glBufferIdx); |
| 29 | + glCreateBuffers(1, &glIndexBufferIdx); |
| 30 | + |
| 31 | + { |
| 32 | + GLuint glAttrSlot1 = 0; |
| 33 | + |
| 34 | + glVertexArrayVertexBuffer(glAttributesIdx, glAttrSlot1, glBufferIdx, 0, sizeof(Vertex3D)); |
| 35 | + |
| 36 | + glVertexArrayAttribFormat(glAttributesIdx, 0, 3, GL_FLOAT, GL_FALSE, 0); |
| 37 | + glEnableVertexArrayAttrib(glAttributesIdx, 0); |
| 38 | + glVertexArrayAttribBinding(glAttributesIdx, 0, glAttrSlot1); |
| 39 | + |
| 40 | + glVertexArrayAttribFormat(glAttributesIdx, 1, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex3D, normal)); |
| 41 | + glEnableVertexArrayAttrib(glAttributesIdx, 1); |
| 42 | + glVertexArrayAttribBinding(glAttributesIdx, 1, glAttrSlot1); |
| 43 | + |
| 44 | + glVertexArrayAttribFormat(glAttributesIdx, 2, 2, GL_FLOAT, GL_FALSE, offsetof(Vertex3D, uv)); |
| 45 | + glEnableVertexArrayAttrib(glAttributesIdx, 2); |
| 46 | + glVertexArrayAttribBinding(glAttributesIdx, 2, glAttrSlot1); |
| 47 | + |
| 48 | + glVertexArrayAttribFormat(glAttributesIdx, 3, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex3D, tangent)); |
| 49 | + glEnableVertexArrayAttrib(glAttributesIdx, 3); |
| 50 | + glVertexArrayAttribBinding(glAttributesIdx, 3, glAttrSlot1); |
| 51 | + |
| 52 | + glVertexArrayElementBuffer(glAttributesIdx, glIndexBufferIdx); |
| 53 | + } |
| 54 | + |
| 55 | + generateIcosphere(radius, subdivisions); |
| 56 | + |
| 57 | + glNamedBufferData(glBufferIdx, vertices.size() * sizeof(Vertex3D), vertices.data(), GL_STATIC_DRAW); |
| 58 | + glNamedBufferData(glIndexBufferIdx, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW); |
| 59 | +} |
| 60 | + |
| 61 | +model::Icosphere::~Icosphere() { |
| 62 | + glDeleteVertexArrays(1, &glAttributesIdx); |
| 63 | + glDeleteBuffers(1, &glBufferIdx); |
| 64 | + glDeleteBuffers(1, &glIndexBufferIdx); |
| 65 | +} |
| 66 | + |
| 67 | +void model::Icosphere::draw() const { |
| 68 | + glBindVertexArray(glAttributesIdx); |
| 69 | + glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); |
| 70 | +} |
| 71 | + |
| 72 | +void model::Icosphere::generateIcosphere(float radius, int subdivisions) { |
| 73 | + vertices.clear(); |
| 74 | + indices.clear(); |
| 75 | + |
| 76 | + // Create icosahedron vertices |
| 77 | + const float t = (1.0f + std::sqrt(5.0f)) / 2.0f; // Golden ratio |
| 78 | + |
| 79 | + std::vector<glm::vec3> icosahedronVertices = { |
| 80 | + glm::normalize(glm::vec3(-1, t, 0)), glm::normalize(glm::vec3(1, t, 0)), glm::normalize(glm::vec3(-1, -t, 0)), |
| 81 | + glm::normalize(glm::vec3(1, -t, 0)), glm::normalize(glm::vec3(0, -1, t)), glm::normalize(glm::vec3(0, 1, t)), |
| 82 | + glm::normalize(glm::vec3(0, -1, -t)), glm::normalize(glm::vec3(0, 1, -t)), glm::normalize(glm::vec3(t, 0, -1)), |
| 83 | + glm::normalize(glm::vec3(t, 0, 1)), glm::normalize(glm::vec3(-t, 0, -1)), glm::normalize(glm::vec3(-t, 0, 1))}; |
| 84 | + |
| 85 | + // Create icosahedron faces |
| 86 | + std::vector<std::array<int, 3>> icosahedronFaces = { |
| 87 | + {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}, |
| 88 | + {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}}; |
| 89 | + |
| 90 | + // Use a map to avoid duplicate vertices |
| 91 | + std::unordered_map<glm::vec3, GLuint, Vec3Hash, Vec3Equal> vertexMap; |
| 92 | + |
| 93 | + auto addVertex = [&](const glm::vec3& position) -> GLuint { |
| 94 | + auto it = vertexMap.find(position); |
| 95 | + if (it != vertexMap.end()) { |
| 96 | + return it->second; |
| 97 | + } |
| 98 | + |
| 99 | + GLuint index = vertices.size(); |
| 100 | + glm::vec3 normal = glm::normalize(position); |
| 101 | + glm::vec3 scaledPosition = normal * radius; |
| 102 | + glm::vec2 uv = calculateSphericalUV(normal); |
| 103 | + |
| 104 | + vertices.push_back({scaledPosition, normal, uv}); |
| 105 | + vertexMap[position] = index; |
| 106 | + return index; |
| 107 | + }; |
| 108 | + |
| 109 | + // Add initial icosahedron vertices |
| 110 | + for (const auto& vertex : icosahedronVertices) { |
| 111 | + addVertex(vertex); |
| 112 | + } |
| 113 | + |
| 114 | + // Add initial faces |
| 115 | + std::vector<std::array<GLuint, 3>> faces; |
| 116 | + for (const auto& face : icosahedronFaces) { |
| 117 | + faces.push_back({static_cast<GLuint>(face[0]), static_cast<GLuint>(face[1]), static_cast<GLuint>(face[2])}); |
| 118 | + } |
| 119 | + |
| 120 | + // Subdivide faces |
| 121 | + for (int level = 0; level < subdivisions; ++level) { |
| 122 | + std::vector<std::array<GLuint, 3>> newFaces; |
| 123 | + |
| 124 | + for (const auto& face : faces) { |
| 125 | + glm::vec3 v1 = glm::normalize(vertices[face[0]].pos / radius); |
| 126 | + glm::vec3 v2 = glm::normalize(vertices[face[1]].pos / radius); |
| 127 | + glm::vec3 v3 = glm::normalize(vertices[face[2]].pos / radius); |
| 128 | + |
| 129 | + // Calculate midpoints and normalize them to the sphere surface |
| 130 | + glm::vec3 m1 = glm::normalize((v1 + v2) * 0.5f); |
| 131 | + glm::vec3 m2 = glm::normalize((v2 + v3) * 0.5f); |
| 132 | + glm::vec3 m3 = glm::normalize((v3 + v1) * 0.5f); |
| 133 | + |
| 134 | + GLuint i1 = addVertex(v1); |
| 135 | + GLuint i2 = addVertex(v2); |
| 136 | + GLuint i3 = addVertex(v3); |
| 137 | + GLuint im1 = addVertex(m1); |
| 138 | + GLuint im2 = addVertex(m2); |
| 139 | + GLuint im3 = addVertex(m3); |
| 140 | + |
| 141 | + // Create 4 new triangles |
| 142 | + newFaces.push_back({i1, im1, im3}); |
| 143 | + newFaces.push_back({i2, im2, im1}); |
| 144 | + newFaces.push_back({i3, im3, im2}); |
| 145 | + newFaces.push_back({im1, im2, im3}); |
| 146 | + } |
| 147 | + |
| 148 | + faces = std::move(newFaces); |
| 149 | + } |
| 150 | + |
| 151 | + // Convert faces to indices |
| 152 | + for (const auto& face : faces) { |
| 153 | + indices.push_back(face[0]); |
| 154 | + indices.push_back(face[1]); |
| 155 | + indices.push_back(face[2]); |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +glm::vec2 model::Icosphere::calculateSphericalUV(const glm::vec3& normal) { |
| 160 | + float u = 0.5f + std::atan2(normal.z, normal.x) / constants::TAU; |
| 161 | + float v = 0.5f - std::asin(normal.y) / constants::PI; |
| 162 | + return glm::vec2(u, v); |
| 163 | +} |
0 commit comments