mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 20:01:26 +00:00
Give OBJ Quad and Flipped Faces the Texture Coordinates of Their Own Corners
load_obj emits the second triangle of a quad from corners 0, 2 and 3, but read its texture coordinates from corners 0, 1 and 2, so half of every textured quad sampled the wrong part of the texture. The corner indices are now passed down to where the coordinates are read. A mesh with inward-facing triangles is flipped after loading, which swaps corners 1 and 2 of every face. The texture coordinates were left as they were. They are now swapped along with the corners.
This commit is contained in:
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
its.indices.emplace_back(indices[0], indices[1], indices[2]);
|
||||
int face_index =its.indices.size() - 1;
|
||||
RGBA face_color;
|
||||
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
|
||||
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
|
||||
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
|
||||
bool is_merge_ka_kd = true;
|
||||
for (size_t n = 0; n < 3; n++) {
|
||||
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
return Vec2f::Zero();
|
||||
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
|
||||
};
|
||||
std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])};
|
||||
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
|
||||
obj_info.uvs.emplace_back(uv_array);
|
||||
}
|
||||
obj_info.face_colors.emplace_back(face_color);
|
||||
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
}
|
||||
}
|
||||
};
|
||||
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
|
||||
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
|
||||
if (data.usemtls.size() == 1) {
|
||||
set_face_color(face_index, data.usemtls[0].name);
|
||||
set_face_color(face_index, data.usemtls[0].name, corners);
|
||||
} else {
|
||||
for (size_t k = 0; k < data.usemtls.size(); k++) {
|
||||
auto mtl = data.usemtls[k];
|
||||
if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
|
||||
set_face_color(face_index, data.usemtls[k].name);
|
||||
set_face_color(face_index, data.usemtls[k].name, corners);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index);
|
||||
set_face_color_by_mtl(face_index, {0, 1, 2});
|
||||
}
|
||||
if (cnt == 4) {
|
||||
its.indices.emplace_back(indices[0], indices[2], indices[3]);
|
||||
int face_index = its.indices.size() - 1;
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index);
|
||||
set_face_color_by_mtl(face_index, {0, 2, 3});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,12 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
message = _L("This OBJ file couldn't be read because it's empty.");
|
||||
return false;
|
||||
}
|
||||
if (meshptr->volume() < 0)
|
||||
if (meshptr->volume() < 0) {
|
||||
meshptr->flip_triangles();
|
||||
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
|
||||
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
|
||||
std::swap(uv[1], uv[2]);
|
||||
}
|
||||
// Hand the parsed material table back so callers can build a TexturedMesh from it.
|
||||
if (out_mtl)
|
||||
*out_mtl = mtl_data;
|
||||
|
||||
@@ -19,9 +19,8 @@ struct LoadedObj
|
||||
ObjInfo info;
|
||||
};
|
||||
|
||||
// A tetrahedron with a material and two texture coordinates, (0.25, 0.5) and (0.75, 1).
|
||||
// Only the first face and the vt lines are varied; the other three faces reference vt 1.
|
||||
LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::string &vts = "vt 0.25 0.5\nvt 0.75 1\n")
|
||||
// Loads an OBJ made of the given lines, with a material library that defines material "a".
|
||||
LoadedObj load_textured_obj(const std::string &body)
|
||||
{
|
||||
ScopedTemporaryFile obj(".obj");
|
||||
ScopedTemporaryFile mtl(".mtl");
|
||||
@@ -31,12 +30,7 @@ LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::st
|
||||
}
|
||||
{
|
||||
boost::nowide::ofstream out(obj.string());
|
||||
out << "mtllib " << mtl.path().filename().string() << "\n"
|
||||
<< "v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n"
|
||||
<< vts
|
||||
<< "usemtl a\n"
|
||||
<< first_face << "\n"
|
||||
<< "f 1/1 2/1 4/1\nf 1/1 4/1 3/1\nf 2/1 3/1 4/1\n";
|
||||
out << "mtllib " << mtl.path().filename().string() << "\n" << body;
|
||||
}
|
||||
LoadedObj loaded;
|
||||
std::string message;
|
||||
@@ -44,6 +38,21 @@ LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::st
|
||||
return loaded;
|
||||
}
|
||||
|
||||
// A tetrahedron with a material and two texture coordinates, (0.25, 0.5) and (0.75, 1).
|
||||
// Only the first face and the vt lines are varied; the other three faces reference vt 1.
|
||||
LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::string &vts = "vt 0.25 0.5\nvt 0.75 1\n")
|
||||
{
|
||||
return load_textured_obj("v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n" + vts + "usemtl a\n" + first_face + "\n" +
|
||||
"f 1/1 2/1 4/1\nf 1/1 4/1 3/1\nf 2/1 3/1 4/1\n");
|
||||
}
|
||||
|
||||
// Texture coordinate n is (n / 10, n / 20), so a UV identifies the vt it came from.
|
||||
void check_uv_is_vt(const Vec2f &uv, int vt)
|
||||
{
|
||||
CHECK_THAT(uv.x(), WithinAbs(vt / 10., 1e-6));
|
||||
CHECK_THAT(uv.y(), WithinAbs(vt / 20., 1e-6));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("An out-of-range texture index falls back to a zero UV and keeps the geometry", "[OBJ][Regression]")
|
||||
@@ -120,3 +129,39 @@ TEST_CASE("A texture coordinate with w does not shift the indices of the ones af
|
||||
CHECK_THAT(uv[2].x(), WithinAbs(0.75, 1e-6));
|
||||
CHECK_THAT(uv[2].y(), WithinAbs(1., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Both triangles of a quad take the texture coordinates of their own corners", "[OBJ][Regression]")
|
||||
{
|
||||
const LoadedObj loaded = load_textured_obj("v 0 0 0\nv 10 0 0\nv 10 10 0\nv 0 10 0\n"
|
||||
"vt 0.1 0.05\nvt 0.2 0.1\nvt 0.3 0.15\nvt 0.4 0.2\n"
|
||||
"usemtl a\n"
|
||||
"f 1/1 2/2 3/3 4/4\n");
|
||||
|
||||
REQUIRE(loaded.ok);
|
||||
REQUIRE(loaded.mesh.facets_count() == 2);
|
||||
REQUIRE(loaded.info.uvs.size() == 2);
|
||||
check_uv_is_vt(loaded.info.uvs[0][0], 1);
|
||||
check_uv_is_vt(loaded.info.uvs[0][1], 2);
|
||||
check_uv_is_vt(loaded.info.uvs[0][2], 3);
|
||||
check_uv_is_vt(loaded.info.uvs[1][0], 1);
|
||||
check_uv_is_vt(loaded.info.uvs[1][1], 3);
|
||||
check_uv_is_vt(loaded.info.uvs[1][2], 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Texture coordinates follow the corners of a mesh that is flipped on load", "[OBJ][Regression]")
|
||||
{
|
||||
// The faces wind inwards, so the loader flips them. Vertex n uses vt n.
|
||||
const LoadedObj loaded = load_textured_obj("v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n"
|
||||
"vt 0.1 0.05\nvt 0.2 0.1\nvt 0.3 0.15\nvt 0.4 0.2\n"
|
||||
"usemtl a\n"
|
||||
"f 1/1 2/2 3/3\nf 1/1 4/4 2/2\nf 1/1 3/3 4/4\nf 2/2 4/4 3/3\n");
|
||||
|
||||
REQUIRE(loaded.ok);
|
||||
const indexed_triangle_set &its = loaded.mesh.its;
|
||||
CHECK(its_volume(its) > 0.f);
|
||||
REQUIRE(its.indices.size() == 4);
|
||||
REQUIRE(loaded.info.uvs.size() == 4);
|
||||
for (size_t face = 0; face < its.indices.size(); ++face)
|
||||
for (int corner = 0; corner < 3; ++corner)
|
||||
check_uv_is_vt(loaded.info.uvs[face][corner], its.indices[face][corner] + 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user