From d221bb6c07cc15dee0202c23e7b881609f18206a Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Mon, 29 Sep 2025 17:22:55 +0100 Subject: [PATCH] Move Utah teapot model reading code into dedicated module --- CMakeLists.txt | 1 + main.c | 4 +- model.c | 225 +------------------------------------------ model.h | 2 +- model_format_utah.c | 229 ++++++++++++++++++++++++++++++++++++++++++++ model_format_utah.h | 8 ++ 6 files changed, 245 insertions(+), 224 deletions(-) create mode 100644 model_format_utah.c create mode 100644 model_format_utah.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 18bf350..f0dc4b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable(demo linear_alg.c main.c model.c + model_format_utah.c ) configure_target(demo) target_include_directories(demo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/main.c b/main.c index 197de48..480a3a4 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ #include "gpu.h" -#include "model.h" +#include "model_format_utah.h" #include #include @@ -51,7 +51,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "Usage: %s PATH/TO/MODEL\n", argv[0]); return 1; } - model_t model = model_read(argv[1]); + model_t model = model_format_utah_read(argv[1]); // Set up window glfwInit(); diff --git a/model.c b/model.c index 5653628..cd55d7b 100644 --- a/model.c +++ b/model.c @@ -1,233 +1,16 @@ #include "model.h" #include -#include -#include -#include -#include -#include #include -#include -#define BUFFER_SIZE 256 - -#define FIB_WRAP_COEFF 11400714819323198485LLU - -typedef struct { - uint8_t *rp; - size_t vertex_count; - size_t index_count; - size_t capacity; - size_t buffered; - const int fd; - uint8_t buffer[BUFFER_SIZE]; -} read_ctx_t; - -typedef struct { - unsigned probe_count; - index_t index; - bool occupied; -} table_node_t; - -typedef struct { - table_node_t *nodes; - size_t size; - unsigned shift; - unsigned max_probe_count; -} table_t; - -static uint32_t hash_vertex(vertex_t v) +model_t model_init(size_t vertex_count, size_t index_count) { - const uint32_t px = *((uint32_t *)&v.position.x); - const uint32_t py = *((uint32_t *)&v.position.y); - const uint32_t pz = *((uint32_t *)&v.position.z); - const uint32_t nx = *((uint32_t *)&v.normal.x); - const uint32_t ny = *((uint32_t *)&v.normal.y); - const uint32_t nz = *((uint32_t *)&v.normal.z); - return px ^ (py << 1) ^ (pz << 2) ^ (pz << 3) ^ (nx << 4) ^ (ny << 5) - ^ (nz << 6); -} - -static bool vertex_equals(vertex_t v, vertex_t u) -{ - return v.position.x == u.position.x && v.position.y == u.position.y - && v.position.z == u.position.z && v.normal.x == u.normal.x - && v.normal.y == u.normal.y && v.normal.z == u.normal.z; -} - -static unsigned wrap(table_t *table, uint32_t hash) -{ - return (unsigned)((hash * FIB_WRAP_COEFF) >> table->shift); -} - -static bool lookup( - table_t *table, model_t *model, unsigned base, vertex_t vertex, - index_t *index_out) -{ - for (unsigned i = 0; i < table->max_probe_count; ++i) { - const unsigned t = (base + i) & (UINT64_MAX >> table->shift); - const table_node_t *node = table->nodes + t; - if (!node->occupied) - return false; - - const vertex_t node_vertex = model->vertices[node->index]; - if (vertex_equals(vertex, node_vertex)) { - *index_out = node->index; - return true; - } - } - return false; -} - -static void insert(table_t *table, unsigned base, index_t index) -{ - for (unsigned i = 0; i < table->size; ++i) { - const unsigned t = (base + i) & (UINT64_MAX >> table->shift); - table_node_t *node = table->nodes + t; - - if (!node->occupied) { - node->occupied = true; - node->probe_count = i; - node->index = index; - if (i > table->max_probe_count) - table->max_probe_count = i; - return; - } - - if (node->probe_count < i) { - const unsigned node_probe_count = node->probe_count; - node->probe_count = i; - i = node_probe_count; - const index_t node_index = node->index; - node->index = index; - index = node_index; - } - } - - assert(false); -} - -static index_t handle_vertex(table_t *table, model_t *model, vertex_t vertex) -{ - index_t index; - const unsigned base = wrap(table, hash_vertex(vertex)); - const bool exists = lookup(table, model, base, vertex, &index); - if (exists) - return index; - index = model->vertex_count; - model->vertices[model->vertex_count++] = vertex; - insert(table, base, index); - return index; -} - -static uint8_t read_byte(read_ctx_t *ctx) -{ - if (ctx->rp >= ctx->buffer + ctx->buffered) { - const ssize_t res = read(ctx->fd, ctx->buffer, BUFFER_SIZE); - assert(res != -1); - ctx->buffered = (size_t)res; - ctx->rp = ctx->buffer; - } - return *ctx->rp++; -} - -static size_t read_triangle_count(read_ctx_t *ctx) -{ - size_t triangle_count = 0; - while (1) { - const uint8_t b = read_byte(ctx); - if (isdigit(b)) { - triangle_count = 10 * triangle_count + b - '0'; - } else { - assert(b == '\n'); - break; - } - } - return triangle_count; -} - -static float read_float(read_ctx_t *ctx) -{ - bool negative = false; - float value = 0, digit_div = 0; - while (1) { - const uint8_t b = read_byte(ctx); - if (b == '-') { - assert(value == 0); - negative = true; - } else if (b == '.') { - digit_div = 10; - } else if (isdigit(b)) { - if (digit_div == 0) { - value = 10 * value + (b - '0'); - } else { - value += (float)(b - '0') / digit_div; - digit_div *= 10; - } - } else { - assert(b == ' ' || b == '\n'); - break; - } - } - return (negative ? -1 : 1) * value; -} - -static vec3_t read_vec3(read_ctx_t *ctx) -{ - const float x = read_float(ctx); - const float y = read_float(ctx); - const float z = read_float(ctx); - return (vec3_t) { x, y, z }; -} - -static vertex_t read_vertex(read_ctx_t *ctx) -{ - const vec3_t position = read_vec3(ctx); - const vec3_t normal = read_vec3(ctx); - return (vertex_t) { position, normal }; -} - -model_t model_read(const char *path) -{ - read_ctx_t ctx = { .fd = open(path, O_RDONLY), .rp = ctx.buffer }; - assert(ctx.fd != -1); - - const size_t triangle_count = read_triangle_count(&ctx); - const size_t index_count = 3 * triangle_count; - model_t model = { - .indices = malloc(index_count * sizeof(index_t)), + .indices = calloc(index_count, sizeof(index_t)), .index_count = index_count, - .vertices = malloc(index_count * sizeof(vertex_t)), + .vertices = calloc(vertex_count, sizeof(vertex_t)), + .vertex_count = vertex_count, }; - assert(model.vertices != nullptr); - assert(model.indices != nullptr); - - table_t table = { .max_probe_count = 0 }; - table.size = stdc_bit_ceil((size_t)(1.5 * index_count)); - table.shift = 64 - stdc_trailing_zeros(table.size); - table.nodes = calloc(table.size, sizeof(table_node_t)); - assert(table.nodes != nullptr); - - for (size_t i = 0; i < index_count; ++i) { - const vertex_t vertex = read_vertex(&ctx); - index_t index = handle_vertex(&table, &model, vertex); - model.indices[i] = index; - - // Skip newline after each triangle - if (i % 3 == 2) { - const uint8_t b = read_byte(&ctx); - assert(b == '\n'); - } - } - - // Shrink vertex buffer down to needed size - model.vertices - = realloc(model.vertices, model.vertex_count * sizeof(vertex_t)); - assert(model.vertices != nullptr); - - free(table.nodes); - close(ctx.fd); return model; } diff --git a/model.h b/model.h index ab7dd0b..03061ea 100644 --- a/model.h +++ b/model.h @@ -20,7 +20,7 @@ typedef struct { size_t index_count; } model_t; -model_t model_read(const char *path); +model_t model_init(size_t vertex_count, size_t index_count); void model_cleanup(model_t *m); #endif diff --git a/model_format_utah.c b/model_format_utah.c new file mode 100644 index 0000000..4d7808b --- /dev/null +++ b/model_format_utah.c @@ -0,0 +1,229 @@ +#include "model_format_utah.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 256 + +#define FIB_WRAP_COEFF 11400714819323198485LLU + +typedef struct { + uint8_t *rp; + size_t vertex_count; + size_t index_count; + size_t capacity; + size_t buffered; + const int fd; + uint8_t buffer[BUFFER_SIZE]; +} read_ctx_t; + +typedef struct { + unsigned probe_count; + index_t index; + bool occupied; +} table_node_t; + +typedef struct { + table_node_t *nodes; + size_t size; + unsigned shift; + unsigned max_probe_count; +} table_t; + +static uint32_t hash_vertex(vertex_t v) +{ + const uint32_t px = *((uint32_t *)&v.position.x); + const uint32_t py = *((uint32_t *)&v.position.y); + const uint32_t pz = *((uint32_t *)&v.position.z); + const uint32_t nx = *((uint32_t *)&v.normal.x); + const uint32_t ny = *((uint32_t *)&v.normal.y); + const uint32_t nz = *((uint32_t *)&v.normal.z); + return px ^ (py << 1) ^ (pz << 2) ^ (pz << 3) ^ (nx << 4) ^ (ny << 5) + ^ (nz << 6); +} + +static bool vertex_equals(vertex_t v, vertex_t u) +{ + return v.position.x == u.position.x && v.position.y == u.position.y + && v.position.z == u.position.z && v.normal.x == u.normal.x + && v.normal.y == u.normal.y && v.normal.z == u.normal.z; +} + +static unsigned wrap(table_t *table, uint32_t hash) +{ + return (unsigned)((hash * FIB_WRAP_COEFF) >> table->shift); +} + +static bool lookup( + table_t *table, model_t *model, unsigned base, vertex_t vertex, + index_t *index_out) +{ + for (unsigned i = 0; i < table->max_probe_count; ++i) { + const unsigned t = (base + i) & (UINT64_MAX >> table->shift); + const table_node_t *node = table->nodes + t; + if (!node->occupied) + return false; + + const vertex_t node_vertex = model->vertices[node->index]; + if (vertex_equals(vertex, node_vertex)) { + *index_out = node->index; + return true; + } + } + return false; +} + +static void insert(table_t *table, unsigned base, index_t index) +{ + for (unsigned i = 0; i < table->size; ++i) { + const unsigned t = (base + i) & (UINT64_MAX >> table->shift); + table_node_t *node = table->nodes + t; + + if (!node->occupied) { + node->occupied = true; + node->probe_count = i; + node->index = index; + if (i > table->max_probe_count) + table->max_probe_count = i; + return; + } + + if (node->probe_count < i) { + const unsigned node_probe_count = node->probe_count; + node->probe_count = i; + i = node_probe_count; + const index_t node_index = node->index; + node->index = index; + index = node_index; + } + } + + assert(false); +} + +static index_t handle_vertex(table_t *table, model_t *model, vertex_t vertex) +{ + index_t index; + const unsigned base = wrap(table, hash_vertex(vertex)); + const bool exists = lookup(table, model, base, vertex, &index); + if (exists) + return index; + index = model->vertex_count; + model->vertices[model->vertex_count++] = vertex; + insert(table, base, index); + return index; +} + +static uint8_t read_byte(read_ctx_t *ctx) +{ + if (ctx->rp >= ctx->buffer + ctx->buffered) { + const ssize_t res = read(ctx->fd, ctx->buffer, BUFFER_SIZE); + assert(res != -1); + ctx->buffered = (size_t)res; + ctx->rp = ctx->buffer; + } + return *ctx->rp++; +} + +static size_t read_triangle_count(read_ctx_t *ctx) +{ + size_t triangle_count = 0; + while (1) { + const uint8_t b = read_byte(ctx); + if (isdigit(b)) { + triangle_count = 10 * triangle_count + b - '0'; + } else { + assert(b == '\n'); + break; + } + } + return triangle_count; +} + +static float read_float(read_ctx_t *ctx) +{ + bool negative = false; + float value = 0, digit_div = 0; + while (1) { + const uint8_t b = read_byte(ctx); + if (b == '-') { + assert(value == 0); + negative = true; + } else if (b == '.') { + digit_div = 10; + } else if (isdigit(b)) { + if (digit_div == 0) { + value = 10 * value + (b - '0'); + } else { + value += (float)(b - '0') / digit_div; + digit_div *= 10; + } + } else { + assert(b == ' ' || b == '\n'); + break; + } + } + return (negative ? -1 : 1) * value; +} + +static vec3_t read_vec3(read_ctx_t *ctx) +{ + const float x = read_float(ctx); + const float y = read_float(ctx); + const float z = read_float(ctx); + return (vec3_t) { x, y, z }; +} + +static vertex_t read_vertex(read_ctx_t *ctx) +{ + const vec3_t position = read_vec3(ctx); + const vec3_t normal = read_vec3(ctx); + return (vertex_t) { position, normal }; +} + +model_t model_format_utah_read(const char *path) +{ + read_ctx_t ctx = { .fd = open(path, O_RDONLY), .rp = ctx.buffer }; + assert(ctx.fd != -1); + + const size_t triangle_count = read_triangle_count(&ctx); + const size_t index_count = 3 * triangle_count; + + // Begin with vertex buffer at maximum possible size + model_t model = model_init(index_count, index_count); + model.vertex_count = 0; + + // Set up hash table for vertex de-duplication + table_t table = { .max_probe_count = 0 }; + table.size = stdc_bit_ceil((size_t)(1.5 * index_count)); + table.shift = 64 - stdc_trailing_zeros(table.size); + table.nodes = calloc(table.size, sizeof(table_node_t)); + assert(table.nodes != nullptr); + + for (size_t i = 0; i < index_count; ++i) { + const vertex_t vertex = read_vertex(&ctx); + index_t index = handle_vertex(&table, &model, vertex); + model.indices[i] = index; + + // Skip newline after each triangle + if (i % 3 == 2) { + const uint8_t b = read_byte(&ctx); + assert(b == '\n'); + } + } + + // Shrink vertex buffer down to needed size + model.vertices + = realloc(model.vertices, model.vertex_count * sizeof(vertex_t)); + assert(model.vertices != nullptr); + + free(table.nodes); + close(ctx.fd); + return model; +} diff --git a/model_format_utah.h b/model_format_utah.h new file mode 100644 index 0000000..88ba647 --- /dev/null +++ b/model_format_utah.h @@ -0,0 +1,8 @@ +#ifndef MODEL_FORMAT_UTAH_H +#define MODEL_FORMAT_UTAH_H + +#include "model.h" + +model_t model_format_utah_read(const char *path); + +#endif