511 lines
18 KiB
C++
511 lines
18 KiB
C++
#include <map>
|
|
#include <unordered_set>
|
|
#include <set>
|
|
#include <queue>
|
|
#include <cmath>
|
|
#include <simpleuv/uvunwrapper.h>
|
|
#include <simpleuv/parametrize.h>
|
|
#include <simpleuv/chartpacker.h>
|
|
#include <simpleuv/triangulate.h>
|
|
#include <QDebug>
|
|
|
|
namespace simpleuv
|
|
{
|
|
|
|
void UvUnwrapper::setMesh(const Mesh &mesh)
|
|
{
|
|
m_mesh = mesh;
|
|
}
|
|
|
|
const std::vector<FaceTextureCoords> &UvUnwrapper::getFaceUvs() const
|
|
{
|
|
return m_faceUvs;
|
|
}
|
|
|
|
const std::vector<QRectF> &UvUnwrapper::getChartRects() const
|
|
{
|
|
return m_chartRects;
|
|
}
|
|
|
|
const std::vector<int> &UvUnwrapper::getChartSourcePartitions() const
|
|
{
|
|
return m_chartSourcePartitions;
|
|
}
|
|
|
|
void UvUnwrapper::buildEdgeToFaceMap(const std::vector<Face> &faces, std::map<std::pair<size_t, size_t>, size_t> &edgeToFaceMap)
|
|
{
|
|
edgeToFaceMap.clear();
|
|
for (decltype(faces.size()) index = 0; index < faces.size(); ++index) {
|
|
const auto &face = faces[index];
|
|
for (size_t i = 0; i < 3; i++) {
|
|
size_t j = (i + 1) % 3;
|
|
edgeToFaceMap[{face.indices[i], face.indices[j]}] = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::buildEdgeToFaceMap(const std::vector<size_t> &group, std::map<std::pair<size_t, size_t>, size_t> &edgeToFaceMap)
|
|
{
|
|
edgeToFaceMap.clear();
|
|
for (const auto &index: group) {
|
|
const auto &face = m_mesh.faces[index];
|
|
for (size_t i = 0; i < 3; i++) {
|
|
size_t j = (i + 1) % 3;
|
|
edgeToFaceMap[{face.indices[i], face.indices[j]}] = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::splitPartitionToIslands(const std::vector<size_t> &group, std::vector<std::vector<size_t>> &islands)
|
|
{
|
|
std::map<std::pair<size_t, size_t>, size_t> edgeToFaceMap;
|
|
buildEdgeToFaceMap(group, edgeToFaceMap);
|
|
|
|
std::unordered_set<size_t> processedFaces;
|
|
std::queue<size_t> waitFaces;
|
|
for (const auto &indexInGroup: group) {
|
|
if (processedFaces.find(indexInGroup) != processedFaces.end())
|
|
continue;
|
|
waitFaces.push(indexInGroup);
|
|
std::vector<size_t> island;
|
|
while (!waitFaces.empty()) {
|
|
size_t index = waitFaces.front();
|
|
waitFaces.pop();
|
|
if (processedFaces.find(index) != processedFaces.end())
|
|
continue;
|
|
const auto &face = m_mesh.faces[index];
|
|
for (size_t i = 0; i < 3; i++) {
|
|
size_t j = (i + 1) % 3;
|
|
auto findOppositeFaceResult = edgeToFaceMap.find({face.indices[j], face.indices[i]});
|
|
if (findOppositeFaceResult == edgeToFaceMap.end())
|
|
continue;
|
|
waitFaces.push(findOppositeFaceResult->second);
|
|
}
|
|
island.push_back(index);
|
|
processedFaces.insert(index);
|
|
}
|
|
if (island.empty())
|
|
continue;
|
|
islands.push_back(island);
|
|
}
|
|
}
|
|
|
|
double UvUnwrapper::distanceBetweenVertices(const Vertex &first, const Vertex &second)
|
|
{
|
|
float x = first.xyz[0] - second.xyz[0];
|
|
float y = first.xyz[1] - second.xyz[1];
|
|
float z = first.xyz[2] - second.xyz[2];
|
|
return std::sqrt(x*x + y*y + z*z);
|
|
}
|
|
|
|
void UvUnwrapper::calculateFaceTextureBoundingBox(const std::vector<FaceTextureCoords> &faceTextureCoords,
|
|
float &left, float &top, float &right, float &bottom)
|
|
{
|
|
bool leftFirstTime = true;
|
|
bool topFirstTime = true;
|
|
bool rightFirstTime = true;
|
|
bool bottomFirstTime = true;
|
|
for (const auto &item: faceTextureCoords) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
const auto &x = item.coords[i].uv[0];
|
|
const auto &y = item.coords[i].uv[1];
|
|
if (leftFirstTime || x < left) {
|
|
left = x;
|
|
leftFirstTime = false;
|
|
}
|
|
if (rightFirstTime || x > right) {
|
|
right = x;
|
|
rightFirstTime = false;
|
|
}
|
|
if (topFirstTime || y < top) {
|
|
top = y;
|
|
topFirstTime = false;
|
|
}
|
|
if (bottomFirstTime || y > bottom) {
|
|
bottom = y;
|
|
bottomFirstTime = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::triangulateRing(const std::vector<Vertex> &verticies,
|
|
std::vector<Face> &faces, const std::vector<size_t> &ring)
|
|
{
|
|
triangulate(verticies, faces, ring);
|
|
}
|
|
|
|
// The hole filling faces should be put in the back of faces vector, so these uv coords of appended faces will be disgarded.
|
|
bool UvUnwrapper::fixHolesExceptTheLongestRing(const std::vector<Vertex> &verticies, std::vector<Face> &faces, size_t *remainingHoleNum)
|
|
{
|
|
std::map<std::pair<size_t, size_t>, size_t> edgeToFaceMap;
|
|
buildEdgeToFaceMap(faces, edgeToFaceMap);
|
|
|
|
std::map<size_t, std::vector<size_t>> holeVertexLink;
|
|
for (const auto &face: faces) {
|
|
for (size_t i = 0; i < 3; i++) {
|
|
size_t j = (i + 1) % 3;
|
|
auto findOppositeFaceResult = edgeToFaceMap.find({face.indices[j], face.indices[i]});
|
|
if (findOppositeFaceResult != edgeToFaceMap.end())
|
|
continue;
|
|
holeVertexLink[face.indices[j]].push_back(face.indices[i]);
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<std::vector<size_t>, double>> holeRings;
|
|
while (!holeVertexLink.empty()) {
|
|
bool foundRing = false;
|
|
std::vector<size_t> ring;
|
|
std::unordered_set<size_t> visited;
|
|
std::set<std::pair<size_t, size_t>> visitedPath;
|
|
double ringLength = 0;
|
|
while (!foundRing) {
|
|
ring.clear();
|
|
visited.clear();
|
|
ringLength = 0;
|
|
auto first = holeVertexLink.begin()->first;
|
|
auto index = first;
|
|
auto prev = first;
|
|
ring.push_back(first);
|
|
visited.insert(first);
|
|
while (true) {
|
|
auto findLinkResult = holeVertexLink.find(index);
|
|
if (findLinkResult == holeVertexLink.end()) {
|
|
qDebug() << "Search ring failed";
|
|
return false;
|
|
}
|
|
for (const auto &item: findLinkResult->second) {
|
|
if (item == first) {
|
|
foundRing = true;
|
|
break;
|
|
}
|
|
}
|
|
if (foundRing)
|
|
break;
|
|
if (findLinkResult->second.size() > 1) {
|
|
bool foundNewPath = false;
|
|
for (const auto &item: findLinkResult->second) {
|
|
if (visitedPath.find({prev, item}) == visitedPath.end()) {
|
|
index = item;
|
|
foundNewPath = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundNewPath) {
|
|
qDebug() << "No new path to try";
|
|
return false;
|
|
}
|
|
visitedPath.insert({prev, index});
|
|
} else {
|
|
index = *findLinkResult->second.begin();
|
|
}
|
|
if (visited.find(index) != visited.end()) {
|
|
while (index != *ring.begin())
|
|
ring.erase(ring.begin());
|
|
foundRing = true;
|
|
break;
|
|
}
|
|
ring.push_back(index);
|
|
visited.insert(index);
|
|
ringLength += distanceBetweenVertices(verticies[index], verticies[prev]);
|
|
prev = index;
|
|
}
|
|
}
|
|
if (ring.size() < 3) {
|
|
qDebug() << "Ring too short, size:" << ring.size();
|
|
return false;
|
|
}
|
|
holeRings.push_back({ring, ringLength});
|
|
for (size_t i = 0; i < ring.size(); ++i) {
|
|
size_t j = (i + 1) % ring.size();
|
|
auto findLinkResult = holeVertexLink.find(ring[i]);
|
|
for (auto it = findLinkResult->second.begin(); it != findLinkResult->second.end(); ++it) {
|
|
if (*it == ring[j]) {
|
|
findLinkResult->second.erase(it);
|
|
if (findLinkResult->second.empty())
|
|
holeVertexLink.erase(ring[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (holeRings.size() > 1) {
|
|
// Sort by ring length, the longer ring sit in the lower array indices
|
|
std::sort(holeRings.begin(), holeRings.end(), [](const std::pair<std::vector<size_t>, double> &first, const std::pair<std::vector<size_t>, double> &second) {
|
|
return first.second > second.second;
|
|
});
|
|
for (size_t i = 1; i < holeRings.size(); ++i) {
|
|
triangulateRing(verticies, faces, holeRings[i].first);
|
|
}
|
|
holeRings.resize(1);
|
|
}
|
|
|
|
if (remainingHoleNum)
|
|
*remainingHoleNum = holeRings.size();
|
|
return true;
|
|
}
|
|
|
|
void UvUnwrapper::makeSeamAndCut(const std::vector<Vertex> &verticies,
|
|
const std::vector<Face> &faces,
|
|
std::map<size_t, size_t> &localToGlobalFacesMap,
|
|
std::vector<size_t> &firstGroup, std::vector<size_t> &secondGroup)
|
|
{
|
|
// We group the chart by first pick the top(max y) triangle, then join the adjecent traigles until the joint count reach to half of total
|
|
|
|
double maxY = 0;
|
|
bool firstTime = true;
|
|
int choosenIndex = -1;
|
|
for (decltype(faces.size()) i = 0; i < faces.size(); ++i) {
|
|
const auto &face = faces[i];
|
|
for (int j = 0; j < 3; ++j) {
|
|
const auto &vertex = verticies[face.indices[j]];
|
|
if (firstTime || vertex.xyz[2] > maxY) {
|
|
maxY = vertex.xyz[2];
|
|
firstTime = false;
|
|
choosenIndex = i;
|
|
}
|
|
}
|
|
}
|
|
if (-1 == choosenIndex)
|
|
return;
|
|
|
|
std::map<std::pair<size_t, size_t>, size_t> edgeToFaceMap;
|
|
buildEdgeToFaceMap(faces, edgeToFaceMap);
|
|
|
|
std::unordered_set<size_t> processedFaces;
|
|
std::queue<size_t> waitFaces;
|
|
waitFaces.push(choosenIndex);
|
|
while (!waitFaces.empty()) {
|
|
auto index = waitFaces.front();
|
|
waitFaces.pop();
|
|
if (processedFaces.find(index) != processedFaces.end())
|
|
continue;
|
|
const auto &face = faces[index];
|
|
for (size_t i = 0; i < 3; i++) {
|
|
size_t j = (i + 1) % 3;
|
|
auto findOppositeFaceResult = edgeToFaceMap.find({face.indices[j], face.indices[i]});
|
|
if (findOppositeFaceResult == edgeToFaceMap.end())
|
|
continue;
|
|
waitFaces.push(findOppositeFaceResult->second);
|
|
}
|
|
processedFaces.insert(index);
|
|
firstGroup.push_back(localToGlobalFacesMap[index]);
|
|
if (firstGroup.size() * 2 >= faces.size())
|
|
break;
|
|
}
|
|
for (decltype(faces.size()) index = 0; index < faces.size(); ++index) {
|
|
if (processedFaces.find(index) != processedFaces.end())
|
|
continue;
|
|
secondGroup.push_back(localToGlobalFacesMap[index]);
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::calculateSizeAndRemoveInvalidCharts()
|
|
{
|
|
auto charts = m_charts;
|
|
auto chartSourcePartitions = m_chartSourcePartitions;
|
|
m_charts.clear();
|
|
chartSourcePartitions.clear();
|
|
for (size_t chartIndex = 0; chartIndex < charts.size(); ++chartIndex) {
|
|
auto &chart = charts[chartIndex];
|
|
float left, top, right, bottom;
|
|
left = top = right = bottom = 0;
|
|
calculateFaceTextureBoundingBox(chart.second, left, top, right, bottom);
|
|
std::pair<float, float> size = {right - left, bottom - top};
|
|
if (size.first <= 0 || std::isnan(size.first) || std::isinf(size.first) ||
|
|
size.second <= 0 || std::isnan(size.second) || std::isinf(size.second)) {
|
|
qDebug() << "Found invalid chart size:" << size.first << "x" << size.second;
|
|
continue;
|
|
}
|
|
for (auto &item: chart.second) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
item.coords[i].uv[0] -= left;
|
|
item.coords[i].uv[1] -= top;
|
|
}
|
|
}
|
|
//qDebug() << "left:" << left << "top:" << top << "right:" << right << "bottom:" << bottom;
|
|
//qDebug() << "width:" << size.first << "height:" << size.second;
|
|
m_chartSizes.push_back(size);
|
|
m_charts.push_back(chart);
|
|
m_chartSourcePartitions.push_back(chartSourcePartitions[chartIndex]);
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::packCharts()
|
|
{
|
|
ChartPacker chartPacker;
|
|
chartPacker.setCharts(m_chartSizes);
|
|
chartPacker.pack();
|
|
m_chartRects.resize(m_chartSizes.size());
|
|
const std::vector<std::tuple<float, float, float, float, bool>> &packedResult = chartPacker.getResult();
|
|
for (decltype(m_charts.size()) i = 0; i < m_charts.size(); ++i) {
|
|
const auto &chartSize = m_chartSizes[i];
|
|
auto &chart = m_charts[i];
|
|
if (i >= packedResult.size()) {
|
|
for (auto &item: chart.second) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
item.coords[i].uv[0] = 0;
|
|
item.coords[i].uv[1] = 0;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
const auto &result = packedResult[i];
|
|
auto &left = std::get<0>(result);
|
|
auto &top = std::get<1>(result);
|
|
auto &width = std::get<2>(result);
|
|
auto &height = std::get<3>(result);
|
|
auto &flipped = std::get<4>(result);
|
|
if (flipped)
|
|
m_chartRects[i] = {left, top, height, width};
|
|
else
|
|
m_chartRects[i] = {left, top, width, height};
|
|
if (flipped) {
|
|
for (auto &item: chart.second) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
std::swap(item.coords[i].uv[0], item.coords[i].uv[1]);
|
|
}
|
|
}
|
|
}
|
|
for (auto &item: chart.second) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
item.coords[i].uv[0] /= chartSize.first;
|
|
item.coords[i].uv[1] /= chartSize.second;
|
|
item.coords[i].uv[0] *= width;
|
|
item.coords[i].uv[1] *= height;
|
|
item.coords[i].uv[0] += left;
|
|
item.coords[i].uv[1] += top;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::finalizeUv()
|
|
{
|
|
m_faceUvs.resize(m_mesh.faces.size());
|
|
for (const auto &chart: m_charts) {
|
|
for (decltype(chart.second.size()) i = 0; i < chart.second.size(); ++i) {
|
|
auto &faceUv = m_faceUvs[chart.first[i]];
|
|
faceUv = chart.second[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::partition()
|
|
{
|
|
m_partitions.clear();
|
|
if (m_mesh.facePartitions.empty()) {
|
|
for (decltype(m_mesh.faces.size()) i = 0; i < m_mesh.faces.size(); i++) {
|
|
m_partitions[0].push_back(i);
|
|
}
|
|
} else {
|
|
for (decltype(m_mesh.faces.size()) i = 0; i < m_mesh.faces.size(); i++) {
|
|
int partition = m_mesh.facePartitions[i];
|
|
m_partitions[partition].push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::unwrapSingleIsland(const std::vector<size_t> &group, int sourcePartition, bool skipCheckHoles)
|
|
{
|
|
if (group.empty())
|
|
return;
|
|
|
|
std::vector<Vertex> localVertices;
|
|
std::vector<Face> localFaces;
|
|
std::map<size_t, size_t> globalToLocalVerticesMap;
|
|
std::map<size_t, size_t> localToGlobalFacesMap;
|
|
for (decltype(group.size()) i = 0; i < group.size(); ++i) {
|
|
const auto &globalFace = m_mesh.faces[group[i]];
|
|
Face localFace;
|
|
for (size_t j = 0; j < 3; j++) {
|
|
int globalVertexIndex = globalFace.indices[j];
|
|
if (globalToLocalVerticesMap.find(globalVertexIndex) == globalToLocalVerticesMap.end()) {
|
|
localVertices.push_back(m_mesh.vertices[globalVertexIndex]);
|
|
globalToLocalVerticesMap[globalVertexIndex] = (int)localVertices.size() - 1;
|
|
}
|
|
localFace.indices[j] = globalToLocalVerticesMap[globalVertexIndex];
|
|
}
|
|
localFaces.push_back(localFace);
|
|
localToGlobalFacesMap[localFaces.size() - 1] = group[i];
|
|
}
|
|
|
|
//if (skipCheckHoles) {
|
|
// parametrizeSingleGroup(localVertices, localFaces, localToGlobalFacesMap, localFaces.size());
|
|
// return;
|
|
//}
|
|
|
|
decltype(localFaces.size()) faceNumBeforeFix = localFaces.size();
|
|
size_t remainingHoleNumAfterFix = 0;
|
|
if (!fixHolesExceptTheLongestRing(localVertices, localFaces, &remainingHoleNumAfterFix)) {
|
|
qDebug() << "fixHolesExceptTheLongestRing failed";
|
|
return;
|
|
}
|
|
if (1 == remainingHoleNumAfterFix) {
|
|
parametrizeSingleGroup(localVertices, localFaces, localToGlobalFacesMap, faceNumBeforeFix, sourcePartition);
|
|
return;
|
|
}
|
|
|
|
if (0 == remainingHoleNumAfterFix) {
|
|
std::vector<size_t> firstGroup;
|
|
std::vector<size_t> secondGroup;
|
|
makeSeamAndCut(localVertices, localFaces, localToGlobalFacesMap, firstGroup, secondGroup);
|
|
if (firstGroup.empty() || secondGroup.empty()) {
|
|
qDebug() << "Cut mesh failed";
|
|
return;
|
|
}
|
|
unwrapSingleIsland(firstGroup, sourcePartition, true);
|
|
unwrapSingleIsland(secondGroup, sourcePartition, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void UvUnwrapper::parametrizeSingleGroup(const std::vector<Vertex> &verticies,
|
|
const std::vector<Face> &faces,
|
|
std::map<size_t, size_t> &localToGlobalFacesMap,
|
|
size_t faceNumToChart,
|
|
int sourcePartition)
|
|
{
|
|
std::vector<TextureCoord> localVertexUvs;
|
|
if (!parametrize(verticies, faces, localVertexUvs))
|
|
return;
|
|
std::pair<std::vector<size_t>, std::vector<FaceTextureCoords>> chart;
|
|
for (size_t i = 0; i < faceNumToChart; ++i) {
|
|
const auto &localFace = faces[i];
|
|
auto globalFaceIndex = localToGlobalFacesMap[i];
|
|
FaceTextureCoords faceUv;
|
|
for (size_t j = 0; j < 3; j++) {
|
|
const auto &localVertexIndex = localFace.indices[j];
|
|
const auto &vertexUv = localVertexUvs[localVertexIndex];
|
|
faceUv.coords[j].uv[0] = vertexUv.uv[0];
|
|
faceUv.coords[j].uv[1] = vertexUv.uv[1];
|
|
}
|
|
chart.first.push_back(globalFaceIndex);
|
|
chart.second.push_back(faceUv);
|
|
}
|
|
if (chart.first.empty())
|
|
return;
|
|
m_charts.push_back(chart);
|
|
m_chartSourcePartitions.push_back(sourcePartition);
|
|
}
|
|
|
|
void UvUnwrapper::unwrap()
|
|
{
|
|
partition();
|
|
|
|
m_faceUvs.resize(m_mesh.faces.size());
|
|
for (const auto &group: m_partitions) {
|
|
std::vector<std::vector<size_t>> islands;
|
|
splitPartitionToIslands(group.second, islands);
|
|
for (const auto &island: islands)
|
|
unwrapSingleIsland(island, group.first);
|
|
}
|
|
|
|
calculateSizeAndRemoveInvalidCharts();
|
|
packCharts();
|
|
finalizeUv();
|
|
}
|
|
|
|
}
|