3D OBJ loading
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetmeshloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#define FAST_OBJ_IMPLEMENTATION
|
||||
#include "fast_obj.h"
|
||||
|
||||
// Wraps assetfile_t for fast_obj callbacks. Only the primary OBJ file is
|
||||
// opened through the ZIP; returning NULL for any secondary path (e.g. .mtl)
|
||||
// causes fast_obj to skip material loading, which is fine for our use case.
|
||||
static void* meshFileOpen(const char *path, void *user_data) {
|
||||
assetfile_t *file = (assetfile_t *)user_data;
|
||||
if(file->zipFile != NULL) return NULL;
|
||||
errorret_t ret = assetFileOpen(file);
|
||||
if(ret.code != ERROR_OK) return NULL;
|
||||
return file;
|
||||
}
|
||||
|
||||
static void meshFileClose(void *handle, void *user_data) {
|
||||
if(handle == NULL) return;
|
||||
assetfile_t *file = (assetfile_t *)handle;
|
||||
errorCatch(assetFileClose(file));
|
||||
}
|
||||
|
||||
static size_t meshFileRead(void *handle, void *dst, size_t bytes, void *user_data) {
|
||||
if(handle == NULL) return 0;
|
||||
assetfile_t *file = (assetfile_t *)handle;
|
||||
errorret_t ret = assetFileRead(file, dst, bytes);
|
||||
if(ret.code != ERROR_OK) return 0;
|
||||
return (size_t)file->lastRead;
|
||||
}
|
||||
|
||||
static unsigned long meshFileSize(void *handle, void *user_data) {
|
||||
if(handle == NULL) return 0;
|
||||
assetfile_t *file = (assetfile_t *)handle;
|
||||
return (unsigned long)file->size;
|
||||
}
|
||||
|
||||
errorret_t assetMeshLoader(assetfile_t *file) {
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(file->output, "Asset file output cannot be NULL.");
|
||||
|
||||
fastObjCallbacks callbacks = {
|
||||
.file_open = meshFileOpen,
|
||||
.file_close = meshFileClose,
|
||||
.file_read = meshFileRead,
|
||||
.file_size = meshFileSize,
|
||||
};
|
||||
|
||||
fastObjMesh *obj = fast_obj_read_with_callbacks(file->filename, &callbacks, file);
|
||||
if(obj == NULL) {
|
||||
errorThrow("Failed to parse OBJ: %s", file->filename);
|
||||
}
|
||||
|
||||
// Count output vertices, triangulating any polygons via fan decomposition.
|
||||
int32_t vertexCount = 0;
|
||||
for(unsigned int i = 0; i < obj->face_count; i++) {
|
||||
if(obj->face_vertices[i] >= 3) {
|
||||
vertexCount += (int32_t)(obj->face_vertices[i] - 2) * 3;
|
||||
}
|
||||
}
|
||||
|
||||
if(vertexCount == 0) {
|
||||
fast_obj_destroy(obj);
|
||||
errorThrow("OBJ has no valid faces: %s", file->filename);
|
||||
}
|
||||
|
||||
meshvertex_t *vertices = (meshvertex_t *)memoryAllocate(
|
||||
sizeof(meshvertex_t) * vertexCount
|
||||
);
|
||||
memoryZero(vertices, sizeof(meshvertex_t) * vertexCount);
|
||||
|
||||
int32_t vi = 0;
|
||||
fastObjIndex *idx = obj->indices;
|
||||
for(unsigned int fi = 0; fi < obj->face_count; fi++) {
|
||||
unsigned int fv = obj->face_vertices[fi];
|
||||
// Fan triangulation: anchor at idx[0], triangle (0, j, j+1).
|
||||
for(unsigned int j = 1; j + 1 < fv; j++) {
|
||||
fastObjIndex corners[3] = { idx[0], idx[j], idx[j + 1] };
|
||||
for(int c = 0; c < 3; c++) {
|
||||
vertices[vi].color = COLOR_WHITE_4B;
|
||||
|
||||
unsigned int p = corners[c].p;
|
||||
if(p > 0 && p < obj->position_count) {
|
||||
vertices[vi].pos[0] = obj->positions[p * 3 + 0];
|
||||
vertices[vi].pos[1] = obj->positions[p * 3 + 1];
|
||||
vertices[vi].pos[2] = obj->positions[p * 3 + 2];
|
||||
}
|
||||
|
||||
unsigned int t = corners[c].t;
|
||||
if(t > 0 && t < obj->texcoord_count) {
|
||||
vertices[vi].uv[0] = obj->texcoords[t * 2 + 0];
|
||||
vertices[vi].uv[1] = obj->texcoords[t * 2 + 1];
|
||||
}
|
||||
|
||||
vi++;
|
||||
}
|
||||
}
|
||||
idx += fv;
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
errorret_t ret = meshInit(
|
||||
(mesh_t *)file->output,
|
||||
MESH_PRIMITIVE_TYPE_TRIANGLES,
|
||||
vertexCount,
|
||||
vertices
|
||||
);
|
||||
memoryFree(vertices);
|
||||
return ret;
|
||||
}
|
||||
|
||||
errorret_t assetMeshLoad(const char_t *path, mesh_t *out) {
|
||||
return assetLoad(path, assetMeshLoader, NULL, out);
|
||||
}
|
||||
Reference in New Issue
Block a user