17#ifndef ALICEO2_CADSUPPORT_BOUNDEDSURFACE_H_
18#define ALICEO2_CADSUPPORT_BOUNDEDSURFACE_H_
91 return {vector.xCoord * scale, vector.yCoord * scale, vector.zCoord * scale};
96 return vector * scale;
144 const double trace = gUU + gVV;
145 const double determinant = gUU * gVV - gUV * gUV;
147 const double discriminant = std::max(0., trace * trace - 4. * determinant);
148 return std::sqrt(std::max(0., 0.5 * (trace + std::sqrt(discriminant))));
154template <
typename Surface>
157 return {[](
const void* context,
const Vec2& uv,
double& gUU,
double& gUV,
double& gVV) {
158 static_cast<const Surface*
>(context)->parametricMetric(uv, gUU, gUV, gVV);
163inline double dot(
const Vec3& firstVector,
const Vec3& secondVector)
178 return dot(vector, vector);
183 return std::sqrt(
normSq(vector));
188 const double vectorNorm =
norm(vector);
192 return vector * (1. / vectorNorm);
197 if (dimension == 0) {
198 return vector.xCoord;
200 if (dimension == 1) {
201 return vector.yCoord;
203 return vector.zCoord;
208 if (dimension == 0) {
209 vector.xCoord =
value;
210 }
else if (dimension == 1) {
211 vector.yCoord =
value;
213 vector.zCoord =
value;
219 return std::isfinite(point.
uCoord) && std::isfinite(point.
vCoord);
224 return std::isfinite(point.
xCoord) && std::isfinite(point.
yCoord) && std::isfinite(point.
zCoord);
229 const double deltaU = firstPoint.
uCoord - secondPoint.
uCoord;
230 const double deltaV = firstPoint.
vCoord - secondPoint.
vCoord;
231 return deltaU * deltaU + deltaV * deltaV;
236 return normSq(firstPoint - secondPoint);
246 const Vec2 segmentVector = segmentEnd - segmentStart;
247 const double segmentLengthSq = segmentVector.
uCoord * segmentVector.
uCoord + segmentVector.
vCoord * segmentVector.
vCoord;
251 const double pointProjection = ((point.
uCoord - segmentStart.
uCoord) * segmentVector.
uCoord +
254 const double clampedProjection = std::max(0., std::min(1., pointProjection));
255 const Vec2 closestPoint{segmentStart.
uCoord + clampedProjection * segmentVector.
uCoord,
256 segmentStart.
vCoord + clampedProjection * segmentVector.
vCoord};
262 const Vec3 segmentVector = segmentEnd - segmentStart;
263 const double segmentLengthSq =
normSq(segmentVector);
267 const double pointProjection =
dot(point - segmentStart, segmentVector) / segmentLengthSq;
268 const double clampedProjection = std::max(0., std::min(1., pointProjection));
269 const Vec3 closestPoint = segmentStart + segmentVector * clampedProjection;
280 gUU =
dot(axisU, axisU);
281 gUV =
dot(axisU, axisV);
282 gVV =
dot(axisV, axisV);
297 gUU = radiusAtHeight * radiusAtHeight;
306 const double parallelRadius =
radius * std::sin(theta);
307 gUU = parallelRadius * parallelRadius;
316 const double ringRadius = majorRadius + minorRadius * std::cos(phiTube);
317 gUU = ringRadius * ringRadius;
319 gVV = minorRadius * minorRadius;
325 return std::abs(firstDistance - secondDistance) <=
360 const double segmentLengthSq = segmentVector.
uCoord * segmentVector.
uCoord +
366 const double projection = ((point.
uCoord -
start.uCoord) * segmentVector.
uCoord +
369 parameter = std::max(0., std::min(1., projection));
412 return "orientation normalized to match wire role";
414 return "wire contains a non-finite vertex";
416 return "wire edges do not form a closed loop";
418 return "wire needs at least three distinct vertices";
420 return "wire has a coincident (pinched) vertex";
422 return "wire has zero area";
424 return "unknown wire status";
430 const double scale = metric.
maxScale(uv);
449 return static_cast<int>(
index);
467 vertices.reserve(inputVertices.size());
468 bool droppedAVertex =
false;
470 for (
const auto&
vertex : inputVertices) {
478 droppedAVertex =
true;
485 droppedAVertex =
true;
494 for (
size_t firstIndex = 0; firstIndex <
vertices.size(); ++firstIndex) {
495 for (
size_t secondIndex = firstIndex + 1; secondIndex <
vertices.size(); ++secondIndex) {
510 const int storedCount =
static_cast<int>(
vertices.size());
511 sourceEdge.assign(
static_cast<size_t>(storedCount), -1);
512 if (!droppedAVertex) {
520 if ((area > 0.) != wantPositiveArea) {
524 std::vector<int> reversedSource(
static_cast<size_t>(storedCount), -1);
526 reversedSource[
static_cast<size_t>(
index)] =
527 sourceEdge[
static_cast<size_t>((storedCount - 2 -
index % storedCount + 2 * storedCount) % storedCount)];
542 if (
edges.size() < 3) {
546 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
551 const Vec2& nextStart =
edges[(edgeIndex + 1) %
edges.size()].start;
552 if (metric.distanceSq(
edges[edgeIndex].end, nextStart) > joinTolerance * joinTolerance) {
558 std::vector<Vec2> ringVertices;
559 ringVertices.reserve(
edges.size());
560 for (
const auto& singleEdge :
edges) {
561 ringVertices.push_back(singleEdge.start);
563 return initialize(ringVertices, wireRole, status, metric);
569 for (
size_t vertexIndex = 0; vertexIndex <
vertices.size(); ++vertexIndex) {
570 const auto& currentVertex =
vertices[vertexIndex];
572 area += currentVertex.uCoord * nextVertex.vCoord - nextVertex.uCoord * currentVertex.vCoord;
605 const double bandSq = band * band;
607 for (
size_t vertexIndex = 0; vertexIndex <
vertices.size(); ++vertexIndex) {
608 const auto& segmentStart =
vertices[vertexIndex];
613 const bool crossesScanline = (segmentStart.vCoord > point.
vCoord) != (segmentEnd.vCoord > point.
vCoord);
614 if (crossesScanline) {
615 const double intersectionU = segmentStart.uCoord + (point.
vCoord - segmentStart.vCoord) *
616 (segmentEnd.uCoord - segmentStart.uCoord) /
617 (segmentEnd.vCoord - segmentStart.vCoord);
618 if (point.
uCoord < intersectionU) {
634 const Vec2& thirdVertex)
636 const double firstCross =
cross2D(secondVertex - firstVertex, point - firstVertex);
637 const double secondCross =
cross2D(thirdVertex - secondVertex, point - secondVertex);
638 const double thirdCross =
cross2D(firstVertex - thirdVertex, point - thirdVertex);
643inline double ringSignedArea(
const std::vector<Vec2>& points,
const std::vector<int>& ring)
655 std::vector<int> ring(
static_cast<size_t>(
count));
656 std::iota(ring.begin(), ring.end(),
first);
658 std::reverse(ring.begin(), ring.end());
669 return cross2D(toNext, direction) > 0. &&
cross2D(direction, toPrevious) > 0.;
671 return !(
cross2D(toPrevious, direction) >= 0. &&
cross2D(direction, toNext) >= 0.);
677 const double secondStartSide =
cross2D(firstEnd - firstStart, secondStart - firstStart);
678 const double secondEndSide =
cross2D(firstEnd - firstStart, secondEnd - firstStart);
679 const double firstStartSide =
cross2D(secondEnd - secondStart, firstStart - secondStart);
680 const double firstEndSide =
cross2D(secondEnd - secondStart, firstEnd - secondStart);
681 if (secondStartSide * secondEndSide < 0. && firstStartSide * firstEndSide < 0.) {
691inline std::vector<int>
bridgeHoles(
const std::vector<Vec2>& outer,
const std::vector<std::vector<Vec2>>& holeLoops,
692 std::vector<Vec2>& points)
695 std::vector<int> ring =
orientedRing(points, 0,
static_cast<int>(outer.size()),
true);
696 std::vector<std::vector<int>> holes;
697 for (
const auto& loop : holeLoops) {
698 if (loop.size() >= 3) {
699 const int first =
static_cast<int>(points.size());
700 points.insert(points.end(), loop.begin(), loop.end());
701 holes.push_back(
orientedRing(points,
first,
static_cast<int>(loop.size()),
false));
704 auto maxU = [&](
const std::vector<int>& hole) {
705 double largest = -std::numeric_limits<double>::infinity();
706 for (
int index : hole) {
707 largest = std::max(largest, points[
index].uCoord);
712 std::stable_sort(holes.begin(), holes.end(), [&](
const auto&
first,
const auto& second) { return maxU(first) > maxU(second); });
715 auto bridgeIsClear = [&](
int holeVertex,
int ringVertex,
size_t firstPendingHole) {
716 auto crossesLoop = [&](
const std::vector<int>& loop) {
718 const int edgeStart = loop[
position];
719 const int edgeEnd = loop[(
position + 1) % loop.size()];
720 if (edgeStart == holeVertex || edgeEnd == holeVertex || edgeStart == ringVertex || edgeEnd == ringVertex) {
723 if (
segmentsTouch(points[holeVertex], points[ringVertex], points[edgeStart], points[edgeEnd])) {
729 if (crossesLoop(ring)) {
732 for (
size_t holeIndex = firstPendingHole; holeIndex < holes.size(); ++holeIndex) {
733 if (crossesLoop(holes[holeIndex])) {
740 for (
size_t holeIndex = 0; holeIndex < holes.size(); ++holeIndex) {
741 const auto& hole = holes[holeIndex];
742 const size_t holeCount = hole.size();
744 std::vector<size_t> holeOrder(holeCount);
745 std::iota(holeOrder.begin(), holeOrder.end(),
size_t{0});
746 std::stable_sort(holeOrder.begin(), holeOrder.end(),
747 [&](
size_t first,
size_t second) { return points[hole[first]].uCoord > points[hole[second]].uCoord; });
748 bool bridged =
false;
749 for (
size_t holePosition : holeOrder) {
750 const int holeVertex = hole[holePosition];
751 const Vec2& holePoint = points[holeVertex];
752 const Vec2& holePrevious = points[hole[(holePosition + holeCount - 1) % holeCount]];
753 const Vec2& holeNext = points[hole[(holePosition + 1) % holeCount]];
754 const size_t ringCount = ring.size();
755 std::vector<size_t> ringOrder(ringCount);
756 std::iota(ringOrder.begin(), ringOrder.end(),
size_t{0});
757 std::stable_sort(ringOrder.begin(), ringOrder.end(), [&](
size_t first,
size_t second) {
758 return distanceSq(points[ring[first]], holePoint) < distanceSq(points[ring[second]], holePoint);
760 for (
size_t ringPosition : ringOrder) {
761 const int ringVertex = ring[ringPosition];
762 const Vec2& ringPoint = points[ringVertex];
764 !
leavesIntoMaterial(points[ring[(ringPosition + ringCount - 1) % ringCount]], ringPoint,
765 points[ring[(ringPosition + 1) % ringCount]], holePoint - ringPoint) ||
767 !bridgeIsClear(holeVertex, ringVertex, holeIndex)) {
771 std::vector<int> splice;
772 splice.reserve(holeCount + 2);
773 for (
size_t step = 0; step <= holeCount; ++step) {
774 splice.push_back(hole[(holePosition + step) % holeCount]);
776 splice.push_back(ringVertex);
777 ring.insert(ring.begin() +
static_cast<std::ptrdiff_t
>(ringPosition) + 1, splice.begin(), splice.end());
790inline std::vector<std::array<int, 3>>
triangulateRing(
const std::vector<Vec2>& points, std::vector<int> remainingIndices)
792 std::vector<std::array<int, 3>> triangles;
793 const size_t ringCount = remainingIndices.size();
794 size_t guardCounter = 0;
795 while (remainingIndices.size() > 3 && guardCounter++ < ringCount * ringCount) {
796 bool clippedEar =
false;
797 for (
size_t indexPosition = 0; indexPosition < remainingIndices.size(); ++indexPosition) {
798 const int previousIndex = remainingIndices[(indexPosition + remainingIndices.size() - 1) % remainingIndices.size()];
799 const int currentIndex = remainingIndices[indexPosition];
800 const int nextIndex = remainingIndices[(indexPosition + 1) % remainingIndices.size()];
802 const auto& previousVertex = points[previousIndex];
803 const auto& currentVertex = points[currentIndex];
804 const auto& nextVertex = points[nextIndex];
805 if (
cross2D(currentVertex - previousVertex, nextVertex - currentVertex) <=
kTolerance) {
809 bool containsOtherVertex =
false;
810 for (
int candidateIndex : remainingIndices) {
811 if (candidateIndex == previousIndex || candidateIndex == currentIndex || candidateIndex == nextIndex) {
814 if (
pointInTriangle(points[candidateIndex], previousVertex, currentVertex, nextVertex)) {
815 containsOtherVertex =
true;
819 if (containsOtherVertex) {
823 triangles.push_back({previousIndex, currentIndex, nextIndex});
824 remainingIndices.erase(remainingIndices.begin() + indexPosition);
834 if (remainingIndices.size() == 3) {
835 triangles.push_back({remainingIndices[0], remainingIndices[1], remainingIndices[2]});
841inline bool coversRing(
const std::vector<Vec2>& points,
const std::vector<int>& ring,
const std::vector<std::array<int, 3>>& triangles)
844 for (
const auto& triangle : triangles) {
845 area += 0.5 *
cross2D(points[triangle[1]] - points[triangle[0]], points[triangle[2]] - points[triangle[0]]);
848 return std::abs(area - ringArea) <= 1.e-6 * std::abs(ringArea);
853inline constexpr double kPi = 3.14159265358979323846;
874 return std::max(1, std::min(fullTurnChunks,
static_cast<int>(std::ceil(span /
kCoverChunkAngle))));
880 const double atStart =
a * std::cos(
t0) +
b * std::sin(
t0);
881 const double atEnd =
a * std::cos(
t1) +
b * std::sin(
t1);
882 minimum = std::min(atStart, atEnd);
883 maximum = std::max(atStart, atEnd);
884 const double amplitude = std::hypot(
a,
b);
885 const double crest = std::atan2(
b,
a);
888 const double crestInRange = crest -
kTwoPi * std::floor((crest -
t0) /
kTwoPi);
889 if (crestInRange <=
t1) {
892 const double trough = crest +
kPi;
893 const double troughInRange = trough -
kTwoPi * std::floor((trough -
t0) /
kTwoPi);
894 if (troughInRange <=
t1) {
895 minimum = -amplitude;
927 return delta <= sweep + tolerance || delta >=
kTwoPi - tolerance;
933 nodes.assign(std::max(
n, 1), 0.);
938 for (
int i = 0;
i <
n; ++
i) {
939 double root = std::cos(
kPi * (
i + 0.75) / (
n + 0.5));
941 for (
int iteration = 0; iteration < 100; ++iteration) {
942 double previous = 1.;
943 double current = root;
944 for (
int degreeIndex = 2; degreeIndex <=
n; ++degreeIndex) {
945 const double next = ((2 * degreeIndex - 1) * root * current - (degreeIndex - 1) * previous) / degreeIndex;
949 derivative =
n * (root * current - previous) / (root * root - 1.);
952 if (std::abs(delta) < 1.e-15) {
965 const double discriminant = coeffQ * coeffQ / 4. + coeffP * coeffP * coeffP / 27.;
966 if (!(coeffP < 0.) || discriminant > 0.) {
967 const double sqrtDiscriminant = std::sqrt(std::max(0., discriminant));
968 roots[0] = std::cbrt(-0.5 * coeffQ + sqrtDiscriminant) + std::cbrt(-0.5 * coeffQ - sqrtDiscriminant);
972 const double magnitude = 2. * std::sqrt(-coeffP / 3.);
973 const double cosineArgument = std::max(-1., std::min(1., 3. * coeffQ / (coeffP * magnitude)));
974 const double baseAngle = std::acos(cosineArgument);
975 for (
int branch = 0; branch < 3; ++branch) {
976 roots[branch] = magnitude * std::cos((baseAngle -
kTwoPi * branch) / 3.);
994 assert(
count < 4 &&
"QuarticRoots holds at most four roots");
1013 *takenBranch = branch;
1020 if (!(std::abs(a4) > 0.)) {
1024 double coeffB = a3 / a4, coeffC = a2 / a4, coeffD = a1 / a4, coeffE = a0 / a4;
1025 if (!std::isfinite(coeffB) || !std::isfinite(coeffC) || !std::isfinite(coeffD) || !std::isfinite(coeffE)) {
1029 const double rootBound = std::max({std::abs(coeffB), std::sqrt(std::abs(coeffC)),
1030 std::cbrt(std::abs(coeffD)), std::sqrt(std::sqrt(std::abs(coeffE)))});
1031 int boundExponent = 0;
1032 std::frexp(rootBound, &boundExponent);
1033 const double scale = std::ldexp(1., boundExponent);
1035 coeffC /= scale * scale;
1036 coeffD /= scale * scale * scale;
1037 coeffE /= scale * scale * scale * scale;
1040 const double termP = coeffC - 3. * coeffB * coeffB / 8.;
1041 const double termQ = coeffD - coeffB * coeffC / 2. + coeffB * coeffB * coeffB / 8.;
1042 const double termR =
1043 coeffE - coeffB * coeffD / 4. + coeffB * coeffB * coeffC / 16. - 3. * coeffB * coeffB * coeffB * coeffB / 256.;
1044 const double shift = -coeffB / 4.;
1046 auto addQuadraticRoots = [&](
double quadB,
double quadC) {
1047 const double discriminant = quadB * quadB - 4. * quadC;
1048 if (discriminant < 0.) {
1051 const double sqrtDiscriminant = std::sqrt(discriminant);
1052 roots.
push_back(shift + 0.5 * (-quadB - sqrtDiscriminant));
1053 roots.
push_back(shift + 0.5 * (-quadB + sqrtDiscriminant));
1056 auto addBiquadraticRoots = [&]() {
1058 const double discriminant = termP * termP - 4. * termR;
1059 if (discriminant < 0.) {
1062 const double sqrtDiscriminant = std::sqrt(discriminant);
1063 for (
const double zSquared : {0.5 * (-termP + sqrtDiscriminant), 0.5 * (-termP - sqrtDiscriminant)}) {
1064 if (zSquared >= 0.) {
1065 const double z = std::sqrt(zSquared);
1077 const double cubicA2 = termP;
1078 const double cubicA1 = termP * termP / 4. - termR;
1079 const double cubicA0 = -termQ * termQ / 8.;
1080 const double cubicP = cubicA1 - cubicA2 * cubicA2 / 3.;
1081 const double cubicQ = 2. * cubicA2 * cubicA2 * cubicA2 / 27. - cubicA2 * cubicA1 / 3. + cubicA0;
1082 std::array<double, 3> cubicRoots;
1084 double resolvent = 0.;
1086 resolvent = std::max(resolvent, cubicRoots[
index] - cubicA2 / 3.);
1089 const double resolventScale = std::max({std::abs(cubicA2), std::sqrt(std::abs(cubicA1)),
1090 std::cbrt(std::abs(cubicA0))});
1092 const double sqrtTwoResolvent = std::sqrt(2. * resolvent);
1093 const double linearTerm = sqrtTwoResolvent * termQ / (4. * resolvent);
1094 addQuadraticRoots(-sqrtTwoResolvent, termP / 2. + resolvent + linearTerm);
1095 addQuadraticRoots(sqrtTwoResolvent, termP / 2. + resolvent - linearTerm);
1102 addBiquadraticRoots();
1106 auto quartic = [&](
double x) {
return (((
x + coeffB) *
x + coeffC) *
x + coeffD) *
x + coeffE; };
1107 auto quarticDerivative = [&](
double x) {
return ((4. *
x + 3. * coeffB) *
x + 2. * coeffC) *
x + coeffD; };
1108 for (
double& root : roots) {
1109 for (
int iteration = 0; iteration < 2; ++iteration) {
1110 const double step = quartic(root) / quarticDerivative(root);
1111 if (std::isfinite(step) && std::abs(step) <= 2.) {
1116 for (
double& root : roots) {
1176 static Curve2D makeArc(
const Vec2& arcCenter,
double arcRadius,
double arcStartAngle,
double arcEndAngle)
1180 curve.
center = arcCenter;
1181 curve.
radius = arcRadius;
1196 std::vector<double> splineWeights, std::vector<double> splineKnots)
1200 curve.
degree = splineDegree;
1201 curve.
poles = std::move(splinePoles);
1202 curve.
weights = std::move(splineWeights);
1203 curve.
knots = std::move(splineKnots);
1220 const size_t lastKnot =
knots.size() - 1;
1244 const int lastPole =
static_cast<int>(
poles.size()) - 1;
1245 if (knotValue >=
knots[lastPole + 1]) {
1252 int high = lastPole + 1;
1253 int mid = (low + high) / 2;
1254 while (knotValue <
knots[mid] || knotValue >=
knots[mid + 1]) {
1255 if (knotValue <
knots[mid]) {
1260 mid = (low + high) / 2;
1267 std::vector<double>& basisDeriv)
const
1270 std::vector<std::vector<double>> ndu(p + 1, std::vector<double>(p + 1, 0.));
1271 std::vector<double>
left(p + 1, 0.);
1272 std::vector<double>
right(p + 1, 0.);
1274 for (
int j = 1;
j <= p; ++
j) {
1278 for (
int r = 0;
r <
j; ++
r) {
1280 const double temp = ndu[
r][
j - 1] / ndu[
j][
r];
1281 ndu[
r][
j] = saved +
right[
r + 1] * temp;
1282 saved =
left[
j -
r] * temp;
1286 basis.assign(p + 1, 0.);
1287 basisDeriv.assign(p + 1, 0.);
1288 for (
int j = 0;
j <= p; ++
j) {
1289 basis[
j] = ndu[
j][p];
1292 for (
int r = 0;
r <= p; ++
r) {
1294 const int pk = p - 1;
1296 d += (1. / ndu[pk + 1][
r - 1]) * ndu[
r - 1][pk];
1299 d += (-1. / ndu[pk + 1][
r]) * ndu[
r][pk];
1301 basisDeriv[
r] =
d * p;
1311 std::vector<double> basis;
1312 std::vector<double> basisDeriv;
1314 Vec2 weightedSum{0., 0.};
1315 Vec2 weightedDeriv{0., 0.};
1316 double weightTotal = 0.;
1317 double weightDeriv = 0.;
1318 for (
int j = 0;
j <= p; ++
j) {
1319 const int idx = span - p +
j;
1321 weightedSum.uCoord += basis[
j] *
weight *
poles[idx].uCoord;
1322 weightedSum.vCoord += basis[
j] *
weight *
poles[idx].vCoord;
1323 weightTotal += basis[
j] *
weight;
1324 weightedDeriv.uCoord += basisDeriv[
j] *
weight *
poles[idx].uCoord;
1325 weightedDeriv.vCoord += basisDeriv[
j] *
weight *
poles[idx].vCoord;
1326 weightDeriv += basisDeriv[
j] *
weight;
1328 const double invWeight = (std::abs(weightTotal) >
kTolerance) ? 1. / weightTotal : 0.;
1329 pointOut = {weightedSum.
uCoord * invWeight, weightedSum.vCoord * invWeight};
1330 derivativeOut = {(weightedDeriv.uCoord * weightTotal - weightedSum.uCoord * weightDeriv) * invWeight * invWeight,
1331 (weightedDeriv.vCoord * weightTotal - weightedSum.vCoord * weightDeriv) * invWeight * invWeight};
1350 Vec2 startPointValue;
1352 Vec2 unusedDerivative;
1355 samples.push_back(startPointValue);
1364 const size_t firstInterior =
static_cast<size_t>(
degree) + 1;
1365 const size_t endInterior = std::min(
poles.size(),
knots.size());
1366 if (firstInterior >= endInterior) {
1369 const auto begin =
knots.begin() +
static_cast<std::ptrdiff_t
>(firstInterior);
1370 const auto end =
knots.begin() +
static_cast<std::ptrdiff_t
>(endInterior);
1371 const auto firstAbove = std::upper_bound(begin,
end, lowT);
1372 return firstAbove !=
end && *firstAbove < highT;
1386 const size_t firstInterior =
static_cast<size_t>(
degree) + 1;
1387 const size_t endInterior = std::min(
poles.size(),
knots.size());
1405 const double knotSpan =
bsplineT1() - knotStart;
1406 const double knotMid = knotStart + 0.5 * (from + to) * knotSpan;
1407 size_t spanIndex =
static_cast<size_t>(
degree);
1408 while (spanIndex + 1 <
poles.size() && spanIndex + 1 <
knots.size() &&
knots[spanIndex + 1] <= knotMid) {
1411 const size_t firstPole = spanIndex -
static_cast<size_t>(
degree);
1412 double lowU = std::numeric_limits<double>::infinity();
1413 double highU = -std::numeric_limits<double>::infinity();
1416 highU = std::max(highU,
poles[
index].uCoord);
1418 return (highU >= lowU) ? (highU - lowU) : 0.;
1424 const double low = std::min(angleFrom, angleTo);
1425 const double high = std::max(angleFrom, angleTo);
1426 double variation = 0.;
1427 double previous = low;
1428 const double firstTurn = std::ceil(low /
kPi) *
kPi;
1429 for (
double turn = firstTurn; turn < high; turn +=
kPi) {
1430 variation += std::abs(
radius * (std::cos(turn) - std::cos(previous)));
1433 return variation + std::abs(
radius * (std::cos(high) - std::cos(previous)));
1439 const double tMid = 0.5 * (
t0 +
t1);
1441 Vec2 unusedDerivative;
1445 const auto deviationSq = [&](
const Vec2& point) {
1449 double flatness = deviationSq(midPoint);
1450 for (
const double fraction : {0.25, 0.75}) {
1453 flatness = std::max(flatness, deviationSq(probePoint));
1490 const int nPoles =
static_cast<int>(
poles.size());
1494 if (
static_cast<int>(
knots.size()) != nPoles +
degree + 1) {
1497 if (!
weights.empty() &&
static_cast<int>(
weights.size()) != nPoles) {
1500 for (
const auto& pole :
poles) {
1580 const double length = std::sqrt(delta.uCoord * delta.uCoord + delta.vCoord * delta.vCoord);
1599 const double direction =
sweep() >= 0. ? 1. : -1.;
1600 return {-direction * std::sin(
angle), direction * std::cos(
angle)};
1606 const double totalSweep =
sweep();
1607 const double magnitude = std::abs(totalSweep);
1619 const double totalSweep =
sweep();
1625 return std::max(0., std::min(1., delta / std::abs(totalSweep)));
1631 auto include = [&](
const Vec2& point) {
1640 for (
const auto& pole :
poles) {
1651 auto include = [&](
const Vec2& point) {
1667 template <
typename Include>
1675 for (
double cardinal : cardinalAngles) {
1689 if (polyline.size() < 2) {
1693 const int segments =
static_cast<int>(polyline.size()) - 1;
1694 double bestDistanceSq = std::numeric_limits<double>::infinity();
1695 Vec2 bestPoint = polyline.front();
1696 double bestParameter = 0.;
1698 const Vec2 segmentStart = polyline[
index];
1699 const Vec2 segmentVector = polyline[
index + 1] - segmentStart;
1700 const double segmentLengthSq =
1702 double projection = 0.;
1707 projection = std::max(0., std::min(1., projection));
1709 const Vec2 candidate{segmentStart.
uCoord + projection * segmentVector.
uCoord,
1710 segmentStart.
vCoord + projection * segmentVector.
vCoord};
1712 if (candidateDistanceSq < bestDistanceSq) {
1713 bestDistanceSq = candidateDistanceSq;
1714 bestPoint = candidate;
1731 parameter = std::max(0., std::min(1., projection));
1737 if (deltaU * deltaU + deltaV * deltaV <=
kToleranceSq) {
1741 const double angle = std::atan2(deltaV, deltaU);
1750 return startCandidate;
1753 return endCandidate;
1773 const int order =
bsplineRational() ? std::max(2 * p + 2, 8) : (p + 1);
1774 std::vector<double>
nodes;
1775 std::vector<double> nodeWeights;
1778 const int lastSpan =
static_cast<int>(
poles.size()) - 1;
1779 for (
int spanIndex = p; spanIndex <= lastSpan; ++spanIndex) {
1780 const double spanLow =
knots[spanIndex];
1781 const double spanHigh =
knots[spanIndex + 1];
1782 const double halfSpan = 0.5 * (spanHigh - spanLow);
1786 const double spanMid = 0.5 * (spanLow + spanHigh);
1787 for (
int nodeIndex = 0; nodeIndex < order; ++nodeIndex) {
1788 const double knotValue = spanMid + halfSpan *
nodes[nodeIndex];
1793 nodeWeights[nodeIndex] * halfSpan;
1811 if (polyline.size() < 2) {
1816 const Vec2 segmentStart = polyline[
index];
1817 const Vec2 segmentEnd = polyline[
index + 1];
1818 const Vec2 segmentVector = segmentEnd - segmentStart;
1819 const double segmentLengthSq =
1821 double projection = 0.;
1826 projection = std::max(0., std::min(1., projection));
1828 const Vec2 candidate{segmentStart.
uCoord + projection * segmentVector.
uCoord,
1829 segmentStart.
vCoord + projection * segmentVector.
vCoord};
1833 const bool firstAbove = segmentStart.
vCoord > point.
vCoord;
1834 const bool secondAbove = segmentEnd.
vCoord > point.
vCoord;
1835 if (firstAbove != secondAbove) {
1836 const double intersectU =
1839 if (point.
uCoord < intersectU) {
1851 auto segmentCrossing = [&](
const Vec2&
first,
const Vec2& second,
double exactIntersectU) {
1852 const bool firstAbove =
first.vCoord > point.
vCoord;
1853 const bool secondAbove = second.vCoord > point.
vCoord;
1854 if (firstAbove == secondAbove) {
1857 return point.
uCoord < exactIntersectU;
1863 if (firstAbove == secondAbove) {
1869 return (point.
uCoord < intersectU) ? 1 : 0;
1875 if (polyline.size() < 2) {
1883 const Vec2 second = polyline[
index + 1];
1886 if (firstAbove == secondAbove) {
1889 const double intersectU =
1892 if (point.
uCoord < intersectU) {
1900 const double totalSweep =
sweep();
1904 std::array<double, 8> breakParameters{};
1906 breakParameters[breakCount++] = 0.;
1909 const int firstK =
static_cast<int>(std::floor((lowAngle -
kHalfPi) /
kPi)) - 1;
1910 const int lastK =
static_cast<int>(std::ceil((highAngle -
kHalfPi) /
kPi)) + 1;
1911 for (
int k = firstK; k <= lastK && breakCount < 7; ++k) {
1912 const double extremeAngle =
kHalfPi + k *
kPi;
1913 if (extremeAngle <= lowAngle + kTolerance || extremeAngle >= highAngle -
kTolerance) {
1916 const double extremeParameter = (extremeAngle -
startAngle) / totalSweep;
1918 breakParameters[breakCount++] = extremeParameter;
1921 breakParameters[breakCount++] = 1.;
1922 std::sort(breakParameters.begin(), breakParameters.begin() + breakCount);
1925 ratio = std::max(-1., std::min(1., ratio));
1926 const double cosMagnitude = std::sqrt(std::max(0., 1. - ratio * ratio));
1932 const double midAngle =
startAngle + 0.5 * (breakParameters[
index] + breakParameters[
index + 1]) * totalSweep;
1933 const double cosSign = std::cos(midAngle) >= 0. ? 1. : -1.;
1935 if (segmentCrossing(subStart, subEnd, intersectU)) {
1960 const double knotSum =
knots.front() +
knots.back();
1961 std::vector<double> reversedKnots(
knots.size());
1965 knots = std::move(reversedKnots);
1986 return static_cast<int>(
index);
1999 for (
const auto& curve :
curves) {
2018 if (metric.distanceSq(currentEnd, nextStart) > joinTolerance * joinTolerance) {
2037 if ((area > 0.) != wantPositiveArea) {
2051 for (
const auto& curve :
curves) {
2053 curve.bsplineSamples();
2066 for (
auto& curve :
curves) {
2067 curve.reverseInPlace();
2075 for (
const auto& curve :
curves) {
2087 for (
const auto& curve :
curves) {
2088 area += curve.signedAreaContribution();
2096 for (
const auto& curve :
curves) {
2104 for (
const auto& curve :
curves) {
2117 const double bandSq = band * band;
2121 for (
const auto& curve :
curves) {
2123 if (curve.bsplineBandOrCrossings(point, bandSq, crossings)) {
2126 }
else if (curve.distanceSq(point) <= bandSq) {
2129 crossings += curve.rightwardCrossings(point, curve.loopStart(), curve.loopEnd());
2149 for (
const auto& curve :
curves) {
2151 samples.push_back(curve.startPoint());
2155 std::vector<Vec2> curveSamples;
2156 curve.bsplineSampleInto(curveSamples);
2162 const int arcSteps =
2163 std::max(1,
static_cast<int>(std::lround(segmentsPerArc * std::abs(curve.sweep()) /
kTwoPi)));
2164 for (
int step = 0; step < arcSteps; ++step) {
2165 samples.push_back(curve.pointAt(
static_cast<double>(step) / arcSteps));
2180 const double windowCenter = 0.5 * (uMin + uMax);
2186 const Vec2& point,
bool* boundary =
nullptr,
2189 if (boundary !=
nullptr) {
2193 const auto outerClassification = outerWire.
classify(point, lengthFloor);
2198 if (boundary !=
nullptr) {
2203 for (
const auto& innerWire : innerWires) {
2204 const auto innerClassification = innerWire.classify(point, lengthFloor);
2206 if (boundary !=
nullptr) {
2224template <
typename Ant
iderivative>
2228 static thread_local std::vector<double>
nodes;
2229 static thread_local std::vector<double>
weights;
2234 static thread_local std::vector<double> breakpoints;
2235 breakpoints.clear();
2236 breakpoints.push_back(from);
2238 std::sort(breakpoints.begin() + 1, breakpoints.end(),
2239 [forward = (to >= from)](
double first,
double second) { return forward ? first < second : first > second; });
2240 breakpoints.push_back(to);
2244 const double segmentFrom = breakpoints[
segment];
2245 const double segmentTo = breakpoints[
segment + 1];
2246 if (segmentFrom == segmentTo) {
2249 const double travelU = curve.
uVariation(std::min(segmentFrom, segmentTo), std::max(segmentFrom, segmentTo));
2250 const int pieces = std::max(1,
static_cast<int>(std::ceil(travelU /
kContourMaxSpanU)));
2251 for (
int piece = 0; piece < pieces; ++piece) {
2252 const double low = segmentFrom + (segmentTo - segmentFrom) * piece / pieces;
2253 const double high = segmentFrom + (segmentTo - segmentFrom) * (piece + 1) / pieces;
2254 const double half = 0.5 * (high - low);
2255 const double mid = 0.5 * (high + low);
2268template <
typename Ant
iderivative>
2270 const Antiderivative& antiderivative)
2272 const auto loopIntegral = [&antiderivative](
const CurveWire& wire) {
2275 const auto& curve = wire.curves[
index];
2278 const Vec2 seamFrom = curve.endPoint();
2279 const Vec2 seamTo = wire.curves[(
index + 1) % wire.curves.size()].startPoint();
2289 double total = loopIntegral(outerWire);
2290 for (
const auto& innerWire : innerWires) {
2291 total += loopIntegral(innerWire);
2297template <
typename Integrand>
2299 const Integrand& integrand,
int samplesPerAxis = 128)
2301 Vec2 lower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2302 Vec2 upper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2307 const double stepU = (
upper.uCoord -
lower.uCoord) / samplesPerAxis;
2308 const double stepV = (
upper.vCoord -
lower.vCoord) / samplesPerAxis;
2309 const double cellArea = stepU * stepV;
2311 for (
int indexU = 0; indexU < samplesPerAxis; ++indexU) {
2312 const double uCoord =
lower.uCoord + (indexU + 0.5) * stepU;
2313 for (
int indexV = 0; indexV < samplesPerAxis; ++indexV) {
2314 const double vCoord =
lower.vCoord + (indexV + 0.5) * stepV;
2316 sum += integrand(uCoord, vCoord) * cellArea;
2325 const std::vector<std::vector<Curve2D>>& innerTrims,
CurveWire& outerWire,
2332 errorMessage = std::string(
"quadric outer trim wire invalid: ") +
wireStatusMessage(status);
2336 innerWires.reserve(innerTrims.size());
2337 for (
const auto& innerLoop : innerTrims) {
2338 CurveWire innerWire;
2340 if (!innerWire.initialize(innerLoop,
WireRole::Inner, innerStatus, metric, joinTolerance)) {
2341 errorMessage = std::string(
"quadric inner trim wire invalid: ") +
wireStatusMessage(innerStatus);
2344 innerWires.push_back(std::move(innerWire));
2346 lower = {std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2347 upper = {-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2350 errorMessage =
"quadric trim wire has non-finite parametric bounds";
2355 Vec2 tightLower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2356 Vec2 tightUpper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2359 errorMessage =
"quadric trim wire spans more than a full turn in phi";
2374 for (
const auto& curve : wire.
curves) {
2377 std::vector<Vec2> curveSamples;
2378 curve.bsplineSampleInto(curveSamples);
2384 Vec2 lower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2385 Vec2 upper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2388 int steps = std::max(1,
static_cast<int>(std::lround(segmentsPerTurn * uSpan /
kTwoPi)));
2390 steps = std::max(steps,
static_cast<int>(std::lround(segmentsPerTurn * std::abs(curve.sweep()) /
kTwoPi)));
2391 steps = std::max(steps, 1);
2393 for (
int step = 0; step < steps; ++step) {
2394 samples.push_back(curve.pointAt(
static_cast<double>(step) / steps));
2402 const std::vector<Vec3>& points,
2403 std::vector<int> remainingIndices)
2405 const std::vector<int> ring = remainingIndices;
2408 const double notAnEar = std::numeric_limits<double>::infinity();
2409 auto earChord = [&](
size_t indexPosition) {
2410 const int previousIndex = remainingIndices[(indexPosition + remainingIndices.size() - 1) % remainingIndices.size()];
2411 const int currentIndex = remainingIndices[indexPosition];
2412 const int nextIndex = remainingIndices[(indexPosition + 1) % remainingIndices.size()];
2413 const auto& previousVertex = uvPoints[previousIndex];
2414 const auto& currentVertex = uvPoints[currentIndex];
2415 const auto& nextVertex = uvPoints[nextIndex];
2416 if (
cross2D(currentVertex - previousVertex, nextVertex - currentVertex) <=
kTolerance) {
2419 for (
int candidateIndex : remainingIndices) {
2420 if (candidateIndex == previousIndex || candidateIndex == currentIndex || candidateIndex == nextIndex) {
2423 if (
pointInTriangle(uvPoints[candidateIndex], previousVertex, currentVertex, nextVertex)) {
2427 return norm(points[nextIndex] - points[previousIndex]);
2430 std::vector<double> chords(remainingIndices.size());
2431 for (
size_t indexPosition = 0; indexPosition < remainingIndices.size(); ++indexPosition) {
2432 chords[indexPosition] = earChord(indexPosition);
2435 std::vector<std::array<int, 3>> triangles;
2436 while (remainingIndices.size() > 3) {
2438 size_t bestPosition = remainingIndices.size();
2439 double bestChord = notAnEar;
2440 for (
size_t indexPosition = 0; indexPosition < remainingIndices.size(); ++indexPosition) {
2441 if (chords[indexPosition] < bestChord) {
2442 bestChord = chords[indexPosition];
2443 bestPosition = indexPosition;
2446 if (bestPosition == remainingIndices.size()) {
2450 const size_t count = remainingIndices.size();
2451 const int previousIndex = remainingIndices[(bestPosition +
count - 1) %
count];
2452 const int clippedIndex = remainingIndices[bestPosition];
2453 const int nextIndex = remainingIndices[(bestPosition + 1) %
count];
2454 triangles.push_back({previousIndex, clippedIndex, nextIndex});
2455 remainingIndices.erase(remainingIndices.begin() + bestPosition);
2456 chords.erase(chords.begin() + bestPosition);
2459 const size_t newCount = remainingIndices.size();
2460 const size_t beforePosition = (bestPosition + newCount - 1) % newCount;
2461 const size_t afterPosition = bestPosition % newCount;
2462 for (
size_t indexPosition = 0; indexPosition < newCount; ++indexPosition) {
2463 if (indexPosition == beforePosition || indexPosition == afterPosition) {
2464 chords[indexPosition] = earChord(indexPosition);
2465 }
else if (chords[indexPosition] == notAnEar) {
2466 const auto& previousVertex = uvPoints[remainingIndices[(indexPosition + newCount - 1) % newCount]];
2467 const auto& currentVertex = uvPoints[remainingIndices[indexPosition]];
2468 const auto& nextVertex = uvPoints[remainingIndices[(indexPosition + 1) % newCount]];
2469 if (
pointInTriangle(uvPoints[clippedIndex], previousVertex, currentVertex, nextVertex)) {
2470 chords[indexPosition] = earChord(indexPosition);
2476 if (remainingIndices.size() == 3) {
2477 triangles.push_back({remainingIndices[0], remainingIndices[1], remainingIndices[2]});
2483template <
typename MapUV>
2485 std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
2488 if (outerSamples.size() < 3) {
2491 std::vector<std::vector<Vec2>> holes;
2492 for (
const auto& innerWire : innerWires) {
2495 std::vector<Vec2> uvPoints;
2496 const auto ring =
bridgeHoles(outerSamples, holes, uvPoints);
2497 const int firstVertexIndex =
static_cast<int>(vertices.size());
2498 for (
const auto& sample : uvPoints) {
2499 vertices.push_back(mapUV(sample.uCoord, sample.vCoord));
2501 const std::vector<Vec3> points(vertices.begin() + firstVertexIndex, vertices.end());
2503 if (!
coversRing(uvPoints, ring, localTriangles)) {
2506 for (
const auto& triangle : localTriangles) {
2507 triangles.push_back(
2508 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
2513template <
typename MapUV>
2515 const MapUV& mapUV,
double orientationSign,
2516 std::vector<std::pair<Vec3, Vec3>>&
edges)
2518 auto appendLoop = [&](
const CurveWire& wire) {
2520 const size_t sampleCount =
samples.size();
2521 for (
size_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
2523 const Vec2& next =
samples[(sampleIndex + 1) % sampleCount];
2526 if (orientationSign >= 0.) {
2527 edges.emplace_back(edgeStart, edgeEnd);
2529 edges.emplace_back(edgeEnd, edgeStart);
2533 appendLoop(outerWire);
2534 for (
const auto& innerWire : innerWires) {
2535 appendLoop(innerWire);
2543template <
typename MapUV>
2545 size_t index,
const MapUV& mapUV, std::vector<Vec3>&
samples)
2548 size_t local =
index;
2549 if (local < outerWire.
curves.size()) {
2552 local -= outerWire.
curves.size();
2553 for (
const auto& innerWire : innerWires) {
2554 if (local < innerWire.curves.size()) {
2558 local -= innerWire.
curves.size();
2561 if (wire ==
nullptr) {
2568 const Curve2D& curve = wire->
curves[
static_cast<size_t>(stored)];
2579template <
typename MapUV>
2581 size_t index,
const MapUV& mapUV, std::vector<Vec3>&
samples)
2584 size_t local =
index;
2585 if (local < outerWire.
vertices.size()) {
2588 local -= outerWire.
vertices.size();
2589 for (
const auto& innerWire : innerWires) {
2590 if (local < innerWire.vertices.size()) {
2594 local -= innerWire.
vertices.size();
2597 if (wire ==
nullptr) {
2620inline void assembleRims(
const std::vector<std::pair<Vec3, Vec3>>&
edges, std::vector<SurfaceRim>& rims)
2622 if (
edges.empty()) {
2626 using VertexKey = std::tuple<int64_t, int64_t, int64_t>;
2627 auto keyOf = [&](
const Vec3& point) {
2628 return VertexKey{quantize(point.xCoord), quantize(point.yCoord), quantize(point.zCoord)};
2632 std::vector<bool> consumed(
edges.size(),
false);
2633 std::map<VertexKey, std::vector<size_t>> edgesByMidpoint;
2634 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
2635 const Vec3 midpoint = (
edges[edgeIndex].first +
edges[edgeIndex].second) * 0.5;
2636 const auto [xKey, yKey, zKey] = keyOf(midpoint);
2637 bool cancelled =
false;
2638 for (
int64_t dx = -1; dx <= 1 && !cancelled; ++dx) {
2639 for (
int64_t dy = -1; dy <= 1 && !cancelled; ++dy) {
2640 for (
int64_t dz = -1; dz <= 1 && !cancelled; ++dz) {
2641 const auto found = edgesByMidpoint.find(VertexKey{xKey + dx, yKey + dy, zKey + dz});
2642 if (found == edgesByMidpoint.end()) {
2645 for (
const size_t candidate : found->second) {
2646 if (consumed[candidate] ||
2651 consumed[candidate] =
true;
2652 consumed[edgeIndex] =
true;
2660 edgesByMidpoint[keyOf(midpoint)].push_back(edgeIndex);
2664 std::map<VertexKey, std::vector<size_t>> edgesByStart;
2665 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
2666 if (!consumed[edgeIndex]) {
2667 edgesByStart[keyOf(
edges[edgeIndex].
first)].push_back(edgeIndex);
2673 auto findSuccessor = [&](
const Vec3& point) ->
long long {
2674 const auto [xKey, yKey, zKey] = keyOf(point);
2675 for (
int64_t dx = -1; dx <= 1; ++dx) {
2676 for (
int64_t dy = -1; dy <= 1; ++dy) {
2677 for (
int64_t dz = -1; dz <= 1; ++dz) {
2678 const auto found = edgesByStart.find(VertexKey{xKey + dx, yKey + dy, zKey + dz});
2679 if (found == edgesByStart.end()) {
2682 for (
const size_t candidate : found->second) {
2684 return static_cast<long long>(candidate);
2693 for (
size_t seed = 0; seed <
edges.size(); ++seed) {
2694 if (consumed[seed]) {
2697 consumed[seed] =
true;
2707 const long long next = findSuccessor(rim.
points.back());
2711 consumed[
static_cast<size_t>(next)] =
true;
2712 rim.
points.push_back(
edges[
static_cast<size_t>(next)].second);
2714 if (rim.
points.size() >= 2) {
2715 rims.push_back(std::move(rim));
2759 constexpr double kBig = std::numeric_limits<double>::max();
2762 boxes.push_back(
box);
2770 double maxDistance, std::vector<RayHit>& hits)
const = 0;
2799 std::vector<std::array<int, 3>>& triangles)
const = 0;
2807 std::vector<std::pair<Vec3, Vec3>>
edges;
2822 const std::vector<Vec2>& outerWireVertices,
2823 const std::vector<std::vector<Vec2>>& innerWireVertices, std::string& errorMessage)
2826 errorMessage =
"surface frame contains a non-finite value";
2830 mOrigin = surfaceOrigin;
2831 mAxisU = surfaceAxisU;
2832 mAxisV = surfaceAxisV;
2833 const Vec3 normalVector =
cross(mAxisU, mAxisV);
2834 mAreaScale =
norm(normalVector);
2836 errorMessage =
"surface frame axes are degenerate";
2839 mNormal = normalVector * (1. / mAreaScale);
2841 mMetricUU =
dot(mAxisU, mAxisU);
2842 mMetricUV =
dot(mAxisU, mAxisV);
2843 mMetricVV =
dot(mAxisV, mAxisV);
2844 const double metricDet = mMetricUU * mMetricVV - mMetricUV * mMetricUV;
2846 errorMessage =
"surface frame metric is singular";
2849 mInverseMetricDet = 1. / metricDet;
2855 errorMessage = std::string(
"outer wire invalid: ") +
wireStatusMessage(outerStatus);
2860 mInnerWires.clear();
2861 mInnerWires.reserve(innerWireVertices.size());
2862 mInnerReoriented =
false;
2863 for (
const auto& innerWireInput : innerWireVertices) {
2867 errorMessage = std::string(
"inner wire invalid: ") +
wireStatusMessage(innerStatus);
2871 mInnerWires.emplace_back(std::move(innerWire));
2874 const auto ringOf = [
this](
const SurfaceWire& wire) {
2875 std::vector<Vec3> ring;
2876 ring.reserve(wire.vertices.size());
2877 for (
const auto&
vertex : wire.vertices) {
2882 mOuterRing = ringOf(mOuterWire);
2883 mInnerRings.clear();
2884 for (
const auto& innerWire : mInnerWires) {
2885 mInnerRings.push_back(ringOf(innerWire));
2895 return mOrigin + mAxisU * point.
uCoord + mAxisV * point.
vCoord;
2900 const Vec3 relativePoint = point - mOrigin;
2901 const double projectionU =
dot(relativePoint, mAxisU);
2902 const double projectionV =
dot(relativePoint, mAxisV);
2903 return {(projectionU * mMetricVV - projectionV * mMetricUV) * mInverseMetricDet,
2904 (projectionV * mMetricUU - projectionU * mMetricUV) * mInverseMetricDet};
2911 if (boundary !=
nullptr) {
2915 const auto outerClassification = mOuterWire.
classify(point, mTrimBand);
2920 if (boundary !=
nullptr) {
2926 for (
const auto& innerWire : mInnerWires) {
2927 const auto innerClassification = innerWire.classify(point, mTrimBand);
2929 if (boundary !=
nullptr) {
2950 double maxDistance, std::vector<RayHit>& hits)
const override
2952 const double denominator =
dot(mNormal, rayDirection);
2956 const double candidateDistance =
dot(mOrigin - rayOrigin, mNormal) / denominator;
2957 if (candidateDistance < minDistance || candidateDistance > maxDistance) {
2960 const Vec3 candidatePoint = rayOrigin + rayDirection * candidateDistance;
2961 bool onTrimBoundary =
false;
2965 hits.push_back({candidateDistance, mNormal, onTrimBoundary});
2970 double bestDistanceSq = std::numeric_limits<double>::infinity();
2971 for (
size_t vertexIndex = 0; vertexIndex < ring.size(); ++vertexIndex) {
2973 std::min(bestDistanceSq,
pointSegmentDistanceSq(point, ring[vertexIndex], ring[(vertexIndex + 1) % ring.size()]));
2975 return bestDistanceSq;
2983 return signedPlaneDistance * signedPlaneDistance;
2987 for (
const auto& innerRing : mInnerRings) {
2990 return bestDistanceSq;
2997 auto extendPoint = [&](
const Vec2& surfacePoint) {
3010 for (
const auto& innerWire : mInnerWires) {
3011 for (
const auto&
vertex : innerWire.vertices) {
3019 double parametricArea = std::abs(mOuterWire.
signedArea());
3020 for (
const auto& innerWire : mInnerWires) {
3021 parametricArea -= std::abs(innerWire.signedArea());
3023 return std::max(0., parametricArea) * mAreaScale;
3036 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3038 std::vector<std::vector<Vec2>> holes;
3039 for (
const auto& innerWire : mInnerWires) {
3040 holes.push_back(innerWire.vertices);
3042 std::vector<Vec2> localVertices;
3044 const int firstVertexIndex =
static_cast<int>(vertices.size());
3045 for (
const auto&
vertex : localVertices) {
3050 if (!
coversRing(localVertices, ring, localTriangles)) {
3053 for (
const auto& triangle : localTriangles) {
3054 triangles.push_back(
3055 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
3062 for (
size_t vertexIndex = 0; vertexIndex < wire.vertices.size(); ++vertexIndex) {
3063 const Vec3 edgeStart =
toGlobal(wire.vertices[vertexIndex]);
3064 const Vec3 edgeEnd =
toGlobal(wire.vertices[(vertexIndex + 1) % wire.vertices.size()]);
3065 edges.emplace_back(edgeStart, edgeEnd);
3068 appendWire(mOuterWire);
3069 for (
const auto& innerWire : mInnerWires) {
3070 appendWire(innerWire);
3077 mOuterWire, mInnerWires,
index,
3086 double mMetricUU = 0.;
3087 double mMetricUV = 0.;
3088 double mMetricVV = 0.;
3089 double mInverseMetricDet = 0.;
3090 double mAreaScale = 0.;
3091 bool mOuterReoriented =
false;
3092 bool mInnerReoriented =
false;
3093 double mTrimBand = 0.;
3095 std::vector<SurfaceWire> mInnerWires;
3096 std::vector<Vec3> mOuterRing;
3097 std::vector<std::vector<Vec3>> mInnerRings;
3105 const std::vector<Curve2D>& outerCurves,
3106 const std::vector<std::vector<Curve2D>>& innerCurves, std::string& errorMessage,
3110 errorMessage =
"surface frame contains a non-finite value";
3115 errorMessage =
"curved planar surface requires orthonormal frame axes";
3119 mOrigin = surfaceOrigin;
3120 mAxisU = surfaceAxisU;
3121 mAxisV = surfaceAxisV;
3122 mNormal =
cross(mAxisU, mAxisV);
3128 errorMessage = std::string(
"outer wire invalid: ") +
wireStatusMessage(outerStatus);
3133 mInnerWires.clear();
3134 mInnerWires.reserve(innerCurves.size());
3135 for (
const auto& innerCurveLoop : innerCurves) {
3139 errorMessage = std::string(
"inner wire invalid: ") +
wireStatusMessage(innerStatus);
3143 mInnerWires.emplace_back(std::move(innerWire));
3149 for (
const auto& innerWire : mInnerWires) {
3150 mCapacityExact = mCapacityExact && !innerWire.hasBSpline();
3162 const Vec3 relativePoint = point - mOrigin;
3163 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV)};
3170 if (boundary !=
nullptr) {
3174 const auto outerClassification = mOuterWire.
classify(point, mTrimFloor);
3179 if (boundary !=
nullptr) {
3185 for (
const auto& innerWire : mInnerWires) {
3186 const auto innerClassification = innerWire.classify(point, mTrimFloor);
3188 if (boundary !=
nullptr) {
3209 double maxDistance, std::vector<RayHit>& hits)
const override
3211 const double denominator =
dot(mNormal, rayDirection);
3215 const double candidateDistance =
dot(mOrigin - rayOrigin, mNormal) / denominator;
3216 if (candidateDistance < minDistance || candidateDistance > maxDistance) {
3219 bool onTrimBoundary =
false;
3223 hits.push_back({candidateDistance, mNormal, onTrimBoundary});
3231 return signedPlaneDistance * signedPlaneDistance;
3236 double bestCurveDistanceSq = std::numeric_limits<double>::infinity();
3237 for (
const auto& curve : mOuterWire.
curves) {
3238 bestCurveDistanceSq = std::min(bestCurveDistanceSq, curve.distanceSq(projectedPoint));
3240 for (
const auto& innerWire : mInnerWires) {
3241 for (
const auto& curve : innerWire.curves) {
3242 bestCurveDistanceSq = std::min(bestCurveDistanceSq, curve.distanceSq(projectedPoint));
3245 return bestCurveDistanceSq + signedPlaneDistance * signedPlaneDistance;
3252 Vec2 parametricLower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
3253 Vec2 parametricUpper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
3257 for (
const double cornerU : {parametricLower.uCoord, parametricUpper.uCoord}) {
3258 for (
const double cornerV : {parametricLower.vCoord, parametricUpper.vCoord}) {
3259 const Vec3 globalCorner =
toGlobal({cornerU, cornerV});
3272 double parametricArea = std::abs(mOuterWire.
signedArea());
3273 for (
const auto& innerWire : mInnerWires) {
3274 parametricArea -= std::abs(innerWire.signedArea());
3276 return std::max(0., parametricArea);
3292 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3301 const int outerCount =
static_cast<int>(
samples.size());
3302 std::vector<std::vector<Vec2>> holes;
3303 for (
const auto& innerWire : mInnerWires) {
3304 holes.push_back(innerWire.sampledBoundary());
3305 if (!holes.back().empty()) {
3306 holes.back().pop_back();
3309 std::vector<Vec2> localVertices;
3312 const int firstVertexIndex =
static_cast<int>(vertices.size());
3313 for (
const auto&
vertex : localVertices) {
3317 if (!
coversRing(localVertices, ring, localTriangles)) {
3320 for (
const auto& triangle : localTriangles) {
3321 triangles.push_back(
3322 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
3328 auto appendWire = [&](
const CurveWire& wire) {
3329 const auto samples = wire.sampledBoundary();
3330 for (
size_t sampleIndex = 0; sampleIndex + 1 <
samples.size(); ++sampleIndex) {
3334 appendWire(mOuterWire);
3335 for (
const auto& innerWire : mInnerWires) {
3336 appendWire(innerWire);
3343 mOuterWire, mInnerWires,
index,
3352 bool mReoriented =
false;
3353 bool mCapacityExact =
true;
3354 double mTrimFloor = 0.;
3356 std::vector<CurveWire> mInnerWires;
3361 double phiStart,
double phiSweep,
double heightMin,
double heightMax,
3362 double radiusAtMin,
double radiusAtMax,
3363 std::vector<BoundedSurface::CoverBox>& boxes)
3366 for (
int chunk = 0; chunk <
chunks; ++chunk) {
3367 const double phiLow = phiStart + phiSweep * chunk /
chunks;
3368 const double phiHigh = phiStart + phiSweep * (chunk + 1) /
chunks;
3371 for (
int dimension = 0; dimension < 3; ++dimension) {
3372 double radialLow = 0.;
3373 double radialHigh = 0.;
3377 lower[dimension] = std::min(centerAtMin + radiusAtMin * radialLow, centerAtMax + radiusAtMax * radialLow);
3378 upper[dimension] = std::max(centerAtMin + radiusAtMin * radialHigh, centerAtMax + radiusAtMax * radialHigh);
3389 double heightMin,
double heightMax,
double phiStart,
double phiSweep,
bool innerWall,
3390 std::string& errorMessage)
3393 !std::isfinite(heightMin) || !std::isfinite(heightMax) || !std::isfinite(phiStart) ||
3394 !std::isfinite(phiSweep)) {
3395 errorMessage =
"cylindrical surface parameter is non-finite";
3399 errorMessage =
"cylindrical surface needs a positive radius";
3403 errorMessage =
"cylindrical surface needs a positive height range";
3407 errorMessage =
"cylindrical surface needs an angular sweep in (0, 2pi]";
3410 if (!
makeFrame(axis, referenceAxisU, mAxisU, mAxisV, mAxisW, errorMessage)) {
3414 mCenter = centerPoint;
3417 mHeightMin = heightMin;
3418 mHeightMax = heightMax;
3419 mPhiStart = phiStart;
3420 mPhiSweep = std::min(phiSweep,
kTwoPi);
3421 mNormalSign = innerWall ? -1. : 1.;
3427 double heightMin,
double heightMax,
double phiStart,
double phiSweep,
bool innerWall,
3428 const std::vector<Curve2D>& outerTrim,
const std::vector<std::vector<Curve2D>>& innerTrims,
3431 if (!
initialize(centerPoint, axis, referenceAxisU,
radius, heightMin, heightMax, phiStart, phiSweep, innerWall,
3440 mPhiStart =
lower.uCoord;
3442 mHeightMin =
lower.vCoord;
3443 mHeightMax =
upper.vCoord;
3444 mHasWireTrim =
true;
3453 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
3460 std::string& errorMessage)
3463 errorMessage =
"surface axis is degenerate";
3467 const Vec3 projectedU = referenceAxisU - axisW *
dot(referenceAxisU, axisW);
3469 errorMessage =
"surface reference axis is parallel to the main axis";
3473 axisV =
cross(axisW, axisU);
3481 const Vec3 relativePoint = point - mCenter;
3482 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
3497 return mCenter + mAxisW *
height + (mAxisU * std::cos(phi) + mAxisV * std::sin(phi)) * mRadius;
3503 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3504 if (std::abs(radialDistance - mRadius) >
kTolerance) {
3510 const double phi = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
3518 double maxDistance, std::vector<RayHit>& hits)
const override
3521 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
3523 const double quadraticA = localDirection.
xCoord * localDirection.xCoord +
3524 localDirection.yCoord * localDirection.yCoord;
3528 const double quadraticB = 2. * (localOrigin.
xCoord * localDirection.xCoord +
3529 localOrigin.
yCoord * localDirection.yCoord);
3530 const double quadraticC = localOrigin.
xCoord * localOrigin.
xCoord +
3531 localOrigin.
yCoord * localOrigin.
yCoord - mRadius * mRadius;
3532 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
3533 if (discriminant <= 0.) {
3536 const double sqrtDiscriminant = std::sqrt(discriminant);
3537 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
3538 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
3543 for (
const double candidate : {firstRoot, secondRoot}) {
3544 if (candidate < minDistance || candidate > maxDistance) {
3547 const double hitU = localOrigin.
xCoord + candidate * localDirection.xCoord;
3548 const double hitV = localOrigin.
yCoord + candidate * localDirection.yCoord;
3549 const double hitHeight = localOrigin.
zCoord + candidate * localDirection.zCoord;
3550 const double hitPhi = std::atan2(hitV, hitU);
3551 bool onTrimBoundary =
false;
3553 if (!
pointInTrim(hitPhi, hitHeight, &onTrimBoundary)) {
3559 const double radialDistance = std::hypot(hitU, hitV);
3560 const Vec3 hitNormal = (mAxisU * (hitU / radialDistance) + mAxisV * (hitV / radialDistance)) * mNormalSign;
3561 hits.push_back({candidate, hitNormal, onTrimBoundary});
3569 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3572 Vec2{mRadius, mHeightMax});
3574 const double distanceToStartSeam =
3576 const double endPhi = mPhiStart + mPhiSweep;
3577 const double distanceToEndSeam =
3579 return std::min(distanceToStartSeam, distanceToEndSeam);
3585 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3587 return mAxisU * mNormalSign;
3589 return (mAxisU * (localPoint.
xCoord / radialDistance) + mAxisV * (localPoint.
yCoord / radialDistance)) *
3603 const double centreU =
dot(mCenter, mAxisU);
3604 const double centreV =
dot(mCenter, mAxisV);
3605 const double factor = mNormalSign * mRadius / 3.;
3607 return factor * (centreU * std::sin(phi) - centreV * std::cos(phi) + mRadius * phi);
3610 const double endPhi = mPhiStart + mPhiSweep;
3611 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
3612 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
3613 const double height = mHeightMax - mHeightMin;
3614 return mNormalSign * mRadius *
height * (phiFactor + mRadius * mPhiSweep) / 3.;
3622 for (
const double height : {mHeightMin, mHeightMax}) {
3623 const Vec3 rimCenter = mCenter + mAxisW *
height;
3624 for (
int dimension = 0; dimension < 3; ++dimension) {
3625 const double radialExtent = mRadius * std::hypot(
component(mAxisU, dimension),
component(mAxisV, dimension));
3626 const double centerValue =
component(rimCenter, dimension);
3627 if (dimension == 0) {
3628 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
3629 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
3630 }
else if (dimension == 1) {
3631 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
3632 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
3634 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
3635 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
3644 appendArcBandCoverBoxes(mCenter, mAxisU, mAxisV, mAxisW, mPhiStart, mPhiSweep, mHeightMin, mHeightMax, mRadius,
3652 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
3655 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3662 const int firstVertexIndex =
static_cast<int>(vertices.size());
3663 for (
int step = 0; step <=
segments; ++step) {
3664 const double phi = mPhiStart + mPhiSweep * step /
segments;
3665 vertices.push_back(
pointAt(phi, mHeightMin));
3666 vertices.push_back(
pointAt(phi, mHeightMax));
3668 for (
int step = 0; step <
segments; ++step) {
3669 const int base = firstVertexIndex + 2 * step;
3670 triangles.push_back({base, base + 2, base + 3});
3671 triangles.push_back({base, base + 3, base + 1});
3685 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
3686 if (mNormalSign > 0.) {
3687 edges.emplace_back(edgeStart, edgeEnd);
3689 edges.emplace_back(edgeEnd, edgeStart);
3692 for (
int step = 0; step <
segments; ++step) {
3693 const double phi = mPhiStart + mPhiSweep * step /
segments;
3694 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) /
segments;
3695 emitEdge(
pointAt(phi, mHeightMin),
pointAt(nextPhi, mHeightMin));
3696 emitEdge(
pointAt(nextPhi, mHeightMax),
pointAt(phi, mHeightMax));
3699 const double endPhi = mPhiStart + mPhiSweep;
3700 emitEdge(
pointAt(endPhi, mHeightMin),
pointAt(endPhi, mHeightMax));
3701 emitEdge(
pointAt(mPhiStart, mHeightMax),
pointAt(mPhiStart, mHeightMin));
3707 if (!mHasWireTrim) {
3718 double mRadius = 0.;
3719 double mHeightMin = 0.;
3720 double mHeightMax = 0.;
3721 double mPhiStart = 0.;
3722 double mPhiSweep =
kTwoPi;
3723 double mPhiTolerance = 0.;
3724 double mNormalSign = 1.;
3725 bool mHasWireTrim =
false;
3727 std::vector<CurveWire> mTrimInner;
3735 double thetaMin,
double thetaMax,
double phiStart,
double phiSweep,
bool innerWall,
3736 std::string& errorMessage)
3739 !std::isfinite(thetaMin) || !std::isfinite(thetaMax) || !std::isfinite(phiStart) ||
3740 !std::isfinite(phiSweep)) {
3741 errorMessage =
"spherical surface parameter is non-finite";
3745 errorMessage =
"spherical surface needs a positive radius";
3749 errorMessage =
"spherical surface needs a polar range within [0, pi]";
3753 errorMessage =
"spherical surface needs an angular sweep in (0, 2pi]";
3762 mThetaMin = std::max(0., thetaMin);
3763 mThetaMax = std::min(
kPi, thetaMax);
3764 mPhiStart = phiStart;
3765 mPhiSweep = std::min(phiSweep,
kTwoPi);
3766 mNormalSign = innerWall ? -1. : 1.;
3772 double thetaMin,
double thetaMax,
double phiStart,
double phiSweep,
bool innerWall,
3773 const std::vector<Curve2D>& outerTrim,
const std::vector<std::vector<Curve2D>>& innerTrims,
3776 if (!
initialize(
center, polarAxis, referenceAxisU,
radius, thetaMin, thetaMax, phiStart, phiSweep, innerWall,
3785 mPhiStart =
lower.uCoord;
3787 mThetaMin = std::max(0.,
lower.vCoord);
3788 mThetaMax = std::min(
kPi,
upper.vCoord);
3789 mHasWireTrim =
true;
3796 bool pointInTrim(
double phi,
double theta,
bool* boundary =
nullptr)
const
3798 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
3806 const Vec3 relativePoint = point - mCenter;
3807 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
3812 if (boundary !=
nullptr) {
3815 const double pointRadius =
norm(localPoint);
3820 const double theta = std::acos(std::max(-1., std::min(1., localPoint.
zCoord / pointRadius)));
3821 const double transverseDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3825 return theta >= mThetaMin - thetaTolerance && theta <= mThetaMax + thetaTolerance;
3829 if (theta < mThetaMin - thetaTolerance || theta > mThetaMax + thetaTolerance) {
3841 const double sinTheta = std::sin(theta);
3842 return mCenter + (mAxisU * (sinTheta * std::cos(phi)) + mAxisV * (sinTheta * std::sin(phi)) +
3843 mAxisW * std::cos(theta)) *
3857 double maxDistance, std::vector<RayHit>& hits)
const override
3859 const Vec3 relativeOrigin = rayOrigin - mCenter;
3860 const double quadraticA =
normSq(rayDirection);
3864 const double quadraticB = 2. *
dot(relativeOrigin, rayDirection);
3865 const double quadraticC =
normSq(relativeOrigin) - mRadius * mRadius;
3866 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
3867 if (discriminant <= 0.) {
3870 const double sqrtDiscriminant = std::sqrt(discriminant);
3871 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
3872 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
3877 for (
const double candidate : {firstRoot, secondRoot}) {
3878 if (candidate < minDistance || candidate > maxDistance) {
3881 const Vec3 localHit =
toLocal(rayOrigin + rayDirection * candidate);
3882 bool onTrimBoundary =
false;
3886 hits.push_back({candidate,
3888 (mNormalSign / mRadius),
3897 const double radialOffset =
norm(localPoint) - mRadius;
3898 return radialOffset * radialOffset;
3904 const double pointRadius =
norm(localPoint);
3906 return mAxisW * mNormalSign;
3908 return (mAxisU * localPoint.
xCoord + mAxisV * localPoint.
yCoord + mAxisW * localPoint.
zCoord) *
3909 (mNormalSign / pointRadius);
3922 const double centreU =
dot(mCenter, mAxisU);
3923 const double centreV =
dot(mCenter, mAxisV);
3924 const double centreW =
dot(mCenter, mAxisW);
3925 const double factor = mNormalSign * mRadius * mRadius / 3.;
3927 const double sinTheta = std::sin(theta);
3928 return factor * sinTheta *
3929 (sinTheta * (centreU * std::sin(phi) - centreV * std::cos(phi)) +
3930 (centreW * std::cos(theta) + mRadius) * phi);
3933 const double endPhi = mPhiStart + mPhiSweep;
3934 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
3935 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
3936 const double thetaIntegralSinSq =
3937 0.5 * ((mThetaMax - std::sin(mThetaMax) * std::cos(mThetaMax)) -
3938 (mThetaMin - std::sin(mThetaMin) * std::cos(mThetaMin)));
3939 const double thetaIntegralSinCos =
3940 0.5 * (std::sin(mThetaMax) * std::sin(mThetaMax) - std::sin(mThetaMin) * std::sin(mThetaMin));
3941 const double thetaIntegralSin = std::cos(mThetaMin) - std::cos(mThetaMax);
3942 return mNormalSign * mRadius * mRadius *
3943 (phiFactor * thetaIntegralSinSq +
dot(mCenter, mAxisW) * mPhiSweep * thetaIntegralSinCos +
3944 mRadius * mPhiSweep * thetaIntegralSin) /
3965 for (
int thetaChunk = 0; thetaChunk < thetaChunks; ++thetaChunk) {
3966 const double thetaLow =
kPi * thetaChunk / thetaChunks;
3967 const double thetaHigh =
kPi * (thetaChunk + 1) / thetaChunks;
3968 for (
int phiChunk = 0; phiChunk < phiChunks; ++phiChunk) {
3969 const double phiLow =
kTwoPi * phiChunk / phiChunks;
3970 const double phiHigh =
kTwoPi * (phiChunk + 1) / phiChunks;
3973 for (
int dimension = 0; dimension < 3; ++dimension) {
3974 double inPlaneLow = 0.;
3975 double inPlaneHigh = 0.;
3979 const double axisComponent =
component(mAxisW, dimension);
3980 const double high =
sinusoidMaximum(axisComponent, inPlaneHigh, thetaLow, thetaHigh);
3981 const double low =
sinusoidMinimum(axisComponent, inPlaneLow, thetaLow, thetaHigh);
3982 lower[dimension] =
component(mCenter, dimension) + mRadius * low;
3983 upper[dimension] =
component(mCenter, dimension) + mRadius * high;
3992 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
3997 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * (mThetaMax - mThetaMin) /
kTwoPi)));
4000 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
4003 appendCurveTrimMesh(mTrimOuter, mTrimInner, [
this](
double phi,
double theta) {
return pointAt(theta, phi); }, vertices, triangles);
4008 const int firstVertexIndex =
static_cast<int>(vertices.size());
4009 for (
int thetaStep = 0; thetaStep <= thetaSteps; ++thetaStep) {
4010 const double theta = mThetaMin + (mThetaMax - mThetaMin) * thetaStep / thetaSteps;
4011 for (
int phiStep = 0; phiStep <= phiSteps; ++phiStep) {
4012 vertices.push_back(
pointAt(theta, mPhiStart + mPhiSweep * phiStep / phiSteps));
4015 const int rowLength = phiSteps + 1;
4016 for (
int thetaStep = 0; thetaStep < thetaSteps; ++thetaStep) {
4017 for (
int phiStep = 0; phiStep < phiSteps; ++phiStep) {
4018 const int base = firstVertexIndex + thetaStep * rowLength + phiStep;
4019 triangles.push_back({base, base + 1, base + rowLength + 1});
4020 triangles.push_back({base, base + rowLength + 1, base + rowLength});
4035 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
4036 if (mNormalSign > 0.) {
4037 edges.emplace_back(edgeStart, edgeEnd);
4039 edges.emplace_back(edgeEnd, edgeStart);
4044 const double endPhi = mPhiStart + mPhiSweep;
4045 if (mThetaMin > thetaTolerance) {
4046 for (
int step = 0; step < phiSteps; ++step) {
4047 const double phi = mPhiStart + mPhiSweep * step / phiSteps;
4048 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) / phiSteps;
4052 if (mThetaMax <
kPi - thetaTolerance) {
4053 for (
int step = 0; step < phiSteps; ++step) {
4054 const double phi = mPhiStart + mPhiSweep * step / phiSteps;
4055 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) / phiSteps;
4061 for (
int step = 0; step < thetaSteps; ++step) {
4062 const double theta = mThetaMin + (mThetaMax - mThetaMin) * step / thetaSteps;
4063 const double nextTheta = mThetaMin + (mThetaMax - mThetaMin) * (step + 1) / thetaSteps;
4064 emitEdge(
pointAt(theta, mPhiStart),
pointAt(nextTheta, mPhiStart));
4072 if (!mHasWireTrim) {
4083 double mRadius = 0.;
4084 double mThetaMin = 0.;
4085 double mThetaMax =
kPi;
4086 double mPhiStart = 0.;
4087 double mPhiSweep =
kTwoPi;
4088 double mNormalSign = 1.;
4089 bool mHasWireTrim =
false;
4091 std::vector<CurveWire> mTrimInner;
4099 double radiusAtMax,
double heightMin,
double heightMax,
double phiStart,
double phiSweep,
4100 bool innerWall, std::string& errorMessage)
4102 if (!
finite(centerPoint) || !
finite(axis) || !
finite(referenceAxisU) || !std::isfinite(radiusAtMin) ||
4103 !std::isfinite(radiusAtMax) || !std::isfinite(heightMin) || !std::isfinite(heightMax) ||
4104 !std::isfinite(phiStart) || !std::isfinite(phiSweep)) {
4105 errorMessage =
"conical surface parameter is non-finite";
4109 std::max(radiusAtMin, radiusAtMax) <=
kTolerance) {
4110 errorMessage =
"conical surface needs non-negative radii, at least one positive";
4114 errorMessage =
"conical surface needs a positive height range";
4118 errorMessage =
"conical surface needs an angular sweep in (0, 2pi]";
4125 mCenter = centerPoint;
4126 mHeightMin = heightMin;
4127 mHeightMax = heightMax;
4128 mSlope = (radiusAtMax - radiusAtMin) / (heightMax - heightMin);
4129 mRadius0 = radiusAtMin - mSlope * heightMin;
4130 mPhiStart = phiStart;
4131 mPhiSweep = std::min(phiSweep,
kTwoPi);
4132 mNormalSign = innerWall ? -1. : 1.;
4139 double radiusAtMax,
double heightMin,
double heightMax,
double phiStart,
double phiSweep,
4140 bool innerWall,
const std::vector<Curve2D>& outerTrim,
4141 const std::vector<std::vector<Curve2D>>& innerTrims, std::string& errorMessage,
4144 if (!
initialize(centerPoint, axis, referenceAxisU, radiusAtMin, radiusAtMax, heightMin, heightMax, phiStart,
4145 phiSweep, innerWall, errorMessage)) {
4153 mPhiStart =
lower.uCoord;
4155 mHeightMin =
lower.vCoord;
4156 mHeightMax =
upper.vCoord;
4158 mHasWireTrim =
true;
4167 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
4179 const Vec3 relativePoint = point - mCenter;
4180 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
4195 return mCenter + mAxisW *
height + (mAxisU * std::cos(phi) + mAxisV * std::sin(phi)) *
radiusAt(
height);
4202 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4204 if (std::abs(radialDistance - surfaceRadius) >
kTolerance * std::sqrt(1. + mSlope * mSlope)) {
4210 const double phi = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
4218 double maxDistance, std::vector<RayHit>& hits)
const override
4221 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
4224 const double surfaceRadiusAtOrigin = mRadius0 + mSlope * localOrigin.
zCoord;
4225 const double quadraticA = localDirection.xCoord * localDirection.xCoord +
4226 localDirection.yCoord * localDirection.yCoord -
4227 mSlope * mSlope * localDirection.zCoord * localDirection.zCoord;
4228 const double quadraticB = 2. * (localOrigin.
xCoord * localDirection.xCoord +
4229 localOrigin.
yCoord * localDirection.yCoord -
4230 mSlope * localDirection.zCoord * surfaceRadiusAtOrigin);
4231 const double quadraticC = localOrigin.
xCoord * localOrigin.
xCoord +
4233 surfaceRadiusAtOrigin * surfaceRadiusAtOrigin;
4235 std::array<double, 2> candidates{};
4236 int candidateCount = 0;
4241 candidates[candidateCount++] = -quadraticC / quadraticB;
4243 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
4244 if (discriminant <= 0.) {
4247 const double sqrtDiscriminant = std::sqrt(discriminant);
4248 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
4249 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
4253 candidates[candidateCount++] = std::min(firstRoot, secondRoot);
4254 candidates[candidateCount++] = std::max(firstRoot, secondRoot);
4257 for (
int candidateIndex = 0; candidateIndex < candidateCount; ++candidateIndex) {
4258 const double candidate = candidates[candidateIndex];
4259 if (candidate < minDistance || candidate > maxDistance) {
4262 const double hitHeight = localOrigin.
zCoord + candidate * localDirection.zCoord;
4263 const double hitSurfaceRadius =
radiusAt(hitHeight);
4267 const double hitU = localOrigin.
xCoord + candidate * localDirection.xCoord;
4268 const double hitV = localOrigin.
yCoord + candidate * localDirection.yCoord;
4269 const double radialDistance = std::hypot(hitU, hitV);
4273 const double hitPhi = std::atan2(hitV, hitU);
4274 bool onTrimBoundary =
false;
4276 if (!
pointInTrim(hitPhi, hitHeight, &onTrimBoundary)) {
4282 const double normalScale = mNormalSign / std::sqrt(1. + mSlope * mSlope);
4283 const Vec3 hitNormal =
4284 (mAxisU * (hitU / radialDistance) + mAxisV * (hitV / radialDistance) - mAxisW * mSlope) * normalScale;
4285 hits.push_back({candidate, hitNormal, onTrimBoundary});
4293 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4298 const double endPhi = mPhiStart + mPhiSweep;
4299 const double distanceToStartSeam =
4301 const double distanceToEndSeam =
4303 return std::min(distanceToStartSeam, distanceToEndSeam);
4309 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4310 const double normalScale = mNormalSign / std::sqrt(1. + mSlope * mSlope);
4312 return (mAxisU - mAxisW * mSlope) * normalScale;
4314 return (mAxisU * (localPoint.
xCoord / radialDistance) + mAxisV * (localPoint.
yCoord / radialDistance) -
4329 const double centreU =
dot(mCenter, mAxisU);
4330 const double centreV =
dot(mCenter, mAxisV);
4331 const double centreW =
dot(mCenter, mAxisW);
4334 return mNormalSign / 3. * localRadius *
4335 (centreU * std::sin(phi) - centreV * std::cos(phi) +
4336 (localRadius - mSlope * (centreW +
height)) * phi);
4339 const double endPhi = mPhiStart + mPhiSweep;
4340 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
4341 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
4342 const double radiusIntegral = mRadius0 * (mHeightMax - mHeightMin) +
4343 0.5 * mSlope * (mHeightMax * mHeightMax - mHeightMin * mHeightMin);
4344 return mNormalSign * radiusIntegral *
4345 (phiFactor + (mRadius0 - mSlope *
dot(mCenter, mAxisW)) * mPhiSweep) / 3.;
4352 for (
const double height : {mHeightMin, mHeightMax}) {
4353 const Vec3 rimCenter = mCenter + mAxisW *
height;
4355 for (
int dimension = 0; dimension < 3; ++dimension) {
4356 const double radialExtent =
4358 const double centerValue =
component(rimCenter, dimension);
4359 if (dimension == 0) {
4360 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
4361 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
4362 }
else if (dimension == 1) {
4363 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
4364 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
4366 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
4367 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
4377 std::max(0.,
radiusAt(mHeightMin)), std::max(0.,
radiusAt(mHeightMax)), boxes);
4382 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
4385 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
4392 const int firstVertexIndex =
static_cast<int>(vertices.size());
4393 for (
int step = 0; step <=
segments; ++step) {
4394 const double phi = mPhiStart + mPhiSweep * step /
segments;
4395 vertices.push_back(
pointAt(phi, mHeightMin));
4396 vertices.push_back(
pointAt(phi, mHeightMax));
4398 for (
int step = 0; step <
segments; ++step) {
4399 const int base = firstVertexIndex + 2 * step;
4402 triangles.push_back({base, base + 2, base + 3});
4405 triangles.push_back({base, base + 3, base + 1});
4421 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
4422 if (mNormalSign > 0.) {
4423 edges.emplace_back(edgeStart, edgeEnd);
4425 edges.emplace_back(edgeEnd, edgeStart);
4428 for (
int step = 0; step <
segments; ++step) {
4429 const double phi = mPhiStart + mPhiSweep * step /
segments;
4430 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) /
segments;
4432 emitEdge(
pointAt(phi, mHeightMin),
pointAt(nextPhi, mHeightMin));
4435 emitEdge(
pointAt(nextPhi, mHeightMax),
pointAt(phi, mHeightMax));
4439 const double endPhi = mPhiStart + mPhiSweep;
4440 emitEdge(
pointAt(endPhi, mHeightMin),
pointAt(endPhi, mHeightMax));
4441 emitEdge(
pointAt(mPhiStart, mHeightMax),
pointAt(mPhiStart, mHeightMin));
4447 if (!mHasWireTrim) {
4458 double mRadius0 = 0.;
4460 double mHeightMin = 0.;
4461 double mHeightMax = 0.;
4462 double mPhiStart = 0.;
4463 double mPhiSweep =
kTwoPi;
4464 double mPhiTolerance = 0.;
4465 double mNormalSign = 1.;
4466 bool mHasWireTrim =
false;
4468 std::vector<CurveWire> mTrimInner;
4477 double minorRadius,
double phiStart,
double phiSweep,
double tubeStart,
double tubeSweep,
4478 bool innerWall, std::string& errorMessage)
4480 if (!
finite(centerPoint) || !
finite(axis) || !
finite(referenceAxisU) || !std::isfinite(majorRadius) ||
4481 !std::isfinite(minorRadius) || !std::isfinite(phiStart) || !std::isfinite(phiSweep) ||
4482 !std::isfinite(tubeStart) || !std::isfinite(tubeSweep)) {
4483 errorMessage =
"toroidal surface parameter is non-finite";
4487 errorMessage =
"toroidal surface needs positive major and minor radii";
4491 errorMessage =
"toroidal surface needs a ring sweep in (0, 2pi]";
4495 errorMessage =
"toroidal surface needs a tube sweep in (0, 2pi]";
4502 mCenter = centerPoint;
4503 mMajorRadius = majorRadius;
4504 mMinorRadius = minorRadius;
4507 mPhiStart = phiStart;
4508 mPhiSweep = std::min(phiSweep,
kTwoPi);
4509 mTubeStart = tubeStart;
4510 mTubeSweep = std::min(tubeSweep,
kTwoPi);
4511 mNormalSign = innerWall ? -1. : 1.;
4517 double minorRadius,
double phiStart,
double phiSweep,
double tubeStart,
double tubeSweep,
4518 bool innerWall,
const std::vector<Curve2D>& outerTrim,
4519 const std::vector<std::vector<Curve2D>>& innerTrims, std::string& errorMessage,
4522 if (!
initialize(centerPoint, axis, referenceAxisU, majorRadius, minorRadius, phiStart, phiSweep, tubeStart,
4523 tubeSweep, innerWall, errorMessage)) {
4532 errorMessage =
"toroidal trim wire spans more than a full turn in the tube angle";
4535 mPhiStart =
lower.uCoord;
4537 mTubeStart =
lower.vCoord;
4539 mHasWireTrim =
true;
4546 bool pointInTrim(
double phiRing,
double phiTube,
bool* boundary =
nullptr)
const
4548 const double uCoord =
unwrapAngleInto(phiRing, mPhiStart, mPhiStart + mPhiSweep);
4549 const double vCoord =
unwrapAngleInto(phiTube, mTubeStart, mTubeStart + mTubeSweep);
4558 const Vec3 relativePoint = point - mCenter;
4559 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
4574 const double ringRadius = mMajorRadius + mMinorRadius * std::cos(phiTube);
4575 return mCenter + (mAxisU * std::cos(phiRing) + mAxisV * std::sin(phiRing)) * ringRadius +
4576 mAxisW * (mMinorRadius * std::sin(phiTube));
4582 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4584 return mAxisW * (localPoint.
zCoord >= 0. ? mNormalSign : -mNormalSign);
4586 const double radialFactor = (rho - mMajorRadius) / rho;
4590 return mAxisU * mNormalSign;
4598 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4599 const double meridianDistance = std::hypot(rho - mMajorRadius, localPoint.
zCoord) - mMinorRadius;
4600 if (std::abs(meridianDistance) >
kTolerance) {
4603 const double phiTube = std::atan2(localPoint.
zCoord, rho - mMajorRadius);
4607 const double phiRing = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
4615 double maxDistance, std::vector<RayHit>& hits)
const override
4618 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
4622 const double dirDotDir =
normSq(localDirection);
4626 const double originDotDir =
dot(localOrigin, localDirection);
4627 const double originDotOrigin =
normSq(localOrigin);
4628 const double constantK = mMajorRadius * mMajorRadius - mMinorRadius * mMinorRadius;
4629 const double transverseE = localDirection.xCoord * localDirection.xCoord +
4630 localDirection.yCoord * localDirection.yCoord;
4631 const double transverseF = localOrigin.
xCoord * localDirection.xCoord +
4632 localOrigin.
yCoord * localDirection.yCoord;
4633 const double transverseG = localOrigin.
xCoord * localOrigin.
xCoord +
4635 const double fourRSquared = 4. * mMajorRadius * mMajorRadius;
4637 const double coeff4 = dirDotDir * dirDotDir;
4638 const double coeff3 = 4. * dirDotDir * originDotDir;
4639 const double coeff2 =
4640 4. * originDotDir * originDotDir + 2. * dirDotDir * (originDotOrigin + constantK) - fourRSquared * transverseE;
4641 const double coeff1 = 4. * originDotDir * (originDotOrigin + constantK) - 2. * fourRSquared * transverseF;
4642 const double coeff0 = (originDotOrigin + constantK) * (originDotOrigin + constantK) - fourRSquared * transverseG;
4645 if (candidates.
empty()) {
4648 std::sort(candidates.
begin(), candidates.
end());
4651 size_t rootIndex = 0;
4652 while (rootIndex < candidates.
size()) {
4653 size_t clusterEnd = rootIndex + 1;
4654 double clusterSum = candidates[rootIndex];
4655 while (clusterEnd < candidates.
size() &&
sameIntersection(candidates[clusterEnd], candidates[clusterEnd - 1])) {
4656 clusterSum += candidates[clusterEnd];
4659 const size_t clusterSize = clusterEnd - rootIndex;
4660 rootIndex = clusterEnd;
4664 const double candidate = clusterSum /
static_cast<double>(
clusterSize);
4665 if (candidate < minDistance || candidate > maxDistance) {
4668 const Vec3 localHit =
toLocal(rayOrigin + rayDirection * candidate);
4669 const double rho = std::hypot(localHit.
xCoord, localHit.
yCoord);
4673 const double phiTube = std::atan2(localHit.
zCoord, rho - mMajorRadius);
4674 const double phiRing = std::atan2(localHit.
yCoord, localHit.
xCoord);
4675 bool onTrimBoundary =
false;
4677 if (!
pointInTrim(phiRing, phiTube, &onTrimBoundary)) {
4683 hits.push_back({candidate,
localNormal(localHit), onTrimBoundary});
4691 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4692 const double meridianDistance = std::hypot(rho - mMajorRadius, localPoint.
zCoord) - mMinorRadius;
4693 return meridianDistance * meridianDistance;
4708 const double centreU =
dot(mCenter, mAxisU);
4709 const double centreV =
dot(mCenter, mAxisV);
4710 const double centreW =
dot(mCenter, mAxisW);
4712 const double cosTube = std::cos(phiTube);
4713 const double sinTube = std::sin(phiTube);
4714 const double rho = mMajorRadius + mMinorRadius * cosTube;
4715 return mNormalSign * mMinorRadius * rho / 3. *
4716 (cosTube * (centreU * std::sin(phiRing) - centreV * std::cos(phiRing)) +
4717 (centreW * sinTube + rho * cosTube + mMinorRadius * sinTube * sinTube) * phiRing);
4721 const double majorR = mMajorRadius;
4722 const double minorR = mMinorRadius;
4723 const double u0 = mPhiStart, u1 = mPhiStart + mPhiSweep;
4724 const double v0 = mTubeStart,
v1 = mTubeStart + mTubeSweep;
4725 const double centerU =
dot(mCenter, mAxisU);
4726 const double centerV =
dot(mCenter, mAxisV);
4727 const double centerW =
dot(mCenter, mAxisW);
4728 const double deltaU = u1 - u0;
4729 const double deltaV =
v1 -
v0;
4730 const double sinIntegralU = std::sin(u1) - std::sin(u0);
4731 const double cosIntegralU = std::cos(u0) - std::cos(u1);
4732 const double sinIntegralV = std::sin(
v1) - std::sin(
v0);
4733 const double sinFromCosV = std::cos(
v0) - std::cos(
v1);
4734 const double cosSquaredV = 0.5 * deltaV + 0.25 * (std::sin(2. *
v1) - std::sin(2. *
v0));
4735 const double sinCosV = 0.25 * (std::cos(2. *
v0) - std::cos(2. *
v1));
4738 const double centerlessV =
4739 minorR * ((majorR * majorR + minorR * minorR) * sinIntegralV + majorR * minorR * deltaV +
4740 majorR * minorR * cosSquaredV);
4742 const double centerWpart = minorR * (majorR * sinFromCosV + minorR * sinCosV);
4744 const double centerUVpart =
4745 (centerU * sinIntegralU + centerV * cosIntegralU) * minorR * (majorR * sinIntegralV + minorR * cosSquaredV);
4747 const double total = deltaU * centerlessV + deltaU * centerW * centerWpart + centerUVpart;
4748 return mNormalSign * total / 3.;
4756 const double outerRadius = mMajorRadius + mMinorRadius;
4757 for (
int dimension = 0; dimension < 3; ++dimension) {
4758 const double radialExtent = outerRadius * std::hypot(
component(mAxisU, dimension),
component(mAxisV, dimension)) +
4759 mMinorRadius * std::abs(
component(mAxisW, dimension));
4760 const double centerValue =
component(mCenter, dimension);
4761 if (dimension == 0) {
4762 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
4763 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
4764 }
else if (dimension == 1) {
4765 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
4766 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
4768 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
4769 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
4777 if (mMajorRadius < mMinorRadius) {
4783 for (
int ringChunk = 0; ringChunk < ringChunks; ++ringChunk) {
4784 const double ringLow =
kTwoPi * ringChunk / ringChunks;
4785 const double ringHigh =
kTwoPi * (ringChunk + 1) / ringChunks;
4786 for (
int tubeChunk = 0; tubeChunk < tubeChunks; ++tubeChunk) {
4787 const double tubeLow =
kTwoPi * tubeChunk / tubeChunks;
4788 const double tubeHigh =
kTwoPi * (tubeChunk + 1) / tubeChunks;
4791 for (
int dimension = 0; dimension < 3; ++dimension) {
4792 double inPlaneLow = 0.;
4793 double inPlaneHigh = 0.;
4798 const double axisComponent =
component(mAxisW, dimension);
4799 const double high =
sinusoidMaximum(inPlaneHigh, axisComponent, tubeLow, tubeHigh);
4800 const double low =
sinusoidMinimum(inPlaneLow, axisComponent, tubeLow, tubeHigh);
4801 lower[dimension] =
component(mCenter, dimension) + inPlaneLow * mMajorRadius + mMinorRadius * low;
4802 upper[dimension] =
component(mCenter, dimension) + inPlaneHigh * mMajorRadius + mMinorRadius * high;
4811 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
4816 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mTubeSweep /
kTwoPi)));
4819 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
4822 appendCurveTrimMesh(mTrimOuter, mTrimInner, [
this](
double phiRing,
double phiTube) {
return pointAt(phiRing, phiTube); }, vertices, triangles);
4827 const int firstVertexIndex =
static_cast<int>(vertices.size());
4828 for (
int ringStep = 0; ringStep <= ringSteps; ++ringStep) {
4829 const double phiRing = mPhiStart + mPhiSweep * ringStep / ringSteps;
4830 for (
int tubeStep = 0; tubeStep <= tubeSteps; ++tubeStep) {
4831 vertices.push_back(
pointAt(phiRing, mTubeStart + mTubeSweep * tubeStep / tubeSteps));
4834 const int rowLength = tubeSteps + 1;
4835 for (
int ringStep = 0; ringStep < ringSteps; ++ringStep) {
4836 for (
int tubeStep = 0; tubeStep < tubeSteps; ++tubeStep) {
4837 const int base = firstVertexIndex + ringStep * rowLength + tubeStep;
4838 triangles.push_back({base, base + rowLength, base + rowLength + 1});
4839 triangles.push_back({base, base + rowLength + 1, base + 1});
4854 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
4855 if (mNormalSign > 0.) {
4856 edges.emplace_back(edgeStart, edgeEnd);
4858 edges.emplace_back(edgeEnd, edgeStart);
4863 const double endRing = mPhiStart + mPhiSweep;
4864 const double endTube = mTubeStart + mTubeSweep;
4866 for (
int step = 0; step < ringSteps; ++step) {
4867 const double phiRing = mPhiStart + mPhiSweep * step / ringSteps;
4868 const double nextRing = mPhiStart + mPhiSweep * (step + 1) / ringSteps;
4869 emitEdge(
pointAt(phiRing, mTubeStart),
pointAt(nextRing, mTubeStart));
4874 for (
int step = 0; step < tubeSteps; ++step) {
4875 const double phiTube = mTubeStart + mTubeSweep * step / tubeSteps;
4876 const double nextTube = mTubeStart + mTubeSweep * (step + 1) / tubeSteps;
4878 emitEdge(
pointAt(mPhiStart, nextTube),
pointAt(mPhiStart, phiTube));
4885 if (!mHasWireTrim) {
4896 double mMajorRadius = 0.;
4897 double mMinorRadius = 0.;
4898 double mPhiStart = 0.;
4899 double mPhiSweep =
kTwoPi;
4900 double mTubeStart = 0.;
4901 double mTubeSweep =
kTwoPi;
4902 double mRingTolerance = 0.;
4903 double mTubeTolerance = 0.;
4904 double mNormalSign = 1.;
4905 bool mHasWireTrim =
false;
4907 std::vector<CurveWire> mTrimInner;
4994 std::map<uint32_t, std::vector<std::pair<int, size_t>>> claims;
4995 for (
size_t surfaceIndex = 0; surfaceIndex <
surfaces.size(); ++surfaceIndex) {
4996 if (
surfaces[surfaceIndex] ==
nullptr) {
4999 const auto& refs =
surfaces[surfaceIndex]->boundaryEdges();
5000 for (
size_t slot = 0; slot < refs.size(); ++slot) {
5001 if (refs[slot].degenerate) {
5006 claims[refs[slot].edgeId].emplace_back(
static_cast<int>(surfaceIndex), slot);
5010 std::vector<Vec3>
first;
5011 std::vector<Vec3> second;
5012 for (
const auto& [edgeId, holders] : claims) {
5013 if (holders.size() != 2) {
5016 const auto& [firstSurface, firstSlot] = holders[0];
5017 const auto& [secondSurface, secondSlot] = holders[1];
5018 if (!
surfaces[
static_cast<size_t>(firstSurface)]->sampleTrimCurve(firstSlot,
first) ||
5019 !
surfaces[
static_cast<size_t>(secondSurface)]->sampleTrimCurve(secondSlot, second) ||
first.size() < 2 ||
5020 second.size() < 2) {
5021 ++
report.sharedEdgesUnmeasured;
5024 ++
report.sharedEdgesMeasured;
5025 auto worstAgainst = [](
const std::vector<Vec3>& probes,
const std::vector<Vec3>& polyline,
Vec3& where) {
5027 for (
const Vec3& probe : probes) {
5028 double nearest = std::numeric_limits<double>::infinity();
5032 if (nearest > worst) {
5037 return std::sqrt(worst);
5039 Vec3 forwardPoint{};
5040 Vec3 backwardPoint{};
5041 const double forwardWorst = worstAgainst(
first, second, forwardPoint);
5042 const double backwardWorst = worstAgainst(second,
first, backwardPoint);
5043 const double deviation = std::max(forwardWorst, backwardWorst);
5044 if (deviation >
report.maxSharedEdgeDeviation) {
5045 report.maxSharedEdgeDeviation = deviation;
5046 report.maxSharedEdgeDeviationEdge = edgeId;
5047 report.maxSharedEdgeDeviationPoint = forwardWorst >= backwardWorst ? forwardPoint : backwardPoint;
5048 report.maxSharedEdgeDeviationFaces[0] = firstSurface;
5049 report.maxSharedEdgeDeviationFaces[1] = secondSurface;
5058 report.rimEpsilon = epsilon;
5060 std::vector<SurfaceRim> rims;
5061 std::vector<int> rimIndexOnSurface;
5062 for (
size_t surfaceIndex = 0; surfaceIndex <
surfaces.size(); ++surfaceIndex) {
5063 if (
surfaces[surfaceIndex] ==
nullptr) {
5066 const size_t firstNewRim = rims.size();
5067 surfaces[surfaceIndex]->appendRims(rims);
5068 for (
size_t rimIndex = firstNewRim; rimIndex < rims.size(); ++rimIndex) {
5069 rims[rimIndex].surfaceIndex =
static_cast<int>(surfaceIndex);
5070 rimIndexOnSurface.push_back(
static_cast<int>(rimIndex - firstNewRim));
5073 report.rims =
static_cast<int>(rims.size());
5080 constexpr double kMaxSmoothTurn = 0.52;
5087 std::vector<Chord> chords;
5088 std::vector<std::pair<size_t, size_t>> chordRange(rims.size());
5089 for (
size_t rimIndex = 0; rimIndex < rims.size(); ++rimIndex) {
5091 chordRange[rimIndex].first = chords.size();
5092 const size_t pointCount = rim.
points.size();
5093 std::vector<double> vertexSagitta(pointCount, 0.);
5094 const size_t interiorCount = rim.
closed ? pointCount : (pointCount >= 2 ? pointCount - 2 : 0);
5097 const Vec3 incoming = rim.
points[middle] - rim.
points[(middle + pointCount - 1) % pointCount];
5098 const Vec3 outgoing = rim.
points[(middle + 1) % pointCount] - rim.
points[middle];
5099 const double incomingLength =
norm(incoming);
5100 const double outgoingLength =
norm(outgoing);
5104 const double turn = std::acos(std::clamp(
dot(incoming, outgoing) / (incomingLength * outgoingLength), -1., 1.));
5105 if (turn > kMaxSmoothTurn) {
5108 vertexSagitta[middle] = 0.25 * (incomingLength + outgoingLength) * std::tan(0.25 * turn);
5109 report.rimChordResolution = std::max(
report.rimChordResolution, vertexSagitta[middle]);
5111 const size_t chordCount = rim.
closed ? pointCount : pointCount - 1;
5112 for (
size_t pointIndex = 0; pointIndex < chordCount; ++pointIndex) {
5113 const size_t nextIndex = (pointIndex + 1) % pointCount;
5115 std::max(vertexSagitta[pointIndex], vertexSagitta[nextIndex])});
5117 chordRange[rimIndex].second = chords.size();
5119 if (chords.empty()) {
5125 auto grow = [&](
const Vec3& point) {
5126 lower = {std::min(
lower.xCoord, point.xCoord), std::min(
lower.yCoord, point.yCoord),
5127 std::min(
lower.zCoord, point.zCoord)};
5128 upper = {std::max(
upper.xCoord, point.xCoord), std::max(
upper.yCoord, point.yCoord),
5129 std::max(
upper.zCoord, point.zCoord)};
5131 for (
const Chord& chord : chords) {
5135 const int gridDimension =
5136 std::clamp(
static_cast<int>(std::cbrt(
static_cast<double>(chords.size()))), 1, 32);
5138 const double cellSize =
5141 return std::clamp(
static_cast<int>((
coordinate -
origin) / cellSize), 0, gridDimension - 1);
5143 auto cellIndex = [&](
int xCell,
int yCell,
int zCell) {
5144 return (xCell * gridDimension + yCell) * gridDimension + zCell;
5146 std::vector<std::vector<int>>
cells(
static_cast<size_t>(gridDimension) * gridDimension * gridDimension);
5147 for (
size_t chordIndex = 0; chordIndex < chords.size(); ++chordIndex) {
5148 const Chord& chord = chords[chordIndex];
5149 const int xLow = cellOf(std::min(chord.start.xCoord, chord.end.xCoord),
lower.xCoord);
5150 const int xHigh = cellOf(std::max(chord.start.xCoord, chord.end.xCoord),
lower.xCoord);
5151 const int yLow = cellOf(std::min(chord.start.yCoord, chord.end.yCoord),
lower.yCoord);
5152 const int yHigh = cellOf(std::max(chord.start.yCoord, chord.end.yCoord),
lower.yCoord);
5153 const int zLow = cellOf(std::min(chord.start.zCoord, chord.end.zCoord),
lower.zCoord);
5154 const int zHigh = cellOf(std::max(chord.start.zCoord, chord.end.zCoord),
lower.zCoord);
5155 for (
int xCell = xLow; xCell <= xHigh; ++xCell) {
5156 for (
int yCell = yLow; yCell <= yHigh; ++yCell) {
5157 for (
int zCell = zLow; zCell <= zHigh; ++zCell) {
5158 cells[cellIndex(xCell, yCell, zCell)].push_back(
static_cast<int>(chordIndex));
5165 double distance = std::numeric_limits<double>::infinity();
5166 int chordIndex = -1;
5168 bool withinBand =
false;
5171 std::array<int, 3> coincidentFaces{-1, -1, -1};
5172 int coincidentFaceCount = 0;
5175 const double maxBand = epsilon + 2. *
report.rimChordResolution;
5176 auto nearestOtherFace = [&](
const Vec3& probe,
int ownSurfaceIndex,
double probeResolution) {
5178 auto consider = [&](
int chordIndex) {
5179 const Chord& chord = chords[
static_cast<size_t>(chordIndex)];
5180 if (chord.surfaceIndex == ownSurfaceIndex) {
5186 match.chordIndex = chordIndex;
5188 if (
distance <= epsilon + probeResolution + chord.resolution) {
5189 match.withinBand =
true;
5191 if (
distance <= epsilon &&
match.coincidentFaceCount <
static_cast<int>(
match.coincidentFaces.size())) {
5192 for (
int seen = 0; seen <
match.coincidentFaceCount; ++seen) {
5193 if (
match.coincidentFaces[
static_cast<size_t>(seen)] == chord.surfaceIndex) {
5197 match.coincidentFaces[
static_cast<size_t>(
match.coincidentFaceCount++)] = chord.surfaceIndex;
5200 const int xCentre = cellOf(probe.
xCoord,
lower.xCoord);
5201 const int yCentre = cellOf(probe.
yCoord,
lower.yCoord);
5202 const int zCentre = cellOf(probe.
zCoord,
lower.zCoord);
5203 for (
int shell = 0; shell < gridDimension; ++shell) {
5205 const double shellReach = (shell - 1) * cellSize;
5206 if (shell > 0 && shellReach > std::max(
match.distance, maxBand)) {
5209 for (
int xCell = xCentre - shell; xCell <= xCentre + shell; ++xCell) {
5210 if (xCell < 0 || xCell >= gridDimension) {
5213 for (
int yCell = yCentre - shell; yCell <= yCentre + shell; ++yCell) {
5214 if (yCell < 0 || yCell >= gridDimension) {
5217 for (
int zCell = zCentre - shell; zCell <= zCentre + shell; ++zCell) {
5218 if (zCell < 0 || zCell >= gridDimension) {
5221 const bool onShell = std::abs(xCell - xCentre) == shell || std::abs(yCell - yCentre) == shell ||
5222 std::abs(zCell - zCentre) == shell;
5226 for (
const int chordIndex :
cells[cellIndex(xCell, yCell, zCell)]) {
5227 consider(chordIndex);
5236 report.rimRecords.reserve(rims.size());
5237 for (
size_t rimIndex = 0; rimIndex < rims.size(); ++rimIndex) {
5238 bool hasUnmatched =
false;
5239 bool hasNonManifold =
false;
5240 int sameDirectionVotes = 0;
5241 int oppositeDirectionVotes = 0;
5245 record.
closed = rims[rimIndex].closed;
5246 record.
chords =
static_cast<int>(chordRange[rimIndex].second - chordRange[rimIndex].first);
5247 for (
size_t chordIndex = chordRange[rimIndex].
first; chordIndex < chordRange[rimIndex].second; ++chordIndex) {
5248 const Chord& chord = chords[chordIndex];
5249 const Vec3 along = chord.end - chord.start;
5250 const double chordLength =
norm(along);
5251 report.totalRimLength += chordLength;
5252 record.
length += chordLength;
5253 const Vec3 probe = chord.start + along * 0.5;
5254 const Match
match = nearestOtherFace(probe, chord.surfaceIndex, chord.resolution);
5255 if (std::isfinite(
match.distance)) {
5263 if (
match.coincidentFaceCount > 1) {
5264 hasNonManifold =
true;
5266 if (!
match.withinBand) {
5267 hasUnmatched =
true;
5269 report.unmatchedRimLength += chordLength;
5273 const Chord& partner = chords[
static_cast<size_t>(
match.chordIndex)];
5274 if (
dot(along, partner.end - partner.start) < 0.) {
5275 ++oppositeDirectionVotes;
5277 ++sameDirectionVotes;
5280 if (hasNonManifold) {
5281 ++
report.nonManifoldRims;
5283 }
else if (hasUnmatched) {
5286 }
else if (sameDirectionVotes > oppositeDirectionVotes) {
5293 report.rimRecords.push_back(record);
5302 size_t surfacesPresent = 0;
5303 size_t surfacesStatingEdges = 0;
5309 if (!
surface->boundaryEdges().empty()) {
5310 ++surfacesStatingEdges;
5313 if (surfacesPresent == 0 || surfacesStatingEdges != surfacesPresent) {
5316 report.edgeIdentityAvailable =
true;
5323 std::map<uint32_t, Incidence> incidences;
5325 std::map<uint32_t, std::vector<int>> owners;
5326 for (
size_t surfaceIndex = 0; surfaceIndex <
surfaces.size(); ++surfaceIndex) {
5327 if (
surfaces[surfaceIndex] ==
nullptr) {
5330 for (
const auto&
ref :
surfaces[surfaceIndex]->boundaryEdges()) {
5331 Incidence& incidence = incidences[
ref.edgeId];
5332 if (
ref.degenerate) {
5333 ++incidence.degenerate;
5334 }
else if (
ref.reversed) {
5335 ++incidence.reversed;
5337 ++incidence.forward;
5339 owners[
ref.edgeId].push_back(
static_cast<int>(surfaceIndex));
5360 if (rank(candidate) > rank(
state)) {
5365 for (
const auto& [edgeId, incidence] : incidences) {
5367 if (incidence.degenerate > 0 && incidence.forward + incidence.reversed == 0) {
5368 ++
report.edgeDegenerateCount;
5371 const int total = incidence.forward + incidence.reversed;
5374 ++
report.edgeBoundaryCount;
5376 }
else if (total == 2) {
5377 if (incidence.forward == 1 && incidence.reversed == 1) {
5378 ++
report.edgeSharedCount;
5380 ++
report.edgeReversedCount;
5384 ++
report.edgeNonManifoldCount;
5388 for (
const int owner : owners[edgeId]) {
5389 worsen(faceState[
static_cast<size_t>(owner)],
state);
5394 report.closed = (
report.edgeBoundaryCount == 0) && (
report.edgeNonManifoldCount == 0);
5395 report.orientationConsistent = (
report.edgeReversedCount == 0);
5399 report.nonManifoldRims = 0;
5402 const RimState state = record.surfaceIndex >= 0 && record.surfaceIndex <
static_cast<int>(faceState.size())
5403 ? faceState[
static_cast<size_t>(record.surfaceIndex)]
5405 record.state =
state;
5408 ++
report.nonManifoldRims;
5427 double modelTolerance = 0.)
5432 using VertexKey = std::tuple<int64_t, int64_t, int64_t>;
5433 auto keyOf = [&](
const Vec3& point) {
5434 return VertexKey{quantize(point.xCoord), quantize(point.yCoord), quantize(point.zCoord)};
5437 std::vector<std::pair<Vec3, Vec3>> directedEdges;
5440 surface->appendDirectedEdges(directedEdges);
5446 std::map<std::pair<VertexKey, VertexKey>, std::pair<int, int>> edgeCounts;
5447 for (
const auto& directedEdge : directedEdges) {
5448 const VertexKey startKey = keyOf(directedEdge.first);
5449 const VertexKey endKey = keyOf(directedEdge.second);
5450 if (startKey == endKey) {
5453 const bool forward = startKey < endKey;
5454 const auto orderedKey = forward ? std::make_pair(startKey, endKey) : std::make_pair(endKey, startKey);
5455 auto& counts = edgeCounts[orderedKey];
5463 for (
const auto& [edgeKey, counts] : edgeCounts) {
5464 const int total = counts.first + counts.second;
5467 }
else if (total == 2) {
5468 if (counts.first != 1 || counts.second != 1) {
5472 ++
report.nonManifoldEdges;
5480 report.orientationConsistent = (
report.reversedRims == 0);
header::DataOrigin origin
std::vector< double > sum
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X coordinate
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events d
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by factor
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this parameter(fixed during CTF decoding)") AddOptionRTC(defaultZOffsetOverR
constexpr int p1()
constexpr to accelerate the coordinates changing
std::vector< SidecarEdge > edges
const Acts::Surface * surface
std::array< float, NCoordinates > derivative
std::vector< std::shared_ptr< Surface > > surfaces
Abstract analytic surface patch: one support surface plus its trim, with the kernels the navigation n...
void setBoundaryEdges(std::vector< BoundaryEdgeRef > refs)
virtual void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const =0
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
virtual bool containsPointOnSurface(const Vec3 &point) const =0
True if the 3D point lies on the trimmed patch within tolerance.
std::pair< Vec3, Vec3 > CoverBox
One axis-aligned cover box of the sub-patch BVH, as a (lower corner, upper corner) pair.
double parametricLengthSqAt(const Vec2 &uv, const Vec2 &delta) const
The 3D length squared spanned by a parametric displacement delta starting at uv.
virtual bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
virtual void conservativeBounds(Vec3 &lower, Vec3 &upper) const =0
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
virtual void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const =0
Append this patch's visualization triangulation (navigation must never depend on it).
virtual ~BoundedSurface()=default
virtual void appendRims(std::vector< SurfaceRim > &rims) const
Append the trim boundary as rims, one polyline per loop; the default chains appendDirectedEdges().
virtual double capacityContribution() const =0
Signed divergence-theorem contribution to the enclosed volume.
virtual void parametricMetric(const Vec2 &uv, double &gUU, double &gUV, double &gVV) const =0
The first fundamental form at uv, turning parametric displacements into 3D lengths; it varies over th...
virtual void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const =0
Append the 3D directed boundary edges of the patch, for solid-closure validation.
virtual void appendCoverBoxes(std::vector< CoverBox > &boxes) const
std::vector< BoundaryEdgeRef > mBoundaryEdges
virtual Vec3 normalAt(const Vec3 &point) const =0
Outward-oriented normal at (or nearest to) the given point.
virtual bool capacityIsExact() const =0
Whether capacityContribution() is analytically exact for this surface.
const std::vector< BoundaryEdgeRef > & boundaryEdges() const
virtual double distanceSqToPatch(const Vec3 &point) const =0
Squared distance from a 3D point to the trimmed patch (used for Safety).
A cone whose radius varies linearly with height, trimmed as the cylinder; one radius may be zero (an ...
double radiusAt(double height) const
double capacityContribution() const override
Divergence-theorem contribution over the (phi, h) rectangle; a wire trim uses the contour form,...
Vec3 pointAt(double phi, double height) const
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
bool heightInRange(double height) const
bool phiInSweep(double phi) const
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
double meanRadius() const
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
Vec3 normalAt(const Vec3 &point) const override
Outward-oriented normal at (or nearest to) the given point.
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double radiusAtMin, double radiusAtMax, double heightMin, double heightMax, double phiStart, double phiSweep, bool innerWall, const std::vector< Curve2D > &outerTrim, const std::vector< std::vector< Curve2D > > &innerTrims, std::string &errorMessage, double joinTolerance=kWireJoinTolerance)
Wire-trimmed overload: the scalar radii pin r(h); the wires in the (phi[rad], h[cm]) domain decide co...
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double radiusAtMin, double radiusAtMax, double heightMin, double heightMax, double phiStart, double phiSweep, bool innerWall, std::string &errorMessage)
double distanceSqToPatch(const Vec3 &point) const override
Distance to the patch: exact for the parametric rectangle, a lower bound for a wire trim.
void parametricMetric(const Vec2 &uv, double &gUU, double &gUV, double &gVV) const override
(u, v) = (phi[rad], h[cm]): the azimuthal scale is the local radius, and a step in h spans sqrt(1 + s...
void appendCoverBoxes(std::vector< CoverBox > &boxes) const override
Cover boxes: as for the cylinder, with the rim radii from the linear radius law.
Vec3 toLocal(const Vec3 &point) const
bool pointInTrim(double phi, double height, bool *boundary=nullptr) const
True if the (phi, h) point lies in the trim wire (phi unwrapped into the wire window).
A plane trimmed by curved (line/arc/B-spline) loops in an orthonormal frame: exact caps,...
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
Vec3 toGlobal(const Vec2 &point) const
double capacityContribution() const override
Signed divergence-theorem contribution to the enclosed volume.
double distanceSqToPatch(const Vec3 &point) const override
Squared distance from a 3D point to the trimmed patch (used for Safety).
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
Vec3 normalAt(const Vec3 &) const override
Outward-oriented normal at (or nearest to) the given point.
bool containsLocal(const Vec2 &point, bool *boundary=nullptr) const
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
Vec2 toLocal(const Vec3 &point) const
double planeDistance(const Vec3 &point) const
bool wasReoriented() const
True if the outer or any inner wire had to be re-oriented during initialization.
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
bool initialize(const Vec3 &surfaceOrigin, const Vec3 &surfaceAxisU, const Vec3 &surfaceAxisV, const std::vector< Curve2D > &outerCurves, const std::vector< std::vector< Curve2D > > &innerCurves, std::string &errorMessage, double joinTolerance=kWireJoinTolerance)
void parametricMetric(const Vec2 &, double &gUU, double &gUV, double &gVV) const override
A cylinder of given radius around an axis, trimmed to a (phi, h) rectangle or by curve wires; innerWa...
static bool makeFrame(const Vec3 &axis, const Vec3 &referenceAxisU, Vec3 &axisU, Vec3 &axisV, Vec3 &axisW, std::string &errorMessage)
bool heightInRange(double height) const
Vec3 pointAt(double phi, double height) const
Vec3 normalAt(const Vec3 &point) const override
Outward-oriented normal at (or nearest to) the given point.
void appendCoverBoxes(std::vector< CoverBox > &boxes) const override
Cover boxes: the sweep window in angular chunks, which holds every point that realises distanceSqToPa...
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
void parametricMetric(const Vec2 &, double &gUU, double &gUV, double &gVV) const override
(u, v) = (phi[rad], h[cm]): X_phi has length r and X_h is the unit axis.
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
bool pointInTrim(double phi, double height, bool *boundary=nullptr) const
True if the (phi, h) point lies in the trim wire (phi unwrapped into the wire window).
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double radius, double heightMin, double heightMax, double phiStart, double phiSweep, bool innerWall, std::string &errorMessage)
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
bool phiInSweep(double phi) const
Vec3 toLocal(const Vec3 &point) const
double distanceSqToPatch(const Vec3 &point) const override
Distance to the patch: exact for the parametric rectangle, a lower bound for a wire trim.
double capacityContribution() const override
Divergence-theorem contribution over the (phi, h) rectangle; a wire trim uses the contour form,...
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double radius, double heightMin, double heightMax, double phiStart, double phiSweep, bool innerWall, const std::vector< Curve2D > &outerTrim, const std::vector< std::vector< Curve2D > > &innerTrims, std::string &errorMessage, double joinTolerance=kWireJoinTolerance)
Wire-trimmed overload: the wires in the (phi[rad], h[cm]) domain decide containment; the window tight...
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
double distanceSqToPatch(const Vec3 &point) const override
Squared distance from a 3D point to the trimmed patch (used for Safety).
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
bool initialize(const Vec3 &surfaceOrigin, const Vec3 &surfaceAxisU, const Vec3 &surfaceAxisV, const std::vector< Vec2 > &outerWireVertices, const std::vector< std::vector< Vec2 > > &innerWireVertices, std::string &errorMessage)
Vec2 toLocal(const Vec3 &point) const
Vec3 toGlobal(const Vec2 &point) const
Vec3 normalAt(const Vec3 &) const override
Outward-oriented normal at (or nearest to) the given point.
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
double capacityContribution() const override
Signed divergence-theorem contribution to the enclosed volume.
double distanceSqToEdges(const Vec3 &point, const std::vector< Vec3 > &ring) const
bool wasReoriented() const
True if either the outer or any inner wire had to be re-oriented during initialization.
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
double planeDistance(const Vec3 &point) const
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
void parametricMetric(const Vec2 &, double &gUU, double &gUV, double &gVV) const override
Constant over the plane, with a cross term: the frame axes need be neither unit-length nor orthogonal...
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
bool containsLocal(const Vec2 &point, bool *boundary=nullptr) const
A sphere of given radius trimmed to a (theta, phi) rectangle or by curve wires; innerWall points the ...
void appendCoverBoxes(std::vector< CoverBox > &boxes) const override
Cover boxes: the whole sphere in (theta, phi) chunks, since distanceSqToPatch ignores the trim.
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
bool initialize(const Vec3 ¢er, const Vec3 &polarAxis, const Vec3 &referenceAxisU, double radius, double thetaMin, double thetaMax, double phiStart, double phiSweep, bool innerWall, std::string &errorMessage)
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
Vec3 pointAt(double theta, double phi) const
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
Vec3 normalAt(const Vec3 &point) const override
Outward-oriented normal at (or nearest to) the given point.
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
double distanceSqToPatch(const Vec3 &point) const override
Distance to the patch: exact inside the trim, else the full-sphere distance, a lower bound.
void parametricMetric(const Vec2 &uv, double &gUU, double &gUV, double &gVV) const override
(u, v) = (phi[rad], theta[rad]); gUU vanishes at either pole.
bool directionInTrim(const Vec3 &localPoint, bool *boundary=nullptr) const
bool pointInTrim(double phi, double theta, bool *boundary=nullptr) const
True if the (phi, theta) point lies in the trim wire (phi unwrapped into the wire window).
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
double capacityContribution() const override
Divergence-theorem contribution over the (theta, phi) rectangle; a wire trim uses the contour form in...
int thetaSegments() const
bool initialize(const Vec3 ¢er, const Vec3 &polarAxis, const Vec3 &referenceAxisU, double radius, double thetaMin, double thetaMax, double phiStart, double phiSweep, bool innerWall, const std::vector< Curve2D > &outerTrim, const std::vector< std::vector< Curve2D > > &innerTrims, std::string &errorMessage, double joinTolerance=kWireJoinTolerance)
Wire-trimmed overload: the wires in the (phi[rad], theta[rad]) domain decide containment; the window ...
Vec3 toLocal(const Vec3 &point) const
void appendDisplayMesh(std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles) const override
Append this patch's visualization triangulation (navigation must never depend on it).
bool containsPointOnSurface(const Vec3 &point) const override
True if the 3D point lies on the trimmed patch within tolerance.
bool tubeInSweep(double phiTube) const
Vec3 pointAt(double phiRing, double phiTube) const
void appendCoverBoxes(std::vector< CoverBox > &boxes) const override
Cover boxes: the full torus in angular chunks, since the meridian projection ignores the trim; a spin...
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double majorRadius, double minorRadius, double phiStart, double phiSweep, double tubeStart, double tubeSweep, bool innerWall, std::string &errorMessage)
bool fullTubeSweep() const
bool initialize(const Vec3 ¢erPoint, const Vec3 &axis, const Vec3 &referenceAxisU, double majorRadius, double minorRadius, double phiStart, double phiSweep, double tubeStart, double tubeSweep, bool innerWall, const std::vector< Curve2D > &outerTrim, const std::vector< std::vector< Curve2D > > &innerTrims, std::string &errorMessage, double joinTolerance=kWireJoinTolerance)
Wire-trimmed overload: the wires in the (phiRing, phiTube) domain decide containment; a trim wrapping...
void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const override
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
void conservativeBounds(Vec3 &lower, Vec3 &upper) const override
Accumulate a conservative axis-aligned bounding box of the trimmed patch.
bool capacityIsExact() const override
Whether capacityContribution() is analytically exact for this surface.
Vec3 localNormal(const Vec3 &localPoint) const
Unit outward normal (pointing away from the tube spine) from a local surface point.
Vec3 normalAt(const Vec3 &point) const override
Outward-oriented normal at (or nearest to) the given point.
Vec3 toLocal(const Vec3 &point) const
double distanceSqToPatch(const Vec3 &point) const override
Distance to the patch: exact for the full torus by the meridian distance, a lower bound for a trimmed...
double capacityContribution() const override
Divergence-theorem contribution over the (phiRing, phiTube) rectangle; a wire trim uses the contour f...
bool sampleTrimCurve(size_t index, std::vector< Vec3 > &samples) const override
Sample trim curve index into 3D, in construction order; false when this face has no such curve.
void appendDirectedEdges(std::vector< std::pair< Vec3, Vec3 > > &edges) const override
Append the 3D directed boundary edges of the patch, for solid-closure validation.
bool pointInTrim(double phiRing, double phiTube, bool *boundary=nullptr) const
Whether (phiRing, phiTube) lies in the trim wire, both angles unwrapped into their windows.
void parametricMetric(const Vec2 &uv, double &gUU, double &gUV, double &gVV) const override
(u, v) = (phiRing[rad], phiTube[rad]): the tube scale is r, the ring scale the distance from the axis...
bool fullRingSweep() const
bool ringInSweep(double phiRing) const
bool match(const std::vector< std::string > &queries, const char *pattern)
GLsizei const GLuint const GLfloat * weights
GLint GLsizei GLsizei height
GLuint GLuint GLfloat weight
GLboolean GLboolean GLboolean b
GLsizei GLsizei GLfloat distance
GLsizei const GLfloat * value
GLuint GLsizei GLsizei * length
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLint GLenum GLboolean normalized
GLint GLint GLsizei GLsizei GLsizei depth
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
GLboolean GLboolean GLboolean GLboolean a
GLsizei const GLint * box
GLdouble GLdouble GLdouble z
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
void report(gsl::span< o2::InteractionTimeRecord > irs, int threshold, bool verbose)
bool pointInTriangle(const Vec2 &point, const Vec2 &firstVertex, const Vec2 &secondVertex, const Vec2 &thirdVertex)
bool sampleTrimCurveOfCurveWires(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, size_t index, const MapUV &mapUV, std::vector< Vec3 > &samples)
Sample input curve index of a curve-wire trim into 3D through mapUV; false when out of range or not t...
void applyEdgeIdentityClosure(const std::vector< std::unique_ptr< BoundedSurface > > &surfaces, ClosureReport &report)
double trimLengthFloor(const ParametricMetric &metric, const Vec2 &uv)
kTolerance as a parametric separation at uv: the floor of every trim's on-boundary band.
void assembleRims(const std::vector< std::pair< Vec3, Vec3 > > &edges, std::vector< SurfaceRim > &rims)
Chain a face's directed chords into rims by matching endpoints within kTolerance, appending them to r...
constexpr double wireJoinToleranceFor(double modelTolerance)
The wire-join band for a model with a declared tolerance: that tolerance when looser than kWireJoinTo...
CurveKind
Kind of a 2D trimmed boundary curve.
@ Line
straight line segment
@ BSpline
clamped (rational) B-spline curve
double distanceSq(const Vec2 &firstPoint, const Vec2 &secondPoint)
double contourIntegralAlongCurve(const Curve2D &curve, const Antiderivative &antiderivative, double from, double to)
double ringSignedArea(const std::vector< Vec2 > &points, const std::vector< int > &ring)
Signed area of the ring of indices ring into points; positive when it winds counter-clockwise.
bool segmentsTouch(const Vec2 &firstStart, const Vec2 &firstEnd, const Vec2 &secondStart, const Vec2 &secondEnd)
Whether segments firstStart - firstEnd and secondStart - secondEnd cross or touch within kTolerance.
void assignComponent(Vec3 &vector, int dimension, double value)
void coneParametricMetric(double radiusAtHeight, double slope, double &gUU, double &gUV, double &gVV)
QuarticBranch
Which of solveQuarticReal's branches produced its roots, for the tests.
@ NotAQuartic
the leading coefficient vanishes; no roots are produced
@ Resolvent
Ferrari's general branch, through the resolvent cubic.
@ Biquadratic
the depressed quartic's odd term is zero, so y^4 + p y^2 + r = 0 is solved directly
@ Reversed
well-formed but re-oriented to match its role (simple, logged repair)
@ Valid
well-formed and already correctly oriented
@ DegenerateVertex
a non-adjacent vertex coincided (self-touching / pinched loop)
@ NonFinite
a vertex/edge contained a non-finite coordinate
@ Open
an explicit edge list did not form a closed loop
@ TooFewVertices
fewer than three distinct vertices after cleanup
@ ZeroArea
the loop encloses no area
bool curveTrimContains(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, const Vec2 &point, bool *boundary=nullptr, const ParametricMetric &metric={})
Whether a parametric point is in a curve-wire trim (outer loop minus holes); boundary reports an on-b...
void measureSharedEdgeDeviation(const std::vector< std::unique_ptr< BoundedSurface > > &surfaces, ClosureReport &report)
Measure the Hausdorff distance between the two faces of each shared edge into report; it decides noth...
Vec3 operator*(const Vec3 &vector, double scale)
constexpr double kBSplineFlatness
Chord flatness of the adaptive B-spline sampler, in the curve's parametric units; a B-spline trim is ...
constexpr double kContourMaxSpanU
WireClassification
Classification of a parametric point against a closed wire.
void planeParametricMetric(const Vec3 &axisU, const Vec3 &axisV, double &gUU, double &gUV, double &gVV)
Vec3 operator-(const Vec3 &firstVector, const Vec3 &secondVector)
double pointSegmentDistanceSq(const Vec2 &point, const Vec2 &segmentStart, const Vec2 &segmentEnd)
void appendCurveTrimMesh(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, const MapUV &mapUV, std::vector< Vec3 > &vertices, std::vector< std::array< int, 3 > > &triangles)
Append the display triangulation of a wire-trimmed quadric patch: the sampled loops with the holes br...
double dot(const Vec3 &firstVector, const Vec3 &secondVector)
bool leavesIntoMaterial(const Vec2 &previous, const Vec2 &vertex, const Vec2 &next, const Vec2 &direction)
Whether direction leaves ring corner vertex into the material, which lies left of previous -> vertex ...
Vec3 operator+(const Vec3 &firstVector, const Vec3 &secondVector)
std::vector< int > orientedRing(const std::vector< Vec2 > &points, int first, int count, bool counterClockwise)
The indices first .. first + count - 1 as a ring wound as counterClockwise asks.
constexpr double kWireJoinTolerance
Wire-closure tolerance, a 3D length in cm through the surface metric: the CAD extractor's endpoint pr...
double integrateOverCurveTrim(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, const Integrand &integrand, int samplesPerAxis=128)
Midpoint-rule integral of integrand over the trimmed region; kept as the independent check of the con...
bool sameIntersection(double firstDistance, double secondDistance)
void torusParametricMetric(double majorRadius, double minorRadius, double phiTube, double &gUU, double &gUV, double &gVV)
Torus, (u, v) = (phiRing[rad], phiTube[rad]). The ring scale runs from R - r to R + r.
std::vector< int > bridgeHoles(const std::vector< Vec2 > &outer, const std::vector< std::vector< Vec2 > > &holeLoops, std::vector< Vec2 > &points)
The ring of outer with each hole bridged in at its nearest visible vertex, indexing points,...
void cylinderParametricMetric(double radius, double &gUU, double &gUV, double &gVV)
Cylinder, (u, v) = (phi[rad], h[cm]).
double sinusoidMaximum(double a, double b, double t0, double t1)
double integrateOverCurveTrimByParts(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, const Antiderivative &antiderivative)
Green's theorem over a wire-trimmed patch: the double integral of f is the contour integral of F dv,...
void sphereParametricMetric(double radius, double theta, double &gUU, double &gUV, double &gVV)
constexpr int kContourQuadratureOrder
Gauss-Legendre nodes per contour sub-interval, and the widest u span one sub-interval covers.
constexpr double kRayTolerance
minimum positive ray parameter t
bool coversRing(const std::vector< Vec2 > &points, const std::vector< int > &ring, const std::vector< std::array< int, 3 > > &triangles)
Whether triangles cover the area enclosed by ring.
bool sampleTrimCurveOfSurfaceWires(const SurfaceWire &outerWire, const std::vector< SurfaceWire > &innerWires, size_t index, const MapUV &mapUV, std::vector< Vec3 > &samples)
The same for a polygon (vertex-ring) trim, whose curves are all straight segments.
void gaussLegendre(int n, std::vector< double > &nodes, std::vector< double > &weights)
The n-point Gauss-Legendre nodes and weights on [-1, 1], by Newton iteration on P_n.
void measureRimClosure(const std::vector< std::unique_ptr< BoundedSurface > > &surfaces, double epsilon, ClosureReport &report)
Measure the face-to-face gaps of surfaces as curves into report, probing chord midpoints against othe...
ParametricMetric parametricMetricOf(const Surface &surface)
std::vector< Vec2 > sampleCurveWireByU(const CurveWire &wire, int segmentsPerTurn=kArcSamples)
Sub-sample a curve-wire loop so its u span is chorded at segmentsPerTurn per turn,...
std::vector< std::array< int, 3 > > triangulateRing(const std::vector< Vec2 > &points, std::vector< int > remainingIndices)
Ear-clipping triangulation of a ring of indices into points; an index may repeat, as at the two ends ...
double unwrapAngleInto(double angle, double uMin, double uMax)
Shift angle by whole turns to lie as close as possible to the window [uMin, uMax].
std::vector< std::array< int, 3 > > triangulateRingByShortestChord(const std::vector< Vec2 > &uvPoints, const std::vector< Vec3 > &points, std::vector< int > remainingIndices)
Ear-clipping of a ring into uvPoints that clips the ear with the shortest 3D diagonal first,...
int coverChunkCount(double span)
bool buildCurveTrim(const std::vector< Curve2D > &outerTrim, const std::vector< std::vector< Curve2D > > &innerTrims, CurveWire &outerWire, std::vector< CurveWire > &innerWires, Vec2 &lower, Vec2 &upper, std::string &errorMessage, const ParametricMetric &metric={}, double joinTolerance=kWireJoinTolerance)
Build validated outer and inner trim wires and the outer loop's parametric bounds; rejects a trim wid...
constexpr double kQuarticEpsilon
Zero threshold of solveQuarticReal's branch tests, in machine epsilons relative to the normalised ter...
constexpr double kBVHBoxTolerance
Widening of the BVH leaf boxes before the outward float rounding; it dominates every navigation lengt...
void appendCurveTrimEdges(const CurveWire &outerWire, const std::vector< CurveWire > &innerWires, const MapUV &mapUV, double orientationSign, std::vector< std::pair< Vec3, Vec3 > > &edges)
Append the directed 3D boundary edges of a wire-trimmed quadric patch; a negative orientationSign rev...
ClosureReport validateClosure(const std::vector< std::unique_ptr< BoundedSurface > > &surfaces, double modelTolerance=0.)
Validate closure and orientation of surfaces by half-edges, measure the rims, and count edge identiti...
constexpr double kToleranceSq
constexpr double kAreaTolerance
degenerate (zero) parametric area
@ Reversed
matched, but the partner traverses the shared curve the same way
@ NonManifold
some chord has two or more other faces within the declared tolerance
@ Matched
every chord has another face within its match band, traversed the other way
@ Boundary
some chord has no other face within its match band
void sinusoidRange(double a, double b, double t0, double t1, double &minimum, double &maximum)
Exact range of a cos(t) + b sin(t) over [t0, t1], at most a turn: the endpoint values,...
double normSq(const Vec3 &vector)
constexpr int kSharedEdgeSamples
Samples per trim curve when measuring a shared edge's deviation; it never enters a verdict.
double component(const Vec3 &vector, int dimension)
constexpr double kCoverChunkAngle
Widest angular span of one cover box: pi/4, eight boxes per full turn.
QuarticRoots solveQuarticReal(double a4, double a3, double a2, double a1, double a0, QuarticBranch *takenBranch=nullptr)
bool finite(const Vec2 &point)
constexpr double kBSplineFlatnessSq
Vec3 cross(const Vec3 &firstVector, const Vec3 &secondVector)
constexpr double kClosureQuantum
constexpr double kRimMatchTolerance
Rim-matching distance in cm when the model states no tolerance: the extractor precision,...
double angularTolerance(double radius)
Angular tolerance equivalent to a kTolerance arc length at the given radius.
const char * wireStatusMessage(WireStatus status)
Human-readable description of a wire status, for logging.
constexpr double kTolerance
generic length tolerance
double cross2D(const Vec2 &firstVector, const Vec2 &secondVector)
double sinusoidMinimum(double a, double b, double t0, double t1)
void appendArcBandCoverBoxes(const Vec3 ¢er, const Vec3 &axisU, const Vec3 &axisV, const Vec3 &axisW, double phiStart, double phiSweep, double heightMin, double heightMax, double radiusAtMin, double radiusAtMax, std::vector< BoundedSurface::CoverBox > &boxes)
Cover boxes of a band of revolution between two rim circles: the phi window in chunks,...
bool angleInSweepRange(double angle, double start, double sweep, double tolerance)
constexpr int kArcSamples
Chords per full-circle arc for display and rims, shared by all surfaces so shared rims match; divisib...
constexpr double kIntersectionTolerance
clustering of near-equal intersections
double parametricLengthSq(double gUU, double gUV, double gVV, const Vec2 &delta)
The 3D length squared of parametric displacement delta under the first fundamental form (gUU,...
int solveDepressedCubic(double coeffP, double coeffQ, std::array< double, 3 > &roots)
double norm(const Vec3 &vector)
uint32_t edgeId
index into the model's edge table; identity, not a coordinate
bool anchored
Whether trim curve i exists to sample for edge i; false for a parametric-rectangle trim.
bool reversed
this face runs against the edge's own direction
Whether a set of bounded surfaces forms a closed, consistently oriented 2-manifold,...
double rimChordResolution
int sharedEdgesUnmeasured
int rims
total number of trim loops over all faces
int reversedEdges
edges shared by two faces in the same direction
int boundaryEdges
edges present on only one face (e.g. a missing face)
int maxSharedEdgeDeviationFaces[2]
between which two faces
bool orientationConsistent
shared edges are traversed in opposite directions
double totalRimLength
summed length in cm of every face's trim boundary
int edgeSharedCount
appearing exactly twice, opposite sense: a properly shared edge
uint32_t maxSharedEdgeDeviationEdge
which edge that was
int edgeBoundaryCount
appearing once: a face is missing on the other side
double rimEpsilon
the declared matching tolerance, in cm
std::vector< RimRecord > rimRecords
double maxSharedEdgeDeviation
Largest Hausdorff distance between two faces' realisations of a shared edge, in cm; a measurement,...
int nonManifoldEdges
edges shared by more than two faces
int nonManifoldRims
some chord has two or more other faces within rimEpsilon
bool closed
every boundary edge is shared by exactly two faces
double unmatchedRimLength
how much of it has no other face within the match band, cm
bool edgeIdentityAvailable
int edgeReversedCount
appearing exactly twice, but with the same sense
int sharedEdgesMeasured
shared edges both of whose faces could be sampled
double signedVolume
divergence-theorem volume; positive if normals point out
int edgeNonManifoldCount
appearing three or more times
Vec3 maxSharedEdgeDeviationPoint
and where on it
int edgeIncidences
distinct edge identifiers seen over all faces
One trimmed boundary curve in a surface's (u, v) domain: a line segment, a circular arc or a clamped ...
Vec2 tangentAt(double parameter) const
Unit tangent at parameter parameter, pointing in the direction of increasing parameter.
std::vector< Vec2 > poles
static Curve2D makeBSpline(int splineDegree, std::vector< Vec2 > splinePoles, std::vector< double > splineWeights, std::vector< double > splineKnots)
bool bsplineRational() const
True if the curve carries non-unit weights (a rational B-spline).
bool angleInSweep(double angle) const
True if angle lies within the arc's angular sweep (accounting for direction and wrap).
static Curve2D makeLine(const Vec2 &start, const Vec2 &end)
int rightwardCrossings(const Vec2 &point, const Vec2 &canonicalStart, const Vec2 &canonicalEnd) const
Rightward crossings of a horizontal ray from point, with the caller's canonical endpoints so that sea...
const std::vector< Vec2 > & bsplineSamples() const
The flattened polyline in bsplineCache, computed here if the wire has not filled it.
void bsplineBasis(int span, double knotValue, std::vector< double > &basis, std::vector< double > &basisDeriv) const
Non-zero degree-p basis functions and first derivatives at knotValue in span (The NURBS Book,...
void bsplineSampleRecursive(double t0, double t1, const Vec2 &p0, const Vec2 &p1, double flatnessSq, int depth, std::vector< Vec2 > &samples) const
static Curve2D makeArc(const Vec2 &arcCenter, double arcRadius, double arcStartAngle, double arcEndAngle)
Vec2 derivativeAt(double parameter) const
dC/dt at parameter in [0, 1], unnormalised; tangentAt() is it normalised.
void bsplineSampleInto(std::vector< Vec2 > &samples, double flatnessSq=kBSplineFlatnessSq, int maxDepth=16) const
Adaptively sample the B-spline into an on-curve polyline, subdividing until each chord is flat to sqr...
Vec2 lineStart
line: start point (unused for arcs)
bool bsplineIsClamped() const
True when the knot vector is clamped, so the curve interpolates its first and last pole.
double uVariation(double from, double to) const
An upper bound on how far u travels along the curve between from and to.
Vec2 center
arc: circle centre (unused for lines)
bool spansInteriorKnot(double lowT, double highT) const
Whether a knot lies strictly inside (lowT, highT); such an interval is never called flat.
void extendBounds(Vec2 &lower, Vec2 &upper) const
Accumulate this curve's exact extent into a parametric axis-aligned bounding box.
Vec2 closestPoint(const Vec2 &point, double ¶meter) const
Closest point on the curve to point, returning the clamped parameter in parameter.
Vec2 lineEnd
line: end point (unused for arcs)
void reverseInPlace()
Reverse the curve's direction in place (start <-> end), keeping the same geometric image.
double endAngle
arc: end angle [rad] (sweep = endAngle - startAngle)
std::vector< double > knots
bool bsplineBandOrCrossings(const Vec2 &point, double bandSq, int &crossings) const
void setCanonicalEndpoints(const Vec2 &start, const Vec2 &end)
Vec2 pointAtAngle(double angle) const
std::vector< Vec2 > bsplineCache
The flattened on-curve polyline, both ends included; CurveWire::initialize fills it and reversing cle...
int bsplineSpan(double knotValue) const
Knot span index of parameter knotValue for the clamped knot vector.
bool hasCanonicalEndpoints
double startAngle
arc: start angle [rad]
Vec2 pointAt(double parameter) const
Point at curve parameter parameter in [0, 1] (0 at the start, 1 at the end).
static Curve2D makeCircle(const Vec2 &arcCenter, double arcRadius, bool clockwise=false)
Full circle as one arc curve (counter-clockwise unless clockwise is set).
Vec2 bsplinePointAt(double parameter) const
B-spline point at curve parameter parameter in [0, 1].
double signedAreaContribution() const
void extendTightBounds(Vec2 &lower, Vec2 &upper) const
As extendBounds, measured on the curve: a B-spline contributes its sampled polyline,...
std::vector< double > weights
void includeAnalyticExtremes(const Include &include) const
Endpoints plus an arc's axis-extreme points inside the sweep: the exact extent of a line or an arc.
double radius
arc: circle radius
double angleParameter(double angle) const
Map an angle known to lie within the sweep to a clamped parameter in [0, 1].
double representationTolerance() const
How far this curve's representation can sit from the curve, in parametric units: kBSplineFlatness for...
void appendInteriorKnots(double from, double to, std::vector< double > &breakpoints) const
Append the interior knots in (from, to), in the curve's [0, 1] parameter; none for a line or an arc.
double distanceSq(const Vec2 &point) const
Squared distance from point to the curve.
void bsplineEval(double knotValue, Vec2 &pointOut, Vec2 &derivativeOut) const
One closed, oriented boundary loop of Curve2D segments: outer loops wind counter-clockwise,...
double representationTolerance() const
The widest gap between the loop's representation and its boundary, in parametric units; 0 for lines a...
WireClassification classify(const Vec2 &point, const ParametricMetric &metric={}) const
metric only sizes the on-boundary band; the winding count is topological.
void fillBSplineCaches() const
Fill every B-spline's polyline cache now, so that const navigation queries only read it.
int storedIndexOfSource(int inputIndex) const
The stored curve that came from input curve inputIndex, or -1 if there is none.
double mRepresentationTolerance
The largest representationTolerance() over the curves, fixed when the curves are set.
std::vector< int > sourceCurve
For each stored curve its input index; reverse() is the only reordering, and sidecar v3 edge identiti...
void tightParametricBounds(Vec2 &lower, Vec2 &upper) const
Add the loop's extent measured on the curves to a parametric bounding box; use it to reject a wire as...
void parametricBounds(Vec2 &lower, Vec2 &upper) const
Add the loop's conservative extent, a B-spline's pole hull included, to a parametric bounding box.
std::vector< Vec2 > sampledBoundary(int segmentsPerArc=kArcSamples) const
void reverse()
Reverse the loop orientation in place (order and per-curve direction).
std::vector< Curve2D > curves
WireClassification classify(const Vec2 &point, double lengthFloor) const
Classify a point against the loop with band floor lengthFloor: Boundary within the band,...
bool initialize(const std::vector< Curve2D > &inputCurves, WireRole wireRole, WireStatus &status, const ParametricMetric &metric={}, double joinTolerance=kWireJoinTolerance)
Build and validate the wire from an ordered closed list of curves, joining within joinTolerance throu...
double boundaryBand(double lengthFloor) const
double signedArea() const
Exact signed area enclosed by the loop (positive when counter-clockwise).
How a wire converts a parametric separation into a 3D length: the owning surface's first fundamental ...
double maxScale(const Vec2 &uv) const
The largest 3D length a unit parametric displacement spans at uv: the square root of the larger eigen...
double distanceSq(const Vec2 &from, const Vec2 &to) const
The 3D distance squared between two nearby parametric points, with the form evaluated at from.
void(*)(const void *context, const Vec2 &uv, double &gUU, double &gUV, double &gVV) Evaluate
double lengthSq(const Vec2 &uv, const Vec2 &delta) const
The 3D length squared spanned by the parametric displacement delta starting at uv.
The real roots of a quartic: at most four, held inline.
double operator[](size_t index) const
void push_back(double root)
const double * begin() const
const double * end() const
One ray/surface intersection: the ray parameter and the outward normal; a quadric patch can give seve...
bool onTrimBoundary
The hit lies within the trim's on-boundary band, so its inside/outside side is a tie-break,...
One trim loop of one face as measureRimClosure saw it, naming the rim and its worst chord.
int rimIndexOnSurface
which trim loop of that face, in the order the face emits them
bool closed
the polyline returns to its own first point
double maxIsolation
Largest distance from a chord midpoint of this rim to another face's chord, and where: how alone the ...
int maxIsolationFace
the face owning the nearest chord there, or -1 if there was none
int surfaceIndex
the owning face's index in the solid's surface list
double length
summed chord length, cm
int unmatchedChords
of them, how many found no other face within their match band
One straight line segment of a polygon wire, in a surface's parametric (u, v) domain.
double distanceSq(const Vec2 &point) const
Squared distance from a parametric point to this edge.
void extendBounds(Vec2 &lower, Vec2 &upper) const
Accumulate the edge endpoints into a parametric axis-aligned bounding box.
Vec2 closestPoint(const Vec2 &point, double ¶meter) const
One trim loop of one face as an ordered 3D polyline, compared with other faces' rims as a curve.
std::vector< Vec3 > points
consecutive samples; a closed rim does not repeat the first point
int surfaceIndex
index of the owning face in the solid's surface list
bool closed
the polyline returns to its own first point
One closed, oriented polygon loop in a surface's parametric domain: outer loops wind counter-clockwis...
double signedArea() const
std::vector< int > sourceEdge
For each stored segment its input segment, or -1 once a vertex was dropped; sidecar v3 edge identitie...
bool initializeFromEdges(const std::vector< SurfaceEdge > &edges, WireRole wireRole, WireStatus &status, const ParametricMetric &metric={}, double joinTolerance=kWireJoinTolerance)
Build and validate the wire from an ordered edge list, joining within joinTolerance through metric,...
SurfaceEdge edge(int index) const
int storedIndexOfSource(int inputIndex) const
The stored segment that came from input segment inputIndex, or -1 if there is none.
std::vector< Vec2 > vertices
bool initialize(const std::vector< Vec2 > &inputVertices, WireRole wireRole, WireStatus &status, const ParametricMetric &metric={})
Build and validate the wire from an implicitly closed vertex ring; metric turns separations into 3D l...
WireClassification classify(const Vec2 &point, const ParametricMetric &metric={}) const
metric sizes the band only: a polygon is exact, so its band is the length floor.
WireClassification classify(const Vec2 &point, double band) const
Classify against the polygon with an on-boundary half-width of band, in parametric units.
std::vector< Vec2 > sampledBoundary() const
The de-duplicated vertex ring, closed back to its first vertex.
void parametricBounds(Vec2 &lower, Vec2 &upper) const
A 2D point/vector in a surface's parametric (u, v) domain.
A 3D point/vector in the solid's local frame.
std::vector< Cell > cells