From 974f858eeffb1166a89fe4e5292ce18117b13157 Mon Sep 17 00:00:00 2001 From: turanszkij Date: Wed, 4 Apr 2018 16:26:16 +0100 Subject: [PATCH] obj loader updates --- WickedEngine/wiLoader.cpp | 32 ++- WickedEngine/wiOBJLoader.h | 412 +++++++++++++++++++++++++++++-------- WickedEngine/wiVersion.cpp | 2 +- 3 files changed, 355 insertions(+), 91 deletions(-) diff --git a/WickedEngine/wiLoader.cpp b/WickedEngine/wiLoader.cpp index 94c79f693..bd19c43a5 100644 --- a/WickedEngine/wiLoader.cpp +++ b/WickedEngine/wiLoader.cpp @@ -2848,13 +2848,20 @@ void Model::LoadFromDisk(const std::string& fileName, const std::string& identif for (size_t i = 0; i < shape.mesh.indices.size(); i += 3) { - // Reorder face-winding to match defaults: tinyobj::index_t reordered_indices[] = { shape.mesh.indices[i + 0], - shape.mesh.indices[i + 2], shape.mesh.indices[i + 1], + shape.mesh.indices[i + 2], }; + // todo: option param would be better + bool flipCulling = false; + if (flipCulling) + { + reordered_indices[1] = shape.mesh.indices[i + 2]; + reordered_indices[2] = shape.mesh.indices[i + 1]; + } + for (auto& index : reordered_indices) { Mesh::Vertex_FULL vert; @@ -2896,6 +2903,14 @@ void Model::LoadFromDisk(const std::string& fileName, const std::string& identif } vert.tex.z = (float)registered_materialIndices[materialIndex]; // this indexes a mesh subset + // todo: option parameter would be better + const bool flipZ = true; + if (flipZ) + { + vert.pos.z *= -1; + vert.nor.z *= -1; + } + // eliminate duplicate vertices by means of hashing: size_t hashes[] = { hash{}(index.vertex_index), @@ -2918,6 +2933,19 @@ void Model::LoadFromDisk(const std::string& fileName, const std::string& identif } mesh->aabb.create(min, max); + // We need to eliminate colliding mesh names, because objects can reference them by names: + // Note: in engine, object is decoupled from mesh, for instancing support. OBJ file have only meshes and names can collide there. + string meshName = mesh->name; + uint32_t unique_counter = 0; + bool meshNameCollision = this->meshes.count(meshName) != 0; + while (meshNameCollision) + { + meshName = mesh->name + to_string(unique_counter); + meshNameCollision = this->meshes.count(meshName) != 0; + unique_counter++; + } + mesh->name = meshName; + object->meshName = mesh->name; this->objects.insert(object); diff --git a/WickedEngine/wiOBJLoader.h b/WickedEngine/wiOBJLoader.h index 27fd05a9a..f38781ba5 100644 --- a/WickedEngine/wiOBJLoader.h +++ b/WickedEngine/wiOBJLoader.h @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2012-2017 Syoyo Fujita and many contributors. +Copyright (c) 2012-2018 Syoyo Fujita and many contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.1.1 : Support smoothing groups(#162) // version 1.1.0 : Support parsing vertex color(#144) // version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) // version 1.0.7 : Support multiple tex options(#126) @@ -51,6 +52,16 @@ THE SOFTWARE. namespace tinyobj { +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#pragma clang diagnostic ignored "-Wpadded" + +#endif + // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... // // -blendu on | off # set horizontal texture blending @@ -218,7 +229,10 @@ namespace tinyobj { // face. 3 = polygon, 4 = quad, // ... Up to 255. std::vector material_ids; // per-face material ID - std::vector tags; // SubD tag + std::vector smoothing_group_ids; // per-face smoothing group + // ID(0 = off. positive value + // = group id) + std::vector tags; // SubD tag } mesh_t; typedef struct { @@ -358,6 +372,7 @@ namespace tinyobj { #include #include #include +#include #include #include @@ -366,14 +381,25 @@ namespace tinyobj { MaterialReader::~MaterialReader() {} - struct vertex_index { + struct vertex_index_t { int v_idx, vt_idx, vn_idx; - vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} - explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} - vertex_index(int vidx, int vtidx, int vnidx) + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} }; + // Internal data structure for face representation + // index + smoothing group. + struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0) {} + }; + struct tag_sizes { tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} int num_ints; @@ -667,9 +693,10 @@ namespace tinyobj { } // Extension: parse vertex with colors(6 items) - static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, real_t *r, - real_t *g, real_t *b, - const char **token, const double default_x = 0.0, + static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + const char **token, + const double default_x = 0.0, const double default_y = 0.0, const double default_z = 0.0) { (*x) = parseReal(token, default_x); @@ -741,7 +768,7 @@ namespace tinyobj { return ts; } - (*token)++; // Skip '/' + (*token)++; // Skip '/' (*token) += strspn((*token), " \t"); ts.num_reals = atoi((*token)); @@ -749,7 +776,7 @@ namespace tinyobj { if ((*token)[0] != '/') { return ts; } - (*token)++; // Skip '/' + (*token)++; // Skip '/' ts.num_strings = parseInt(token); @@ -758,12 +785,12 @@ namespace tinyobj { // Parse triples with index offsets: i, i/j/k, i//k, i/j static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, - vertex_index *ret) { + vertex_index_t *ret) { if (!ret) { return false; } - vertex_index vi(-1); + vertex_index_t vi(-1); if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { return false; @@ -811,8 +838,8 @@ namespace tinyobj { } // Parse raw triples: i, i/j/k, i//k, i/j - static vertex_index parseRawTriple(const char **token) { - vertex_index vi(static_cast(0)); // 0 is an invalid index in OBJ + static vertex_index_t parseRawTriple(const char **token) { + vertex_index_t vi(static_cast(0)); // 0 is an invalid index in OBJ vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); @@ -857,22 +884,22 @@ namespace tinyobj { else { texopt->imfchan = 'm'; } - texopt->bump_multiplier = 1.0f; + texopt->bump_multiplier = static_cast(1.0); texopt->clamp = false; texopt->blendu = true; texopt->blendv = true; - texopt->sharpness = 1.0f; - texopt->brightness = 0.0f; - texopt->contrast = 1.0f; - texopt->origin_offset[0] = 0.0f; - texopt->origin_offset[1] = 0.0f; - texopt->origin_offset[2] = 0.0f; - texopt->scale[0] = 1.0f; - texopt->scale[1] = 1.0f; - texopt->scale[2] = 1.0f; - texopt->turbulence[0] = 0.0f; - texopt->turbulence[1] = 0.0f; - texopt->turbulence[2] = 0.0f; + texopt->sharpness = static_cast(1.0); + texopt->brightness = static_cast(0.0); + texopt->contrast = static_cast(1.0); + texopt->origin_offset[0] = static_cast(0.0); + texopt->origin_offset[1] = static_cast(0.0); + texopt->origin_offset[2] = static_cast(0.0); + texopt->scale[0] = static_cast(1.0); + texopt->scale[1] = static_cast(1.0); + texopt->scale[2] = static_cast(1.0); + texopt->turbulence[0] = static_cast(0.0); + texopt->turbulence[1] = static_cast(0.0); + texopt->turbulence[2] = static_cast(0.0); texopt->type = TEXTURE_TYPE_NONE; const char *token = linebuf; // Assume line ends with NULL @@ -970,24 +997,24 @@ namespace tinyobj { material->reflection_texname = ""; material->alpha_texname = ""; for (int i = 0; i < 3; i++) { - material->ambient[i] = 0.f; - material->diffuse[i] = 0.f; - material->specular[i] = 0.f; - material->transmittance[i] = 0.f; - material->emission[i] = 0.f; + material->ambient[i] = static_cast(0.0); + material->diffuse[i] = static_cast(0.0); + material->specular[i] = static_cast(0.0); + material->transmittance[i] = static_cast(0.0); + material->emission[i] = static_cast(0.0); } material->illum = 0; - material->dissolve = 1.f; - material->shininess = 1.f; - material->ior = 1.f; + material->dissolve = static_cast(1.0); + material->shininess = static_cast(1.0); + material->ior = static_cast(1.0); - material->roughness = 0.f; - material->metallic = 0.f; - material->sheen = 0.f; - material->clearcoat_thickness = 0.f; - material->clearcoat_roughness = 0.f; - material->anisotropy_rotation = 0.f; - material->anisotropy = 0.f; + material->roughness = static_cast(0.0); + material->metallic = static_cast(0.0); + material->sheen = static_cast(0.0); + material->clearcoat_thickness = static_cast(0.0); + material->clearcoat_roughness = static_cast(0.0); + material->anisotropy_rotation = static_cast(0.0); + material->anisotropy = static_cast(0.0); material->roughness_texname = ""; material->metallic_texname = ""; material->sheen_texname = ""; @@ -997,61 +1024,223 @@ namespace tinyobj { material->unknown_parameter.clear(); } - static bool exportFaceGroupToShape( - shape_t *shape, const std::vector > &faceGroup, - const std::vector &tags, const int material_id, - const std::string &name, bool triangulate) { + // code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html + template + static int pnpoly(int nvert, T *vertx, T *verty, T testx, + T testy) { + int i, j, c = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + if (((verty[i] > testy) != (verty[j] > testy)) && + (testx < + (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + + vertx[i])) + c = !c; + } + return c; + } + + // TODO(syoyo): refactor function. + static bool exportFaceGroupToShape(shape_t *shape, + const std::vector &faceGroup, + const std::vector &tags, + const int material_id, + const std::string &name, bool triangulate, + const std::vector &v) { if (faceGroup.empty()) { return false; } // Flatten vertices and indices for (size_t i = 0; i < faceGroup.size(); i++) { - const std::vector &face = faceGroup[i]; + const face_t &face = faceGroup[i]; - vertex_index i0 = face[0]; - vertex_index i1(-1); - vertex_index i2 = face[1]; + if (face.vertex_indices.size() < 3) { + // Face must have 3+ vertices. + continue; + } - size_t npolys = face.size(); + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1(-1); + vertex_index_t i2 = face.vertex_indices[1]; + + size_t npolys = face.vertex_indices.size(); if (triangulate) { - // Polygon -> triangle fan conversion - for (size_t k = 2; k < npolys; k++) { - i1 = i2; - i2 = face[k]; + // find the two axes to work in + size_t axes[2] = { 1, 2 }; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + i2 = face.vertex_indices[(k + 2) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t e0x = v1x - v0x; + real_t e0y = v1y - v0y; + real_t e0z = v1z - v0z; + real_t e1x = v2x - v1x; + real_t e1y = v2y - v1y; + real_t e1z = v2z - v1z; + real_t cx = std::fabs(e0y * e1z - e0z * e1y); + real_t cy = std::fabs(e0z * e1x - e0x * e1z); + real_t cz = std::fabs(e0x * e1y - e0y * e1x); + const real_t epsilon = std::numeric_limits::epsilon(); + if (cx > epsilon || cy > epsilon || cz > epsilon) { + // found a corner + if (cx > cy && cx > cz) { + } + else { + axes[0] = 0; + if (cz > cx && cz > cy) axes[1] = 1; + } + break; + } + } - index_t idx0, idx1, idx2; - idx0.vertex_index = i0.v_idx; - idx0.normal_index = i0.vn_idx; - idx0.texcoord_index = i0.vt_idx; - idx1.vertex_index = i1.v_idx; - idx1.normal_index = i1.vn_idx; - idx1.texcoord_index = i1.vt_idx; - idx2.vertex_index = i2.v_idx; - idx2.normal_index = i2.vn_idx; - idx2.texcoord_index = i2.vt_idx; + real_t area = 0; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + real_t v0x = v[vi0 * 3 + axes[0]]; + real_t v0y = v[vi0 * 3 + axes[1]]; + real_t v1x = v[vi1 * 3 + axes[0]]; + real_t v1y = v[vi1 * 3 + axes[1]]; + area += (v0x * v1y - v0y * v1x) * static_cast(0.5); + } - shape->mesh.indices.push_back(idx0); - shape->mesh.indices.push_back(idx1); - shape->mesh.indices.push_back(idx2); + int maxRounds = + 10; // arbitrary max loop count to protect against unexpected errors - shape->mesh.num_face_vertices.push_back(3); - shape->mesh.material_ids.push_back(material_id); + face_t remainingFace = face; // copy + size_t guess_vert = 0; + vertex_index_t ind[3]; + real_t vx[3]; + real_t vy[3]; + while (remainingFace.vertex_indices.size() > 3 && maxRounds > 0) { + npolys = remainingFace.vertex_indices.size(); + if (guess_vert >= npolys) { + maxRounds -= 1; + guess_vert -= npolys; + } + for (size_t k = 0; k < 3; k++) { + ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; + size_t vi = size_t(ind[k].v_idx); + vx[k] = v[vi * 3 + axes[0]]; + vy[k] = v[vi * 3 + axes[1]]; + } + real_t e0x = vx[1] - vx[0]; + real_t e0y = vy[1] - vy[0]; + real_t e1x = vx[2] - vx[1]; + real_t e1y = vy[2] - vy[1]; + real_t cross = e0x * e1y - e0y * e1x; + // if an internal angle + if (cross * area < static_cast(0.0)) { + guess_vert += 1; + continue; + } + + // check all other verts in case they are inside this triangle + bool overlap = false; + for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { + size_t ovi = size_t( + remainingFace.vertex_indices[(guess_vert + otherVert) % npolys] + .v_idx); + real_t tx = v[ovi * 3 + axes[0]]; + real_t ty = v[ovi * 3 + axes[1]]; + if (pnpoly(3, vx, vy, tx, ty)) { + overlap = true; + break; + } + } + + if (overlap) { + guess_vert += 1; + continue; + } + + // this triangle is an ear + { + index_t idx0, idx1, idx2; + idx0.vertex_index = ind[0].v_idx; + idx0.normal_index = ind[0].vn_idx; + idx0.texcoord_index = ind[0].vt_idx; + idx1.vertex_index = ind[1].v_idx; + idx1.normal_index = ind[1].vn_idx; + idx1.texcoord_index = ind[1].vt_idx; + idx2.vertex_index = ind[2].v_idx; + idx2.normal_index = ind[2].vn_idx; + idx2.texcoord_index = ind[2].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + } + + // remove v1 from the list + size_t removed_vert_index = (guess_vert + 1) % npolys; + while (removed_vert_index + 1 < npolys) { + remainingFace.vertex_indices[removed_vert_index] = + remainingFace.vertex_indices[removed_vert_index + 1]; + removed_vert_index += 1; + } + remainingFace.vertex_indices.pop_back(); + } + + if (remainingFace.vertex_indices.size() == 3) { + i0 = remainingFace.vertex_indices[0]; + i1 = remainingFace.vertex_indices[1]; + i2 = remainingFace.vertex_indices[2]; + { + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + } } } else { for (size_t k = 0; k < npolys; k++) { index_t idx; - idx.vertex_index = face[k].v_idx; - idx.normal_index = face[k].vn_idx; - idx.texcoord_index = face[k].vt_idx; + idx.vertex_index = face.vertex_indices[k].v_idx; + idx.normal_index = face.vertex_indices[k].vn_idx; + idx.texcoord_index = face.vertex_indices[k].vt_idx; shape->mesh.indices.push_back(idx); } shape->mesh.num_face_vertices.push_back( static_cast(npolys)); shape->mesh.material_ids.push_back(material_id); // per face + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); // per face } } @@ -1246,7 +1435,7 @@ namespace tinyobj { // We invert value of Tr(assume Tr is in range [0, 1]) // NOTE: Interpretation of Tr is application(exporter) dependent. For // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) - material.dissolve = 1.0f - parseReal(&token); + material.dissolve = static_cast(1.0) - parseReal(&token); } has_tr = true; continue; @@ -1562,13 +1751,17 @@ namespace tinyobj { std::vector vt; std::vector vc; std::vector tags; - std::vector > faceGroup; + std::vector faceGroup; std::string name; // material std::map material_map; int material = -1; + // smoothing group id + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. + shape_t shape; std::string linebuf; @@ -1641,11 +1834,13 @@ namespace tinyobj { token += 2; token += strspn(token, " \t"); - std::vector face; - face.reserve(3); + face_t face; + + face.smoothing_group_id = current_smoothing_id; + face.vertex_indices.reserve(3); while (!IS_NEW_LINE(token[0])) { - vertex_index vi; + vertex_index_t vi; if (!parseTriple(&token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2), &vi)) { @@ -1655,14 +1850,13 @@ namespace tinyobj { return false; } - face.push_back(vi); + face.vertex_indices.push_back(vi); size_t n = strspn(token, " \t\r"); token += n; } // replace with emplace_back + std::move on C++11 - faceGroup.push_back(std::vector()); - faceGroup[faceGroup.size() - 1].swap(face); + faceGroup.push_back(face); continue; } @@ -1687,7 +1881,7 @@ namespace tinyobj { // this time. // just clear `faceGroup` after `exportFaceGroupToShape()` call. exportFaceGroupToShape(&shape, faceGroup, tags, material, name, - triangulate); + triangulate, v); faceGroup.clear(); material = newMaterialId; } @@ -1743,7 +1937,7 @@ namespace tinyobj { if (token[0] == 'g' && IS_SPACE((token[1]))) { // flush previous face group. bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, - triangulate); + triangulate, v); (void)ret; // return value not used. if (shape.mesh.indices.size() > 0) { @@ -1781,7 +1975,7 @@ namespace tinyobj { if (token[0] == 'o' && IS_SPACE((token[1]))) { // flush previous face group. bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, - triangulate); + triangulate, v); if (ret) { shapes->push_back(shape); } @@ -1825,13 +2019,51 @@ namespace tinyobj { } tags.push_back(tag); + + continue; } - // Ignore unknown command. + if (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; + + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { + continue; + } + + if (token[0] == '\r' || token[1] == '\n') { + continue; + } + + if (strlen(token) >= 3) { + if (token[0] == 'o' && token[1] == 'f' && token[2] == 'f') { + current_smoothing_id = 0; + } + } + else { + // assume number + int smGroupId = parseInt(&token); + if (smGroupId < 0) { + // parse error. force set to 0. + // FIXME(syoyo): Report warning. + current_smoothing_id = 0; + } + else { + current_smoothing_id = static_cast(smGroupId); + } + } + + continue; + } // smoothing group id + + // Ignore unknown command. } bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, - triangulate); + triangulate, v); // exportFaceGroupToShape return false when `usemtl` is called in the last // line. // we also add `shape` to `shapes` when `shape.mesh` has already some @@ -1939,7 +2171,7 @@ namespace tinyobj { indices.clear(); while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseRawTriple(&token); + vertex_index_t vi = parseRawTriple(&token); index_t idx; idx.vertex_index = vi.v_idx; @@ -2137,6 +2369,10 @@ namespace tinyobj { return true; } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } // namespace tinyobj #endif diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 71d443737..51f7e138a 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates const int minor = 16; // minor bug fixes, alterations, refactors, updates - const int revision = 37; + const int revision = 38; long GetVersion()