#include "iron_obj.h" #include #include #include #include "iron_array.h" #include "iron_vec4.h" #include "iron_string.h" #include "iron_gc.h" static raw_mesh_t *part = NULL; static f32_array_t pos_temp; static f32_array_t uv_temp; static f32_array_t nor_temp; static uint32_t va[512]; static uint32_t ua[512]; static uint32_t na[512]; static int vi = 0; static int ui = 0; static int ni = 0; static uint8_t buf[128]; static char str[256]; static int vind_off = 0; static int tind_off = 0; static int nind_off = 0; static uint8_t *bytes = NULL; static size_t bytes_length = 0; static f32_array_t *pos_first; static f32_array_t *uv_first; static f32_array_t *nor_first; static int read_int() { int bi = 0; while (true) { // Read into buffer char c = bytes[part->pos]; if (c == '/' || c == '\n' || c == '\r' || c == ' ') { break; } part->pos++; buf[bi++] = c; } int res = 0; // Parse buffer into int int dec = 1; int off = buf[0] == '-' ? 1 : 0; int len = bi - 1; for (int i = 0; i < bi - off; ++i) { res += (buf[len - i] - 48) * dec; dec *= 10; } if (off > 0) { res *= -1; } return res; } static void read_face_fast() { while (true) { va[vi++] = read_int() - 1; part->pos++; // '/' ua[ui++] = read_int() - 1; part->pos++; // '/' na[ni++] = read_int() - 1; if (bytes[part->pos] == '\n' || bytes[part->pos] == '\r') { break; } part->pos++; // ' ' // Some exporters put space at the end of "f" line if (vi >= 3 && (bytes[part->pos] == '\n' || bytes[part->pos] == '\r')) { break; } } } static void read_face() { while (true) { va[vi++] = read_int() - 1; if (uv_temp.length > 0 || nor_temp.length > 0) { part->pos++; // "/" if (uv_temp.length > 0) { ua[ui++] = read_int() - 1; } if (nor_temp.length > 0) { // Some exporters put fake uv index even when uv data is not present... (f 1/1/1 instead of f 1//1) bool has_bogus_uv = uv_temp.length == 0 && bytes[part->pos] != '/'; if (has_bogus_uv) { read_int(); } part->pos++; // "/" na[ni++] = read_int() - 1; } } // Some exporters put "//" even when normal and uv data are not present (f 1//) else if (uv_temp.length == 0 && nor_temp.length == 0 && bytes[part->pos] == '/') { part->pos += 2; } if (bytes[part->pos] == '\n' || bytes[part->pos] == '\r') { break; } part->pos++; // " " // Some exporters put space at the end of "f" line if (vi >= 3 && (bytes[part->pos] == '\n' || bytes[part->pos] == '\r')) { break; } } } static float read_float() { int bi = 0; while (true) { // Read into buffer char c = bytes[part->pos]; if (c == ' ' || c == '\n' || c == '\r') { break; } if (c == 'E' || c == 'e') { part->pos++; int first = buf[0] == '-' ? -(buf[1] - 48) : buf[0] - 48; int exp = read_int(); int dec = 1; int loop = exp > 0 ? exp : -exp; for (int i = 0; i < loop; ++i) { dec *= 10; } return exp > 0 ? (float)first * dec : (float)first / dec; } part->pos++; buf[bi++] = c; } float res = 0.0; // Parse buffer into float int64_t dot = 1; int64_t dec = 1; int off = buf[0] == '-' ? 1 : 0; int len = bi - 1; for (int i = 0; i < bi - off; ++i) { char c = buf[len - i]; if (c == '.') { dot = dec; continue; } res += (c - 48) * dec; dec *= 10; } if (off > 0) { res /= -dot; } else { res /= dot; } return res; } static char *read_string() { size_t begin = part->pos; while (true) { char c = bytes[part->pos]; if (c == '\n' || c == '\r' || c == ' ') { break; } part->pos++; } for (int i = 0; i < part->pos - begin; ++i) { str[i] = bytes[begin + i]; } str[part->pos - begin] = '\0'; return str; } static void next_line() { while (true) { char c = bytes[part->pos++]; if (c == '\n' || part->pos >= bytes_length) { break; // \n, \r\n } } } static int get_tile(int i1, int i2, int i3, i32_array_t *uv_indices, int tiles_u) { float u1 = uv_temp.buffer[uv_indices->buffer[i1] * 2 ]; float v1 = uv_temp.buffer[uv_indices->buffer[i1] * 2 + 1]; float u2 = uv_temp.buffer[uv_indices->buffer[i2] * 2 ]; float v2 = uv_temp.buffer[uv_indices->buffer[i2] * 2 + 1]; float u3 = uv_temp.buffer[uv_indices->buffer[i3] * 2 ]; float v3 = uv_temp.buffer[uv_indices->buffer[i3] * 2 + 1]; int tile_u = (int)((u1 + u2 + u3) / 3); int tile_v = (int)((v1 + v2 + v3) / 3); return tile_u + tile_v * tiles_u; } static bool pnpoly(float v0x, float v0y, float v1x, float v1y, float v2x, float v2y, float px, float py) { // https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html bool c = false; if (((v0y > py) != (v2y > py)) && (px < (v2x - v0x) * (py - v0y) / (v2y - v0y) + v0x)) { c = !c; } if (((v1y > py) != (v0y > py)) && (px < (v0x - v1x) * (py - v1y) / (v0y - v1y) + v1x)) { c = !c; } if (((v2y > py) != (v1y > py)) && (px < (v1x - v2x) * (py - v2y) / (v1y - v2y) + v2x)) { c = !c; } return c; } iron_vector4_t calc_normal(iron_vector4_t a, iron_vector4_t b, iron_vector4_t c) { iron_vector4_t cb = vec4_sub(c, b); iron_vector4_t ab = vec4_sub(a, b); cb = vec4_cross(cb, ab); cb = vec4_norm(cb); return cb; } // 'o' for object split, 'g' for groups, 'u'semtl for materials raw_mesh_t *obj_parse(buffer_t *file_bytes, char split_code, uint64_t start_pos, bool udim) { bytes = file_bytes->buffer; bytes_length = file_bytes->length; part = gc_alloc(sizeof(raw_mesh_t)); part->scale_pos = 1.0; part->scale_tex = 1.0; part->pos = start_pos; part->udims_u = 1; part->udims_v = 1; part->name = string_copy(str); i32_array_t pos_indices = {0}; i32_array_t uv_indices = {0}; i32_array_t nor_indices = {0}; bool reading_faces = false; bool reading_object = false; bool full_attrib = false; if (start_pos == 0) { vind_off = tind_off = nind_off = 0; } if (split_code == 'u' && start_pos > 0) { pos_temp = *pos_first; nor_temp = *nor_first; uv_temp = *uv_first; } else { memset(&pos_temp, 0, sizeof(pos_temp)); memset(&uv_temp, 0, sizeof(uv_temp)); memset(&nor_temp, 0, sizeof(nor_temp)); } while (true) { if (part->pos >= bytes_length) { break; } char c0 = bytes[part->pos++]; if (reading_object && reading_faces && (c0 == 'v' || c0 == split_code)) { part->pos--; part->has_next = true; break; } if (c0 == 'v') { char c1 = bytes[part->pos++]; if (c1 == ' ') { if (bytes[part->pos] == ' ') part->pos++; // Some exporters put additional space directly after "v" f32_array_push(&pos_temp, read_float()); part->pos++; // Space f32_array_push(&pos_temp, read_float()); part->pos++; // Space f32_array_push(&pos_temp, read_float()); } else if (c1 == 't') { part->pos++; // Space f32_array_push(&uv_temp, read_float()); part->pos++; // Space f32_array_push(&uv_temp, read_float()); if (nor_temp.length > 0) { full_attrib = true; } } else if (c1 == 'n') { part->pos++; // Space f32_array_push(&nor_temp, read_float()); part->pos++; // Space f32_array_push(&nor_temp, read_float()); part->pos++; // Space f32_array_push(&nor_temp, read_float()); if (uv_temp.length > 0) { full_attrib = true; } } } else if (c0 == 'f') { part->pos++; // Space if (bytes[part->pos] == ' ') { part->pos++; // Some exporters put additional space directly after "f" } reading_faces = true; vi = ui = ni = 0; full_attrib ? read_face_fast() : read_face(); if (vi <= 4) { // Convex, fan triangulation i32_array_push(&pos_indices, va[0]); i32_array_push(&pos_indices, va[1]); i32_array_push(&pos_indices, va[2]); for (int i = 3; i < vi; ++i) { i32_array_push(&pos_indices, va[0]); i32_array_push(&pos_indices, va[i - 1]); i32_array_push(&pos_indices, va[i]); } if (uv_temp.length > 0) { i32_array_push(&uv_indices, ua[0]); i32_array_push(&uv_indices, ua[1]); i32_array_push(&uv_indices, ua[2]); for (int i = 3; i < ui; ++i) { i32_array_push(&uv_indices, ua[0]); i32_array_push(&uv_indices, ua[i - 1]); i32_array_push(&uv_indices, ua[i]); } } if (nor_temp.length > 0) { i32_array_push(&nor_indices, na[0]); i32_array_push(&nor_indices, na[1]); i32_array_push(&nor_indices, na[2]); for (int i = 3; i < ni; ++i) { i32_array_push(&nor_indices, na[0]); i32_array_push(&nor_indices, na[i - 1]); i32_array_push(&nor_indices, na[i]); } } } else { // Convex or concave, ear clipping int _vind_off = split_code == 'u' ? 0 : vind_off; int _nind_off = split_code == 'u' ? 0 : nind_off; float nx = 0.0; float ny = 0.0; float nz = 0.0; if (nor_temp.length > 0) { nx = nor_temp.buffer[(na[0] - _nind_off) * 3 ]; ny = nor_temp.buffer[(na[0] - _nind_off) * 3 + 1]; nz = nor_temp.buffer[(na[0] - _nind_off) * 3 + 2]; } else { iron_vector4_t n = calc_normal( vec4_create(pos_temp.buffer[(va[0] - _vind_off) * 3], pos_temp.buffer[(va[0] - _vind_off) * 3 + 1], pos_temp.buffer[(va[0] - _vind_off) * 3 + 2], 1.0f), vec4_create(pos_temp.buffer[(va[1] - _vind_off) * 3], pos_temp.buffer[(va[1] - _vind_off) * 3 + 1], pos_temp.buffer[(va[1] - _vind_off) * 3 + 2], 1.0f), vec4_create(pos_temp.buffer[(va[2] - _vind_off) * 3], pos_temp.buffer[(va[2] - _vind_off) * 3 + 1], pos_temp.buffer[(va[2] - _vind_off) * 3 + 2], 1.0f) ); nx = n.x; ny = n.y; nz = n.z; } float nxabs = (float)fabs(nx); float nyabs = (float)fabs(ny); float nzabs = (float)fabs(nz); bool flip = nx + ny + nz > 0; int axis = nxabs > nyabs && nxabs > nzabs ? 0 : nyabs > nxabs && nyabs > nzabs ? 1 : 2; int axis0 = axis == 0 ? (flip ? 2 : 1) : axis == 1 ? (flip ? 0 : 2) : (flip ? 1 : 0); int axis1 = axis == 0 ? (flip ? 1 : 2) : axis == 1 ? (flip ? 2 : 0) : (flip ? 0 : 1); int loops = 0; int i = -1; while (vi > 3 && loops++ < vi) { i = (i + 1) % vi; int i1 = (i + 1) % vi; int i2 = (i + 2) % vi; int vi0 = (va[i ] - _vind_off) * 3; int vi1 = (va[i1] - _vind_off) * 3; int vi2 = (va[i2] - _vind_off) * 3; float v0x = pos_temp.buffer[vi0 + axis0]; float v0y = pos_temp.buffer[vi0 + axis1]; float v1x = pos_temp.buffer[vi1 + axis0]; float v1y = pos_temp.buffer[vi1 + axis1]; float v2x = pos_temp.buffer[vi2 + axis0]; float v2y = pos_temp.buffer[vi2 + axis1]; float e0x = v0x - v1x; // Not an interior vertex float e0y = v0y - v1y; float e1x = v2x - v1x; float e1y = v2y - v1y; float cross = e0x * e1y - e0y * e1x; if (cross <= 0) { continue; } bool overlap = false; // Other vertex found inside this triangle for (int j = 0; j < vi - 3; ++j) { int j0 = (va[(i + 3 + j) % vi] - _vind_off) * 3; float px = pos_temp.buffer[j0 + axis0]; float py = pos_temp.buffer[j0 + axis1]; if (pnpoly(v0x, v0y, v1x, v1y, v2x, v2y, px, py)) { overlap = true; break; } } if (overlap) { continue; } i32_array_push(&pos_indices, va[i ]); // Found ear i32_array_push(&pos_indices, va[i1]); i32_array_push(&pos_indices, va[i2]); if (uv_temp.length > 0) { i32_array_push(&uv_indices, ua[i ]); i32_array_push(&uv_indices, ua[i1]); i32_array_push(&uv_indices, ua[i2]); } if (nor_temp.length > 0) { i32_array_push(&nor_indices, na[i ]); i32_array_push(&nor_indices, na[i1]); i32_array_push(&nor_indices, na[i2]); } for (int j = ((i + 1) % vi); j < vi - 1; ++j) { // Consume vertex va[j] = va[j + 1]; ua[j] = ua[j + 1]; na[j] = na[j + 1]; } vi--; i--; loops = 0; } i32_array_push(&pos_indices, va[0]); // Last one i32_array_push(&pos_indices, va[1]); i32_array_push(&pos_indices, va[2]); if (uv_temp.length > 0) { i32_array_push(&uv_indices, ua[0]); i32_array_push(&uv_indices, ua[1]); i32_array_push(&uv_indices, ua[2]); } if (nor_temp.length > 0) { i32_array_push(&nor_indices, na[0]); i32_array_push(&nor_indices, na[1]); i32_array_push(&nor_indices, na[2]); } } } else if (c0 == split_code) { if (split_code == 'u') { part->pos += 5; // "u"semtl } part->pos++; // Space if (!udim) { reading_object = true; } part->name = string_copy(read_string()); } next_line(); } if (start_pos > 0) { if (split_code != 'u') { for (int i = 0; i < pos_indices.length; ++i) { pos_indices.buffer[i] -= vind_off; } for (int i = 0; i < uv_indices.length; ++i) { uv_indices.buffer[i] -= tind_off; } for (int i = 0; i < nor_indices.length; ++i) { nor_indices.buffer[i] -= nind_off; } } } else { if (split_code == 'u') { pos_first = &pos_temp; nor_first = &nor_temp; uv_first = &uv_temp; } } vind_off += (int)(pos_temp.length / 3); // Assumes separate vertex data per object tind_off += (int)(uv_temp.length / 2); nind_off += (int)(nor_temp.length / 3); // Pack positions to (-1, 1) range part->scale_pos = 0.0; for (int i = 0; i < pos_temp.length; ++i) { float f = (float)fabs(pos_temp.buffer[i]); if (part->scale_pos < f) { part->scale_pos = f; } } float inv = 32767 * (1 / part->scale_pos); part->posa = calloc(sizeof(i16_array_t), 1); part->posa->length = part->posa->capacity = pos_indices.length * 4; part->posa->buffer = malloc(part->posa->capacity * sizeof(int16_t)); part->inda = calloc(sizeof(u32_array_t), 1); part->inda->length = part->inda->capacity = pos_indices.length; part->inda->buffer = malloc(part->inda->capacity * sizeof(uint32_t)); part->vertex_count = pos_indices.length; part->index_count = pos_indices.length; int inda_length = pos_indices.length; for (int i = 0; i < pos_indices.length; ++i) { part->posa->buffer[i * 4 ] = (int)( pos_temp.buffer[pos_indices.buffer[i] * 3 ] * inv); part->posa->buffer[i * 4 + 1] = (int)(-pos_temp.buffer[pos_indices.buffer[i] * 3 + 2] * inv); part->posa->buffer[i * 4 + 2] = (int)( pos_temp.buffer[pos_indices.buffer[i] * 3 + 1] * inv); part->inda->buffer[i] = i; } if (nor_indices.length > 0) { part->nora = calloc(sizeof(i16_array_t), 1); part->nora->length = part->nora->capacity = nor_indices.length * 2; part->nora->buffer = malloc(part->nora->capacity * sizeof(int16_t)); for (int i = 0; i < pos_indices.length; ++i) { part->nora->buffer[i * 2 ] = (int)( nor_temp.buffer[nor_indices.buffer[i] * 3 ] * 32767); part->nora->buffer[i * 2 + 1] = (int)(-nor_temp.buffer[nor_indices.buffer[i] * 3 + 2] * 32767); part->posa->buffer[i * 4 + 3] = (int)( nor_temp.buffer[nor_indices.buffer[i] * 3 + 1] * 32767); } } else { // Calc normals part->nora = calloc(sizeof(i16_array_t), 1); part->nora->length = part->nora->capacity = inda_length * 2; part->nora->buffer = malloc(part->nora->capacity * sizeof(int16_t)); for (int i = 0; i < (int)(inda_length / 3); ++i) { int i1 = part->inda->buffer[i * 3 ]; int i2 = part->inda->buffer[i * 3 + 1]; int i3 = part->inda->buffer[i * 3 + 2]; iron_vector4_t n = calc_normal( vec4_create(part->posa->buffer[i1 * 4], part->posa->buffer[i1 * 4 + 1], part->posa->buffer[i1 * 4 + 2], 1.0), vec4_create(part->posa->buffer[i2 * 4], part->posa->buffer[i2 * 4 + 1], part->posa->buffer[i2 * 4 + 2], 1.0), vec4_create(part->posa->buffer[i3 * 4], part->posa->buffer[i3 * 4 + 1], part->posa->buffer[i3 * 4 + 2], 1.0) ); part->nora->buffer[i1 * 2 ] = (int)(n.x * 32767); part->nora->buffer[i1 * 2 + 1] = (int)(n.y * 32767); part->posa->buffer[i1 * 4 + 3] = (int)(n.z * 32767); part->nora->buffer[i2 * 2 ] = (int)(n.x * 32767); part->nora->buffer[i2 * 2 + 1] = (int)(n.y * 32767); part->posa->buffer[i2 * 4 + 3] = (int)(n.z * 32767); part->nora->buffer[i3 * 2 ] = (int)(n.x * 32767); part->nora->buffer[i3 * 2 + 1] = (int)(n.y * 32767); part->posa->buffer[i3 * 4 + 3] = (int)(n.z * 32767); } } if (uv_indices.length > 0) { if (udim) { // Find number of tiles int tiles_u = 1; int tiles_v = 1; for (int i = 0; i < (int)(uv_temp.length / 2); ++i) { while (uv_temp.buffer[i * 2 ] > tiles_u) tiles_u++; while (uv_temp.buffer[i * 2 + 1] > tiles_v) tiles_v++; } // Amount of indices pre tile uint32_t *num = (uint32_t *)malloc(tiles_u * tiles_v * sizeof(uint32_t)); memset(num, 0, tiles_u * tiles_v * sizeof(uint32_t)); for (int i = 0; i < (int)(inda_length / 3); ++i) { int tile = get_tile(part->inda->buffer[i * 3], part->inda->buffer[i * 3 + 1], part->inda->buffer[i * 3 + 2], &uv_indices, tiles_u); num[tile] += 3; } // Split indices per tile part->udims = any_array_create(tiles_u * tiles_v); part->udims_u = tiles_u; part->udims_v = tiles_v; for (int i = 0; i < tiles_u * tiles_v; ++i) { part->udims->buffer[i] = u32_array_create(num[i]); num[i] = 0; } for (int i = 0; i < (int)(inda_length / 3); ++i) { int i1 = part->inda->buffer[i * 3 ]; int i2 = part->inda->buffer[i * 3 + 1]; int i3 = part->inda->buffer[i * 3 + 2]; int tile = get_tile(i1, i2, i3, &uv_indices, tiles_u); u32_array_t *a = part->udims->buffer[tile]; a->buffer[num[tile]++] = i1; a->buffer[num[tile]++] = i2; a->buffer[num[tile]++] = i3; } // Normalize uvs to 0-1 range int16_t *uvtiles = (int16_t *)malloc(uv_temp.length * sizeof(int16_t)); for (int i = 0; i < (int)(inda_length / 3); ++i) { // TODO: merge loops int i1 = part->inda->buffer[i * 3 ]; int i2 = part->inda->buffer[i * 3 + 1]; int i3 = part->inda->buffer[i * 3 + 2]; int tile = get_tile(i1, i2, i3, &uv_indices, tiles_u); int tile_u = tile % tiles_u; int tile_v = (int)(tile / tiles_u); uvtiles[uv_indices.buffer[i1] * 2 ] = tile_u; uvtiles[uv_indices.buffer[i1] * 2 + 1] = tile_v; uvtiles[uv_indices.buffer[i2] * 2 ] = tile_u; uvtiles[uv_indices.buffer[i2] * 2 + 1] = tile_v; uvtiles[uv_indices.buffer[i3] * 2 ] = tile_u; uvtiles[uv_indices.buffer[i3] * 2 + 1] = tile_v; } for (int i = 0; i < uv_temp.length; ++i) { uv_temp.buffer[i] -= uvtiles[i]; } free(uvtiles); free(num); } part->texa = calloc(sizeof(i16_array_t), 1); part->texa->length = part->texa->capacity = uv_indices.length * 2; part->texa->buffer = malloc(part->texa->capacity * sizeof(int16_t)); for (int i = 0; i < uv_indices.length; ++i) { float uvx = uv_temp.buffer[uv_indices.buffer[i] * 2]; if (uvx > 1.0) { uvx = uvx - (int)(uvx); } float uvy = uv_temp.buffer[uv_indices.buffer[i] * 2 + 1]; if (uvy > 1.0) { uvy = uvy - (int)(uvy); } part->texa->buffer[i * 2 ] = (int)( uvx * 32767); part->texa->buffer[i * 2 + 1] = (int)((1.0 - uvy) * 32767); } } bytes = NULL; if (!part->has_next) { pos_first = nor_first = uv_first = NULL; array_free(&pos_temp); array_free(&uv_temp); array_free(&nor_temp); } array_free(&pos_indices); array_free(&uv_indices); array_free(&nor_indices); return part; } void obj_destroy(raw_mesh_t *part) { // if (part->udims != NULL) { // for (int i = 0; i < part->udims_u * part->udims_v; ++i) { // free(part->udims[i]); // } // free(part->udims); // } free(part->posa); free(part->nora); free(part->texa); free(part->inda); gc_free(part); }