fixed texture defragmentation crashes with non-manifold edges and zero-area faces

This commit is contained in:
Andrea Maggiordomo 2021-06-08 17:58:21 +02:00
parent 42aec03b3f
commit 8634eaefe6
10 changed files with 223 additions and 104 deletions

View File

@ -71,6 +71,13 @@ struct FF {
int e[3]; // opposite edge index
};
inline bool IsEdgeManifold3D(Mesh& m, const MeshFace& f, int i, Mesh::PerFaceAttributeHandle<FF>& ffadj)
{
const MeshFace& ff = m.face[ffadj[f].f[i]]; // opposite face
int ffi = ffadj[f].e[i]; // opposite index
return tri::Index(m, f) == ffadj[ff].f[ffi];
}
inline Mesh::PerFaceAttributeHandle<FF> Get3DFaceAdjacencyAttribute(Mesh& m);
inline Mesh::PerFaceAttributeHandle<TexCoordStorage> GetWedgeTexCoordStorageAttribute(Mesh& m);
inline Mesh::PerMeshAttributeHandle<BoundaryInfo> GetBoundaryInfoAttribute(Mesh& m);

View File

@ -386,9 +386,11 @@ GraphHandle ComputeGraph(Mesh &m, TextureObjectHandle textureObject)
RegionID regionId = f.id;
graph->GetChart_Insert(regionId)->AddFace(&f);
for (int i = 0; i < f.VN(); ++i) {
RegionID adjId = m.face[ffadj[f].f[i]].id;
if (regionId != adjId) {
(graph->GetChart_Insert(regionId)->adj).insert(graph->GetChart_Insert(adjId));
if (IsEdgeManifold3D(m, f, i, ffadj)) {
RegionID adjId = m.face[ffadj[f].f[i]].id;
if (regionId != adjId) {
(graph->GetChart_Insert(regionId)->adj).insert(graph->GetChart_Insert(adjId));
}
}
}
}
@ -396,4 +398,73 @@ GraphHandle ComputeGraph(Mesh &m, TextureObjectHandle textureObject)
return graph;
}
void DisconnectCharts(GraphHandle graph)
{
typedef std::pair<int, RegionID> VertexRID;
Mesh& m = graph->mesh;
int numExtraVertices = 0;
std::map<VertexRID, int> remap;
tri::UpdateFlags<Mesh>::VertexClearV(m);
for (auto& c : graph->charts) {
std::set<Mesh::VertexPointer> vset;
for (auto fptr : c.second->fpVec) {
for (int i = 0; i < 3; ++i) {
vset.insert(fptr->V(i));
}
}
for (auto vp : vset) {
if (vp->IsV()) {
numExtraVertices++;
remap[std::make_pair(tri::Index(m, vp), c.first)] = -1;
}
vp->SetV();
}
}
auto vi = tri::Allocator<Mesh>::AddVertices(m, numExtraVertices);
tri::UpdateFlags<Mesh>::VertexClearV(m);
for (auto& entry : remap) {
VertexRID vrid = entry.first;
vi->ImportData(m.vert[vrid.first]);
m.vert[vrid.first].SetV();
ensure(entry.second == -1);
entry.second = tri::Index(m, *vi);
vi++;
}
int updated = 0;
int iters = 0;
for (auto& c : graph->charts) {
for (auto fptr : c.second->fpVec) {
for (int i = 0; i < 3; ++i) {
VertexRID vrid = std::make_pair(tri::Index(m, fptr->V(i)), c.first);
iters++;
if (fptr->V(i)->IsV() && remap.count(vrid) > 0) {
int vind = remap[vrid];
fptr->V(i) = &m.vert[vind];
updated++;
}
}
}
}
// safety check
tri::UpdateFlags<Mesh>::VertexClearV(m);
for (auto& c : graph->charts) {
std::set<Mesh::VertexPointer> vset;
for (auto fptr : c.second->fpVec) {
for (int i = 0; i < 3; ++i) {
vset.insert(fptr->V(i));
}
}
for (auto vp : vset) {
ensure(!vp->IsV());
vp->SetV();
}
}
}

View File

