|
@@ -12,38 +12,113 @@
|
|
|
// See the License for the specific language governing permissions and
|
|
|
// limitations under the License.
|
|
|
|
|
|
-#include "./impl.h"
|
|
|
+#include "impl.h"
|
|
|
|
|
|
#include <algorithm>
|
|
|
#include <atomic>
|
|
|
+#include <cstring>
|
|
|
#include <map>
|
|
|
#include <optional>
|
|
|
|
|
|
-#include "./hashtable.h"
|
|
|
-#include "./mesh_fixes.h"
|
|
|
-#include "./parallel.h"
|
|
|
-#include "./svd.h"
|
|
|
-
|
|
|
-#ifdef MANIFOLD_EXPORT
|
|
|
-#include <string.h>
|
|
|
-
|
|
|
-#include <iostream>
|
|
|
-#endif
|
|
|
+#include "csg_tree.h"
|
|
|
+#include "hashtable.h"
|
|
|
+#include "manifold/optional_assert.h"
|
|
|
+#include "mesh_fixes.h"
|
|
|
+#include "parallel.h"
|
|
|
+#include "shared.h"
|
|
|
+#include "svd.h"
|
|
|
|
|
|
namespace {
|
|
|
using namespace manifold;
|
|
|
|
|
|
-constexpr uint64_t kRemove = std::numeric_limits<uint64_t>::max();
|
|
|
-
|
|
|
-void AtomicAddVec3(vec3& target, const vec3& add) {
|
|
|
- for (int i : {0, 1, 2}) {
|
|
|
- std::atomic<double>& tar =
|
|
|
- reinterpret_cast<std::atomic<double>&>(target[i]);
|
|
|
- double old_val = tar.load(std::memory_order_relaxed);
|
|
|
- while (!tar.compare_exchange_weak(old_val, old_val + add[i],
|
|
|
- std::memory_order_relaxed)) {
|
|
|
+/**
|
|
|
+ * Returns arc cosine of 𝑥.
|
|
|
+ *
|
|
|
+ * @return value in range [0,M_PI]
|
|
|
+ * @return NAN if 𝑥 ∈ {NAN,+INFINITY,-INFINITY}
|
|
|
+ * @return NAN if 𝑥 ∉ [-1,1]
|
|
|
+ */
|
|
|
+double sun_acos(double x) {
|
|
|
+ /*
|
|
|
+ * Origin of acos function: FreeBSD /usr/src/lib/msun/src/e_acos.c
|
|
|
+ * Changed the use of union to memcpy to avoid undefined behavior.
|
|
|
+ * ====================================================
|
|
|
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
|
|
|
+ *
|
|
|
+ * Developed at SunSoft, a Sun Microsystems, Inc. business.
|
|
|
+ * Permission to use, copy, modify, and distribute this
|
|
|
+ * software is freely granted, provided that this notice
|
|
|
+ * is preserved.
|
|
|
+ * ====================================================
|
|
|
+ */
|
|
|
+ constexpr double pio2_hi =
|
|
|
+ 1.57079632679489655800e+00; /* 0x3FF921FB, 0x54442D18 */
|
|
|
+ constexpr double pio2_lo =
|
|
|
+ 6.12323399573676603587e-17; /* 0x3C91A626, 0x33145C07 */
|
|
|
+ constexpr double pS0 =
|
|
|
+ 1.66666666666666657415e-01; /* 0x3FC55555, 0x55555555 */
|
|
|
+ constexpr double pS1 =
|
|
|
+ -3.25565818622400915405e-01; /* 0xBFD4D612, 0x03EB6F7D */
|
|
|
+ constexpr double pS2 =
|
|
|
+ 2.01212532134862925881e-01; /* 0x3FC9C155, 0x0E884455 */
|
|
|
+ constexpr double pS3 =
|
|
|
+ -4.00555345006794114027e-02; /* 0xBFA48228, 0xB5688F3B */
|
|
|
+ constexpr double pS4 =
|
|
|
+ 7.91534994289814532176e-04; /* 0x3F49EFE0, 0x7501B288 */
|
|
|
+ constexpr double pS5 =
|
|
|
+ 3.47933107596021167570e-05; /* 0x3F023DE1, 0x0DFDF709 */
|
|
|
+ constexpr double qS1 =
|
|
|
+ -2.40339491173441421878e+00; /* 0xC0033A27, 0x1C8A2D4B */
|
|
|
+ constexpr double qS2 =
|
|
|
+ 2.02094576023350569471e+00; /* 0x40002AE5, 0x9C598AC8 */
|
|
|
+ constexpr double qS3 =
|
|
|
+ -6.88283971605453293030e-01; /* 0xBFE6066C, 0x1B8D0159 */
|
|
|
+ constexpr double qS4 =
|
|
|
+ 7.70381505559019352791e-02; /* 0x3FB3B8C5, 0xB12E9282 */
|
|
|
+ auto R = [=](double z) {
|
|
|
+ double p, q;
|
|
|
+ p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5)))));
|
|
|
+ q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4)));
|
|
|
+ return p / q;
|
|
|
+ };
|
|
|
+ double z, w, s, c, df;
|
|
|
+ uint64_t xx;
|
|
|
+ uint32_t hx, lx, ix;
|
|
|
+ memcpy(&xx, &x, sizeof(xx));
|
|
|
+ hx = xx >> 32;
|
|
|
+ ix = hx & 0x7fffffff;
|
|
|
+ /* |x| >= 1 or nan */
|
|
|
+ if (ix >= 0x3ff00000) {
|
|
|
+ lx = xx;
|
|
|
+ if (((ix - 0x3ff00000) | lx) == 0) {
|
|
|
+ /* acos(1)=0, acos(-1)=pi */
|
|
|
+ if (hx >> 31) return 2 * pio2_hi + 0x1p-120f;
|
|
|
+ return 0;
|
|
|
}
|
|
|
+ return 0 / (x - x);
|
|
|
}
|
|
|
+ /* |x| < 0.5 */
|
|
|
+ if (ix < 0x3fe00000) {
|
|
|
+ if (ix <= 0x3c600000) /* |x| < 2**-57 */
|
|
|
+ return pio2_hi + 0x1p-120f;
|
|
|
+ return pio2_hi - (x - (pio2_lo - x * R(x * x)));
|
|
|
+ }
|
|
|
+ /* x < -0.5 */
|
|
|
+ if (hx >> 31) {
|
|
|
+ z = (1.0 + x) * 0.5;
|
|
|
+ s = sqrt(z);
|
|
|
+ w = R(z) * s - pio2_lo;
|
|
|
+ return 2 * (pio2_hi - (s + w));
|
|
|
+ }
|
|
|
+ /* x > 0.5 */
|
|
|
+ z = (1.0 - x) * 0.5;
|
|
|
+ s = sqrt(z);
|
|
|
+ memcpy(&xx, &s, sizeof(xx));
|
|
|
+ xx &= 0xffffffff00000000;
|
|
|
+ memcpy(&df, &xx, sizeof(xx));
|
|
|
+ c = (z - df * df) / (s + df);
|
|
|
+ w = R(z) * s + c;
|
|
|
+ return 2 * (df + w);
|
|
|
}
|
|
|
|
|
|
struct Transform4x3 {
|
|
@@ -52,143 +127,12 @@ struct Transform4x3 {
|
|
|
vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); }
|
|
|
};
|
|
|
|
|
|
-template <bool calculateTriNormal>
|
|
|
-struct AssignNormals {
|
|
|
- VecView<vec3> faceNormal;
|
|
|
- VecView<vec3> vertNormal;
|
|
|
- VecView<const vec3> vertPos;
|
|
|
- VecView<const Halfedge> halfedges;
|
|
|
-
|
|
|
- void operator()(const int face) {
|
|
|
- vec3& triNormal = faceNormal[face];
|
|
|
-
|
|
|
- ivec3 triVerts;
|
|
|
- for (int i : {0, 1, 2}) triVerts[i] = halfedges[3 * face + i].startVert;
|
|
|
-
|
|
|
- vec3 edge[3];
|
|
|
- for (int i : {0, 1, 2}) {
|
|
|
- const int j = (i + 1) % 3;
|
|
|
- edge[i] = la::normalize(vertPos[triVerts[j]] - vertPos[triVerts[i]]);
|
|
|
- }
|
|
|
-
|
|
|
- if (calculateTriNormal) {
|
|
|
- triNormal = la::normalize(la::cross(edge[0], edge[1]));
|
|
|
- if (std::isnan(triNormal.x)) triNormal = vec3(0, 0, 1);
|
|
|
- }
|
|
|
-
|
|
|
- // corner angles
|
|
|
- vec3 phi;
|
|
|
- double dot = -la::dot(edge[2], edge[0]);
|
|
|
- phi[0] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot));
|
|
|
- dot = -la::dot(edge[0], edge[1]);
|
|
|
- phi[1] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot));
|
|
|
- phi[2] = kPi - phi[0] - phi[1];
|
|
|
-
|
|
|
- // assign weighted sum
|
|
|
- for (int i : {0, 1, 2}) {
|
|
|
- AtomicAddVec3(vertNormal[triVerts[i]], phi[i] * triNormal);
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
struct UpdateMeshID {
|
|
|
const HashTableD<uint32_t> meshIDold2new;
|
|
|
|
|
|
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
|
|
|
};
|
|
|
|
|
|
-struct CoplanarEdge {
|
|
|
- VecView<std::pair<int, int>> face2face;
|
|
|
- VecView<double> triArea;
|
|
|
- VecView<const Halfedge> halfedge;
|
|
|
- VecView<const vec3> vertPos;
|
|
|
- VecView<const TriRef> triRef;
|
|
|
- VecView<const ivec3> triProp;
|
|
|
- const int numProp;
|
|
|
- const double epsilon;
|
|
|
- const double tolerance;
|
|
|
-
|
|
|
- void operator()(const int edgeIdx) {
|
|
|
- const Halfedge edge = halfedge[edgeIdx];
|
|
|
- const Halfedge pair = halfedge[edge.pairedHalfedge];
|
|
|
- const int edgeFace = edgeIdx / 3;
|
|
|
- const int pairFace = edge.pairedHalfedge / 3;
|
|
|
-
|
|
|
- if (triRef[edgeFace].meshID != triRef[pairFace].meshID) return;
|
|
|
-
|
|
|
- const vec3 base = vertPos[edge.startVert];
|
|
|
- const int baseNum = edgeIdx - 3 * edgeFace;
|
|
|
- const int jointNum = edge.pairedHalfedge - 3 * pairFace;
|
|
|
-
|
|
|
- if (numProp > 0) {
|
|
|
- if (triProp[edgeFace][baseNum] != triProp[pairFace][Next3(jointNum)] ||
|
|
|
- triProp[edgeFace][Next3(baseNum)] != triProp[pairFace][jointNum])
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!edge.IsForward()) return;
|
|
|
-
|
|
|
- const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
|
|
- const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
|
|
- const vec3 jointVec = vertPos[pair.startVert] - base;
|
|
|
- const vec3 edgeVec =
|
|
|
- vertPos[halfedge[3 * edgeFace + edgeNum].startVert] - base;
|
|
|
- const vec3 pairVec =
|
|
|
- vertPos[halfedge[3 * pairFace + pairNum].startVert] - base;
|
|
|
-
|
|
|
- const double length = std::max(la::length(jointVec), la::length(edgeVec));
|
|
|
- const double lengthPair =
|
|
|
- std::max(la::length(jointVec), la::length(pairVec));
|
|
|
- vec3 normal = la::cross(jointVec, edgeVec);
|
|
|
- const double area = la::length(normal);
|
|
|
- const double areaPair = la::length(la::cross(pairVec, jointVec));
|
|
|
-
|
|
|
- // make sure we only write this once
|
|
|
- if (edgeIdx % 3 == 0) triArea[edgeFace] = area;
|
|
|
- // Don't link degenerate triangles
|
|
|
- if (area < length * epsilon || areaPair < lengthPair * epsilon) return;
|
|
|
-
|
|
|
- const double volume = std::abs(la::dot(normal, pairVec));
|
|
|
- // Only operate on coplanar triangles
|
|
|
- if (volume > std::max(area, areaPair) * tolerance) return;
|
|
|
-
|
|
|
- face2face[edgeIdx] = std::make_pair(edgeFace, pairFace);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-struct CheckCoplanarity {
|
|
|
- VecView<int> comp2tri;
|
|
|
- VecView<const Halfedge> halfedge;
|
|
|
- VecView<const vec3> vertPos;
|
|
|
- std::vector<int>* components;
|
|
|
- const double tolerance;
|
|
|
-
|
|
|
- void operator()(int tri) {
|
|
|
- const int component = (*components)[tri];
|
|
|
- const int referenceTri =
|
|
|
- reinterpret_cast<std::atomic<int>*>(&comp2tri[component])
|
|
|
- ->load(std::memory_order_relaxed);
|
|
|
- if (referenceTri < 0 || referenceTri == tri) return;
|
|
|
-
|
|
|
- const vec3 origin = vertPos[halfedge[3 * referenceTri].startVert];
|
|
|
- const vec3 normal = la::normalize(
|
|
|
- la::cross(vertPos[halfedge[3 * referenceTri + 1].startVert] - origin,
|
|
|
- vertPos[halfedge[3 * referenceTri + 2].startVert] - origin));
|
|
|
-
|
|
|
- for (const int i : {0, 1, 2}) {
|
|
|
- const vec3 vert = vertPos[halfedge[3 * tri + i].startVert];
|
|
|
- // If any component vertex is not coplanar with the component's reference
|
|
|
- // triangle, unmark the entire component so that none of its triangles are
|
|
|
- // marked coplanar.
|
|
|
- if (std::abs(la::dot(normal, vert - origin)) > tolerance) {
|
|
|
- reinterpret_cast<std::atomic<int>*>(&comp2tri[component])
|
|
|
- ->store(-1, std::memory_order_relaxed);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
int GetLabels(std::vector<int>& components,
|
|
|
const Vec<std::pair<int, int>>& edges, int numNodes) {
|
|
|
UnionFind<> uf(numNodes);
|
|
@@ -199,19 +143,6 @@ int GetLabels(std::vector<int>& components,
|
|
|
|
|
|
return uf.connectedComponents(components);
|
|
|
}
|
|
|
-
|
|
|
-void DedupePropVerts(manifold::Vec<ivec3>& triProp,
|
|
|
- const Vec<std::pair<int, int>>& vert2vert,
|
|
|
- size_t numPropVert) {
|
|
|
- ZoneScoped;
|
|
|
- std::vector<int> vertLabels;
|
|
|
- const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert);
|
|
|
-
|
|
|
- std::vector<int> label2vert(numLabels);
|
|
|
- for (size_t v = 0; v < numPropVert; ++v) label2vert[vertLabels[v]] = v;
|
|
|
- for (auto& prop : triProp)
|
|
|
- for (int i : {0, 1, 2}) prop[i] = label2vert[vertLabels[prop[i]]];
|
|
|
-}
|
|
|
} // namespace
|
|
|
|
|
|
namespace manifold {
|
|
@@ -271,7 +202,7 @@ Manifold::Impl::Impl(Shape shape, const mat3x4 m) {
|
|
|
CreateHalfedges(triVerts);
|
|
|
Finish();
|
|
|
InitializeOriginal();
|
|
|
- CreateFaces();
|
|
|
+ MarkCoplanar();
|
|
|
}
|
|
|
|
|
|
void Manifold::Impl::RemoveUnreferencedVerts() {
|
|
@@ -297,124 +228,165 @@ void Manifold::Impl::InitializeOriginal(bool keepFaceID) {
|
|
|
const int meshID = ReserveIDs(1);
|
|
|
meshRelation_.originalID = meshID;
|
|
|
auto& triRef = meshRelation_.triRef;
|
|
|
- triRef.resize(NumTri());
|
|
|
+ triRef.resize_nofill(NumTri());
|
|
|
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
|
|
|
[meshID, keepFaceID, &triRef](const int tri) {
|
|
|
- triRef[tri] = {meshID, meshID, tri,
|
|
|
- keepFaceID ? triRef[tri].faceID : tri};
|
|
|
+ triRef[tri] = {meshID, meshID, -1,
|
|
|
+ keepFaceID ? triRef[tri].coplanarID : tri};
|
|
|
});
|
|
|
meshRelation_.meshIDtransform.clear();
|
|
|
meshRelation_.meshIDtransform[meshID] = {meshID};
|
|
|
}
|
|
|
|
|
|
-void Manifold::Impl::CreateFaces() {
|
|
|
+void Manifold::Impl::MarkCoplanar() {
|
|
|
ZoneScoped;
|
|
|
- Vec<std::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
|
|
- Vec<std::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
|
|
- Vec<double> triArea(NumTri());
|
|
|
+ const int numTri = NumTri();
|
|
|
+ struct TriPriority {
|
|
|
+ double area2;
|
|
|
+ int tri;
|
|
|
+ };
|
|
|
+ Vec<TriPriority> triPriority(numTri);
|
|
|
+ for_each_n(autoPolicy(numTri), countAt(0), numTri,
|
|
|
+ [&triPriority, this](int tri) {
|
|
|
+ meshRelation_.triRef[tri].coplanarID = -1;
|
|
|
+ if (halfedge_[3 * tri].startVert < 0) {
|
|
|
+ triPriority[tri] = {0, tri};
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
|
|
|
+ triPriority[tri] = {
|
|
|
+ length2(cross(vertPos_[halfedge_[3 * tri].endVert] - v,
|
|
|
+ vertPos_[halfedge_[3 * tri + 1].endVert] - v)),
|
|
|
+ tri};
|
|
|
+ });
|
|
|
|
|
|
- const size_t numProp = NumProp();
|
|
|
- if (numProp > 0) {
|
|
|
- for_each_n(
|
|
|
- autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(),
|
|
|
- [&vert2vert, numProp, this](const int edgeIdx) {
|
|
|
- const Halfedge edge = halfedge_[edgeIdx];
|
|
|
- const Halfedge pair = halfedge_[edge.pairedHalfedge];
|
|
|
- const int edgeFace = edgeIdx / 3;
|
|
|
- const int pairFace = edge.pairedHalfedge / 3;
|
|
|
-
|
|
|
- if (meshRelation_.triRef[edgeFace].meshID !=
|
|
|
- meshRelation_.triRef[pairFace].meshID)
|
|
|
- return;
|
|
|
-
|
|
|
- const int baseNum = edgeIdx - 3 * edgeFace;
|
|
|
- const int jointNum = edge.pairedHalfedge - 3 * pairFace;
|
|
|
-
|
|
|
- const int prop0 = meshRelation_.triProperties[edgeFace][baseNum];
|
|
|
- const int prop1 =
|
|
|
- meshRelation_
|
|
|
- .triProperties[pairFace][jointNum == 2 ? 0 : jointNum + 1];
|
|
|
- if (prop0 == prop1) return;
|
|
|
-
|
|
|
- bool propEqual = true;
|
|
|
- for (size_t p = 0; p < numProp; ++p) {
|
|
|
- if (meshRelation_.properties[numProp * prop0 + p] !=
|
|
|
- meshRelation_.properties[numProp * prop1 + p]) {
|
|
|
- propEqual = false;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (propEqual) {
|
|
|
- vert2vert[edgeIdx] = std::make_pair(prop0, prop1);
|
|
|
- }
|
|
|
- });
|
|
|
- DedupePropVerts(meshRelation_.triProperties, vert2vert, NumPropVert());
|
|
|
+ stable_sort(triPriority.begin(), triPriority.end(),
|
|
|
+ [](auto a, auto b) { return a.area2 > b.area2; });
|
|
|
+
|
|
|
+ Vec<int> interiorHalfedges;
|
|
|
+ for (const auto tp : triPriority) {
|
|
|
+ if (meshRelation_.triRef[tp.tri].coplanarID >= 0) continue;
|
|
|
+
|
|
|
+ meshRelation_.triRef[tp.tri].coplanarID = tp.tri;
|
|
|
+ if (halfedge_[3 * tp.tri].startVert < 0) continue;
|
|
|
+ const vec3 base = vertPos_[halfedge_[3 * tp.tri].startVert];
|
|
|
+ const vec3 normal = faceNormal_[tp.tri];
|
|
|
+ interiorHalfedges.resize(3);
|
|
|
+ interiorHalfedges[0] = 3 * tp.tri;
|
|
|
+ interiorHalfedges[1] = 3 * tp.tri + 1;
|
|
|
+ interiorHalfedges[2] = 3 * tp.tri + 2;
|
|
|
+ while (!interiorHalfedges.empty()) {
|
|
|
+ const int h =
|
|
|
+ NextHalfedge(halfedge_[interiorHalfedges.back()].pairedHalfedge);
|
|
|
+ interiorHalfedges.pop_back();
|
|
|
+ if (meshRelation_.triRef[h / 3].coplanarID >= 0) continue;
|
|
|
+
|
|
|
+ const vec3 v = vertPos_[halfedge_[h].endVert];
|
|
|
+ if (std::abs(dot(v - base, normal)) < tolerance_) {
|
|
|
+ meshRelation_.triRef[h / 3].coplanarID = tp.tri;
|
|
|
+
|
|
|
+ if (interiorHalfedges.empty() ||
|
|
|
+ h != halfedge_[interiorHalfedges.back()].pairedHalfedge) {
|
|
|
+ interiorHalfedges.push_back(h);
|
|
|
+ } else {
|
|
|
+ interiorHalfedges.pop_back();
|
|
|
+ }
|
|
|
+ const int hNext = NextHalfedge(h);
|
|
|
+ interiorHalfedges.push_back(hNext);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
+/**
|
|
|
+ * Dereference duplicate property vertices if they are exactly floating-point
|
|
|
+ * equal. These unreferenced properties are then removed by CompactProps.
|
|
|
+ */
|
|
|
+void Manifold::Impl::DedupePropVerts() {
|
|
|
+ ZoneScoped;
|
|
|
+ const size_t numProp = NumProp();
|
|
|
+ if (numProp == 0) return;
|
|
|
+
|
|
|
+ Vec<std::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
|
|
for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(),
|
|
|
- CoplanarEdge({face2face, triArea, halfedge_, vertPos_,
|
|
|
- meshRelation_.triRef, meshRelation_.triProperties,
|
|
|
- meshRelation_.numProp, epsilon_, tolerance_}));
|
|
|
-
|
|
|
- std::vector<int> components;
|
|
|
- const int numComponent = GetLabels(components, face2face, NumTri());
|
|
|
-
|
|
|
- Vec<int> comp2tri(numComponent, -1);
|
|
|
- for (size_t tri = 0; tri < NumTri(); ++tri) {
|
|
|
- const int comp = components[tri];
|
|
|
- const int current = comp2tri[comp];
|
|
|
- if (current < 0 || triArea[tri] > triArea[current]) {
|
|
|
- comp2tri[comp] = tri;
|
|
|
- triArea[comp] = triArea[tri];
|
|
|
- }
|
|
|
- }
|
|
|
+ [&vert2vert, numProp, this](const int edgeIdx) {
|
|
|
+ const Halfedge edge = halfedge_[edgeIdx];
|
|
|
+ const int edgeFace = edgeIdx / 3;
|
|
|
+ const int pairFace = edge.pairedHalfedge / 3;
|
|
|
+
|
|
|
+ if (meshRelation_.triRef[edgeFace].meshID !=
|
|
|
+ meshRelation_.triRef[pairFace].meshID)
|
|
|
+ return;
|
|
|
+
|
|
|
+ const int prop0 = halfedge_[edgeIdx].propVert;
|
|
|
+ const int prop1 =
|
|
|
+ halfedge_[NextHalfedge(edge.pairedHalfedge)].propVert;
|
|
|
+ bool propEqual = true;
|
|
|
+ for (size_t p = 0; p < numProp; ++p) {
|
|
|
+ if (properties_[numProp * prop0 + p] !=
|
|
|
+ properties_[numProp * prop1 + p]) {
|
|
|
+ propEqual = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (propEqual) {
|
|
|
+ vert2vert[edgeIdx] = std::make_pair(prop0, prop1);
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), NumTri(),
|
|
|
- CheckCoplanarity(
|
|
|
- {comp2tri, halfedge_, vertPos_, &components, tolerance_}));
|
|
|
+ std::vector<int> vertLabels;
|
|
|
+ const size_t numPropVert = NumPropVert();
|
|
|
+ const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert);
|
|
|
|
|
|
- Vec<TriRef>& triRef = meshRelation_.triRef;
|
|
|
- for (size_t tri = 0; tri < NumTri(); ++tri) {
|
|
|
- const int referenceTri = comp2tri[components[tri]];
|
|
|
- if (referenceTri >= 0) {
|
|
|
- triRef[tri].faceID = referenceTri;
|
|
|
- }
|
|
|
- }
|
|
|
+ std::vector<int> label2vert(numLabels);
|
|
|
+ for (size_t v = 0; v < numPropVert; ++v) label2vert[vertLabels[v]] = v;
|
|
|
+ for (Halfedge& edge : halfedge_)
|
|
|
+ edge.propVert = label2vert[vertLabels[edge.propVert]];
|
|
|
}
|
|
|
|
|
|
+constexpr int kRemovedHalfedge = -2;
|
|
|
+
|
|
|
/**
|
|
|
- * Create the halfedge_ data structure from an input triVerts array like Mesh.
|
|
|
+ * Create the halfedge_ data structure from a list of triangles. If the optional
|
|
|
+ * prop2vert array is missing, it's assumed these triangles are are pointing to
|
|
|
+ * both vert and propVert indices. If prop2vert is present, the triangles are
|
|
|
+ * assumed to be pointing to propVert indices only. The prop2vert array is used
|
|
|
+ * to map the propVert indices to vert indices.
|
|
|
*/
|
|
|
-void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
|
|
|
+void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triProp,
|
|
|
+ const Vec<ivec3>& triVert) {
|
|
|
ZoneScoped;
|
|
|
- const size_t numTri = triVerts.size();
|
|
|
+ const size_t numTri = triProp.size();
|
|
|
const int numHalfedge = 3 * numTri;
|
|
|
// drop the old value first to avoid copy
|
|
|
- halfedge_.resize(0);
|
|
|
- halfedge_.resize(numHalfedge);
|
|
|
+ halfedge_.clear(true);
|
|
|
+ halfedge_.resize_nofill(numHalfedge);
|
|
|
Vec<uint64_t> edge(numHalfedge);
|
|
|
Vec<int> ids(numHalfedge);
|
|
|
auto policy = autoPolicy(numTri, 1e5);
|
|
|
sequence(ids.begin(), ids.end());
|
|
|
for_each_n(policy, countAt(0), numTri,
|
|
|
- [this, &edge, &triVerts](const int tri) {
|
|
|
- const ivec3& verts = triVerts[tri];
|
|
|
+ [this, &edge, &triProp, &triVert](const int tri) {
|
|
|
+ const ivec3& props = triProp[tri];
|
|
|
for (const int i : {0, 1, 2}) {
|
|
|
const int j = (i + 1) % 3;
|
|
|
const int e = 3 * tri + i;
|
|
|
- halfedge_[e] = {verts[i], verts[j], -1};
|
|
|
+ const int v0 = triVert.empty() ? props[i] : triVert[tri][i];
|
|
|
+ const int v1 = triVert.empty() ? props[j] : triVert[tri][j];
|
|
|
+ DEBUG_ASSERT(v0 != v1, logicErr, "topological degeneracy");
|
|
|
+ halfedge_[e] = {v0, v1, -1, props[i]};
|
|
|
// Sort the forward halfedges in front of the backward ones
|
|
|
// by setting the highest-order bit.
|
|
|
- edge[e] = uint64_t(verts[i] < verts[j] ? 1 : 0) << 63 |
|
|
|
- ((uint64_t)std::min(verts[i], verts[j])) << 32 |
|
|
|
- std::max(verts[i], verts[j]);
|
|
|
+ edge[e] = uint64_t(v0 < v1 ? 1 : 0) << 63 |
|
|
|
+ ((uint64_t)std::min(v0, v1)) << 32 |
|
|
|
+ std::max(v0, v1);
|
|
|
}
|
|
|
});
|
|
|
// Stable sort is required here so that halfedges from the same face are
|
|
|
// paired together (the triangles were created in face order). In some
|
|
|
// degenerate situations the triangulator can add the same internal edge in
|
|
|
// two different faces, causing this edge to not be 2-manifold. These are
|
|
|
- // fixed by duplicating verts in SimplifyTopology.
|
|
|
+ // fixed by duplicating verts in CleanupTopology.
|
|
|
stable_sort(ids.begin(), ids.end(), [&edge](const int& a, const int& b) {
|
|
|
return edge[a] < edge[b];
|
|
|
});
|
|
@@ -422,26 +394,62 @@ void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
|
|
|
// Mark opposed triangles for removal - this may strand unreferenced verts
|
|
|
// which are removed later by RemoveUnreferencedVerts() and Finish().
|
|
|
const int numEdge = numHalfedge / 2;
|
|
|
- for (int i = 0; i < numEdge; ++i) {
|
|
|
+
|
|
|
+ const auto body = [&](int i, int consecutiveStart, int segmentEnd) {
|
|
|
const int pair0 = ids[i];
|
|
|
- Halfedge h0 = halfedge_[pair0];
|
|
|
- int k = i + numEdge;
|
|
|
+ Halfedge& h0 = halfedge_[pair0];
|
|
|
+ int k = consecutiveStart + numEdge;
|
|
|
while (1) {
|
|
|
const int pair1 = ids[k];
|
|
|
- Halfedge h1 = halfedge_[pair1];
|
|
|
+ Halfedge& h1 = halfedge_[pair1];
|
|
|
if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break;
|
|
|
if (halfedge_[NextHalfedge(pair0)].endVert ==
|
|
|
halfedge_[NextHalfedge(pair1)].endVert) {
|
|
|
- h0 = {-1, -1, -1};
|
|
|
- h1 = {-1, -1, -1};
|
|
|
+ h0.pairedHalfedge = h1.pairedHalfedge = kRemovedHalfedge;
|
|
|
// Reorder so that remaining edges pair up
|
|
|
if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]);
|
|
|
break;
|
|
|
}
|
|
|
++k;
|
|
|
- if (k >= numHalfedge) break;
|
|
|
+ if (k >= segmentEnd + numEdge) break;
|
|
|
}
|
|
|
+ if (i + 1 == segmentEnd) return consecutiveStart;
|
|
|
+ Halfedge& h1 = halfedge_[ids[i + 1]];
|
|
|
+ if (h0.startVert == h1.startVert && h0.endVert == h1.endVert)
|
|
|
+ return consecutiveStart;
|
|
|
+ return i + 1;
|
|
|
+ };
|
|
|
+
|
|
|
+#if MANIFOLD_PAR == 1
|
|
|
+ Vec<std::pair<int, int>> ranges;
|
|
|
+ const int increment = std::min(
|
|
|
+ std::max(numEdge / tbb::this_task_arena::max_concurrency() / 2, 1024),
|
|
|
+ numEdge);
|
|
|
+ const auto duplicated = [&](int a, int b) {
|
|
|
+ const Halfedge& h0 = halfedge_[ids[a]];
|
|
|
+ const Halfedge& h1 = halfedge_[ids[b]];
|
|
|
+ return h0.startVert == h1.startVert && h0.endVert == h1.endVert;
|
|
|
+ };
|
|
|
+ int end = 0;
|
|
|
+ while (end < numEdge) {
|
|
|
+ const int start = end;
|
|
|
+ end = std::min(end + increment, numEdge);
|
|
|
+ // make sure duplicated halfedges are in the same partition
|
|
|
+ while (end < numEdge && duplicated(end - 1, end)) end++;
|
|
|
+ ranges.push_back(std::make_pair(start, end));
|
|
|
}
|
|
|
+ for_each(ExecutionPolicy::Par, ranges.begin(), ranges.end(),
|
|
|
+ [&](const std::pair<int, int>& range) {
|
|
|
+ const auto [start, end] = range;
|
|
|
+ int consecutiveStart = start;
|
|
|
+ for (int i = start; i < end; ++i)
|
|
|
+ consecutiveStart = body(i, consecutiveStart, end);
|
|
|
+ });
|
|
|
+#else
|
|
|
+ int consecutiveStart = 0;
|
|
|
+ for (int i = 0; i < numEdge; ++i)
|
|
|
+ consecutiveStart = body(i, consecutiveStart, numEdge);
|
|
|
+#endif
|
|
|
|
|
|
// Once sorted, the first half of the range is the forward halfedges, which
|
|
|
// correspond to their backward pair at the same offset in the second half
|
|
@@ -449,9 +457,11 @@ void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
|
|
|
for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) {
|
|
|
const int pair0 = ids[i];
|
|
|
const int pair1 = ids[i + numEdge];
|
|
|
- if (halfedge_[pair0].startVert >= 0) {
|
|
|
+ if (halfedge_[pair0].pairedHalfedge != kRemovedHalfedge) {
|
|
|
halfedge_[pair0].pairedHalfedge = pair1;
|
|
|
halfedge_[pair1].pairedHalfedge = pair0;
|
|
|
+ } else {
|
|
|
+ halfedge_[pair0] = halfedge_[pair1] = {-1, -1, -1};
|
|
|
}
|
|
|
});
|
|
|
}
|
|
@@ -468,13 +478,13 @@ void Manifold::Impl::Update() {
|
|
|
collider_.UpdateBoxes(faceBox);
|
|
|
}
|
|
|
|
|
|
-void Manifold::Impl::MarkFailure(Error status) {
|
|
|
+void Manifold::Impl::MakeEmpty(Error status) {
|
|
|
bBox_ = Box();
|
|
|
- vertPos_.resize(0);
|
|
|
- halfedge_.resize(0);
|
|
|
- vertNormal_.resize(0);
|
|
|
- faceNormal_.resize(0);
|
|
|
- halfedgeTangent_.resize(0);
|
|
|
+ vertPos_.clear();
|
|
|
+ halfedge_.clear();
|
|
|
+ vertNormal_.clear();
|
|
|
+ faceNormal_.clear();
|
|
|
+ halfedgeTangent_.clear();
|
|
|
meshRelation_ = MeshRelationD();
|
|
|
status_ = status;
|
|
|
}
|
|
@@ -489,15 +499,14 @@ void Manifold::Impl::WarpBatch(std::function<void(VecView<vec3>)> warpFunc) {
|
|
|
warpFunc(vertPos_.view());
|
|
|
CalculateBBox();
|
|
|
if (!IsFinite()) {
|
|
|
- MarkFailure(Error::NonFiniteVertex);
|
|
|
+ MakeEmpty(Error::NonFiniteVertex);
|
|
|
return;
|
|
|
}
|
|
|
Update();
|
|
|
- faceNormal_.resize(0); // force recalculation of triNormal
|
|
|
- CalculateNormals();
|
|
|
+ faceNormal_.clear(); // force recalculation of triNormal
|
|
|
SetEpsilon();
|
|
|
Finish();
|
|
|
- CreateFaces();
|
|
|
+ MarkCoplanar();
|
|
|
meshRelation_.originalID = -1;
|
|
|
}
|
|
|
|
|
@@ -511,13 +520,15 @@ Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
|
|
|
return result;
|
|
|
}
|
|
|
if (!all(la::isfinite(transform_))) {
|
|
|
- result.MarkFailure(Error::NonFiniteVertex);
|
|
|
+ result.MakeEmpty(Error::NonFiniteVertex);
|
|
|
return result;
|
|
|
}
|
|
|
result.collider_ = collider_;
|
|
|
result.meshRelation_ = meshRelation_;
|
|
|
result.epsilon_ = epsilon_;
|
|
|
result.tolerance_ = tolerance_;
|
|
|
+ result.numProp_ = numProp_;
|
|
|
+ result.properties_ = properties_;
|
|
|
result.bBox_ = bBox_;
|
|
|
result.halfedge_ = halfedge_;
|
|
|
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
|
@@ -592,23 +603,73 @@ void Manifold::Impl::SetEpsilon(double minEpsilon, bool useSingle) {
|
|
|
void Manifold::Impl::CalculateNormals() {
|
|
|
ZoneScoped;
|
|
|
vertNormal_.resize(NumVert());
|
|
|
- auto policy = autoPolicy(NumTri(), 1e4);
|
|
|
- fill(vertNormal_.begin(), vertNormal_.end(), vec3(0.0));
|
|
|
- bool calculateTriNormal = false;
|
|
|
+ auto policy = autoPolicy(NumTri());
|
|
|
+
|
|
|
+ std::vector<std::atomic<int>> vertHalfedgeMap(NumVert());
|
|
|
+ for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) {
|
|
|
+ vertHalfedgeMap[vert] = std::numeric_limits<int>::max();
|
|
|
+ });
|
|
|
+
|
|
|
+ auto atomicMin = [&vertHalfedgeMap](int value, int vert) {
|
|
|
+ if (vert < 0) return;
|
|
|
+ int old = std::numeric_limits<int>::max();
|
|
|
+ while (!vertHalfedgeMap[vert].compare_exchange_strong(old, value))
|
|
|
+ if (old < value) break;
|
|
|
+ };
|
|
|
if (faceNormal_.size() != NumTri()) {
|
|
|
faceNormal_.resize(NumTri());
|
|
|
- calculateTriNormal = true;
|
|
|
+ for_each_n(policy, countAt(0), NumTri(), [&](const int face) {
|
|
|
+ vec3& triNormal = faceNormal_[face];
|
|
|
+ if (halfedge_[3 * face].startVert < 0) {
|
|
|
+ triNormal = vec3(0, 0, 1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ivec3 triVerts;
|
|
|
+ for (int i : {0, 1, 2}) {
|
|
|
+ int v = halfedge_[3 * face + i].startVert;
|
|
|
+ triVerts[i] = v;
|
|
|
+ atomicMin(3 * face + i, v);
|
|
|
+ }
|
|
|
+
|
|
|
+ vec3 edge[3];
|
|
|
+ for (int i : {0, 1, 2}) {
|
|
|
+ const int j = (i + 1) % 3;
|
|
|
+ edge[i] = la::normalize(vertPos_[triVerts[j]] - vertPos_[triVerts[i]]);
|
|
|
+ }
|
|
|
+ triNormal = la::normalize(la::cross(edge[0], edge[1]));
|
|
|
+ if (std::isnan(triNormal.x)) triNormal = vec3(0, 0, 1);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ for_each_n(policy, countAt(0), halfedge_.size(),
|
|
|
+ [&](const int i) { atomicMin(i, halfedge_[i].startVert); });
|
|
|
}
|
|
|
- if (calculateTriNormal)
|
|
|
- for_each_n(
|
|
|
- policy, countAt(0), NumTri(),
|
|
|
- AssignNormals<true>({faceNormal_, vertNormal_, vertPos_, halfedge_}));
|
|
|
- else
|
|
|
- for_each_n(
|
|
|
- policy, countAt(0), NumTri(),
|
|
|
- AssignNormals<false>({faceNormal_, vertNormal_, vertPos_, halfedge_}));
|
|
|
- for_each(policy, vertNormal_.begin(), vertNormal_.end(),
|
|
|
- [](vec3& v) { v = SafeNormalize(v); });
|
|
|
+
|
|
|
+ for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) {
|
|
|
+ int firstEdge = vertHalfedgeMap[vert].load();
|
|
|
+ // not referenced
|
|
|
+ if (firstEdge == std::numeric_limits<int>::max()) {
|
|
|
+ vertNormal_[vert] = vec3(0.0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ vec3 normal = vec3(0.0);
|
|
|
+ ForVert(firstEdge, [&](int edge) {
|
|
|
+ ivec3 triVerts = {halfedge_[edge].startVert, halfedge_[edge].endVert,
|
|
|
+ halfedge_[NextHalfedge(edge)].endVert};
|
|
|
+ vec3 currEdge =
|
|
|
+ la::normalize(vertPos_[triVerts[1]] - vertPos_[triVerts[0]]);
|
|
|
+ vec3 prevEdge =
|
|
|
+ la::normalize(vertPos_[triVerts[0]] - vertPos_[triVerts[2]]);
|
|
|
+
|
|
|
+ // if it is not finite, this means that the triangle is degenerate, and we
|
|
|
+ // should just exclude it from the normal calculation...
|
|
|
+ if (!la::isfinite(currEdge[0]) || !la::isfinite(prevEdge[0])) return;
|
|
|
+ double dot = -la::dot(prevEdge, currEdge);
|
|
|
+ double phi = dot >= 1 ? 0 : (dot <= -1 ? kPi : sun_acos(dot));
|
|
|
+ normal += phi * faceNormal_[edge / 3];
|
|
|
+ });
|
|
|
+ vertNormal_[vert] = SafeNormalize(normal);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -632,55 +693,13 @@ void Manifold::Impl::IncrementMeshIDs() {
|
|
|
UpdateMeshID({meshIDold2new.D()}));
|
|
|
}
|
|
|
|
|
|
+#ifdef MANIFOLD_DEBUG
|
|
|
/**
|
|
|
- * Returns a sparse array of the bounding box overlaps between the edges of
|
|
|
- * the input manifold, Q and the faces of this manifold. Returned indices only
|
|
|
- * point to forward halfedges.
|
|
|
- */
|
|
|
-SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
|
|
|
- bool inverted) const {
|
|
|
- ZoneScoped;
|
|
|
- Vec<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
|
|
|
- const size_t numEdge = edges.size();
|
|
|
- Vec<Box> QedgeBB(numEdge);
|
|
|
- const auto& vertPos = Q.vertPos_;
|
|
|
- auto policy = autoPolicy(numEdge, 1e5);
|
|
|
- for_each_n(
|
|
|
- policy, countAt(0), numEdge, [&QedgeBB, &edges, &vertPos](const int e) {
|
|
|
- QedgeBB[e] = Box(vertPos[edges[e].first], vertPos[edges[e].second]);
|
|
|
- });
|
|
|
-
|
|
|
- SparseIndices q1p2(0);
|
|
|
- if (inverted)
|
|
|
- q1p2 = collider_.Collisions<false, true>(QedgeBB.cview());
|
|
|
- else
|
|
|
- q1p2 = collider_.Collisions<false, false>(QedgeBB.cview());
|
|
|
-
|
|
|
- if (inverted)
|
|
|
- for_each(policy, countAt(0_uz), countAt(q1p2.size()),
|
|
|
- ReindexEdge<true>({edges, q1p2}));
|
|
|
- else
|
|
|
- for_each(policy, countAt(0_uz), countAt(q1p2.size()),
|
|
|
- ReindexEdge<false>({edges, q1p2}));
|
|
|
- return q1p2;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Returns a sparse array of the input vertices that project inside the XY
|
|
|
- * bounding boxes of the faces of this manifold.
|
|
|
+ * Debugging output using high precision OBJ files with specialized comments
|
|
|
*/
|
|
|
-SparseIndices Manifold::Impl::VertexCollisionsZ(VecView<const vec3> vertsIn,
|
|
|
- bool inverted) const {
|
|
|
- ZoneScoped;
|
|
|
- if (inverted)
|
|
|
- return collider_.Collisions<false, true>(vertsIn);
|
|
|
- else
|
|
|
- return collider_.Collisions<false, false>(vertsIn);
|
|
|
-}
|
|
|
-
|
|
|
-#ifdef MANIFOLD_DEBUG
|
|
|
std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) {
|
|
|
- stream << std::setprecision(17); // for double precision
|
|
|
+ stream << std::setprecision(19); // for double precision
|
|
|
+ stream << std::fixed; // for uniformity in output numbers
|
|
|
stream << "# ======= begin mesh ======" << std::endl;
|
|
|
stream << "# tolerance = " << impl.tolerance_ << std::endl;
|
|
|
stream << "# epsilon = " << impl.epsilon_ << std::endl;
|
|
@@ -699,13 +718,19 @@ std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) {
|
|
|
stream << "# ======== end mesh =======" << std::endl;
|
|
|
return stream;
|
|
|
}
|
|
|
-#endif
|
|
|
|
|
|
-#ifdef MANIFOLD_EXPORT
|
|
|
-Manifold Manifold::ImportMeshGL64(std::istream& stream) {
|
|
|
+/**
|
|
|
+ * Import a mesh from a Wavefront OBJ file that was exported with Write. This
|
|
|
+ * function is the counterpart to Write and should be used with it. This
|
|
|
+ * function is not guaranteed to be able to import OBJ files not written by the
|
|
|
+ * Write function.
|
|
|
+ */
|
|
|
+Manifold Manifold::ReadOBJ(std::istream& stream) {
|
|
|
+ if (!stream.good()) return Invalid();
|
|
|
+
|
|
|
MeshGL64 mesh;
|
|
|
std::optional<double> epsilon;
|
|
|
- stream.precision(17);
|
|
|
+ stream >> std::setprecision(19);
|
|
|
while (true) {
|
|
|
char c = stream.get();
|
|
|
if (stream.eof()) break;
|
|
@@ -718,7 +743,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
|
|
|
stream.get(tmp.data(), SIZE, '\n');
|
|
|
if (strncmp(tmp.data(), "tolerance", SIZE) == 0) {
|
|
|
// skip 3 letters
|
|
|
- for (int i : {0, 1, 2}) stream.get();
|
|
|
+ for (int _ : {0, 1, 2}) stream.get();
|
|
|
stream >> mesh.tolerance;
|
|
|
} else if (strncmp(tmp.data(), "epsilon =", SIZE) == 0) {
|
|
|
double tmp;
|
|
@@ -727,7 +752,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
|
|
|
} else {
|
|
|
// add it back because it is not what we want
|
|
|
int end = 0;
|
|
|
- while (tmp[end] != 0 && end < SIZE) end++;
|
|
|
+ while (end < SIZE && tmp[end] != 0) end++;
|
|
|
while (--end > -1) stream.putback(tmp[end]);
|
|
|
}
|
|
|
c = stream.get();
|
|
@@ -739,29 +764,42 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
|
|
|
break;
|
|
|
}
|
|
|
case 'v':
|
|
|
- for (int i : {0, 1, 2}) {
|
|
|
+ for (int _ : {0, 1, 2}) {
|
|
|
double x;
|
|
|
stream >> x;
|
|
|
mesh.vertProperties.push_back(x);
|
|
|
}
|
|
|
break;
|
|
|
case 'f':
|
|
|
- for (int i : {0, 1, 2}) {
|
|
|
+ for (int _ : {0, 1, 2}) {
|
|
|
uint64_t x;
|
|
|
stream >> x;
|
|
|
mesh.triVerts.push_back(x - 1);
|
|
|
}
|
|
|
break;
|
|
|
+ case '\r':
|
|
|
case '\n':
|
|
|
break;
|
|
|
default:
|
|
|
- DEBUG_ASSERT(false, userErr, "unexpected character in MeshGL64 import");
|
|
|
+ DEBUG_ASSERT(false, userErr, "unexpected character in Manifold import");
|
|
|
}
|
|
|
}
|
|
|
auto m = std::make_shared<Manifold::Impl>(mesh);
|
|
|
if (epsilon) m->SetEpsilon(*epsilon);
|
|
|
return Manifold(m);
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Export the mesh to a Wavefront OBJ file in a way that preserves the full
|
|
|
+ * 64-bit precision of the vertex positions, as well as storing metadata such as
|
|
|
+ * the tolerance and epsilon. Useful for debugging and testing. Files written
|
|
|
+ * by WriteOBJ should be read back in with ReadOBJ.
|
|
|
+ */
|
|
|
+bool Manifold::WriteOBJ(std::ostream& stream) const {
|
|
|
+ if (!stream.good()) return false;
|
|
|
+ stream << *this->GetCsgLeafNode().GetImpl();
|
|
|
+ return true;
|
|
|
+}
|
|
|
#endif
|
|
|
|
|
|
} // namespace manifold
|