123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- #include <assert.h>
- #include <stdio.h>
- #include <errno.h>
- #include <float.h>
- #include <limits.h>
- #include <string.h>
- #define NOB_IMPLEMENTATION
- #define NOB_STRIP_PREFIX
- #include "nob.h"
- typedef struct {
- float x, y;
- } Vector2;
- Vector2 make_vector2(float x, float y)
- {
- Vector2 v2;
- v2.x = x;
- v2.y = y;
- return v2;
- }
- typedef struct {
- float x, y, z;
- } Vector3;
- Vector3 make_vector3(float x, float y, float z)
- {
- Vector3 v3;
- v3.x = x;
- v3.y = y;
- v3.z = z;
- return v3;
- }
- typedef struct {
- int *items;
- size_t capacity;
- size_t count;
- } Face_Indices;
- typedef struct {
- int *items;
- size_t capacity;
- size_t count;
- } Vertex_Indices;
- typedef struct {
- Vector3 position;
- Face_Indices faces;
- int component; // 0 means never visited, >0 is the index of the component vertex belongs to
- } Vertex;
- Vertex make_vertex(float x, float y, float z)
- {
- return (Vertex) {
- .position = make_vector3(x, y, z),
- };
- }
- typedef struct {
- Vertex *items;
- size_t capacity;
- size_t count;
- } Vertices;
- #define VERTICES_PER_FACE 3
- typedef struct {
- int v[VERTICES_PER_FACE];
- int vt[VERTICES_PER_FACE];
- int vn[VERTICES_PER_FACE];
- } Face;
- Face make_face(int v1, int v2, int v3, int vt1, int vt2, int vt3, int vn1, int vn2, int vn3)
- {
- static_assert(VERTICES_PER_FACE == 3, "");
- Face f = {
- .v = {v1, v2, v3},
- .vt = {vt1, vt2, vt3},
- .vn = {vn1, vn2, vn3},
- };
- return f;
- }
- typedef struct {
- Face *items;
- size_t capacity;
- size_t count;
- } Faces;
- typedef struct {
- Vector3 *items;
- size_t capacity;
- size_t count;
- } Normals;
- typedef struct {
- Vector2 *items;
- size_t capacity;
- size_t count;
- } TexCoords;
- typedef struct {
- int *items;
- size_t count;
- size_t capacity;
- } Component_Indices;
- bool is_deleted_face(Vertices vertices, Face face, Component_Indices delete_components)
- {
- for (size_t i = 0; i < VERTICES_PER_FACE; ++i) {
- // TODO: @perf if this is too slow due to too many deleted components you can always just pre-sort the indices and do a binary search
- for (size_t j = 0; j < delete_components.count; ++j) {
- if (vertices.items[face.v[i]].component == delete_components.items[j]) {
- return true;
- }
- }
- }
- return false;
- }
- void generate_code(FILE *out, Vertices vertices, TexCoords texcoords, Normals normals, Faces faces, Component_Indices delete_components)
- {
- fprintf(out, "#ifndef OBJ_H_\n");
- fprintf(out, "#define OBJ_H_\n");
- fprintf(out, "#define vertices_count %zu\n", vertices.count);
- if (vertices.count == 0) {
- fprintf(out, "static const float vertices[1][3] = {0};\n");
- } else {
- fprintf(out, "static const float vertices[][3] = {\n");
- for (size_t i = 0; i < vertices.count; ++i) {
- Vector3 v = vertices.items[i].position;
- fprintf(out, " {%f, %f, %f},\n", v.x, v.y, v.z);
- }
- fprintf(out, "};\n");
- }
- fprintf(out, "#define texcoords_count %zu\n", texcoords.count);
- if (texcoords.count == 0) {
- fprintf(out, "static const float texcoords[1][3] = {0};\n");
- } else {
- fprintf(out, "static const float texcoords[][3] = {\n");
- for (size_t i = 0; i < texcoords.count; ++i) {
- Vector2 vt = texcoords.items[i];
- fprintf(out, " {%f, %f},\n", vt.x, vt.y);
- }
- fprintf(out, "};\n");
- }
- fprintf(out, "#define normals_count %zu\n", normals.count);
- if (normals.count == 0) {
- fprintf(out, "static const float normals[1][3] = {0};\n");
- } else {
- fprintf(out, "static const float normals[][3] = {\n");
- for (size_t i = 0; i < normals.count; ++i) {
- Vector3 vn = normals.items[i];
- fprintf(out, " {%f, %f, %f},\n", vn.x, vn.y, vn.z);
- }
- fprintf(out, "};\n");
- }
- size_t visible_faces_count = 0;
- for (size_t i = 0; i < faces.count; ++i) {
- if (!is_deleted_face(vertices, faces.items[i], delete_components)) {
- visible_faces_count += 1;
- }
- }
- fprintf(out, "#define faces_count %zu\n", visible_faces_count);
- if (visible_faces_count == 0) {
- fprintf(out, "static const int faces[1][9] = {0};\n");
- } else {
- fprintf(out, "static const int faces[%zu][9] = {\n", visible_faces_count);
- for (size_t i = 0; i < faces.count; ++i) {
- if (!is_deleted_face(vertices, faces.items[i], delete_components)) {
- Face f = faces.items[i];
- fprintf(out, " {%d, %d, %d, %d, %d, %d, %d, %d, %d},\n", f.v[0], f.v[1], f.v[2], f.vt[0], f.vt[1], f.vt[2], f.vn[0], f.vn[1], f.vn[2]);
- }
- }
- fprintf(out, "};\n");
- }
- fprintf(out, "#endif // OBJ_H_\n");
- }
- Vector3 remap_object(Vector3 v, float scale, float lx, float hx, float ly, float hy, float lz, float hz)
- {
- float cx = lx + (hx - lx)/2;
- float cy = ly + (hy - ly)/2;
- float cz = lz + (hz - lz)/2;
- v.z = (v.z - cz)*scale;
- v.x = (v.x - cx)*scale;
- v.y = (v.y - cy)*scale;
- return v;
- }
- void usage(const char *program_name)
- {
- fprintf(stderr, "Usage: %s [OPTIONS] <INPUT.obj>\n", program_name);
- fprintf(stderr, "Options:\n");
- fprintf(stderr, " -o output file path\n");
- fprintf(stderr, " -s scale the model\n");
- }
- void parse_face_triple(String_View *line, int *lf, int *hf, int *v, int *vt, int *vn)
- {
- char *endptr;
- *line = sv_trim_left(*line);
- *v = strtol(line->data, &endptr, 10) - 1; // NOTE: -1 is to account for 1-based indexing.
- if (*lf > *v) *lf = *v;
- if (*hf < *v) *hf = *v;
- sv_chop_left(line, endptr - line->data);
- *vt = 0;
- if (line->count > 0 && line->data[0] == '/') {
- sv_chop_left(line, 1);
- *vt = strtol(line->data, &endptr, 10) - 1; // NOTE: -1 is to account for 1-based indexing.
- sv_chop_left(line, endptr - line->data);
- }
- *vn = 0;
- if (line->count > 0 && line->data[0] == '/') {
- sv_chop_left(line, 1);
- *vn = strtol(line->data, &endptr, 10) - 1; // NOTE: -1 is to account for 1-based indexing.
- sv_chop_left(line, endptr - line->data);
- }
- while (line->count > 0 && !isspace(*line->data)) sv_chop_left(line, 1);
- }
- int unvisited_vertex(Vertices vertices)
- {
- for (size_t i = 0; i < vertices.count; ++i) {
- if (!vertices.items[i].component) {
- return (int)i;
- }
- }
- return -1;
- }
- int main(int argc, char **argv)
- {
- int result = 0;
- assert(argc > 0);
- const char *program_name = shift(argv, argc);
- const char *output_file_path = NULL;
- const char *input_file_path = NULL;
- float scale = 0.75;
- Component_Indices delete_components = {0};
- // TODO: consider using https://github.com/tsoding/flag.h in here
- while (argc > 0) {
- const char *flag = shift(argv, argc);
- if (strcmp(flag, "-o") == 0) {
- if (argc <= 0) {
- usage(program_name);
- fprintf(stderr, "ERROR: no value is provided for flag %s\n", flag);
- return_defer(1);
- }
- if (output_file_path != NULL) {
- usage(program_name);
- fprintf(stderr, "ERROR: %s was already provided\n", flag);
- return_defer(1);
- }
- output_file_path = shift(argv, argc);
- } else if (strcmp(flag, "-s") == 0) {
- if (argc <= 0) {
- usage(program_name);
- fprintf(stderr, "ERROR: no value is provided for flag %s\n", flag);
- return_defer(1);
- }
- const char *value = shift(argv, argc);
- scale = strtof(value, NULL);
- } else if (strcmp(flag, "-d") == 0) {
- if (argc <= 0) {
- usage(program_name);
- fprintf(stderr, "ERROR: no value is provided for flag %s\n", flag);
- return_defer(1);
- }
- const char *value = shift(argv, argc);
- da_append(&delete_components, atoi(value));
- } else {
- if (input_file_path != NULL) {
- usage(program_name);
- fprintf(stderr, "ERROR: input file path was already provided\n");
- return_defer(1);
- }
- input_file_path = flag;
- }
- }
- if (input_file_path == NULL) {
- usage(program_name);
- fprintf(stderr, "ERROR: no input file path is provided\n");
- return_defer(1);
- }
- if (output_file_path == NULL) {
- usage(program_name);
- fprintf(stderr, "ERROR: no output file path is provided\n");
- return_defer(1);
- }
- String_Builder buffer = {0};
- if (!read_entire_file(input_file_path, &buffer)) return_defer(1);
- String_View content = sb_to_sv(buffer);
- Vertices vertices = {0};
- TexCoords texcoords = {0};
- Normals normals = {0};
- Faces faces = {0};
- float lx = FLT_MAX, hx = FLT_MIN;
- float ly = FLT_MAX, hy = FLT_MIN;
- float lz = FLT_MAX, hz = FLT_MIN;
- int lf = INT_MAX, hf = INT_MIN;
- bool one_object_encountered = false;
- size_t one_object_line_number = 0;
- for (size_t line_number = 1; content.count > 0; ++line_number) {
- String_View line = sv_trim_left(sv_chop_by_delim(&content, '\n'));
- if (line.count > 0 && *line.data != '#') {
- String_View kind = sv_chop_by_delim(&line, ' ');
- if (sv_eq(kind, sv_from_cstr("v"))) {
- char *endptr;
- line = sv_trim_left(line);
- float x = strtof(line.data, &endptr);
- if (lx > x) lx = x;
- if (hx < x) hx = x;
- sv_chop_left(&line, endptr - line.data);
- line = sv_trim_left(line);
- float y = strtof(line.data, &endptr);
- if (ly > y) ly = y;
- if (hy < y) hy = y;
- sv_chop_left(&line, endptr - line.data);
- line = sv_trim_left(line);
- float z = strtof(line.data, &endptr);
- if (lz > z) lz = z;
- if (hz < z) hz = z;
- sv_chop_left(&line, endptr - line.data);
- da_append(&vertices, make_vertex(x, y, z));
- } else if (sv_eq(kind, sv_from_cstr("f"))) {
- // TODO: This code assumes that we already parsed all of the vertices.
- // Since we don't have any OBJ files in the assets that have faces before
- // vertices, it does not really matter that much. If we ever have any
- // of such OBJ files, it's easy to restructure this loop to support any
- // order of the elements.
- int v1, v2, v3, vt1, vt2, vt3, vn1, vn2, vn3;
- int face_index = faces.count;
- parse_face_triple(&line, &lf, &hf, &v1, &vt1, &vn1);
- da_append(&vertices.items[v1].faces, face_index);
- parse_face_triple(&line, &lf, &hf, &v2, &vt2, &vn2);
- da_append(&vertices.items[v2].faces, face_index);
- parse_face_triple(&line, &lf, &hf, &v3, &vt3, &vn3);
- da_append(&vertices.items[v3].faces, face_index);
- da_append(&faces, make_face(v1, v2, v3, vt1, vt2, vt3, vn1, vn2, vn3));
- } else if (sv_eq(kind, sv_from_cstr("mtllib"))) {
- fprintf(stderr, "%s:%zu: WARNING: mtllib is not supported yet. Ignoring it...\n", input_file_path, line_number);
- } else if (sv_eq(kind, sv_from_cstr("usemtl"))) {
- fprintf(stderr, "%s:%zu: WARNING: usemtl is not supported yet. Ignoring it...\n", input_file_path, line_number);
- } else if (sv_eq(kind, sv_from_cstr("o"))) {
- if (one_object_encountered) {
- fprintf(stderr, "%s:%zu: ERROR: %s supports only one object as of right now.\n", input_file_path, line_number, program_name);
- fprintf(stderr, "%s:%zu: NOTE: we already processing this object\n", input_file_path, one_object_line_number);
- return_defer(1);
- }
- line = sv_trim_left(line);
- String_View name = line;
- fprintf(stderr, "%s:%zu: INFO: processing object `"SV_Fmt"`\n", input_file_path, line_number, SV_Arg(name));
- one_object_encountered = true;
- one_object_line_number = line_number;
- } else if (sv_eq(kind, sv_from_cstr("s"))) {
- fprintf(stderr, "%s:%zu: WARNING: smooth groups are not supported right now. Ignoring them...\n", input_file_path, line_number);
- } else if (sv_eq(kind, sv_from_cstr("vn"))) {
- char *endptr;
- line = sv_trim_left(line);
- float x = strtof(line.data, &endptr);
- sv_chop_left(&line, endptr - line.data);
- line = sv_trim_left(line);
- float y = strtof(line.data, &endptr);
- sv_chop_left(&line, endptr - line.data);
- line = sv_trim_left(line);
- float z = strtof(line.data, &endptr);
- sv_chop_left(&line, endptr - line.data);
- da_append(&normals, make_vector3(x, y, z));
- } else if (sv_eq(kind, sv_from_cstr("vt"))) {
- char *endptr;
- line = sv_trim_left(line);
- float x = strtof(line.data, &endptr);
- sv_chop_left(&line, endptr - line.data);
- line = sv_trim_left(line);
- float y = strtof(line.data, &endptr);
- sv_chop_left(&line, endptr - line.data);
- da_append(&texcoords, make_vector2(x, y));
- } else {
- fprintf(stderr, "%s:%zu: ERROR: unknown kind of entry `"SV_Fmt"`\n", input_file_path, line_number, SV_Arg(kind));
- return_defer(1);
- }
- }
- }
- int min_faces = INT_MAX;
- int max_faces = INT_MIN;
- for (size_t i = 0; i < vertices.count; ++i) {
- int count = vertices.items[i].faces.count;
- if (min_faces > count) min_faces = count;
- if (max_faces < count) max_faces = count;
- }
- size_t comp_count = 0;
- int start = unvisited_vertex(vertices);
- while (start >= 0) {
- comp_count += 1;
- Vertex_Indices wave = {0};
- Vertex_Indices next_wave = {0};
- da_append(&wave, start);
- vertices.items[start].component = comp_count;
- while (wave.count > 0) {
- for (size_t i = 0; i < wave.count; ++i) {
- Vertex *vertex = &vertices.items[wave.items[i]];
- for (size_t j = 0; j < vertex->faces.count; ++j) {
- for (size_t k = 0; k < VERTICES_PER_FACE; ++k) {
- int neighbor_index = faces.items[vertex->faces.items[j]].v[k];
- if (!vertices.items[neighbor_index].component) {
- da_append(&next_wave, neighbor_index);
- vertices.items[neighbor_index].component = comp_count;
- }
- }
- }
- }
- wave.count = 0;
- Vertex_Indices temp = wave;
- wave = next_wave;
- next_wave = temp;
- }
- start = unvisited_vertex(vertices);
- }
- printf("Input: %s\n", input_file_path);
- printf("Output: %s\n", output_file_path);
- printf("Vertices: %zu (x: %f..%f, y: %f..%f, z: %f..%f)\n", vertices.count, lx, hx, ly, hy, lz, hz);
- printf("Normals: %zu\n", normals.count);
- printf("Texture Coordinates: %zu\n", texcoords.count);
- printf("Faces: %zu (index: %d..%d)\n", faces.count, lf, hf);
- printf("Faces per vertex: %d..%d\n", min_faces, max_faces);
- printf("Components Count: %zu\n", comp_count);
- printf("Deleted Components: ");
- for (size_t i = 0; i < delete_components.count; ++i) {
- printf("%d ", delete_components.items[i]);
- }
- printf("\n");
- for (size_t i = 0; i < vertices.count; ++i) {
- vertices.items[i].position = remap_object(vertices.items[i].position, scale, lx, hx, ly, hy, lz, hz);
- }
- FILE *out = fopen(output_file_path, "wb");
- if (out == NULL) {
- fprintf(stderr, "ERROR: Could not write file %s: %s\n", output_file_path, strerror(errno));
- return_defer(1);
- }
- generate_code(out, vertices, texcoords, normals, faces, delete_components);
- defer:
- return result;
- }
|