#include "impl.h"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <map>
#include <optional>
#include "csg_tree.h"
#include "disjoint_sets.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;
double sun_acos(double x) {
constexpr double pio2_hi =
1.57079632679489655800e+00;
constexpr double pio2_lo =
6.12323399573676603587e-17;
constexpr double pS0 =
1.66666666666666657415e-01;
constexpr double pS1 =
-3.25565818622400915405e-01;
constexpr double pS2 =
2.01212532134862925881e-01;
constexpr double pS3 =
-4.00555345006794114027e-02;
constexpr double pS4 =
7.91534994289814532176e-04;
constexpr double pS5 =
3.47933107596021167570e-05;
constexpr double qS1 =
-2.40339491173441421878e+00;
constexpr double qS2 =
2.02094576023350569471e+00;
constexpr double qS3 =
-6.88283971605453293030e-01;
constexpr double qS4 =
7.70381505559019352791e-02;
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;
if (ix >= 0x3ff00000) {
lx = xx;
if (((ix - 0x3ff00000) | lx) == 0) {
if (hx >> 31) return 2 * pio2_hi + 0x1p-120f;
return 0;
}
return 0 / (x - x);
}
if (ix < 0x3fe00000) {
if (ix <= 0x3c600000)
return pio2_hi + 0x1p-120f;
return pio2_hi - (x - (pio2_lo - x * R(x * x)));
}
if (hx >> 31) {
z = (1.0 + x) * 0.5;
s = sqrt(z);
w = R(z) * s - pio2_lo;
return 2 * (pio2_hi - (s + w));
}
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 {
const mat3x4 transform;
vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); }
};
struct UpdateMeshID {
const HashTableD<uint32_t> meshIDold2new;
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
};
int GetLabels(std::vector<int>& components,
const Vec<std::pair<int, int>>& edges, int numNodes) {
DisjointSets uf(numNodes);
for (auto edge : edges) {
if (edge.first == -1 || edge.second == -1) continue;
uf.unite(edge.first, edge.second);
}
return uf.connectedComponents(components);
}
}
namespace manifold {
#if (MANIFOLD_PAR == 1)
tbb::task_arena gc_arena(1, 1, tbb::task_arena::priority::low);
#endif
std::atomic<uint32_t> Manifold::Impl::meshIDCounter_(1);
uint32_t Manifold::Impl::ReserveIDs(uint32_t n) {
return Manifold::Impl::meshIDCounter_.fetch_add(n, std::memory_order_relaxed);
}
Manifold::Impl::Impl(Shape shape, const mat3x4 m) {
std::vector<vec3> vertPos;
std::vector<ivec3> triVerts;
switch (shape) {
case Shape::Tetrahedron:
vertPos = {{-1.0, -1.0, 1.0},
{-1.0, 1.0, -1.0},
{1.0, -1.0, -1.0},
{1.0, 1.0, 1.0}};
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
break;
case Shape::Cube:
vertPos = {{0.0, 0.0, 0.0},
{0.0, 0.0, 1.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 1.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 1.0},
{1.0, 1.0, 0.0},
{1.0, 1.0, 1.0}};
triVerts = {{1, 0, 4}, {2, 4, 0},
{1, 3, 0}, {3, 1, 5},
{3, 2, 0}, {3, 7, 2},
{5, 4, 6}, {5, 1, 4},
{6, 4, 2}, {7, 6, 2},
{7, 3, 5}, {7, 5, 6}};
break;
case Shape::Octahedron:
vertPos = {{1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, -1.0}};
triVerts = {{0, 2, 4}, {1, 5, 3},
{2, 1, 4}, {3, 5, 0},
{1, 3, 4}, {0, 5, 2},
{3, 0, 4}, {2, 5, 1}};
break;
}
vertPos_ = vertPos;
for (auto& v : vertPos_) v = m * vec4(v, 1.0);
CreateHalfedges(triVerts);
Finish();
InitializeOriginal();
MarkCoplanar();
}
void Manifold::Impl::RemoveUnreferencedVerts() {
ZoneScoped;
const int numVert = NumVert();
Vec<int> keep(numVert, 0);
auto policy = autoPolicy(numVert, 1e5);
for_each(policy, halfedge_.cbegin(), halfedge_.cend(), [&keep](Halfedge h) {
if (h.startVert >= 0) {
reinterpret_cast<std::atomic<int>*>(&keep[h.startVert])
->store(1, std::memory_order_relaxed);
}
});
for_each_n(policy, countAt(0), numVert, [&keep, this](int v) {
if (keep[v] == 0) {
vertPos_[v] = vec3(NAN);
}
});
}
void Manifold::Impl::InitializeOriginal(bool keepFaceID) {
const int meshID = ReserveIDs(1);
meshRelation_.originalID = meshID;
auto& triRef = meshRelation_.triRef;
triRef.resize_nofill(NumTri());
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
[meshID, keepFaceID, &triRef](const int tri) {
triRef[tri] = {meshID, meshID, -1,
keepFaceID ? triRef[tri].coplanarID : tri};
});
meshRelation_.meshIDtransform.clear();
meshRelation_.meshIDtransform[meshID] = {meshID};
}
void Manifold::Impl::MarkCoplanar() {
ZoneScoped;
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};
});
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);
}
}
}
}
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(),
[&vert2vert, numProp, this](const int edgeIdx) {
const Halfedge edge = halfedge_[edgeIdx];
if (edge.pairedHalfedge < 0) return;
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);
}
});
std::vector<int> vertLabels;
const size_t numPropVert = NumPropVert();
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 (Halfedge& edge : halfedge_)
edge.propVert = label2vert[vertLabels[edge.propVert]];
}
constexpr int kRemovedHalfedge = -2;
struct HalfedgePairData {
int largeVert;
int tri;
int edgeIndex;
bool operator<(const HalfedgePairData& other) const {
return largeVert < other.largeVert ||
(largeVert == other.largeVert && tri < other.tri);
}
};
template <bool useProp, typename F>
struct PrepHalfedges {
VecView<Halfedge> halfedges;
const VecView<ivec3> triProp;
const VecView<ivec3> triVert;
F& f;
void operator()(const int tri) {
const ivec3& props = triProp[tri];
for (const int i : {0, 1, 2}) {
const int j = Next3(i);
const int k = Next3(j);
const int e = 3 * tri + i;
const int v0 = useProp ? props[i] : triVert[tri][i];
const int v1 = useProp ? props[j] : triVert[tri][j];
DEBUG_ASSERT(v0 != v1, logicErr, "topological degeneracy");
halfedges[e] = {v0, v1, -1, props[i]};
f(e, v0, v1);
}
}
};
void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triProp,
const Vec<ivec3>& triVert) {
ZoneScoped;
const size_t numTri = triProp.size();
const int numHalfedge = 3 * numTri;
halfedge_.clear(true);
halfedge_.resize_nofill(numHalfedge);
auto policy = autoPolicy(numTri, 1e5);
int vertCount = static_cast<int>(vertPos_.size());
Vec<int> ids(numHalfedge);
{
ZoneScopedN("PrepHalfedges");
if (vertCount < (1 << 18)) {
Vec<uint64_t> edge(numHalfedge);
auto setEdge = [&edge](int e, int v0, int v1) {
edge[e] = static_cast<uint64_t>(v0 < v1 ? 1 : 0) << 63 |
(static_cast<uint64_t>(std::min(v0, v1))) << 32 |
static_cast<uint64_t>(std::max(v0, v1));
};
if (triVert.empty()) {
for_each_n(policy, countAt(0), numTri,
PrepHalfedges<true, decltype(setEdge)>{halfedge_, triProp,
triVert, setEdge});
} else {
for_each_n(policy, countAt(0), numTri,
PrepHalfedges<false, decltype(setEdge)>{halfedge_, triProp,
triVert, setEdge});
}
sequence(ids.begin(), ids.end());
stable_sort(ids.begin(), ids.end(), [&edge](const int& a, const int& b) {
return edge[a] < edge[b];
});
} else {
Vec<HalfedgePairData> entries(numHalfedge);
Vec<int> offsets(vertCount * 2, 0);
auto setOffset = [&offsets, vertCount](int _e, int v0, int v1) {
const int offset = v0 > v1 ? 0 : vertCount;
AtomicAdd(offsets[std::min(v0, v1) + offset], 1);
};
if (triVert.empty()) {
for_each_n(policy, countAt(0), numTri,
PrepHalfedges<true, decltype(setOffset)>{
halfedge_, triProp, triVert, setOffset});
} else {
for_each_n(policy, countAt(0), numTri,
PrepHalfedges<false, decltype(setOffset)>{
halfedge_, triProp, triVert, setOffset});
}
exclusive_scan(offsets.begin(), offsets.end(), offsets.begin());
for_each_n(policy, countAt(0), numTri,
[this, &offsets, &entries, vertCount](const int tri) {
for (const int i : {0, 1, 2}) {
const int e = 3 * tri + i;
const int v0 = halfedge_[e].startVert;
const int v1 = halfedge_[e].endVert;
const int offset = v0 > v1 ? 0 : vertCount;
const int start = std::min(v0, v1);
const int index = AtomicAdd(offsets[start + offset], 1);
entries[index] = {std::max(v0, v1), tri, e};
}
});
for_each_n(policy, countAt(0), offsets.size(), [&](const int v) {
int start = v == 0 ? 0 : offsets[v - 1];
int end = offsets[v];
for (int i = start; i < end; ++i) ids[i] = i;
std::sort(ids.begin() + start, ids.begin() + end,
[&entries](int a, int b) { return entries[a] < entries[b]; });
for (int i = start; i < end; ++i) ids[i] = entries[ids[i]].edgeIndex;
});
}
}
const int numEdge = numHalfedge / 2;
const auto body = [&](int i, int consecutiveStart, int segmentEnd) {
const int pair0 = ids[i];
Halfedge& h0 = halfedge_[pair0];
int k = consecutiveStart + numEdge;
while (1) {
const int pair1 = ids[k];
Halfedge& h1 = halfedge_[pair1];
if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break;
if (halfedge_[NextHalfedge(pair0)].endVert ==
halfedge_[NextHalfedge(pair1)].endVert) {
h0.pairedHalfedge = h1.pairedHalfedge = kRemovedHalfedge;
if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]);
break;
}
++k;
if (k >= segmentEnd + numEdge) break;
}
if (i + 1 == segmentEnd) return consecutiveStart;
Halfedge& h1 = halfedge_[ids[i + 1]];
if (h1.startVert == h0.startVert && h1.endVert == h0.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);
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
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].pairedHalfedge != kRemovedHalfedge) {
halfedge_[pair0].pairedHalfedge = pair1;
halfedge_[pair1].pairedHalfedge = pair0;
} else {
halfedge_[pair0] = halfedge_[pair1] = {-1, -1, -1};
}
});
}
void Manifold::Impl::Update() {
CalculateBBox();
Vec<Box> faceBox;
Vec<uint32_t> faceMorton;
GetFaceBoxMorton(faceBox, faceMorton);
collider_.UpdateBoxes(faceBox);
}
void Manifold::Impl::MakeEmpty(Error status) {
bBox_ = Box();
vertPos_.clear();
halfedge_.clear();
vertNormal_.clear();
faceNormal_.clear();
halfedgeTangent_.clear();
meshRelation_ = MeshRelationD();
status_ = status;
}
void Manifold::Impl::Warp(std::function<void(vec3&)> warpFunc) {
WarpBatch([&warpFunc](VecView<vec3> vecs) {
for_each(ExecutionPolicy::Seq, vecs.begin(), vecs.end(), warpFunc);
});
}
void Manifold::Impl::WarpBatch(std::function<void(VecView<vec3>)> warpFunc) {
warpFunc(vertPos_.view());
CalculateBBox();
if (!IsFinite()) {
MakeEmpty(Error::NonFiniteVertex);
return;
}
Update();
faceNormal_.clear();
SetEpsilon();
Finish();
MarkCoplanar();
meshRelation_.originalID = -1;
}
Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
ZoneScoped;
if (transform_ == mat3x4(la::identity)) return *this;
auto policy = autoPolicy(NumVert());
Impl result;
if (status_ != Manifold::Error::NoError) {
result.status_ = status_;
return result;
}
if (!all(la::isfinite(transform_))) {
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());
result.meshRelation_.originalID = -1;
for (auto& m : result.meshRelation_.meshIDtransform) {
m.second.transform = transform_ * Mat4(m.second.transform);
}
result.vertPos_.resize(NumVert());
result.faceNormal_.resize(faceNormal_.size());
result.vertNormal_.resize(vertNormal_.size());
transform(vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
Transform4x3({transform_}));
mat3 normalTransform = NormalTransform(transform_);
transform(faceNormal_.begin(), faceNormal_.end(), result.faceNormal_.begin(),
TransformNormals({normalTransform}));
transform(vertNormal_.begin(), vertNormal_.end(), result.vertNormal_.begin(),
TransformNormals({normalTransform}));
const bool invert = la::determinant(mat3(transform_)) < 0;
if (halfedgeTangent_.size() > 0) {
for_each_n(policy, countAt(0), halfedgeTangent_.size(),
TransformTangents({result.halfedgeTangent_, 0, mat3(transform_),
invert, halfedgeTangent_, halfedge_}));
}
if (invert) {
for_each_n(policy, countAt(0), result.NumTri(),
FlipTris({result.halfedge_}));
}
if (!result.collider_.Transform(transform_)) result.Update();
result.CalculateBBox();
result.epsilon_ *= SpectralNorm(mat3(transform_));
result.SetEpsilon(result.epsilon_);
return result;
}
void Manifold::Impl::SetEpsilon(double minEpsilon, bool useSingle) {
epsilon_ = MaxEpsilon(minEpsilon, bBox_);
double minTol = epsilon_;
if (useSingle)
minTol =
std::max(minTol, std::numeric_limits<float>::epsilon() * bBox_.Scale());
tolerance_ = std::max(tolerance_, minTol);
}
void Manifold::Impl::CalculateNormals() {
ZoneScoped;
vertNormal_.resize(NumVert());
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());
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); });
}
for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) {
int firstEdge = vertHalfedgeMap[vert].load();
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 (!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);
});
}
void Manifold::Impl::IncrementMeshIDs() {
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
std::map<int, Relation> oldTransforms;
std::swap(meshRelation_.meshIDtransform, oldTransforms);
const int numMeshIDs = oldTransforms.size();
int nextMeshID = ReserveIDs(numMeshIDs);
for (const auto& pair : oldTransforms) {
meshIDold2new.D().Insert(pair.first, nextMeshID);
meshRelation_.meshIDtransform[nextMeshID++] = pair.second;
}
const size_t numTri = NumTri();
for_each_n(autoPolicy(numTri, 1e5), meshRelation_.triRef.begin(), numTri,
UpdateMeshID({meshIDold2new.D()}));
}
#ifdef MANIFOLD_DEBUG
std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) {
stream << std::setprecision(19);
stream << std::fixed;
stream << "# ======= begin mesh ======" << std::endl;
stream << "# tolerance = " << impl.tolerance_ << std::endl;
stream << "# epsilon = " << impl.epsilon_ << std::endl;
for (const vec3& v : impl.vertPos_)
stream << "v " << v.x << " " << v.y << " " << v.z << std::endl;
std::vector<ivec3> triangles;
triangles.reserve(impl.halfedge_.size() / 3);
for (size_t i = 0; i < impl.halfedge_.size(); i += 3)
triangles.emplace_back(impl.halfedge_[i].startVert + 1,
impl.halfedge_[i + 1].startVert + 1,
impl.halfedge_[i + 2].startVert + 1);
sort(triangles.begin(), triangles.end());
for (const auto& tri : triangles)
stream << "f " << tri.x << " " << tri.y << " " << tri.z << std::endl;
stream << "# ======== end mesh =======" << std::endl;
return stream;
}
Manifold Manifold::ReadOBJ(std::istream& stream) {
if (!stream.good()) return Invalid();
MeshGL64 mesh;
std::optional<double> epsilon;
stream >> std::setprecision(19);
while (true) {
char c = stream.get();
if (stream.eof()) break;
switch (c) {
case '#': {
char c = stream.get();
if (c == ' ') {
constexpr int SIZE = 10;
std::array<char, SIZE> tmp;
stream.get(tmp.data(), SIZE, '\n');
if (strncmp(tmp.data(), "tolerance", SIZE) == 0) {
for (int _ : {0, 1, 2}) stream.get();
stream >> mesh.tolerance;
} else if (strncmp(tmp.data(), "epsilon =", SIZE) == 0) {
double tmp;
stream >> tmp;
epsilon = {tmp};
} else {
int end = 0;
while (end < SIZE && tmp[end] != 0) end++;
while (--end > -1) stream.putback(tmp[end]);
}
c = stream.get();
}
while (c != '\n' && !stream.eof()) {
c = stream.get();
}
break;
}
case 'v':
for (int _ : {0, 1, 2}) {
double x;
stream >> x;
mesh.vertProperties.push_back(x);
}
break;
case 'f':
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 Manifold import");
}
}
auto m = std::make_shared<Manifold::Impl>(mesh);
if (epsilon) m->SetEpsilon(*epsilon);
return Manifold(m);
}
bool Manifold::WriteOBJ(std::ostream& stream) const {
if (!stream.good()) return false;
stream << *this->GetCsgLeafNode().GetImpl();
return true;
}
#endif
}