#include "render/mesh.h" #include #include #include static bool grow(void **data, size_t *capacity, size_t needed, size_t elem_size) { if (needed <= *capacity) return true; size_t cap = *capacity ? *capacity : 64; while (cap < needed) cap *= 2; void *p = realloc(*data, cap * elem_size); if (!p) return false; *data = p; *capacity = cap; return true; } void MeshBuilder_init(MeshBuilder *b) { memset(b, 0, sizeof(*b)); } void MeshBuilder_free(MeshBuilder *b) { free(b->vertices); free(b->indices); memset(b, 0, sizeof(*b)); } GLuint MeshBuilder_vertex(MeshBuilder *b, Vec3 position, Vec3 normal, Vec3 color) { if (!grow((void **)&b->vertices, &b->vertex_capacity, b->vertex_count + 1, sizeof(Vertex))) { b->out_of_memory = true; return 0; } b->vertices[b->vertex_count] = (Vertex){ position, normal, color }; return (GLuint)b->vertex_count++; } #define MAX_SEGMENTS 96 void MeshBuilder_triangle(MeshBuilder *b, GLuint i0, GLuint i1, GLuint i2) { if (b->out_of_memory) return; /* Enforce counter-clockwise winding relative to the vertex normals. */ const Vertex *v0 = &b->vertices[i0], *v1 = &b->vertices[i1], *v2 = &b->vertices[i2]; Vec3 face = vec3_cross(vec3_sub(v1->position, v0->position), vec3_sub(v2->position, v0->position)); Vec3 normal = vec3_add(vec3_add(v0->normal, v1->normal), v2->normal); if (vec3_dot(face, normal) < 0.0f) { GLuint t = i1; i1 = i2; i2 = t; } if (!grow((void **)&b->indices, &b->index_capacity, b->index_count + 3, sizeof(GLuint))) { b->out_of_memory = true; return; } b->indices[b->index_count++] = i0; b->indices[b->index_count++] = i1; b->indices[b->index_count++] = i2; } void MeshBuilder_quad(MeshBuilder *b, GLuint i0, GLuint i1, GLuint i2, GLuint i3) { MeshBuilder_triangle(b, i0, i1, i2); MeshBuilder_triangle(b, i2, i3, i0); } int MeshBuilder_segments(float length, float cell_size) { int n = (int)ceilf(length / cell_size); if (n < 1) n = 1; if (n > MAX_SEGMENTS) n = MAX_SEGMENTS; return n; } static Vec3 lerp3(Vec3 a, Vec3 b, float t) { return vec3_add(a, vec3_scale(vec3_sub(b, a), t)); } void MeshBuilder_gridQuad(MeshBuilder *b, Vec3 p0, Vec3 p1, Vec3 p2, Vec3 p3, Vec3 normal, Vec3 color, float cell_size) { const int nu = MeshBuilder_segments(vec3_length(vec3_sub(p1, p0)), cell_size); const int nv = MeshBuilder_segments(vec3_length(vec3_sub(p3, p0)), cell_size); const GLuint base = (GLuint)b->vertex_count; for (int j = 0; j <= nv; j++) { float v = (float)j / (float)nv; Vec3 left = lerp3(p0, p3, v), right = lerp3(p1, p2, v); for (int i = 0; i <= nu; i++) MeshBuilder_vertex(b, lerp3(left, right, (float)i / (float)nu), normal, color); } for (int j = 0; j < nv; j++) { for (int i = 0; i < nu; i++) { GLuint a = base + (GLuint)(j * (nu + 1) + i); GLuint c = a + (GLuint)(nu + 1); MeshBuilder_quad(b, a, a + 1, c + 1, c); } } } void MeshBuilder_gridTriangle(MeshBuilder *b, Vec3 p0, Vec3 p1, Vec3 p2, Vec3 normal, Vec3 color, float cell_size) { float longest = fmaxf(vec3_length(vec3_sub(p1, p0)), fmaxf(vec3_length(vec3_sub(p2, p0)), vec3_length(vec3_sub(p2, p1)))); const int n = MeshBuilder_segments(longest, cell_size); const Vec3 du = vec3_scale(vec3_sub(p1, p0), 1.0f / (float)n); const Vec3 dv = vec3_scale(vec3_sub(p2, p0), 1.0f / (float)n); const GLuint base = (GLuint)b->vertex_count; /* Rows j = 0..n, each with n - j + 1 vertices. */ for (int j = 0; j <= n; j++) for (int i = 0; i <= n - j; i++) MeshBuilder_vertex(b, vec3_add(p0, vec3_add(vec3_scale(du, (float)i), vec3_scale(dv, (float)j))), normal, color); GLuint row = base; for (int j = 0; j < n; j++) { GLuint next = row + (GLuint)(n - j + 1); for (int i = 0; i < n - j; i++) { MeshBuilder_triangle(b, row + i, row + i + 1, next + i); if (i < n - j - 1) MeshBuilder_triangle(b, row + i + 1, next + i + 1, next + i); } row = next; } } Mesh Mesh_fromBuilder(MeshBuilder *b) { Mesh m = { 0 }; if (b->out_of_memory || b->index_count == 0) { SDL_Log("Mesh build failed (out of memory or empty)"); MeshBuilder_free(b); return m; } /* bounding sphere around the middle of the bounding box */ Vec3 lo = b->vertices[0].position, hi = lo; for (size_t i = 1; i < b->vertex_count; i++) { Vec3 p = b->vertices[i].position; lo = vec3(fminf(lo.x, p.x), fminf(lo.y, p.y), fminf(lo.z, p.z)); hi = vec3(fmaxf(hi.x, p.x), fmaxf(hi.y, p.y), fmaxf(hi.z, p.z)); } m.center = vec3_scale(vec3_add(lo, hi), 0.5f); for (size_t i = 0; i < b->vertex_count; i++) m.radius = fmaxf(m.radius, vec3_length(vec3_sub(b->vertices[i].position, m.center))); glGenVertexArrays(1, &m.vao); glGenBuffers(1, &m.vbo); glGenBuffers(1, &m.ebo); glBindVertexArray(m.vao); glBindBuffer(GL_ARRAY_BUFFER, m.vbo); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(b->vertex_count * sizeof(Vertex)), b->vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(b->index_count * sizeof(GLuint)), b->indices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, position)); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, normal)); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color)); glEnableVertexAttribArray(2); glBindVertexArray(0); m.index_count = (GLsizei)b->index_count; MeshBuilder_free(b); return m; } void Mesh_draw(const Mesh *mesh) { if (!mesh->vao) return; glBindVertexArray(mesh->vao); glDrawElements(GL_TRIANGLES, mesh->index_count, GL_UNSIGNED_INT, NULL); glBindVertexArray(0); } void Mesh_destroy(Mesh *mesh) { if (mesh->ebo) glDeleteBuffers(1, &mesh->ebo); if (mesh->vbo) glDeleteBuffers(1, &mesh->vbo); if (mesh->vao) glDeleteVertexArrays(1, &mesh->vao); memset(mesh, 0, sizeof(*mesh)); }