dust3d/thirdparty/simpleuv/simpleuv/uvunwrapper.cpp

608 lines
22 KiB
C++
Raw Normal View History

#include <map>
#include <unordered_set>
2018-10-16 00:04:08 +00:00
#include <set>
#include <queue>
2019-03-03 06:44:48 +00:00
#include <cmath>
#include <simpleuv/uvunwrapper.h>
#include <simpleuv/parametrize.h>
#include <simpleuv/chartpacker.h>
#include <simpleuv/triangulate.h>
2019-08-10 13:42:08 +00:00
#include <Eigen/Dense>
#include <Eigen/Geometry>
namespace simpleuv
{
const std::vector<float> UvUnwrapper::m_rotateDegrees = {5, 15, 20, 25, 30, 35, 40, 45};
void UvUnwrapper::setMesh(const Mesh &mesh)
{
m_mesh = mesh;
}
2019-08-10 14:03:49 +00:00
void UvUnwrapper::setTexelSize(float texelSize)
{
m_texelSizePerUnit = texelSize;
}
2019-05-12 08:15:07 +00:00
const std::vector<FaceTextureCoords> &UvUnwrapper::getFaceUvs() const
{
return m_faceUvs;
}
2019-08-10 13:42:08 +00:00
const std::vector<Rect> &UvUnwrapper::getChartRects() const
2019-05-12 08:15:07 +00:00
{
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;
2018-11-17 23:02:12 +00:00
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;
2018-11-17 23:02:12 +00:00
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);
2019-08-09 10:26:31 +00:00
bool segmentByNormal = !m_mesh.faceNormals.empty() && m_segmentByNormal;
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;
2018-11-17 23:02:12 +00:00
auto findOppositeFaceResult = edgeToFaceMap.find({face.indices[j], face.indices[i]});
if (findOppositeFaceResult == edgeToFaceMap.end())
continue;
2019-08-09 10:26:31 +00:00
if (segmentByNormal) {
if (dotProduct(m_mesh.faceNormals[findOppositeFaceResult->second],
2019-08-09 22:27:26 +00:00
m_mesh.faceNormals[m_segmentPreferMorePieces ? indexInGroup : index]) < m_segmentDotProductThreshold) {
2019-08-09 10:26:31 +00:00
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);
2018-10-16 00:04:08 +00:00
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;
2018-11-17 23:02:12 +00:00
auto findOppositeFaceResult = edgeToFaceMap.find({face.indices[j], face.indices[i]});
if (findOppositeFaceResult != edgeToFaceMap.end())
continue;
2018-11-17 23:02:12 +00:00
holeVertexLink[face.indices[j]].push_back(face.indices[i]);
}
}
std::vector<std::pair<std::vector<size_t>, double>> holeRings;
while (!holeVertexLink.empty()) {
2018-10-16 00:04:08 +00:00
bool foundRing = false;
std::vector<size_t> ring;
2018-10-16 00:04:08 +00:00
std::unordered_set<size_t> visited;
std::set<std::pair<size_t, size_t>> visitedPath;
double ringLength = 0;
2018-10-16 00:04:08 +00:00
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()) {
2019-08-10 13:42:08 +00:00
//qDebug() << "Search ring failed";
2018-10-16 00:04:08 +00:00
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) {
2019-08-10 13:42:08 +00:00
//qDebug() << "No new path to try";
2018-10-16 00:04:08 +00:00
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;
}
2018-10-16 00:04:08 +00:00
}
if (ring.size() < 3) {
2019-08-10 13:42:08 +00:00
//qDebug() << "Ring too short, size:" << ring.size();
2018-10-16 00:04:08 +00:00
return false;
}
holeRings.push_back({ring, ringLength});
2018-10-16 00:04:08 +00:00
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) {
2018-11-17 23:02:12 +00:00
// 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) {
2018-11-17 23:02:12 +00:00
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;
2018-11-17 23:02:12 +00:00
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]);
}
}
2019-08-10 13:42:08 +00:00
float UvUnwrapper::areaOf3dTriangle(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c)
2019-08-09 12:33:42 +00:00
{
auto ab = b - a;
auto ac = c - a;
2019-08-10 13:42:08 +00:00
return 0.5 * (ab.cross(ac)).norm();
2019-08-09 12:33:42 +00:00
}
2019-08-10 13:42:08 +00:00
float UvUnwrapper::areaOf2dTriangle(const Eigen::Vector2d &a, const Eigen::Vector2d &b, const Eigen::Vector2d &c)
2019-08-09 12:33:42 +00:00
{
2019-08-10 13:42:08 +00:00
return areaOf3dTriangle(Eigen::Vector3d(a.x(), a.y(), 0),
Eigen::Vector3d(b.x(), b.y(), 0),
Eigen::Vector3d(c.x(), c.y(), 0));
2019-08-09 12:33:42 +00:00
}
void UvUnwrapper::calculateSizeAndRemoveInvalidCharts()
{
auto charts = m_charts;
2019-05-12 08:15:07 +00:00
auto chartSourcePartitions = m_chartSourcePartitions;
m_charts.clear();
2019-05-12 08:15:07 +00:00
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)) {
2019-08-10 13:42:08 +00:00
//qDebug() << "Found invalid chart size:" << size.first << "x" << size.second;
continue;
}
2019-08-09 12:33:42 +00:00
float surfaceArea = 0;
for (const auto &item: chart.first) {
const auto &face = m_mesh.faces[item];
2019-08-10 13:42:08 +00:00
surfaceArea += areaOf3dTriangle(Eigen::Vector3d(m_mesh.vertices[face.indices[0]].xyz[0],
2019-08-09 12:33:42 +00:00
m_mesh.vertices[face.indices[0]].xyz[1],
m_mesh.vertices[face.indices[0]].xyz[2]),
2019-08-10 13:42:08 +00:00
Eigen::Vector3d(m_mesh.vertices[face.indices[1]].xyz[0],
2019-08-09 12:33:42 +00:00
m_mesh.vertices[face.indices[1]].xyz[1],
m_mesh.vertices[face.indices[1]].xyz[2]),
2019-08-10 13:42:08 +00:00
Eigen::Vector3d(m_mesh.vertices[face.indices[2]].xyz[0],
2019-08-09 12:33:42 +00:00
m_mesh.vertices[face.indices[2]].xyz[1],
m_mesh.vertices[face.indices[2]].xyz[2]));
}
float uvArea = 0;
for (auto &item: chart.second) {
for (int i = 0; i < 3; ++i) {
item.coords[i].uv[0] -= left;
item.coords[i].uv[1] -= top;
}
2019-08-10 13:42:08 +00:00
uvArea += areaOf2dTriangle(Eigen::Vector2d(item.coords[0].uv[0], item.coords[0].uv[1]),
Eigen::Vector2d(item.coords[1].uv[0], item.coords[1].uv[1]),
Eigen::Vector2d(item.coords[2].uv[0], item.coords[2].uv[1]));
}
if (m_enableRotation) {
2019-08-10 13:42:08 +00:00
Eigen::Vector3d center(size.first * 0.5, size.second * 0.5, 0);
float minRectArea = size.first * size.second;
float minRectLeft = 0;
float minRectTop = 0;
bool rotated = false;
for (const auto &degree: m_rotateDegrees) {
2019-08-10 13:42:08 +00:00
Eigen::Matrix3d matrix;
matrix = Eigen::AngleAxisd(degree * 180.0 / 3.1415926, Eigen::Vector3d::UnitZ());
std::vector<FaceTextureCoords> rotatedUvs;
for (auto &item: chart.second) {
FaceTextureCoords rotatedCoords;
for (int i = 0; i < 3; ++i) {
2019-08-10 13:42:08 +00:00
Eigen::Vector3d point(item.coords[i].uv[0], item.coords[i].uv[1], 0);
point -= center;
point = matrix * point;
rotatedCoords.coords[i].uv[0] = point.x();
rotatedCoords.coords[i].uv[1] = point.y();
}
rotatedUvs.push_back(rotatedCoords);
}
left = top = right = bottom = 0;
calculateFaceTextureBoundingBox(rotatedUvs, left, top, right, bottom);
std::pair<float, float> newSize = {right - left, bottom - top};
float newRectArea = newSize.first * newSize.second;
if (newRectArea < minRectArea) {
minRectArea = newRectArea;
size = newSize;
minRectLeft = left;
minRectTop = top;
rotated = true;
chart.second = rotatedUvs;
}
}
if (rotated) {
for (auto &item: chart.second) {
for (int i = 0; i < 3; ++i) {
item.coords[i].uv[0] -= minRectLeft;
item.coords[i].uv[1] -= minRectTop;
}
}
}
}
//qDebug() << "left:" << left << "top:" << top << "right:" << right << "bottom:" << bottom;
//qDebug() << "width:" << size.first << "height:" << size.second;
2019-08-09 12:33:42 +00:00
float ratioOfSurfaceAreaAndUvArea = uvArea > 0 ? surfaceArea / uvArea : 1.0;
float scale = ratioOfSurfaceAreaAndUvArea * m_texelSizePerUnit;
m_chartSizes.push_back(size);
2019-08-09 12:33:42 +00:00
m_scaledChartSizes.push_back(std::make_pair(size.first * scale, size.second * scale));
m_charts.push_back(chart);
2019-05-12 08:15:07 +00:00
m_chartSourcePartitions.push_back(chartSourcePartitions[chartIndex]);
}
}
void UvUnwrapper::packCharts()
{
ChartPacker chartPacker;
2019-08-09 12:33:42 +00:00
chartPacker.setCharts(m_scaledChartSizes);
m_resultTextureSize = chartPacker.pack();
2019-05-12 08:15:07 +00:00
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);
2019-05-12 08:15:07 +00:00
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);
}
}
}
2019-05-12 08:15:07 +00:00
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++) {
2018-11-17 23:02:12 +00:00
int globalVertexIndex = globalFace.indices[j];
if (globalToLocalVerticesMap.find(globalVertexIndex) == globalToLocalVerticesMap.end()) {
2018-12-29 00:02:53 +00:00
localVertices.push_back(m_mesh.vertices[globalVertexIndex]);
globalToLocalVerticesMap[globalVertexIndex] = (int)localVertices.size() - 1;
}
2018-11-17 23:02:12 +00:00
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)) {
2019-08-10 13:42:08 +00:00
//qDebug() << "fixHolesExceptTheLongestRing failed";
return;
}
if (1 == remainingHoleNumAfterFix) {
2019-05-12 08:15:07 +00:00
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()) {
2019-08-10 13:42:08 +00:00
//qDebug() << "Cut mesh failed";
return;
}
2019-05-12 08:15:07 +00:00
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,
2019-05-12 08:15:07 +00:00
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++) {
2018-11-17 23:02:12 +00:00
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);
2019-05-12 08:15:07 +00:00
m_chartSourcePartitions.push_back(sourcePartition);
}
2019-08-10 14:03:49 +00:00
float UvUnwrapper::getTextureSize() const
2019-08-09 12:33:42 +00:00
{
return m_resultTextureSize;
}
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)
2019-05-12 08:15:07 +00:00
unwrapSingleIsland(island, group.first);
}
calculateSizeAndRemoveInvalidCharts();
packCharts();
finalizeUv();
}
}