diff --git a/models/issue-162-smoothing-group.mtl b/models/issue-162-smoothing-group.mtl new file mode 100644 index 0000000..8894d7e --- /dev/null +++ b/models/issue-162-smoothing-group.mtl @@ -0,0 +1,23 @@ +newmtl test1 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.5 0.2 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 + + newmtl test2 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.2 0.5 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 diff --git a/models/issue-162-smoothing-group.obj b/models/issue-162-smoothing-group.obj new file mode 100644 index 0000000..0b0c8b3 --- /dev/null +++ b/models/issue-162-smoothing-group.obj @@ -0,0 +1,51 @@ + +# cube.obj +# + +mtllib issue-162-smoothing-group.mtl + +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 + +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 + +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 + +usemtl test1 +g test1 +s 1 +f 1/1/1 2/2/1 3/3/1 +f 3/3/1 2/2/1 4/4/1 + +usemtl test2 +g test2 + +s off +f 3/1/2 4/2/2 5/3/2 +f 5/3/2 4/2/2 6/4/2 +s 3 +f 5/4/3 6/3/3 7/2/3 +f 7/2/3 6/3/3 8/1/3 +s 4 +f 7/1/4 8/2/4 1/3/4 +f 1/3/4 8/2/4 2/4/4 +s 0 +f 2/1/5 8/2/5 4/3/5 +f 4/3/5 8/2/5 6/4/5 +s 6 +f 7/1/6 1/2/6 5/3/6 +f 5/3/6 1/2/6 3/4/6 diff --git a/tests/tester.cc b/tests/tester.cc index cd972f8..d5d83c6 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -717,6 +717,40 @@ TEST_CASE("texture-name-whitespace", "[Issue145]") { } +TEST_CASE("smoothing-group", "[Issue162]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-162-smoothing-group.obj", gMtlBasePath); + + + if (!err.empty()) { + std::cerr << "[Issue162] " << err << std::endl; + } + + REQUIRE(true == ret); + REQUIRE(2 == shapes.size()); + + REQUIRE(2 == shapes[0].mesh.smoothing_group_ids.size()); + REQUIRE(1 == shapes[0].mesh.smoothing_group_ids[0]); + REQUIRE(1 == shapes[0].mesh.smoothing_group_ids[1]); + + REQUIRE(10 == shapes[1].mesh.smoothing_group_ids.size()); + REQUIRE(0 == shapes[1].mesh.smoothing_group_ids[0]); + REQUIRE(0 == shapes[1].mesh.smoothing_group_ids[1]); + REQUIRE(3 == shapes[1].mesh.smoothing_group_ids[2]); + REQUIRE(3 == shapes[1].mesh.smoothing_group_ids[3]); + REQUIRE(4 == shapes[1].mesh.smoothing_group_ids[4]); + REQUIRE(4 == shapes[1].mesh.smoothing_group_ids[5]); + REQUIRE(0 == shapes[1].mesh.smoothing_group_ids[6]); + REQUIRE(0 == shapes[1].mesh.smoothing_group_ids[7]); + REQUIRE(6 == shapes[1].mesh.smoothing_group_ids[8]); + REQUIRE(6 == shapes[1].mesh.smoothing_group_ids[9]); + +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 68bb055..9c1f521 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -225,7 +225,10 @@ typedef struct { // 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 { @@ -384,15 +387,14 @@ struct vertex_index_t { // Internal data structure for face representation // index + smoothing group. struct face_t { - int smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. int pad_; - std::vector vertex_indices; // face vertex indices. + std::vector vertex_indices; // face vertex indices. - face_t() : smoothing_group_id(0) { - } + face_t() : smoothing_group_id(0) {} }; - struct tag_sizes { tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} int num_ints; @@ -680,11 +682,12 @@ static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, } // 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, - const double default_y = 0.0, - const double default_z = 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); (*y) = parseReal(token, default_y); (*z) = parseReal(token, default_z); @@ -747,7 +750,7 @@ static tag_sizes parseTagTriple(const char **token) { return ts; } - (*token)++; // Skip '/' + (*token)++; // Skip '/' (*token) += strspn((*token), " \t"); ts.num_reals = atoi((*token)); @@ -755,7 +758,7 @@ static tag_sizes parseTagTriple(const char **token) { if ((*token)[0] != '/') { return ts; } - (*token)++; // Skip '/' + (*token)++; // Skip '/' ts.num_strings = parseInt(token); @@ -926,7 +929,7 @@ static bool ParseTextureNameAndOption(std::string *texname, token += 4; parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); } else { - // Assume texture filename + // Assume texture filename #if 0 size_t len = strcspn(token, " \t\r"); // untile next space texture_name = std::string(token, token + len); @@ -991,22 +994,26 @@ static void InitMaterial(material_t *material) { } // code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html -static int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy) -{ +static int pnpoly(int nvert, float *vertx, float *verty, float testx, + float 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; + 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) { +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; } @@ -1022,154 +1029,165 @@ static bool exportFaceGroupToShape( size_t npolys = face.vertex_indices.size(); if (triangulate) { - // 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; - float cx = std::fabs(e0y*e1z - e0z*e1y); - float cy = std::fabs(e0z*e1x - e0x*e1z); - float cz = std::fabs(e0x*e1y - e0y*e1x); - const float epsilon = 0.0001f; - 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; - } - } + // 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; + float cx = std::fabs(e0y * e1z - e0z * e1y); + float cy = std::fabs(e0z * e1x - e0x * e1z); + float cz = std::fabs(e0x * e1y - e0y * e1x); + const float epsilon = 0.0001f; + 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; + } + } - 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)*0.5f; - } + 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) * 0.5f; + } - int maxRounds = 10; // arbitrary max loop count to protect against unexpected errors + int maxRounds = + 10; // arbitrary max loop count to protect against unexpected errors - 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 < 0.0f ) { guess_vert += 1; continue; } + 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 < 0.0f) { + 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; - } - } + // 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; } + 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; + // 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.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.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(); - } + // 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; + 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.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.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; @@ -1182,6 +1200,8 @@ static bool exportFaceGroupToShape( 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 } } @@ -1690,7 +1710,6 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector vt; std::vector vc; std::vector tags; - std::vector smoothing_groups; std::vector faceGroup; std::string name; @@ -1699,7 +1718,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, int material = -1; // smoothing group id - int current_smoothing_id = 0; // Initial value. 0 means no smoothing. + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. shape_t shape; @@ -1881,7 +1901,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, shapes->push_back(shape); } - shape = shape_t(); + shape = shape_t(); // material = -1; faceGroup.clear(); @@ -1956,41 +1976,42 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tags.push_back(tag); - continue; - } - - 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 (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; - 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) { - // ??? + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { continue; } - current_smoothing_id = smGroupId; - } + if (token[0] == '\r' || token[1] == '\n') { + continue; + } - continue; - } // smoothing group id + 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. }