Use MeshState in MeshGenerator

master
Jeremy HU 2022-11-01 20:53:02 +11:00
parent 9fe28f84df
commit 1fa1e0e199
4 changed files with 69 additions and 92 deletions

View File

@ -103,24 +103,6 @@ void MeshGenerator::subdivideFace(std::vector<Vector2>* face)
} }
} }
bool MeshGenerator::isWatertight(const std::vector<std::vector<size_t>>& faces)
{
std::set<std::pair<size_t, size_t>> halfEdges;
for (const auto& face : faces) {
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto insertResult = halfEdges.insert({ face[i], face[j] });
if (!insertResult.second)
return false;
}
}
for (const auto& it : halfEdges) {
if (halfEdges.find({ it.second, it.first }) == halfEdges.end())
return false;
}
return true;
}
void MeshGenerator::recoverQuads(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& triangles, const std::set<std::pair<PositionKey, PositionKey>>& sharedQuadEdges, std::vector<std::vector<size_t>>& triangleAndQuads) void MeshGenerator::recoverQuads(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& triangles, const std::set<std::pair<PositionKey, PositionKey>>& sharedQuadEdges, std::vector<std::vector<size_t>>& triangleAndQuads)
{ {
std::vector<PositionKey> verticesPositionKeys; std::vector<PositionKey> verticesPositionKeys;
@ -439,7 +421,7 @@ bool MeshGenerator::fetchPartOrderedNodes(const std::string& partIdString, std::
builderNodeIdStringToIndexMap.insert({ nodeIdString, builderNodes.size() }); builderNodeIdStringToIndexMap.insert({ nodeIdString, builderNodes.size() });
builderNodes.emplace_back(MeshNode { builderNodes.emplace_back(MeshNode {
Vector3((double)x, (double)y, (double)z), (double)radius }); Vector3((double)x, (double)y, (double)z), (double)radius, Uuid(nodeIdString) });
} }
if (builderNodes.empty()) { if (builderNodes.empty()) {
@ -480,7 +462,7 @@ bool MeshGenerator::fetchPartOrderedNodes(const std::string& partIdString, std::
return true; return true;
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineStitchingMesh(const std::vector<std::string>& partIdStrings, std::unique_ptr<MeshState> MeshGenerator::combineStitchingMesh(const std::vector<std::string>& partIdStrings,
const std::vector<std::string>& componentIdStrings, const std::vector<std::string>& componentIdStrings,
GeneratedComponent& componentCache) GeneratedComponent& componentCache)
{ {
@ -511,7 +493,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineStitchingMesh(const st
stitchMeshBuilder->generatedFaces(), stitchMeshBuilder->generatedFaces(),
&componentCache.sharedQuadEdges); &componentCache.sharedQuadEdges);
auto mesh = std::make_unique<MeshCombiner::Mesh>(stitchMeshBuilder->generatedVertices(), auto mesh = std::make_unique<MeshState>(stitchMeshBuilder->generatedVertices(),
stitchMeshBuilder->generatedFaces()); stitchMeshBuilder->generatedFaces());
if (mesh && mesh->isNull()) if (mesh && mesh->isNull())
mesh.reset(); mesh.reset();
@ -563,7 +545,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineStitchingMesh(const st
return mesh; return mesh;
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combinePartMesh(const std::string& partIdString, std::unique_ptr<MeshState> MeshGenerator::combinePartMesh(const std::string& partIdString,
const std::string& componentIdString, const std::string& componentIdString,
bool* hasError) bool* hasError)
{ {
@ -704,9 +686,9 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combinePartMesh(const std::st
} }
bool hasMeshError = false; bool hasMeshError = false;
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshState> mesh;
mesh = std::make_unique<MeshCombiner::Mesh>(partCache.vertices, partCache.faces); mesh = std::make_unique<MeshState>(partCache.vertices, partCache.faces);
if (mesh->isNull()) { if (mesh->isNull()) {
hasMeshError = true; hasMeshError = true;
} }
@ -800,9 +782,9 @@ std::string MeshGenerator::componentColorName(const std::map<std::string, std::s
return std::string(); return std::string();
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const std::string& componentIdString, CombineMode* combineMode) std::unique_ptr<MeshState> MeshGenerator::combineComponentMesh(const std::string& componentIdString, CombineMode* combineMode)
{ {
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshState> mesh;
Uuid componentId; Uuid componentId;
const std::map<std::string, std::string>* component = &m_snapshot->rootComponent; const std::map<std::string, std::string>* component = &m_snapshot->rootComponent;
@ -822,7 +804,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const st
if (m_cacheEnabled) { if (m_cacheEnabled) {
if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) { if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) {
if (nullptr != componentCache.mesh) if (nullptr != componentCache.mesh)
return std::make_unique<MeshCombiner::Mesh>(*componentCache.mesh); return std::make_unique<MeshState>(*componentCache.mesh);
} }
} }
@ -887,7 +869,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const st
} }
combineGroups[currentGroupIndex].second.push_back(childIdString); combineGroups[currentGroupIndex].second.push_back(childIdString);
} }
std::vector<std::tuple<std::unique_ptr<MeshCombiner::Mesh>, CombineMode, std::string>> groupMeshes; std::vector<std::tuple<std::unique_ptr<MeshState>, CombineMode, std::string>> groupMeshes;
for (const auto& group : combineGroups) { for (const auto& group : combineGroups) {
auto childMesh = combineComponentChildGroupMesh(group.second, componentCache); auto childMesh = combineComponentChildGroupMesh(group.second, componentCache);
if (nullptr == childMesh || childMesh->isNull()) if (nullptr == childMesh || childMesh->isNull())
@ -908,7 +890,7 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const st
} }
if (nullptr != mesh) if (nullptr != mesh)
componentCache.mesh = std::make_unique<MeshCombiner::Mesh>(*mesh); componentCache.mesh = std::make_unique<MeshState>(*mesh);
if (nullptr != mesh && mesh->isNull()) { if (nullptr != mesh && mesh->isNull()) {
mesh.reset(); mesh.reset();
@ -917,9 +899,9 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentMesh(const st
return mesh; return mesh;
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineMultipleMeshes(std::vector<std::tuple<std::unique_ptr<MeshCombiner::Mesh>, CombineMode, std::string>>&& multipleMeshes) std::unique_ptr<MeshState> MeshGenerator::combineMultipleMeshes(std::vector<std::tuple<std::unique_ptr<MeshState>, CombineMode, std::string>>&& multipleMeshes)
{ {
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshState> mesh;
std::string meshIdStrings; std::string meshIdStrings;
for (auto& it : multipleMeshes) { for (auto& it : multipleMeshes) {
auto subMesh = std::move(std::get<0>(it)); auto subMesh = std::move(std::get<0>(it));
@ -936,20 +918,20 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineMultipleMeshes(std::ve
auto combinerMethod = childCombineMode == CombineMode::Inversion ? MeshCombiner::Method::Diff : MeshCombiner::Method::Union; auto combinerMethod = childCombineMode == CombineMode::Inversion ? MeshCombiner::Method::Diff : MeshCombiner::Method::Union;
auto combinerMethodString = combinerMethod == MeshCombiner::Method::Union ? "+" : "-"; auto combinerMethodString = combinerMethod == MeshCombiner::Method::Union ? "+" : "-";
meshIdStrings += combinerMethodString + subMeshIdString; meshIdStrings += combinerMethodString + subMeshIdString;
std::unique_ptr<MeshCombiner::Mesh> newMesh; std::unique_ptr<MeshState> newMesh;
auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings); auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings);
if (findCached != m_cacheContext->cachedCombination.end()) { if (findCached != m_cacheContext->cachedCombination.end()) {
if (nullptr != findCached->second.mesh) { if (nullptr != findCached->second) {
newMesh = std::make_unique<MeshCombiner::Mesh>(*findCached->second.mesh); newMesh = std::make_unique<MeshState>(*findCached->second);
} }
} else { } else {
newMesh = combineTwoMeshes(*mesh, newMesh = MeshState::combine(*mesh,
*subMesh, *subMesh,
combinerMethod); combinerMethod);
if (nullptr != newMesh) if (nullptr != newMesh)
m_cacheContext->cachedCombination.insert({ meshIdStrings, GeneratedCombination { std::make_unique<MeshCombiner::Mesh>(*newMesh) } }); m_cacheContext->cachedCombination.insert({ meshIdStrings, std::make_unique<MeshState>(*newMesh) });
else else
m_cacheContext->cachedCombination.insert({ meshIdStrings, GeneratedCombination {} }); m_cacheContext->cachedCombination.insert({ meshIdStrings, nullptr });
} }
if (newMesh && !newMesh->isNull()) { if (newMesh && !newMesh->isNull()) {
mesh = std::move(newMesh); mesh = std::move(newMesh);
@ -963,12 +945,12 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineMultipleMeshes(std::ve
return mesh; return mesh;
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentChildGroupMesh(const std::vector<std::string>& componentIdStrings, GeneratedComponent& componentCache) std::unique_ptr<MeshState> MeshGenerator::combineComponentChildGroupMesh(const std::vector<std::string>& componentIdStrings, GeneratedComponent& componentCache)
{ {
std::vector<std::tuple<std::unique_ptr<MeshCombiner::Mesh>, CombineMode, std::string>> multipleMeshes; std::vector<std::tuple<std::unique_ptr<MeshState>, CombineMode, std::string>> multipleMeshes;
for (const auto& childIdString : componentIdStrings) { for (const auto& childIdString : componentIdStrings) {
CombineMode childCombineMode = CombineMode::Normal; CombineMode childCombineMode = CombineMode::Normal;
std::unique_ptr<MeshCombiner::Mesh> subMesh = combineComponentMesh(childIdString, &childCombineMode); std::unique_ptr<MeshState> subMesh = combineComponentMesh(childIdString, &childCombineMode);
if (CombineMode::Uncombined == childCombineMode) { if (CombineMode::Uncombined == childCombineMode) {
continue; continue;
@ -999,40 +981,6 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineComponentChildGroupMes
return combineMultipleMeshes(std::move(multipleMeshes)); return combineMultipleMeshes(std::move(multipleMeshes));
} }
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineTwoMeshes(const MeshCombiner::Mesh& first, const MeshCombiner::Mesh& second,
MeshCombiner::Method method)
{
if (first.isNull() || second.isNull())
return nullptr;
std::vector<std::pair<MeshCombiner::Source, size_t>> combinedVerticesSources;
auto newMesh = std::unique_ptr<MeshCombiner::Mesh>(MeshCombiner::combine(first,
second,
method,
&combinedVerticesSources));
if (nullptr == newMesh)
return nullptr;
if (!newMesh->isNull()) {
MeshRecombiner recombiner;
std::vector<Vector3> combinedVertices;
std::vector<std::vector<size_t>> combinedFaces;
newMesh->fetch(combinedVertices, combinedFaces);
recombiner.setVertices(&combinedVertices, &combinedVerticesSources);
recombiner.setFaces(&combinedFaces);
if (recombiner.recombine()) {
if (isWatertight(recombiner.regeneratedFaces())) {
auto reMesh = std::make_unique<MeshCombiner::Mesh>(recombiner.regeneratedVertices(), recombiner.regeneratedFaces());
if (!reMesh->isNull()) {
newMesh = std::move(reMesh);
}
}
}
}
if (newMesh->isNull()) {
return nullptr;
}
return newMesh;
}
void MeshGenerator::makeXmirror(const std::vector<Vector3>& sourceVertices, const std::vector<std::vector<size_t>>& sourceFaces, void MeshGenerator::makeXmirror(const std::vector<Vector3>& sourceVertices, const std::vector<std::vector<size_t>>& sourceFaces,
std::vector<Vector3>* destVertices, std::vector<std::vector<size_t>>* destFaces) std::vector<Vector3>* destVertices, std::vector<std::vector<size_t>>* destFaces)
{ {
@ -1135,7 +1083,7 @@ void MeshGenerator::postprocessObject(Object* object)
object->setTriangleVertexNormals(triangleVertexNormals); object->setTriangleVertexNormals(triangleVertexNormals);
} }
void MeshGenerator::collectIncombinableMesh(const MeshCombiner::Mesh* mesh, const GeneratedComponent& componentCache) void MeshGenerator::collectIncombinableMesh(const MeshState* mesh, const GeneratedComponent& componentCache)
{ {
if (nullptr == mesh) if (nullptr == mesh)
return; return;

View File

@ -30,6 +30,7 @@
#include <dust3d/base/uuid.h> #include <dust3d/base/uuid.h>
#include <dust3d/mesh/mesh_combiner.h> #include <dust3d/mesh/mesh_combiner.h>
#include <dust3d/mesh/mesh_node.h> #include <dust3d/mesh/mesh_node.h>
#include <dust3d/mesh/mesh_state.h>
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <unordered_map> #include <unordered_map>
@ -70,7 +71,7 @@ public:
}; };
struct GeneratedComponent { struct GeneratedComponent {
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshState> 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::unordered_map<Uuid, std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> partTriangleUvs;
std::vector<std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> seamTriangleUvs; std::vector<std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> seamTriangleUvs;
@ -91,16 +92,11 @@ public:
} }
}; };
struct GeneratedCombination {
std::unique_ptr<MeshCombiner::Mesh> mesh;
std::vector<std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> seamTriangleUvs;
};
struct GeneratedCacheContext { struct GeneratedCacheContext {
std::map<std::string, GeneratedComponent> components; std::map<std::string, GeneratedComponent> components;
std::map<std::string, GeneratedPart> parts; std::map<std::string, GeneratedPart> parts;
std::map<std::string, std::string> partMirrorIdMap; std::map<std::string, std::string> partMirrorIdMap;
std::map<std::string, GeneratedCombination> cachedCombination; std::map<std::string, std::unique_ptr<MeshState>> cachedCombination;
}; };
struct ComponentPreview { struct ComponentPreview {
@ -151,27 +147,25 @@ private:
bool m_weldEnabled = true; bool m_weldEnabled = true;
void collectParts(); void collectParts();
void collectIncombinableMesh(const MeshCombiner::Mesh* mesh, const GeneratedComponent& componentCache); void collectIncombinableMesh(const MeshState* mesh, const GeneratedComponent& componentCache);
bool checkIsComponentDirty(const std::string& componentIdString); bool checkIsComponentDirty(const std::string& componentIdString);
bool checkIsPartDirty(const std::string& partIdString); bool checkIsPartDirty(const std::string& partIdString);
bool checkIsPartDependencyDirty(const std::string& partIdString); bool checkIsPartDependencyDirty(const std::string& partIdString);
void checkDirtyFlags(); void checkDirtyFlags();
std::unique_ptr<MeshCombiner::Mesh> combinePartMesh(const std::string& partIdString, std::unique_ptr<MeshState> combinePartMesh(const std::string& partIdString,
const std::string& componentIdString, const std::string& componentIdString,
bool* hasError); bool* hasError);
std::unique_ptr<MeshCombiner::Mesh> combineComponentMesh(const std::string& componentIdString, CombineMode* combineMode); std::unique_ptr<MeshState> combineComponentMesh(const std::string& componentIdString, CombineMode* combineMode);
void makeXmirror(const std::vector<Vector3>& sourceVertices, const std::vector<std::vector<size_t>>& sourceFaces, void makeXmirror(const std::vector<Vector3>& sourceVertices, const std::vector<std::vector<size_t>>& sourceFaces,
std::vector<Vector3>* destVertices, std::vector<std::vector<size_t>>* destFaces); std::vector<Vector3>* destVertices, std::vector<std::vector<size_t>>* destFaces);
void collectSharedQuadEdges(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& faces, void collectSharedQuadEdges(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& faces,
std::set<std::pair<PositionKey, PositionKey>>* sharedQuadEdges); std::set<std::pair<PositionKey, PositionKey>>* sharedQuadEdges);
std::unique_ptr<MeshCombiner::Mesh> combineTwoMeshes(const MeshCombiner::Mesh& first, const MeshCombiner::Mesh& second,
MeshCombiner::Method method);
const std::map<std::string, std::string>* findComponent(const std::string& componentIdString); const std::map<std::string, std::string>* findComponent(const std::string& componentIdString);
CombineMode componentCombineMode(const std::map<std::string, std::string>* component); CombineMode componentCombineMode(const std::map<std::string, std::string>* component);
std::unique_ptr<MeshCombiner::Mesh> combineComponentChildGroupMesh(const std::vector<std::string>& componentIdStrings, std::unique_ptr<MeshState> combineComponentChildGroupMesh(const std::vector<std::string>& componentIdStrings,
GeneratedComponent& componentCache); GeneratedComponent& componentCache);
std::unique_ptr<MeshCombiner::Mesh> combineMultipleMeshes(std::vector<std::tuple<std::unique_ptr<MeshCombiner::Mesh>, CombineMode, std::string>>&& multipleMeshes); std::unique_ptr<MeshState> combineMultipleMeshes(std::vector<std::tuple<std::unique_ptr<MeshState>, CombineMode, std::string>>&& multipleMeshes);
std::unique_ptr<MeshCombiner::Mesh> combineStitchingMesh(const std::vector<std::string>& partIdStrings, std::unique_ptr<MeshState> combineStitchingMesh(const std::vector<std::string>& partIdStrings,
const std::vector<std::string>& componentIdStrings, const std::vector<std::string>& componentIdStrings,
GeneratedComponent& componentCache); GeneratedComponent& componentCache);
std::string componentColorName(const std::map<std::string, std::string>* component); std::string componentColorName(const std::map<std::string, std::string>* component);
@ -187,7 +181,6 @@ private:
static void chamferFace(std::vector<Vector2>* face); static void chamferFace(std::vector<Vector2>* face);
static void subdivideFace(std::vector<Vector2>* face); static void subdivideFace(std::vector<Vector2>* face);
static bool isWatertight(const std::vector<std::vector<size_t>>& faces);
static void flattenLinks(const std::unordered_map<size_t, size_t>& links, static void flattenLinks(const std::unordered_map<size_t, size_t>& links,
std::vector<size_t>* array, std::vector<size_t>* array,
bool* isCircle); bool* isCircle);

View File

@ -25,17 +25,37 @@
namespace dust3d { namespace dust3d {
MeshState::MeshState(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& faces)
{
mesh = std::make_unique<MeshCombiner::Mesh>(vertices, faces);
}
MeshState::MeshState(const MeshState& other)
{
if (nullptr != other.mesh)
mesh = std::make_unique<MeshCombiner::Mesh>(*other.mesh);
seamTriangleUvs = other.seamTriangleUvs;
}
void MeshState::fetch(std::vector<Vector3>& vertices, std::vector<std::vector<size_t>>& faces) const void MeshState::fetch(std::vector<Vector3>& vertices, std::vector<std::vector<size_t>>& faces) const
{ {
if (mesh) if (mesh)
mesh->fetch(vertices, faces); mesh->fetch(vertices, faces);
} }
bool MeshState::isNull() const
{
if (nullptr == mesh)
return true;
return mesh->isNull();
}
std::unique_ptr<MeshState> MeshState::combine(const MeshState& first, const MeshState& second, std::unique_ptr<MeshState> MeshState::combine(const MeshState& first, const MeshState& second,
MeshCombiner::Method method) MeshCombiner::Method method)
{ {
if (first.mesh->isNull() || second.mesh->isNull()) if (first.mesh->isNull() || second.mesh->isNull())
return nullptr; return nullptr;
auto newMeshState = std::make_unique<MeshState>();
std::vector<std::pair<MeshCombiner::Source, size_t>> combinedVerticesSources; std::vector<std::pair<MeshCombiner::Source, size_t>> combinedVerticesSources;
auto newMesh = std::unique_ptr<MeshCombiner::Mesh>(MeshCombiner::combine(*first.mesh, auto newMesh = std::unique_ptr<MeshCombiner::Mesh>(MeshCombiner::combine(*first.mesh,
*second.mesh, *second.mesh,
@ -54,6 +74,19 @@ std::unique_ptr<MeshState> MeshState::combine(const MeshState& first, const Mesh
if (MeshState::isWatertight(recombiner.regeneratedFaces())) { if (MeshState::isWatertight(recombiner.regeneratedFaces())) {
auto reMesh = std::make_unique<MeshCombiner::Mesh>(recombiner.regeneratedVertices(), recombiner.regeneratedFaces()); auto reMesh = std::make_unique<MeshCombiner::Mesh>(recombiner.regeneratedVertices(), recombiner.regeneratedFaces());
if (!reMesh->isNull()) { if (!reMesh->isNull()) {
for (const auto& uvSeams : recombiner.generatedBridgingTriangleUvs()) {
std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>> uvs;
for (const auto& it : uvSeams) {
uvs.insert(std::make_pair(std::array<PositionKey, 3> {
PositionKey(it.first[0]),
PositionKey(it.first[1]),
PositionKey(it.first[2]) },
it.second));
}
if (uvs.empty())
continue;
newMeshState->seamTriangleUvs.push_back(uvs);
}
newMesh = std::move(reMesh); newMesh = std::move(reMesh);
} }
} }
@ -62,7 +95,6 @@ std::unique_ptr<MeshState> MeshState::combine(const MeshState& first, const Mesh
if (newMesh->isNull()) { if (newMesh->isNull()) {
return nullptr; return nullptr;
} }
auto newMeshState = std::make_unique<MeshState>();
newMeshState->mesh = std::move(newMesh); newMeshState->mesh = std::move(newMesh);
return newMeshState; return newMeshState;
} }

View File

@ -34,7 +34,11 @@ public:
std::unique_ptr<MeshCombiner::Mesh> mesh; std::unique_ptr<MeshCombiner::Mesh> mesh;
std::vector<std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> seamTriangleUvs; std::vector<std::map<std::array<PositionKey, 3>, std::array<Vector2, 3>>> seamTriangleUvs;
MeshState() = default;
MeshState(const std::vector<Vector3>& vertices, const std::vector<std::vector<size_t>>& faces);
MeshState(const MeshState& other);
void fetch(std::vector<Vector3>& vertices, std::vector<std::vector<size_t>>& faces) const; void fetch(std::vector<Vector3>& vertices, std::vector<std::vector<size_t>>& faces) const;
bool isNull() const;
static std::unique_ptr<MeshState> combine(const MeshState& first, const MeshState& second, static std::unique_ptr<MeshState> combine(const MeshState& first, const MeshState& second,
MeshCombiner::Method method); MeshCombiner::Method method);
static bool isWatertight(const std::vector<std::vector<size_t>>& faces); static bool isWatertight(const std::vector<std::vector<size_t>>& faces);