Nimbin[12]?Games / Entropie / entropie/src/render/mesh.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/render/mesh.c 6.6 KB · 187 lines raw
#include "render/mesh.h"

#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL.h>

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));
}