Changed MeshRelation API (#184)

* cause repro

* added originalID to BaryRef

* make coverage check informational

* unique meshIDs for compose

* make AsOriginal decimate

* remove vscode properties from git

* update docs

* remove vertNormal handling

* Revert "remove vertNormal handling"

This reverts commit c92d590e4d23795504db9afb62eb45adb43d5308.

* alternative fix
This commit is contained in:
Emmett Lalish
2022-08-13 17:20:43 -07:00
committed by GitHub
parent 591b9e648d
commit 747dcd94bc
16 changed files with 148 additions and 189 deletions
+8
View File
@@ -0,0 +1,8 @@
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true
+1
View File
@@ -107,3 +107,4 @@ Temporary Items
build
__pycache__
.vscode/c_cpp_properties.json
-22
View File
@@ -1,22 +0,0 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"configurationProvider": "vector-of-bool.cmake-tools"
}
],
"version": 4
}
+6 -1
View File
@@ -54,7 +54,12 @@ Manifold MengerSponge(int n) {
result -= hole;
hole = hole.Rotate(0, 0, 90);
result -= hole;
result = result.AsOriginal();
// Alternative order causes degenerate triangles
// Manifold tmp1 = hole.Rotate(90) + hole.Rotate(90).Rotate(0, 0, 90);
// Manifold tmp2 = hole + tmp1;
// result = result - tmp2;
return result;
}
} // namespace manifold
+1 -1
View File
@@ -118,7 +118,7 @@ class Manifold {
*/
///@{
MeshRelation GetMeshRelation() const;
std::vector<int> GetMeshIDs() const;
int OriginalID() const;
Manifold AsOriginal() const;
///@}
+11 -21
View File
@@ -19,10 +19,6 @@
#include "par.h"
#include "polygon.h"
// Mark the meshID as coming from P by flipping the 30th bit.
// The 30th bit is used to avoid flipping the sign.
constexpr int kFromP = 1 << 30;
using namespace manifold;
using namespace thrust::placeholders;
@@ -421,6 +417,8 @@ struct CreateBarycentric {
glm::vec3 *barycentricR;
BaryRef *faceRef;
int *idx;
const int offsetQ;
const int firstNewVert;
const glm::vec3 *vertPosR;
const glm::vec3 *vertPosP;
@@ -446,9 +444,10 @@ struct CreateBarycentric {
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
faceRef[halfedgeR.face] = oldRef;
if (halfedgeRef.PQ == 0) {
// Mark the meshID as coming from P
faceRef[halfedgeR.face].meshID |= kFromP;
if (halfedgeRef.PQ == 1) {
// Mark the meshID as coming from Q
faceRef[halfedgeR.face].meshID += offsetQ;
}
if (halfedgeR.startVert < firstNewVert) { // retained vert
@@ -488,6 +487,8 @@ std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
VecDH<BaryRef> faceRef(numFaceR);
VecDH<int> halfedgeBary(halfedgeRef.size());
const int offsetQ = Manifold::Impl::meshIDCounter_;
VecDH<int> idx(1, 0);
for_each_n(
autoPolicy(halfedgeRef.size()),
@@ -495,12 +496,13 @@ std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
halfedgeRef.size(),
CreateBarycentric(
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
inP.meshRelation_.barycentric.cptrD(),
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
outR.meshRelation_.barycentric.resize(idx[0]);
return std::make_pair(faceRef, halfedgeBary);
}
} // namespace
@@ -674,19 +676,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
if (ManifoldParams().intermediateChecks)
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
// Index starting from 0 is chosen because I want the kernel part as simple as
// possible.
VecDH<int> meshIDs;
VecDH<int> original;
for (auto &entry : inP_.meshRelation_.originalID) {
meshIDs.push_back(entry.first | kFromP);
original.push_back(entry.second);
}
for (auto &entry : inQ_.meshRelation_.originalID) {
meshIDs.push_back(entry.first);
original.push_back(entry.second);
}
outR.UpdateMeshIDs(meshIDs, original);
outR.IncrementMeshIDs(0, outR.NumTri());
#ifdef MANIFOLD_DEBUG
simplify.Stop();
-10
View File
@@ -381,13 +381,6 @@ std::vector<Manifold> Manifold::Decompose() const {
return meshes;
}
VecDH<int> vertLabel(components);
// meshID mapping for UpdateMeshIDs
VecDH<int> meshIDs;
VecDH<int> original;
for (auto& entry : pImpl_->meshRelation_.originalID) {
meshIDs.push_back(entry.first);
original.push_back(entry.second);
}
std::vector<Manifold> meshes;
for (int i = 0; i < numLabel; ++i) {
@@ -421,9 +414,6 @@ std::vector<Manifold> Manifold::Decompose() const {
impl->Finish();
// meshIDs and original will only be sorted after successful updates, so we
// can keep using the old one.
impl->UpdateMeshIDs(meshIDs, original);
meshes.push_back(Manifold(impl));
}
return meshes;
+4 -12
View File
@@ -210,18 +210,10 @@ Manifold::Impl CsgLeafNode::Compose(
combined.halfedge_.begin() + nextEdge,
UpdateHalfedge({nextVert, nextEdge, nextTri}));
// Assign new IDs to triangles added in this iteration, to differentiate
// triangles coming from different manifolds.
VecDH<int> meshIDs;
VecDH<int> original;
for (auto &entry : node->pImpl_->meshRelation_.originalID) {
meshIDs.push_back(entry.first);
original.push_back(entry.second);
}
int meshIDStart = combined.meshRelation_.originalID.size();
combined.UpdateMeshIDs(meshIDs, original, nextTri,
node->pImpl_->meshRelation_.triBary.size(),
meshIDStart);
// Since the nodes may be copies containing the same meshIDs, it is
// important to increment them separately so that each node instance gets
// unique meshIDs.
combined.IncrementMeshIDs(nextTri, node->pImpl_->NumTri());
nextVert += node->pImpl_->NumVert();
nextEdge += 2 * node->pImpl_->NumEdge();
+45 -48
View File
@@ -146,11 +146,29 @@ struct InitializeBaryRef {
int tri = thrust::get<1>(inOut);
baryRef.meshID = meshID;
baryRef.originalID = meshID;
baryRef.tri = tri;
baryRef.vertBary = {-3, -2, -1};
}
};
struct MarkMeshID {
int* includesMeshID;
__host__ __device__ void operator()(BaryRef& ref) {
includesMeshID[ref.meshID] = 1;
}
};
struct UpdateMeshID {
const int* meshIDold2new;
const int meshIDoffset;
__host__ __device__ void operator()(BaryRef& ref) {
ref.meshID = meshIDold2new[ref.meshID] + meshIDoffset;
}
};
struct CheckProperties {
const int numSets;
@@ -349,9 +367,8 @@ void Manifold::Impl::ReinitializeReference(int meshID) {
// 0 -> meshID, because the meshID after boolean operation also starts from 0.
for_each_n(autoPolicy(NumTri()),
zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
InitializeBaryRef({0, halfedge_.cptrD()}));
meshRelation_.originalID.clear();
meshRelation_.originalID[0] = meshID;
InitializeBaryRef({meshID, halfedge_.cptrD()}));
meshRelation_.originalID = meshID;
}
int Manifold::Impl::InitializeNewReference(
@@ -591,53 +608,33 @@ void Manifold::Impl::CalculateNormals() {
}
/**
* Update meshID and originalID in meshRelation_.triBary[startTri..startTri+n]
* according to meshIDs -> originalIDs mapping. The updated meshID will start
* from startID.
* Will raise an exception if meshRelation_.triBary[startTri..startTri+n]
* contains a meshID not in meshIDs.
*
* We remap them into indices starting from startID. The exact value value is
* not important as long as
* 1. They are distinct
* 2. `originalID[meshID]` is the original mesh ID of the triangle
*
* Use this when the mesh is a combination of several meshes or a subset of a
* larger mesh, e.g. after performing boolean operations, compose or decompose.
* Remaps all the contained meshIDs to new unique values to represent new
* instances of these meshes.
*/
void Manifold::Impl::UpdateMeshIDs(VecDH<int>& meshIDs, VecDH<int>& originalIDs,
int startTri, int n, int startID) {
if (n == -1) n = meshRelation_.triBary.size();
sort_by_key(autoPolicy(n), meshIDs.begin(), meshIDs.end(),
originalIDs.begin());
constexpr int kOccurred = 1 << 30;
VecDH<int> error(1, -1);
const int numMesh = meshIDs.size();
const int* meshIDsPtr = meshIDs.cptrD();
int* originalPtr = originalIDs.ptrD();
int* errorPtr = error.ptrD();
for_each(autoPolicy(n), meshRelation_.triBary.begin() + startTri,
meshRelation_.triBary.begin() + startTri + n,
[=] __host__ __device__(BaryRef & b) {
int index = thrust::lower_bound(meshIDsPtr, meshIDsPtr + numMesh,
b.meshID) -
meshIDsPtr;
if (index >= numMesh || meshIDsPtr[index] != b.meshID) {
*errorPtr = b.meshID;
}
b.meshID = index + startID;
originalPtr[index] |= kOccurred;
});
void Manifold::Impl::IncrementMeshIDs(int start, int length) {
VecDH<BaryRef>& triBary = meshRelation_.triBary;
ASSERT(start >= 0 && length >= 0 && start + length <= triBary.size(),
logicErr, "out of bounds");
const auto policy = autoPolicy(length);
// Use double the space since the Boolean has P and Q instances.
VecDH<int> includesMeshID(2 * Manifold::Impl::meshIDCounter_, 0);
ASSERT(error[0] == -1, logicErr,
"Manifold::UpdateMeshIDs: meshID " + std::to_string(error[0]) +
" not found in meshIDs.");
for (int i = 0; i < numMesh; ++i) {
if (originalIDs[i] & kOccurred) {
originalIDs[i] &= ~kOccurred;
meshRelation_.originalID[i + startID] = originalIDs[i];
}
}
auto begin = triBary.begin() + start;
auto end = begin + length;
for_each(policy, begin, end, MarkMeshID({includesMeshID.ptrD()}));
inclusive_scan(autoPolicy(includesMeshID.size()), includesMeshID.begin(),
includesMeshID.end(), includesMeshID.begin());
const int numMeshIDs = includesMeshID[includesMeshID.size() - 1];
const int meshIDstart = Manifold::Impl::meshIDCounter_.fetch_add(
numMeshIDs, std::memory_order_relaxed);
// We do start - 1 because the inclusive scan makes our first index 1 instead
// of 0.
for_each(policy, begin, end,
UpdateMeshID({includesMeshID.cptrD(), meshIDstart - 1}));
}
/**
+3 -12
View File
@@ -27,17 +27,9 @@ namespace manifold {
struct Manifold::Impl {
struct MeshRelationD {
VecDH<glm::vec3> barycentric;
/// meshID in BaryRef has different meaning in MeshRelation and
/// MeshRelationD:
/// - In `MeshRelation`: The original mesh triangle index.
/// - In `MeshRelationD`: The original mesh triangle index =
/// `originalID[meshID]`
///
/// @note Triangles coming from different manifolds should have different
/// mesh ID, otherwise `SimplifyTopology` will not work properly.
VecDH<BaryRef> triBary;
/// meshID to originalID mapping.
std::unordered_map<int, int> originalID;
/// The meshID of this Manifold if it is an original; -1 otherwise.
int originalID = -1;
};
Box bBox_;
@@ -70,8 +62,7 @@ struct Manifold::Impl {
void ReinitializeReference(int meshID);
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
void CalculateNormals();
void UpdateMeshIDs(VecDH<int>& meshIDs, VecDH<int>& originalIDs,
int startTri = 0, int n = -1, int startID = 0);
void IncrementMeshIDs(int start, int length);
void Update();
void MarkFailure(Error status);
+25 -29
View File
@@ -275,60 +275,56 @@ Curvature Manifold::GetCurvature() const {
}
/**
* Gets the relationship to the previous mesh, for the purpose of assinging
* Gets the relationship to the previous meshes, for the purpose of assinging
* properties like texture coordinates. The triBary vector is the same length as
* Mesh.triVerts and .meshID indicates the mesh instance and .tri is that mesh's
* triangle index to which these barycentric coordinates refer. BaryRef.vertBary
* gives an index for each vertex into the barycentric vector if that index is
* >= 0, indicating it is a new vertex. If the index is < 0, this indicates it
* is an original vertex, the index + 3 vert of the referenced triangle.
* Mesh.triVerts: BaryRef.originalID indicates the source mesh and BaryRef.tri
* is that mesh's triangle index to which these barycentric coordinates refer.
* BaryRef.vertBary gives an index for each vertex into the barycentric vector
* if that index is >= 0, indicating it is a new vertex. If the index is < 0,
* this indicates it is an original vertex, the index + 3 vert of the referenced
* triangle.
*
* BaryRef.meshID is a unique ID to the particular instance of a given mesh. For
* instance, if you want to convert the triangle mesh to a polygon mesh, all the
* triangles from a given face will have the same .meshID and .tri values.
*/
MeshRelation Manifold::GetMeshRelation() const {
MeshRelation out;
const auto& relation = GetCsgLeafNode().GetImpl()->meshRelation_;
out.triBary.insert(out.triBary.end(), relation.triBary.begin(),
relation.triBary.end());
for (auto& bary : out.triBary) {
bary.meshID = relation.originalID.at(bary.meshID);
}
out.barycentric.insert(out.barycentric.end(), relation.barycentric.begin(),
relation.barycentric.end());
return out;
}
/**
* Returns a vector of unique meshIDs that are referenced by this manifold's
* meshRelation. If this manifold has been newly constructed then there will
* only be a single meshID, which can be associated with the input mesh for
* future reference.
* If this mesh is an original, this returns its meshID that can be referenced
* by product manifolds' MeshRelation. If this manifold is a product, this
* returns -1.
*/
std::vector<int> Manifold::GetMeshIDs() const {
std::vector<int> out;
out.reserve(GetCsgLeafNode().GetImpl()->meshRelation_.originalID.size());
for (auto& entry : GetCsgLeafNode().GetImpl()->meshRelation_.originalID) {
out.push_back(entry.second);
}
return out;
int Manifold::OriginalID() const {
return GetCsgLeafNode().GetImpl()->meshRelation_.originalID;
}
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its meshID as an original,
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
* meaning it will now be referenced by its descendents instead of the meshes it
* was built from, allowing you to differentiate the copies when applying your
* properties to the final result. Its new meshID is returned.
* properties to the final result.
*
* This function also condenses all coplanar faces in the relation, allowing
* these edges to be collapsed. If you plan to have inconsistent properties
* across these faces, meaning you want to preserve some of these edges, you
* should instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*
* @returns New Mesh
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to construct
* a new manifold.
*/
Manifold Manifold::AsOriginal() const {
auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
newImpl->InitializeNewReference();
newImpl->SimplifyTopology();
newImpl->Finish();
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
}
+6 -4
View File
@@ -107,13 +107,15 @@ struct InteriorVerts {
const int b = (i == n - 1) ? -1 : first + n - i + 1;
const int c = (j == n - 1) ? -3 : first + 1;
glm::ivec3 vertBary(c, a, b);
triBary[posTri] = {-1, tri, vertBary};
triBaryNew[posTri++] = {baryOld.meshID, baryOld.tri, vertBary};
triBary[posTri] = {-1, -1, tri, vertBary};
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID, baryOld.tri,
vertBary};
if (j < n - 1 - i) {
int d = b + 1; // d cannot be a retained vert
vertBary = {b, d, c};
triBary[posTri] = {-1, tri, vertBary};
triBaryNew[posTri++] = {baryOld.meshID, baryOld.tri, vertBary};
triBary[posTri] = {-1, -1, tri, vertBary};
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID,
baryOld.tri, vertBary};
}
if (i == 0 || j == 0 || k == 0) continue;
+6 -3
View File
@@ -208,9 +208,12 @@ void Manifold::Impl::Finish() {
ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0, logicErr,
"faceNormal size = " + std::to_string(faceNormal_.size()) +
", NumTri = " + std::to_string(NumTri()));
ASSERT(vertNormal_.size() == NumVert() || vertNormal_.size() == 0, logicErr,
"vertNormal size = " + std::to_string(vertNormal_.size()) +
", NumVert = " + std::to_string(NumVert()));
// TODO: figure out why this has a flaky failure and then enable reading
// vertNormals from a Mesh.
// ASSERT(vertNormal_.size() == NumVert() || vertNormal_.size() == 0,
// logicErr,
// "vertNormal size = " + std::to_string(vertNormal_.size()) +
// ", NumVert = " + std::to_string(NumVert()));
CalculateNormals();
collider_ = Collider(faceBox, faceMorton);
+8 -2
View File
@@ -222,8 +222,13 @@ struct Curvature {
* Mesh.
*/
struct BaryRef {
/// The original mesh triangle index.
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
/// match for two triangles, then they are coplanar and came from the same
/// face.
int meshID;
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
/// reapplying properties like UV coordinates to the output mesh.
int originalID;
/// The triangle index of the original triangle this was part of:
/// Mesh.triVerts[tri].
int tri;
@@ -446,7 +451,8 @@ inline std::ostream& operator<<(std::ostream& stream, const glm::mat4x3& mat) {
}
inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) {
return stream << "meshID: " << ref.meshID << ", tri: " << ref.tri
return stream << "meshID: " << ref.meshID
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
<< ", uvw idx: " << ref.vertBary;
}
+2 -2
View File
@@ -28,9 +28,9 @@ namespace manifold {
*/
struct Material {
/// Roughness value between 0 (shiny) and 1 (matte).
float roughness = 1;
float roughness = 0.2;
/// Metalness value, generally either 0 (dielectric) or 1 (metal).
float metalness = 0;
float metalness = 1;
/// Color (RGBA) multiplier to apply to the whole mesh (each value between 0
/// and 1).
glm::vec4 color = glm::vec4(1.0f);
+22 -22
View File
@@ -75,7 +75,7 @@ void Related(const Manifold& out, const std::vector<Mesh>& input,
Mesh output = out.GetMesh();
MeshRelation relation = out.GetMeshRelation();
for (int tri = 0; tri < out.NumTri(); ++tri) {
int meshID = relation.triBary[tri].meshID;
int meshID = relation.triBary[tri].originalID;
int meshIdx = meshID2idx.at(meshID);
ASSERT_LT(meshIdx, input.size());
const Mesh& inMesh = input[meshIdx];
@@ -99,14 +99,14 @@ void RelatedOp(const Manifold& inP, const Manifold& inQ, const Manifold& outR) {
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
std::vector<int> meshIDs = inP.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size();
int meshID = inP.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(inP.GetMesh());
meshIDs = inQ.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size();
meshID = inQ.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(inQ.GetMesh());
Related(outR, input, meshID2idx);
@@ -298,9 +298,9 @@ TEST(Manifold, Decompose) {
std::map<int, int> meshID2idx;
for (const Manifold& manifold : manifoldList) {
std::vector<int> meshIDs = manifold.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size();
int meshID = manifold.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(manifold.GetMesh());
}
@@ -563,9 +563,9 @@ TEST(Manifold, MeshRelation) {
input.push_back(ImportMesh("data/gyroidpuzzle.ply"));
Manifold gyroid(input[0]);
std::vector<int> meshIDs = gyroid.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size() - 1;
int meshID = gyroid.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size() - 1;
Related(gyroid, input, meshID2idx);
}
@@ -577,9 +577,9 @@ TEST(Manifold, MeshRelationRefine) {
input.push_back(Csaszar());
Manifold csaszar(input[0]);
std::vector<int> meshIDs = csaszar.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size() - 1;
int meshID = csaszar.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size() - 1;
Related(csaszar, input, meshID2idx);
csaszar.Refine(4);
@@ -864,14 +864,14 @@ TEST(Boolean, Gyroid) {
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
std::vector<int> meshIDs = gyroid.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size();
int meshID = gyroid.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(gyroidpuzzle);
meshIDs = gyroid2.GetMeshIDs();
EXPECT_EQ(meshIDs.size(), 1);
meshID2idx[meshIDs[0]] = input.size();
meshID = gyroid2.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(gyroidpuzzle2);
Related(result, input, meshID2idx);