This commit is contained in:
alessandro bason 2026-03-03 16:57:56 +01:00
commit 7f37187c92
46 changed files with 62931 additions and 0 deletions

472
src/obj.c Normal file
View file

@ -0,0 +1,472 @@
#include "colla/colla.h"
#include "libs/sokol_gfx.h"
#include "libs/stb_image.h"
#define CGLTF_IMPLEMENTATION
#include "libs/cgltf.h"
#include "vecmath.h"
typedef struct vertex_t vertex_t;
struct vertex_t {
vec3 pos;
vec3 norm;
vec2 tex;
};
typedef struct material_t material_t;
struct material_t {
// texture
vec3 ambient_color;
vec3 diff_color;
/*
* (*) -> unused
* Ka: ambient color
* Kd: diffuse color
* Ks*: specular color
* Ns*: specular exponent
* d*: dissolve (opacity)
* Ni*: optical density (???)
* Ke*: emissive color
* illum*: illumination mode:
0. Color on and Ambient off
1. Color on and Ambient on
2. Highlight on
3. Reflection on and Ray trace on
4. Transparency: Glass on, Reflection: Ray trace on
5. Reflection: Fresnel on and Ray trace on
6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
8. Reflection on and Ray trace off
9. Transparency: Glass on, Reflection: Ray trace off
10. Casts shadows onto invisible surfaces
* */
};
typedef struct obj_t obj_t;
struct obj_t {
sg_bindings bindings;
u16 draw_count;
};
darr_define(f2_list_t, vec2);
darr_define(f3_list_t, vec3);
darr_define(i3_list_t, int3);
#define list_get(l, i) do { } while (0)
typedef struct {
f3_list_t *verts;
f3_list_t *norms;
f2_list_t *uvs;
i3_list_t *faces;
u32 vcount;
u32 ncount;
u32 tcount;
u32 fcount;
} obj_ctx_t;
#define READ_F2(arr, c) do { \
vec2 v = {0}; \
istr_skip_whitespace(in); istr_get_float(in, &v.x); \
istr_skip_whitespace(in); istr_get_float(in, &v.y); \
darr_push(arena, arr, v); \
++(c); \
} while (0)
#define READ_F3(arr, c) do { \
vec3 v = {0}; \
istr_skip_whitespace(in); istr_get_float(in, &v.x); \
istr_skip_whitespace(in); istr_get_float(in, &v.y); \
istr_skip_whitespace(in); istr_get_float(in, &v.z); \
darr_push(arena, arr, v); \
++(c); \
} while (0)
#define READ_I3(arr, c) do { \
int3 v = {0}; \
istr_skip_whitespace(in); istr_get_i32(in, &v.x); \
istr_skip_whitespace(in); istr_get_i32(in, &v.y); \
istr_skip_whitespace(in); istr_get_i32(in, &v.z); \
darr_push(arena, arr, v); \
++(c); \
} while (0)
void obj__parse_line(arena_t *arena, instream_t *in, obj_ctx_t *ctx) {
if (istr_peek(in) == '#') {
return;
}
switch (istr_peek(in)) {
// vertex stuff
case 'v':
istr_skip(in, 1);
switch (istr_get(in)) {
// vertex
case ' ':
READ_F3(ctx->verts, ctx->vcount);
break;
// normal
case 'n':
READ_F3(ctx->norms, ctx->ncount);
break;
// texture
case 't':
READ_F2(ctx->uvs, ctx->tcount);
break;
}
return;
// faces
case 'f':
READ_I3(ctx->faces, ctx->fcount);
return;
// smooth shading
case 's':
// not implemented
return;
// group
case 'g':
// not implemented
return;
// object
case 'o':
// not implemented
return;
case '#':
return;
}
strview_t word = istr_get_word(in);
if (strv_equals(word, strv("mtllib"))) {
// load mtl file
}
else if (strv_equals(word, strv("usemtl"))) {
// use material
}
}
obj_t obj_load(arena_t scratch, strview_t filename) {
obj_t out = {0};
str_t text = os_file_read_all_str(&scratch, filename);
instream_t in = istr_init(strv(text));
obj_ctx_t ctx = {0};
while (!istr_is_finished(&in)) {
instream_t line = istr_init(istr_get_line(&in));
obj__parse_line(&scratch, &line, &ctx);
}
debug("%u %u %u", ctx.vcount, ctx.ncount, ctx.tcount);
colla_assert(ctx.vcount == ctx.ncount && ctx.vcount == ctx.tcount);
// copy over vertex data
vertex_t *vertices = alloc(&scratch, vertex_t, ctx.vcount);
{
u32 i = 0;
for_each (v, ctx.verts) {
for (int k = 0; k < v->count; ++k) {
vertices[i++].pos = v->items[k];
}
}
}
{
u32 i = 0;
for_each (v, ctx.uvs) {
for (int k = 0; k < v->count; ++k) {
vertices[i++].tex = v->items[k];
}
}
}
{
u32 i = 0;
for_each (v, ctx.norms) {
for (int k = 0; k < v->count; ++k) {
vertices[i++].norm = v->items[k];
}
}
}
// copy over indices data
u32 icount = ctx.fcount * 3;
u16 *indices = alloc(&scratch, u16, icount);
{
u32 i = 0;
for_each (v, ctx.faces) {
for (int k = 0; k < v->count; ++k) {
indices[i++] = v->items[k].x - 1;
indices[i++] = v->items[k].y - 1;
indices[i++] = v->items[k].z - 1;
}
}
}
sg_buffer vertex_buffer = sg_make_buffer(&(sg_buffer_desc){
.data = {
.ptr = vertices,
.size = sizeof(vertex_t) * ctx.vcount,
},
.usage = {
.vertex_buffer = true,
.immutable = true,
},
});
sg_buffer index_buffer = sg_make_buffer(&(sg_buffer_desc){
.data = {
.ptr = indices,
.size = sizeof(u16) * ctx.fcount * 3,
},
.usage = {
.index_buffer = true,
.immutable = true,
},
});
return (obj_t){
.bindings = {
.vertex_buffers[0] = vertex_buffer,
.index_buffer = index_buffer,
// .views[0] = {0},
},
.draw_count = icount,
};
}
cgltf_result obj__cgltf_file_read_cb(
const cgltf_memory_options *m,
const cgltf_file_options *opt,
const char *path,
cgltf_size *size,
void **data
) {
debug("(cgltf) loading %s", path);
arena_t *arena = opt->user_data;
buffer_t buf = os_file_read_all(arena, strv(path));
if (!buf.data) {
return cgltf_result_io_error;
}
*data = buf.data;
*size = buf.len;
return cgltf_result_success;
}
void obj__cgltf_file_release_callback(
const cgltf_memory_options *m,
const cgltf_file_options *opt,
void *data,
cgltf_size size
) {
// no-op
}
#define MAX_SCENE_OBJECTS 64
typedef struct scene_t scene_t;
struct scene_t {
obj_t objects[MAX_SCENE_OBJECTS];
int object_count;
};
sg_image obj__load_texture(arena_t scratch, cgltf_texture *texture) {
if (!texture || !texture->image) {
return (sg_image){0};
}
cgltf_image *image = texture->image;
u8 *bytes = NULL;
usize size = 0;
if (image->uri) {
buffer_t buf = os_file_read_all(&scratch, strv(image->uri));
bytes = buf.data;
size = buf.len;
}
else if (image->buffer_view) {
cgltf_buffer_view *view = image->buffer_view;
bytes = (u8*)view->buffer->data + view->offset;
size = view->size;
}
else {
fatal("idk how do load this dog (%s)", texture->name);
}
int width, height, comp;
u8 *pixels = stbi_load_from_memory(bytes, (int)size, &width, &height, &comp, 4);
if (!pixels) {
fatal("(stbi) couldn't load image %s: %s", image->name, stbi_failure_reason());
}
sg_image out = sg_make_image(&(sg_image_desc){
.pixel_format = SG_PIXELFORMAT_RGBA8,
.width = width,
.height = height,
.data.mip_levels[0] = {
.ptr = pixels,
.size = width * height * 4,
},
});
stbi_image_free(pixels);
return out;
}
scene_t obj_load_gltf(arena_t scratch, strview_t filename) {
scene_t scene = {0};
str_t fname = str(&scratch, filename);
buffer_t buf = os_file_read_all(&scratch, filename);
cgltf_options options = {
.file = {
.read = obj__cgltf_file_read_cb,
.release = obj__cgltf_file_release_callback,
.user_data = &scratch,
},
};
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(
&options,
buf.data,
buf.len,
&data
);
if (result != cgltf_result_success) {
fatal("(cgltf) failed to load %v", filename);
return (scene_t){0};
}
result = cgltf_load_buffers(&options, data, fname.buf);
if (result != cgltf_result_success) {
fatal("(cgltf) failed to load %v's buffers", filename);
return (scene_t){0};
}
colla_assert(data->meshes_count == 1);
sg_sampler sampler = sg_make_sampler(&(sg_sampler_desc){
.min_filter = SG_FILTER_NEAREST,
.mag_filter = SG_FILTER_NEAREST,
});
u32 missing_texture[] = {
0xFFFF00FF, 0x00000000,
0x00000000, 0xFFFF00FF,
};
sg_image missing_img = sg_make_image(&(sg_image_desc){
.width = 2,
.height = 2,
.pixel_format = SG_PIXELFORMAT_RGBA8,
.data.mip_levels[0] = SG_RANGE(missing_texture),
});
for (usize i = 0; i < data->meshes_count; ++i) {
cgltf_mesh *mesh = &data->meshes[i];
colla_assert(MAX_SCENE_OBJECTS > mesh->primitives_count);
scene.object_count = mesh->primitives_count;
for (usize p = 0; p < mesh->primitives_count; ++p) {
cgltf_primitive *prim = &mesh->primitives[p];
obj_t *obj = &scene.objects[p];
// HACK: HELL!
usize vert_count = prim->attributes[0].data->count;
vertex_t *verts = alloc(&scratch, vertex_t, vert_count);
cgltf_accessor *in = prim->indices;
u16 *indices = alloc(&scratch, u16, in->count);
for (usize k = 0; k < in->count; ++k) {
indices[k] = (u16)cgltf_accessor_read_index(in, k);
}
cgltf_material *mat = prim->material;
cgltf_texture_view texture_view = mat->pbr_metallic_roughness.base_color_texture;
sg_image image = obj__load_texture(scratch, texture_view.texture);
if (image.id == 0) {
image = missing_img;
}
sg_view view = sg_make_view(&(sg_view_desc){
.texture.image = image,
});
obj->bindings.views[0] = view;
obj->bindings.samplers[0] = sampler;
for (usize a = 0; a < prim->attributes_count; ++a) {
cgltf_attribute *attr = &prim->attributes[a];
cgltf_accessor *acc = attr->data;
switch (attr->type) {
case cgltf_attribute_type_position:
{
for (usize k = 0; k < acc->count; ++k) {
cgltf_accessor_read_float(acc, k, verts[k].pos.data, 3);
}
break;
}
case cgltf_attribute_type_normal:
for (usize k = 0; k < acc->count; ++k) {
cgltf_accessor_read_float(acc, k, verts[k].norm.data, 3);
}
break;
case cgltf_attribute_type_texcoord:
for (usize k = 0; k < acc->count; ++k) {
cgltf_accessor_read_float(acc, k, verts[k].tex.data, 2);
}
break;
default: break;
}
}
sg_buffer vertex_buffer = sg_make_buffer(&(sg_buffer_desc){
.data = {
.ptr = verts,
.size = sizeof(vertex_t) * vert_count,
},
.usage = {
.vertex_buffer = true,
.immutable = true,
},
});
sg_buffer index_buffer = sg_make_buffer(&(sg_buffer_desc){
.data = {
.ptr = indices,
.size = sizeof(u16) * in->count,
},
.usage = {
.index_buffer = true,
.immutable = true,
},
});
obj->bindings.index_buffer = index_buffer;
obj->bindings.vertex_buffers[0] = vertex_buffer;
obj->draw_count = in->count;
}
}
cgltf_free(data);
return scene;
}