316 using EdgeIDs = std::pair<short int, short int>;
321 std::vector<Record>
result;
322 if(polyPoints.size() < 3)
328 (void)intersectInternalNodes;
331 return a[0]*
b[1]-
a[1]*
b[0];
334 double signedArea = 0.0;
335 for(
size_t i=0;
i<polyPoints.size(); ++
i)
338 const VectorLowerDim b = polyPoints[(
i+1)%polyPoints.size()].first-polyPoints.front().first;
339 scale = std::max(scale,
a.norm());
340 signedArea += cross(
a,
b);
342 const double tolerance = 1.e-9*scale;
347 size_t retainedEdges=0;
348 for(
size_t i=0;
i<polyPoints.size(); ++
i)
350 if((polyPoints[(
i+1)%polyPoints.size()].first-polyPoints[
i].first).norm()>tolerance)
364 for(
const auto&
vertex : polyPoints)
367 if(d.squaredNorm()>
longest.squaredNorm())
372 double orientation=0.0;
373 for(
const auto&
vertex : polyPoints)
375 const double turn=cross(
longest,
vertex.first-polyPoints.front().first);
376 if(std::abs(turn)>tolerance*
longest.norm())
386 const bool forward = (std::abs(signedArea)>tolerance*tolerance ? signedArea : orientation)>0.0;
388 std::vector<std::shared_ptr<Patch>> heldPatches;
391 auto patch = boundary.getPatch(shift);
392 heldPatches.push_back(patch);
395 const auto traversable = [](
const Edge* edge)
397 for(
const auto* face : edge->meshIntersection->faces)
399 if(!face->periodicFacePair.second ||
400 face->regionIDs != face->periodicFacePair.second->regionIDs)
405 return edge->deltaShift.squaredNorm() > 0.0;
407 const auto crossSeam = [&](
const Edge* edge)
409 auto patch =
getPatch(edge->patch->shift+edge->deltaShift);
412 throw std::runtime_error(
"Periodic polygon walk: same-grain edge has no twin after creating its neighbor.");
416 const auto edgeIDs = [&](
const Patch* patch,
const VectorLowerDim& point)
419 for(
const auto& edge : patch->edges())
422 const double t = d.dot(point-*edge->source)/d.squaredNorm();
423 if(
t>=-tolerance/d.norm() &&
t<=1.0+tolerance/d.norm() &&
424 std::abs(cross(d,point-*edge->source))<=tolerance*d.norm())
428 ids.first=edge->edgeID;
432 ids.second=edge->edgeID;
438 const auto originalSegment = [&](
size_t index)
440 const size_t next=(
index+1)%polyPoints.size();
443 const auto metadata = [&](
const VectorLowerDim& point, std::optional<size_t> segment,
444 std::optional<size_t>
vertex, Arc incoming,
445 std::optional<size_t> incomingSegment,
bool seam)
448 value.originalVertex=
vertex;
449 value.incomingArc=incoming;
450 value.crossesPeriodicSeam=seam;
453 const auto origin=originalSegment(*segment);
454 const VectorLowerDim d=polyPoints[origin.sinkIndex].first-polyPoints[origin.sourceIndex].first;
455 const double t=d.squaredNorm()>0.0 ? d.dot(point-polyPoints[origin.sourceIndex].first)/d.squaredNorm() : 0.0;
460 value.incomingOriginalSegment=originalSegment(*incomingSegment);
464 const auto classify = [](Metadata& value)
466 if(value.originalVertex)
470 else if(value.onPhysicalBoundary)
480 const auto append = [&](
const VectorLowerDim& point,
const Patch* patch,
const T* original, Metadata value)
482 const EdgeIDs geometricIDs=edgeIDs(patch,point);
483 value.atPatchCorner=geometricIDs.second>=0;
484 for(
short id : {geometricIDs.first,geometricIDs.second})
486 if(
id>=0 && !traversable(patch->edges()[
id].get()))
488 value.onPhysicalBoundary=
true;
491 const EdgeIDs ids = original ? EdgeIDs(-1,-1) : geometricIDs;
492 const auto samePort=[&](
const Metadata& previous)
494 if((!previous.originalVertex && !previous.polygonLocation) ||
495 (!value.originalVertex && !value.polygonLocation))
499 if(previous.originalVertex && value.originalVertex)
501 return previous.originalVertex==value.originalVertex;
503 return previous.polygonLocation && value.polygonLocation &&
504 previous.polygonLocation->segment.sourceIndex==value.polygonLocation->segment.sourceIndex &&
505 std::abs(previous.polygonLocation->parameter-value.polygonLocation->parameter)<=1.e-10;
507 if(!
result.empty() && samePort(std::get<6>(
result.back())) &&
508 (std::get<0>(
result.back())-point).norm()<=tolerance &&
509 (std::get<1>(
result.back())-patch->shift).norm()<=tolerance)
511 auto merged=std::get<6>(
result.back());
512 merged.crossesPeriodicSeam=merged.crossesPeriodicSeam || value.crossesPeriodicSeam;
513 merged.onPhysicalBoundary=merged.onPhysicalBoundary || value.onPhysicalBoundary;
514 merged.atPatchCorner=merged.atPatchCorner || value.atPatchCorner;
515 if(value.polygonLocation)
517 merged.polygonLocation=value.polygonLocation;
519 if(value.originalVertex)
521 merged.originalVertex=value.originalVertex;
527 result.emplace_back(point,patch->shift,ids,EdgeMap(),0,original,merged);
531 std::get<6>(
result.back())=merged;
536 result.emplace_back(point,patch->shift,ids,EdgeMap(),0,original,value);
540 const auto central=
getPatch(VectorDim::Zero());
543 for(
const auto&
vertex : polyPoints)
545 lower=lower.cwiseMin(
vertex.first);
546 upper=upper.cwiseMax(
vertex.first);
548 for(
const auto& edge : central->edges())
550 lower=lower.cwiseMin(*edge->source);
551 upper=upper.cwiseMax(*edge->source);
553 std::vector<std::shared_ptr<Patch>>
patches{central};
554 std::set<const Patch*> discovered{central.get()};
559 if(traversable(edge.get()))
561 const auto neighbor=crossSeam(edge.get());
562 if(discovered.insert(
neighbor.get()).second)
566 for(
const auto& other :
neighbor->edges())
568 lo=lo.cwiseMin(*other->source);
569 hi=hi.cwiseMax(*other->source);
571 if((lo.array()<=upper.array()+tolerance).all() &&
572 (hi.array()>=lower.array()-tolerance).all())
581 throw std::runtime_error(
"Periodic polygon graph exceeded its patch limit.");
591 std::vector<Piece> pieces;
592 for(
size_t segment=0; segment<polyPoints.size(); ++segment)
595 const VectorLowerDim d=polyPoints[(segment+1)%polyPoints.size()].first-
a;
596 if(d.norm()<=tolerance)
600 const double paramTolerance=tolerance/d.norm();
601 std::vector<Piece> intervals;
602 for(
const auto& patch :
patches)
607 for(
const auto& edge : patch->edges())
610 const double f0=cross(direction,
a-*edge->source);
611 const double f1=cross(direction,
a+d-*edge->source);
618 const double coordinateScale=std::max({1.0,
a.norm(),(
a+d).norm(),
619 edge->source->norm(),edge->sink->norm()});
620 const double distanceTolerance=traversable(edge.get()) ?
621 64*std::numeric_limits<double>::epsilon()*coordinateScale : tolerance;
622 const double eps=distanceTolerance*direction.norm();
623 if(f0 < -eps &&
f1 < -eps)
630 enter=std::max(enter,f0/(f0-
f1));
634 leave=std::min(leave,f0/(f0-
f1));
637 if(!outside && leave-enter>paramTolerance)
639 intervals.push_back({segment,enter,leave,patch.get(),
a+enter*d,
a+leave*d});
642 std::stable_sort(intervals.begin(),intervals.end(),[](
const Piece&
a,
const Piece&
b)
644 return a.begin<b.begin;
648 for(
size_t i=0;
i<intervals.size();)
651 while(
end<intervals.size() && std::abs(intervals[
end].begin-intervals[
i].begin)<=paramTolerance &&
652 std::abs(intervals[
end].end-intervals[
i].end)<=paramTolerance)
661 if(intervals[
j].patch==pieces.back().patch)
668 pieces.push_back(intervals[chosen]);
672 const auto nextPhysicalInDirection=[&](
const Edge* edge,
bool direction)
674 const Edge* next=direction ? edge->next : edge->prev;
676 while(traversable(next))
678 if(!
corners.insert(next).second)
680 throw std::runtime_error(
"Periodic polygon graph has a cyclic seam corner.");
683 next=direction ? next->twin->next : next->twin->prev;
692 for(
const auto& patch :
patches)
695 for(
const auto& edge : patch->edges())
697 interior+=*edge->source;
699 interior/=patch->edges().size();
701 for(
size_t i=0;
i<polyPoints.size(); ++
i)
705 if(
a.y()<=0 &&
b.y()>0 && cross(
a,
b)>0)
709 else if(
a.y()>0 &&
b.y()<=0 && cross(
a,
b)<0)
718 const Edge* seed=
nullptr;
719 for(
const auto& edge : patch->edges())
721 if(!traversable(edge.get()))
729 throw std::runtime_error(
"Enclosed periodic patch has no physical boundary seed.");
731 const bool direction=winding>0;
732 for(
int layer=0; layer<std::abs(winding); ++layer)
734 const Edge* edge=seed;
735 std::set<const Edge*> visited;
738 if(!visited.insert(edge).second || visited.size()>100000)
740 throw std::runtime_error(
"Enclosed physical boundary does not form a closed cycle.");
743 auto enclosed=metadata(
corner,std::nullopt,std::nullopt,Arc::PhysicalBoundary,std::nullopt,
false);
744 enclosed.enclosureLayer=layer;
745 append(
corner,edge->patch,
nullptr,enclosed);
746 const Edge* next=nextPhysicalInDirection(edge,direction);
747 if(next->patch!=edge->patch)
749 std::get<6>(
result.back()).crossesPeriodicSeam=
true;
750 enclosed.incomingArc=Arc::PeriodicSeam;
751 enclosed.crossesPeriodicSeam=
true;
752 append(
corner,next->patch,
nullptr,enclosed);
763 const size_t none=std::numeric_limits<size_t>::max();
764 const auto continuous=[&](
size_t i,
size_t j)
766 const auto&
a=pieces[
i];
767 const auto&
b=pieces[
j];
768 if((
a.sink-
b.source).norm()>tolerance)
772 if(
a.segment==
b.segment &&
b.begin>=
a.end-1.e-10)
780 if((
a.sink-polyPoints[(
a.segment+1)%polyPoints.size()].first).norm()>tolerance ||
781 (
b.source-polyPoints[
b.segment].first).norm()>tolerance)
785 for(
size_t k=(
a.segment+1)%polyPoints.size();
k!=
b.segment;
k=(
k+1)%polyPoints.size())
787 if((polyPoints[(
k+1)%polyPoints.size()].first-polyPoints[
k].first).norm()>tolerance)
794 const auto nextPhysical=[&](
const Edge* edge)
796 return nextPhysicalInDirection(edge,forward);
798 const auto boundaryPort=[&](
const Patch* patch,
const VectorLowerDim& point)
800 const EdgeIDs ids=edgeIDs(patch,point);
801 const Edge* found=
nullptr;
802 for(
short id : {ids.first,ids.second})
804 if(
id>=0 && !traversable(patch->edges()[
id].get()))
806 found=patch->edges()[id].get();
808 if((point-
end).norm()<=tolerance)
810 found=nextPhysical(found);
817 throw std::runtime_error(
"Periodic polygon cut has no physical boundary port.");
830 std::vector<Port> ports;
831 std::vector<size_t> successor(pieces.size(),none);
832 std::vector<size_t> exitPort(pieces.size(),none);
833 std::vector<size_t> entryPort(pieces.size(),none);
834 std::vector<bool> boundaryWrap(pieces.size(),
false);
835 std::vector<bool> boundaryReverse(pieces.size(),
false);
838 for(
size_t i=0;
i<pieces.size(); ++
i)
840 const size_t j=(
i+1)%pieces.size();
847 const size_t out=ports.size();
848 for(
bool exit : {
true,
false})
850 const size_t piece=exit ?
i :
j;
851 const VectorLowerDim point=exit ? pieces[piece].sink : pieces[piece].source;
852 const Edge* edge=boundaryPort(pieces[piece].patch,point);
855 ports.push_back({piece,exit,edge,d.dot(point-
a)/d.squaredNorm(),point,ports.size()+1});
863 ports.back().polygonNext=0;
865 std::map<const Edge*,std::vector<size_t>> edgePorts;
866 std::set<const Edge*> physicalEdges;
867 for(
const auto& patch :
patches)
869 for(
const auto& edge : patch->edges())
871 if(!traversable(edge.get()))
873 physicalEdges.insert(edge.get());
877 for(
size_t i=0;
i<ports.size(); ++
i)
879 physicalEdges.insert(ports[
i].edge);
880 edgePorts[ports[
i].edge].push_back(
i);
884 std::map<const Edge*,const Edge*> boundaryNext;
885 std::map<const Edge*,const Edge*> boundaryPrevious;
886 std::set<const Edge*> hasPredecessor;
887 for(
const auto* edge : physicalEdges)
889 const Edge* next=nextPhysical(edge);
890 if(physicalEdges.count(next))
892 boundaryNext[edge]=next;
893 boundaryPrevious[next]=edge;
894 hasPredecessor.insert(next);
896 auto& list=edgePorts[edge];
897 std::stable_sort(list.begin(),list.end(),[&](
size_t a,
size_t b)
899 return ports[a].parameter<ports[b].parameter;
902 std::vector<const Edge*> seeds;
903 for(
const auto* edge : physicalEdges)
905 if(!hasPredecessor.count(edge))
907 seeds.push_back(edge);
910 seeds.insert(seeds.end(),physicalEdges.begin(),physicalEdges.end());
911 std::set<const Edge*> connected;
912 for(
const auto* seed : seeds)
914 if(connected.count(seed))
918 std::vector<std::vector<size_t>> buckets;
919 const Edge* edge=seed;
920 while(edge && connected.insert(edge).second)
922 const auto& list=edgePorts[edge];
923 for(
size_t i=0;
i<list.size();)
926 const double eps=tolerance/(*edge->sink-*edge->source).norm();
927 while(
j<list.size() && std::abs(ports[list[
j]].parameter-ports[list[
i]].parameter)<=eps)
931 buckets.emplace_back(list.begin()+
i,list.begin()+
j);
934 const auto next=boundaryNext.find(edge);
935 edge=next==boundaryNext.end() ? nullptr : next->second;
946 for(
size_t i=0;
i<buckets.size(); ++
i)
948 for(
size_t port : buckets[
i])
950 winding+=ports[port].exit ? 1 : -1;
960 throw std::runtime_error(
"Unbalanced physical-boundary intersection ports.");
962 bool zeroWindingCut=
false;
969 for(
size_t k=0;
k<buckets.size() && !zeroWindingCut;++
k)
971 const auto& port=ports[buckets[
k].front()];
972 const auto& previous=ports[buckets[(
k+buckets.size()-1)%buckets.size()].back()];
976 std::vector<VectorLowerDim>
samples;
977 const Edge* gapEdge=previous.edge;
979 bool wrap=gapEdge==port.edge && previous.parameter>=port.parameter;
980 std::set<const Edge*> gapVisited;
981 while(gapVisited.insert(gapEdge).second)
983 const bool last=gapEdge==port.edge && !wrap;
985 (forward ? *gapEdge->sink : *gapEdge->source);
986 if((gapEnd-gapStart).norm()>2*tolerance)
988 samples.push_back(0.5*(gapStart+gapEnd));
995 gapEdge=boundaryNext.at(gapEdge);
996 gapStart=forward ? *gapEdge->source : *gapEdge->sink;
998 if(gapEdge==port.edge)
1000 if((port.point-gapStart).norm()>2*tolerance)
1002 samples.push_back(0.5*(gapStart+port.point));
1007 for(
const auto& sample :
samples)
1009 int sampleWinding=0;
1010 bool onPolygon=
false;
1011 for(
size_t j=0;
j<polyPoints.size();++
j)
1015 if(std::abs(cross(
p,
q))<=tolerance*(
q-
p).norm() &&
p.dot(
q)<=tolerance*tolerance)
1020 if(
p.y()<=0 &&
q.y()>0 && cross(
p,
q)>0)
1024 else if(
p.y()>0 &&
q.y()<=0 && cross(
p,
q)<0)
1029 if(!onPolygon && sampleWinding==0)
1031 std::rotate(buckets.begin(),buckets.begin()+
k,buckets.end());
1032 zeroWindingCut=
true;
1038 if(edge!=seed || zeroWindingCut)
1044 std::vector<size_t> exits;
1045 std::vector<std::set<size_t>> pendingEntries;
1046 const auto connectPorts=[&](
size_t out,
size_t in,
bool reverse)
1048 successor[ports[
out].piece]=ports[
in].piece;
1049 boundaryReverse[ports[
out].piece]=reverse;
1050 if(edge==seed && ports[
out].edge==ports[
in].edge)
1052 const double delta=ports[
in].parameter-ports[
out].parameter;
1053 const double eps=tolerance/(*ports[
out].edge->sink-*ports[
out].edge->source).norm();
1054 boundaryWrap[ports[
out].piece]=reverse ? delta>eps : delta<-eps;
1057 const auto chooseEntry=[&](
size_t out,
const std::set<size_t>& entries)
1059 size_t in=ports[
out].polygonNext;
1060 while(!entries.count(
in) &&
in!=
out)
1062 in=ports[
in].polygonNext;
1066 throw std::runtime_error(
"Disconnected polygon intersection ports.");
1070 for(
const auto& bucket : buckets)
1072 std::vector<size_t> localExits;
1073 std::set<size_t> entries;
1074 for(
size_t port : bucket)
1076 if(ports[port].exit)
1078 localExits.push_back(port);
1082 entries.insert(port);
1087 while(!localExits.empty() && !entries.empty())
1089 const size_t out=localExits.back();
1090 localExits.pop_back();
1091 const size_t in=chooseEntry(
out,entries);
1092 connectPorts(
out,
in,
false);
1095 for(
size_t out : localExits)
1097 if(pendingEntries.empty())
1099 exits.push_back(
out);
1103 auto& candidates=pendingEntries.back();
1104 const size_t in=chooseEntry(
out,candidates);
1105 connectPorts(
out,
in,
true);
1106 candidates.erase(
in);
1107 if(candidates.empty())
1109 pendingEntries.pop_back();
1113 while(!entries.empty())
1117 pendingEntries.push_back(entries);
1122 const size_t out=exits.back();
1124 const size_t in=chooseEntry(
out,entries);
1125 connectPorts(
out,
in,
false);
1130 if(!exits.empty() || !pendingEntries.empty())
1132 throw std::runtime_error(
"Unbalanced open-boundary intersection ports.");
1136 std::vector<size_t> stack;
1137 for(
size_t k=0;
k<buckets.size(); ++
k)
1139 const auto& bucket=buckets[(
first+
k)%buckets.size()];
1140 std::set<size_t> entries;
1141 for(
size_t port : bucket)
1143 if(ports[port].exit)
1145 stack.push_back(port);
1149 entries.insert(port);
1152 while(!entries.empty())
1156 throw std::runtime_error(
"Physical-boundary entry has no exit port.");
1158 const size_t out=stack.back();
1163 size_t in=ports[
out].polygonNext;
1164 while(!entries.count(
in) &&
in!=
out)
1166 in=ports[
in].polygonNext;
1170 throw std::runtime_error(
"Disconnected polygon intersection ports.");
1172 successor[ports[
out].piece]=ports[
in].piece;
1175 boundaryWrap[ports[
out].piece]=ports[
out].edge==ports[
in].edge &&
1176 ports[
in].parameter<ports[
out].parameter-tolerance/(*ports[
out].edge->sink-*ports[
out].edge->source).norm();
1182 throw std::runtime_error(
"Physical-boundary exit has no entry port.");
1188 for(
size_t i=0;
i<pieces.size(); ++
i)
1190 if(pieces[
i].begin==0.0)
1196 const auto vertexAt=[&](
const Piece& piece,
bool sink)->std::optional<size_t>
1198 if(sink && piece.end==1.0)
1200 return (piece.segment+1)%polyPoints.size();
1202 if(!sink && piece.begin==0.0)
1204 return piece.segment;
1206 return std::nullopt;
1208 const auto appendPort=[&](
const VectorLowerDim& point,
const Patch* patch,
size_t segment,
1209 std::optional<size_t>
vertex,Arc arc,
bool seam)
1211 append(point,patch,
vertex ? polyPoints[*
vertex].second :
nullptr,
1212 metadata(point,segment,
vertex,arc,arc==Arc::OriginalSegment ? std::optional<size_t>(segment) : std::nullopt,seam));
1214 std::set<size_t> walked;
1220 throw std::runtime_error(
"Invalid polygon intersection successor cycle.");
1222 const auto& piece=pieces[
current];
1223 const size_t next=successor[
current];
1224 const auto& target=pieces[next];
1225 const bool boundaryArc=exitPort[
current]!=none;
1226 appendPort(piece.sink,piece.patch,piece.segment,vertexAt(piece,
true),Arc::OriginalSegment,
false);
1229 const Edge* edge=ports[exitPort[
current]].edge;
1230 const Edge* destination=ports[entryPort[next]].edge;
1231 const Patch* active=piece.patch;
1232 if(edge->patch!=active)
1234 std::get<6>(
result.back()).crossesPeriodicSeam=
true;
1236 appendPort(piece.sink,active,piece.segment,vertexAt(piece,
true),Arc::PeriodicSeam,
true);
1238 std::set<const Edge*> route;
1239 bool wrap=boundaryWrap[
current];
1240 while(edge!=destination || wrap)
1243 if(!route.insert(edge).second)
1245 throw std::runtime_error(
"Boundary arc cannot reach its assigned entry port.");
1248 append(
corner,active,
nullptr,metadata(
corner,std::nullopt,std::nullopt,Arc::PhysicalBoundary,std::nullopt,
false));
1249 const auto& adjacency=boundaryReverse[
current] ? boundaryPrevious : boundaryNext;
1250 const auto following=adjacency.find(edge);
1251 if(following==adjacency.end())
1253 throw std::runtime_error(
"Boundary arc has no stored successor.");
1255 edge=following->second;
1256 if(edge->patch!=active)
1258 std::get<6>(
result.back()).crossesPeriodicSeam=
true;
1260 append(
corner,active,
nullptr,metadata(
corner,std::nullopt,std::nullopt,Arc::PeriodicSeam,std::nullopt,
true));
1263 appendPort(target.source,active,target.segment,vertexAt(target,
false),Arc::PhysicalBoundary,
false);
1264 if(active!=target.patch)
1266 std::get<6>(
result.back()).crossesPeriodicSeam=
true;
1267 appendPort(target.source,target.patch,target.segment,vertexAt(target,
false),Arc::PeriodicSeam,
true);
1270 else if(piece.patch!=target.patch)
1272 std::get<6>(
result.back()).crossesPeriodicSeam=
true;
1273 appendPort(target.source,target.patch,target.segment,vertexAt(target,
false),Arc::PeriodicSeam,
true);
1297 for(
size_t i=begin;
i<
end; ++
i)
1302 for(
size_t i=begin;
i<=
end; ++
i)