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
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

View File

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

View File

@ -849,6 +849,7 @@ std::unique_ptr<MeshCombiner::Mesh> 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<MeshCombiner::Mesh> 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<Vector3> combinedVertices;

View File

@ -58,6 +58,7 @@ public:
struct GeneratedComponent {
std::unique_ptr<MeshCombiner::Mesh> mesh;
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::vector<ObjectNode> objectNodes;
std::vector<std::pair<std::pair<Uuid, Uuid>, std::pair<Uuid, Uuid>>> objectEdges;

View File

@ -25,92 +25,90 @@
#include <dust3d/uv/max_rectangles.h>
namespace dust3d {
namespace uv {
void ChartPacker::setCharts(const std::vector<std::pair<float, float>>& chartSizes)
{
m_chartSizes = chartSizes;
}
void ChartPacker::setCharts(const std::vector<std::pair<float, float>>& chartSizes)
{
m_chartSizes = chartSizes;
}
const std::vector<std::tuple<float, float, float, float, bool>>& ChartPacker::getResult()
{
return m_result;
}
const std::vector<std::tuple<float, float, float, float, bool>>& 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<MaxRectanglesSize> 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<MaxRectanglesPosition> bestResult;
for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) {
std::vector<MaxRectanglesPosition> 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<uv::MaxRectanglesSize> 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<uv::MaxRectanglesPosition> bestResult;
for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); ++i) {
std::vector<uv::MaxRectanglesPosition> 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;
}
}
}

View File

@ -28,30 +28,28 @@
#include <vector>
namespace dust3d {
namespace uv {
class ChartPacker {
public:
void setCharts(const std::vector<std::pair<float, float>>& chartSizes);
const std::vector<std::tuple<float, float, float, float, bool>>& getResult();
float pack();
bool tryPack(float textureSize);
class ChartPacker {
public:
void setCharts(const std::vector<std::pair<float, float>>& chartSizes);
const std::vector<std::tuple<float, float, float, float, bool>>& getResult();
float pack();
bool tryPack(float textureSize);
private:
double calculateTotalArea();
private:
double calculateTotalArea();
std::vector<std::pair<float, float>> m_chartSizes;
std::vector<std::tuple<float, float, float, float, bool>> 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<std::pair<float, float>> m_chartSizes;
std::vector<std::tuple<float, float, float, float, bool>> 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

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