@ -107,6 +107,11 @@ void CopyToMesh(FaceGroup& fg, Mesh& m);
* to determine chart adjacency relations */
GraphHandle ComputeGraph(Mesh &m, TextureObjectHandle textureObject);
/* This function ensures that the vertices referenced by each chart are unique
* to the chart. Necessary because non-manifold vertices adjacent to
* non-manifold edges cannot be split by the VCG's SplitNonManifoldVertices() */
void DisconnectCharts(GraphHandle graph);
/*
* MeshGraph class
*

View File

@ -204,30 +204,28 @@ Outline2d ExtractOutline2d(FaceGroup& chart)
}
}
int i;
vcg::Box2d box = chart.UVBox();
bool useChartBBAsOutline = false;
std::size_t maxsz = 0;
for (std::size_t i = 0; i < outline2Vec.size(); ++i) {
maxsz = std::max(maxsz, outline2Vec[i].size());
}
int outlineIndex = -1;
double largestArea = 0;
if (maxsz == 0) {
useChartBBAsOutline = true;
} else {
i = (outline2Vec.size() == 1) ? 0 : tri::OutlineUtil<double>::LargestOutline2(outline2Vec);
if (tri::OutlineUtil<double>::Outline2Area(outline2Vec[i]) < 0)
for (int i = 0; i < outline2Vec.size(); ++i) {
double outlineArea = tri::OutlineUtil<double>::Outline2Area(outline2Vec[i]);
if (outlineArea < 0)
tri::OutlineUtil<double>::ReverseOutline2(outline2Vec[i]);
vcg::Box2d outlineBox;
for (const auto& p : outline2Vec[i])
outlineBox.Add(p);
if (outlineBox.DimX() < box.DimX() || outlineBox.DimY() < box.DimY())
useChartBBAsOutline = true;
if (std::abs(outlineArea) >= largestArea) {
vcg::Box2d outlineBox;
for (const auto& p : outline2Vec[i])
outlineBox.Add(p);
if (outlineBox.DimX() >= box.DimX() && outlineBox.DimY() >= box.DimY()) {
outlineIndex = i;
largestArea = std::abs(outlineArea);
}
}
}
if (useChartBBAsOutline) {
LOG_WARN << "Failed to compute outline, falling back to uv bounding box for chart " << chart.id;
if (outlineIndex == -1) {
LOG_WARN << "Outline not bounding, falling back to UV bounding box for chart " << chart.id;
outline.clear();
outline.push_back(Point2d(box.min.X(), box.min.Y()));
outline.push_back(Point2d(box.max.X(), box.min.Y()));
@ -235,7 +233,7 @@ Outline2d ExtractOutline2d(FaceGroup& chart)
outline.push_back(Point2d(box.min.X(), box.max.Y()));
return outline;
} else {
return outline2Vec[i];
return outline2Vec[outlineIndex];
}
}

View File

@ -268,7 +268,7 @@ AlgoStateHandle InitializeState(GraphHandle graph, const AlgoParameters& algoPar
state->inputUVBorderLength = 0;
state->currentUVBorderLength = 0;
BuildSeamMesh(graph->mesh, state->sm);
BuildSeamMesh(graph->mesh, state->sm, graph);
std::vector<SeamHandle> seams = GenerateSeams(state->sm);
// disconnecting seams are (initially) clustered by chart adjacency
@ -392,15 +392,9 @@ void GreedyOptimization(GraphHandle graph, AlgoStateHandle state, const AlgoPara
}
}
}
PrintStateInfo(state, graph, params);
LogExecutionStats();
Mesh shell;
LOG_INFO << "Atlas energy after optimization is " << ARAP::ComputeEnergyFromStoredWedgeTC(graph->mesh, nullptr, nullptr);
}
void Finalize(GraphHandle graph, int *vndup)
@ -684,6 +678,11 @@ static OffsetMap AlignAndMerge(ClusteredSeamHandle csh, SeamData& sd, const Matc
Mesh::VertexPointer v0b = edge.fb->V0(edge.eb);
Mesh::VertexPointer v1b = edge.fb->V1(edge.eb);
sd.seamVertices.insert(v0a);
sd.seamVertices.insert(v1a);
sd.seamVertices.insert(v0b);
sd.seamVertices.insert(v1b);
if (v0a->P() != edge.V(0)->P())
std::swap(v0a, v1a);
if (v0b->P() != edge.V(0)->P())
@ -707,9 +706,11 @@ static OffsetMap AlignAndMerge(ClusteredSeamHandle csh, SeamData& sd, const Matc
}
}
// for each vertex merged, store its original vfadj fan
for (auto& entry : sd.mrep) {
face::VFStarVF(entry.first, sd.vfmap[entry.first].first, sd.vfmap[entry.first].second);
for (auto vp : sd.seamVertices) {
std::vector<Mesh::FacePointer> faces;
std::vector<int> indices;
face::VFStarVF(vp, faces, indices);
sd.vfTopologyFaceSet.insert(faces.begin(), faces.end());
}
// update vertex references
@ -726,8 +727,7 @@ static OffsetMap AlignAndMerge(ClusteredSeamHandle csh, SeamData& sd, const Matc
}
}
// update topologies. face-face is trivial, for vertex-face we
// merge the VF lists
// update topologies. face-face is trivial
// face-face
for (SeamHandle sh : csh->seams) {
@ -740,19 +740,20 @@ static OffsetMap AlignAndMerge(ClusteredSeamHandle csh, SeamData& sd, const Matc
}
}
//tri::UpdateTopology<Mesh>::VertexFace(graph->mesh);
{
for (Mesh::VertexPointer vp : sd.seamVertices) {
vp->VFp() = 0;
vp->VFi() = 0;
}
// iterate over emap, and concatenate vf adjacencies
// ugly... essentially we have a list of vfadj fans stored in vfmap,
// and we concatenate the end of a fan with the beginning of the next
// note that we only change the data stored in the faces (i.e. the list) and
// never touch the data stored on the vertices
for (auto& entry : sd.evec) {
if (entry.second.size() > 1) {
std::vector<Mesh::VertexPointer>& verts = entry.second;
for (unsigned i = 0; i < entry.second.size() - 1; ++i) {
sd.vfmap[verts[i]].first.back()->VFp(sd.vfmap[verts[i]].second.back()) = sd.vfmap[verts[i+1]].first.front();
sd.vfmap[verts[i]].first.back()->VFi(sd.vfmap[verts[i]].second.back()) = sd.vfmap[verts[i+1]].second.front();
for (Mesh::FacePointer fptr : sd.vfTopologyFaceSet) {
for (int i = 0; i < 3; ++i) {
if (sd.seamVertices.find(fptr->V(i)) != sd.seamVertices.end()) {
(*fptr).VFp(i) = (*fptr).V(i)->VFp();
(*fptr).VFi(i) = (*fptr).V(i)->VFi();
(*fptr).V(i)->VFp() = &(*fptr);
(*fptr).V(i)->VFi() = i;
}
}
}
}
@ -789,12 +790,21 @@ static void ComputeOptimizationArea(SeamData& sd, Mesh& mesh, OffsetMap& om)
sd.verticesWithinThreshold = ComputeVerticesWithinOffsetThreshold(mesh, om, sd);
sd.optimizationArea.clear();
auto ffadj = Get3DFaceAdjacencyAttribute(mesh);
for (auto fptr : fpvec) {
bool addFace = false;
bool edgeManifold = true;
for (int i = 0; i < 3; ++i) {
if (sd.verticesWithinThreshold.find(fptr->V(i)) != sd.verticesWithinThreshold.end()) {
sd.optimizationArea.insert(fptr);
break;
}
edgeManifold &= IsEdgeManifold3D(mesh, *fptr, i, ffadj);
if (sd.verticesWithinThreshold.find(fptr->V(i)) != sd.verticesWithinThreshold.end())
addFace = true;
}
if (addFace && edgeManifold)
sd.optimizationArea.insert(fptr);
if (addFace && !edgeManifold) {
sd.verticesWithinThreshold.erase(fptr->V(0));
sd.verticesWithinThreshold.erase(fptr->V(1));
sd.verticesWithinThreshold.erase(fptr->V(2));
}
}
@ -859,6 +869,7 @@ static std::unordered_set<Mesh::VertexPointer> ComputeVerticesWithinOffsetThresh
std::vector<Mesh::FacePointer> faces;
std::vector<int> indices;
face::VFStarVF(node.first, faces, indices);
for (unsigned i = 0; i < faces.size(); ++i) {
if(faces[i]->id != sd.a->id && faces[i]->id != sd.b->id){
LOG_ERR << "issue at face " << tri::Index(m, faces[i]);
@ -882,7 +893,7 @@ static std::unordered_set<Mesh::VertexPointer> ComputeVerticesWithinOffsetThresh
Mesh::VertexPointer v2 = faces[i]->V2(indices[i]);
double d2 = dist[node.first] - EdgeLengthUV(*faces[i], e2);
if (d2 >= 0 && (dist.find(v2) == dist.end() || dist[v2] < d2)) {
if (d2 >= 0 && (dist.find(v2) == dist.end() || dist[v2] < d2)) {
dist[v2] = d2;
h.push_back(std::make_pair(v2, d2));
std::push_heap(h.begin(), h.end(), cmp);
@ -1142,6 +1153,7 @@ static CheckStatus OptimizeChart(SeamData& sd, GraphHandle graph, bool fixInters
// split non manifold vertices
while (tri::Clean<Mesh>::SplitNonManifoldVertex(sd.shell, 0.3))
;
ensure(tri::Clean<Mesh>::CountNonManifoldEdgeFF(sd.shell) == 0);
CutAlongSeams(sd.shell);
@ -1449,12 +1461,21 @@ static void RejectMove(const SeamData& sd, AlgoStateHandle state, GraphHandle gr
// restore vertex-face topology
// iterate over emap, and split the lists according to the original topology
// recall that we never touched any vertex topology attribute
for (auto& entry : sd.evec) {
//ensure(entry.second.size() > 1); not true for self seams at the cone vertex
const std::vector<Mesh::VertexPointer>& verts = entry.second;
for (unsigned i = 0; i < entry.second.size() - 1; ++i) {
sd.vfmap.at(verts[i]).first.back()->VFp(sd.vfmap.at(verts[i]).second.back()) = 0;
sd.vfmap.at(verts[i]).first.back()->VFi(sd.vfmap.at(verts[i]).second.back()) = 0;
{
for (Mesh::VertexPointer vp : sd.seamVertices) {
vp->VFp() = 0;
vp->VFi() = 0;
}
for (Mesh::FacePointer fptr : sd.vfTopologyFaceSet) {
for (int i = 0; i < 3; ++i) {
if (sd.seamVertices.find(fptr->V(i)) != sd.seamVertices.end()) {
(*fptr).VFp(i) = (*fptr).V(i)->VFp();
(*fptr).VFi(i) = (*fptr).V(i)->VFi();
(*fptr).V(i)->VFp() = &(*fptr);
(*fptr).V(i)->VFi() = i;
}
}
}
}

View File

@ -65,12 +65,12 @@ struct SeamData {
std::vector<int> vertexinda;
std::vector<int> vertexindb;
std::unordered_set<Mesh::VertexPointer> seamVertices;
std::unordered_set<Mesh::FacePointer> vfTopologyFaceSet;
std::map<Mesh::VertexPointer, Mesh::VertexPointer> mrep;
std::map<SeamMesh::VertexPointer, std::vector<Mesh::VertexPointer>> evec;
typedef std::pair<std::vector<Mesh::FacePointer>, std::vector<int>> FanInfo;
std::map<Mesh::VertexPointer, FanInfo> vfmap;
std::unordered_set<Mesh::VertexPointer> verticesWithinThreshold;
std::unordered_set<Mesh::FacePointer> optimizationArea;
std::vector<vcg::Point2d> texcoordoptVert;

View File

@ -135,7 +135,7 @@ void ExtractUVCoordinates(ClusteredSeamHandle csh, std::vector<Point2d>& uva, st
}
}
void BuildSeamMesh(Mesh& m, SeamMesh& seamMesh)
void BuildSeamMesh(Mesh& m, SeamMesh& seamMesh, GraphHandle graph)
{
seamMesh.Clear();
@ -145,18 +145,22 @@ void BuildSeamMesh(Mesh& m, SeamMesh& seamMesh)
tri::UpdateFlags<Mesh>::FaceClearFaceEdgeS(m);
for (auto& f : m.face) {
for (int i = 0; i < 3; ++i) {
if (face::IsBorder(f, i) && f.IsFaceEdgeS(i) == false){
if (IsEdgeManifold3D(m, f, i, ffadj) && face::IsBorder(f, i) && f.IsFaceEdgeS(i) == false) {
PosF pa(&f, i);
PosF pb = GetDualPos(m, pa, ffadj);
if (pa.F()->id > pb.F()->id)
std::swap(pa, pb);
auto ei = tri::Allocator<SeamMesh>::AddEdge(seamMesh, pa.V()->P(), pa.VFlip()->P());
ei->fa = pa.F();
ei->ea = pa.E();
ei->fb = pb.F();
ei->eb = pb.E();
pa.F()->SetFaceEdgeS(pa.E());
pb.F()->SetFaceEdgeS(pb.E());
ChartHandle ca = graph->GetChart(pa.F()->id);
ChartHandle cb = graph->GetChart(pb.F()->id);
if (ca == cb || ca->adj.count(cb) > 0) {
if (pa.F()->id > pb.F()->id)
std::swap(pa, pb);
auto ei = tri::Allocator<SeamMesh>::AddEdge(seamMesh, pa.V()->P(), pa.VFlip()->P());
ei->fa = pa.F();
ei->ea = pa.E();
ei->fb = pb.F();
ei->eb = pb.E();
pa.F()->SetFaceEdgeS(pa.E());
pb.F()->SetFaceEdgeS(pb.E());
}
}
}
}

View File

@ -57,7 +57,7 @@ double ComputeSeamLength3D(SeamHandle sh);
// a is a set of ids that logically describe one side of the seam (whose coordinates are inserted in uva)
void ExtractUVCoordinates(ClusteredSeamHandle csh, std::vector<Point2d>& uva, std::vector<Point2d>& uvb, const std::unordered_set<RegionID>& a);
void BuildSeamMesh(Mesh& m, SeamMesh& seamMesh);
void BuildSeamMesh(Mesh& m, SeamMesh& seamMesh, GraphHandle graph);
std::vector<SeamHandle> GenerateSeams(SeamMesh& seamMesh);
std::vector<ClusteredSeamHandle> ClusterSeamsByChartId(const std::vector<SeamHandle>& seams);
ClusteredSeamHandle Flatten(const std::vector<ClusteredSeamHandle>& cshVec);

View File

@ -86,6 +86,7 @@ void TextureObject::Bind(int i)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width(), img.height(), GL_BGRA, GL_UNSIGNED_BYTE, img.constBits());
glGenerateMipmap(GL_TEXTURE_2D);
CheckGLError();
Mirror(img);
}
else {
glBindTexture(GL_TEXTURE_2D, texNameVec[i]);

View File

@ -52,11 +52,11 @@ FilterTextureDefragPlugin::FilterTextureDefragPlugin()
typeList = {
FP_TEXTURE_DEFRAG,
};
for(ActionIDType tt: types())
actionList.push_back(new QAction(filterName(tt), this));
LOG_INIT(logging::Level::Warning);
LOG_INIT(logging::Level::Error);
LOG_SET_THREAD_NAME("TextureDefrag");
}
@ -171,6 +171,14 @@ RichParameterList FilterTextureDefragPlugin::initParameterList(const QAction *ac
0.025,
"Global ARAP distortion tolerance",
"Global ARAP distortion tolerance when merging a seam. If the global atlas energy is higher than this value, the operation is reverted."));
parlst.addParam(RichDynamicFloat(
"uvReductionLimit",
0.0,
0.0,
100.0,
"UV Length Target (percentage)",
"Target UV length as percentage of the input length. The algorithm halts if the target UV length has be en reached, or if no futher "
"seams can be merged."));
parlst.addParam(RichFloat(
"offsetFactor",
5.0,
@ -201,19 +209,17 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
switch(ID(filter)) {
case FP_TEXTURE_DEFRAG:
{
if (vcg::tri::Clean<CMeshO>::CountNonManifoldEdgeFF(md.mm()->cm) > 0 ||
vcg::tri::Clean<CMeshO>::CountNonManifoldVertexFF(md.mm()->cm) > 0) {
throw MLException("Mesh has non-manifold vertices and/or faces. Texture map defragmentation filter requires manifoldness.");
}
cb(0, "Initializing layer...");
MeshModel& mm = *(md.addNewMesh(md.mm()->cm, "texdefrag_" + currentModel.label()));
mm.updateDataMask(&currentModel);
for (const std::string& txtname : currentModel.cm.textures){
mm.addTexture(txtname, currentModel.getTexture(txtname));
}
QString path = currentModel.pathName();
tri::Clean<CMeshO>::RemoveZeroAreaFace(mm.cm);
tri::Clean<CMeshO>::RemoveDuplicateVertex(mm.cm);
tri::Allocator<CMeshO>::CompactEveryVector(mm.cm);
tri::UpdateTopology<CMeshO>::FaceFace(mm.cm);
if (tri::Clean<CMeshO>::CountNonManifoldEdgeFF(mm.cm) > 0)
log(GLLogStream::Levels::WARNING, "Texture Defragmentation: mesh has non-manifold edges, seam topology may be unreliable");
@ -222,8 +228,6 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
QDir wd = QDir::current();
QDir::setCurrent(path);
tri::Allocator<CMeshO>::CompactEveryVector(mm.cm);
// build mesh object
Mesh defragMesh;
auto fi = tri::Allocator<Mesh>::AddFaces(defragMesh, mm.cm.FN());
@ -252,8 +256,8 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
// build textureobjecthandle object
TextureObjectHandle textureObject = std::make_shared<TextureObject>();
for (const string& textureName : mm.cm.textures) {
textureObject->AddImage(mm.getTexture(textureName));
for (const string& textureName : currentModel.cm.textures) {
textureObject->AddImage(currentModel.getTexture(textureName));
}
AlgoParameters ap;
@ -262,7 +266,7 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
ap.boundaryTolerance = par.getFloat("boundaryTolerance");
ap.distortionTolerance = par.getFloat("distortionTolerance");
ap.globalDistortionThreshold = par.getFloat("globalDistortionTolerance");
ap.UVBorderLengthReduction = 0.0;
ap.UVBorderLengthReduction = par.getFloat("uvReductionLimit") / 100.0f;
ap.offsetFactor = par.getFloat("offsetFactor");
ap.timelimit = par.getFloat("timelimit");
@ -273,24 +277,22 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
ScaleTextureCoordinatesToImage(defragMesh, textureObject);
// setup proxy mesh
{
Compute3DFaceAdjacencyAttribute(defragMesh);
CutAlongSeams(defragMesh);
tri::Allocator<Mesh>::CompactEveryVector(defragMesh);
tri::UpdateTopology<Mesh>::FaceFace(defragMesh);
while (tri::Clean<Mesh>::SplitNonManifoldVertex(defragMesh, 0))
;
tri::UpdateTopology<Mesh>::VertexFace(defragMesh);
tri::Allocator<Mesh>::CompactEveryVector(defragMesh);
}
ComputeWedgeTexCoordStorageAttribute(defragMesh);
Compute3DFaceAdjacencyAttribute(defragMesh);
CutAlongSeams(defragMesh);
GraphHandle graph = ComputeGraph(defragMesh, textureObject);
while (tri::Clean<Mesh>::SplitNonManifoldVertex(defragMesh, 0))
;
tri::Allocator<Mesh>::CompactEveryVector(defragMesh);
DisconnectCharts(graph);
tri::UpdateTopology<Mesh>::FaceFace(defragMesh);
tri::UpdateTopology<Mesh>::VertexFace(defragMesh);
ComputeWedgeTexCoordStorageAttribute(defragMesh);
std::map<RegionID, bool> flipped;
for (auto& c : graph->charts)
flipped[c.first] = c.second->UVFlipped();
@ -323,7 +325,7 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
}
}
cb(70, "Packing new atlas...");
cb(70, "Packing atlas...");
// clear texture coordinates of empty-area charts
std::vector<ChartHandle> chartsToPack;
for (auto& entry : graph->charts) {
@ -341,6 +343,16 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
}
}
// Set non-manifold edges as border, otherwise outline extraction fails
for (auto& f : graph->mesh.face) {
for (int i = 0; i < 3; ++i) {
if (!face::IsManifold(f, i)) {
f.FFp(i) = &f;
f.FFi(i) = i;
}
}
}
std::vector<TextureSize> texszVec;
int npacked = Pack(chartsToPack, textureObject, texszVec);
@ -374,7 +386,7 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
mm.clearTextures();
const char *imageFormat = "png";
QString textureBase = QFileInfo(currentModel.fullName()).baseName() + "_optimized_texture_";
QString textureBase = mm.label() + "_optimized_texture_";
for (unsigned i = 0; i < newTextures.size(); ++i) {
QString tname = textureBase + QString(std::to_string(i).c_str()) + "." + imageFormat;
mm.addTexture(tname.toStdString(), *newTextures[i]);
@ -390,7 +402,7 @@ std::map<std::string, QVariant> FilterTextureDefragPlugin::applyFilter(
default:
wrongActionCalled(filter);
}
return std::map<std::string, QVariant>();
}