Path: blob/master/thirdparty/manifold/src/boolean_result.cpp
9903 views
// Copyright 2021 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include <algorithm>15#include <array>16#include <map>1718#include "boolean3.h"19#include "parallel.h"20#include "utils.h"2122#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)23#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 124#include <tbb/concurrent_map.h>25#include <tbb/parallel_for.h>2627template <typename K, typename V>28using concurrent_map = tbb::concurrent_map<K, V>;29#else30template <typename K, typename V>31// not really concurrent when tbb is disabled32using concurrent_map = std::map<K, V>;33#endif3435using namespace manifold;3637template <>38struct std::hash<std::pair<int, int>> {39size_t operator()(const std::pair<int, int>& p) const {40return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);41}42};4344namespace {4546constexpr int kParallelThreshold = 128;4748struct AbsSum {49int operator()(int a, int b) const { return abs(a) + abs(b); }50};5152struct DuplicateVerts {53VecView<vec3> vertPosR;54VecView<const int> inclusion;55VecView<const int> vertR;56VecView<const vec3> vertPosP;5758void operator()(const int vert) {59const int n = std::abs(inclusion[vert]);60for (int i = 0; i < n; ++i) {61vertPosR[vertR[vert] + i] = vertPosP[vert];62}63}64};6566template <bool atomic>67struct CountVerts {68VecView<Halfedge> halfedges;69VecView<int> count;70VecView<const int> inclusion;7172void operator()(size_t i) {73if (atomic)74AtomicAdd(count[i / 3], std::abs(inclusion[halfedges[i].startVert]));75else76count[i / 3] += std::abs(inclusion[halfedges[i].startVert]);77}78};7980template <const bool inverted, const bool atomic>81struct CountNewVerts {82VecView<int> countP;83VecView<int> countQ;84VecView<const int> i12;85const Vec<std::array<int, 2>>& pq;86VecView<const Halfedge> halfedges;8788void operator()(const int idx) {89int edgeP = pq[idx][inverted ? 1 : 0];90int faceQ = pq[idx][inverted ? 0 : 1];91int inclusion = std::abs(i12[idx]);9293if (atomic) {94AtomicAdd(countQ[faceQ], inclusion);95const Halfedge half = halfedges[edgeP];96AtomicAdd(countP[edgeP / 3], inclusion);97AtomicAdd(countP[half.pairedHalfedge / 3], inclusion);98} else {99countQ[faceQ] += inclusion;100const Halfedge half = halfedges[edgeP];101countP[edgeP / 3] += inclusion;102countP[half.pairedHalfedge / 3] += inclusion;103}104}105};106107std::tuple<Vec<int>, Vec<int>> SizeOutput(108Manifold::Impl& outR, const Manifold::Impl& inP, const Manifold::Impl& inQ,109const Vec<int>& i03, const Vec<int>& i30, const Vec<int>& i12,110const Vec<int>& i21, const Vec<std::array<int, 2>>& p1q2,111const Vec<std::array<int, 2>>& p2q1, bool invertQ) {112ZoneScoped;113Vec<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);114// note: numFaceR <= facePQ2R.size() = sidesPerFacePQ.size() + 1115116auto sidesPerFaceP = sidesPerFacePQ.view(0, inP.NumTri());117auto sidesPerFaceQ = sidesPerFacePQ.view(inP.NumTri(), inQ.NumTri());118119if (inP.halfedge_.size() >= 1e5) {120for_each(ExecutionPolicy::Par, countAt(0_uz), countAt(inP.halfedge_.size()),121CountVerts<true>({inP.halfedge_, sidesPerFaceP, i03}));122for_each(ExecutionPolicy::Par, countAt(0_uz), countAt(inQ.halfedge_.size()),123CountVerts<true>({inQ.halfedge_, sidesPerFaceQ, i30}));124} else {125for_each(ExecutionPolicy::Seq, countAt(0_uz), countAt(inP.halfedge_.size()),126CountVerts<false>({inP.halfedge_, sidesPerFaceP, i03}));127for_each(ExecutionPolicy::Seq, countAt(0_uz), countAt(inQ.halfedge_.size()),128CountVerts<false>({inQ.halfedge_, sidesPerFaceQ, i30}));129}130131if (i12.size() >= 1e5) {132for_each_n(ExecutionPolicy::Par, countAt(0), i12.size(),133CountNewVerts<false, true>(134{sidesPerFaceP, sidesPerFaceQ, i12, p1q2, inP.halfedge_}));135for_each_n(ExecutionPolicy::Par, countAt(0), i21.size(),136CountNewVerts<true, true>(137{sidesPerFaceQ, sidesPerFaceP, i21, p2q1, inQ.halfedge_}));138} else {139for_each_n(ExecutionPolicy::Seq, countAt(0), i12.size(),140CountNewVerts<false, false>(141{sidesPerFaceP, sidesPerFaceQ, i12, p1q2, inP.halfedge_}));142for_each_n(ExecutionPolicy::Seq, countAt(0), i21.size(),143CountNewVerts<true, false>(144{sidesPerFaceQ, sidesPerFaceP, i21, p2q1, inQ.halfedge_}));145}146147Vec<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);148auto keepFace = TransformIterator(sidesPerFacePQ.begin(),149[](int x) { return x > 0 ? 1 : 0; });150151inclusive_scan(keepFace, keepFace + sidesPerFacePQ.size(),152facePQ2R.begin() + 1);153int numFaceR = facePQ2R.back();154facePQ2R.resize(inP.NumTri() + inQ.NumTri());155156outR.faceNormal_.resize_nofill(numFaceR);157158Vec<size_t> tmpBuffer(outR.faceNormal_.size());159auto faceIds = TransformIterator(countAt(0_uz), [&sidesPerFacePQ](size_t i) {160if (sidesPerFacePQ[i] > 0) return i;161return std::numeric_limits<size_t>::max();162});163164auto next =165copy_if(faceIds, faceIds + inP.faceNormal_.size(), tmpBuffer.begin(),166[](size_t v) { return v != std::numeric_limits<size_t>::max(); });167168gather(tmpBuffer.begin(), next, inP.faceNormal_.begin(),169outR.faceNormal_.begin());170171auto faceIdsQ =172TransformIterator(countAt(0_uz), [&sidesPerFacePQ, &inP](size_t i) {173if (sidesPerFacePQ[i + inP.faceNormal_.size()] > 0) return i;174return std::numeric_limits<size_t>::max();175});176auto end =177copy_if(faceIdsQ, faceIdsQ + inQ.faceNormal_.size(), next,178[](size_t v) { return v != std::numeric_limits<size_t>::max(); });179180if (invertQ) {181gather(next, end,182TransformIterator(inQ.faceNormal_.begin(), Negate<vec3>()),183outR.faceNormal_.begin() + std::distance(tmpBuffer.begin(), next));184} else {185gather(next, end, inQ.faceNormal_.begin(),186outR.faceNormal_.begin() + std::distance(tmpBuffer.begin(), next));187}188189auto newEnd = remove(sidesPerFacePQ.begin(), sidesPerFacePQ.end(), 0);190Vec<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);191inclusive_scan(sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);192outR.halfedge_.resize(faceEdge.back());193194return std::make_tuple(faceEdge, facePQ2R);195}196197struct EdgePos {198double edgePos;199int vert;200int collisionId;201bool isStart;202};203204// thread sanitizer doesn't really know how to check when there are too many205// mutex206#if defined(__has_feature)207#if __has_feature(thread_sanitizer)208__attribute__((no_sanitize("thread")))209#endif210#endif211void AddNewEdgeVerts(212// we need concurrent_map because we will be adding things concurrently213concurrent_map<int, std::vector<EdgePos>> &edgesP,214concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,215const Vec<std::array<int, 2>> &p1q2, const Vec<int> &i12, const Vec<int> &v12R,216const Vec<Halfedge> &halfedgeP, bool forward, size_t offset) {217ZoneScoped;218// For each edge of P that intersects a face of Q (p1q2), add this vertex to219// P's corresponding edge vector and to the two new edges, which are220// intersections between the face of Q and the two faces of P attached to the221// edge. The direction and duplicity are given by i12, while v12R remaps to222// the output vert index. When forward is false, all is reversed.223auto process = [&](std::function<void(size_t)> lock,224std::function<void(size_t)> unlock, size_t i) {225const int edgeP = p1q2[i][forward ? 0 : 1];226const int faceQ = p1q2[i][forward ? 1 : 0];227const int vert = v12R[i];228const int inclusion = i12[i];229230Halfedge halfedge = halfedgeP[edgeP];231std::pair<int, int> keyRight = {halfedge.pairedHalfedge / 3, faceQ};232if (!forward) std::swap(keyRight.first, keyRight.second);233234std::pair<int, int> keyLeft = {edgeP / 3, faceQ};235if (!forward) std::swap(keyLeft.first, keyLeft.second);236237bool direction = inclusion < 0;238std::hash<std::pair<int, int>> pairHasher;239std::array<std::tuple<bool, size_t, std::vector<EdgePos>*>, 3> edges = {240std::make_tuple(direction, std::hash<int>{}(edgeP), &edgesP[edgeP]),241std::make_tuple(direction ^ !forward, // revert if not forward242pairHasher(keyRight), &edgesNew[keyRight]),243std::make_tuple(direction ^ forward, // revert if forward244pairHasher(keyLeft), &edgesNew[keyLeft])};245for (const auto& tuple : edges) {246lock(std::get<1>(tuple));247for (int j = 0; j < std::abs(inclusion); ++j)248std::get<2>(tuple)->push_back(249{0.0, vert + j, static_cast<int>(i + offset), std::get<0>(tuple)});250unlock(std::get<1>(tuple));251direction = !direction;252}253};254#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)255// parallelize operations, requires concurrent_map so we can only enable this256// with tbb257if (p1q2.size() > kParallelThreshold) {258// ideally we should have 1 mutex per key, but kParallelThreshold is enough259// to avoid contention for most of the cases260std::array<std::mutex, kParallelThreshold> mutexes;261static tbb::affinity_partitioner ap;262auto processFun = std::bind(263process, [&](size_t hash) { mutexes[hash % mutexes.size()].lock(); },264[&](size_t hash) { mutexes[hash % mutexes.size()].unlock(); },265std::placeholders::_1);266tbb::parallel_for(267tbb::blocked_range<size_t>(0_uz, p1q2.size(), 32),268[&](const tbb::blocked_range<size_t>& range) {269for (size_t i = range.begin(); i != range.end(); i++) processFun(i);270},271ap);272return;273}274#endif275auto processFun =276std::bind(process, [](size_t) {}, [](size_t) {}, std::placeholders::_1);277for (size_t i = 0; i < p1q2.size(); ++i) processFun(i);278}279280std::vector<Halfedge> PairUp(std::vector<EdgePos>& edgePos) {281// Pair start vertices with end vertices to form edges. The choice of pairing282// is arbitrary for the manifoldness guarantee, but must be ordered to be283// geometrically valid. If the order does not go start-end-start-end... then284// the input and output are not geometrically valid and this algorithm becomes285// a heuristic.286DEBUG_ASSERT(edgePos.size() % 2 == 0, topologyErr,287"Non-manifold edge! Not an even number of points.");288size_t nEdges = edgePos.size() / 2;289auto middle = std::partition(edgePos.begin(), edgePos.end(),290[](EdgePos x) { return x.isStart; });291DEBUG_ASSERT(static_cast<size_t>(middle - edgePos.begin()) == nEdges,292topologyErr, "Non-manifold edge!");293auto cmp = [](EdgePos a, EdgePos b) {294return a.edgePos < b.edgePos ||295// we also sort by collisionId to make things deterministic296(a.edgePos == b.edgePos && a.collisionId < b.collisionId);297};298std::stable_sort(edgePos.begin(), middle, cmp);299std::stable_sort(middle, edgePos.end(), cmp);300std::vector<Halfedge> edges;301for (size_t i = 0; i < nEdges; ++i)302edges.push_back({edgePos[i].vert, edgePos[i + nEdges].vert, -1});303return edges;304}305306void AppendPartialEdges(Manifold::Impl& outR, Vec<char>& wholeHalfedgeP,307Vec<int>& facePtrR,308concurrent_map<int, std::vector<EdgePos>>& edgesP,309Vec<TriRef>& halfedgeRef, const Manifold::Impl& inP,310const Vec<int>& i03, const Vec<int>& vP2R,311const Vec<int>::IterC faceP2R, bool forward) {312ZoneScoped;313// Each edge in the map is partially retained; for each of these, look up314// their original verts and include them based on their winding number (i03),315// while remapping them to the output using vP2R. Use the verts position316// projected along the edge vector to pair them up, then distribute these317// edges to their faces.318Vec<Halfedge>& halfedgeR = outR.halfedge_;319const Vec<vec3>& vertPosP = inP.vertPos_;320const Vec<Halfedge>& halfedgeP = inP.halfedge_;321322for (auto& value : edgesP) {323const int edgeP = value.first;324std::vector<EdgePos>& edgePosP = value.second;325326const Halfedge& halfedge = halfedgeP[edgeP];327wholeHalfedgeP[edgeP] = false;328wholeHalfedgeP[halfedge.pairedHalfedge] = false;329330const int vStart = halfedge.startVert;331const int vEnd = halfedge.endVert;332const vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart];333// Fill in the edge positions of the old points.334for (EdgePos& edge : edgePosP) {335edge.edgePos = la::dot(outR.vertPos_[edge.vert], edgeVec);336}337338int inclusion = i03[vStart];339EdgePos edgePos = {la::dot(outR.vertPos_[vP2R[vStart]], edgeVec),340vP2R[vStart], std::numeric_limits<int>::max(),341inclusion > 0};342for (int j = 0; j < std::abs(inclusion); ++j) {343edgePosP.push_back(edgePos);344++edgePos.vert;345}346347inclusion = i03[vEnd];348edgePos = {la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec), vP2R[vEnd],349std::numeric_limits<int>::max(), inclusion < 0};350for (int j = 0; j < std::abs(inclusion); ++j) {351edgePosP.push_back(edgePos);352++edgePos.vert;353}354355// sort edges into start/end pairs along length356std::vector<Halfedge> edges = PairUp(edgePosP);357358// add halfedges to result359const int faceLeftP = edgeP / 3;360const int faceLeft = faceP2R[faceLeftP];361const int faceRightP = halfedge.pairedHalfedge / 3;362const int faceRight = faceP2R[faceRightP];363// Negative inclusion means the halfedges are reversed, which means our364// reference is now to the endVert instead of the startVert, which is one365// position advanced CCW. This is only valid if this is a retained vert; it366// will be ignored later if the vert is new.367const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1};368const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1};369370for (Halfedge e : edges) {371const int forwardEdge = facePtrR[faceLeft]++;372const int backwardEdge = facePtrR[faceRight]++;373374e.pairedHalfedge = backwardEdge;375halfedgeR[forwardEdge] = e;376halfedgeRef[forwardEdge] = forwardRef;377378std::swap(e.startVert, e.endVert);379e.pairedHalfedge = forwardEdge;380halfedgeR[backwardEdge] = e;381halfedgeRef[backwardEdge] = backwardRef;382}383}384}385386void AppendNewEdges(387Manifold::Impl& outR, Vec<int>& facePtrR,388concurrent_map<std::pair<int, int>, std::vector<EdgePos>>& edgesNew,389Vec<TriRef>& halfedgeRef, const Vec<int>& facePQ2R, const int numFaceP) {390ZoneScoped;391// Pair up each edge's verts and distribute to faces based on indices in key.392Vec<Halfedge>& halfedgeR = outR.halfedge_;393Vec<vec3>& vertPosR = outR.vertPos_;394395for (auto& value : edgesNew) {396const int faceP = value.first.first;397const int faceQ = value.first.second;398std::vector<EdgePos>& edgePos = value.second;399400Box bbox;401for (auto edge : edgePos) {402bbox.Union(vertPosR[edge.vert]);403}404const vec3 size = bbox.Size();405// Order the points along their longest dimension.406const int i = (size.x > size.y && size.x > size.z) ? 0407: size.y > size.z ? 1408: 2;409for (auto& edge : edgePos) {410edge.edgePos = vertPosR[edge.vert][i];411}412413// sort edges into start/end pairs along length.414std::vector<Halfedge> edges = PairUp(edgePos);415416// add halfedges to result417const int faceLeft = facePQ2R[faceP];418const int faceRight = facePQ2R[numFaceP + faceQ];419const TriRef forwardRef = {0, -1, faceP, -1};420const TriRef backwardRef = {1, -1, faceQ, -1};421for (Halfedge e : edges) {422const int forwardEdge = facePtrR[faceLeft]++;423const int backwardEdge = facePtrR[faceRight]++;424425e.pairedHalfedge = backwardEdge;426halfedgeR[forwardEdge] = e;427halfedgeRef[forwardEdge] = forwardRef;428429std::swap(e.startVert, e.endVert);430e.pairedHalfedge = forwardEdge;431halfedgeR[backwardEdge] = e;432halfedgeRef[backwardEdge] = backwardRef;433}434}435}436437struct DuplicateHalfedges {438VecView<Halfedge> halfedgesR;439VecView<TriRef> halfedgeRef;440VecView<int> facePtr;441VecView<const char> wholeHalfedgeP;442VecView<const Halfedge> halfedgesP;443VecView<const int> i03;444VecView<const int> vP2R;445VecView<const int> faceP2R;446const bool forward;447448void operator()(const int idx) {449if (!wholeHalfedgeP[idx]) return;450Halfedge halfedge = halfedgesP[idx];451if (!halfedge.IsForward()) return;452453const int inclusion = i03[halfedge.startVert];454if (inclusion == 0) return;455if (inclusion < 0) { // reverse456std::swap(halfedge.startVert, halfedge.endVert);457}458halfedge.startVert = vP2R[halfedge.startVert];459halfedge.endVert = vP2R[halfedge.endVert];460const int faceLeftP = idx / 3;461const int newFace = faceP2R[faceLeftP];462const int faceRightP = halfedge.pairedHalfedge / 3;463const int faceRight = faceP2R[faceRightP];464// Negative inclusion means the halfedges are reversed, which means our465// reference is now to the endVert instead of the startVert, which is one466// position advanced CCW.467const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1};468const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1};469470for (int i = 0; i < std::abs(inclusion); ++i) {471int forwardEdge = AtomicAdd(facePtr[newFace], 1);472int backwardEdge = AtomicAdd(facePtr[faceRight], 1);473halfedge.pairedHalfedge = backwardEdge;474475halfedgesR[forwardEdge] = halfedge;476halfedgesR[backwardEdge] = {halfedge.endVert, halfedge.startVert,477forwardEdge};478halfedgeRef[forwardEdge] = forwardRef;479halfedgeRef[backwardEdge] = backwardRef;480481++halfedge.startVert;482++halfedge.endVert;483}484}485};486487void AppendWholeEdges(Manifold::Impl& outR, Vec<int>& facePtrR,488Vec<TriRef>& halfedgeRef, const Manifold::Impl& inP,489const Vec<char> wholeHalfedgeP, const Vec<int>& i03,490const Vec<int>& vP2R, VecView<const int> faceP2R,491bool forward) {492ZoneScoped;493for_each_n(494autoPolicy(inP.halfedge_.size()), countAt(0), inP.halfedge_.size(),495DuplicateHalfedges({outR.halfedge_, halfedgeRef, facePtrR, wholeHalfedgeP,496inP.halfedge_, i03, vP2R, faceP2R, forward}));497}498499struct MapTriRef {500VecView<const TriRef> triRefP;501VecView<const TriRef> triRefQ;502const int offsetQ;503504void operator()(TriRef& triRef) {505const int tri = triRef.faceID;506const bool PQ = triRef.meshID == 0;507triRef = PQ ? triRefP[tri] : triRefQ[tri];508if (!PQ) triRef.meshID += offsetQ;509}510};511512void UpdateReference(Manifold::Impl& outR, const Manifold::Impl& inP,513const Manifold::Impl& inQ, bool invertQ) {514const int offsetQ = Manifold::Impl::meshIDCounter_;515for_each_n(516autoPolicy(outR.NumTri(), 1e5), outR.meshRelation_.triRef.begin(),517outR.NumTri(),518MapTriRef({inP.meshRelation_.triRef, inQ.meshRelation_.triRef, offsetQ}));519520for (const auto& pair : inP.meshRelation_.meshIDtransform) {521outR.meshRelation_.meshIDtransform[pair.first] = pair.second;522}523for (const auto& pair : inQ.meshRelation_.meshIDtransform) {524outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second;525outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=526invertQ;527}528}529530struct Barycentric {531VecView<vec3> uvw;532VecView<const TriRef> ref;533VecView<const vec3> vertPosP;534VecView<const vec3> vertPosQ;535VecView<const vec3> vertPosR;536VecView<const Halfedge> halfedgeP;537VecView<const Halfedge> halfedgeQ;538VecView<const Halfedge> halfedgeR;539const double epsilon;540541void operator()(const int tri) {542const TriRef refPQ = ref[tri];543if (halfedgeR[3 * tri].startVert < 0) return;544545const int triPQ = refPQ.faceID;546const bool PQ = refPQ.meshID == 0;547const auto& vertPos = PQ ? vertPosP : vertPosQ;548const auto& halfedge = PQ ? halfedgeP : halfedgeQ;549550mat3 triPos;551for (const int j : {0, 1, 2})552triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];553554for (const int i : {0, 1, 2}) {555const int vert = halfedgeR[3 * tri + i].startVert;556uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, epsilon);557}558}559};560561void CreateProperties(Manifold::Impl& outR, const Manifold::Impl& inP,562const Manifold::Impl& inQ) {563ZoneScoped;564const int numPropP = inP.NumProp();565const int numPropQ = inQ.NumProp();566const int numProp = std::max(numPropP, numPropQ);567outR.numProp_ = numProp;568if (numProp == 0) return;569570const int numTri = outR.NumTri();571Vec<vec3> bary(outR.halfedge_.size());572for_each_n(autoPolicy(numTri, 1e4), countAt(0), numTri,573Barycentric({bary, outR.meshRelation_.triRef, inP.vertPos_,574inQ.vertPos_, outR.vertPos_, inP.halfedge_,575inQ.halfedge_, outR.halfedge_, outR.epsilon_}));576577using Entry = std::pair<ivec3, int>;578int idMissProp = outR.NumVert();579std::vector<std::vector<Entry>> propIdx(outR.NumVert() + 1);580std::vector<int> propMissIdx[2];581propMissIdx[0].resize(inQ.NumPropVert(), -1);582propMissIdx[1].resize(inP.NumPropVert(), -1);583584outR.properties_.reserve(outR.NumVert() * numProp);585int idx = 0;586587for (int tri = 0; tri < numTri; ++tri) {588// Skip collapsed triangles589if (outR.halfedge_[3 * tri].startVert < 0) continue;590591const TriRef ref = outR.meshRelation_.triRef[tri];592const bool PQ = ref.meshID == 0;593const int oldNumProp = PQ ? numPropP : numPropQ;594const auto& properties = PQ ? inP.properties_ : inQ.properties_;595const auto& halfedge = PQ ? inP.halfedge_ : inQ.halfedge_;596597for (const int i : {0, 1, 2}) {598const int vert = outR.halfedge_[3 * tri + i].startVert;599const vec3& uvw = bary[3 * tri + i];600601ivec4 key(PQ, idMissProp, -1, -1);602if (oldNumProp > 0) {603int edge = -2;604for (const int j : {0, 1, 2}) {605if (uvw[j] == 1) {606// On a retained vert, the propVert must also match607key[2] = halfedge[3 * ref.faceID + j].propVert;608edge = -1;609break;610}611if (uvw[j] == 0) edge = j;612}613if (edge >= 0) {614// On an edge, both propVerts must match615const int p0 = halfedge[3 * ref.faceID + Next3(edge)].propVert;616const int p1 = halfedge[3 * ref.faceID + Prev3(edge)].propVert;617key[1] = vert;618key[2] = std::min(p0, p1);619key[3] = std::max(p0, p1);620} else if (edge == -2) {621key[1] = vert;622}623}624625if (key.y == idMissProp && key.z >= 0) {626// only key.x/key.z matters627auto& entry = propMissIdx[key.x][key.z];628if (entry >= 0) {629outR.halfedge_[3 * tri + i].propVert = entry;630continue;631}632entry = idx;633} else {634auto& bin = propIdx[key.y];635bool bFound = false;636for (const auto& b : bin) {637if (b.first == ivec3(key.x, key.z, key.w)) {638bFound = true;639outR.halfedge_[3 * tri + i].propVert = b.second;640break;641}642}643if (bFound) continue;644bin.push_back(std::make_pair(ivec3(key.x, key.z, key.w), idx));645}646647outR.halfedge_[3 * tri + i].propVert = idx++;648for (int p = 0; p < numProp; ++p) {649if (p < oldNumProp) {650vec3 oldProps;651for (const int j : {0, 1, 2})652oldProps[j] =653properties[oldNumProp * halfedge[3 * ref.faceID + j].propVert +654p];655outR.properties_.push_back(la::dot(uvw, oldProps));656} else {657outR.properties_.push_back(0);658}659}660}661}662}663664void ReorderHalfedges(VecView<Halfedge>& halfedges) {665ZoneScoped;666// halfedges in the same face are added in non-deterministic order, so we have667// to reorder them for determinism668669// step 1: reorder within the same face, such that the halfedge with the670// smallest starting vertex is placed first671for_each(autoPolicy(halfedges.size() / 3), countAt(0),672countAt(halfedges.size() / 3), [&halfedges](size_t tri) {673std::array<Halfedge, 3> face = {halfedges[tri * 3],674halfedges[tri * 3 + 1],675halfedges[tri * 3 + 2]};676int index = 0;677for (int i : {1, 2})678if (face[i].startVert < face[index].startVert) index = i;679for (int i : {0, 1, 2})680halfedges[tri * 3 + i] = face[(index + i) % 3];681});682// step 2: fix paired halfedge683for_each(autoPolicy(halfedges.size() / 3), countAt(0),684countAt(halfedges.size() / 3), [&halfedges](size_t tri) {685for (int i : {0, 1, 2}) {686Halfedge& curr = halfedges[tri * 3 + i];687int oppositeFace = curr.pairedHalfedge / 3;688int index = -1;689for (int j : {0, 1, 2})690if (curr.startVert == halfedges[oppositeFace * 3 + j].endVert)691index = j;692curr.pairedHalfedge = oppositeFace * 3 + index;693}694});695}696697} // namespace698699namespace manifold {700701Manifold::Impl Boolean3::Result(OpType op) const {702#ifdef MANIFOLD_DEBUG703Timer assemble;704assemble.Start();705#endif706707DEBUG_ASSERT((expandP_ > 0) == (op == OpType::Add), logicErr,708"Result op type not compatible with constructor op type.");709const int c1 = op == OpType::Intersect ? 0 : 1;710const int c2 = op == OpType::Add ? 1 : 0;711const int c3 = op == OpType::Intersect ? 1 : -1;712713if (inP_.status_ != Manifold::Error::NoError) {714auto impl = Manifold::Impl();715impl.status_ = inP_.status_;716return impl;717}718if (inQ_.status_ != Manifold::Error::NoError) {719auto impl = Manifold::Impl();720impl.status_ = inQ_.status_;721return impl;722}723724if (inP_.IsEmpty()) {725if (!inQ_.IsEmpty() && op == OpType::Add) {726return inQ_;727}728return Manifold::Impl();729} else if (inQ_.IsEmpty()) {730if (op == OpType::Intersect) {731return Manifold::Impl();732}733return inP_;734}735736if (!valid) {737auto impl = Manifold::Impl();738impl.status_ = Manifold::Error::ResultTooLarge;739return impl;740}741742const bool invertQ = op == OpType::Subtract;743744// Convert winding numbers to inclusion values based on operation type.745Vec<int> i12(x12_.size());746Vec<int> i21(x21_.size());747Vec<int> i03(w03_.size());748Vec<int> i30(w30_.size());749750transform(x12_.begin(), x12_.end(), i12.begin(),751[c3](int v) { return c3 * v; });752transform(x21_.begin(), x21_.end(), i21.begin(),753[c3](int v) { return c3 * v; });754transform(w03_.begin(), w03_.end(), i03.begin(),755[c1, c3](int v) { return c1 + c3 * v; });756transform(w30_.begin(), w30_.end(), i30.begin(),757[c2, c3](int v) { return c2 + c3 * v; });758759Vec<int> vP2R(inP_.NumVert());760exclusive_scan(i03.begin(), i03.end(), vP2R.begin(), 0, AbsSum());761int numVertR = AbsSum()(vP2R.back(), i03.back());762const int nPv = numVertR;763764Vec<int> vQ2R(inQ_.NumVert());765exclusive_scan(i30.begin(), i30.end(), vQ2R.begin(), numVertR, AbsSum());766numVertR = AbsSum()(vQ2R.back(), i30.back());767const int nQv = numVertR - nPv;768769Vec<int> v12R(v12_.size());770if (v12_.size() > 0) {771exclusive_scan(i12.begin(), i12.end(), v12R.begin(), numVertR, AbsSum());772numVertR = AbsSum()(v12R.back(), i12.back());773}774const int n12 = numVertR - nPv - nQv;775776Vec<int> v21R(v21_.size());777if (v21_.size() > 0) {778exclusive_scan(i21.begin(), i21.end(), v21R.begin(), numVertR, AbsSum());779numVertR = AbsSum()(v21R.back(), i21.back());780}781const int n21 = numVertR - nPv - nQv - n12;782783// Create the output Manifold784Manifold::Impl outR;785786if (numVertR == 0) return outR;787788outR.epsilon_ = std::max(inP_.epsilon_, inQ_.epsilon_);789outR.tolerance_ = std::max(inP_.tolerance_, inQ_.tolerance_);790791outR.vertPos_.resize_nofill(numVertR);792// Add vertices, duplicating for inclusion numbers not in [-1, 1].793// Retained vertices from P and Q:794for_each_n(autoPolicy(inP_.NumVert(), 1e4), countAt(0), inP_.NumVert(),795DuplicateVerts({outR.vertPos_, i03, vP2R, inP_.vertPos_}));796for_each_n(autoPolicy(inQ_.NumVert(), 1e4), countAt(0), inQ_.NumVert(),797DuplicateVerts({outR.vertPos_, i30, vQ2R, inQ_.vertPos_}));798// New vertices created from intersections:799for_each_n(autoPolicy(i12.size(), 1e4), countAt(0), i12.size(),800DuplicateVerts({outR.vertPos_, i12, v12R, v12_}));801for_each_n(autoPolicy(i21.size(), 1e4), countAt(0), i21.size(),802DuplicateVerts({outR.vertPos_, i21, v21R, v21_}));803804PRINT(nPv << " verts from inP");805PRINT(nQv << " verts from inQ");806PRINT(n12 << " new verts from edgesP -> facesQ");807PRINT(n21 << " new verts from facesP -> edgesQ");808809// Build up new polygonal faces from triangle intersections. At this point the810// calculation switches from parallel to serial.811812// Level 3813814// This key is the forward halfedge index of P or Q. Only includes intersected815// edges.816concurrent_map<int, std::vector<EdgePos>> edgesP, edgesQ;817// This key is the face index of <P, Q>818concurrent_map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;819820AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true, 0);821AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false,822p1q2_.size());823824v12R.clear();825v21R.clear();826827// Level 4828Vec<int> faceEdge;829Vec<int> facePQ2R;830std::tie(faceEdge, facePQ2R) =831SizeOutput(outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ);832833i12.clear();834i21.clear();835836// This gets incremented for each halfedge that's added to a face so that the837// next one knows where to slot in.838Vec<int> facePtrR = faceEdge;839// Intersected halfedges are marked false.840Vec<char> wholeHalfedgeP(inP_.halfedge_.size(), true);841Vec<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);842// The halfedgeRef contains the data that will become triRef once the faces843// are triangulated.844Vec<TriRef> halfedgeRef(2 * outR.NumEdge());845846AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,847i03, vP2R, facePQ2R.begin(), true);848AppendPartialEdges(outR, wholeHalfedgeQ, facePtrR, edgesQ, halfedgeRef, inQ_,849i30, vQ2R, facePQ2R.begin() + inP_.NumTri(), false);850851edgesP.clear();852edgesQ.clear();853854AppendNewEdges(outR, facePtrR, edgesNew, halfedgeRef, facePQ2R,855inP_.NumTri());856857edgesNew.clear();858859AppendWholeEdges(outR, facePtrR, halfedgeRef, inP_, wholeHalfedgeP, i03, vP2R,860facePQ2R.cview(0, inP_.NumTri()), true);861AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,862facePQ2R.cview(inP_.NumTri(), inQ_.NumTri()), false);863864wholeHalfedgeP.clear();865wholeHalfedgeQ.clear();866facePtrR.clear();867facePQ2R.clear();868i03.clear();869i30.clear();870vP2R.clear();871vQ2R.clear();872873#ifdef MANIFOLD_DEBUG874assemble.Stop();875Timer triangulate;876triangulate.Start();877#endif878879// Level 6880881if (ManifoldParams().intermediateChecks)882DEBUG_ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");883884outR.Face2Tri(faceEdge, halfedgeRef);885halfedgeRef.clear();886faceEdge.clear();887888ReorderHalfedges(outR.halfedge_);889890#ifdef MANIFOLD_DEBUG891triangulate.Stop();892Timer simplify;893simplify.Start();894#endif895896if (ManifoldParams().intermediateChecks)897DEBUG_ASSERT(outR.IsManifold(), logicErr,898"triangulated mesh is not manifold!");899900CreateProperties(outR, inP_, inQ_);901902UpdateReference(outR, inP_, inQ_, invertQ);903904outR.SimplifyTopology(nPv + nQv);905outR.RemoveUnreferencedVerts();906907if (ManifoldParams().intermediateChecks)908DEBUG_ASSERT(outR.Is2Manifold(), logicErr,909"simplified mesh is not 2-manifold!");910911#ifdef MANIFOLD_DEBUG912simplify.Stop();913Timer sort;914sort.Start();915#endif916917outR.Finish();918outR.IncrementMeshIDs();919920#ifdef MANIFOLD_DEBUG921sort.Stop();922if (ManifoldParams().verbose) {923assemble.Print("Assembly");924triangulate.Print("Triangulation");925simplify.Print("Simplification");926sort.Print("Sorting");927std::cout << outR.NumVert() << " verts and " << outR.NumTri() << " tris"928<< std::endl;929}930#endif931932return outR;933}934935} // namespace manifold936937938