From 5fc51c3b0249b9d910075e0fb76d7edbe501b804 Mon Sep 17 00:00:00 2001 From: Jeremy HU Date: Mon, 24 Oct 2022 23:48:18 +1100 Subject: [PATCH] Add UV map packer --- application/application.pro | 2 + dust3d/base/object.h | 4 + dust3d/mesh/mesh_generator.cc | 4 + dust3d/mesh/mesh_generator.h | 1 + dust3d/uv/chart_packer.cc | 154 +++++++++++++++++----------------- dust3d/uv/chart_packer.h | 38 ++++----- dust3d/uv/uv_map_packer.cc | 104 +++++++++++++++++++++++ dust3d/uv/uv_map_packer.h | 68 +++++++++++++++ 8 files changed, 277 insertions(+), 98 deletions(-) create mode 100644 dust3d/uv/uv_map_packer.cc create mode 100644 dust3d/uv/uv_map_packer.h diff --git a/application/application.pro b/application/application.pro index 6ec3b93f..0d96e57b 100644 --- a/application/application.pro +++ b/application/application.pro @@ -318,6 +318,8 @@ HEADERS += ../dust3d/uv/triangulate.h SOURCES += ../dust3d/uv/triangulate.cc HEADERS += ../dust3d/uv/unwrap_uv.h SOURCES += ../dust3d/uv/unwrap_uv.cc +HEADERS += ../dust3d/uv/uv_map_packer.h +SOURCES += ../dust3d/uv/uv_map_packer.cc HEADERS += ../dust3d/uv/uv_mesh_data_type.h SOURCES += ../dust3d/uv/uv_mesh_data_type.cc HEADERS += ../dust3d/uv/uv_unwrapper.h diff --git a/dust3d/base/object.h b/dust3d/base/object.h index bbaca995..d778b10f 100644 --- a/dust3d/base/object.h +++ b/dust3d/base/object.h @@ -23,13 +23,16 @@ #ifndef DUST3D_BASE_OBJECT_H_ #define DUST3D_BASE_OBJECT_H_ +#include #include +#include #include #include #include #include #include #include +#include #include namespace dust3d { @@ -59,6 +62,7 @@ public: std::vector> vertexSourceNodes; std::vector> triangleAndQuads; std::vector> triangles; + std::unordered_map, std::array>> partTriangleUvs; std::vector triangleNormals; std::vector triangleColors; bool alphaEnabled = false; diff --git a/dust3d/mesh/mesh_generator.cc b/dust3d/mesh/mesh_generator.cc index 136a2aa7..a806cde9 100644 --- a/dust3d/mesh/mesh_generator.cc +++ b/dust3d/mesh/mesh_generator.cc @@ -849,6 +849,7 @@ std::unique_ptr MeshGenerator::combineComponentMesh(const st for (const auto& vertex : partCache.vertices) componentCache.noneSeamVertices.insert(vertex); collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges); + componentCache.partTriangleUvs.insert({ Uuid(partIdString), partCache.triangleUvs }); for (const auto& it : partCache.objectNodes) componentCache.objectNodes.push_back(it); for (const auto& it : partCache.objectEdges) @@ -989,6 +990,8 @@ std::unique_ptr MeshGenerator::combineComponentChildGroupMes componentCache.noneSeamVertices.insert(vertex); for (const auto& it : childComponentCache.sharedQuadEdges) componentCache.sharedQuadEdges.insert(it); + for (const auto& it : childComponentCache.partTriangleUvs) + componentCache.partTriangleUvs.insert({ it.first, it.second }); for (const auto& it : childComponentCache.objectNodes) componentCache.objectNodes.push_back(it); for (const auto& it : childComponentCache.objectEdges) @@ -1356,6 +1359,7 @@ void MeshGenerator::generate() m_object->nodes = componentCache.objectNodes; m_object->edges = componentCache.objectEdges; + m_object->partTriangleUvs = componentCache.partTriangleUvs; m_nodeVertices = componentCache.objectNodeVertices; std::vector combinedVertices; diff --git a/dust3d/mesh/mesh_generator.h b/dust3d/mesh/mesh_generator.h index ae645bd9..50d42970 100644 --- a/dust3d/mesh/mesh_generator.h +++ b/dust3d/mesh/mesh_generator.h @@ -58,6 +58,7 @@ public: struct GeneratedComponent { std::unique_ptr mesh; std::set> sharedQuadEdges; + std::unordered_map, std::array>> partTriangleUvs; std::set noneSeamVertices; std::vector objectNodes; std::vector, std::pair>> objectEdges; diff --git a/dust3d/uv/chart_packer.cc b/dust3d/uv/chart_packer.cc index 92c55fb5..28cf44f9 100644 --- a/dust3d/uv/chart_packer.cc +++ b/dust3d/uv/chart_packer.cc @@ -25,92 +25,90 @@ #include namespace dust3d { -namespace uv { - void ChartPacker::setCharts(const std::vector>& chartSizes) - { - m_chartSizes = chartSizes; - } +void ChartPacker::setCharts(const std::vector>& chartSizes) +{ + m_chartSizes = chartSizes; +} - const std::vector>& ChartPacker::getResult() - { - return m_result; - } +const std::vector>& ChartPacker::getResult() +{ + return m_result; +} - double ChartPacker::calculateTotalArea() - { - double totalArea = 0; - for (const auto& chartSize : m_chartSizes) { - totalArea += chartSize.first * chartSize.second; - } - return totalArea; +double ChartPacker::calculateTotalArea() +{ + double totalArea = 0; + for (const auto& chartSize : m_chartSizes) { + totalArea += chartSize.first * chartSize.second; } + return totalArea; +} - bool ChartPacker::tryPack(float textureSize) - { - std::vector rects; - int width = textureSize * m_floatToIntFactor; - int height = width; - float paddingSize = m_paddingSize * width; - float paddingSize2 = paddingSize + paddingSize; - for (const auto& chartSize : m_chartSizes) { - MaxRectanglesSize r; - r.width = chartSize.first * m_floatToIntFactor + paddingSize2; - r.height = chartSize.second * m_floatToIntFactor + paddingSize2; - rects.push_back(r); - } - const MaxRectanglesFreeRectChoiceHeuristic methods[] = { - kMaxRectanglesBestShortSideFit, - kMaxRectanglesBestLongSideFit, - kMaxRectanglesBestAreaFit, - kMaxRectanglesBottomLeftRule, - kMaxRectanglesContactPointRule - }; - float occupancy = 0; - float bestOccupancy = 0; - std::vector bestResult; - for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) { - std::vector result(rects.size()); - if (0 != maxRectangles(width, height, rects.size(), rects.data(), methods[i], true, result.data(), &occupancy)) { - continue; - } - if (occupancy > bestOccupancy) { - bestResult = result; - bestOccupancy = occupancy; - } - } - if (bestResult.size() != rects.size()) - return false; - m_result.resize(bestResult.size()); - for (decltype(bestResult.size()) i = 0; i < bestResult.size(); ++i) { - const auto& result = bestResult[i]; - const auto& rect = rects[i]; - auto& dest = m_result[i]; - std::get<0>(dest) = (float)(result.left + paddingSize) / width; - std::get<1>(dest) = (float)(result.top + paddingSize) / height; - std::get<2>(dest) = (float)(rect.width - paddingSize2) / width; - std::get<3>(dest) = (float)(rect.height - paddingSize2) / height; - std::get<4>(dest) = result.rotated; - } - return true; +bool ChartPacker::tryPack(float textureSize) +{ + std::vector rects; + int width = textureSize * m_floatToIntFactor; + int height = width; + float paddingSize = m_paddingSize * width; + float paddingSize2 = paddingSize + paddingSize; + for (const auto& chartSize : m_chartSizes) { + uv::MaxRectanglesSize r; + r.width = chartSize.first * m_floatToIntFactor + paddingSize2; + r.height = chartSize.second * m_floatToIntFactor + paddingSize2; + rects.push_back(r); } + const uv::MaxRectanglesFreeRectChoiceHeuristic methods[] = { + uv::kMaxRectanglesBestShortSideFit, + uv::kMaxRectanglesBestLongSideFit, + uv::kMaxRectanglesBestAreaFit, + uv::kMaxRectanglesBottomLeftRule, + uv::kMaxRectanglesContactPointRule + }; + float occupancy = 0; + float bestOccupancy = 0; + std::vector bestResult; + for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) { + std::vector result(rects.size()); + if (0 != maxRectangles(width, height, rects.size(), rects.data(), methods[i], true, result.data(), &occupancy)) { + continue; + } + if (occupancy > bestOccupancy) { + bestResult = result; + bestOccupancy = occupancy; + } + } + if (bestResult.size() != rects.size()) + return false; + m_result.resize(bestResult.size()); + for (decltype(bestResult.size()) i = 0; i < bestResult.size(); ++i) { + const auto& result = bestResult[i]; + const auto& rect = rects[i]; + auto& dest = m_result[i]; + std::get<0>(dest) = (float)(result.left + paddingSize) / width; + std::get<1>(dest) = (float)(result.top + paddingSize) / height; + std::get<2>(dest) = (float)(rect.width - paddingSize2) / width; + std::get<3>(dest) = (float)(rect.height - paddingSize2) / height; + std::get<4>(dest) = result.rotated; + } + return true; +} - float ChartPacker::pack() - { - float textureSize = 0; - float initialGuessSize = std::sqrt(calculateTotalArea() * m_initialAreaGuessFactor); - while (true) { - textureSize = initialGuessSize * m_textureSizeFactor; - ++m_tryNum; - if (tryPack(textureSize)) - break; - m_textureSizeFactor += m_textureSizeGrowFactor; - if (m_tryNum >= m_maxTryNum) { - break; - } +float ChartPacker::pack() +{ + float textureSize = 0; + float initialGuessSize = std::sqrt(calculateTotalArea() * m_initialAreaGuessFactor); + while (true) { + textureSize = initialGuessSize * m_textureSizeFactor; + ++m_tryNum; + if (tryPack(textureSize)) + break; + m_textureSizeFactor += m_textureSizeGrowFactor; + if (m_tryNum >= m_maxTryNum) { + break; } - return textureSize; } + return textureSize; +} } -} diff --git a/dust3d/uv/chart_packer.h b/dust3d/uv/chart_packer.h index 522e8d5c..5a3acef1 100644 --- a/dust3d/uv/chart_packer.h +++ b/dust3d/uv/chart_packer.h @@ -28,30 +28,28 @@ #include namespace dust3d { -namespace uv { - class ChartPacker { - public: - void setCharts(const std::vector>& chartSizes); - const std::vector>& getResult(); - float pack(); - bool tryPack(float textureSize); +class ChartPacker { +public: + void setCharts(const std::vector>& chartSizes); + const std::vector>& getResult(); + float pack(); + bool tryPack(float textureSize); - private: - double calculateTotalArea(); +private: + double calculateTotalArea(); - std::vector> m_chartSizes; - std::vector> m_result; - float m_initialAreaGuessFactor = 1.1; - float m_textureSizeGrowFactor = 0.05; - float m_floatToIntFactor = 10000; - size_t m_tryNum = 0; - float m_textureSizeFactor = 1.0; - float m_paddingSize = 0.005; - size_t m_maxTryNum = 100; - }; + std::vector> m_chartSizes; + std::vector> m_result; + float m_initialAreaGuessFactor = 1.1; + float m_textureSizeGrowFactor = 0.05; + float m_floatToIntFactor = 10000; + size_t m_tryNum = 0; + float m_textureSizeFactor = 1.0; + float m_paddingSize = 0.005; + size_t m_maxTryNum = 100; +}; } -} #endif diff --git a/dust3d/uv/uv_map_packer.cc b/dust3d/uv/uv_map_packer.cc new file mode 100644 index 00000000..5ce035d8 --- /dev/null +++ b/dust3d/uv/uv_map_packer.cc @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016-2022 Jeremy HU . All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +namespace dust3d { + +UvMapPacker::UvMapPacker() +{ +} + +void UvMapPacker::addPart(const Part& part) +{ + m_partTriangleUvs.push_back(part); +} + +void UvMapPacker::pack() +{ + if (m_partTriangleUvs.empty()) + return; + + std::vector> chartSizes(m_partTriangleUvs.size()); + for (size_t i = 0; i < m_partTriangleUvs.size(); ++i) { + const auto& part = m_partTriangleUvs[i]; + chartSizes[i] = { part.width, part.height }; + } + + ChartPacker chartPacker; + chartPacker.setCharts(chartSizes); + m_packedTextureSize = chartPacker.pack(); + const auto& packedResult = chartPacker.getResult(); + for (size_t i = 0; i < packedResult.size(); ++i) { + auto& part = m_partTriangleUvs[i]; + 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); + Layout layout; + layout.id = part.id; + layout.flipped = flipped; + if (flipped) { + layout.left = left; + layout.top = top; + layout.width = height; + layout.height = width; + } else { + layout.left = left; + layout.top = top; + layout.width = width; + layout.height = height; + } + if (flipped) { + for (auto& it : part.localUv) { + std::swap(it.second[0][0], it.second[0][1]); + std::swap(it.second[1][0], it.second[1][1]); + std::swap(it.second[2][0], it.second[2][1]); + } + } + for (const auto& it : part.localUv) { + layout.globalUv.insert({ it.first, + std::array { + Vector2((left + it.second[0].x() * width) / m_packedTextureSize, + (top + it.second[0].y() * height) / m_packedTextureSize), + Vector2((left + it.second[1].x() * width) / m_packedTextureSize, + (top + it.second[1].y() * height) / m_packedTextureSize), + Vector2((left + it.second[2].x() * width) / m_packedTextureSize, + (top + it.second[2].y() * height) / m_packedTextureSize) } }); + } + m_packedLayouts.emplace_back(layout); + } +} + +double UvMapPacker::packedTextureSize() +{ + return m_packedTextureSize; +} + +const std::vector& UvMapPacker::packedLayouts() +{ + return m_packedLayouts; +} +} diff --git a/dust3d/uv/uv_map_packer.h b/dust3d/uv/uv_map_packer.h new file mode 100644 index 00000000..9b0084a4 --- /dev/null +++ b/dust3d/uv/uv_map_packer.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016-2022 Jeremy HU . All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef DUST3D_UV_MAP_PACKER_H_ +#define DUST3D_UV_MAP_PACKER_H_ + +#include +#include +#include +#include +#include +#include + +namespace dust3d { + +class UvMapPacker { +public: + struct Part { + Uuid id; + double width; + double height; + std::map, std::array> localUv; + }; + + struct Layout { + Uuid id; + double left; + double top; + double width; + double height; + bool flipped; + std::map, std::array> globalUv; + }; + + UvMapPacker(); + void addPart(const Part& part); + void pack(); + const std::vector& packedLayouts(); + double packedTextureSize(); + +private: + std::vector m_partTriangleUvs; + std::vector m_packedLayouts; + double m_packedTextureSize = 0.0; +}; + +} + +#endif