Add UV map packer

master
Jeremy HU 2022-10-24 23:48:18 +11:00
parent 2b83283961
commit 5fc51c3b02
8 changed files with 277 additions and 98 deletions

View File

@ -318,6 +318,8 @@ HEADERS += ../dust3d/uv/triangulate.h
SOURCES += ../dust3d/uv/triangulate.cc SOURCES += ../dust3d/uv/triangulate.cc
HEADERS += ../dust3d/uv/unwrap_uv.h HEADERS += ../dust3d/uv/unwrap_uv.h
SOURCES += ../dust3d/uv/unwrap_uv.cc 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 HEADERS += ../dust3d/uv/uv_mesh_data_type.h
SOURCES += ../dust3d/uv/uv_mesh_data_type.cc SOURCES += ../dust3d/uv/uv_mesh_data_type.cc
HEADERS += ../dust3d/uv/uv_unwrapper.h HEADERS += ../dust3d/uv/uv_unwrapper.h

View File

@ -23,13 +23,16 @@
#ifndef DUST3D_BASE_OBJECT_H_ #ifndef DUST3D_BASE_OBJECT_H_
#define DUST3D_BASE_OBJECT_H_ #define DUST3D_BASE_OBJECT_H_
#include <array>
#include <dust3d/base/color.h> #include <dust3d/base/color.h>
#include <dust3d/base/position_key.h>
#include <dust3d/base/rectangle.h> #include <dust3d/base/rectangle.h>
#include <dust3d/base/uuid.h> #include <dust3d/base/uuid.h>
#include <dust3d/base/vector2.h> #include <dust3d/base/vector2.h>
#include <dust3d/base/vector3.h> #include <dust3d/base/vector3.h>
#include <map> #include <map>
#include <set> #include <set>
#include <unordered_map>
#include <vector> #include <vector>
namespace dust3d { namespace dust3d {
@ -59,6 +62,7 @@ public:
std::vector<std::pair<Uuid, Uuid>> vertexSourceNodes; std::vector<std::pair<Uuid, Uuid>> vertexSourceNodes;
std::vector<std::vector<size_t>> triangleAndQuads; std::vector<std::vector<size_t>> triangleAndQuads;
std::vector<std::vector<size_t>> triangles; std::vector<std::vector<size_t>> triangles;
std::unordered_map<Uuid, std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> partTriangleUvs;
std::vector<Vector3> triangleNormals; std::vector<Vector3> triangleNormals;
std::vector<Color> triangleColors; std::vector<Color> triangleColors;
bool alphaEnabled = false; bool alphaEnabled = false;

View File

@ -849,6 +849,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const st
for (const auto& vertex : partCache.vertices) for (const auto& vertex : partCache.vertices)
componentCache.noneSeamVertices.insert(vertex); componentCache.noneSeamVertices.insert(vertex);
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges); collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
componentCache.partTriangleUvs.insert({ Uuid(partIdString), partCache.triangleUvs });
for (const auto& it : partCache.objectNodes) for (const auto& it : partCache.objectNodes)
componentCache.objectNodes.push_back(it); componentCache.objectNodes.push_back(it);
for (const auto& it : partCache.objectEdges) for (const auto& it : partCache.objectEdges)
@ -989,6 +990,8 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentChildGroupMes
componentCache.noneSeamVertices.insert(vertex); componentCache.noneSeamVertices.insert(vertex);
for (const auto& it : childComponentCache.sharedQuadEdges) for (const auto& it : childComponentCache.sharedQuadEdges)
componentCache.sharedQuadEdges.insert(it); componentCache.sharedQuadEdges.insert(it);
for (const auto& it : childComponentCache.partTriangleUvs)
componentCache.partTriangleUvs.insert({ it.first, it.second });
for (const auto& it : childComponentCache.objectNodes) for (const auto& it : childComponentCache.objectNodes)
componentCache.objectNodes.push_back(it); componentCache.objectNodes.push_back(it);
for (const auto& it : childComponentCache.objectEdges) for (const auto& it : childComponentCache.objectEdges)
@ -1356,6 +1359,7 @@ void MeshGenerator::generate()
m_object->nodes = componentCache.objectNodes; m_object->nodes = componentCache.objectNodes;
m_object->edges = componentCache.objectEdges; m_object->edges = componentCache.objectEdges;
m_object->partTriangleUvs = componentCache.partTriangleUvs;
m_nodeVertices = componentCache.objectNodeVertices; m_nodeVertices = componentCache.objectNodeVertices;
std::vector<Vector3> combinedVertices; std::vector<Vector3> combinedVertices;

View File

@ -58,6 +58,7 @@ public:
struct GeneratedComponent { struct GeneratedComponent {
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshCombiner::Mesh> mesh;
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges; std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
std::unordered_map<Uuid, std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> partTriangleUvs;
std::set<PositionKey> noneSeamVertices; std::set<PositionKey> noneSeamVertices;
std::vector<ObjectNode> objectNodes; std::vector<ObjectNode> objectNodes;
std::vector<std::pair<std::pair<Uuid, Uuid>, std::pair<Uuid, Uuid>>> objectEdges; std::vector<std::pair<std::pair<Uuid, Uuid>, std::pair<Uuid, Uuid>>> objectEdges;

View File

@ -25,52 +25,51 @@
#include <dust3d/uv/max_rectangles.h> #include <dust3d/uv/max_rectangles.h>
namespace dust3d { namespace dust3d {
namespace uv {
void ChartPacker::setCharts(const std::vector<std::pair<float, float>>& chartSizes) void ChartPacker::setCharts(const std::vector<std::pair<float, float>>& chartSizes)
{ {
m_chartSizes = chartSizes; m_chartSizes = chartSizes;
} }
const std::vector<std::tuple<float, float, float, float, bool>>& ChartPacker::getResult() const std::vector<std::tuple<float, float, float, float, bool>>& ChartPacker::getResult()
{ {
return m_result; return m_result;
} }
double ChartPacker::calculateTotalArea() double ChartPacker::calculateTotalArea()
{ {
double totalArea = 0; double totalArea = 0;
for (const auto& chartSize : m_chartSizes) { for (const auto& chartSize : m_chartSizes) {
totalArea += chartSize.first * chartSize.second; totalArea += chartSize.first * chartSize.second;
} }
return totalArea; return totalArea;
} }
bool ChartPacker::tryPack(float textureSize) bool ChartPacker::tryPack(float textureSize)
{ {
std::vector<MaxRectanglesSize> rects; std::vector<uv::MaxRectanglesSize> rects;
int width = textureSize * m_floatToIntFactor; int width = textureSize * m_floatToIntFactor;
int height = width; int height = width;
float paddingSize = m_paddingSize * width; float paddingSize = m_paddingSize * width;
float paddingSize2 = paddingSize + paddingSize; float paddingSize2 = paddingSize + paddingSize;
for (const auto& chartSize : m_chartSizes) { for (const auto& chartSize : m_chartSizes) {
MaxRectanglesSize r; uv::MaxRectanglesSize r;
r.width = chartSize.first * m_floatToIntFactor + paddingSize2; r.width = chartSize.first * m_floatToIntFactor + paddingSize2;
r.height = chartSize.second * m_floatToIntFactor + paddingSize2; r.height = chartSize.second * m_floatToIntFactor + paddingSize2;
rects.push_back(r); rects.push_back(r);
} }
const MaxRectanglesFreeRectChoiceHeuristic methods[] = { const uv::MaxRectanglesFreeRectChoiceHeuristic methods[] = {
kMaxRectanglesBestShortSideFit, uv::kMaxRectanglesBestShortSideFit,
kMaxRectanglesBestLongSideFit, uv::kMaxRectanglesBestLongSideFit,
kMaxRectanglesBestAreaFit, uv::kMaxRectanglesBestAreaFit,
kMaxRectanglesBottomLeftRule, uv::kMaxRectanglesBottomLeftRule,
kMaxRectanglesContactPointRule uv::kMaxRectanglesContactPointRule
}; };
float occupancy = 0; float occupancy = 0;
float bestOccupancy = 0; float bestOccupancy = 0;
std::vector<MaxRectanglesPosition> bestResult; std::vector<uv::MaxRectanglesPosition> bestResult;
for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) { for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) {
std::vector<MaxRectanglesPosition> result(rects.size()); std::vector<uv::MaxRectanglesPosition> result(rects.size());
if (0 != maxRectangles(width, height, rects.size(), rects.data(), methods[i], true, result.data(), &occupancy)) { if (0 != maxRectangles(width, height, rects.size(), rects.data(), methods[i], true, result.data(), &occupancy)) {
continue; continue;
} }
@ -93,10 +92,10 @@ namespace uv {
std::get<4>(dest) = result.rotated; std::get<4>(dest) = result.rotated;
} }
return true; return true;
} }
float ChartPacker::pack() float ChartPacker::pack()
{ {
float textureSize = 0; float textureSize = 0;
float initialGuessSize = std::sqrt(calculateTotalArea() * m_initialAreaGuessFactor); float initialGuessSize = std::sqrt(calculateTotalArea() * m_initialAreaGuessFactor);
while (true) { while (true) {
@ -110,7 +109,6 @@ namespace uv {
} }
} }
return textureSize; return textureSize;
} }
} }
}

View File

@ -28,16 +28,15 @@
#include <vector> #include <vector>
namespace dust3d { namespace dust3d {
namespace uv {
class ChartPacker { class ChartPacker {
public: public:
void setCharts(const std::vector<std::pair<float, float>>& chartSizes); void setCharts(const std::vector<std::pair<float, float>>& chartSizes);
const std::vector<std::tuple<float, float, float, float, bool>>& getResult(); const std::vector<std::tuple<float, float, float, float, bool>>& getResult();
float pack(); float pack();
bool tryPack(float textureSize); bool tryPack(float textureSize);
private: private:
double calculateTotalArea(); double calculateTotalArea();
std::vector<std::pair<float, float>> m_chartSizes; std::vector<std::pair<float, float>> m_chartSizes;
@ -49,9 +48,8 @@ namespace uv {
float m_textureSizeFactor = 1.0; float m_textureSizeFactor = 1.0;
float m_paddingSize = 0.005; float m_paddingSize = 0.005;
size_t m_maxTryNum = 100; size_t m_maxTryNum = 100;
}; };
} }
}
#endif #endif

104
dust3d/uv/uv_map_packer.cc Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2016-2022 Jeremy HU <jeremy-at-dust3d dot org>. 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 <dust3d/uv/chart_packer.h>
#include <dust3d/uv/uv_map_packer.h>
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<std::pair<float, float>> 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, 3> {
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::Layout>& UvMapPacker::packedLayouts()
{
return m_packedLayouts;
}
}

68
dust3d/uv/uv_map_packer.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2016-2022 Jeremy HU <jeremy-at-dust3d dot org>. 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 <array>
#include <dust3d/base/position_key.h>
#include <dust3d/base/uuid.h>
#include <dust3d/base/vector2.h>
#include <map>
#include <vector>
namespace dust3d {
class UvMapPacker {
public:
struct Part {
Uuid id;
double width;
double height;
std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>> localUv;
};
struct Layout {
Uuid id;
double left;
double top;
double width;
double height;
bool flipped;
std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>> globalUv;
};
UvMapPacker();
void addPart(const Part& part);
void pack();
const std::vector<Layout>& packedLayouts();
double packedTextureSize();
private:
std::vector<Part> m_partTriangleUvs;
std::vector<Layout> m_packedLayouts;
double m_packedTextureSize = 0.0;
};
}
#endif